banner
Nanomoa

Nanomoa's Blog

"Bytes and Brackets: My Journey in Go and C++ Exploration."
github
zhihu
telegram
twitter

CS106L Spring 2024 Study Notes

Course Homepage: CS106L

1. Welcome#

This class is mainly a brief introduction to the CS106L course, assignment arrangements, a listing of course content, and an introduction to the history of C++ development.

Course Introduction:#

This course mainly introduces modern features of C++, spending little time on basic syntax, and requires a certain language foundation.

Assignment Arrangements:#

There will be seven simple assignments in this course, and the currently available assignments are as follows:

Assignment NumberAssignment TitleAssignment Link
Assignment 1SimpleEnrollGo to complete
Assignment 2Marriage PactGo to complete
Assignment 3Make a class!Go to complete
Assignment 4Weather ForecastGo to complete
.........

Course Content:#

NumberCourse Content
1Introduction to C++ features
2Initialization, References, Streams
3Containers, Iterators, Pointers
4Classes, Template Classes, const
5Template Functions, Functions, lambda
6Operators, Special Member Functions
7Move Semantics, Type Safety

Development History:#

Refer to the slides from the first class.

2. Types and Structs#

Types:#

Basic Types:#

C++ provides the following basic types:

int val = 5; //32 bits
char ch = 'F'; //8 bits
float decimalVal1 = 5.0; //32 bits
double decimalVal2 = 5.0; //64 bits
bool bVal = true; //1 bit

Support for the string type is implemented in the string header file.

std::string str = "Nanomoa";

C++ is a statically typed language, which means that any variable/function, etc., must explicitly declare its type before runtime.

The difference between dynamic and static typing:

  • Dynamic Typing: Checks types line by line during code execution, which may lead to some errors at runtime.
  • Static Typing: Checks types at compile time before execution, and compilation errors will prevent the program from running. Code in statically typed languages is generally more readable.

In C++, you cannot define multiple functions with the same name, but you can implement different versions through function overloading.

Function overloading requires that the function names are the same, but at least one of the return type, parameter types, or the number of parameters must differ.

Structs:#

Introduction of the auto Keyword:#

Used to replace actual types, allowing the compiler to automatically infer the type based on the variable's value.

auto a = 1; // int
auto b = 3.14; // double
auto c = 'a'; // char
auto d = "Hello"; // char*

Structs:#

A struct is a collection of variables, where each field has its own type.

Example:

struct Student {
    std::string name;
    int age;
};

Student stu;
stu.name = "Nanomoa";
stu.age = 20;

Structs can be used to pass a group of information as parameters or as return values (structs can indirectly provide multiple return values from functions).

Example:

void printStudentInfo(Student s) {
    std::cout << s.name << " " << s.age << std::endl;
}

Student getStudent() {
    Student s;
    s.name = "Test";
    s.age = 20;
    return s;
}

Student s = getStudent();
printStudentInfo(s);

Struct Initialization:

Student s;
s.name = "A";
s.age = 20;

// or
s = {"A", 20};

Introduction of std::pair:

std::pair is a template provided in STL that can accept any pair of different types, containing two fields first and second.

Example:

std::pair<int, std::string> val = {1, "AA"};
std::cout << val.first << " " << val.second << std::endl; // "1 AA"

You can use std::pair to implement functions that return values along with operation status:

std::pair<bool, std::string> func() {
    bool is_success = false;
    // todo ...
    if(is_success) {
        return {true, "Success Info"};
    } else {
        return {false, "Failed Info"};
    }
}

std::pair<bool, std::string> res = func();
auto res1 = func();

3. Initialization & References#

Initialization:#

Direct Initialization:#

The method is as follows:

int valInt = 1;
int valInt1(2);

Note: C++ does not care whether the value passed for initialization is an integer (passing a floating-point number will convert it to an integer).

Example:

int valInt = 3.00; // valInt => 3
int valInt1(4.5); // valInt1 => 4

Uniform Initialization:#

Supported since C++11, it is safer and performs type checking. Example:

int valInt{1}; // Correct
int valInt1{3.14}; // Cannot compile

Usage:

// std::map
std::map<std::string, int> ages{
    {"Nanomoa", 20},
    {"Test", 21}
};

// std::vector
std::vector<int> vec{1, 2, 3, 4, 5};

// struct
struct Student {
    std::string name;
    int age;
};

Student stu;
stu.name = "Nanomoa";
stu.age = 20;
// Equivalent to the below
Student s = {"Nanomoa", 20};

Structured Binding:#

Used to initialize multiple entities from the elements or members of an object, supported since C++17, example:

std::tuple<std::string, std::string, std::string> getClassInfo() {
    std::string className = "CS106L";
    std::string buildingName = "Turing Auditorium";
    std::string language = "C++";
    return {className, buildingName, language};
}

auto classInfo = getClassInfo();
std::string className = std::get<0>(classInfo);
std::string buildingName = std::get<1>(classInfo);
std::string language = std::get<2>(classInfo);
// Equivalent to the below (structured binding)
auto [className, buildingName, language] = getClassInfo();

References:#

A reference is an alias for an existing entity, denoted by the symbol &, example:

int num = 5;
int& ref = num;
ref = 10;
std::cout << ref << ' ' << num << std::endl; // 10 10

References as function parameters have the same effect:

void func(int& n) {
    n = std::pow(n, 2);
}

int main() {
    int num = 2;
    func(num);
    std::cout << num << std::endl; // 4
    return 0;
}
// Passing nums in this function does not modify it
void func(std::vector<std::pair<int, int>> &nums) {
    for (auto [num1, num2] : nums) { // Here, the current pair's values are copied
        num1++; // This modifies the copied pair, not the pair in nums
        num2++;
    }
}

// In this function, nums is modified
void func1(std::vector<std::pair<int, int>> &nums) {
    for (auto& [num1, num2] : nums) { // Here, the current pair is referenced
        num1++;
        num2++;
    }
}

Lvalues/Rvalues:#

  • Lvalue: Can be on the left side of an assignment, and also on the right side.
  • Rvalue: Can only be on the right side of an assignment.

Example:

// x can be an lvalue
int y = x;
x = 10;
// 10 can be an rvalue
int y = 10;
10 = x; // Error

Note: You cannot bind an rvalue to a non-const lvalue, example:

int squareN(int& num) {
    return std::pow(num, 2);
}

auto val = squareN(2); // Compilation error

Const:#

Objects declared with const cannot be modified, example:

std::vector<int> vec{1, 2, 3}; // Non-const
vec.push_back(4); // Modifiable
std::vector<int>& ref_vec{vec}; // Reference
ref_vec.push_back(5);
const std::vector<int> const_vec{1, 2, 3}; // Const
const_vec.push_back(4); // Compilation error, cannot modify
const std::vector<int>& ref_const_vec{const_vec}; // Const reference
ref_const_vec.push_back(4); // Cannot modify

Note: For objects declared as const, you cannot declare a non-const reference to them, example:

const std::vector<int> vec{1, 2, 3};
std::vector<int>& ref_vec{vec}; // Error
const std::vector<int>& const_ref_vec{vec}; // Correct

How to Compile C++:#

g++ -std=c++11 main.cpp -o main

or

g++ -std=c++11 main.cpp

4. Streams#

What are streams:#

A general input-output abstraction in C++.

cin and cout:#

Located in the iostream header file.

cerr and clog#
  • cerr: Used for outputting errors.
  • clog: Used for logging non-critical events.
cout and cin#

std::cout and the IO library:

std::cout << "Hello, World" << std::endl;

std::cout is a stream.

The std::cout stream is an instance of std::ostream, which is the standard output stream.

An Input Stream:

"3.14" -> Input Stream -> type conversion -> (double)(3.14)

double pi;
std::cin >> pi;
std::cout << pi / 2 << std::endl;

std::cin is the console input stream.

The std::cout stream is an instance of std::istream, which is the standard input stream.

stringstreams:#

std::stringstream#

A way to treat strings as streams, useful for handling mixed-type data.

std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot";
std::stringstream ss(initial_quote); // Can also use ss << initial_quote;
std::string first,
            last,
            language,
            extracted_quote;
ss >> first >> last >> language >> extracted_quote;
std::cout << first << " " << last << " " << language << " " << extracted_quote << std::endl;
first => Bjarne 
last => Stroustrup 
language => C 
extracted_quote => makes

extracted_quote => makes, note that using the >> operator only reads up to the next space.

If we need to read all content after "makes", we should use getline(), example:

std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot";

std::stringstream ss(initial_quote);

std::string first,
            last,
            language,
            extracted_quote;
ss >> first >> last >> language;
std::getline(ss, extracted_quote);
std::cout << first << " " << last << " " << language << " " << extracted_quote << std::endl;
first => Bjarne 
last => Stroustrup 
language => C 
extracted_quote => makes it easy to shoot yourself in the foot

Output Streams:#

A way to write data.

double tao = 6.28;
std::cout << tao;

tao -> std::cout stream -> "6.28" -> flush -> console/others.

The std::cout stream is line-buffered, meaning that the content in the buffer will not be displayed on the target until it is flushed.

Line-buffered: The buffer is flushed when encountering '\n'.

Example:

std::cout << "Starting intense computation...";
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "finished computation\n";

The effect of this code is not to first output "Starting intense computation..." and then wait 5 seconds to output "finished computation".

Instead, it waits 5 seconds and then outputs both "Starting intense computation..." and "finished computation" simultaneously.

This is because std::cout << "Starting intense computation..."; did not flush.

Methods to solve this issue:

std::cout << "Starting intense computation...\n";
// or
std::cout << "Starting intense computation..." << std::endl;
// or
std::cout << "Starting intense computation...";
std::cout << std::flush;

std::ios::sync_with_stdio(false) is used to disable synchronization between C++ standard streams (like cout and cin) and C standard IO (like printf and scanf), which can improve IO performance.

However, note: After disabling synchronization, do not mix C++ streams with C standard IO, as it may lead to undefined behavior.

Output File Streams:#

std::ofstream, a way to write data to files, commonly used methods include is_open(), open(), close(), fail().

Example:

std::ofstream ofs("hello.txt"); // Creates a stream for hello.txt, if the file does not exist, it will be created first.
if (ofs.is_open()) { // Check if opened
    ofs << "Hello CS106L" << '\n'; // If opened, write content to the file.
}
ofs.close(); // Close, cannot operate after closing.
ofs.open("hello.txt"); // Open again.
ofs << "Open Again" << '\n'; // Write content.

Expected file content:

Hello CS106L
Open Again

Actual content:

Open Again

If you need to append content instead of overwriting, you should open it as follows:

ofs.open("hello.txt", std::ios::app);

Input File Streams:#

std::ifstream, a way to read content from files, usage is basically the same as std::ofstream.

Example:

std::ifstream ifs("hello.txt");
if (ifs.is_open()) {
    std::string line1;
    std::getline(ifs, line1);
    std::cout << line1 << '\n';
}
if (ifs.is_open()) {
    std::string line2;
    std::getline(ifs, line2);
    std::cout << line2 << '\n';
}

Input Streams:#

std::istream, a way to read data.

std::cin is buffered, reading until the next ' ' or '\n' or '\t' position.

Example:

/*
3.14 6.28
Nanomoa AAAA
*/
double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::cin >> name;
std::cout << pi << " " << tao << " " << name << '\n';
// 3.14 6.28 Nanomoa

If we want name to be "Nanomoa AAAA", we should use std::getline():

double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::getline(std::cin, name);
std::cout << pi << " " << tao << " " << name << '\n';

However, we find that after reading pi and tao, the program ends.

This is because std::getline(std::cin, name) reads the '\n' after tao.

Thus, we should read the space first before reading name:

double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::getline(std::cin, name);
std::getline(std::cin, name);
std::cout << pi << " " << tao << " " << name << '\n';

However, it is not recommended to use std::getline and std::cin together, as they parse data differently.

If you must use both, you can use std::getline with std::stringstream instead:

double pi, tao;
std::string name;
std::string line;
std::getline(std::cin, line);
std::stringstream ss(line);
ss >> pi >> tao;
std::getline(std::cin, line);
name = line;
std::cout << pi << " " << tao << " " << name << std::endl;

5. Containers#

(Updating...)

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.