객체지향 프로그래밍
Object Oriented Programming
Chae-Bong Sohn
cbsohn@kw.ac.kr
C++ Primer Fifth Edition
Stanley B. Lippman, Josée Lajoie, Barbara E. Moo
Addison-Wesley
2 https:/ssl.kw.ac.kr
Strings, Vectors, and Arrays
3 https:/ssl.kw.ac.kr
3.1. Namespace using Declarations
• A using declaration lets us use a name from a namespace without qualifying the name with a
namespace_name::prefix.
• A using declaration has the form using namespace::name;
• Once the using declaration has been made, we can access name directly:
#include <iostream>
// using declaration; when we use the name cin, we get the one from the namespace std
using std::cin;
int main() {
int i;
cin >> i; // ok: cin is a synonym for std::cin
cout << i; // error: no using declaration; we must use the full name
std::cout << i; // ok: explicitly use cout from namepsace std
return 0;
}
4 https:/ssl.kw.ac.kr
A Separate using Declaration Is Required for Each Name
• Each using declaration introduces a single namespace member.
• This behavior lets us be specific about which names we’re using.
• As an example, we’ll rewrite the program from § 1.2 with using declarations for the library
names it uses:
#include <iostream>
// using declarations for names from the standard library
using std::cin;
using std::cout; using std::endl;
int main() {
cout << "Enter two numbers:" << endl;
int v1, v2;
cin >> v1 >> v2;
cout << "The sum of " << v1 << " and " << v2
<< " is " << v1 + v2 << endl;
return 0;
}
5 https:/ssl.kw.ac.kr
Header Should Not Include using Declarations
• Code inside headers (§ 2.6.3) ordinarily should not use using declarations.
• The reason is that the contents of a header are copied into the including program’s text.
• If a header has a using declaration, then every program that includes that header gets that
same using declaration.
• As a result, a program that didn’t intend to use the specified library name might encounter
unexpected name conflicts.
6 https:/ssl.kw.ac.kr
3.2. Library string Type
• A string is a variable-length sequence of characters.
• To use the string type, we must include the string header.
• Because it is part of the library, string is defined in the std namespace.
• Our examples assume the following code:
#include <string>
using std::string;
• This section describes the most common string operations; § 9.5 will cover
additional operations.
3.2.1. Defining and Initializing strings
• Each class defines how objects of its type can be initialized.
• A class may define many different ways to initialize objects of its type.
• Each way must be distinguished from the others either by the number of initializers that we
supply, or by the types of those initializers.
• Table 3.1 lists the most common ways to initialize strings.
• Some examples:
string s1; // default initialization; s1 is the empty string
string s2 = s1; // s2 is a copy of s1
string s3 = "hiya"; // s3 is a copy of the string literal
string s4(10, 'c'); // s4 is cccccccccc
Direct and Copy Forms of Initialization
• When we initialize a variable using =, we are asking the compiler to copy initialize the object by
copying the initializer on the right-hand side into the object being created.
• Otherwise, when we omit the =, we use direct initialization.
string s5 = "hiya"; // copy initialization
string s6("hiya"); // direct initialization
string s7(10, 'c'); // direct initialization; s7 is cccccccccc
• When we want to use several values, we can indirectly use the copy form of initialization by
explicitly creating a (temporary) object to copy:
string s8 = string(10, 'c'); // copy initialization; s8 is cccccccccc
Direct and Copy Forms of Initialization
• The initializer of s8—string(10, 'c')—creates a string of the given size and
character value and then copies that value into s8.
• It is as if we had written
string temp(10, 'c'); // temp is cccccccccc
string s8 = temp; // copy temp into s8
• Although the code used to initialize s8 is legal, it is less readable and offers no compensating
advantage over the way we initialized s7.
Table 3.1. Ways to Initialize a string
3.2.2. Operations on strings
Reading and Writing strings
• As we saw in Chapter 1, we use the iostream library to read and write values of built-in types
such as int, double, and so on.
• We use the same IO operators to read and write strings:
// Note: #include and using declarations must be added to compile this code
int main()
{
string s; // empty string
cin >> s; // read a whitespace-separated string into s
cout << s << endl; // write s to the output
return 0;
}
3.2.2. Operations on strings
Reading and Writing strings
• So, if the input to this program is Hello World! (note leading and trailing spaces), then the
output will be Hello with no extra spaces.
• Like the input and output operations on the built-in types, the string operators return their
left-hand operand as their result. Thus, we can chain together multiple reads or writes:
string s1, s2;
cin >> s1 >> s2; // read first input into s1, second into s2
cout << s1 << s2 << endl; // write both strings
• If we give this version of the program the same input, Hello World!, our output would be
“HelloWorld!”
Reading and Unknown Number of strings
• In § 1.4.3 we wrote a program that read an unknown number of int values.
• We can write a similar program that reads strings instead:
int main()
{
string word;
while (cin >> word) // read until end-of-file
cout << word << endl; // write each word followed by a new line
return 0;
}
• In this program, we read into a string, not an int. Otherwise, the while condition executes similarly
to the one in our previous program.
• The condition tests the stream after the read completes.
• If the stream is valid—it hasn’t hit end-of-file or encountered an invalid input—then the body of the
while is executed.
• The body prints the value we read on the standard output.
• Once we hit end-of-file (or invalid input), we fall out of the while.
Using getline to Read an Entire Line
• Like the input operator, getline returns its istream argument.
• As a result, we can use getline as a condition just as we can use the input operator as a
condition (§ 1.4.3).
• For example, we can rewrite the previous program that wrote one word per line to write a line at a
time instead:
int main()
{
string line;
// read input a line at a time until end-of-file
while (getline(cin, line))
cout << line << endl;
return 0;
}
The string empty and size Operation
• We can revise the previous program to only print lines that are not empty:
// read input a line at a time and discard blank lines
while (getline(cin, line))
if (!line.empty())
cout << line << endl;
• The size member returns the length of a string (i.e., the number of characters in it).
• We can use size to print only lines longer than 80 characters:
string line;
// read input a line at a time and print lines that are longer than 80 characters
while (getline(cin, line))
if (line.size() > 80)
cout << line << endl;
The string::size_type Type
• Admittedly, it can be tedious to type string::size_type.
• Under the new standard, we can ask the compiler to provide the appropriate type by using auto
or decltype (§ 2.5.2):
auto len = line.size(); // len has type string::size_type
• Because size returns an unsigned type, it is essential to remember that expressions that mix
signed and unsigned data can have surprising results (§ 2.1.2).
• For example, if n is an int that holds a negative value, then s.size() < n will almost
surely evaluate as true. It yields true because the negative value in n will convert to a
large unsigned value.
Comparing strings
• The equality operators (== and !=) test whether two strings are equal or unequal,
respectively.
• Two strings are equal if they are the same length and contain the same characters.
• The relational operators <, <=, >, >= test whether one string is less than, less than or
equal to, greater than, or greater than or equal to another.
• These operators use the same strategy as a (case-sensitive) dictionary:
1. If two strings have different lengths and if every character in the shorter string is equal to the
corresponding character of the longer string, then the shorter string is less than the longer one.
2. If any characters at corresponding positions in the two strings differ, then the result of the string
comparison is the result of comparing the first character at which the strings differ.
Comparing strings
• As an example, consider the following strings:
string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";
• Using rule 1, we see that str is less than phrase. By applying rule 2, we see that slang is greater
than both str and phrase.
Assignment for strings
• In general, the library types strive to make it as easy to use a library type as it is to use a built-
in type.
• To this end, most of the library types support assignment.
• In the case of strings, we can assign one string object to another:
string st1(10, 'c'), st2; // st1 is cccccccccc; st2 is an empty string
st1 = st2; // assignment: replace contents of st1 with a copy of st2
// both st1 and st2 are now the empty string
Adding Two strings
• Adding two strings yields a new string that is the concatenation of the left-hand followed
by the right-hand operand.
• That is, when we use the plus operator (+) on strings, the result is a new string whose
characters are a copy of those in the left-hand operand followed by those from the right-hand
operand.
• The compound assignment operator (+=) (§ 1.4.1) appends the right-hand operand to the lefthand
string:
string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2; // s3 is hello, world\n
s1 += s2; // equivalent to s1 = s1 + s2
Adding Literals and strings
• As we saw in § 2.1.2, we can use one type where another type is expected if there is a conversion
from the given type to the expected type.
• The string library lets us convert both character literals and character string literals (§ 2.1.3)
to strings.
• Because we can use these literals where a string is expected, we can rewrite the previous
program as follows:
string s1 = "hello", s2 = "world"; // no punctuation in s1 or s2
string s3 = s1 + ", " + s2 + '\n';
• When we mix strings and string or character literals, at least one operand to each + operator
must be of string type:
string s4 = s1 + ", "; // ok: adding a string and a literal
string s5 = "hello" + ", "; // error: no string operand
string s6 = s1 + ", " + "world"; // ok: each + has a string operand
string s7 = "hello" + ", " + s2; // error: can't add string literals
Adding Literals and strings
• The initializations of s4 and s5 involve only a single operation each, so it is easy to see whether the
initialization is legal.
• The initialization of s6 may appear surprising, but it works in much the same way as when we chain
together input or output expressions (§ 1.2, p. 7).
• This initialization groups as
string s6 = (s1 + ", ") + "world";
• The subexpression s1 + ", " returns a string, which forms the left-hand operand of the second +
operator. It is as if we had written
string tmp = s1 + ", "; // ok: + has a string operand
s6 = tmp + "world"; // ok: + has a string operand
• On the other hand, the initialization of s7 is illegal, which we can see if we parenthesize the expression:
string s7 = ("hello" + ", ") + s2; // error: can't add string literals
• Now it should be easy to see that the first subexpression adds two string literals.
• There is no way to do so, and so the statement is in error.
Exercises Section 3.2.2
• Exercise 3.2: Write a program to read the standard input a line at a time. Modify your program to
read a word at a time.
• Exercise 3.3: Explain how whitespace characters are handled in the string input operator and
in the getline function.
• Exercise 3.4: Write a program to read two strings and report whether the strings are
equal. If not, report which of the two is larger. Now, change the program to report whether the
strings have the same length, and if not, report which is longer.
• Exercise 3.5: Write a program to read strings from the standard input, concatenating what is
read into one large string. Print the concatenated string. Next, change the program to
separate adjacent input strings by a space.
3.2.3. Dealing with the Characters in a string
• Often we need to deal with the individual characters in a string. We might want to check to see
whether a string contains any whitespace, or to change the characters to lowercase, or to see
whether a given character is present, and so on.
• One part of this kind of processing involves how we gain access to the characters themselves. Sometimes
we need to process every character.
• Other times we need to process only a specific character, or we can stop processing once some condition is
met.
• It turns out that the best way to deal with these cases involves different language and library facilities.
• The other part of processing characters is knowing and/or changing the characteristics of a character.
• This part of the job is handled by a set of library functions, described in Table 3.3 (overleaf). These
functions are defined in the cctype header.
Table 3.3. cctype Functions
Processing Every character? Use Range-Based for
• If we want to do something to every character in a string, by far the best approach is to use a
statement introduced by the new standard: the range for statement.
• This statement iterates through the elements in a given sequence and performs some operation on each
value in that sequence.
• The syntactic form is
for (declaration : expression)
statement
• A string represents a sequence of characters, so we can use a string as the expression in a range
for.
• As a simple example, we can use a range for to print each character from a string on its own line of
output:
string str("some string");
// print the characters in str one character to a line
for (auto c : str) // for every char in str
cout << c << endl; // print the current character followed by a newline
Processing Every character? Use Range-Based for
• As a somewhat more complicated example, we’ll use a range for and the ispunct function to count
the number of punctuation characters in a string:
string s("Hello World!!!");
// punct_cnt has the same type that s.size returns; see § 2.5.3 (p. 70)
decltype(s.size()) punct_cnt = 0;
// count the number of punctuation characters in s
for (auto c : s) // for every char in s
if (ispunct(c)) // if the character is punctuation
++punct_cnt; // increment the punctuation counter
cout << punct_cnt
<< " punctuation characters in " << s << endl;
• The output of this program is
3 punctuation characters in Hello World!!!
Using a Range for to Change the Characters in a string
• Suppose that instead of counting punctuation, we wanted to convert a string to all uppercase letters.
• To do so we can use the library toupper function, which takes a character and returns the uppercase
version of that character.
• To convert the whole string we need to call toupper on each character and put the result back in
that character:
string s("Hello World!!!");
// convert s to uppercase
for (auto &c : s) // for every char in s (note: c is a reference)
c = toupper(c); // c is a reference, so the assignment changes the char in s
cout << s << endl;
• The output of this code is
HELLO WORLD!!!
Processing Only Some Characters?
• The following example uses the subscript operator to print the first character in a string:
if (!s.empty()) // make sure there's a character to print
cout << s[0] << endl; // print the first character in s
• Before accessing the character, we check that s is not empty.
• Any time we use a subscript, we must ensure that there is a value at the given location.
• If s is empty, then s[0] is undefined.
• So long as the string is not const (§ 2.4), we can assign a new value to the character that the
subscript operator returns.
• For example, we can capitalize the first letter as follows:
string s("some string");
if (!s.empty()) // make sure there's a character in s[0]
s[0] = toupper(s[0]); // assign a new value to the first character in s
• The output of this program is
Some string
Using a subscript for Iteration
• As a another example, we’ll change the first word in s to all uppercase:
// process characters in s until we run out of characters or we hit a whitespace
for (decltype(s.size()) index = 0;
index != s.size() && !isspace(s[index]); ++index)
s[index] = toupper(s[index]); // capitalize the current character
• This program generates
SOME string
Using a Subscript for Random Access
• As an example, let’s assume we have a number between 0 and 15 and we want to generate the hexadecimal
representation of that number.
• We can do so using a string that is initialized to hold the 16 hexadecimal “digits”:
const string hexdigits = "0123456789ABCDEF"; // possible hex digits
cout << "Enter a series of numbers between 0 and 15" << " separated by spaces.
Hit ENTER when finished: " << endl;
string result; // will hold the resulting hexify'd string
string::size_type n; // hold numbers from the input
while (cin >> n)
if (n < hexdigits.size()) // ignore invalid input
result += hexdigits[n]; // fetch the indicated hex digit
cout << "Your hex number is: " << result << endl;
• If we give this program the input
12 0 5 15 8 15
• the output will be
Your hex number is: C05F8F
Exercises Section 3.2.3
• Exercise 3.6: Use a range for to change all the characters in a string to X.
• Exercise 3.7: What would happen if you define the loop control variable in the previous exercise as type
char? Predict the results and then change your program to use a char to see if you were right.
• Exercise 3.8: Rewrite the program in the first exercise, first using a while and again using a traditional
for loop. Which of the three approaches do you prefer and why?
• Exercise 3.9: What does the following program do? Is it valid? If not, why not?
string s;
cout << s[0] << endl;
• Exercise 3.10: Write a program that reads a string of characters including punctuation and writes what was
read but with the punctuation removed.
• Exercise 3.11: Is the following range for legal? If so, what is the type of c?
const string s = "Keep out!";
for (auto &c : s) { /* ... */ }
3.3. Library vector Type
• To use a vector, we must include the appropriate header.
• In our examples, we also assume that an appropriate using declaration is made:
#include <vector>
using std::vector;
• A vector is a class template. C++ has both class and function templates.
• Writing a template requires a fairly deep understanding of C++.
• Indeed, we won’t see how to create our own templates until Chapter 16! Fortunately, we can use templates
without knowing how to write them.
• Templates are not themselves functions or classes.
• Instead, they can be thought of as instructions to the compiler for generating classes or functions.
• The process that the compiler uses to create classes or functions from templates is called instantiation.
• When we use a template, we specify what kind of class or function we want the compiler to instantiate.
3.3. Library vector Type
• For a class template, we specify which class to instantiate by supplying additional information, the
nature of which depends on the template.
• How we specify the information is always the same: We supply it inside a pair of angle brackets
following the template’s name.
• In the case of vector, the additional information we supply is the type of the objects the
vector will hold:
vector<int> ivec; // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
vector<vector<string>> file; // vector whose elements are vectors
• In this example, the compiler generates three distinct types from the vector template:
vector<int>, vector<Sales_item>, and vector<vector<string>>.
3.3. Library vector Type
• It is worth noting that earlier versions of C++ used a slightly different syntax to define a
vector whose elements are themselves vectors (or another template type).
• In the past, we had to supply a space between the closing angle bracket of the outer vector
and its element type—vector<vector<int> > rather than vector<vector<int>>.
• Warning
• Some compilers may require the old-style declarations for a vector of vectors, for example,
vector<vector<int> >.
3.3.1. Defining and Initializing vectors
• We can default initialize a vector (§ 2.2.1), which creates an empty vector of the specified type:
vector<string> svec; // default initialization; svec has no elements
• It might seem that an empty vector would be of little use.
• However, as we’ll see shortly, we can (efficiently) add elements to a vector We can also supply initial
value(s) for the element(s) when we define a vector.
• For example, we can copy elements from another vector.
• When we copy a vector, each element in the new vector is a copy of the corresponding element in
the original vector.
• The two vectors must be the same type:
vector<int> ivec; // initially empty
// give ivec some values
vector<int> ivec2(ivec); // copy elements of ivec into ivec2
vector<int> ivec3 = ivec; // copy elements of ivec into ivec3
vector<string> svec(ivec2); // error: svec holds strings, not ints
Table 3.4. Ways to Initialize a vector
List Initializing a vector
• Another way to provide element values, is that under the new standard, we can list initialize (§
2.2.1) a vector from a list of zero or more initial element values enclosed in curly braces:
vector<string> articles = {"a", "an", "the"};
• The resulting vector has three elements; the first holds the string "a", the second holds
"an", and the last is "the".
vector<string> v1{"a", "an", "the"}; // list initialization
vector<string> v2("a", "an", "the"); // error
Creating a Specified Number of Elements
• We can also initialize a vector from a count and an element value.
• The count determines how many elements the vector will have; the value provides the initial value for each of
those elements:
vector<int> ivec(10, -1); // ten int elements, each initialized to -1
vector<string> svec(10, "hi!"); // ten strings; each element is "hi!"
Value Initialization
• If the vector holds elements of a built-in type, such as int, then the element initializer has
a value of 0.
• If the elements are of a class type, such as string, then the element initializer is itself
default initialized:
vector<int> ivec(10); // ten elements, each initialized to 0
vector<string> svec(10); // ten elements, each an empty string
• There are two restrictions on this form of initialization: The first restriction is that some classes
require that we always supply an explicit initializer (§ 2.2.1).
• If our vector holds objects of a type that we cannot default initialize, then we must supply an
initial element value; it is not possible to create vectors of such types by supplying only a size.
• The second restriction is that when we supply an element count without also supplying an initial
value, we must use the direct form of initialization:
vector<int> vi = 10; // error: must use direct initialization to supply a size
List Initializer or Element Count?
• In a few cases, what initialization means depends upon whether we use curly braces or
parentheses to pass the initializer(s).
• For example, when we initialize a vector<int> from a single int value, that value might
represent the vector’s size or it might be an element value.
vector<int> v1(10); // v1 has ten elements with value 0
vector<int> v2{10}; // v2 has one element with value 10
vector<int> v3(10, 1); // v3 has ten elements with value 1
vector<int> v4{10, 1}; // v4 has two elements with values 10 and 1
List Initializer or Element Count?
• On the other hand, if we use braces and there is no way to use the initializers to list initialize the
object, then those values will be used to construct the object.
• For example, to list initialize a vector of strings, we must supply values that can be used
as strings.
• In this case, there is no confusion about whether to list initialize the elements or construct a
vector of the given size:
vector<string> v5{"hi"}; // list initialization: v5 has one element
vector<string> v6("hi"); // error: can't construct a vector from a string literal
vector<string> v7{10}; // v7 has ten default-initialized elements
vector<string> v8{10, "hi"}; // v8 has ten elements with value "hi"
Exercises Section 3.3.1
• Exercise 3.12: Which, if any, of the following vector definitions are in error? For those that
are legal, explain what the definition does. For those that are not legal, explain why they are
illegal.
(a) vector<vector<int>> ivec;
(b) vector<string> svec = ivec;
(c) vector<string> svec(10, "null");
• Exercise 3.13: How many elements are there in each of the following vectors? What are the
values of the elements?
(a) vector<int> v1;
(b) vector<int> v2(10);
(c) vector<int> v3(10, 42);
(d) vector<int> v4{10};
(e) vector<int> v5{10, 42};
(f) vector<string> v6{10};
(g) vector<string> v7{10, "hi"};
3.3.2. Adding Elements to a vector
• As one example, if we need a vector with values from 0 to 9, we can easily use list
initialization.
• What if we wanted elements from 0 to 99 or 0 to 999? List initialization would be too unwieldy.
• In such cases, it is better to create an empty vector and use a vector member named
push_back to add elements at run time.
• The push_back operation takes a value and “pushes” that value as a new last element onto
the “back” of the vector. For example:
vector<int> v2; // empty vector
for (int i = 0; i != 100; ++i)
v2.push_back(i); // append sequential integers to v2
// at end of loop v2 has 100 elements, values 0 . . . 99
• Even though we know we ultimately will have 100 elements, we define v2 as empty.
• Each iteration adds the next sequential integer as a new element in v2.
3.3.2. Adding Elements to a vector
• We use the same approach when we want to create a vector where we don’t know until run
time how many elements the vector should have.
• For example, we might read the input, storing the values we read in the vector:
// read words from the standard input and store them as elements in a vector
string word;
vector<string> text; // empty vector
while (cin >> word) {
text.push_back(word); // append word to text
}
• Again, we start with an initially empty vector. This time, we read and store an unknown number
of values in text.
Exercise Section 3.3.2
• Exercise 3.14: Write a program to read a sequence of ints from cin and store those values in
a vector.
• Exercise 3.15: Repeat the previous program but read strings this time.
3.3.3. Other vector Operation
• We access the elements of a vector the same way that we access the characters in a
string: through their position in the vector. For example, we can use a range for (§
3.2.3) to process all the elements in a vector:
vector<int> v{1,2,3,4,5,6,7,8,9};
for (auto &i : v) // for each element in v (note: i is a reference)
i *= i; // square the element value
for (auto i : v) // for each element in v
cout << i << " "; // print the element
cout << endl;
• In the first loop, we define our control variable, i, as a reference so that we can use i to assign
new values to the elements in v. We let auto deduce the type of i.
• This loop uses a new form of the compound assignment operator (§ 1.4.1).
Table 3.5: vector Operations
Computing a vector Index
• As an example, let’s assume that we have a collection of grades that range from 0 through 100.
We’d like to count how many grades fall into various clusters of 10.
• Between zero and 100 there are 101 possible grades.
• These grades can be represented by 11 clusters: 10 clusters of 10 grades each plus one cluster for
the perfect score of 100.
• The first cluster will count grades of 0 through 9, the second will count grades from 10 through 19,
and so on. The final cluster counts how many scores of 100 were achieved.
• Clustering the grades this way, if our input is
42 65 95 100 39 67 95 76 88 76 83 92 76 93
• then the output should be
00011023241
Computing a vector Index
// count the number of grades by clusters of ten: 0--9, 10--19, . .. 90--99, 100
vector<unsigned> scores(11, 0); // 11 buckets, all initially 0
unsigned grade;
while (cin >> grade) { // read the grades
if (grade <= 100) // handle only valid grades
++scores[grade/10]; // increment the counter for the current cluster
}
• We start by defining a vector to hold the cluster counts. In this case, we do want each
element to have the same value, so we allocate all 11 elements, each of which is initialized to 0.
• The while condition reads the grades. Inside the loop, we check that the grade we read has a
valid value (i.e., that it is less than or equal to 100).
• Assuming the grade is valid, we increment the appropriate counter for grade.
Subscripting Does Not Add Elements
• Programmers new to C++ sometimes think that subscripting a vector adds elements; it does
not.
• The following code intends to add ten elements to ivec:
vector<int> ivec; // empty vector
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
ivec[ix] = ix; // disaster: ivec has no elements
• However, it is in error: ivec is an empty vector; there are no elements to subscript!
• As we’ve seen, the right way to write this loop is to use push_back:
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
ivec.push_back(ix); // ok: adds a new element with value ix
Exercises Section 3.3.3
• Exercise 3.16: Write a program to print the size and contents of the vectors from exercise 3.13.
Check whether your answers to that exercise were correct. If not, restudy § 3.3.1 until you
understand why you were wrong.
• Exercise 3.17: Read a sequence of words from cin and store the values a vector. After
you’ve read all the words, process the vector and change each word to uppercase. Print the
transformed elements, eight words to a line.
• Exercise 3.18: Is the following program legal? If not, how might you fix it?
vector<int> ivec;
ivec[0] = 42;
• Exercise 3.19: List three ways to define a vector and give it ten elements, each with the
value 42. Indicate whether there is a preferred way to do so and why.
• Exercise 3.20: Read a set of integers into a vector. Print the sum of each pair of adjacent
elements. Change your program so that it prints the sum of the first and last elements, followed
by the sum of the second and second-tolast, and so on.
3.4. Introducing Iterators
• Although we can use subscripts to access the characters of a string or the elements in a vector,
there is a more general mechanism—known as iterators—that we can use for the same purpose.
• Technically speaking, a string is not a container type, but string supports many of the container
operations.
• As we’ve seen string, like vector has a subscript operator.
• Like vectors, strings also have iterators.
• Like pointers (§ 2.3.2), iterators give us indirect access to an object.
• In the case of an iterator, that object is an element in a container or a character in a string.
• We can use an iterator to fetch an element and iterators have operations to move from one element to
another. As with pointers, an iterator may be valid or invalid.
• A valid iterator either denotes an element or denotes a position one past the last element in a container.
All other iterator values are invalid.
3.4.1. Using Iterators
• Unlike pointers, we do not use the address-of operator to obtain an iterator.
• Instead, types that have iterators have members that return iterators.
• In particular, these types have members named begin and end.
• The begin member returns an iterator that denotes the first element (or first character), if
there is one:
// the compiler determines the type of b and e; see § 2.5.2 (p. 68)
// b denotes the first element and e denotes one past the last element in v
auto b = v.begin(), e = v.end(); // b and e have the same type
Iterator Operations
• As an example, we’ll rewrite the program from § 3.2.3 that capitalized the first character of a
string using an iterator instead of a subscript:
string s("some string");
if (s.begin() != s.end()) { // make sure s is not empty
auto it = s.begin(); // it denotes the first character in s
*it = toupper(*it); // make that character uppercase
}
• As in our original program, we first check that s isn’t empty. In this case, we do so by comparing
the iterators returned by begin and end.
• Those iterators are equal if the string is empty.
• If they are unequl, there is at least one character in s.
Table 3.6. Standard Container Iterator Operations
Moving Iterators from One Element to Another
• Iterators use the increment (++) operator (§ 1.4.1) to move from one element to the next.
• Incrementing an iterator is a logically similar operation to incrementing an integer.
• In the case of integers, the effect is to “add 1” to the integer’s value.
• In the case of iterators, the effect is to “advance the iterator by one position.”
• Using the increment operator, we can rewrite our program that changed the case of the first word
in a string to use iterators instead:
// process characters in s until we run out of characters or we hit a whitespace
for (auto it = s.begin(); it != s.end() && !isspace(*it);
++it)
*it = toupper(*it); // capitalize the current character
Iterator Types
• Just as we do not know the precise type of a vector’s or string’s size_type member (§
3.2.2), so too, we generally do not know—and do not need to know—the precise type of an iterator.
• Instead, as with size_type, the library types that have iterators define types named
iterator and const_iterator that represent actual iterator types:
vector<int>::iterator it; // it can read and write vector<int> elements
string::iterator it2; // it2 can read and write characters in a string
vector<int>::const_iterator it3; // it3 can read but not write elements
string::const_iterator it4; // it4 can read but not write characters
The begin and end Operations
• The type returned by begin and end depends on whether the object on which they operator is const.
• If the object is const, then begin and end return a const_iterator; if the object is not
const, they return iterator:
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1 has type vector<int>::iterator
auto it2 = cv.begin(); // it2 has type vector<int>::const_iterator
• Often this default behavior is not what we want.
• For reasons we’ll explain in § 6.2.3, it is usually best to use a const type (such as const_iterator)
when we need to read but do not need to write to an object.
• To let us ask specifically for the const_iterator type, the new standard introduced two new
functions named cbegin and cend:
auto it3 = v.cbegin(); // it3 has type vector<int>::const_iterator
Combining Dereference and Member Access
• For example, we might have a vector of strings and we might need to know whether a
given element is empty.
• Assuming it is an iterator into this vector, we can check whether the string that it
denotes is empty as follows:
(*it).empty()
• For reasons we’ll cover in § 4.1.2, the parentheses in (*it).empty() are necessary.
• The parentheses say to apply the dereference operator to it and to apply the dot operator (§
1.5.2) to the result of dereferencing it.
• Without parentheses, the dot operator would apply to it, not to the resulting object:
(*it).empty() // dereferences it and calls the member empty on the resulting object
*it.empty() // error: attempts to fetch the member named empty from it // but it is an iterator and
has no member named empty
Some vector Operations Invalidate Iterators
• In § 3.3.2 we noted that there are implications of the fact that vectors can grow dynamically.
• We also noted that one such implication is that we cannot add elements to a vector inside a
range for loop.
• Another implication is that any operation, such as push_back, that changes the size of a
vector potentially invalidates all iterators into that vector.
• We’ll explore how iterators become invalid in more detail in § 9.3.6.
Exercises Section 3.4.1
• Exercise 3.21: Redo the first exercise from § 3.3.3 using iterators.
• Exercise 3.22: Revise the loop that printed the first paragraph in text to instead change the
elements in text that correspond to the first paragraph to all uppercase. After you’ve updated
text, print its contents.
• Exercise 3.23: Write a program to create a vector with ten int elements. Using an iterator,
assign each element a value that is twice its current value. Test your program by printing the
vector.
3.4.2. Iterator Arithmetic
• Incrementing an iterator moves the iterator one element at a time.
• All the library containers have iterators that support increment.
• Similarly, we can use == and != to compare two valid iterators (§ 3.4) into any of the library
container types.
• Iterators for string and vector support additional operations that can move an iterator
multiple elements at a time.
• They also support all the relational operators.
• These operations, which are often referred to as iterator arithmetic, are described in Table 3.7.
Table 3.7. Operations Supported by vector and string Iterators
Arithmetic Operations on Iterators
• We can add (or subtract) an integral value and an iterator.
• Doing so returns an iterator positioned forward (or backward) that many elements.
• When we add or subtract an integral value and an iterator, the result must denote an element in
the same vector (or string) or denote one past the end of the associated vector (or
string).
• As an example, we can compute an iterator to the element nearest the middle of a vector:
// compute an iterator to the element closest to the midpoint of vi
auto mid = vi.begin() + vi.size() / 2;
• If vi has 20 elements, then vi.size()/2 is 10. In this case, we’d set mid equal to
vi.begin() + 10.
• Remembering that subscripts start at 0, this element is the same as vi[10], the element ten
past the first.
Using Iterator Arithmetic
• We can do a binary search using iterators as follows:
// text must be sorted
// beg and end will denote the range we're searching
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // original midpoint
// while there are still elements to look at and we haven't yet found sought
while (mid != end && *mid != sought) {
if (sought < *mid) // is the element we want in the first half?
end = mid; // if so, adjust the range to ignore the second half
else // the element we want is in the second half
beg = mid + 1; // start looking with the element just after mid
mid = beg + (end - beg)/2; // new midpoint
}
Exercises Section 3.4.2
• Exercise 3.24: Redo the last exercise from § 3.3.3 using iterators.
• Exercise 3.25: Rewrite the grade clustering program from § 3.3.3 using iterators instead of
subscripts.
• Exercise 3.26: In the binary search program on page 112, why did we write mid = beg +
(end - beg) / 2; instead of mid = (beg + end) /2;?
3.5. Arrays
• An array is a data structure that is similar to the library vector type (§ 3.3) but offers a
different trade-off between performance and flexibility.
• Like a vector, an array is a container of unnamed objects of a single type that we access by
position.
• Unlike a vector, arrays have fixed size; we cannot add elements to an array.
• Because arrays have fixed size, they sometimes offer better run-time performance for specialized
applications.
• However, that run-time advantage comes at the cost of lost flexibility.
3.5.1. Defining and Initializing Built-in Arrays
• Arrays are a compound type (§ 2.3).
An array declarator has the form a[d], where a is the name being defined and d is the dimension of
the array.
• The dimension specifies the number of elements and must be greater than zero.
• The number of elements in an array is part of the array’s type.
• As a result, the dimension must be known at compile time, which means that the dimension must be a
constant expression (§ 2.4.4):
unsigned cnt = 42; // not a constant expression
constexpr unsigned sz = 42; // constant expression
// constexpr see § 2.4.4
int arr[10]; // array of ten ints
int *parr[sz]; // array of 42 pointers to int
string bad[cnt]; // error: cnt is not a constant expression
string strs[get_size()]; // ok if get_size is constexpr, error otherwise
Explicitly Initializing Array Elements
• We can list initialize (§ 3.3.1, p. 98) the elements in an array. When we do so, we can omit the
dimension.
• If we omit the dimension, the compiler infers it from the number of initializers.
• If we specify a dimension, the number of initializers must not exceed the specified size.
• If the dimension is greater than the number of initializers, the initializers are used for the first
elements and any remaining elements are value initialized (§ 3.3.1):
const unsigned sz = 3;
int ia1[sz] = {0,1,2}; // array of three ints with values 0, 1, 2
int a2[] = {0, 1, 2}; // an array of dimension 3
int a3[5] = {0, 1, 2}; // equivalent to a3[] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // same as a4[] = {"hi", "bye", ""}
int a5[2] = {0,1,2}; // error: too many initializers
Character Arrays Are Special
• Character arrays have an additional form of initialization: We can initialize such arrays from a
string literal (§ 2.1.3, p. 39).
• When we use this form of initialization, it is important to remember that string literals end with a
null character.
• That null character is copied into the array along with the characters in the literal:
char a1[] = {'C', '+', '+'}; // list initialization, no null
char a2[] = {'C', '+', '+', '\0'}; // list initialization, explicit null
char a3[] = "C++"; // null terminator added automatically
const char a4[6] = "Daniel"; // error: no space for the null!
No Copy or Assignment
• We cannot initialize an array as a copy of another array, nor is it legal to assign one array to
another:
int a[] = {0, 1, 2}; // array of three ints
int a2[] = a; // error: cannot initialize one array with another
a2 = a; // error: cannot assign one array to another
Understanding Complicated Array Declarations
• Like vectors, arrays can hold objects of most any type.
• For example, we can have an array of pointers.
• Because an array is an object, we can define both pointers and references to arrays.
• Defining arrays that hold pointers is fairly straightforward,
• defining a pointer or reference to an array is a bit more complicated:
int *ptrs[10]; // ptrs is an array of ten pointers to int
int &refs[10] = /* ? */; // error: no arrays of references
int (*Parray)[10] = &arr; // Parray points to an array of ten ints
int (&arrRef)[10] = arr; // arrRef refers to an array of ten ints
int *(&arry)[10] = ptrs; // arry is a reference to an array of ten pointers
Exercise Section 3.5.1
Exercise 3.27: Assuming txt_size is a function that takes no arguments
and returns an int value, which of the following definitions are illegal? Explain why.
unsigned buf_size = 1024;
(a) int ia[buf_size];
(b) int ia[4 * 7 - 14];
(c) int ia[txt_size()];
(d) char st[11] = "fundamental";
Exercise 3.28: What are the values in the following arrays?
string sa[10];
int ia[10];
int main() {
string sa2[10];
int ia2[10];
}
Exercise 3.29: List some of the drawbacks of using an array instead of a vector.
3.5.3. Pointers and Arrays
• Normally, we obtain a pointer to an object by using the address-of operator (§ 2.3.2).
• Generally speaking, the address-of operator may be applied to any object.
• The elements in an array are objects. When we subscript an array, the result is the object at that
location in the array.
• As with any other object, we can obtain a pointer to an array element by taking the address of
that element:
string nums[] = {"one", "two", "three"}; // array of strings
string *p = &nums[0]; // p points to the first element in nums
• However, arrays have a special property—in most places when we use an array, the compiler
automatically substitutes a pointer to the first element:
string *p2 = nums; // equivalent to p2 = &nums[0]
3.5.3. Pointers and Arrays
• There are various implications of the fact that operations on arrays are often really operations on pointers.
• One such implication is that when we use an array as an initializer for a variable defined using auto (§
2.5.2), the deduced type is a pointer, not an array:
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
auto ia2(ia); // ia2 is an int* that points to the first element in ia
ia2 = 42; // error: ia2 is a pointer, and we can't assign an int to a pointer
• Although ia is an array of ten ints, when we use ia as an initializer, the compiler treats that
initialization as if we had written
auto ia2(&ia[0]); // now it's clear that ia2 has type int*
• It is worth noting that this conversion does not happen when we use decltype (§2.5.3). The type
returned by decltype(ia) is array of ten ints:
// ia3 is an array of ten ints
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; // error: can't assign an int* to an array
ia3[4] = i; // ok: assigns the value of i to an element in ia3
Pointers Are Iterators
• For example, we can use the increment operator to move from one element in an array to the next:
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr; // p points to the first element in arr
++p; // p points to arr[1]
• We can obtain an off-the-end pointer by using another special property of arrays.
• We can take the address of the nonexistent element one past the last element of an array:
int *e = &arr[10]; // pointer just past the last element in arr
• Using these pointers we can write a loop to print the elements in arr as follows:
for (int *b = arr; b != e; ++b)
cout << *b << endl; // print the elements in arr
The Library begin and end Functions
• Although we can compute an off-the-end pointer, doing so is error-prone.
• To make it easier and safer to use pointers, the new library includes two functions, named begin
and end.
• These functions act like the similarly named container members (§ 3.4.1).
• However, arrays are not class types, so these functions are not member functions.
• Instead, they take an argument that is an array:
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints
int *beg = begin(ia); // pointer to the first element in ia
int *last = end(ia); // pointer one past the last element in ia
Pointer Arithmetic
• When we add (or subtract) an integral value to (or from) a pointer, the result is a new pointer.
• That new pointer points to the element the given number ahead of (or behind) the original pointer:
constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *ip = arr; // equivalent to int *ip = &arr[0]
int *ip2 = ip + 4; // ip2 points to arr[4], the last element in arr
// ok: arr is converted to a pointer to its first element; p points one past the end of arr
int *p = arr + sz; // use caution -- do not dereference!
int *p2 = arr + 10; // error: arr has only 5 elements; p2 has undefined value
auto n = end(arr) - begin(arr); // n is 5, the number of elements in arr
Interaction between Dereference and Pointer Arithmetic
• The result of adding an integral value to a pointer is itself a pointer.
• Assuming the resulting pointer points to an element, we can dereference the resulting pointer:
int ia[] = {0,2,4,6,8}; // array with 5 elements of type int
int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]
• The expression *(ia + 4) calculates the address four elements past ia and dereferences
the resulting pointer.
• This expression is equivalent to writing ia[4].
• Recall that in § 3.4.1 we noted that parentheses are required in expressions that contain
dereference and dot operators.
• Similarly, the parentheses around this pointer addition are essential.
last = *ia + 4; // ok: last = 4, equivalent to ia[0] + 4
Subscripts and Pointers
• One place where the compiler does this transformation is when we subscript an array. Given
int ia[] = {0,2,4,6,8}; // array with 5 elements of type int
• if we write ia[0], that is an expression that uses the name of an array.
• When we subscript an array, we are really subscripting a pointer to an element in that array:
int i = ia[2]; // ia is converted to a pointer to the first element in ia
// ia[2] fetches the element to which (ia + 2) points
int *p = ia; // p points to the first element in ia
i = *(p + 2); // equivalent to i = ia[2]
Subscripts and Pointers
int *p = &ia[2]; // p points to the element indexed by 2
int j = p[1]; // p[1] is equivalent to *(p + 1),
// p[1] is the same element as ia[3]
int k = p[-2]; // p[-2] is the same element as ia[0]
Exercises Section 3.5.3
• Exercise 3.34: Given that p1 and p2 point to elements in the same array, what does the
following code do? Are there values of p1 or p2 that make this code illegal?
p1 += p2 - p1;
• Exercise 3.35: Using pointers, write a program to set the elements in an array to zero.
• Exercise 3.36: Write a program to compare two arrays for equality. Write a similar program to
compare two vectors.
3.5.4. C-Style Character Strings
• Character string literals are an instance of a more general construct that C++ inherits from C: C-
style character strings.
• C-style strings are not a type. Instead, they are a convention for how to represent and use
character strings.
• Strings that follow this convention are stored in character arrays and are null terminated.
• By nullterminated we mean that the last character in the string is followed by a null character
('\0').
• Ordinarily we use pointers to manipulate these strings.
C Library String Functions
• The Standard C library provides a set of functions, listed in Table 3.8, that operate on C-style
strings.
• These functions are defined in the cstring header, which is the C++ version of the C header
string.h.
• The pointer(s) passed to these routines must point to null-terminated array(s):
char ca[] = {'C', '+', '+'}; // not null terminated
cout << strlen(ca) << endl; // disaster: ca isn't null terminated
• In this case, ca is an array of char but is not null terminated.
• The result is undefined.
• The most likely effect of this call is that strlen will keep looking through the memory that
follows ca until it encounters a null character.
Table 3.8. C-Style Character String Functions
Comparing Strings
• Comparing two C-style strings is done quite differently from how we compare library strings.
When we compare two library strings, we use the normal relational or equality operators:
string s1 = "A string example";
string s2 = "A different string";
if (s1 < s2) // false: s2 is less than s1
• Using these operators on similarly defined C-style strings compares the pointer values, not the
strings themselves:
const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (ca1 < ca2) // undefined: compares two unrelated addresses
• To compare the strings, rather than the pointer values, we can call strcmp. That function
returns 0 if the strings are equal, or a positive or negative value, depending on whether the first
string is larger or smaller than the second:
if (strcmp(ca1, ca2) < 0) // same effect as string comparison s1 < s2
Caller Is Responsible for Size of a Destination String
• Concatenating or copying C-style strings is also very different from the same operations on library
strings.
• For example, if we wanted to concatenate the two strings s1 and s2 defined above, we can
do so directly:
// initialize largeStr as a concatenation of s1, a space, and s2
string largeStr = s1 + " " + s2;
• Doing the same with our two arrays, ca1 and ca2, would be an error.
• The expression ca1 + ca2 tries to add two pointers, which is illegal and meaningless.
• Instead we can use strcat and strcpy.
• However, to use these functions, we must pass an array to hold the resulting string.
• The array we pass must be large enough to hold the generated string, including the null character
at the end.
Caller Is Responsible for Size of a Destination String
• The code we show here, although a common usage pattern, is fraught with potential for serious
error:
// disastrous if we miscalculated the size of largeStr
strcpy(largeStr, ca1); // copies ca1 into largeStr
strcat(largeStr, " "); // adds a space at the end of largeStr
strcat(largeStr, ca2); // concatenates ca2 onto largeStr
• The problem is that we can easily miscalculate the size needed for largeStr.
• Moreover, any time we change the values we want to store in largeStr, we have to remember
to double-check that we calculated its size correctly.
• Unfortunately, programs similar to this code are widely distributed. Programs with such code are
error-prone and often lead to serious security leaks.
Exercises Section 3.5.4
• Exercise 3.37: What does the following program do?
const char ca[] = {'h', 'e', 'l', 'l', 'o'};
const char *cp = ca;
while (*cp) {
cout << *cp << endl;
++cp;
}
• Exercise 3.38: In this section, we noted that it was not only illegal but meaningless to try to add
two pointers. Why would adding two pointers be meaningless?
• Exercise 3.39: Write a program to compare two strings. Now write a program to compare the
values of two C-style character strings.
• Exercise 3.40: Write a program to define two character arrays initialized from string literals. Now
define a third character array to hold the concatenation of the two arrays. Use strcpy and
strcat to copy the two arrays into the third.
3.5.5. Interfacing to Older Code
• Many C++ programs predate the standard library and do not use the string and vector
types.
• Moreover, many C++ programs interface to programs written in C or other languages that cannot
use the C++ library.
• Hence, programs written in modern C++ may have to interface to code that uses arrays and/or C-
style character strings.
• The C++ library offers facilities to make the interface easier to manage.
Mixing Library strings and C-Style Strings
• In § 3.2.1 we saw that we can initialize a string from a string literal:
string s("Hello World"); // s holds Hello World
• More generally, we can use a null-terminated character array anywhere that we can use a string
literal:
• We can use a null-terminated character array to initialize or assign a string.
• We can use a null-terminated character array as one operand (but not both operands) to the string addition
operator or as the right-hand operand in the string compound assignment (+=) operator.
• The reverse functionality is not provided: There is no direct way to use a library string when a
C-style string is required.
• For example, there is no way to initialize a character pointer from a string.
Mixing Library strings and C-Style Strings
• There is, however, a string member function named c_str that we can often use to
accomplish what we want:
char *str = s; // error: can't initialize a char* from a string
const char *str = s.c_str(); // ok
• The name c_str indicates that the function returns a C-style character string.
• That is, it returns a pointer to the beginning of a null-terminated character array that holds the
same data as the characters in the string.
• The type of the pointer is const char*, which prevents us from changing the contents of the
array.
Using an Array to Initialize a vector
• In § 3.5.1 we noted that we cannot initialize a built-in array from another array.
• Nor can we initialize an array from a vector.
• However, we can use an array to initialize a vector.
• To do so, we specify the address of the first element and one past the last element that we wish
to copy:
int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec has six elements; each is a copy of the corresponding element in int_arr
vector<int> ivec(begin(int_arr), end(int_arr));
• The two pointers used to construct ivec mark the range of values to use to initialize the
elements in ivec.
• The second pointer points one past the last element to be copied.
• In this case, we used the library begin and end functions (§ 3.5.3) to pass pointers to the
first and one past the last elements in int_arr.
Using an Array to Initialize a vector
• As a result, ivec will have six elements each of which will have the same value as the
corresponding element in int_arr.
• The specified range can be a subset of the array:
// copies three elements: int_arr[1], int_arr[2], int_arr[3]
vector<int> subVec(int_arr + 1, int_arr + 4);
• This initialization creates subVec with three elements. The values of these elements are copies
of the values in int_arr[1] through int_arr[3].
3.6. Multidimensional Arrays
• Strictly speaking, there are no multidimensional arrays in C++.
• What are commonly referred to as multidimensional arrays are actually arrays of arrays.
• It can be helpful to keep this fact in mind when you use what appears to be a multidimensional
array.
• We define an array whose elements are arrays by providing two dimensions: the dimension of the
array itself and the dimension of its elements:
int ia[3][4]; // array of size 3; each element is an array of ints of size 4
// array of size 10; each element is a 20-element array whose elements are arrays of 30 ints
int arr[10][20][30] = {0}; // initialize all elements to 0
Initializing the Elements of a Multidimensional Array
• As with any array, we can initialize the elements of a multidimensional array by providing a
bracketed list of initializers.
• Multidimensional arrays may be initialized by specifying bracketed values for each row:
int ia[3][4] = { // three elements; each element is an array of size 4
{0, 1, 2, 3}, // initializers for the row indexed by 0
{4, 5, 6, 7}, // initializers for the row indexed by 1
{8, 9, 10, 11} // initializers for the row indexed by 2
};
• The nested braces are optional.
• The following initialization is equivalent, although considerably less clear:
// equivalent initialization without the optional nested braces for each row
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
Initializing the Elements of a Multidimensional Array
• As is the case for single-dimension arrays, elements may be left out of the initializer list.
• We can initialize only the first element of each row as follows:
// explicitly initialize only element 0 in each row
int ia[3][4] = {{ 0 }, { 4 }, { 8 }};
• The remaining elements are value initialized in the same way as ordinary, singledimension arrays (§
3.5.1).
• If the nested braces were omitted, the results would be very different. This code
// explicitly initialize row 0; the remaining elements are value initialized
int ix[3][4] = {0, 3, 6, 9};
• initializes the elements of the first row.
• The remaining elements are initialized to 0.
Subscripting a Multidimensional Array
• If we supply fewer subscripts than there are dimensions, then the result is the inner-array
element at the specified index:
// assigns the first element of arr to the last element in the last row of ia
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1]; // binds row to the second four-element array in ia
• In the first example we supply indices for all the dimensions for both arrays.
• On the left-hand side, ia[2] returns the last row in ia.
• It does not fetch an element from that array but returns the array itself.
• We subscript that array, fetching element [3], which is the last element in that array.
• In the second example, we define row as a reference to an array of four ints.
• We bind that reference to the second row in ia.
Subscripting a Multidimensional Array
• As another example, it is common to use a pair of nested for loops to process the elements in a multidimensional
array:
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 12 uninitialized elements
// for each row
for (size_t i = 0; i != rowCnt; ++i) {
// for each column within the row
for (size_t j = 0; j != colCnt; ++j) {
// assign the element's positional index as its value
ia[i][j] = i * colCnt + j;
}
}
• The outer for loops through each of the array elements in ia. The inner for loops through the elements of
those interior arrays.
• In this case, we set the value of each element as its index in the overall array.
Using a Range for with Multidimensional Arrays
• Under the new standard we can simplify the previous loop by using a range for:
size_t cnt = 0;
for (auto &row : ia) // for every element in the outer array
for (auto &col : row) { // for every element in the inner array
col = cnt; // give this element the next value
++cnt; // increment cnt
}
• As an example, consider the following loop:
for (const auto &row : ia) // for every element in the outer array
for (auto col : row) // for every element in the inner array
cout << col << endl;
• Had we neglected the reference and written these loops as:
for (auto row : ia)
for (auto col : row)
Pointer and Multidimensional Arrays
• As with any array, when we use the name of a multidimensional array, it is automatically
converted to a pointer to the first element in the array.
• Because a multidimensional array is really an array of arrays, the pointer type to which the array
converts is a pointer to the first inner array:
int ia[3][4]; // array of size 3; each element is an array of ints of size 4
int (*p)[4] = ia; // p points to an array of four ints
p = &ia[2]; // p now points to the last element in ia
• Applying the strategy from § 3.5.1, we start by noting that (*p) says p is a pointer.
• Looking right, we see that the object to which p points has a dimension of size 4, and looking
left that the element type is int.
• Hence, p is a pointer to an array of four ints.
Pointer and Multidimensional Arrays
• With the advent of the new standard, we can often avoid having to write the type of a pointer into
an array by using auto or decltype (§ 2.5.2):
// print the value of each element in ia, with each inner array on its own line
// p points to an array of four ints
for (auto p = ia; p != ia + 3; ++p) {
// q points to the first element of an array of four ints; that is, q points to an int
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
Pointer and Multidimensional Arrays
• Of course, we can even more easily write this loop using the library begin and end functions
(§ 3.5.3):
// p points to the first array in ia
for (auto p = begin(ia); p != end(ia); ++p) {
// q points to the first element in an inner array
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q << ' '; // prints the int value to which q points
cout << endl;
}
Type Aliases Simplify Pointers to Multidimensional Arrays
• A type alias (§ 2.5.1) can make it easier to read, write, and understand pointers to multidimensional
arrays.
• For example:
using int_array = int[4]; // new style type alias declaration; see § 2.5.1
typedef int int_array[4]; // equivalent typedef declaration; § 2.5.1
// print the value of each element in ia, with each inner array on its own line
for (int_array *p = ia; p != ia + 3; ++p) {
for (int *q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
Exercises Section 3.6
• Exercise 3.43: Write three different versions of a program to print the elements of ia. One
version should use a range for to manage the iteration, the other two should use an ordinary
for loop in one case using subscripts and in the other using pointers. In all three programs write
all the types directly. That is, do not use a type alias, auto, or decltype to simplify the code.
• Exercise 3.44: Rewrite the programs from the previous exercises using a type alias for the type
of the loop control variables.
• Exercise 3.45: Rewrite the programs again, this time using auto.