CS106L(3): Initialization and References with Coffee
These days I am a bit tired, but I feel refreshed when I read this coffee-like
lecture. Let’s move on to see the ingredients inside! hehe!
Core Idea (Instant Coffee)
- Uniform initialization
- A “uniform” way to initialize variables of different types!
- References <– This is the hard one.
- Allow us to alias variables
- Const
- Allow us to specify that a variable can’t be modified
Lecture 3: Initialization and References - Note (All-in-one coffee mate)
View Lecture Note
Standard C++ Vector - Intro (Cappuccino)
std::vector is a sequence container that encapsulates dynamic size arrays.
To use vector, we need to include #include<vector>
.
Vector often comes with sort
or find
, need to include #include<algorithm>
.
Vector looks like a dynamic array
, visiting element -> O(1), but insert, delete, move -> slow!
Stanford
Vector Example:
Vector<int> v;
Vector<int> v(n, k);
v.add(k);
v[i] = k;
auto k = v[i];
v.isEmpty();
v.size();
v.clear();
v.insert(i, k);
v.remove(i);
Standard Vector:
std::vector<int> v;
std::vector<int> v(n, k);
v.push_back(k);
v[i] = k;
auto k = v[i];
v.empty();
v.size();
v.clear();
Question: What is the differences betweem tje
Stanford
Vector vsStandard
Vector? or just Prof’s humor?
Based on further stirring / research, it seems this vector
type is coming with the following operations:
- Nonmodifying Operation
- Assignment Operation
- Element Access
- Iterator Function
- Insertion and Deletion
- Exception Handling
Hope these will be mentioned in the following lectures. ^_^
Uniform Initialization (Ristretto)
Initialization is how we provide initial values to variables.
int x = 5; // initializing while we declare
int y;
y = 6; // initializing after we declare
In C++, initialization used to be tricky, and varied substantially depending upon the type. It can be complicated based on Types, so we have Uniform Initialization
to use:
Before
std::pair<bool, int> some_pair =
std::make_pair(false, 6);
Student s;
s.name = "Ethan";
s.state = "CA";
s.age = 20;
After
- style 1
std::pair<bool, int>
some_pair{false, 6};
Student s{"Ethan", "CA", 20};
int x{3};
std::vector<int> a_vector{3, 5, 7};
- style 2
std::pair<bool, int>
some_pair = {false, 6};
Student s = {"Ethan", "CA", 20};
int y = {3};
std::vector<int> another_vector = {3, 5, 7};
std::vector
This initialization uses a constructor.
int n = 3;
int k = 5;
std::vector<int> v(n, k); // {5, 5, 5}
Normally, we can replace the () with {} to use uniform initialization – not here!
int n = 3;
int k = 5;
std::vector<int> v(n, k); // {5, 5, 5}
std::vector<int> v2{n, k}; // {3, 5} -- not the same!!
Using {} for vector creates an initializer_list
When we create a std::initializer_list, we actually end up invoking a different constructor!
auto list_init{3, 5}; // type is std::initializer_list
An object of type std::initializer_list is a lightweight proxy object that provides access to an array of objects of type const T.
Moral of the story
Make sure you’re completely clear what constructor you’re invoking when using uniform initialization.
Complicated uniform initialization example
struct Course {
string code;
pair<Time, Time> time;
vector<string> instructors;
};
struct Time {
int hour, minute;
}
...
Course now{"CS106L", { {16, 30}, {17, 50} }, {"Raghuraman", "Chi"} };
Reference (Espresso)
References in variable assignment
Notice the ampersand – ref is an alias for original!
std::vector<int> original{1, 2};
std::vector<int> copy = original;
std::vector<int>& ref = original;
original.push_back(3);
copy.push_back(4);
ref.push_back(5);
// original (and also ref!) = {1, 2, 3, 5}
// copy = {1, 2, 4}
A reference is always an alias to the same variable!
This means setting ref equal to a new value is exactly the same as setting original equal to that value! We can’t change what variable ref aliases.
vector<int> original{1, 2};
vector<int> copy = original;
vector<int>& ref = original;
original.push_back(3);
copy.push_back(4);
ref.push_back(5);
// original (and also ref!) = {1, 2, 3, 5}
// copy = {1, 2, 4}
ref = copy;
copy.push_back(6);
ref.push_back(7);
// original = {1, 2, 4, 7}
// copy = {1, 2, 4, 6}
Live Code Demo: References pitfall
Note: You can only create references to variables
int& thisWontWork = 5; // This doesn't work!
Const and Const References (Latte)
This topic looks smooth, like a glass of milk that you stain
by pouring espresso over it.
const indicates a variable can’t be modified!
std::vector<int> vec{1, 2, 3};
const std::vector<int> c_vec{7, 8}; // a const variable
std::vector<int>& ref = vec; // a regular reference
const std::vector<int>& c_ref = vec; // a const reference
vec.push_back(3); // OKAY
c_vec.push_back(3); // BAD - const
ref.push_back(3); // OKAY
c_ref.push_back(3); // BAD - const
Can’t declare non-const reference to const variable!
The below example isn’t permitted!
const std::vector<int> c_vec{7, 8}; // a const variable
// BAD - can't declare non-const ref to const vector
std::vector<int>& bad_ref = c_vec
Can’t declare a non-const ref to a const ref!
std::vector<int> vec{1, 2, 3};
const std::vector<int>& c_ref = vec; // a const reference
// BAD - Can't declare a non-const reference as equal
// to a const reference!
std::vector<int>& ref = c_ref;
If you don’t write &, C++ will make a copy by default!
std::vector<int> vec{1, 2, 3};
const std::vector<int>& c_ref = vec; // a const reference
// This is a non-const copy of vec, even though we're setting
// it equal to a const reference! Remember that ref is just an
// alias (aka another name) for vec
std::vector<int> copy = c_ref;
copy.push_back(4);
// vec = {1, 2, 3}
// copy = {1, 2, 3, 4}
Need to explicitly specify const and & with auto!
std::vector<int> vec{1, 2, 3};
const std::vector<int> c_vec{7, 8};
std::vector<int>& ref = vec;
const std::vector<int>& c_ref = vec;
auto copy = c_ref; // a non-const copy
const auto copy = c_ref; // a const copy
auto& a_ref = ref; // a non-const reference
const auto& c_aref = ref; // a const reference
Remember: C++, by default, makes copies when we do variable assignment! We need to use & if we need references instead.
More about references (Macchiato)
This topic is like a strong
coffee, but the code example serves as a little bit milk
to soften the bitterness of it.
Can return references as well
This is something that the C++ Standard Library frequently makes use of.
// Note that the parameter must be a non-const reference to return
// a non-const reference to one of its elements!
int& front(std::vector<int>& vec) {
// assuming vec.size() > 0
return vec[0];
}
int main() {
std::vector<int> numbers{1, 2, 3};
front(numbers) = 4; // vec = {4, 2, 3}
return 0;
}
Can also return const references
// Note that the parameter must be a non-const reference to return
// a non-const reference to one of its elements!
int& front(std::vector<int>& vec) {
const int& front(std::vector<int>& vec) {
// assuming vec.size() > 0
return vec[0];
}
Dangling references: references to out-of-scope vars
Dangling Reference ?
A link or pointer to something (instruction, table element, index item, etc.) that no longer contains the same content. If the reference is not a currently valid address, or if it is a valid address but there is no content in that location, it may cause the computer to crash. If the content has changed, it can also cause the system to crash, or, at the very least, produce erroneous output.
Example:
Never return a reference to a local variable! They’ll go out of scope.
int& front(const std::string& file) {
std::vector<int> vec = readFile(file);
return vec[0];
}
int main() {
front("text.txt") = 4; // undefined behavior
return 0;
}
After some research, there is a pointer called wild pointer. A pointer which has not been initialized is known as wild pointer
.
Dangling pointer: If a pointer still references the original memory after it has been freed, it is called a dangling pointer.
Sample Code: shift class hour + 1
Example 1:
void shift(vector<Course>& courses) {
for (size_t i = 0; i < courses.size(); ++i) {
auto& [code, time, instructors] = courses[i]; <-- use reference
time.first.hour++;
time.second.hour++;
}
}
Example 2:
void shift(vector<Course>& courses) {
for (auto& [code, time, instructors] : courses) {
time.first.hour++;
time.second.hour++;
}
}
When do we use references/const references?
- If we’re working with a variable that takes up little space in memory (e.g. int, double), we don’t need to use a reference and can just copy the variable
- If we need to alias the variable to modify it, we can use references
- If we don’t need to modify the variable, but it’s a big variable (e.g. std::vector), we can use const references
Haha, congrats, third lecture finished! This lecture becomes a bit challenging.