Writing a function#
#include <stdio.h>
int square(int x) {
return x * x;
}
int main(void) {
printf("%d\n", square(7)); // 49
return 0;
}
A function has a return type (int), a name, parameters with their types, and a body. return hands back a value and ends the function. A function that returns nothing has the return type void:
void greet(const char *name) {
printf("Hello, %s\n", name);
}
Order and prototypes#
C reads a file top to bottom, so a function must be declared before it is used. Either define it above main, or put a prototype, its first line followed by a semicolon, at the top:
int square(int x); // prototype: "this exists, defined later"
int main(void) {
printf("%d\n", square(7));
return 0;
}
int square(int x) {
return x * x;
}
Arguments are copies#
C passes every argument by value: the function gets its own copy. Changing it inside the function does not affect the caller:
void reset(int x) {
x = 0; // changes the copy only
}
int main(void) {
int n = 5;
reset(n);
printf("%d\n", n); // still 5
}
Pointers: letting a function change a variable#
A pointer holds the address of a variable. &n is "the address of n", and *p is "the variable p points at":
void reset(int *p) {
*p = 0; // change the variable at that address
}
int main(void) {
int n = 5;
reset(&n); // pass n's address
printf("%d\n", n); // 0
}
This is exactly why scanf needs &: it has to write into your variable.
Pointers are also how a function returns more than one result:
void min_max(const int *nums, int n, int *lo, int *hi) {
*lo = nums[0];
*hi = nums[0];
for (int i = 1; i < n; i++) {
if (nums[i] < *lo) *lo = nums[i];
if (nums[i] > *hi) *hi = nums[i];
}
}
// called as: int lo, hi; min_max(nums, n, &lo, &hi);
const int *nums promises the function will not change the array, and the compiler holds it to that.
Scope#
A variable declared inside a function, or inside any { } block, exists only there. Two functions can each have a variable called i without interfering.
Recursion#
A function may call itself, as long as each call works on a smaller problem and there is a case small enough to answer directly:
long long factorial(int n) {
if (n <= 1) {
return 1; // the base case
}
return n * factorial(n - 1);
}
Without the base case it calls itself until the program runs out of stack memory and crashes.
Key points#
- A function declares its return type and parameter types;
voidreturns nothing. - Define functions before use, or declare a prototype.
- Arguments are copies; to change a caller's variable, pass its address and use
*. conston a pointer parameter promises not to change what it points at.- Recursion needs a base case.
Next: arrays, the last of the basics.