We began the lecture with a discussion of the conditional and looping statements that C has available (descriptions are available in K&R Chapter 3):
if ( ) { }
if ( ) { } else { }
switch (int-valued expression) { case int-valued constant : stmt; stmt; break; case next-int-valued constant : stmt; stmt; break; ... default : stmt; stmt; }
for (initialization; termination; update) { }
while (condition) { }
do stmt; while (condition);
Every variable in C has 3 properties:
Since a C programmer may need to store a variable's address for later use, C provides a data type called the pointer type. The pointer type allows us to declare variables that are meant to hold the address of some other variable. The variable holding the address is called a pointer.Declaring a pointer variable is done as follows:
int *pi; /* creates a 4-byte (32-bit) variable whose data type is pointer to int */ int i; /* an int var */ pi = &i; /* pi now holds i's address - i.e., it points to i */
A pointer variable is itself a variable and its value can change. A pointer can hold only one of 3 values:
Pointer variables are NOT the data type of the variable they point to (rather, they are a pointer to a variable of that type). From above, the data type of pi is pointer to int NOT int and the name of the (above mentioned) pointer variable is pi NOT *pi.
char *pc; /* pc is type: pointer to char */ float *pf; /* pf is type: pointer to float */ double *pd; /* pd is type: pointer to double */
int* pi; /* variable of type pointer to int */ /* note the * can go next to the type name or next to the variable name */pi is a pointer variable that can only hold addresses of integer variables. You can obtain the address of any variable by using the & (address-of) operator. We've already seen the address-of operator in the context of input - i.e., scanf() - where we needed the address of the input variable in order to be able to store the new value being entered from the keyboard into the variable.
int i; /* plain old int var */ pi = &i; /* pi now contains the address of variable i */We can use pointer variable pi to manipulate the contents of the int variable i! To do so we must dereference pi as follows:
*pi = 15; /* i now contains 15. */ printf("value pointed to by pi is: %d\n", *pi); /* deref's pi and prints 15 */
The expression *p can be thought of as: the value at the address which is stored in pi.
The expression *pi = 15; can be thought of as: look inside the pi variable. pi will have an address in there for you. Go to that address and overwrite the contents of the memory at that address with the value 15.
Using pi with the * in front of it - like we did above - is called dereferencing the pointer variable. Dereferencing a pointer variable means following that pointer to refer to some other location in memory. NOTE: I can ONLY assign a value, like 15, to a dereferenced pointer, like *pi, if the pointer is pointing to valid (allocated) memory! That is, if I hadn't assigned i's address to pi prior to the assignment of 15 to *pi, my code would fail miserably!
Pointers are a strict data type - you should not assign the address of a variable of any other data type into a pointer of a different type. i.e., we should not do this:
double x = 10; int *pi = &x; /* Generates Compiler warning. */One more thing - if you dereference a pointer containing NULL your program will halt.
C (like Java) has no reference parameters (although C++ does). Thus in C we are always passing arguments by value and it is impossible to modify the actual parameters in the call when you pass them to a function. Furthermore it helps to remember that incoming arguments to a function should really be thought of as local variables in the function and those local vars get a copy of the values passed from their caller. This is why we need pointers.
Pointers are the only mechanism C gives us to modify data declared in one scope using code written in another scope. In other words: If data is declared in function1(...) and we want to write code in function2(...) that modifies the data in function1(...), then we must pass the addresses of the variables we want to change. The calling function sends in addresses and the receiving function must declare those incoming args as pointers. All function2(...) has to do to modify the data in function1(...) is to dereference the pointers sent in.
The last program we looked at examined the main variables, argc and argv. The variable, argc, keeps track of how many command-line arguments are provided; argv stores those arguments as strings.
We examined
All this talk about functions and parameters allows me to introduce another topic - function prototypes.
As we look again at args.c, note the use of a function prototype above main. Without this "function declaration", if you will, every function would have to be defined before use as C is a one-pass compilation process. This would place a lot of burden on the programmer (to remember who calls who) and prevents a possibly more natural grouping of functions. Using function prototypes frees the programmer to place the function definitions anywhere in the file and in any order.