ch1 开始
- 编译、运行程序 p3
ch2 变量和基本类型
- 算数类型 p30
- 字面值常量概念 p35
- const 限定符 p53
- const 的引用 p54
- 指针和 const p56
- 顶层 const p57
- constexpr 和常量表达式 p58
- 字面值类型 p59
- 类型别名 (typedef 和 using)p60
- decltype p62
- c++11 规定可以为数据成员提供一个类内初始值 p65
ch3 字符串、向量和数组
- 直接初始化和拷贝初始化 p76
- string::size_type 类型 p79,p297
- 值初始化 p88
- 范围 for 语句 p82,p168
- iterator 和 const_iterator p97
- size_t 类型,在使用数组下标是通常将其定义为该类型;size_t 是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小 p103
- 使用 范围for 处理多维数组,除了最内层数组外的控制变量使用引用类型 p114
- 多维数组的理解,数组名是指向内层数组第一个元素的指针 p115
ch4 表达式
- 左值和右值 p121
- 命名的强制类型转换 static_cast, dynamic_cast, const_cast, reinterpret_cast p144
ch5 语句
- 范围 for 语句,用于遍历容器和序列中的元素 p168
- try 语句块和异常处理,异常类的使用 p172
ch6 函数
- const 形参和实参 p190
- 指针或引用形参与 const p191
- 数组形参,即数组做函数的形参 p193
- 引用返回左值:调用一个返回引用的函数得到左值 p202
- 返回数组指针的函数的写法(返回数组的指针或引用)p205
- 尾置返回类型 和 decltype p206
- 重载和 const 形参(这里易将顶层 const 和 底层 const 相混淆,顶层 const 是等价的声明而不是重载,而底层 const 是可以重载的,如重载普通成员函数和 const 成员函数 p231) p208
- c++ 的名字查找发生在类型检查之前;一旦在内层作用域查找到对应的名字,便进行类型匹配,即使匹配不成功,因为内层作用域的名字隐藏了外层作用域的同名(不同作用域中的同名函数无法重载,即内层作用域中的函数只会隐藏外层作用域中的同名函数),编译器也不会继续向外层查找,而是会报错。解决方法是使用作用域运算符直接调用外层作用域的名字。 p211,p549
- constexpr 函数 p214
- 函数指针 p221
ch7 类
- this 指针原理,this 指针与 const 成员函数关系,const 成员函数可以重载的原因 p231
- 默认构造函数的 = default (c++11)如果我们定义了构造函数后还需要默认构造函数,可以通过在无参构造函数参数列表后写上 = default 来要求编译器生成构造函数 p237
- 拷贝和赋值在甚么情况下发生 p239
- mutable 可变数据成员 p245
- 一个 const 成员函数如果以引用的形式返回 *this,那么它的返回类型将是常量引用;可以重载普通成员函数和 const 成员函数。 p247
- 类的 const、引用或某种未提供默认构造函数的类类型的成员,必须通过构造函数初始值列表来初始化。 p259
- 委托构造函数 p261
- 转换构造函数(只接受一个参数的构造函数定义了隐式转换规则) p263
- 聚合类 p266
- 字面值常量类 p268
- 类静态成员 p268
ch8 IO库
- IO 对象无拷贝或赋值,因此进行 IO 操作的函数通常以引用方式传递和返回流。 p279
- 流的条件状态 iostate badbit failbit eofbit p280
- 刷新输出缓冲 endl flush ends unitbuf p282
- 关联输入输出流 tie p282
- string 流;istringstream, ostringstream 用法 p288
ch9 顺序容器
- deque 双端队列 p292
- 容器类型成员 size_type, value_type p297
- 将一个容器拷贝到另一个容器:直接拷贝(容器及元素类型需匹配) 或 传递迭代器参数拷贝(不需要严格匹配) p299
- array p301
- swap()(用于 array 会真正交换它们的元素,用于其他容器并不会真正交换元素,而是交换容器的内部数据结构) p303
- 赋值运算符要求左右两边运算对象具有相同的类型;assign() 允许从一个不同但相容的类型赋值 p302
- 向顺序容器添加元素 push_back(), push_front(), insert() p305
- 访问容器中的元素 back(), front(), C.at(n)(越界检查) p310
- 删除容器中的元素 pop_back(), pop_front(), erase(), clear() p311
- 特殊的 forward_list 操作:before_begin()(返回首前迭代器), insert_after(), erase_after() p312
- resize() 改变容器大小 p314
- 容器操作(添加,删除,resize)可能使迭代器失效,但 list 和 forward_list 基本不受影响 p305,p311,p314,p315
- 每次循环中都更新迭代器、不保存(缓存)end() 迭代器 p316
- vector 的 capacity(), reserve() p318
- string 的额外操作:substr() p321 append() p323 replace() p323 find() p325
- 数字到字符串的相互转换(数值转换) to_string(), stoi(),stol(),... p327
- 容器适配器 stack queue priority_queue(堆)
ch10 泛型算法
- find() p336
- fill_n() 向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素(否则就要使用插入迭代器) p341
- copy() 插入迭代器back_inserter p341
- sort() unique() p343
- 谓词 接受谓词参数的算法对输入序列中的元素调用该谓词 p344
- lambda 表达式 [capture list](parameter list) -> return type {function body} p345
- find_if() find_if 用来查找第一个具有特定大小的元素,前两个参数接受一对迭代器,第三个参数接受一个一元谓词,find_if 对输入序列中的每个元素调用给定的这个谓词。返回第一个使谓词返回非 0 值的元素,如果不存在,则返回尾迭代器。 p346
- for_each() 此算法接受一个可调用对象,并对输入序列中每个元素调用此对象 p348
- lambda 表达式中被捕获变量的值是在 lambda 创建时拷贝,因此随后对其修改不会影响到 lambda 内对应的值 p350
- transform() 接受三个迭代器(一对迭代器范围和一个目的位置)和一个可调用对象,对输入序列中的每个元素调用可调用对象,并将结果写到目的位置 p353
- 参数绑定 bind() 函数 auto newCallable = bind(callable, arg_list); p354
- count_if 类似于 find_if,接受一对迭代器和一个一元谓词,对输入序列中的每个元素执行。返回一个计数值,表示谓词有多少次为真。 p354
- 插入迭代器 back_inserter front_inserter inserter p358
- iostream 迭代器 istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据 p359
- 默认初始化 istream_iterator 相当于创建一个可以当作尾后值使用的迭代器 istream_iterator<int> eof; p359
- 反向迭代器 reverse_iterator p363
- base() 将反向迭代器转换为普通迭代器(注:a 和 a.base() 指向不同的元素) p364
ch11 关联容器
- 关联容器关键字类型的要求:对于有序容器,关键字类型必须定义元素比较的方法。默认下,标准库使用关键字类型的 < 运算符来比较两个关键字。 p378
- 在有序关联容器中使用自定义类型,需要在定义关联容器类型时提供比较元素的方法 p379
- pair 类型的定义和操作 p380
- 关联容器的 key_type, value_type 和 value type,得到的元素中的关键字部分是 const 的,不能修改 p381
- 关联容器迭代器:当解引用一个关联容器迭代器时,会得到一个类型为容器的 valye_type 的值的引用(关键字部分是 const 的,不能修改) p382
- 向关联容器中添加元素 insert(), empalce() p384
- 添加单一元素的 insert() 和 emplace() 返回一个 pair,pair 的 first 成员是一个迭代器,指向具有给定关键字的元素(在 map 中,该元素也是一个 pair);pair 的second 成员是一个bool 值,指出元素是否插入成功;如果关键字已在容器中,则 insert() 什么事也不做,bool 部分返回 false p384
- 使用 insert() 向 multiset 或 multimap 中添加元素,接受单个元素的 insert 操作返回一个指向新元素的迭代器,无须返回 bool 值,因为总是插入成功的 p386
- 关联容器使用 erase() 删除元素 p387
- 对 map 使用下标操作,如果关键字不在 map 中,则会为它用值初始化创建一个元素并插入到 map 中 p387
- 不能对一个 multimap 或 unordered_multimap 使用下标操作,因为可能有多个值与一个关键字相关联 p387
- 使用 c.at(k) 访问关键字为 k 的元素,带参数检查;若 k 不在 c 中,抛出一个 out_of_range 异常 p387
- 对 map 进行下标操作,得到一个 mapped_type 对象;解引用一个 map 迭代器,得到一个 valye_type 对象 p388
- 关联容器中使用 find() 访问元素;c.find(k) 返回一个迭代器,指向第一个关键字为 k 的元素;若 k 不在容器中,返回尾后迭代器 p389
- 在 multimap 或 multiset 中查找元素,具有相同关键字的元素会相邻存储;所以可以使用 find + count,lower_bound + upper_bound 或 equal_range 来查找元素 p389-391
- 无序容器不适用比较运算符来组织元素,而是使用哈市函数(hash function)和关键字类型的 == 运算符 p394
- 无序容器中的 桶 bucket p395
- 无序容器使用 hash<key_type> 类型的对象来生成每个元素的哈希值,使用关键字类型的 == 来比较元素 p396
ch12 动态内存
- 静态内存、栈内存、动态内存(堆) p400
- p.get() 返回指向 智能指针 p 指向的对象 的一个普通指针, make_shared<T>() 在动态内存中创建一个对象并初始化它,返回指向该对象的一个 shared_ptr p401
- shared_ptr 的拷贝和赋值与引用计数的变化 p402
- 在自由空间分配的内存是无名的,new 无法为其分配的对象命名,new 返回一个指向该无名对象的指针,默认动态分配对象是默认初始化的(不是值初始化) p407
- 动态分配的 const 对象:用 new 分配 const 对象是合法的,类似于其他任何 const 对象,一个动态分配的 const 对象必须进行初始化 p408
- 可以用 new 返回的指针来初始化智能指针;接受指针参数的智能指针构造函数时 explicit 的,因此不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式;同样,一个返回 shared_ptr 的函数不能在其返回语句中隐式转换一个普通指针 p412
- 默认情况下一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用 delete 释放它所关联的对象;可以将智能指针绑定到一个指向其他类型的资源的指针上,但必须提供自己的操作来替代 delete p412
- p.reset(),p.reset(q),p.reset(q, d);若 p 是唯一指向其对象的 shared_ptr,reset 会释放此对象;若传递了可选的参数内置指针 q,会另 p 指向 q,否则会将 p 置为空。若还传递了参数 d,将会调用 d 而不是 delete 来释放 q。 p413
- 不要混合使用普通指针和智能指针 p413 不要使用 get() 初始化另一个智能指针或为智能指针赋值 p414
- 智能指针和异常:发生异常时,直接管理的内存是不会自动释放的。栈解退:对于栈中的自动类对象,类的析构函数将被调用。 p415
- 使用我们自己的释放操作(自定义 delete 操作)、删除器函数 p416
- 当定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上;类似 shared_ptr,初始化 unique_ptr 必须采用直接初始化的方式 p417
- unique_ptr 不支持普通的拷贝或赋值操作 p417 但可以通过调用 release() 或 reset() 将指针的所有权从一个(非 const)unique_ptr 转移到另一个 unique p418
- u.release() unique_ptr u 放弃对 new 返回的指针的控制权,返回该内置指针,并将 u 置为空 p418
- 如果我们不用另一个智能指针保存 release() 返回的指针,我们的程序就要负责资源的释放,否则会永远丢失指针,无法释放该块内存 p418
- 向 unique_ptr 传递删除器 p419
- weak_ptr 指向由一个 shared_ptr 管理的对象;将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数 p420
- w.reset() w.use_count() w.expired() w.lock() p420
- 动态数组:new 分配要求数量的对象并返回指向第一个对象的指针 p423 当用 new 分配一个数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针 p424
- 默认情况下 new 分配的对象都是默认初始化的,可以对它们进行值初始化 p424
- 使用 unique_ptr 管理动态数组 p425
- allocator 类:new 将内存分配和对象构造组合在了一起,delete 将对象析构和内存释放组合在一起;我们可以分配原始内存(未构造的内存),只在真正需要的时候才执行对象创建操作 p427
- a.allocate(n) 分配内存 a.deallocate(p, n) 释放内存 a.construct(p, args) 在内存上构造对象 a.destroy(p) 析构对象 其中 a 是一个 allocator 对象 p428
- uninitialized_copy uninitialized_copy_n uninitialized_fill uninitialized_fill_n 拷贝和填充未初始化内存算法
ch13 拷贝控制
- 如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数时拷贝构造函数。 p440
- 即使我们定义了其他构造函数,编译器也会合成一个拷贝构造函数。 (对于构造函数:一旦我们定义了构造函数,编译器将不再生成默认构造函数)p440
- 直接初始化和拷贝初始化:直接初始化-->通常找最匹配的构造函数;拷贝初始化-->通常使用拷贝构造函数完成。 p441
- 拷贝初始化(拷贝构造函数)发生的场景 p441
- 拷贝赋值运算符 operator= 发生的场景 p444
- 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
- 可以通过将拷贝控制成员定义为 =default 来显式要求编译器生成合成的版本。p449
- 删除的函数:可以将拷贝构造函数和拷贝赋值运算符定义为删除的函数 =delete 来组织拷贝;p449
- private 拷贝控制:在新标准发布之前,类通过将其拷贝构造函数和拷贝复制运算符声明为 private 的来组织拷贝。 p451
- 定义行为像值的类 p453
- 定义行为像指针的类(模拟引用计数) p455
- 重载 swap() 函数 p457
- 拷贝并交换 赋值运算符 p459
- 动态管理内存类 allocate construct deallocate destroy 的应用 p465
ch14
- 除了重载的函数调用元素安抚 operator() 之外,其他重载运算符不能含有默认实参。
- 不能被重载的运算符 ::, .*, ., ? : p491
- 赋值(=)、下标([])、调用运算符(())和成员访问箭头(->)必须重载为类的成员函数 p493
- 重载输入输出运算符必须是类的非成员函数 p494
- 重载输入运算符必须处理输入可能失败的情况(因为流读取错误类型的数据、读取文件尾或遇到其他错误时,输入可能失败),而输出运算符不需要。 p495
- 重载下标运算符通常以所访问的元素的引用作为返回值;最好同时定义下标运算符的常量版本和非常量版本,当作用于一个常量对象时返回常量引用以确保我们不会给返回的对象赋值。 p501
- 重载的后置递增(递减)运算符版本比前置递增(递减)运算符版本多了一个(不被使用)的 int 形参。 p503
- 重载成员访问运算符 * 和 -> p504
- 函数调用运算符:如果类重载了函数调用运算符,则可以像使用函数一样使用该类的对象。可以将该类的对象像函数一样进行调用,调用该类的对象实际上再运行重载的调用运算符。 p506
- 如果类定义了调用运算符,则该类的对象称作函数对象,该类叫做函数对象类。 p506
- 将函数对象类作为函数(如 for_each, find_if, count_if, transform等)的可调用对象参数(谓词参数)时,函数对象类的重载调用运算符函数接受的参数就是迭代器范围中容器中的元素类型。 p507, p509
- lambda 表达式与编译器为该表达式翻译成的一个未命名类的未命名对象相对应。在 lambda 表达式产生的类中含有一个重载的函数调用运算符,可以利用该重载的函数调用运算符完成 lambda 表达式的工作。产生的类实际上就是函数对象类。 p508
- 默认情况下 lambda 不能改变它捕获的变量。可变 lambda 的原理:其对应的类中的重载函数调用运算符不是 const 的。 p508
- 标准库定义的函数对象:plus, modulus, equal_to 等。这些类都被定义成模板的形式,需要为其指定具体的应用类型(如 plus<int> p),它们在头文件 functional 中。 p509
- lambda表达式产生的类不含有默认构造函数、赋值运算符、默认析构函数;
- 标准库规定其函数对象对于指针同样使用。比较两个无关指针将产生未定义的行为,但可以用一个标准库函数对象来比较,因为标准库函数对象定义的指针间的比较是定义良好的。(
sort(vec.begin(), vec.end(), less<string*>());
比较指向 string 的指针)p510 - 关联容器使用 less<key_type> 进行排序。
- 可调用对象包括:函数、函数指针、lambda 表达式、bind 对象以及 重载了函数调用运算符的类。它们的类型不同,但不同类型的可调用对象可能共享同一种调用形式。调用形式指明了调用返回的类型以及传递给调用的参数类型。如 int(int, int) p511
- 标准库类型 function 在 functional 头文件中,是一个模板类型,其模板信息用来表示该 function 类型能够表示的对象的调用形式。function<T> f 对象用来是一个用来存储可调用对象的空 function。p512
function<int(int, int)>
类对象作为形参时,实参可以是:函数指针 add,标准库函数对象 std::minus<int>(),用户定义的函数对象 divide(),未命名的 lambda 对象或命名的 lambda 对象: p512
map<string, function<int(int, int)>> binops =
{
{"+", add}, // 函数指针
{"-", std::minus<int>()}, // 标准库函数对象
{"/", divide()}, // 用户定义的函数对象
{"*", [](int i, int j){return i * j;}}, // 未命名的 lambda
{"%", mod} // 命名的 lambda 对象
};
- 不能直接将重载函数的名字存入 function 对象中,可以使用存储函数指针或 lambda 来消除二义性。 p513
- 用户自定义的类型转换包括 转换构造函数 和 类型转换运算符。 p514
- 类型转换运算符 是类的一种特殊成员函数,负责将一个类类型的值转换成其他类型。一般形式:
operator type() const
,其中 type 不能为数组或函数类型,但可以是指针(包括数组指针及函数指针)或者引用类型。类型转换运算符没有显式的返回类型,也没有形参,必须定义成类的成员函数,通常不应该改变待转换对象的内容,因此一般被定义为 const 成员。 - 可以定义显式的类型转换运算符防止隐式转换,但如果一个表达式被用作条件(如
while(cin >> value)
),则显式的类型转换仍然被隐式的执行。 p516 - 向 bool 转换的类型转换运算符通常用在条件部分,因此 operator bool 一般定义成 explicit 的,以防止
cin << i
这样的语句合法。
ch15 面向对象程序设计
- 虚函数 p528,p536
- 基类和派生类 p527
- 继承与静态成员:每个类静态成员在整个继承体系中只有一个实例
- final 关键字:可以用在类上,防止该类被继承;或用在虚函数后,防止该虚函数被派生类覆盖。 p533,p538
- 可以将基类的指针或引用绑定在派生类对象上。 p534
- 静态类型与动态类型 p534
- dynamic_cast 和 static_cast 类之间的类型转换
- 因为对虚函数的调用可能在运行时才被解析;所有的虚函数都必须有定义,不管该虚函数是否被用到 p536
- c++ 多态性 p537
- 如果某个函数被声明为了虚函数,则在所有的派生类中它都是虚函数 p537
- 一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。同样,派生类中虚函数的返回类型也必须与基类函数匹配;该规则有一个例外,当派生类的虚函数返回类型是类本身的指针或引用时,也允许。 p537
- final 和 override;override:使用 override 关键字的函数必须要覆盖基类中的某个虚函数。 p538
- 虚函数与默认实参:如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。 p539
- 回避虚函数的机制:使用作用域运算符强制执行虚函数的某个版本而不是进行动态绑定。 p539
- 纯虚函数:一个纯虚函数无须定义,但也可以在类外被定义。用 =0 来说明一个虚函数为纯虚函数。 p540
- 抽象基类:含有一个纯虚函数的类,或者一个派生类未覆盖基类的纯虚函数,它们都是抽象基类。不能创建抽象基类的对象 p541
- protected 关键字:受保护的成员对于派生类的成员和派生类的友元时可访问的;派生类的成员和友元只能通过派生类对象来访问基类的受保护成员,而不能通过基类对象来访问基类的受保护成员。 p543
- 共有继承、私有继承、保护继承 p544
- 友元关系不能继承:如基类的友元在访问派生类成员时不具有特殊性。 p545
- 改变派生类继承的某个名字的访问级别:通过 using 声明。 p545
- struct 和 class 的区别:只有默认成员访问说明符和默认派生访问说明符的区别。 p546
- 继承中的类作用域:当存在继承关系时,派生类的作用域嵌套在基类的作用域之内。如果一个名字在派生类作用域内无法正确解析,则编译器继续在外层的基类作用域中寻找。 p547
- 派生类能重定义基类中的名字,此时定义在内层作用域(即派生类)中的名字将隐藏定义在外层作用域(即基类)中的名字。 p548
- 类对象的函数调用过程;名字查找先于类型检查; p549
- 虚析构函数 p552
ch16 模板与泛型编程
- 编译器通常用函数实参来推断函数模板实参;编译器不能为类模板推断模板参数类型。 p579
- 模板类型参数(使用 typename 或 class 标识)以及非类型模板参数 p580
- 函数模板可以声明为 inline 或 constexpr 的 p581
- 模板编译:当编译器遇到一个模板定义时,它并不生成代码;只有当实例化出模板的一个特定版本时,编译器才会生成代码。p582
- 定义在类模板之外的成员函数以关键字 template 开始,后接类模板参数列表。 p585
- 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化 p587
- 在类模板自己的作用域中,我们可以直接使用模板名而不提供类型实参 p587
- 类模板和友元:一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例;如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例 p588
- 为了引用(类或函数)模板的一个特定实例,必须首先声明模板自身,即前置声明 p589
- 一个类也可以将另一个模板的每个实例都声明为自己的友元,或者限定特定的实例为友元。p589
- 在新标准中,可以将模板自身的类型参数声明为友元。 p590
- 类模板的 static 生源:每个特定类型的实例共享一个 static 成员 p591
- 关于模板参数:模板参数会隐藏外层作用域中声明的相同名字;模板内不能重用模板参数名。
- 当我们希望通知编译器一个名字表示类型而不是表示 static 数据成员时,使用关键字 typename, 而能使用 class。如
T::value
要写成typename T::value
p593 - 我们可以为函数和类模板提供默认模板实参 :与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。 p594
- 成员模板:一个类(普通类或模板类)可以包含本身是模板的成员函数,这种成员被称为成员模板。成员模板不能是虚函数。 p595
- 当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表。如 p597
template <typename T> // 类的类型参数
template <typename It> // 成员模板函数构造函数的类型参数
Blob<T>::Blob(It b, It e) : data(std::make_shared<std::vector<T>>(b, e)) {}
- 显式实例化,用来解决多个文件中实例化相同模板的额外开销;一个显式实例化有如下形式: p597
extern template declaration; //实例化声明
template declaration; //实例化定义
- 编译器通常不对函数模板的实参进行类型转换,而是生成一个新的模板实例。能在调用中应用于函数模板的类型转换的包括如下两项:const 转换(可以将一个非 const 对象的引用或指针传递给一个 const 对象 的引用或指针形参) 和 数组或函数指针抓换(如果数组形参不是引用类型,则一个数组实参可以转换为一个指向其首元素的指针,一个函数实参可以转换为一个该函数类型的指针),其他的类型转换,如算术转换、派生类向基类的转换、用户定义的转换,都不能应用于函数模板。 p601
- 指定函数模板显式模板实参: p604
// T1 是显式指定的,T2 和 T3 是从函数实参类型推断而来的
auto val3 = sum<long long>(i, lng); // long long sum(int, long)
此调用显式指定 T1 的类型,T2 和 T3 的类型有编译器从 i 和 lng 的类型推断出来
显式模板实参按从左至右的顺序于对应的模板参数匹配。
21. 标准库的类型转换 remove_reference p605
22. 可以将一个 const 左值引用绑定到右值上 p608
23. 引用折叠和右值引用参数:1)当一个左值(如 i)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如 T&&)时,编译器推断模板类型参数 T 为实参的左值引用类型;2)如果我们间接创建一个引用的引用(如类型别名或模板参数),则这些引用形成了折叠。 p608
24. std::move() 原理 p610
25. 从一个左值 static_cast 到一个右值引用是允许的:虽然不能隐式地将一个左值转换为右值引用,但可以用 static_cast 显式地将一个左值转换为一个右值引用。 p612
26. 转发 某些函数将一个或多个实参连同类型不变地转发给其他函数 p612
27. 如果一个函数参数是指向模板类型参数的右值引用(如 T&&),它对应的实参的 const 属性和左值/右值属性将得到保持 p613
28. std::forward 返回显式实参类型的右值引用,如 forward<T> 返回 T&& ;结合引用折叠,当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward 会保持实参类型的所有细节。 p614
29. 重载与模板 p614
30. 可变模板参数 模板参数包 函数参数包 p618
31. 模板特例化 p624
ch17 标准库特殊设施
- tuple 类型 类似能存放多个类型的 pair p636
- bitset 是一个类模板,模板参数为一个大小 p640
- 正则表达式 头文件 <regex> regex_match 确定整个输入序列与表达式是否匹配 regex_search 确定输入序列中是否有子串与表达式匹配 p643
- 正则表达式类与输入序列类型 smatch cmatch wsmatch wstring p649
- regex 迭代器类型 sregex_iterator 使用 regex_search 找到输入序列中第一个与 regex 表达式匹配的字符串,使用 sregex_iterator 可以获得所有匹配 p650
- 正则表达式中的子表达式 我们使用括号分组 regex 表达式时,同时也声明了这些用括号括起来的子表达式 smatch.str(0) 表示表达式整体,smatch.str(i) 表示第 i 个子表达式匹配的 string p653
- \{d} \{d}{n} [-. ] ? \\ 在 regex 表达式中的含义 p654
- 每个 smatch 对象的子表达式是 ssub_match 类型。可以用 smatch[n] 来表示该对象;如果一个子表达式是完整匹配的一部分,则其对应的 ssub_match 对象的 matched 成员为 true;smatch[n].str() 表示输入中匹配部分的 string。如果 matched 为 false,则返回空 string p655
- regex_replace 在输入序列中查找并替换一个正则表达式
- 随机数引擎 Engine 类生成一个数值序列 p659
- 随机数分布类型 uniform_int_distribution<unsigned> 分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数 p661
- 将函数中随机数引擎和关联的分布对象定义为 static 的,来避免每次调用函数生成相同的序列。 p662
- 随机实数分布 uniform_real_distribution<double> p664
- 格式化输出 操纵符 p666
- 控制输入格式 p672
- 默认情况下,输入运算符会忽略空白符(空格符、制表符、换行符、换纸符和回车符)。操纵符 noskipws 会灵输入运算符读取空白符,而不是跳过它们 p672
- is.get(ch) 从 istream is 读取下一个字节存入字符 ch 中,并返回 is;os.put(ch) 将字符 ch 输出到 ostream os,返回;is.get() is.peek() 将 is 的下一个字节作为 int 返回,所以判断下一个字符是否为 EOF 可以用: p673
int i;
while ((i == cin.get()) != EOF)
...
一个常见的错误是将 get 或 peek 的返回值赋予一个 char 而不是一个 int
18. 多字节操作 is.get(sink, size, delim) (如果遇到了 delim 分隔符,则将其留在输入流中,不读取出来存入 sink) 和 is.getline(sink, size, delim)(如果遇到了 delim 分隔符,则读取并丢弃 delim) is.read(sink, size) is.gcount() os.write(source, size) is.ignore(size, delim) p674
19. 流随机访问 seek tell 只能对 istream 和派生自 istream 的类型 ifstream 和 istringstream,ostream 和 ofstream ostringstream 等类型使用 p676
ch18 异常处理、命名空间、多重继承与虚继承
- throw 语句寻找匹配的 catch 语句过程:栈展开 p684
- 栈展开过程中对象能够被正确的销毁;如果某个局部对象为类类型,则该对象的析构函数将被自动调用(栈解退) p685
- 如果 catch 的参数是基类类型,我们可以使用其派生类类型的异常对象对其进行初始化 p687
- 重新抛出异常:一条 catch 语句通过重新抛出的操作将异常传递给另外一个 catch 语句。重新抛出仍然是一条 throw 语句,且 throw 语句不包含任何表达式。 p688
throw;
- 只有当 catch 异常声明是引用类型时我们对参数所做的改变才会被保留并继续传播。 p688
- 捕获所有异常的处理代码
catch(...)
p688 - 函数 try 语句块与构造函数:构造函数在进入其函数体前首先执行初始值列表。因为在初始值列表抛出异常时构造函数体内的 try 语句块还未生效,所以构造函数体内的 catch 语句无法处理构造函数初始值列表抛出的异常。要想处理构造函数初始值列表抛出的异常,必须将构造函数写成函数 try 语句块。 p689
- noexcept 说明符 指定某个函数不会抛出异常 p670
- noexcept 说明符接受一个可选的实参,该实参必须能转换为 bool 类型:如果实参是 true,则函数不会抛出异常;如果实参是 false,则函数可能抛出异常 p691
void recoup(int) noexcept(true); // recoup 不会抛出异常
void alloc(int) noexcept(false); // alloc 可能抛出异常
- noexcept 运算符:noexcept 运算符是一个一元运算符,它的返回值是一个 bool 类型的右值常量表达式,参数为一个表达式,用于表示给定的表达式是否会抛出异常。p691
noexcept(recoup(i)); // 如果 recoup 不抛出异常则结果为 true,否则结果为 false
- 异常类层次 exception bad_cast bad_alloc runtime_error logic_error
p693 - 命名空间:只要能出现在全局作用域中的声明就能置于命名空间内,包括类、变量(及其初始化操作)、函数(及其定义)、模板和其他命名空间) p695
- 每个命名空间内都是一个作用域,要在一个命名空间外部使用是个命名空间内的名字,要加作用域限定符。 p696
- 命名空间可以是不连续的
namespace nsp {
...
}
可能是定义了一个明为 nsp 的新命名空间,也可能是为已经存在的命名空间添加一些新成员。 p696
15. 全局命名空间:全局作用域中定义的名字(即在所有类、函数及命名空间之外定义的名字)也就是定义在全局命名空间中。全局命名空间以隐式的方式声明,并且在所有的程序中都存在。全局作用域中定义的名字被隐式地添加到全局命名空间中。 p698
16. 内联命名空间:内联命名空间中地名字可以被外层命名空间直接使用。关键字 inline 必须出现在命名空间第一处定义地地方,后续再打开命名空间地时候可以写 inline,也可以不写。 p700
17. 未命名地命名空间 中定义的变量拥有静态生命周期;它们在第一次使用前创建,直到程序结束才销毁。一个未命名地命名空间可以在给定的文件内不连续,但不能跨越多个文件。定义在未命名的命名空间中的名字可以直接使用,我们不能对未命名的命名空间的成员使用作用域运算符。未命名的命名空间中定义的名字的作用域与该命名空间所在的作用域相同。如果未命名的命名空间定义在文件的最外层作用域中,则该命名空间中的名字一定要与全局作用域中的名字有所区别。 p700
int i; // i 的全局声明
namespace {
int i;
}
i = 10; // 二义性
- 未命名的命名空间取代文件中的静态声明:在文件中进行静态声明的做法已经被 c++ 标准取消了,现在的做法是使用未命名的命名空间。 p701
- using 声明:一次引入命名空间的一个成员 p702
- using 指示:using 指示具有将命名空间成员提升到包含命名空间本身和 using 指示的最近作用域的能力。 p702
- 类、命名空间与作用域:当我们给函数传递一个类类型的对象是,对函数的名字查找除了在常规的作用域查找外还会查找实参类所属的命名空间,这一例外对于传递类的引用或指针的调用同样有效。p706
- 与实参相关的查找与重载:对于接受类类型实参的函数来说,其名字查找将在实参类所属的命名空间中进行。这条规则对于我们如何确定候选函数集也有影响:我们将在每个实参类(以及实参类的基类)所属的命名空间中搜寻候选函数。 p708
- 多重继承与虚继承:在多重继承关系中,派生类的对象包含有每个基类的子对象。多重继承的派生类的构造函数初始值也只能初始化它的直接基类,其中基类的构造顺序与派生类列表中基类的出现顺序保持一致,与派生类构造函数初始值列表中基类的顺序无关。 p711
- 允许派生类从它的一个或几个基类中继承构造函数,但如果从多个基类中继承了相同的构造函数(即形参列表完全相同),则程序将产生错误。 p712
- 多重继承的情况下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则对该名字的使用将具有二义性 p715
- 虚继承和虚基类:无论虚基类在继承体系中出现了多少次,在派生类都只包含唯一一个共享的虚基类子对象 p717
- 虚基类成员的可见性:每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性 p719
- 构造函数与虚继承:在虚派生中,虚基类是由最低层的派生类初始化的。虚基类的最低层派生类的构造函数独自控制虚基类的初始化过程。 p720