Hunter Liu's Website

18. Week 10 Thursday: const and Pointers; C-Style Arrays

≪ 17. Week 10 Tuesday: Pointers! | Table of Contents

Last time, we learned the fundamentals of pointers and got some practise working with them. Let’s now introduce two final extensions of the concept: first, let’s see how the const keyword interacts with pointers, and then we’ll see the notion of C-style arrays.

A Quick Note on const and Pointers.

Let’s consider the following snippet of code:

1int i = 7; 
2const int* ptr = &i; 

How should we interpret the second line? One one hand, ptr could be a pointer to a constant integer. The pointer itself is not constant — we may be allowed to change the memory address it stores, but the integer stored at that memory address is unchangeable. On the other hand, ptr could be a constant pointer to an integer — we are not allowed to change the memory address stored in ptr, but we’re allowed to mess with the integer stored at that address. Or maybe it’s both!

The way to distinguish these when reading code, and therefore also when writing code, is to read the type from right to left. const int* ptr, read from right to left, is a pointer * to an integer that’s constant. On the other hand, int* const ptr is a constant pointer to an integer. const int* const ptr is a constant pointer to a constant integer!

Accordingly, the code

1int i = 7; 
2int j = 8; 
3const int* ptr = &i; 
4ptr = &j; 

does not have any build errors, but writing *ptr = j would cause a build error. Likewise for all of the other permutations of const, int, and *.

Key Takeaway 1.

Whenever const and * both appear in a variable’s type, read the type from right to left to determine the correct interpretation.

Problem 2.

Determine if the following code has any errors or undefined behaviour. If yes, indicate where the errors occur. If not, predict the output.

 1#include <iostream> 
 2
 3using namespace std; 
 4
 5struct Frog {
 6    double weight; 
 7    double* weight_ptr; 
 8
 9    Frog(double _weight) : 
10    	weight(_weight), weight_ptr(&weight) {} 
11
12    void eat_flies() const {
13        *weight_ptr += 10; 
14    } 
15}; 
16
17int main() {
18    const Frog jimmy(15); 
19    jimmy.eat_flies(); 
20
21    cout << "Jimmy weighs " << jimmy.weight << " pounds" << endl; 
22    return 0; 
23} 

C-Style Arrays

The C-style array is just a glorified pointer. When declaring a C-style array, you need to specify the type, the name of the array, and in brackets the size of the array. When defining a C-style array, you can use a list of values in curly braces (similar to a vector!).

1int arr[5] = {1, 3, 5, 7, 9}; 

The above code creates an array called arr with five values, listed in curly braces. You can then access these using the square bracket notation; elements are indexed just like strings and vectors:

1cout << arr[0] << ' ' << arr[2] << endl; 

This prints out 1 7.

Under the hood, C++ creates a small neighbourhood with five contiguous houses on the same street, say 120 Ram Street up through 124 Ram Street. These five addresses hold the numbers 1, 3, 5, 7, 9, respectively. arr remembers where the residential block begins: it is actually an integer pointer containing the value 120 Ram Street. The notation arr[0] is actually the same thing as *arr — go to 120 Ram Street and access that box. arr[2] says to “go two houses down the street from 120 Ram Street and access that box”.

One can do “pointer arithmetic” in a similar vein of thought:

1int* p = arr + 4; 

In this case, since arr is 120 Ram Street, adding four to the address gives us…124 Ram Street. So *p gets us the index 4 item in the array arr, and vice versa!

1*p = 17; 
2cout << arr[4] << endl; // prints 17

I strongly encourage you to write out street addresses when working through exam problems centred on pointers and C-style arrays! To demonstrate why this is a good idea, let’s tie things off with the problem

Problem 3.

Determine the output of the following code:

 1#include <iostream> 
 2
 3using namespace std; 
 4
 5int main() {
 6    char arr[9] = 
 7        { 'd', 'r', 'n', 'm', 'i', 'A', 'h', 'i', 'c' }; 
 8    char c = 'a'; 
 9
10    // ? 
11    char* p = arr + 1; 
12    for(int i = 1; i < 9; i++) {
13        if(p != &c) {
14            p++; 
15        } 
16
17        if(p == arr + 9) {
18            p = &c; 
19        } 
20
21        *p += i; 
22    } 
23
24    // prints out the contents of arr 
25    for(int i = 0; i < 9; i++) {
26        cout << arr[i]; 
27    } 
28    cout << c << endl; 
29
30    return 0; 
31} 

You can still use the bracket notation when working with pointers, but beware: accessing indices that don’t exist results in undefined behaviour (you’re accessing a memory address whose contents are unknown!).

1int i = 7; 
2int* ptr = &i; 
3
4cout << ptr[0] << endl; // okay! 
5cout << ptr[1] << endl; // undefined behaviour...