课程主页: CS106L
1. Welcome#
这节课主要是对 CS106L
课程的简单介绍,作业安排,课程内容的罗列,C++ 发展历史的介绍.
课程介绍:#
这门课主要介绍 C++ 的现代特性,对基本语法基本没有耗费时间,需要一定的语言基础.
作业安排:#
这门课会有七个简单的小作业,目前开放的作业如下:
作业序号 | 作业标题 | 作业链接 |
---|---|---|
Assignment 1 | SimpleEnroll | 前往完成 |
Assignment 2 | Marriage Pact | 前往完成 |
Assignment 3 | Make a class! | 前往完成 |
Assignment 4 | Weather Forecast | 前往完成 |
... | ... | ... |
课程内容:#
编号 | 课程内容 |
---|---|
1 | C++ feature 简介 |
2 | 初始化,引用,Streams |
3 | 容器,迭代器,指针 |
4 | 类,模板类,const |
5 | 模板函数,函数,lambda |
6 | 运算符,特殊成员函数 |
7 | move 语义,类型安全 |
发展历史:#
参照第一节课的 slides 即可.
2. Types and Structs#
类型:#
基本类型:#
C++ 提供了一下几种基本类型:
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
在 string
头文件中实现了对 string 类型的支持
std::string str = "Nanomoa";
C++ 是一门静态类型语言,这意味着任何一个变量 / 函数等等都应该在运行前显示的声明其类型.
动态类型和静态类型的区别:
- 动态类型: 在执行代码是一行一行的检查类型,可能在运行时出现一些错误
- 静态类型: 在运行之前,编译时检查类型,编译错误将无法运行,且静态类型语言的代码可读性更高
在 C++ 中不能定义多个相同的函数,但是可以通过函数重载实现其不同版本.
函数重载需要满足函数名相同,但 返回值类型 / 参数列表中参数的类型 / 参数列表中参数的数量 三者至少有一项不相同.
结构体:#
auto
关键字的引入:#
用于代替实际类型,让编译器自动根据变量的值推断其类型.
auto a = 1; // int
auto b = 3.14; // double
auto c = 'a'; // char
auto d = "Hello"; // char*
结构体:#
结构体是一组变量的集合,其中每个字段都有它们自己的类型.
示例:
struct Student {
std::string name;
int age;
};
Student stu;
stu.name = "Nanomoa";
stu.age = 20;
结构体可以作为参数传递一组信息,或者作为返回值 (可以通过结构体使得函数间接的具有多个返回值).
示例:
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);
结构体初始化:
Student s;
s.name = "A";
s.age = 20;
// or
s = {"A", 20};
std::pair
引入:
std::pair
是 STL 中提供的一个模板,可以接受任意一对不同类型,其包含两个字段 first
和 second
.
示例:
std::pair<int, std::string> val = {1, "AA"};
std::cout << val.first << " " << val.second << std::endl; // "1 AA"
可以配合 std::pair
实现如下返回值携带操作状态的函数:
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#
初始化:#
直接初始化:#
方式如下:
int valInt = 1;
int valInt1(2);
注意: C++ 不在乎初始化传入的数值是否为整数 (传入浮点数会转换成整数)
例:
int valInt = 3.00; // valInt => 3
int valInt1(4.5); // valInt1 => 4
Uniform initialization:#
从 C++11
开始支持,更安全,会进行类型检查,例:
int valInt{1}; // 正确
int valInt1{3.14}; // 无法通过编译
使用:
// 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;
// 等同于下面
Student s = {"Nanomoa", 20};
结构化绑定:#
通过对象的元素或成员初始化多个实体,C++17
开始支持,例:
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);
// 等同于下面(结构化绑定)
auto [className, buildingName, language] = getClassInfo();
引用:#
引用是已存在事物的别名,符号 &
, 例:
int num = 5;
int& ref = num;
ref = 10;
std::cout << ref << ' ' << num << std::endl; // 10 10
引用作为函数参数时也有同样效果:
void func(int& n) {
n = std::pow(n, 2);
}
int main() {
int num = 2;
func(2);
std::cout << num << std::endl; // 4
return 0;
}
// 传入此函数中的nums并没有被修改
void func(std::vector<std::pair<int, int>> &nums) {
for (auto [num1, num2] : nums) { // 此处通过结构化绑定copy了nums中当前pair的值
num1++; // 此处修改的是copy的pair, 而不是nums中的pair
num2++;
}
}
// 此函数中 nums 被修改
void func1(std::vector<std::pair<int, int>> &nums) {
for (auto& [num1, num2] : nums) { // 此处通过结构化绑定引用了nums中当前pair
num1++;
num2++;
}
}
左值 / 右值:#
- 左值:可以在等号左边,也可以在等号右边
- 右值:只能在等号右边
例:
// x 可以作为左值
int y = x;
x = 10;
// 10 可以作为右值
int y = 10;
10 = x; // 错误
注意: 不能将右值绑定到非 const
的左值,例:
int squareN(int& num) {
return std::pow(num, 2);
}
auto val = squareN(2); // 编译错误
Const:#
使用 const
声明的对象不可修改,例:
std::vector<int> vec{1, 2, 3}; // 非 const
vec.push_back(4); // 可修改
std::vector<int>& ref_vec{vec}; // 引用
ref_vec.push_back(5);
const std::vector<int> const_vec{1, 2, 3}; // const
const_vec.push_back(4); // 编译错误, 不可修改
const std::vector<int>& ref_const_vec{const_vec}; // const 引用
ref_const_vec.push_back(4); // 不可修改
注意: 对于 const
声明的对象,不能为其声明 非const
的引用,例:
const std::vector<int> vec{1, 2, 3};
std::vector<int>& ref_vec{vec}; // 错误
const std::vector<int>& const_ref_vec{vec}; // 正确
如何编译 C++:#
g++ -std=c++11 main.cpp -o main
or
g++ -std=c++11 main.cpp
4. Streams#
What are streams:#
C++ 通用的输入输出抽象
cin and cout:#
位于 iostream
头文件中
cerr and clog#
- cerr: 用于输出
errors
- clog: 用于记录非关键事件日志
cout and cin#
std::cout and the IO library:
std::cout << "Hello, World" << std::endl;
std::cout
是一个 stream
std::cout
stream 是 std::ostream
的一个实例,是标准输出流
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
是控制台 输入流
std::cout
stream 是 std::istream
的一个实例,是标准输入流
stringstreams:#
std::stringstream#
一种将字符串看作流的方法,可用于处理混合类型的数据
std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot";
std::stringstream ss(initial_quote); // 也可以 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
, 注意,使用 >>
运算符只会读取到下一个空格前
如果我们需要读取包括 makes 后面的所有内容,应该使用 getline()
, 例:
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:#
一种写入数据的方式
double tao = 6.28;
std::cout << tao;
tao -> std::cout
stream -> "6.28" -> flush -> console/others
std::cout
stream 是 line-buffered, buffer 中的内容在flush前不会显示在目标上
line-buffered: buffer 在遇到 '\n'
时会被刷新
例:
std::cout << "Starting intense computation...";
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "finished computation\n";
这段代码的效果并不是 先输出 "Starting intense computation..." 等待 5s 后再输出 "finished computation"
而是等待 5s 后同时输出 "Starting intense computation..." 和 "finished computation"
因为 std::cout << "Starting intense computation...";
没有 flush
解决该问题的方法:
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)
用于取消 C++ 的标准流(例如 cout 和 cin)和 C 的标准 IO(例如 printf 和 scanf)之间的同步,可以提升 IO 的性能
但注意:禁用同步后不可将 C++ 流与 C 的标准 IO 混用,否则会导致 ub
Output File Streams:#
std::ofstream
, 一种向文件写入数据的方式,常用方法有is_open()
, open()
, close()
, fail()
例:
std::ofstream ofs("hello.txt"); // 为 hello.txt 创建一个流, 如果文件不存在会先新建该文件
if (ofs.is_open()) { // 判断是否打开
ofs << "Hello CS106L" << '\n'; // 如果打开则向文件中写入内容
}
ofs.close(); // 关闭, 关闭后不可操作
ofs.open("hello.txt"); // 打开
ofs << "Open Again" << '\n'; // 写入内容
我们期望的文件内容:
Hello CS106L
Open Again
实际为:
Open Again
如果需要追加内容而不是覆盖内容,应该按照如下方式打开:
ofs.open("hello.txt", std::ios::app);
Input File Streams:#
std::ifstream
, 一种从文件中读取内容的方式,用法与 std::ofstream
基本一致
例:
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
, 一种读取数据的方式
std::cin
是 buffered, 每次读取到下一个 ' ' or '\n' or '\t' 位置
例:
/*
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
我们如果要使得name为 "Nanomoa AAAA", 应该使用 std::getline()
:
double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::getline(std::cin, name);
std::cout << pi << " " << tao << " " << name << '\n';
但是我们发现,读取完 pi 和 tao 程序就结束了
因为此处的 std::getline(std::cin, name)
读取的是tao 后面的 '\n'
所以,我们应该先读取空格后再读取 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';
但是,不推荐 std::getline
和 std::cin
同时使用,因为二者解析数据的方式不同
非要共用的情况下可以使用 std::getline
配合 std::stringstream
代替:
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#
(更新中...)