[Phy 405/905 Home Page] [ Lecturer ]
We begin an in-depth look at pointers by learning how they're assigned, dereferenced, and used in functions.
double my_var=3.4;is an object - its name, an lvalue, refers to "the region of storage" occupied by the variable my_var. So fine - my_var is somewhere in storage, i.e., somewhere in the "memory" of the computer. I put "memory" in quotes, because there are different types of memory - the stack (which is where local variables live), the heap (from which objects are dynamically allocated), virtual memory, .... Usually we don't care where my_var is, precisely, as long as we can access and modify its value:
mY_var = 4.0;But sometimes we would really like to know where my_var lives, i.e., its address. A pointer is a type that holds this information:
cout <<"my_var="<<my_var<<'\n';
A "pointer to type T" is declared as follows:
// declarations of T*:These all declare T*, i.e., "pointers to type T". The second one is my own preferred form because I'm less apt to get confused when writing multiple variable declarations:
T* x;
T *y; // my preference
T * z;
T *x, *y, z; // x and y are T*, but z is simply of type TOf course, someday someone will come up to you, armed with a 2-inch thick stack of Coding Guidelines, and demand that you not use multiple variable declaration statements. Follow your conscience. Personally I think it's quite useful to have multi-var. declarations, even including a mix of pointers and non-pointers, providing that they're all related:
int old_ss, *new_ss;How does a pointer get a value? It can be initialized in its definition statement (recall that definitions can include initialization) or simply assigned using an assignment operator (= for the moment; +=,-= are discussed below, all other assignment operators are not allowed for pointers)
int x=4, *y = &x;The type T that a pointer points to is part of its type (that's how pointers are derived types). Different types of pointers, which can be converted using the casting operation:
int *z;
z = &x;
int a=1, *b;Explicit pointer conversions such as the above are not inherently evil, are sometimes essential, but they are potentially dangerous - merely unportable in some situations, will crash the program in others - and we will consider explicit cases in which we'll have a good reason for pointer casts.
char c='1', *d;
b = (int *)(&a); // better know what you're doing here!
d = (char *)(&b);// better know what you're doing here!
Another pair of casts is possible, because, after all, a pointer holds an address, which is bound to be some kind of (presumably positive) integer:
long unsigned int my_addr = 0xff3c0d013; // culled from aTypically you'd have code like this if you were doing very low-level access of the operating system or hardware internals. It is thus expected that the specific type of integer big enough to hold a (double *) is system-dependent. Also note that explicit casts are necessary; (al)most (all) integers are not automatically converted to pointer type.
// computer manual somewhere? double *x = (double *)(my_addr); // hopefully this is a valid address!
double y;
long unsigned int y_addr = (long unsigned int)(&y); // hopefully // y_addr big enough!
The single exception is the value 0, which, when resulting from any constant expression, can be converted into any type of null pointer.
double *x = 0; // pointer initialized to null pointerThe first line is fine; the second line is unfortunate behavior inherited from ANSI C and not to be recommended. A common practice in ANSI C, of using (void *)0 is simply not going to work in most situations in C++:
int *q = 3.0-1.0+2.0; // not a good idea (very unclear)
double *x = (void *) 0; // legal C, illegal C++The reason is that this was a type-checking loophole for C (a (void *) could be assigned to any type of pointer) that has been closed in C++. So this is something to watch for in porting C code to C++.
int x = 20;The operator * is a prefix operator that groups RL (right-to-left) and has lower precedence than the ++ and -- operators; in the expression ++*y, the dereference wins out, however, because of the RL grouping.
int *y = &x;
// access value of x
cout <<"x is "<<x<<" is "<<*y<<'\n';
// assign a value to x, through y
*y = -10;
++*y; // verify precedence
// check that x is properly updated:
cout <<"x is "<<x<<" is "<<*y<<'\n';
One can have double, triple pointers, accessed the same way. But note! The operand of & must be an lvalue (object or function), and operator* and operator& are not precisely inverses of each other
double x;This is legal since each operand of & is an object (a is a variable, is an object, is an lvalue, so &a is okay). The following is illegal, however:
double *a = &x, **b = &a, ***c = &b, ****d = &c;
double **bad_ptr = &&x; // illegal!!The reason is that the rightmost &, which is evaluated first, yields the address of x, which is a value - not an lvalue - so that if &x were literally equal to "103", the expression would be akin to &103, which is not allowed.
int i, j, *iptr;Certainly the use of pointers in this example is not the clearest way of increasing i,j by 3,5 respectively. What's the point of pointers, then?
i = 4;
j = 0;
*iptr += 3;
iptr = &j;
*ipter += 5;
// **** bad code *****The problem is that the function calls we've used up to this point are only call-by-value - the formal variables x and y are not identified with the actual parameters provided in each call of the function order, they're just copies of the values. Using pointers gets around this problem:
void order(int x, int y)
// orders the two arguments in increasing order
{
if (x > y)
return; // nothing to do, already ordered
else
{
int temp = x;
x = y;
y = temp;
}
}
// ****** better code *****Of course, the function calls are still by value (but by value of a pointer, which is enough for our purposes). One can similarly manipulate (double ****) and so on.
void order(int *x, int *y)
// orders the two arguments in increasing order
{
if (*x > *y)
return; // nothing to do, already ordered
else
{
int temp = *x;
*x = *y;
*y = temp;
}
}
double my_div(double num, double denom, int *flag)
// returns num/denom, but flag=1 if divide by 0, flag=0 no error
{
if (!denom)
{
*flag = 1; // error! division by zero
return 0; // return result is irrelevant for error
}
else
{
*flag = 0;
return num/denom;
}
}
[ Phy 405/905 Home Page] [Lecturer ]