A reference is an alias. It is only an alternative name of another object, but the reference itself is not an object.
Therefore, there are no "references to references".
int ival = 42;
int &ri = ival; // binding `ri` to `ival`.
int & &rr = ri; // Error! No such thing!
What is the meaning of this code? Does it compile?
int &ri2 = ri;
A reference is an alias. It is only an alternative name of another object, but the reference itself is not an object.
Therefore, there are no "references to references".
int ival = 42;
int &ri = ival; // binding `ri` to `ival`.
int & &rr = ri; // Error! No such thing!
What is the meaning of this code? Does it compile?
int &ri2 = ri; // Same as `int &ri2 = ival;`.
ri2 is a reference that is bound to ival.A reference is an alias. It is only an alternative name of another object, but the reference itself is not an object.
Pointers must also point to objects. Therefore, there are no "pointers to references".
int ival = 42;
int &ri = ival; // binding `ri` to `ival`.
int &*pr = &ri; // Error! No such thing!
What is the meaning of this code? Does it compile?
int *pi = &ri;
A reference is an alias. It is only an alternative name of another object, but the reference itself is not an object.
Pointers must also point to objects. Therefore, there are no "pointers to references".
int ival = 42;
int &ri = ival; // binding `ri` to `ival`.
int &*pr = ri; // Error! No such thing!
What is the meaning of this code? Does it compile?
int *pi = &ri; // Same as `int *pi = &ival;`.
Similar to pointers, the ampersand & only applies to one identifier.
int ival = 42, &ri = ival, *pi = &ival;
// `ri` is a reference of type `int &`, which is bound to `ival`.
// `pi` is a pointer of type `int *`, which points to `ival`.
Placing the ampersand near the referred type does not make a difference:
int& x = ival, y = ival, z = ival;
// Only `x` is a reference. `y` and `z` are of type `int`.
* and &Both symbols have many identities!
Type *x = expr, * is a part of the pointer type Type *.Type &r = expr, & is a part of the reference type Type &.*opnd where there is only one operand, * is the dereference operator.&opnd where there is only one operand, & is the address-of operator.a * b where there are two operands, * is the multiplication operator.a & b where there are two operands, & is the bitwise-and operator.forRecall the range-based for loops (range-for):
std::string str;
std::cin >> str;
int lower_cnt = 0;
for (char c : str)
if (std::islower(c))
++lower_cnt;
std::cout << "There are " << lower_cnt << " lowercase letters in total.\n";
The range-for loop in the code above traverses the string, and declares and initializes the variable c in each iteration as if
for (std::size_t i = 0; i != str.size(); ++i) {
char c = str[i]; // Look at this!
if (std::islower(c))
++lower_cnt;
}
forfor (char c : str)
// ...
The range-for loop in the code above traverses the string, and declares and initializes the variable c in each iteration as if
for (std::size_t i = 0; i != str.size(); ++i) {
char c = str[i];
// ...
}
Here c is a copy of str[i]. Therefore, modification on c does not affect the contents in str.
forWhat if we want to change all lowercase letters to their uppercase forms?
for (char c : str)
c = std::toupper(c); // This has no effect.
We need to declare c as a reference.
for (char &c : str)
c = std::toupper(c);
This is the same as
for (std::size_t i = 0; i != str.size(); ++i) {
char &c = str[i];
c = std::toupper(c); // Same as `str[i] = std::toupper(str[i]);`.
}
constWrite a function that accepts a string and returns the number of lowercase letters in it:
int count_lowercase(std::string str) {
int cnt = 0;
for (char c : str)
if (std::islower(c))
++cnt;
return cnt;
}
To call this function:
int result = count_lowercase(my_string);
constint count_lowercase(std::string str) {
int cnt = 0;
for (char c : str)
if (std::islower(c))
++cnt;
return cnt;
}
int result = count_lowercase(my_string);
When passing my_string to count_lowercase, the parameter str is initialized as if
std::string str = my_string;
The contents of the entire string my_string are copied!
constint result = count_lowercase(my_string);
When passing my_string to count_lowercase, the parameter str is initialized as if
std::string str = my_string;
The contents of the entire string my_string are copied! Is this copy necessary?
constint result = count_lowercase(my_string);
When passing my_string to count_lowercase, the parameter str is initialized as if
std::string str = my_string;
The contents of the entire string my_string are copied! This copy is unnecessary, because count_lowercase is a read-only operation on str.
How can we avoid this copy?
constint count_lowercase(std::string &str) { // `str` is a reference.
int cnt = 0;
for (char c : str)
if (std::islower(c))
++cnt;
return cnt;
}
int result = count_lowercase(my_string);
When passing my_string to count_lowercase, the parameter str is initialized as if
std::string &str = my_string;
Which is just a reference initialization. No copy is performed.
constint count_lowercase(std::string &str) { // `str` is a reference.
int cnt = 0;
for (char c : str)
if (std::islower(c))
++cnt;
return cnt;
}
However, this has a problem:
std::string s1 = something(), s2 = some_other_thing();
int result = count_lowercase(s1 + s2); // Error: binding reference to
// a temporary object.
a + b is a temporary object, which str cannot be bound to.
constReferences must be bound to existing objects, not literals or temporaries.
There is an exception to this rule: References-to-const can be bound to anything.
const int &rci = 42; // OK.
const std::string &rcs = a + b; // OK.
rcs is bound to the temporary object returned by a + b as if
std::string tmp = a + b;
const std::string &rcs = tmp;
const in recitations.
constThe answer:
int count_lowercase(const std::string &str) { // `str` is a reference-to-`const`.
int cnt = 0;
for (char c : str)
if (std::islower(c))
++cnt;
return cnt;
}
std::string a = something(), b = some_other_thing();
int res1 = count_lowercase(a); // OK.
int res2 = count_lowercase(a + b); // OK.
int res3 = count_lowercase("hello"); // OK.
constApart from the fact that it avoids copy, declaring the parameter as a reference-to-const also prevents some potential mistakes:
int some_kind_of_counting(const std::string &str, char value) {
int cnt = 0;
for (std::size_t i = 0; i != str.size(); ++i) {
if (str[i] = value) // Ooops! It should be `==`.
++cnt;
else {
// do something ...
// ...
}
}
return cnt;
}
str[i] = value will trigger a compile-error, because str is a reference-to-const.
constconst qualification prevents accidental modifications to it.[Best practice] Pass by reference-to-const if copy is not necessary and the parameter should not be modified.
A reference
A pointer
nullptr.Both a reference and a pointer can be used to refer to an object, but references are more convenient - no need to write the annoying * and &.
Note: nullptr is the null pointer value in C++. Do not use NULL.
std::vectorDefined in the standard library file <vector>.
A "dynamic array".
std::vector is a class template.
Class templates are not themselves classes. Instead, they can be thought of as instructions to the compiler for generating classes.
For std::vector, what kind of class is generated depends on the type of elements we want to store, often called value type. We supply this information inside a pair of angle brackets following the template's name:
std::vector<int> v; // `v` is of type `std::vector<int>`
std::vectorstd::vector is not a type itself. It must be combined with some <T> to form a type.
std::vector v; // Error: missing template argument.
std::vector<int> vi; // An empty vector of `int`s.
std::vector<std::string> vs; // An empty vector of strings.
std::vector<double> vd; // An empty vector of `double`s.
std::vector<std::vector<int>> vvi; // An empty vector of vector of `int`s.
// "2-d" vector.
What are the types of vi, vs and vvi?
std::vectorstd::vector is not a type itself. It must be combined with some <T> to form a type.
std::vector v; // Error: missing template argument.
std::vector<int> vi; // An empty vector of `int`s.
std::vector<std::string> vs; // An empty vector of strings.
std::vector<double> vd; // An empty vector of `double`s.
std::vector<std::vector<int>> vvi; // An empty vector of vector of `int`s.
// "2-d" vector.
What are the types of vi, vs and vvi?
std::vector<int>, std::vector<std::string>, std::vector<std::vector<int>>.std::vectorThere are several common ways of creating a std::vector:
std::vector<int> v{2, 3, 5, 7}; // A vector of `int`s,
// whose elements are {2, 3, 5, 7}.
std::vector<int> v2 = {2, 3, 5, 7}; // Equivalent to ↑
std::vector<std::string> vs{"hello", "world"}; // A vector of strings,
// whose elements are {"hello", "world"}.
std::vector<std::string> vs2 = {"hello", "world"}; // Equivalent to ↑
std::vector<int> v3(10); // A vector of ten `int`s, all initialized to 0.
std::vector<int> v4(10, 42); // A vector of ten `int`s, all initialized to 42.
Note that all the elements in v3 are initialized to 0.
std::vectorCreate a std::vector as a copy of another one:
std::vector<int> v{2, 3, 5, 7};
std::vector<int> v2 = v; // `v2`` is a copy of `v`
std::vector<int> v3(v); // Equivalent
std::vector<int> v4{v}; // Equivalent
No need to write a loop!
Copy assignment is also enabled:
std::vector<int> v1 = something(), v2 = something_else();
v1 = v2;
v1 is deallocated automatically."Class Template Argument Deduction": As long as enough information is supplied in the initializer, the value type can be deduced automatically by the compiler.
std::vector v1{2, 3, 5, 7}; // vector<int>
std::vector v2{3.14, 6.28}; // vector<double>
std::vector v3(10, 42); // vector<int>, deduced from 42 (int)
std::vector v4(10); // Error: cannot deduce template argument type
std::vectorv.size() and v.empty(): same as those on std::string.
std::vector v{2, 3, 5, 7};
std::cout << v.size() << '\n';
if (v.empty()) {
// ...
}
v.clear(): Remove all the elements.
std::vectorv.push_back(x)
int n;
std::cin >> n;
std::vector<int> v;
for (int i = 0; i != n; ++i) {
int x;
std::cin >> x;
v.push_back(x);
}
std::cout << v.size() << '\n'; // n
std::vectorv.pop_back()
Exercise: Given v of type std::vector<int>, remove all the consecutive even numbers in the end.
std::vectorv.pop_back()
Exercise: Given v of type std::vector<int>, remove all the consecutive even numbers in the end.
while (!v.empty() && v.back() % 2 == 0)
v.pop_back();
v.back(): returns the reference to the last element.
v.back() and v.front()Return the references to the last and the first elements, respectively.
It is a reference, through which we can modify the corresponding element.
v.front() = 42;
++v.back();
For v.back(), v.front() and v.pop_back(), the behavior is undefined if v is empty. They do not perform any bounds checking.
for loopsA std::vector can also be traversed using a range-based for loop.
std::vector<int> vi = some_values();
for (int x : vi)
std::cout << x << std::endl;
std::vector<std::string> vs = some_strings();
for (const std::string &s : vs) // use reference-to-const to avoid copy
std::cout << s << std::endl;
Exercise: Use range-based for loops to count the number of uppercase letters in a std::vector<std::string>.
for loopsExercise: Use range-based for loops to count the number of uppercase letters in a std::vector<std::string>.
int cnt = 0;
for (const std::string &s : vs) { // Use reference-to-const to avoid copy
for (char c : s) {
if (std::isupper(c))
++cnt;
}
}
v[i] returns the reference to the element indexed i.
i v.size().v[i] performs no bounds checking.
v.at(i).
i is out of range, a std::out_of_range exception is thrown.Basic and low-level operations are performed automatically:
std::string and std::vector results in an empty string / container, not indeterminate values.std::string and std::vector is done automatically, which performs member-wise copy.Interfaces are consistent:
std::string also has member functions like .push_back(x), .pop_back(), .at(i), .size(), .clear(), etc. which do the same things as on std::vector.for.References
const can be bound to anything.const: avoids copy, accepts both lvalues and rvalues, and prevents accidental modification on what should not be modified.std::vector
std::vector is not a type. It must be combined with some <T> to form a type.std::vector performs member-wise copy.v.size, v.empty, v.push_back, v.pop_back, v.clear, v[i], v.at(i).for to traverse a std::vector.Write the exercises on page 26, 38, 40 and 43 on your own.
"hello") are an exception to this. Integer literals, floating-point literals, character literals, boolean literals and enum items are rvalues, but string literals are lvalues. They do live somewhere in the memory.
for uses iterators, not subscripts.
std::vector v5(n, std::vector(m, 0.0)) in recitations