Show Lecture.Pointers as a slide show.
CS253 Pointers
Philosophy
- Electricity is tricky, dangerous, and necessary.
- I avoid working with bare wires, when I can.
- I use appliances or batteries instead.
- They use electricity, but they do it safely.
- Pointers are tricky, dangerous, and necessary.
- I avoid using pointers, when I can.
- I use references or containers instead.
- They use pointers, but they do it safely.
Pointers
- Java has references.
- C++ has references.
- C++ also has pointers.
- They are similar to, but different than, references.
An example
int a[] = {11, 22, 33, 44, 55};
int *p = &a[2];
cout << *p << '\n';
p += 2;
cout << *p << '\n';
33
55
a
is an array of ints
a[2]
is an int
&a[2]
is an int *, or the address of an int
p
is an int *, or a pointer to an int
*p
is an int
Bad examples
int a[] = {11, 22, 33, 44, 55};
int *p = &a[2], n;
n = p;
c.cc:3: error: invalid conversion from 'int*' to 'int'
int a[] = {11, 22, 33, 44, 55};
int *p = &a[2], n;
p = n;
c.cc:3: error: invalid conversion from 'int' to 'int*'
Declaration style
These are all equivalent, since spaces matter little:
int *a;
int* b;
int*c;
int * d;
int
*
e;
What types are g
and h
?
g
is an int *, but h
is simply an int.
int* g, h;
That’s why I do this:
int *i, j;
Another example
int a[] = {11, 22, 33, 44, 55};
int *p = a;
cout << *p << '\n';
*p += 8;
cout << *p << '\n';
p += 2;
cout << *p << '\n';
11
19
33
a
is an array of ints
a
is equivalent to &a[0]
int a[5] = {11, 22, 33, 44, 55};
cout << a << '\n'
<< &a[0] << '\n';
0x7ffd5a23b560
0x7ffd5a23b560
Null pointers
- A pointer that is intended to not point to anything useful
is a null pointer. It can be represented several ways:
- nullptr: modern C++ way to do it
- NULL: old-fashioned C way to do it
0
: how the implementation actually works. Do not do this.
- A null pointer is false, in a boolean context,
and a non-null pointer is true.
int xyz, *p = &xyz;
if (p)
cout << "Hello\n";
Hello
- An uninitialized pointer is just that, uninitialized. Random.
- If you want it to be null, say so:
int *p = nullptr;
References
A reference is like a pointer, with auto-indirection.
int a[] = {11, 22, 33, 44, 55};
int &r = a[2];
cout << r << '\n';
r += 2;
cout << r << '\n';
33
35
- Think of a reference as an alias.
- A reference, once set, cannot be “re-seated”.
- Pointers can change, but references can’t.
- Trying to change a reference only changes the value of the variable it
refers to; the reference still refers to the same variable afterward.
A common use
void f1(string s) { // call by value
cout << s << '\n';
}
void f2(const string &s) { // call by const reference
cout << s << '\n';
}
int main() {
string taunt = "Your mother is a hamster.";
f1(taunt);
f2(taunt);
return 0;
}
Your mother is a hamster.
Your mother is a hamster.
- Call by reference: fast; call by value: slow (at least for big things).
- Use const so that
f2
can’t change the argument.
Declaration | Explanation |
int *a | non-const pointer to non-const ints |
const int *b | non-const pointer to const int s |
int *const c | const pointer to non-const ints |
const int *const d | const pointer to const int s |
int &e = … | reference a non-const int |
const int &f = … | reference to a const int |
Examples
Declare a pointer to constant integers. We can change the pointer,
but not the integers.
int alpha[] = {11,22,33,44,55};
const int *p = alpha;
p += 2;
cout << *p;
33
int beta[] = {11,22,33,44,55};
const int *p = beta;
*p = 42;
c.cc:3: error: assignment of read-only location '* p'
Examples
Declare a constant pointer to integers. We can change the the integers,
but not the pointer.
int gamma[] = {11,22,33,44,55};
int *const p = gamma;
*p = 42;
cout << *p;
42
int delta[] = {11,22,33,44,55};
int *const p = delta;
p++;
c.cc:3: error: increment of read-only variable 'p'
Dot or pointer?
- Should I use
a->b()
, or a.b()
?
- Remember that
f->g
is equivalent to (*f).g
.
- If
a
is a pointer to an object, use: a->b()
- If
a
is an object, use: a.b()
- If
a
is a reference, then it’s an alias for something—use the
rules for whatever it refers to.
- You can overload
->
for objects—we’ll learn about that later.
Examples
vector<bool> v(42, true); // v is a vector of bool
cout << v.size() << '\n';
42
Here, we use a dot, because v
is an object, not a pointer.
Examples
vector<bool> v(42, true); // v is a vector of bool
vector<bool> *p = &v; // p is a pointer to vector of bool
cout << (*p).size() << '\n'
<< p->size() << '\n';
42
42
Two ways (one ugly, one not) of doing the same thing.
Examples
vector<bool> v(42, true); // v is a vector of bool
vector<bool> &r = v; // r is a reference to a vector of bool
cout << r.size() << '\n';
42
r
is a reference to an object.
- We use dot with an object.
- Therefore, use dot with
r
.
Array Name is a Pointer
The name of an array is equivalent to a pointer
to its first element.
Write the data via the first pointer,
and read it from the second pointer.
double alpha[20];
double *a = alpha;
double *b = &alpha[0];
*a = 123.456;
cout << *b << '\n';
if (a == b)
cout << "Pointers are equal.\n";
123.456
Pointers are equal.
Arrays & Pointers
You can think of there being two sorts of arrays.
- One is where the entire array is local to the function.
- The other is where only a pointer to the data is local
to the function. The data that it points to lives elsewhere.
Stack-Based Array Example
The array alpha
is on the stack.
int alpha[10];
for (int i=0; i<10; i++)
alpha[i] = i*i;
for (int i=0; i<10; i++)
cout << alpha[i] << ' ';
cout << '\n';
cout << "size of alpha: " << sizeof(alpha) << '\n';
0 1 4 9 16 25 36 49 64 81
size of alpha: 40
Array Example
The pointer beta
is on the stack,
pointing to ten ints of dynamic memory.
int *beta = new int[10];
for (int i=0; i<10; i++)
beta[i] = i*i;
for (int i=0; i<10; i++)
cout << beta[i] << ' ';
cout << '\n';
cout << "size of beta: " << sizeof(beta) << '\n';
delete[] beta;
0 1 4 9 16 25 36 49 64 81
size of beta: 8
Character-Based Example
const char a[] = "abcdefghijklm";
const char *b = "nopqrstuvwxyz";
cout << a << ' ' << b << '\n'
<< sizeof(a) << ' ' << sizeof(b) << '\n';
abcdefghijklm nopqrstuvwxyz
14 8
Pointer Arithmetic
- What does it mean to increment a pointer?
- What does
p++
, or ++p
, or p+=1
, or p=p+1
do?
- It does not increment the pointer by one byte.
- It does not increment the pointer by one byte.
- It does not increment the pointer by one byte.
- Instead, it increments the pointer by the size of the objects
that it points to. This is why a pointer is int * or
string *
, and not just *
.
Pointer Addition
int data[] = {11,22,33,44,55,66,77,88,99};
int *p = data; // p points to 11
p += 3; // p now points to 44
cout << *p << '\n';
44
Let’s look at the pointers:
int data[] = {11,22,33,44,55,66,77,88,99};
int *p = data;
cout << p << '\n';
p += 3;
cout << p << '\n';
0x7ffd724be0b0
0x7ffd724be0bc
Pointer Subtraction
Similarly, subtracting pointers yields the number of items,
not the number of bytes.
int data[] = {11,22,33,44,55,66,77,88,99};
int *p = data+7; // p points to 88
cout << *p << '\n';
p--; // p now points to 77
cout << *p << '\n';
88
77
Pointer Subtraction
No, the other kind of pointer subtraction:
int data[20];
int *a = &data[2], *b = &data[9];
cout << b-a << '\n'; // b is 7 past a
7
More Complicated
Of course, you can have a pointer to any type.
// pointer to pointer to int:
int **p;
// pointer to pointer to pointer to pointer to pointer to int
int *****q;
// one hundred pointers to ints:
int *a[100];
// pointer to a function that takes a float & a bool and returns an int
int (*fn)(float, bool);