NOTE:
These are a little sketchy...I'll fill in details 'soon'.
passing a pointer to a function is fairly simple, but can confuse the novice programmer in certain situations
the syntax is thus:
ret func(....type *parg....)
so the argument parg will be a pointer to the specified type inside the function
the caller supplies the actual pointer argument such as:
type *pmine; func(....pmine....)
so calling isn't terribly difficult, either
so where's the confusion? well, parg is a pointer, but it isn't the same pointer as pmine:
pmine +----+ +--------+ | --|--------------------------------->| data | +----+ +--------+ ^ / \ | | | parg | +----+ | | --|--------------------------------------+ +----+
they simply point to the same memory space; parg is a copy of pmine since parg is a pass-by-value argument — not a reference argument!
this means that parg can be used to change the data pmine points to, but not where in memory pmine points
if you need to change where a pointer points, you'll need a reference to it:
ret func(....type * & parg_ref....)
now when the programmer calls your function (just like above, actually), you'll be able to change their pointer's destination (as well as what is there):
pmine +----+ +--------+ | --|--------------------------------->| data | +----+ +--------+ /\ || ++=====++ || || parg_ref===++
that is:
ret func(....type * & parg_ref....) { parg_ref = new_address; // would make pmine itself point to a new destination }
(remember, a reference, once initialized, can never refer to any other memory location and all attempts to change it actually change its aliased entity; and you have to initialize a reference when it is created — you cannot wait and assign it later; like with constants, we see once again that initialization is not the same thing as assignment)
so how do you use parg to access the data of pmine?
if pmine points to a single object, you can use simple dereferencing:
ret func(....type * parg....) { *parg = new_value; }
unless, of course, parg thinks it is pointing to a constant:
ret func(....const type * parg....)
{
*parg = new_value; // not allowed
cout << *parg; // still okay
}
or if pmine was pointing to a class object, we can use its methods:
ret func(....class_type * parg....) { ....parg->mutator(new_value).... }
this is as opposed to the much clunkier * and . combo:
ret func(....class_type * parg....) { ....(*parg).mutator(new_value).... }
which would require parentheses (as you see) because of precedence issues between * and .
if, on the other hand, pmine was pointing to an array of objects, we can use array notation:
ret func(....type * parg....) { parg[0] = new_value; }
or for the array of constant values:
ret func(....const type * parg....)
{
parg[0] = new_value; // not allowed
cout << parg[0]; // still okay
}
there is even another possible way to pass an array pointer:
ret func(....type parg[]....) or ret func(....const type parg[]....)
the watchful reader will note that this is the same syntax we've been using to pass actual arrays (not pointers to arrays) to functions; this is no coincidence
but before we get into that...
there is one last pointer-oriented operator (besides * for dereference): &; this operator doesn't mean 'reference argument'; in fact, as noted before, pointers and reference arguments are different concepts (see above how the pointer argument is actually a by-value argument)
like the , in a variable declaration or an argument list, the & in an argument is simply syntax
this in spite of the fact that there is a , operator (see the middle for loop below) and there is a & operator — try to bear with me
so what does this & operator do? it returns the address in memory of its single operand:
short var; short *pvar; var = 16; cout << var << endl; // prints 16 pvar = &var; *pvar = 4; cout << var << endl; // prints 4
we seldom use it for this purpose (pointing to other variables on the stack), however; we C++ programmers normally use the 'address of' operator for only one thing (which we will see shortly)
it originally came about, however, because C has no concept of the [pure] reference argument; to change actual argument variables, a C programmer would have to pass the address of that variable and the function would have to accept a pointer to it; there was much code such as:
void swap(char *x, char *y) { char t = *x; *x = *y; *y = t; return; }
or:
void add_n(long *x, long n) { *x += n; return; }
and to call such functions, we'd have to do strange things like:
swap(&mych1, &mych2);
or:
add_n(&myx, 5);
using the address operator in the call and the dereference operation during the function's execution when we meant to mutate the value at the end of the pointer...quite nasty! ...and prone to error...
avoid using the C-style 'reference' arguments as they were planned against by Bjarne and the ANSI C++ committee; they still work ancestrally, but use our C++ style reference arguments instead...
as it turns out, an array declared such as:
type arr[CONST_SIZE];
is actually a pointer which must point to a particular memory location (the start of the array set aside by the compiler)
arr +----+ +==+==+==+ | --|--------------------------------->| | | | +----+ +==+==+==+
so you've actually been using pointers for quite some time and just didn't know it!
Is there a difference at all? Well, the array is actually a constant pointer rather than a plain pointer. That is, it has been initialized to point to the sequence of data and cannot be made to point anywhere else — the pointer itself is const and can point to no other address.
If you ever have need, you can do this yourself. It looks a bit strange at first, but you get used to it:
type * const ptr = &object; // ptr will always point to object
The reason that the const keyword must follow the * in the declaration is that a const to the left of the * would modify the base type of the pointer! At some point, someone thought it a good idea to have both type const and const type mean the same thing — and others backed them up at a standards committee! *sigh* *shakes head* *shrug* Wha'cha gonna' do?
So, that means that both of these:
const type * ptr1; type const * ptr2;
make the pointers point to a value they believe to be constant. Likewise, both of these:
const type * const ptr3 = &object; type const * const ptr4 = ptr;
make pointers which constantly point to values they believe to be unchangeable.
In summary, there are four ways you can apply const-ness to a pointer:
Declaration(s) | Meaning |
---|---|
type * ptr1; |
both the pointer's destination and the value at that address can be altered |
const type * ptr2a; type const * ptr2b; |
the pointer's destination can be altered, but the value at that address can NOT be altered |
type * const ptr3 = &object; |
the pointer's destination can NOT be altered, but the value at that address can be altered |
const type * const ptr4a = &object; type const * const ptr4b = ptr2a; |
neither pointer's destination nor the value at that address can be altered |
another means to access elements of a pointer which points to an array is through pointer math; in its simplest form it looks like:
*(parr + n)
this accesses the same element as would:
parr[n]
in fact, many compilers simply translate the latter into the former!
so we could use this in a function like:
void print_arr(const double arr[], const size_t count, ostream & out, const short wide, const bool newline) { size_t i; for (i = 0; i < count; ++i) { out << setw(wide) << *(arr + i); } if (newline) { out << endl; } return; }
note how we used *(arr+i) instead of arr[i]
not terribly amazing, I'll grant you, but entertaining nonetheless; however, note that even though arr is totally denoted as an array argument, we still use pointer math to access the elements
perhaps more importantly, notice that even though arr is a pointer (pointing to constant elements at an unchanging location as it is), we are adding an integer (size_t here) to it; what's that all about?! to the compiler it means 'move a number of bytes beyond this address equal to the integer multiplied by the size of the things (elements) this pointer points to'; whew! in this case, that would be move i*sizeof(double) bytes beyond the first element of arr — the same location arr[i] would have gone!
now some of you are thinking: if I can add an integer to a pointer, can I do += or ++, too? certainly!
pvar = pvar + 1 pvar += 1 pvar++ ++pvar
are all allowed and have the same effect — make pvar point one element beyond where it currently points; this is the basis of another means of accessing arrays in loops:
void print_arr(const double arr[], const size_t count, ostream & out, const short wide, const bool newline) { size_t i; const double * parr; for (parr = arr, i = 0; i < count; ++i, ++parr) { out << setw(wide) << *parr; } if (newline) { out << endl; } return; }
note how we initialize the local parr pointer to the same address that arr represents; we then use ++ to increment parr as the loop moves along; and we simply dereference parr during the loop
but now we are wasting i for no reason! well, since adding an integer to a pointer results in a pointer distant from the original, what might the difference between two pointers be? you guessed it: an integer representing the number of elements between them!
void print_arr(const double arr[], const size_t count, ostream & out, const short wide, const bool newline) { const double * parr; for (parr = arr; (parr-arr) < count; ++parr) { out << setw(wide) << *parr; } if (newline) { out << endl; } return; }
The truly clever programmer could also do things like this with pointer arguments — like on the swap function above:
swap(myarr+cur, myarr+max);
Here myarr represents a local array — aka pointer — and cur and max are indices into that array. Note how we can just add the index to the pointer rather than have to address the resulting element: &(myarr[cur]). Sweet, hunh?
Since C-strings are specially used arrays and arrays are just pointers in disguise, that makes C-strings just pointers, too. That's why you always get error messages about char* or const char* when using literal C-strings in ways nature just didn't intend.
Although this might at first seem less than useful, it turns out that it opens a whole new world of support from cstring in the form of pointer wielding functions. There are strchr and strstr, for instance, which search through a C-string for either a char or a [sub]C-string, respectively. Their result, however, is not the size_t position of the found data but rather a pointer to the found data. This lets them use nullptr as an indicator that something was not found rather than having to make up some bogus position constant like string::npos.
In addition, there is the [in]famous strtok function. It's use has been the stuff of legends. But you can master it by next semester for the mere price of a kidney and an eye! No? Well, then just read about it in the man pages...
Finally, note that strcpy and strcat take pointers to their arguments so that code like this:
strcpy(dest + offsetA, src + offsetB);
becomes legal. Here we are copying part of a source string into another part of a destination string. Neat, eh?
And even in your own code it can prove useful. Look at this sleeker-than-ever end-of-string loop:
char * p = str; // where str is a C-string while ( *p != '\0' ) { // act on *p ++p; }
Isn't that nice? No [] and no index variable...smooth!
since you can add a positive integer to a pointer, and the compiler isn't about to check the sign of that integer, we realize that we can also legitimately access the pointer with a negative integer added:
*(p - 4)
or:
p[-4]
hence that I've asked you to always verify that your indices were not too large and also not too small (many novices balk at the fact that you must check for a negative index — "what utter non-sense," they say)
also, due to the commutative nature of addition/subtraction, you will sometimes see the 'clever' programmer code strange things like:
i[arr]
after all, this is equivalent to:
*(i+arr) == *(arr+i) == arr[i]
and on many compilers this works fine for 1D arrays — beware!
since when a function receives a pointer argument it cannot know whether the pointer points to a single object or an array of objects, take care to document the precondition of where the pointer is assumed to point!