C++查漏补缺

之前学C++都是通过实践来学习,没有很系统地学习过,所以想阅读C++ Primer来进行一些查漏补缺。

大概会记录一些读书笔记,不会面面俱到。

从第十三章开始就变得很专业工具化了,基本是略读,以后用得到再去查阅吧。

第一章 开始

第二章 变量和基本类型

复合类型

引用(左值引用)

引用并非对象,它只是为一个已经存在的对象所起的另外一个名字。

引用不是对象,不能定义引用的引用。

指针

引用不是对象,没有实际地址,不能定义指向引用的指针。

& 和 *

1
2
3
4
5
6
int i= 42
int &r = i; //&紧跟类型名出现,因此是声明的一部分,r是一个引用
int *p; //*紧跟类型名出现,因此是声明的一部分,p是一个指针
p = &i; //&出现在表达式中,是一个取地址符
*p = i; //*出现在表达式中,是一个解引用符
int &r2 = *p; //&是声明的一部分,*是一个解引用符

const限定符

const对象一旦创建后其值就不能再改变,所以const对象必须初始化。

默认状态下,const对象仅在文件内有效。如果想只定义一次并且在多个文件中使用它的话,就要加extern关键字。

顶层const(const在最右)

顶层const表示指针本身是个常量。

底层const(const在最左)

底层const表示指针所指的对象是一个常量。

constexpr

如果变量是一个常量表达式,那就可以把它声明为constexpr类型。

处理类型

decltype:选择并返回操作数的数据类型。

(完全没用过,暂且当成基础知识吧)

第三章 字符串、向量和数组

string

初始化

拷贝初始化:使用等号初始化一个变量。

1
string s5 = "hiya"

直接初始化:不使用等号初始化一个变量。

1
string s6("hiya")

基于范围的for语句

1
2
for (declaration : expression)
statement

使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该时引用变量。

第四章 表达式

类型转换

显示转换

强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast

static_cast

任何具有明确定义的类型转换,只要不包含底层const,都可以用static_cast。

用处:

  1. 把一个较大的算术类型赋值给较小的类型,不在乎潜在的精度损失。
  2. 转换编译器无法自动执行的类型转换。

const_cast

只能改变运算对象的底层const。

用处:

  1. 改变表达式的常量属性。

dynamic_cast

支持运行时类型识别。

reinterpret_cast

通常为运算对象的位模式提供较低层次上的重新解释。

(不太懂,但用不到)

第五章 语句

try语句块和异常处理

throw表达式

程序的异常检测部分使用throw表达式引发一个异常。

try语句块

1
2
3
4
5
6
7
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
}

标准异常

  • exception:一个头文件,定义了最通用的异常类exception,职报告异常的发生,不提供任何额外信息。
  • stdexcept:一个头文件,定义了几种常见的异常类。

image-20211104202801756

  • new头文件定义了bad_alloc异常类型。
  • type_info头文件定义了bad_cast异常类型。

第六章 函数

内联函数

在每个调用点上“内联的”展开。

(就是直接进行函数内部操作,不用去调用函数了)

第七章 类

this

成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。

友元函数

类可以允许其他类或者函数访问它的非公有成员,只要令其他类或者函数成为它的友元,即加上friend关键词。

第八章 IO库

第九章 顺序容器

vector对象是如何增长的

当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。容器预留这些空间作为备用,可以用来保存更多的新元素。

size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。

第十章 泛型算法

lambda表达式

一个lambda表达式表示一个可以调用的代码单元,可以理解为一个未命名的内联函数。

一个lambda表达式具有如下形式:

[capture list] (parameter list) -> return type {function body}

capture_list 是一个lambda所在函数中定义的局部变量的列表。

第十一章 关联容器

无序容器

无序容器在存储上组织为一组桶,每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。如果容器允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。

第十二章 动态内存

智能指针

shared_ptr

类似vector,允许多个指针指向同一个对象。

make_shared函数在动态内存中分配一个对象并初始化它。

程序使用动态内存出于以下三种原因之一:

  1. 程序不知道自己需要使用多少对象
  2. 程序不知道所需对象的准确类型
  3. 程序需要在多个对象之间共享数据

unique_ptr

一个unique_ptr”拥有“它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

第十三章 拷贝控制

拷贝、赋值和销毁

拷贝构造函数

一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

直接初始化 & 拷贝初始化

直接初始化:编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。

拷贝初始化:编译器将右侧运算对象拷贝到正在创建的对象中。

拷贝初始化在以下情况下会发生:

  1. “=”
  2. 将一个对象作为实参传递给一个非引用类型的形参
  3. 从一个返回类型为非引用类型的函数返回一个对象
  4. 用花括号列表初始化一个数组中的元素或者一个聚合类中的成员

拷贝赋值运算符

拷贝赋值运算符接受一个与其所在类相同类型的参数:

1
2
3
4
class Foo {
public:
Foo& operater=(const Foo&); //赋值运算符
};

析构函数

析构函数释放对象使用的资源,并销毁对象的非static数据成员。他没有返回值,也不接收参数,因此它不能被重载。

当指向一个对象的引用或者指针离开作用域时,析构函数不会执行。

第十四章 重载运算与类型转换

(不是很用得上,感觉很类似工具书)

第十五章 面向对象程序设计

第十六章 模板与泛型编程

定义模板

函数模板

1
2
3
4
5
6
template <typename T>
int compare(const T &v1, const T &v2){
if(v1 < v2) return -1;
if(v2 < v1) return 1;
return 0;
}