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.
Ampersand (&)
- The ampersand (
&
) has several meanings in C++.
- In an expression, ampersand means “address of”.
- In a declaration, ampersand means “I am a reference”.
int earth = 616;
int *p = &earth; // p contains the address of earth
int &r = earth; // r is a reference (alias) to earth
cout << earth << ' ' << *p << ' ' << r << '\n';
earth = 1;
cout << earth << ' ' << *p << ' ' << r << '\n';
*p = 2;
cout << earth << ' ' << *p << ' ' << r << '\n';
r = 3;
cout << earth << ' ' << *p << ' ' << r << '\n';
616 616 616
1 1 1
2 2 2
3 3 3
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';
0x7ffd1816b7e0
0x7ffd1816b7e0
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”. It’s permanent.
- Pointers can change, but references can’t.
- Assigning to 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.
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.
You can’t just use *p.size()
because the precedence is wrong;
it parses as *(p.size())
instead of (*p).size()
.
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
This is the best way. The array is on the stack, and goes away when
it falls out of scope.
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
This is the not as good. If you don’t use delete[]
, due to an
early return from a function, an exception, or just forgetting,
then the memory is leaked.
Character-Based Example
const char a[] = "abcdefghijklm";
const char *b = "nopqrstuvwxyz";
cout << a << ' ' << b << '\n'
<< sizeof(a) << ' ' << sizeof(b) << '\n';
abcdefghijklm nopqrstuvwxyz
14 8
sizeof() returns the size of a thing in bytes.
a
contains thirteen characters and '\0'
;
b
is simply a pointer, with an implementation-defined size.
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
Adding an integer to a pointer moves it forward that many items:
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';
0x7ffc5656f100
0x7ffc5656f10c
We incremented p
by 3 ints worth. The actual amount depends
on the size of an int, generally 4 bytes.
Pointer Subtraction
Subtracting an integer from a pointer moves it backward that many items:
int data[] = {11,22,33,44,55,66,77,88,99};
int *p = data+7; // p points to 88
cout << *p << '\n';
p = p - 1; // p now points to 77
cout << *p << '\n';
88
77
Pointer Subtraction
Subtracting two pointers yields the number of items,
not the number of bytes.
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);