This set of slides introduces the reader to the concept of arrays in C++ (with elements of C++11 and C++14). After presenting the array data type, the concept of array-to-pointer decay is introduced. The presentation proceeds with a discussion on how to pass arrays to functions. To this extent, the reader is guided to the use of bounded ranges as the first step towards the use of the Standard Template Library (STL).
9. Memory layout
0 ... 4
┌───┬───┬───┬───┬───┐
numbers:│ │ │ │ │ │
└───┴───┴───┴───┴───┘
Elements in the array are accessed by index
10. Accessing elements
The indexing operator1
[] allows accessing elements in the array
int numbers[3] = {1, 7, 13};
std::cout << numbers[0];
1
Some'mes, it is also referred to as the subscript operator
11. What's the type of x?
int numbers[3] = {1, 7, 13};
??? x = numbers[0];
12. What's the type of x?
int numbers[3] = {1, 7, 13};
int x = numbers[0];
14. Element type
That is, int is the type of numbers[0], ..., numbers[4]
int numbers[5];
15. Element type
It is possible to handle each element in numbers as any other
object of type int
int numbers[3] = {1, 7, 13};
// examples of usage
std::cout << numbers[2];
numbers[1] = 10;
16. What is the type of numbers?
int numbers[3] = {1, 7, 13};
17. For a data type T and an integral constant m, T[m] is the data type
"array of m elements of type T"
18. What about the type of x and y?
int x[3] = {1, 7, 13};
int y[5] = {10, 15, 18, 20, 11};
19. What about the type of x and y?
int x[3] = {1, 7, 13}; // x is of type int[3]
int y[5] = {10, 15, 18, 20, 11}; // y is of type int[5]
20. Array type is a compound type
The array type is a compound type, as it depends on two
orthogonal factors:
• The element type T
• The number of elements m
21. What is the output?
int x[3] = {1, 2, 3};
int y[5] = {7, 8, 9, 10, 11};
if (typeid(x) == typeid(y))
std::cout << "x and y are of the same type";
else
std::cout << "x and y are of unrelated types";
22. What is the output?
int x[3] = {1, 2, 3};
int y[5] = {7, 8, 9, 10, 11};
if (typeid(x) == typeid(y))
std::cout << "x and y are of the same type";
else // the else branch gets executed
std::cout << "x and y are of unrelated types";
23. Unrelated array types
Two arrays are of unrelated types if either the element type or the
size varies
int x[5];
int y[3];
typeid(x) == typeid(y); // false
24. Unrelated array types
Two arrays are of unrelated types if either the element type or the
size varies
int x[5];
char z[5];
typeid(x) == typeid(z); // false
25. Arrays as type costructors
Alterna(vely, one could say that [m] is a type constructor
26. Example
Construc)ng the type "array of 5 elements of type int"
Input type: int
Type constructor: [5]
Output type: int[5]
30. Does it work?
// error: m should be a compile-time integral constant
int m = 5;
int numbers[m];
31. Array size
The size m defines the array type. As such, m must be a compile-
)me integral constant2
int numbers[5]; // correct: 5 is a literal
2
Both gcc and clang implement a non-standard extension that allows crea6ng arrays of variable size
35. Does the following work?
// correct: m is an integral compile-time constant
int const m = 7;
int numbers[m];
36. Constant expressions
The only quan,,es compilers can know without execu,ng the
code are the so called constant expressions
37. Constant expressions
The outcome of constant expressions can be store in constant
variables
int const m = 7 + 2; // C++03
int constexpr n = 7 + 2; // C++11 & C++14
38. Does it work?
int p;
std::cin >> p;
int const m = p;
int numbers[m];
39. Does it work?
int p;
std::cin >> p;
// error: although constant, m is not
// known at compile-time
int const m = p;
int numbers[m];
41. Does it work?
// error: although constant, m is not
// known at compile-time
int p = 12;
int const m = p;
int numbers[m];
42. The std::size_t type
Formally, array sizes should be stored in constant variables of type
std::size_t
std::size_t const m = 7;
int numbers[m];
43. The std::size_t type
std::size_t is an unsigned integral type that can store the size
of any possible array
std::size_t const m = 7;
int numbers[m];
51. A more compact nota,on
We can obtain the same result by using a more compact nota3on.
Specifically, the following two expressions are equivalent:
int* first = &numbers[0];
int* first = numbers;
52. Pointer decay
Note that first is of type int*, whereas numbers is of type
int[5]
int* first = numbers;
53. Pointer decay
int* first = numbers;
• A temporary pointer of type int* is generated from numbers
• The content of such a pointer (i.e., the address of numbers[0])
is copied into first
• At the end of the statement, the temporary pointer is destroyed
54. Pointer decay
When evalua*ng an expression involving an array of type T[m]
• If the expression is valid, the compiler keeps the type as T[m]
• Otherwise, it converts T[m] to T*
55. Pointer decay
int numbers[5] = {1, 7, 13, 5, 9};
std::size_t s = sizeof(numbers); // numbers is
// treated as a
// int[5]
int* first = numbers; // numbers is converted
// to int*
56. Common trait between arrays
That is, the only common trait between the types T[m] and T[n]
is that they both decay to T*
T[m] → T*
T[n] → T*
57. What's the outcome?
int x[5];
int y[3];
int* first_x = x;
int* first_y = y;
typeid(x) == typeid(y); // true or false?
typeid(first_x) == typeid(first_y); // true or false?
58. What's the outcome?
int x[5];
int y[3];
int* first_x = x;
int* first_y = y;
typeid(x) == typeid(y); // false!
typeid(first_x) == typeid(first_y); // true!
60. Loss of informa+on
Once converted to a pointer, there is no way to know the size of
the array from the pointer alone
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers;
// we CANNOT write the below 'size_from_ptr' function
// so as to obtain 5 from first
std::size_t s = size_from_ptr(first);
62. Pointer arithme,c
It is possible to introduce a set of opera2ons on pointers that
define a pointer arithme,c
• Increment and decrement
• Addi.on and subtrac.on
• Comparison
• Assignment
64. Increment and decrement
Pointers can be incremented (resp. decremented) in order to move
to the next (resp. previous) element
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers;
std::cout << *first;
++first;
std::cout << *first;
65. Increment and decrement
Pointers can be incremented (resp. decremented) in order to move
to the next (resp. previous) element
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers;
std::cout << *first; // output: 1
++first;
std::cout << *first; // output: 7
66. Addi$on and subtrac$on
Given a pointer first to the first element, the expression
first + i results in a pointer to the i-th array element
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers;
int* third = first + 2;
int* fifth = first + 4;
std::cout << "the 3rd element is " << *third;
68. Addi$on and subtrac$on
Given two pointers first and last (s.t. first preceeds last),
last - first returns the number of elements in the range
[first, last)
69. Addi$on and subtrac$on
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers + 1; // points to 7
int* last = numbers + 3; // points to 5
std::size_t n = last - first; // n = ???
70. Addi$on and subtrac$on
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers + 1; // points to 7
int* last = numbers + 3; // points to 5
std::size_t n = last - first; // n = 2
71. Addi$on and subtrac$on
While subtrac.on between pointers is well defined, it does not
make sense to add two pointers
int p = first + last; // where is p pointing to?
72. Addi$on and subtrac$on: a metaphor3
3
Example taken from: h2p://stackoverflow.com/a/2935450/1849221
74. Comparison
Two pointers are equal if they point to the same element
int numbers[5] = {1, 7, 13, 5, 9};
int* first = numbers;
int* third = first + 2;
++first; ++first;
first == third; // true!
81. Indexing operator
The indexing operator [] is defined in terms of pointer arithme,c
Given an array a and an index i, the opera1on a[i] is
implemented as *(a + i)
84. Indexing operator
Thanks to the commuta.vity of sum, the following expressions are
equivelent:
std::cout << numbers[2]; // the same as *(numbers + 2);
std::cout << 2[numbers]; // the same as *(2 + numbers);
86. The sum_int func)on
Assume we need to write a func1on sum_int, which computes
the summa1on of an integer sequence
┌──────────────┐
{7, 12, 1} │ │ 20
──────────▶ sum_int ├─────────▶
│ │
└──────────────┘
88. Passing arrays by values
"The way arrays are passed to func3ons is an embarrassment. It dates
back to the 3me when C did not allow passing large objects to a
func3on. Even structures could not be passed by value. Within a few
years it became possible to pass structures by value, but arrays
remained in the embarrassing state."
(A. Stepanov)
89. An interface for sum_int
As an alterna*ve, we could design the sum_int func*on so as to
accept:
• A pointer to the first array element
• The array size
91. An interface for sum_int
int sum_int(int* first, std::size_t m);
92. An interface for sum_int
Note that our signature has the advantage that the func2on can be
used with arrays of any size
int sum_int(int* first, std::size_t m);
94. Implemen'ng sum_int
int sum_int(int* first, std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += *(first + i);
return sum;
}
95. Invoking sum_int
int main() {
int numbers[3] = {12, 7, 1};
int* first = ???;
int r = sum_int(???, ???);
std::cout << "the elements sum up to " << r;
}
96. Invoking sum_int
int main() {
int numbers[3] = {12, 7, 1};
int* first = numbers;
int r = sum_int(first, 3);
std::cout << "the elements sum up to " << r;
}
98. Refining sum_int
int sum_int(int* first, std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += *(first + i);
return sum;
}
99. Refining sum_int
int sum_int(int* first, std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += first[i];
return sum;
}
100. A "convenient" nota,on
In the sole context of func%on declara%on, the following two
signtures4
for sum_int are equivalent:
int sum_int(int* first, std::size_t m);
int sum_int(int first[], std::size_t m);
4
The signature int sum_int(int first[3], std::size_t m) is also possible. However, we avoid using that
as it wrongly suggests that the func;on could accept only arrays of size 3
101. Refining sum_int
int sum_int(int* first, std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += first[i];
return sum;
}
102. Refining sum_int
int sum_int(int first[], std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += first[i];
return sum;
}
103. Refining sum_int
int sum_int(int seq[], std::size_t m) {
int sum = 0;
for (std::size_t i = 0; i < m; ++i)
sum += seq[i];
return sum;
}
104. Refining sum_int
int main() {
int numbers[3] = {12, 7, 1};
int* first = numbers;
int r = sum_int(first, 3);
std::cout << "the elements sum up to " << r;
}
105. Refining sum_int
int main() {
int numbers[3] = {12, 7, 1};
int r = sum_int(numbers, 3);
std::cout << "the elements sum up to " << r;
}
106. The alterna*ve signature
Remember: adop&ng the alterna&ve signature does not mean that
the array will be passed by value
int sum_int(int seq[], std::size_t m);
107. The alterna*ve signature
The alterna*ve signature only gives the illusion of working with
arrays
int sum_int(int seq[], std::size_t m);
108. The alterna*ve signature
The parameter seq is of type int* no ma0er what
int sum_int(int seq[], std::size_t m);
109. The alterna*ve signature
In fact, this "convenient" nota0on is a major source of confusion
among new C/C++ programmers
int sum_int(int seq[], std::size_t m);
111. The Standard Template Library
The C++ Standard Library includes a set of algorithms and data
structures that were originally part of a third-party library, called
the Standard Template Library (STL)
112. The Standard Template
Library
The Standard Template Library was
invented by Alexander Stepanov in 1994
circa, and later included in the C++
Standard Library
113. The Standard Template Library
As an example, std::vector was originally proposed as part of
the STL, and later made into the C++ Standard Library
114. The STL interface
Though we will discuss in depth the STL in the future, we will now
apply its approach to computa+on in the limited case of arrays 5
5
As a ma'er of fact, the very first version of the STL algorithms was designed to work only with arrays
115. sum_int as we le' it
We devised the following signature for sum_int:
int sum_int(int* first, std::size_t m);
116. The STL interface
Let us replace the second parameter in sum_int (the array size),
with a pointer deno8ng the end of the array
int sum_int(int* first, int* last);
117. The STL interface
These two parameters are equivalent, as they both provide an
indica5on on where to stop inspec5ng the array
int sum_int(int* first, std::size_t m);
int sum_int(int* first, int* last);
118. The last pointer
Which is the last posi)on in numbers?
┌───┬───┬───┬───┬───┐
numbers: │ 1 │ 7 │13 │ 5 │ 9 │ int[5]
└───┴───┴───┴───┴───┘
119. The last pointer
Maybe suprisingly, it is convienient to let last point to one
element past the end
121. sum_int as an STL algorithm
int sum_int(int* first, int* last) {
int sum = 0;
while (???) {
???
}
return sum;
}
122. sum_int as an STL algorithm
int sum_int(int* first, int* last) {
int sum = 0;
while (???) {
sum += *first;
++first;
}
return sum;
}
123. sum_int as an STL algorithm
int sum_int(int* first, int* last) {
int sum = 0;
while (first != last) {
sum += *first;
++first;
}
return sum;
}
124. Invoking sum_int
int main() {
int numbers[3] = {12, 7, 1};
int r = sum_int(???, ???);
std::cout << "the elements sum up to " << r;
}
125. Invoking sum_int
int main() {
int numbers[3] = {12, 7, 1};
int r = sum_int(numbers, numbers + 3);
std::cout << "the elements sum up to " << r;
}
126. std::begin and std::end
Compu&ng the beginning and the end of a sequence is so
ubiquitous that C++11 introduced two helper func+ons
int* first = std::begin(numbers);
int* last = std::end(numbers);
127. Invoking sum_int
int main() {
int numbers[3] = {12, 7, 1};
int r = sum_int(std::begin(numbers),
std::end(numbers));
std::cout << "the elements sum up to " << r;
}
128. std::accumulate
As a ma&er of fact, with sum_int we implemented a simplified
version of the standard algorithm std::accumulate
129. std::accumulate: an example
#include <algorithm>
#include <iostream>
int main() {
int numbers[3] = {12, 7, 1};
int r = std::accumulate(std::begin(numbers),
std::end(numbers), 0);
std::cout << "the elements sum up to " << r;
}
138. Bibliography
• S.B. Lippman et al., C++ Primer (5th
Ed.)
• B. Stroustrup, The C++ Programming Language (4th
Ed.)
• A. Stepanov, D. Rose, From MathemaFcs to Generic
Programming
• StackOverflow FAQ, "How do I use arrays in C++?"