Exercise 3.1a (Rewrite of exercise 1.9 with using declarations)
Exercise 3.1b (Rewrite of exercise 1.10 with using declarations)
Exercise 3.1c (Rewrite of exercise 1.11 with using declarations)
Exercise 3.1d (Rewrite of exercise 2.41a with using declarations)
Exercise 3.1e (Rewrite of exercise 2.41b with using declarations)
Exercise 3.1f (Rewrite of exercise 2.41c with using declarations)
Exercise 3.1g (Rewrite of exercise 2.41d with using declarations)
Exercise 3.1h (Rewrite of exercise 2.41f with using declarations)
Exercise 3.2a Read from standard input a line at a time.
Exercise 3.2b Read from standard input a word at a time.
The string input operator trims leading and trailing white space and returns only the characters between. For example the if the user entered the string
There are 5 spaces at the start of this line and one at the end
the input operator would trim the white-space from the beginning of the string (the first five spaces), then read characters until it reached the next white-space. The result of the first read would be There. Basically string input is using white space as a delimiter.
The getline() function will instead retain all white-space and use a new line as it's delimiter. Using the example input above, the getline() function reads in each character (white-space included) until it reaches a newline character. So after the first read the output would be
There are 5 spaces at the start of this line and one at the end
Exercise 3.4a Check equality or largest string.
Exercise 3.4b Check length equality or longest string.
Exercise 3.5a Concatenated strings, no space.
Exercise 3.5b Concatenated strings, with spaces.
If we were to change from auto& to char& the program would work as expected since the deduced type from auto is char anyway. If we assume the question is asking to drop the & all together then the program would not work as expected. If the reference was dropped, the original string would never be modified, instead the local variable inside the range for would be changed to 'X' then overwritten on the next iteration. The original string would be printed having never been altered.
Exercise 3.7 Exercise 3.6 with revised range for that does essentially does nothing.
Exercise 3.8a Rewrite of exercise 3.6 using a while loop.
Exercise 3.8b Rewrite of exercise 3.6 using a traditional for loop.
A range for loop is by far the cleanest solution to these exercises. There are no extra variables to declare or increment like in the while loop and it is easier to read than the traditional for loop.
This code is valid in that the compiler will not complain, it is functionally incorrect though. A default initialised string is an empty string "" it has no content at index 0. Accessing an index of an empty string is undefined.
Legal. c is a const char&. So long as the body of the rage for doesn't try to modify c it will be legal.
(a)
Legal, creates a vector which contains a default initialised vector of type int.
(b)
Illegal, like trying to fit the star shape in the round hole; the vectors must have the same type to perform copy initialisation.
(c)
Legal, constructs the vector using the class constructor resulting in a vector with 10 string elements initialised to "null".
(a)
Empty vector.
(b) Uses the class constructor to construct a
vectorwith 10intsdefault initialised to 0.
(c)
Uses the class constructor to construct a vector with 10 ints initialised to 42.
(d)
Uses list initialisation to create a vector with one 'int` element initialised to 10.
(e)
Uses list initialisation to create a vector with two int elements initialised to 10 and 42 respectively.
(f)
Uses list initialisation to create a vector with 10 default initialised (empty) strings.
(g)
Uses list initialisation to create a vector with 10 string elements initialised to "hi".
The program above is not legal. ivec is an empty vector it has no element at index 0. Attempting to access an out of range index is undefined.
To fix the problem we have three logical options. We can list initialise the vector
vector<int> ivec{42};We can initialise the vector with one default initialised element
vector<int> ivec(1);
ivec[0] = 42;Or we could use the member function push_back
vector<int> ivec;
ivec.push_back(42);List initialisation is my preferred method for this example.
Create an empty vector and populate it using the push_back member function
vector<int> ivec;
for (int i = 0; i < 10; ++i)
ivec.push_back(42);list initialise the vector
vector<int> ivec{42, 42, 42, 42, 42, 42, 42, 42, 42, 42};Use the class constructor
vector<int> ivec(10, 42);The preferred method is to use the class constructor. This method is perfectly suited to the task, it is compact, efficient and less error prone than typing each value individually.
Exercise 3.20a (Sum of adjacent elements)
Exercise 3.20b (Sum of first and last, second and second-to-last, and so on.)
Exercise 3.24a (Rewrite of exercise 3.20a using iterators)
Exercise 3.24b (Rewrite of exercise 3.20b using iterators)
Let's first understand how and why mid = beg + (end - beg) / 2; works, then work out why min = (beg + end) / 2; was not used in this solution.
Both beg and end are iterators of type int. Iterators work much like pointers; beg points to the first element in the vector, end points to 'one-past-the-end'. With this information we can draw a diagram to represent a vector with 5 elements and mark where beg and end are. This should help us understand what's going on.
// Below is a representation of the following vector and beg / end iterators
vector<int> ivec {5, 6, 7, 8, 9};
beg = ivec.begin();
end = ivec.end();
// +---+---+---+---+---+---+
// | 5 | 6 | 7 | 8 | 9 | |
// +---+---+---+---+---+---+
// ^ ^
// b e
// e n
// g dNow what happens when we minus beg from end? When we perform this type of arithmetic on iterators (an iterator minus and iterator) the returned type is not an iterator it is instead a difference_type, that is, a numerical value which is the distance between 2 iterators in the supplied container. In the case of the vector above beg represents the first element, end represents the sixth element (one-past-the-end) so our definition basically reduces to mid = beg + (6 - 1) / 2 or mid = beg + 2 (not +2.5 since integer division drops the fractional component). Adding or subtracting a numerical type from an iterator means increment/decrement the iterator by the numerical value given. In this case, advance beg by 2 places.
// +---+---+---+---+---+---+
// | 5 | 6 | 7 | 8 | 9 | |
// +---+---+---+---+---+---+
// ^ ^ ^
// b m e
// e i n
// g d dThat is how we end up with mid at the midpoint of the vector.
If we try to use the second option mid = (beg + end) / 2; we can immediately see some issues.
Both beg and end are iterators, addition of iterators is not legal. It doesn't make sense to add two iterators together since they're just pointers to an address in memory, it wouldn't provide any useful information (subtraction does make sense since it will produce a type based distance between the two iterators as we've seen above). Even if beg + end was legal, like end - beg it wouldn't produce an iterator. So if this definition was legal, mid would be a number instead of an iterator (pointer) to the midpoint of the vector.
(a)
Illegal, buf_size is not a constant expression.
(b)
Legal, no variables are used in calculation so it is constant.
(c)
Legal if txt_size() is a constant expression, illegal otherwise.
(d)
Illegal, there is no space left in the array for the null (\0) character.
Side note: Both gcc and clang will compile the above array definitions without warning or error since they both allow variable length arrays, via extensions. Add -pedantic to your compile options to ensure the warnings for variable length arrays are shown. Remember, it's better to not use these extensions for the sake of portability.
sa contains 10 default initialised strings (10 empty strings "").
ia contains 10 default initialised ints (10 ints set to 0) since it has been defined outside a function.
string is a library type and by default initialises to "" regardless of where it is defined.
sa2 contains 10 default initialised strings (10 empty strings "").
ia2 contains 10 undefined ints since it has been defined inside a function.
- Dimension (size) of the
arraymust be known at compile time (refer note at the end of exercise 3.27). - Dimension of the
arraycannot be changed once created. - Memory for the complete
arraywill be allocated when it is declared even if you don't end up using all the elements. arraysdon't have the same ease of use that vectors provide.
The index is out of sync with the elements of the array. array indexes start at zero, not one as in the above program.
ia prior to for loop:
index: 0 1 2 3 4 5 6 7 8 9
+---+---+---+---+---+---+---+---+---+---+
elements: | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+---+---+
After the first loop through the for:
index: 0 1 2 3 4 5 6 7 8 9
+---+---+---+---+---+---+---+---+---+---+
elements: | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
+---+---+---+---+---+---+---+---+---+---+
^
i
x
The last loop causes a buffer overflow since ix = 10 and the array index only goes to 9:
index: 0 1 2 3 4 5 6 7 8 9
+---+---+---+---+---+---+---+---+---+---+---+
elements: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
+---+---+---+---+---+---+---+---+---+---+---+
^
i
x
BUFFER OVERFLOW
Exercise 3.32a Copy from one array to another.
Exercise 3.32b Rewrite using vectors
If the array was not initialised, each element would then be default initialised. Since the array is defined within the main function this would mean each element would have an undefined value.
Output example:
index: 0 1 2 3 4 5 6 7 8 9 10
+-----+---+---+---+---------+---+---------+---+------------+-------+---+
elements: | 142 | 0 | 0 | 0 | 4196592 | 0 | 4196160 | 0 | 1388771872 | 32766 | 0 |
+-----+---+---+---+---------+---+---------+---+------------+-------+---+
This code increments p1 by the difference between p2 and p1. Any legal value for p1 and p2 will produce a legal outcome.
Some examples below.
// Example array
int fiveInts[] = {1, 2, 3, 4, 5};
int* p1 = begin(fiveInts);
int* p2 = end(fiveInts);
// +---+---+---+---+---+---+
// | 1 | 2 | 3 | 4 | 5 | |
// +---+---+---+---+---+---+
// ^ ^
// p p
// 1 2
// Now let's increment p1 as per the exercise
p1 += p2 - p1; // Same as p1 += 6 - 1
// +---+---+---+---+---+---+
// | 1 | 2 | 3 | 4 | 5 | |
// +---+---+---+---+---+---+
// ^
// p
// 1
// &
// p
// 2
// One more example
p1 -= 4;
p2 -= 2;
// +---+---+---+---+---+---+
// | 1 | 2 | 3 | 4 | 5 | |
// +---+---+---+---+---+---+
// ^ ^
// p p
// 1 2
// Now let's increment p1 as per the exercise
p1 += p2 - p1; // Same as p1 += 4 - 2
// +---+---+---+---+---+---+
// | 1 | 2 | 3 | 4 | 5 | |
// +---+---+---+---+---+---+
// ^
// p
// 1
// &
// p
// 2As we can see, whatever the starting value, after we increment p1 as shown in the exercise both pointers end up pointing to the same element.
Exercise 3.36a Array comparison.
Exercise 3.36b Vector comparison.
This program will print:
h
e
l
l
o
However, ca doesn't have a terminating null character so the while loop will not end where we expect. It will likely continue to print whatever is in memory until it finds a null character.
As a test let's assign another variable after the while loop.
const char ca[] = {'h', 'e', 'l', 'l', 'o'};
const char *cp = ca;
while (*cp) {
cout << *cp << endl;
++cp;
}
const char secretCode[] = {'1', '2', '3', '4', '5'};This program outputs:
h
e
l
l
o
1
2
3
4
5
Not good. Remember to terminate c-style strings with null characters:
const char ca[] = {'h', 'e', 'l', 'l', 'o', '\0'};If we had two pointers, one to a string the other to an int what would the outcome of adding these pointers be? No idea. What use would the result serve? No idea. Pointers are just memory addresses, we generally don't know beforehand what the address of a newly created object will be so adding them makes little sense especially since they will be different each time the program is run. Even if we consider a container type like a vector for instance. If we had two pointers, each to a different element in the vector, what would the purpose be of adding those pointers? Nothing good I imagine. If for example the first pointer was the 'off-the-end' element, and the second was the third element in the vector, the resulting addition would no doubt be a memory address well outside the vector and therefore illegal, attempts to dereference such a pointer will likely crash the program.
Exercise 3.39a Compare two strings.
Exercise 3.39b Compare two c-style strings.
Exercise 3.43a Using range for.
Exercise 3.43b Using subscript.
Exercise 3.43c Using pointers.
Exercise 3.44a (Rewrite of exercise 3.43a above using type alias).
Exercise 3.44b (Rewrite of exercise 3.43b above using type alias).
Exercise 3.44c (Rewrite of exercise 3.43c above using type alias).
Exercise 3.45a (Rewrite of exercise 3.43a above using auto).
Exercise 3.45b (Rewrite of exercise 3.43b above using auto).
Exercise 3.45c (Rewrite of exercise 3.43c above using auto).
☚ Chapter 02 Chapter 03 Chapter 04 ☛