Let's look at the "calling" of functions first.
In C, the arguments to a function are typically "by value".  Below, after calling sqrt(x), the value x is not changed for sqrt() received a copy of the value x, thus never gaining an ability to affect x.
y = sqrt(x);
// x did not change
printf("%f is the square root of %f\n", y, x);
Array parameters to a function appear to be "by reference".  After fgets() is called, buf is expected to be altered.  fgets() did not receive a copy of, but a "reference" to the char array.  Thus fgets() could affect the contents of the array.
char buf[80] = { 0 };  // array 80 of char
printf("Before <%s>\n", buf);   // "<>"
fgets(buf, sizeof buf, stdin);
printf("After <%s>\n", buf);    // "<Hello World!>"
Let's now look at the "receiving" part: the function.    
Function parameters of type double, int, struct types are received by value.  The value of f here is the copy of the x above.  Any changes made to f do not affect the x in the calling code.
double sqrt(double f) {
  double y;
  ... // lots of code
  return y;
}
Now comes the tricky bit and this is where there is lots of discussion in C.   
The parameter s in fgets() below was not assigned the buf above (array 80 of char), instead s was assigned the pointer type derived from buf: address of the first element of an buf.  By knowing the address of the array elements of buf (via s), fgets() affects what was printed above. 
char *fgets(char * restrict s, int n, FILE * restrict stream) {
  // code
  s[i] = ThisAndThat();
  // code
  return s;
}
Let's now see how fgets() otherwise might be called:
char *p = malloc(80);
*p = '\0';
printf("Before <%s>\n", p);   // "<>"
fgets(p, 80, stdin); 
printf("After <%s>\n", p);    // "<Hello World!>"
In this 2nd example, p is a "pointer to char".  It is passed by value to fgets().  fgets() received, via s, a copy of p.  
The central idea: fgets() does not know if it received an "address of the first element of an array" or a copy of a  "pointer to char".  In both cases, it treats s as a "pointer to char".
Let's assume fgets() did the following.  Before s being changed, it affects the data pointed to by s.  Then s, the pointer, changed.  s is a local variable and this assignment does not change the calling routines variables buf nor p.
char *fgets(char * restrict s, int n, FILE * restrict stream) {
  // lots of code
  s[0] = '\0';
  // more code
  s = NULL;
  return s;
}
Note: There are other issues such as passing functions as parameters and encapsulating arrays in structures to consider.