No explanation of the basic concepts, just a memory help when you don’t use C/C++ frequently, or if you’re a beginner with some bases.
First question: why pointers? Answers: because functions arguments are passed by value in C and C++. Because arrays and strings are crazy in C.
How to read pointers
Here is a handy way of remembering how pointers syntax works.
In variable declaration
Go from the right to the left of the declaration, from the innermost parenthesis, going back to the right when hitting “)”
- Read the name of the declaration
- * is “a pointer to”
- [] is “an array of”
- () is “a function returning”
- Finally, read the type
int *p[]; reads “p is an array of pointers to int”.
int *(*foo())(); reads “foo is a function returning a pointer to a function returning a pointer to int”.
In variable usage
Variable usage reads from left to right.
- Determine “get” (read variable) or “set” (write variable)
- * is “the value pointed to by”
- & is “the address of”
- [i] is “the element at position i”
- Read the variable name
*p = 5; reads “set the value pointed to by p”.
x = &i; reads “get the address of i”.
printf("%i", *foo[0]); reads “get the value pointed to by the element at position 0 of foo”.
C pointers
Manipulating a variable through a pointer is called dereferencing. It is used through an indirection expression.
Manipulating pointers through variables is called decaying.
Given an integer:
1 |
int i = 42; |
- Create an pointer:
123int* p = &i;// orint *p = &i; // preferred syntax - Get the memory location of the variable:
12345printf("%i", p);// orprintf("%i", &i);// orpritf("%i", &*p); // get the address of the value pointed to by p - Get the value of the variable:
12345printf("%i", i);// orprintf("%i", *p);// orprintf("%i", *&*&*&*p); // if you want to be stupid - Set the value of the variable:
123i = 42;// or*p = 42; - Setting a pointer to null allows to assign it to a variable later:
1234int *p = NULL;if (p) {// use *p}
C arrays
Arrays mostly can’t be manipulated directly, but rather through pointers.
The variable
array is, for most intents and purposes, a pointer to the first element of the array (until it’s incremented).
Given an array of integers:
1 |
int array[] = { 45, 67, 89 }; |
- This doesn’t really creates an array. It creates 3 integers side-by-side, and stores the memory location of the first one.
- array == &array == &array[0]
- When you pass an array as an argument to a function, you really pass a pointer to the array’s first element, because the array decays to a pointer.
- Incrementing a pointer to an array really moves the pointer to the next element of the array (the pointer is incremented by the size of the type of the pointer).
1array++; // now the "array" pointer points to the second element. - The subscript operator (the
[] in
array[0]) has nothing to do with arrays themselves, you can also use it on a pointer:
123int *ar_p = array;printf("%i", ar_p[0]); // outputs the memory location of the first elementprintf("%i", *ar_p[0]); // outputs the value of the first element
C structures
Given a structure:
1 2 3 4 5 6 |
struct foo { char name[10]; int result; }; struct foo f; // note you have to specify "struct" in the declaration |
- Accessing members of the structure through the variable:
12f.result = 42;printf("%i", f.result); // outputs "42" - Accessing the members through a pointer:
1234struct foo *p = f;(*p).result = 42;// orp->result = 42; // using a pointer-to-member operator
C++ references
C++ is much less in love with pointers than C, and prefers using reference variables. They are mostly used in the same way as pointers, but their address can’t change (they always points to the same variable).
The main usage of a reference variable is for function parameters, to pass the variables by reference (by default it’s by value), which allows to not use pointers.
Given an integer:
1 |
int i = 42; |
- Create a reference to a variable with int &r = i; ; this is called binding a reference to an object
- You do not need to dereference a reference to access the variable’s value:
12345678i = 42;// orint &r = i;r = 42;// should generate about the same machine code asint *p = i;*p = 42; - To make sure the arguments are passed by reference in a method:
12345678void foo(int &bar) {bar = 42;}// call it with the variable itselfint i = 0;foo(i);printf("%i", i); // outputs 42 - On the other hand, the same method using pointers must be passed the variables by reference in the call:
123456void foo(int *bar) {*bar = 42;}// call it with the references to the variablefoo(&i);
Managed instances
Instances of managed objects should be used through handles ( ^ , also called “hat”). It can be read “is a handle to the instance of the managed class”. Then, you use the handle as if it was the actual instance.
Note that, even though you don’t have to use managed objects in C++/CLI, you have to declare instances of managed objects with a handle.
1 2 3 4 5 6 7 8 9 |
public ref class Foo { int result; // native member System::String^ name; // managed member }; // while the convention for pointers is to prefix "*" to the pointer name, // the convention for handles is to suffix "^" to the class name Foo^ f = gcnew Foo(); printf("%s", f.name); |
Managed pointers
Because of garbage collection (which changes memory addresses), you can’t just use a regular C/C++ pointer to a managed objects. The managed pointers are called interior pointers. A good overview can be found here.
The syntax for interior pointers is very… declarative:
1 |
interior_ptr<int> p = &f->result; |
Native pointers are automatically converted to interior pointers (the inverse is not true):
1 2 3 4 5 |
void foo(interior_ptr<int> i) { *i = 42; } foo(&f->result); // passing a native pointer to the method |
As with pointers in C++, usage interior pointers is not recommended, and you should rather use managed references.
Managed references
A managed reference is called a tracking reference and uses % instead of & .
1 2 3 4 5 |
void foo(int %i) { i = 42; } foo(f->result); |
Managed pointers and references have some weirdness, for which this SO post goes into further details.