目录
全缺省与半缺省
注意事项:a.半缺省参数必须从右往左依次给出,不能间隔着给
b.缺省参数不能再函数声明和定义中同时出现
- 函数重载:
- 定义:C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数的个数,类型或类型顺序)不同,这就是重载
- 原理(为什么C++支持函数重载,而C语言不支持函数重载呢?):
名字修饰:在Linux中(Windows同理)C语言函数修饰后名字不变,而C++的函数修饰后名字变成了【_Z+函数长度+函数名+类型首字母】,编译时进行名字修饰。
链接时链接器看到对象调用函数,但是没有函数的地址,就会到符号表中 寻找函数的地址,然后链接到一起去。
所以只要形参列表不同,C++编译器进行链接时就可以区分出具体是哪一个函数
- 引用
- 什么是引用?引用就是给一个已经存在的变量取别名,编译器不会为引用变量开辟内存空间,他和他引用的变量共用同一块内存空间。
- 引用和指针的区别是什么?
- 定义方式:引用必须初始化,指针可以不初始化
- 内存分配:引用无内存分配,指针是一个存储地址的变量,自身占用空间
- 空值:引用不能为空,指针可以为空
- 解引用:引用不能为解引用,指针需要解引用(*)才能获得指向的值
- 赋值:引用初始化后不可以改变,非const的指针可以改变
- 传递参数:引用为移动传参,指针为传递变量的地址
- 自增(++)自减(--):引用自增为其本身+1,指针则是使其指向偏移一个当前类型大小后的地址
- 在sizeof中的含义不同,引用结果为引用类型的大小,指针结果为地址所占的空间的大小(32位环境下位4字节,64位环境下为8字节)
- 存在多级指针,但是不存在多级引用
- 引用比指针更加安全
- 引用的使用场景
- 做参数
- 做返回值
- inline,const和enum(枚举)
- inline的意义和价值是什么
- 消除函数调用的开销,inline声明后,编译器会尝试将函数直接插入到调用点,而不进行常规的函数调用
- 优化小函数,对于访问器和修改器这样的小函数,他们的开销主要来自函数调用本身,而不是函数体中的代码
- 提升代码可读性
- 编译器优化,inline是一种对编译器的建议,不是强制要求,编译器会自己选择是否使用inline
- 宏的缺点
- 为什么要用inline,const和enum代替C语言中的宏(#define):
- 无类型检查,宏只是简单的文本替换,不进行类型检查
- 可读性和可维护性差
- 不支持调试
- 作用域和生命周期,宏没有作用域的概念,运行后整个文件都有效
- 实现复杂以及无法实现过于复杂的逻辑
- inline的意义和价值是什么
- 面向对象与面向过程:
- C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
- C++是面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互解决问题。
- 面向对象的三大特性:
- 封装:封装是隐藏对象的属性和实现细节仅对外公开接口与对象进行交互。类是实现封装的主要手段。减少数据被意外修改的风险,并使得代码更易于理解和维护。
举例:电脑,手机进提供成品
-
- 继承:继承是一种实现代码重用的手段,允许一个类继承另一个类的属性和方法。通过继承,可以创建具有结构层次的类,使得代码更加模块化和可重用。继承的另一个重要用途是实现多态。
- 多态:不同对象对同一消息做出不同的响应。多态使得代码更加灵活和可拓展,可以在不修改代码的情况下添加新的功能。
- 8个默认成员函数
- 构造和析构
- 哪些成员必须在初始化列表初始化?
- const成员
- 引用成员
- 没有默认构造函数的类的类型成员
- 继承的基类如果没有默认构造函数,则必须在初始化列表中初始化
- 需要特定初始化顺序的成员
- 初始化列表的价值?
- 性能优化
- 避免重复初始化
- 确保正确的初始化顺序
- 提高可读性
- 哪些成员必须在初始化列表初始化?
- 拷贝构造和复制重载
- 移动构造和移动赋值
- 默认生成的条件
- 构造和析构
类中没有显式的定义析构函数,拷贝构造函数和赋值重载中的任意一个
-
- 取地址重载和const取地址重载
- 对象实例化
- 类对象的存储方式:只保存成员变量,成员函数保存在代码段中,
- 空类:编译器给了空类一个字节来唯一标识这个类的对象
- 内存对齐:
- 第一个成员变量的地址一般位于类的地址处,有虚函数除外。
- 如果类包含虚函数,则编译器会给该类创建一个虚函数表,并在每个对象中嵌入一个指向该表的指针,虚函数表一般位于对象的开头部分。
- 其他成员变量要对齐到对齐数(VS下一般是8)的整数倍地址处。
- 类对象的总大小为最大对齐数(所有变量类型的最大值与默认对齐参数取最小值)的整数倍。
- 若存在嵌套的情况,则嵌套的结构体对齐到自己的最大对齐数的整数倍处,类的整体大小就是所有最大对齐数的整数倍。
- 大端存储与小端存储?
- 大端存储:数据的高字节保存在内存的低地址中,数据的低字节保存在内存的高地址中
- 小端存储:高高低低
- this指针
C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象,在函数体中所有“成员变量”的操作,都是通过该指针去访问。
this指针本质上是“成员函数”形参,因此对象中不存储this指针
this指针是“成员函数”第一个隐藏的形参,一般情况下由编译器通过ecx寄存器自动传递。
- 运算符重载
- 哪些运算符不能重载?
- 五种{“.”,“sizeof”,“?:”,“.*”,“::”};
- 运算符重载的意义是什么?
- 支持泛型编程,如模版
- 模拟内置类型的行为
- 提高代码的可读性和简洁性
- 使自定义类型和内置类型在语法上更加一致,
- 哪些运算符不能重载?
- static成员
- 静态成员变量一定要在类外进行初始化
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外进行定义,定义时不添加static关键字,类内只是声明
- 类的静态成员可用类名::静态成员访问,也可以用对象名.静态成员进行访问
- 静态成员函数没有隐藏的this指针,不能访问非静态成员
- 静态成员也是类的成员,受public,private,protected访问限定符的限制
- 友元
- 友元会破坏类的封装性,一般情况下不建议使用
- 友元关系是单向的,不具有交换性
- 友元关系不能传递
- 友元关系不能继承
- 友元函数
- 友元函数可以访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受访问限定符的限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
- 友元类
- 内部类
- 内部类就是外部类的友元类
- 内部类可以定义在外部类的public,private,protected
- 内部类可以直接访问外部类的static成员,不需要外部类的对象/类名
- sizeof(外部类)=外部类,和内部类没有任何关系
- 友元会破坏类的封装性,一般情况下不建议使用
- 拷贝对象时编译器的优化
- 返回值优化
当函数返回一个对象时,编译器可能会优化掉不必要的拷贝或移动操作。具体来说,如果函数创建了一个临时对象并返回他,编译器可能会直接调用该函数的上下文中构造这个对象,而不是先构造再拷贝或者移动。
-
- 移动语义
移动语义允许资源从一个对象“移动”到另一个对象,而不是拷贝。如果可能的话,编译器会尽量尝试使用移动操作来替换拷贝操作。
-
- 内联函数
内联函数允许编译器将函数调用替换为函数体的实际代码,这可以消除函数调用的开销。
-
- 引用折叠
在泛型编程中,引用折叠允许编译器在编译时确定引用的类型,从而不免不必要的拷贝或者移动操作
malloc/calloc/realloc的区别是什么?
- malloc:void* malloc (size_t size);
分配的内容不会初始化
- calloc:void* calloc (size_t num, size_t size);
分配的内容会初始化为0,num为要分配的元素的数量
- realloc:void* realloc (void* ptr, size_t size);
- realloc用于调整已经分配的内存块的大小,ptr为要分配的内存块的指针
- 如果扩大内存块,realloc可能会返回一个新的内存地址(如果原内存块后面没有足够的连续空间可用)
- 如果缩小内存块,realloc可能会保持原内存块地址不变,但是多出的内存会释放
- 如果realloc无法满足要求(例如:内存不足)他可能会返回NULL,在这种情况下,原始的内存块保持不变,并且必须在调用realloc后检查返回的指针
- 申请和释放单个元素的空间,使用new和delete,申请和释放连续的空间,使用new[]和delete[]。
- 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。
operator new/operator delete实际上也是通过malloc/free来申请或者释放空间,
使用格式new (place_address)type或者new (place_address)type(initializer-list)
其中place_address必须是一个指针,initializer-list是类型的初始化列表
定位new表达式实际中一般是配合内存池使用,因为内存池分配的内存没有初始化,所以如果是自定义了类型的对象,需要使用new的定义表达式显式调构造函数进行初始化。
举例
class MyClass {public:int value;
MyClass(int v) : value(v) {}
~MyClass() {std::cout << "Destroying MyClass with value " << value << std::endl; }
void show() {std::cout << "MyClass with value " << value << std::endl; } };
int main() {
// 在栈上分配一个足够大的内存块,用于存放 MyClass 的实例
char buffer[sizeof(MyClass)];
// 使用定位 new 在已分配的内存块上构造 MyClass 对象
MyClass* objPtr = new (buffer) MyClass(10);
// 使用对象
objPtr->show();
// 显式调用析构函数,因为定位 new 不会自动调用析构函数
objPtr->~MyClass();
// 注意:此时 buffer 仍然包含 MyClass 的数据,但 objPtr 不再指向一个有效的对象
return 0;
}
注意,定位new需要显示的调用析构函数来销毁对象
- 常见面试题
- malloc/free和new/delete的区别
- 共同点:都是从堆上申请空间,并且需要用户手动释放
- 不同点:
- malloc/free是函数,new/delete是操作符
- malloc申请的空间不会初始化,new申请的空间会初始化且调用构造函数
- malloc申请空间时需要手动计算空间大小并传递(sizeof),new只需要在后面跟上类型即可,如果是多个对象,[]中加入对象的个数即可
- malloc的返回值是void*,必须使用强制类型转换,new不需要
- malloc申请空间失败时,返回NULL,使用时需要判空,new失败时会返回异常,需要捕捉
- malloc/free不会调用构造函数和析构函数,new/delete会调用
- 内存泄漏
- 内存泄漏指的是因为错误或者疏忽而造成的未释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
- 内存泄漏的危害会导致操作系统/后台服务的响应越来越慢,最终卡死。
- 内存泄漏分为堆内存泄漏和系统资源泄漏
- 如何避免内存泄漏
- 养成良好的变成习惯,申请的内存主动释放。
- 采用RAII思想或者智能指针来管理资源
- 使用内存泄漏工具检测(Windows提供了_CrtDumpMemoryLeaks()函数进行内存泄漏简单的检测,只能检测大概泄漏了多少个字节)
- malloc/free和new/delete的区别
- 优点:
- 模版复用了代码,节省资源,更快的迭代开发,STL因此诞生
- 增强了代码的灵活性
- 缺点:
- 模版会导致代码膨胀问题,也会导致编译时间变长
- 出现模版编译错误时,信息非常凌乱,不易定位错误
- 函数模版的实例化
- 隐式实例化:让编译器通过参数推演模版参数的实际类型
- 显式实例化:在函数名后面<>中指定模版参数的实际类型
- 模版参数的匹配规则
- 一个非模版函数可以和一个同名的函数模版同时存在,而且该函数模版可以实例化为该非模版函数。
- 对于非模版函数和同名函数模版,如果其他条件相同,那么在调用时会优先调用这个非模版函数,如果函数模版可以产生有更好的匹配的函数,那么会先用模版。
- 模版函数不允许自动类型转换,但普通函数可以进行自动类型转换。
- 类模版的实例化:类模版实例化需要在类模版后面跟上<>,然后将实例化的类型放在<>中,类模版的名字不是真正的类,实例化的结果才是真正的类。
- 模版参数为类型形参和非类型形参
- 类型形参:出现在模版参数列表中,跟在class或者typename之类的参数类型名称
- 非类型形参:用一个常量作为类(函数)模版的一个参数,可将该参数当做常量使用
- 浮点数,类对象以及字符串是不允许作为非类型模版参数的
- 非类型的模版参数必须在编译期就能确认结果
- 函数模版的特化
- 类模版的特化
- 全特化:将模版参数列表中所有的参数都确定化
- 偏特化
- 部分特化:将模版参数中的一部分参数进行确定化
- 参数更进一步的限制:如将参数限制成指针类型或者引用类型
- 举例:
- unoredered_map默认支持string做key就是特化的一种场景
- 什么是分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有的目标文件链接起来形成单一的可执行文件的过程称为分离编译
- 为什么模版不支持分离编译?
编译器不知道用户将使用那种类型来实例化模版
- 怎么解决及其原理
- 将声明和定义放在一个文件“xxx .hpp”中。确保了当编译器在编译使用模版的源文件时,能够访问到模版的完整定义。
- 模版定义的位置显示实例化。原理同上
1.继承与多态的定义及理解
继承是一种面型对象编程的核心概念,他允许一个类(派生类/子类)获取另一个类(基类/父类)的全部成员变量和成员函数。
友元关系不能被继承。
基类定义static静态成员,则整个继承体系中只有一个这样的成员
多态允许我们使用基类指针或引用来调用派生类的函数(举例:游戏中不同的角色使用同一类技能造成的不同的效果是一种多态)
方法重写允许子类改变父类方法的行为。当子类对象调用一个方法时,如果该方法在子类中已经被重写,则执行子类的方法;否则,执行父类的方法。这种行为在运行时确定,因此被称为动态绑定或运行时多态。
构成多态的两个必要条件:
必须通过基类的指针或引用调用虚函数
被调用的函数必须是虚函数,且派生类必须完成对基类虚函数的重写
2.基类和派生类对象的赋值转换
派生类的对象可以赋值给基类的对象/指针/引用,这是切片
基类的对象不能赋值给派生类的对象
基类如果是多态类型,基类的指针/引用可以通过dynamic_cast函数进行类型转换
3.菱形继承与菱形虚拟继承
菱形继承会产生数据冗余和二义性的问题
虚拟继承可解决以上问题
如上图,B和C的前4字节均指向了一个地址,这个地址称为虚基表指针,指向的地方称为虚基表,虚基表中存储的内容为偏移量,通过偏移量可以找到共享的A
4.继承和组合
public继承是一种is-a的关系,也就是说每个派生类对象都是一个基类对象
组合是一种has-a的关系,假设B组合了A,也就是说每个B对象中都有一个A对象
能用组合优先使用组合,
继承中基类的内部细节对派生类可见,通常被称为白箱复用,破坏了基类的封装,基类的改变对派生类有很大影响,派生类和基类的依赖关系很强,耦合度高。
组合中对象的内部细节是不可见的,通常被称为黑箱复用,对象组合要求被组合的对象具有良好定义的接口,组合类之间没有很强的依赖关系,耦合度低,有利于保持良好的封装性。
5.重载,重写(覆盖)与重定义(隐藏)
重载:两个函数作用域相同,参数或者返回值不同
重写(覆盖):两个函数分别在子类和父类的作用域,函数名,参数和返回值均相同,两个函数是虚函数(virtual)
重定义(隐藏):两个函数分别在子类和父类的作用域,函数名相同即可
6.虚函数
A.虚函数表
答案是8bites,除了_b成员外,还多了一个_vfptr,这是一个指针,指向的内容是虚函数表(简称虚表),这个指针称为虚函数表指针,一个含有虚函数的类至少都有一个虚函数表
继续分析
代码如下
using namespace std;
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Fun3
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base::Func2()" << endl;
}
void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}
- 因为fun3为普通函数,所以他不会放进虚表中,在哪?代码段
- b和d的虚表指针不同,因为我们对fun1完成了重写,所以d的虚表指针中存储的是重写后的函数fun1,所以虚函数的重写也叫做覆盖。重写是语法的称呼,覆盖是原理层的称呼。
- 派生类虚表生成的步骤:
- a.先将基类的虚表中的内容拷贝到自己的虚表中
b.查询派生类中是否对基类的虚函数完成了重写,若完成了重写,则用派生类中自己的虚函数覆盖基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的声明顺序依次添加到派生类的虚表中
- 虚函数和普通函数一样,都是存储在代码段中的,虚表一般也是存在代码段中的,虚表中的内容是虚函数的指针 指向代码段中的虚函数
继续分析
我们将main改成下面的样子
int main()
{
Base b;
Base& b1 = b;
Derive d;
Base& d1 = d;
b1.Func1();
d1.Func1();
return 0;
}
打开监视窗口
结合多态的两个必要条件和切片的原理
1.必须通过基类的指针或引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须完成对基类虚函数的重写
由此我们可以知道多态的原理
不管是通过基类的指针还是通过基类的引用调用虚函数,都不会影响他的虚函数表,多态便是通过虚函数表完成对虚函数的调用,从而实现基类对象调基类虚函数,派生类对象调派生类虚函数,且对于不同的派生类,只要他重写了虚函数,便可以正确调用自己的虚函数
- 多继承中的虚函数表
- 多继承派生类中新增的虚函数放在第一个继承基类部分的虚函数表中
- 若多继承两个基类中均有同一虚函数,派生类重写了该虚函数,则两个虚函数表中均被替换
7.抽象类
在虚函数的后面写上=0,那么该函数为纯虚函数,包含纯虚函数的类称为抽象类
抽象类的派生类必须重写纯虚函数后才能实例化对象
- static是否可以是虚函数?不能,因为静态成员函数中没有this指针,使用类型::成员函数的调用方式无法访问虚函数表
- inline是否可以是虚函数?可以,inline是一种对编译器的建议,编译器可以不接受这种建议,inline成为虚函数时编译器会忽视inline属性
- 构造函数可以是虚函数吗?不能,因为虚函数表指针是在构造函数的初始化列表中才初始化的
- 析构函数可以是虚函数吗?可以并且推荐析构函数是虚函数,
- 对象访问普通函数快还是虚函数快?如果是普通对象,一样快,如果是指针对象或引用对象,则普通函数快,因为多态时调用虚函数需要到虚函数表中查找
- 虚函数表是在什么阶段生成的?存在哪的?虚函数表是在编译阶段生成的,存在于代码段中
- 什么是抽象类?抽象类体现了什么概念?抽象类强制要求重写虚函数,抽象类体现了接口继承的概念。
抛异常:
throw对象;
捕获异常:
try{ }
catch(要捕获的对象){ } //存在多态的话,这里捕获基类可以利用多态获取异常的信息
catch(...){ }
具体实现见gitee:
Project7/Project7/源.cpp · 樊继强/c++ - Gitee.com
本地位置 D:\代码库\c++\CPP_2023\Project7
1.static_cast ->用于相似类型的强制转换
2.reinterpret_cast ->用于不相似类型的强制转换
3.const_cast ->一般用于删除变量的const属性
4.dynamic_cast ->用于父类指针/引用(对象不行)转换成子类指针/引用(注意与切片区分)
具体实现见gitee:
Project6/Project6/源.cpp · 樊继强/c++ - Gitee.com
本地位置 D:\代码库\c++\CPP_2023\Project6
1.设计一个不能被拷贝的类
2.设计一个只能在堆上创建的类
3.设计一个只能在栈上创建的类
4.设计一个不能被继承的类
5.单例模式,设计一个类,只能创建一个对象
具体实现见gitee:
Project5/Project5/源.cpp · 樊继强/c++ - Gitee.com
本地位置 D:\代码库\c++\CPP_2023\Project5
右值:不可以取地址的值
左值:可以取地址的值
右值引用与左值引用的区别:
- 左值引用只能引用左值,不能引用右值
- const左值引用既可以引用左值,又可以引用右值
- 右值引用只能引用右值,不能引用左值
- 右值引用可以引用move以后的左值
移动构造与移动赋值
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
右值引用的使用场景?
- 移动语义:当对象是将亡值时,可以通过右值引用转移资源到另一个对象,而不是拷贝
- 函数参数:
- 函数返回值:对于返回局部对象或临时对象的函数,使用右值引用可避免返回时的拷贝操作,这一点常与移动构造和移动赋值结合起来
如何减少拷贝?提高效率
- 避免深拷贝,仅转移资源的所有权
- 资源转移,不需要进行实际的复制操作
完美引用:模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
完美转发:std::forward<T>(t)在传参的过程中保持了t的原生类型属性
template<typename T>
void PerfectForward(T&& t)
{
Fun(std::forward<T>(t));
}
- [capture-list] (parameters) mutable -> return-type { statement };
1.[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda
函数使用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
2.(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略
3.mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(即使参数为空)。
4.->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。
5.{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
B.使用场景和优势
使用场景:a.自定义类型的对象在排序和比较大小可以使用
b.多线程编程时可以使用
c.大部分算法库和STL接受lambda表达式作为参数
优势:a.简洁性:lambda表达式可以在需要的地方直接定义,无需创建函数或对象
b.灵活性:lambda可以捕获其所在作用域中的变量,
c.可读性:将函数封装在lambda表达式中,可提高代码的可读性
d.性能:lambda表达式在编译时创建,编译器可对其进行优化
具体实现见gitee:
Project_shared_ptr/Project_shared_ptr/源.cpp · 樊继强/c++ - Gitee.com
本地位置 D:\代码库\c++\CPP_2023\Project_shared_ptr
A.优点:
不需要显式的释放资源,有效避免内存泄漏
采用这种方式,资源在对象的生命周期内始终有效
B.思想:
1.RAII:利用对象的生命周期来控制程序资源,即在对象构造时获取资源,接着控制对资源的访问使其在对象的生命周期内始终有效,最后在对象析构的时候释放资源
2.像指针一样使用:完成T& operator*() 和T* operator->()
C.库函数:
- C++98中实现了auto_ptr,auto_ptr->对于拷贝构造,使用了管理权转移的思想,非常不推荐使用
- C++11中引入了unique_ptr,shared_ptr和weak_ptr,
- unique_ptr->直接禁止了拷贝构造和赋值重载
- shared_ptr->使用计数的思想,允许拷贝构造和复制重载,但是产生了循环引用的问题(例如:双向循环链表)同时,被多个进程同时调用时会计数器会产生线程安全的问题,需要使用mutex(锁)
- 由此产生了weak_ptr,重载了由shared_ptr赋值为weak_ptr,解决循环引用的问题weak_ptr<T>& operator=(const shared_ptr<T>& sp)
- shared_ptr本身是线程安全的,因为计数是经过加锁保护的
shared_ptr的管理对象不一定是安全的,需要主动进行加锁保护
- 可变参数模版
- function包装器
- function<Ret(Args...)> ->function<int(int,int)> fun=...;
- bind绑定
- 线程库thread,锁mutex和原子性操作库atomic
- 列表初始化
- auto
- 范围for
- STL容器变化
- 新增容器array,forward_list(单向链表),unordered_set和unordered_map
- 默认生成的函数中增加了移动构造和移动赋值
- 接口的变化:push_back(T&& t)和empalce