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!

coffee

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 vs Standard 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.

coffeeh

References