c++实战知识点
- 一、概述
- 1.数据
- 2.C++11的原始字面量
- 3.数据类型的别名
- 4.const修饰指针
- 5.void关键字
- 6.内存模型
- 7.二级指针
- 8.函数指针和回调函数
- 9.数组
- 10.C风格字符串
- 11.二维数组用于函数的参数
- 行指针(数组指针)
- 12.引用
- 引用与const
- 13.各种形参的使用场景
- 14.重载operator new 和 operator delete
- 15.内存池
- 二、继承
- 1.继承方式
- 2.继承的对象模型
- 3.名字遮蔽与类作用域
- 4.继承的特殊关系
- 5.运行阶段类型识别-dynamic cast
- 三、typeid运算符和type_info类
- 四、函数模板
- 1.函数模板的特化
- 2.关于函数模板的分文件编写
- 3.decltype关键字
- 4.函数后置返回类型
- 五、模板类
- 1.嵌套和递归使用模版类
- 2.模板类与继承
- 3.模板类与函数
- 4.模板类与友元
- 六、编译预处理
- 七、C++类型转换
- 八、容器
- 1.string容器
- 构造和析构
- string容器操作
- 2.vector容器
- 构造和析构
- vector容器操作
- 3.迭代器
- 4.关于容器中的范围for循环
- 5.list容器
- 构造函数
- 容器的操作
- 6.pair键值对
- 7.map
- 构造函数
- 容器的操作
- map的分段构造
- 8.unordered_map
- 构造函数(和map一样)
- 容器操作
- 9.queue容器
- 构造函数
- 容器操作
- 10.其他容器
- array
- set & multiset
- priority_queue
- 九、STL算法
- 1.for_each
- 2.find_if
- 3.sort
- 4.总结
- 十、函数对象
- 十一、智能指针
- 1.unique_ptr
- 2.shared_ptr
- 3.weak_ptr
- 4.智能指针的删除器
- 十二、文件操作
- 1.写文本文件
- 2.读文本文件
- 3.写二进制文件
- 4.读二进制文件
- 5.更多细节
- 6.文件的位置指针
- 7.文件缓冲区和流状态
- 十三、C++异常
- 十四、C++断言
- 十五、C++11新标准
- 1.long long 类型
- 2.char_16t和char_32t 类型
- 3.原始字面量
- 4.统一的初始化(列表)
- 5.自动推导类型auto
- 6.decltype关键字
- 7.函数后置返回类型
- 8.模板的别名
- 9.空指针nullptr
- 10.智能指针
- 11.异常
- 12.强类型枚举(枚举类)
- 13.explicit关键字
- 14.类内成员初始化
- 15.基于范围的for循环
- 16.新的STL容器
- 17.新的STL方法(成员函数)
- 18.摒弃export
- 19.嵌套模板的尖括号
- 20.final关键字
- 21.override关键字
- 22.数值类型和字符串之间的转换
- 23.静态断言
- 24.常量表达式constexpr
- 25.默认函数控制 =default 和 =delete
- 26.委托构造和继承构造
- 委托构造
- 继承构造
- 27.lambda表达式
- 28.右值引用
- 左值、右值
- 左值引用、右值引用
- 总结
- 29.移动语义
- 注意事项
- 30.完美转发
- 31.可变参数模板
- 十六、时间操作chrono库
- 时间长度
- 系统时间
- 定时器
一、概述
1.数据
数据的分类:数字、字符、字符串。
-
变量的作用域:
-
全局变量:在全部函数外面定义。在定义位置之后的任意函数中都能访问。
-
局部变量:在函数和语句块内部定义。在函数返回或语句块结束时由系统回收。
-
函数的参数是该函数的局部变量。
-
函数内部用static修饰的是静态局部变量。只执行初始化一次,直到程序运行结束回收。
-
静态局部变量作用域被限制在定义它们的函数内。这是因为静态局部变量的存在是为了保存函数内的状态,而不影响全局或外部状态。如果一个函数既可以用全局变量也可以用静态局部变量满足该函数要求,优先考虑静态局部变量,因为更安全。
- 字符的本质
- 字符的本质是整数,取值范围是0~127。
- 在书写的时候可以用单引号包含,也可以用整数。
- 可以与整数进行任何运算,运算的时候,可以用符号,也可以用整数。
- 在ASCII码表中,0~31是控制字符,只能用十进制表示,不能用符号表示。因此用转义字符来书写它们。
2.C++11的原始字面量
- 如果在字符串中存入路径,需要两个\\,以免一个\后面的字符被认为是控制字符。
- 如果路径过长,再输入一个\比较麻烦,因此用原始字面量。
- 此外,如果字符串过长,可以用原始字面量避免用连接符连接字符串,还可以顺便对齐格式。
3.数据类型的别名
4.const修饰指针
- 常量指针:const 数据类型 *变量名
- 不能通过解引用的方法修改内存地址中的值,可以用原始的变量名修改。
- 指针常量:数据类型 * const 变量名
- 指向的变量(对象)不可以改变。
- 在定义的同时必须初始化。
- 可以通过解引用修改内存地址中的值。
- 常指针常量:const 数据类型 * const 变量名
- 指向的对象不可改变,不能通过解引用的方法修改内存地址中的值
5.void关键字
- 函数的形参用void*,表示接受任意数据类型的指针。传入函数的指针不需要进行强制类型转换。
- 不能用void声明变量,它不代表一个真实的变量。
- 不能对void*指针直接解引用,需要转换为其他类型的指针。
- 把其他类型的指针赋值给void*指针不需要转换。
- 把void*指针赋值给其他类型的指针需要转换。
- 在C++中,在控制台打印字符型变量的地址会有bug。因此转换为void*类型再打印。
6.内存模型
7.二级指针
指针是指针变量的简称,也是一种变量,是变量就有地址。
指针用于存放普通变量的地址,二级指针用于存放指针变量的地址。
把普通变量的地址传入函数后可以在函数中修改变量的值,把指针的地址传入函数后可以在函数中修改指针的值。
也可以用一级指针加引用的方式修改指针变量。(把指针当成普通变量)
8.函数指针和回调函数
使用函数指针:
- 声明函数指针
- 让函数指针指向函数的地址
- 通过函数指针调用函数
实际业务:
回调函数。我们写回调函数的时候,只确定回调函数的种类,不关心具体的功能实现。
9.数组
- 关于为什么数组当函数参数传入函数,同时也要传数组的长度?
- 数组作为函数参数时被解释为指针。如果用sizeof(数组名),其实是指针的大小,8个字节,没办法得到数组实际的长度。
- 数组的排序(qsort)
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *))
- 参数:起始地址、元素个数、元素大小、回调函数地址
10.C风格字符串
- C语言:如果char类型的数组的末尾包含了\0,那么该数组中的内容就是一个字符串。
- 清空字符串:memst
字符查找strchr和字符串查找strstr
11.二维数组用于函数的参数
行指针(数组指针)
语法:数据类型 (*行指针名) [行的大小]
一维数组名被解释为数组第0个元素的地址。
数组名在大多数情况下会隐式转换为指向数组第一个元素的指针。这意味着数组名本身是一个指向数组首元素的指针。
对一维数组名取地址得到的是数组的地址,是行地址。
使用取地址运算符 &
对数组名取地址时,得到的是一个指向整个数组的指针(数组指针)。这是一个与数组名本身不同的指针类型。
12.引用
引用作为函数参数,可以避免使用二级指针。
引用与const
普通引用:只能绑定到左值。即,普通引用必须引用一个持久存在的对象。
const
引用:可以绑定到左值和右值(包括临时对象)。这是因为 const
引用不会修改其绑定的对象,因此它可以安全地引用临时对象。
如果函数的实参 不是左值 或 与const引用形参的类型不匹配,那么c++将创建正确类型的匿名变量,将实参的值传递给匿名变量,让形参来引用该变量。
假设我们有如下函数,右值作为实参:
void print(const std::string& str) {
std::cout << str << std::endl;
}
我们可以传递一个右值(如字符串字面值)给这个函数:
print("Hello, World!");
在这种情况下,"Hello, World!"是一个右值,类型是 const char[13]。为了将其传递给 const std::string& 形参,C++ 会执行以下步骤:
- 创建一个匿名的
std::string
对象,并将右值"Hello, World!"
的值赋给这个对象。 - 让形参
str
引用这个匿名的std::string
对象。
最终,这个临时的 std::string
对象会在函数调用结束后被销毁。
我们也可以传递一个类型不匹配但可以转换的实参:
int num = 123;
print(std::to_string(num));
在这种情况下,std::to_string(num)
返回一个临时的 std::string
对象,这也是一个右值。处理方式与上面的例子类似。
13.各种形参的使用场景
- 不需要在函数中修改实参
- 如果实参很小,是基本数据类型或很小的结构体,用值传递。
- 如果实参是数组,则使用const指针,这是唯一选择。(没有为数组建立引用的说法)
- 如果实参是较大的结构体,则使用const指针或const引用。
- 如果实参是类,则使用const引用,传递类的标准方式是引用传递。
- 需要在函数中修改实参。
- 如果实参是内置数据类型,则使用指针。
- 如果实参是数组,只能使用指针。
- 如果实参是结构体,使用指针或引用。
- 如果实参是类,使用引用。
14.重载operator new 和 operator delete
15.内存池
该类占有8个字节大小,因此设计一个18字节大小的内存池,分为两块,每块第一个字节表示该块是否被占用。
-
重载的operator new 和 operator delete函数都是静态函数,因此无法使用类的普通成员变量。
二、继承
1.继承方式
-
不管继承方式如何,基类中的private成员在派生类中始终不能使用。
-
使用using关键字可以改变基类成员在派生类的访问权限。
2.继承的对象模型
- 创建派生类时,先调用基类的构造函数,再调用派生类的构造函数。
- 基类的构造函数负责初始化被继承的数据成员,派生类的构造函数主要用于初始化新增的数据成员。
- 创建派生类对象时,只会申请一次内存,派生类对象包含了基类对象的内存空间,this指针是相同的。
3.名字遮蔽与类作用域
- 如果派生类中的成员(成员变量和成员函数)和基类中的成员重名,通过派生类对象或者在派生类的成员函数中使用该成员时,将使用派生类新增的成员,而不是基类的。注意:如果派生类中有同名函数,将会遮蔽基类中所有的同名函数。
4.继承的特殊关系
- 可以把派生类对象赋值给基类对象,但是会舍弃非基类的成员。
- 基类指针可以在不进行显示转换的情况下指向派生类对象。
- 基类引用可以在不进行显示转换的情况下引用派生类对象。
- 基类指针和引用只能调用基类的方法,不能调用派生类的方法。
5.运行阶段类型识别-dynamic cast
- 基类指针可以指向派生类对象,如何知道基类指针指向的是哪种派生类的对象,想调用派生类的非虚函数。
- dynamic cast用指向基类的指针生成派生类指针。
- dynamic cast只适用于包含虚函数的类。
三、typeid运算符和type_info类
- typeid运算符用于获取数据类型的信息。
- 语法
- typeid(数据类型)
- typeid(变量名或表达式)
- typeid运算符返回type_info类(#include)的对象的引用。
- typeid重载了==和!=运算符,因此可以用于多态的场景,在运行阶段识别对象的数据类型。
四、函数模板
- 可以为类的成员函数、构造函数创建模版,但是不能为虚函数和析构函数创建模板。
1.函数模板的特化
- 关于函数模板的特化,对于自定义数据类型。
- 比如swap交换两个对象,但是自定义数据类型,我们只想交换其中某一种变量,比如说age,而不是整个对象。为此就需要特化。
- 有两种方法,推荐使用类成员函数+非成员函数方法:
2.关于函数模板的分文件编写
- 普通函数的声明在.h中,实现在.cpp中。
- 函数模板的声明和实现都在.h中。
- 特化函数模板的声明在.h中,实现在.cpp中。
- 假如有一个场景:
- 怎么定义tmp的类型?
- 怎么定义tmp的类型?
3.decltype关键字
- decltype用于查询表达式的数据类型
- decltype(expression) var;
- decltype分析表达式并得到它的类型,并用该类型声明一个变量var。
- decltype(expression) var;
- 如果expression是没有用括号扩起来的标识符,则var的类型与该标识符的类型相同,包括const限定符。
- 如果expression是函数调用,则var的类型与函数的返回值类型相同。
- 如果expression是左值(能取地址)、或者用括号扩起来的标识符,那么var的类型是expression的引用。
4.函数后置返回类型
- int fun(int x , double y) == auto fun(int x, double y) -> int
- auto是一个占位符,为函数返回值占了一个位置。
- 回到最开始的func函数中,假设我们想要返回tmp。
- 因为tmp的类型我们不知道,因此必须使用后置返回类型才可以返回tmp。
五、模板类
1.嵌套和递归使用模版类
- 再写一个自定义的Vector
- 接下来我们尝试用vector的自动扩容函数,会发现有个问题。
- 这个问题在于,resize函数中会将指向原来的内存空间指针释放,如果发生浅拷贝,那么新指针中的内容也会被释放,造成悬空指针。如果不delete之前的指针,会造成内存泄漏,治标不治本。
- 既然tmp[i] = data_[i] 会调用类的拷贝赋值函数,之前Stack的拷贝赋值函数没有重写,导致浅拷贝,那么增加深拷贝代码即可解决问题。
- 或者使用移动语义,Stack提供移动构造和赋值函数。
- 这个问题在于,resize函数中会将指向原来的内存空间指针释放,如果发生浅拷贝,那么新指针中的内容也会被释放,造成悬空指针。如果不delete之前的指针,会造成内存泄漏,治标不治本。
2.模板类与继承
- 模板类继承普通类
- 普通类继承模板类
- 普通类继承模板类实例版本
- 模板类继承模板类
3.模板类与函数
- 类的实例化版本
- 函数模板
4.模板类与友元
- 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
- 非约束模板友元:模板类实例化时,每个实例化的类对应n个友元函数。
- 模板类嵌套模板类
六、编译预处理
- C++中常用的宏
- 当前源代码文件名:_FILE_
- 当前源代码函数名:_FUNCTION_
- 当前源代码行号:_LINE_
- 编译的日期:_DATE_
- 编译的时间:_TIME_
- 编译的时间戳:_TIMESTAMP_
- 当用C++编译程序时,宏__cplusplus就会被定义,用于区分c和c++
- 编译和链接
- 对单个文件进行编译
- main函数依赖两个文件
- 把所有需要的文件编译完
- 所有文件编译完成后,开始生成.exe
- 分开编译的好处:每次只编译修改过的源文件,然后再链接,效果最好。
- 编译单个*.cpp文件的时候,编译器只需要知道名称的存在,不会把它们的定义一起编译。
- 但是函数或类的定义不存在,编译不会报错,链接会出现无法解析的外部符号。
- 链接的时候,变量、函数和类的定义只能有一个,否则会出现重定义的错误。
- 把变量、函数和类的定义放在*.h中是不规范的做法,如果*.h被多个*.cpp包含,会出现重定义。
- 尽可能不使用全局变量,如果一定要用,要在*.h文件中声明(需要加extern关键字),在*.cpp中定义。
- 全局的const常量只在单个文件中有效。在头文件中定义。
- 对单个文件进行编译
七、C++类型转换
-
C++认为C风格的类型转换可能会带来隐患,提供了四个关键字:static_cast,const_cast,reinterpret_cast和dynamic_cast用于类型转换。
-
static_cast
- 用于内置数据类型之间的转换
- 用于指针之间的转换
- 用于内置数据类型之间的转换
-
reinterpret_cast
- static_cast不能用于转换不同类型的指针,reinterpret_cast可以。
-
const_cast
- static_cast不能丢掉指针(引用)的const和volitale属性,const_cast可以。
-
dynamic_cast
-
dynamic_cast是 C++ 中用于在运行时进行类型安全的强制类型转换的一种运算符。它主要用于在类层次结构中,将基类指针或引用转换为派生类指针或引用。
dynamic_cast
需要运行时类型信息 (RTTI) 的支持,因此它只能在包含虚函数的多态类型中使用。 -
源类型必须是指针或引用,目标类型必须是多态类型。
-
假设有以下类层次结构:
class Base { virtual void foo() {} // 必须有虚函数 }; class Derived : public Base { void bar() {} }; class AnotherDerived : public Base { void baz() {} };
- 向下转型
Base* basePtr = new Derived(); Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); if (derivedPtr) { derivedPtr->bar(); // 转换成功,可以调用 Derived 类的方法 } else { // 转换失败,derivedPtr 为 nullptr }
- 失败的情况
cpp复制代码Base* basePtr = new AnotherDerived(); Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); if (derivedPtr) { derivedPtr->bar(); // 不会执行,因为转换失败 } else { // 转换失败,derivedPtr 为 nullptr }
- 引用转换
Base& baseRef = *basePtr; try { Derived& derivedRef = dynamic_cast<Derived&>(baseRef); derivedRef.bar(); // 如果转换成功,可以调用 Derived 类的方法 } catch (const std::bad_cast& e) { // 转换失败,抛出 std::bad_cast 异常 std::cerr << "bad_cast: " << e.what() << std::endl; }
-
-
八、容器
1.string容器
构造和析构
- 静态常量成员string::npos为字符数组的最大长度。
string容器操作
2.vector容器
构造和析构
vector容器操作
3.迭代器
- 正向迭代器
- 双向迭代器
- 随机访问迭代器
4.关于容器中的范围for循环
- 如果容器中的元素是结构体和类,迭代器变量应该申明为引用,只读加const约束。
- 如果不加&,每次都是把v中的元素拷贝给i,开销很大。
5.list容器
- list容器封装了双链表。
构造函数
容器的操作
6.pair键值对
- 编译器做了优化,匿名对象和临时对象不调用拷贝构造函数
7.map
- map容器的元素是pair键值对。
构造函数
容器的操作
-
- 关于[]运算符:如果指定键不存在,会向容器中添加新的键值对,如果指定键存在,则读取或修改容器中指定键的值。
- at()成员函数:如果指定键不存在,不会向容器中添加新的键值对,抛出out_of_range异常。
map的分段构造
8.unordered_map
- 封装了哈希表,查找、插入和删除元素时,只需要比较几次key的值。
构造函数(和map一样)
容器操作
9.queue容器
- 逻辑结构是队列,物理结构可以是数组或链表,主要用于多线程之间的数据共享。
- 不支持迭代器
构造函数
- 不能用vector容器构造queue。
容器操作
10.其他容器
array
set & multiset
- set中的元素只有关键字。
priority_queue
- 优先级队列相当于一个有权值的单向队列,在这个队列中,所有元素是按照优先级排序的。
九、STL算法
1.for_each
- 模版编程,通过传迭代器来支持多种类型。
- 如果需要遍历的时候,使用的方法自定义,一种是用函数指针传给foreach具体的方法,一种是仿函数。
- 这两个方法除了foreach第三个参数,其他地方都是一样的,也可以用模板来修改为通用。
- 现在有一个问题,不管是函数还是仿函数,都只支持string类型,现在修改为模板函数。
- 如果使用stl的for_each函数,结果也是一样,说明我们实现的foreach和for_each是一样的。
2.find_if
- 关于为什么stl算法可以用仿函数?仿函数写起来感觉比普通函数麻烦,那么仿函数有什么用?
- 假设我们需要自定义想要的元素,那么普通函数应该这么修改。
- 假如我们需要的参数越来越多,那么普通函数所需要的参数也就会越来越多,我们的框架(算法findif)也需要一同修改,这个不符合代码规范。除非用回调函数传参数的方法。如果用仿函数就不存在这个问题了。
3.sort
- 适用于数组容器vector、string、deque,list容器有成员函数sort,红黑树和哈希表容器没有排序的说法。
- 现在算法如果要换成从大到小排序,或者设计某些复杂的排序规则,那么这样是不够的,因此我们还是用回调的方法来调用程序员自己指定的排序的规则。
4.总结
- STL提供了很多处理容器的函数模板,它们的设计是相同的,特点为:
- 用迭代器表示需要处理数据的时间。
- 返回迭代器放置处理数据的结果。
- 接受一个函数对象参数(结构体参数),用于处理数据(有需要的话)。
十、函数对象
- 函数对象:也叫函数符(functor),包括函数名、函数指针和仿函数。
- 生成器:不用参数就可以调用的函数符。
- 一元函数:用一个参数可以调用的函数符。
- 二元函数:用两个参数可以调用的函数符。
- 一元谓词:返回bool值的一元函数。
- 二元谓词:返回bool值的二元函数。
- 预定义的函数对象:
- 包含头文件
- 如果容器有成员函数,则使用成员函数,如果没有才考虑STL算法。
- 常用STL算法(遍历、排序、查找):
-
- lower_bound:用于查找范围内第一个不小于给定值的元素的位置。换句话说,它返回的是指向第一个大于或等于(≥)给定值的元素的迭代器。
-
- upper_bound:用于查找范围内第一个大于给定值的元素的位置。换句话说,它返回的是指向第一个严格大于(>)给定值的元素的迭代器。
-
- equal_range:用于查找范围内等于给定值的元素的范围。换句话说,它返回的是一对迭代器,第一个迭代器指向第一个不小于给定值的元素,第二个迭代器指向第一个大于给定值的元素。
十一、智能指针
- 普通指针的不足:
- new 和 new[]的内存需要用delete和delete[]释放。
- 程序员的主观失误,忘了释放。
- 程序员也不确定什么时候释放。
1.unique_ptr
- 独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr销毁时,被指向的对象也被销毁。
- 注意事项:
- 不要用同一个裸指针初始化多个unique_ptr对象。
- get()方法返回裸指针。
- 不要用unique_ptr管理不是new分配的内存。
- 用于函数中的参数:
- 传引用(unique_ptr没有拷贝构造函数,不能传值)
- 裸指针
- 更多技巧:
- 将一个unique_ptr赋给另一个的时候,如果源unique_ptr是一个临时右值,编译器允许这么做。如果源unique_ptr将存在一段时间,编译器禁止这样做(unique_ptr删除了拷贝函数)。一般用于函数的返回值。
- 用nullptr给unique_ptr赋值将释放对象,空的unique_ptr = nullptr
- release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针。(可以用于将unique_ptr传递给子函数,子函数将负责释放对象。)
- std::move()可以转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)。
- 四种应用场景:
- swap()交换两个unique_ptr的控制权。
- unique_ptr也可以像普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
- unique_ptr不是绝对安全,如果程序调用exit()退出,全局的unique_ptr可以释放,局部的unique_ptr无法释放。
- unique_ptr提供了支持数组的具体化版本。
- 将一个unique_ptr赋给另一个的时候,如果源unique_ptr是一个临时右值,编译器允许这么做。如果源unique_ptr将存在一段时间,编译器禁止这样做(unique_ptr删除了拷贝函数)。一般用于函数的返回值。
2.shared_ptr
- shared_ptr共享它指向的对象,多个shared_ptr可以指向相同的对象,在内部采用计数机制来实现。
- 当新的shared_ptr与对象关联时,引用计数增加1。
- 当引用计数变为0时,表示shared_ptr没有与任何对象关联,则释放该对象。
- shared_ptr没有删除拷贝构造和赋值函数。
- 初始化
- 使用方法
- use_count()方法返回引用计数器的值。
- unique()方法,如果use_count()为1,返回true,否则返回false。
- get()方法返回裸指针。
- shared_ptr支持赋值,左值的shared_ptr计数器将减1,右值shared_ptr的计数器将加1。
- 不要用同一个裸指针初始化多个shared_ptr。
- 不要用shared_ptr管理不是new分配的内存。
- 更多细节
- 存在的问题
- 如果出现了循环引用的问题,引用计数永远无法归0,资源就不会被释放。
- 如果出现了循环引用的问题,引用计数永远无法归0,资源就不会被释放。
3.weak_ptr
- weak_ptr是为了配合shared_ptr引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期。也就是说将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
- 不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放。
- weak_ptr没有重载->和*运算符,不能直接访问资源。
4.智能指针的删除器
- 在默认情况下,智能指针用delete 原始指针 来释放它管理的资源。
- 程序员可以自定义删除器,改变智能指针释放资源的行为。
- 删除器可以是全局函数、仿函数和lambda表达式,形参为原始指针。
十二、文件操作
1.写文本文件
2.读文本文件
3.写二进制文件
4.读二进制文件
5.更多细节
6.文件的位置指针
- 对文件进行读、写操作时,文件的位置指针指向当前文件读、写的位置。不管用哪个类操作文件,位置指针只有一个。
- 获取文件位置指针
- 移动文件位置指针
7.文件缓冲区和流状态
- 在缺省状态下,输出缓冲区中的数据满了才把数据写入磁盘,但是这种模式不一定能满足业务的需求。
- 输出缓冲区的操作:
- flush()成员函数:直接刷新缓冲区。
- endl:换行然后刷新缓冲区。
- unitbuf:设置fout输出流,在每次操作之后都进行刷新缓冲区。
- fout<<unitbuf;
- nounitbuf:设置fout输出流,让fout回到缺省的状态。
- fout<<nounitbuf;
- 流状态
- eofbit
- 当输入流操作到达文件末尾的时候,将设置eofbit。
- eof()成员函数检查流是否设置了eofbit。
- badbit和failbit
- clear()成员函数清理流状态。
- setstate()成员函数重置流状态。
- eofbit
十三、C++异常
- 语法
- 捕获一切异常
- 捕获指定的异常
- 捕获一切异常
- 如何避免异常
- 实际开发中上述写法已弃用,写起来太麻烦。
- 重点关注的异常
- std::bad_alloc
- 如果内存不足,调用new会产生该异常,导致程序终止。
- std::bad_cast
- dynamic_cast可以用于引用和指针,如果是指针转化不正确会返回空指针,但是没有与空指针对应的引用值,如果转换请求不正确,会抛出该异常。
- dynamic_cast可以用于引用和指针,如果是指针转化不正确会返回空指针,但是没有与空指针对应的引用值,如果转换请求不正确,会抛出该异常。
- std::bad_typeid
- 假设有表达式typeid(*ptr),当ptr是空指针时,如果ptr是多态的类型,将引发std::bad_typeid异常。
-
-
- std::bad_alloc
- 实际开发中上述写法已弃用,写起来太麻烦。
十四、C++断言
- C++11静态断言
十五、C++11新标准
1.long long 类型
2.char_16t和char_32t 类型
3.原始字面量
见第一章:C++11的原始字面量
4.统一的初始化(列表)
5.自动推导类型auto
6.decltype关键字
在函数模板中介绍。
7.函数后置返回类型
在函数模板中介绍。
8.模板的别名
9.空指针nullptr
10.智能指针
在第十一章
11.异常
12.强类型枚举(枚举类)
13.explicit关键字
在自动类型转换中有说明
14.类内成员初始化
15.基于范围的for循环
16.新的STL容器
17.新的STL方法(成员函数)
18.摒弃export
19.嵌套模板的尖括号
20.final关键字
21.override关键字
22.数值类型和字符串之间的转换
23.静态断言
在断言一章介绍
24.常量表达式constexpr
函数形参用const修饰只是表示只读,不会在函数内修改传入的实参,并不表示常量。
25.默认函数控制 =default 和 =delete
26.委托构造和继承构造
委托构造
继承构造
27.lambda表达式
- 捕获列表
28.右值引用
左值、右值
左值引用、右值引用
总结
29.移动语义
如果涉及到大量的资源拷贝,那么拷贝构造和拷贝赋值就会很慢,我们可以考虑将原始指针移动到新对象中,避免了拷贝数据的开销。
注意事项
30.完美转发
直接调用func1函数是可以的,但是如果调用其他函数,其他函数调用了这两个函数,怎么保证属性不变?
main将参数传给func2的时候有左右值区分,我们希望func2把参数传给func1的时候,也要保留左右值区分。
只能通过显示调用move来解决。如果是模板函数呢?
也就是说,只有模板函数的参数使用两个&可以既接受左值,又接收右值,普通函数不行。
31.可变参数模板
除了可变参数,还可以有其他常规参数。
假设写一个表白函数,表白前先喊一句口号。
十六、时间操作chrono库
时间长度
系统时间
如果需要打印日志,将时间存起来,需要使用stringstream转化。
定时器