C++11新特性
1、智能指针
2、Lambda表达式
3、线程库
4、原子操作
5、统一的列表初始化 {}
6、右值引用和移动构造
7、引入nullptr指针
8、类型推导 auto 和 decltype
智能指针:
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源,常用的执政指针如下:
shared_ptr:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。调用 reset() 函数之后,new 对象的引用计数就会减 1,当前智能指针对象被赋值为nullptr;
unique_ptr:独占指针,采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
weak_ptr :弱指针;指向 shared_ptr 管理的 new 对象,却没有该对象的所有权,即无法通过 weak_ptr 对象管理 new 对象。最常见的用法:1、shared_ptr 对象的有效性;2、解决循环引用导致的内存泄露的问题。
auto_ptr:已被弃用,不做讲解
如果手写指针指针类,需要实现的函数:
智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。
除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。
通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1
一个构造函数、拷贝构造函数、复制构造函数、析构函数、移动函数
Lambda表达式
利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象,具体格式如下:
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}
示例:
int nRet = [](int x, int y) {return x + y;};//返回x和y的和
lamda表达式说明:
- []。没有任何函数对象参数。
- [=]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- [&]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
- [this]。函数体内可以使用 Lambda 所在类中的成员变量。
- [a]。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
- [&a]。将 a 按引用进行传递。
- [a,&b]。将 a 按值传递,b 按引用进行传递。
- [=,&a,&b]。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
- [&,a,b]。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
注意:值传递,在lambda函数定义时就确定了,不会随lambda引用改变。
线程库
在 C++11 之前,涉及到多线程问题,都是和平台相关的,比如 Windows 和 Linux 下各有自己的接口,这使得代码的可移植性比较差。C++11提供了线程库Thread,
示例:
void func(int n){return n;}
int n=10;
thread t1(func, n);//创建线程t1执行func函数,n为参数
t1.join();//阻塞线程,等待func线程函数执行完成
thread 类常用函数:
?1、 thread():构造一个线程对象,没有关联任何线程函数,即没有启动任何线程。
2、 thread(fn, args1, args2, ...):构造一个线程对象,关联线程函数 fn ,args1,args2,… 为线程函数的参数。
?3、get_id() :获取线程 id 。
?4、 joinable():判断线程对象是否可以被 join 。
?5、join():阻塞等待线程对象终止。
?6、detach():分离线程对象。
原子操作
所谓原子操作,其意义就是“原子是最小的,不可分割的最小个体”。当多个线程访问同一个全局资源的时候,能够确保所有其它的线程都不在同一时间访问相同的资源。在c++11之前,多线程访问同一变量时,需要加锁控制,例如:
long total = 0;
mutex m;// 对共享资源进行保护的互斥对象
void threadFunc() {
for(int i=0; i<1000000;++i) {
m.lock();// 访问之前,锁定互斥对象
total++;
m.unlock();// 访问完成后,释放互斥对象
}
}
引入原子操作include <atomic>,代码如下:
atomic_long total(0);
void threadFunc() {
for(int i=0; i<1000000;++i) {
total++; //无需加锁控制
}
}
常见的原子类型有:
原子类型名称 | 对应内置类型 |
atomic_bool | bool |
atomic_char | atomic_char |
atomic_char | signed char |
atomic_uchar | unsigned char |
atomic_short | short |
atomic_ushort | unsigned short |
atomic_int | int |
atomic_uint | unsigned int |
atomic_long | long |
atomic_ulong | unsigned long |
atomic_llong | long long |
atomic_ullong | unsigned long long |
atomic_ullong | unsigned long long |
atomic_char16_t | char16_t |
atomic_char32_t | char32_t |
atomic_wchar_t | wchar_t |
统一的列表初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
示例:
struct Point {
int x;
int y;
};
int a{ 2 }; //支持使用{}的统一初始化了
int array1[]{ 1, 2, 3, 4, 5 };
int* pa = new int[4]{ 0 };
Point p{ 1, 2 };
右值引用和移动构造
顾名思义 ,对左值的引用就是左值引用, 对于右值的引用就是右值引用
定义左值和右值的区别, 可否进行取地址, 可以取地址的就是左值, 不可以取地址的就是右值
示例:
int a = 10;
int& b = a; //此处是左值引用
int&& c = 2; //此处是右值引用
int&& h = std::move(a); //std::move 作用 将左值引用转换为右值引用
移动构造
移动构造是C++11标准中提供的一种新的构造方法,移动构造接管源对象,既不会产生额外的拷贝开销,也不会给新对象分配内存空间。提高程序的执行效率,节省内存消耗。移动构造的本质是一种资源窃取, 资源转移。
示例:
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
//拷贝构造
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//添加移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
nullptr指针
在C语言中,NULL被定义为(void*)0,而在C++语言中,NULL则被定义为整数0。编译器一般对其实际定义如下:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
在C++中指针必须有明确的类型定义。但是将NULL定义为0带来的另一个问题是无法与整数的0区分。因为C++中允许有函数重载,所以引入nullptr,nullptr可以明确区分整型和指针类型,能够根据环境自动转换成相应的指针类型,但不会被转换为任何整型,所以不会造成参数传递错误。
移动构造
类型推导 auto 和 decltype
auto 关键字的作用在编译阶段对于=右边的对象进行自动的类型推导
例如:
auto index = 10;//index为整型
auto str = "abc";//str为字符串类型
vector<int>vTest;
auto iter=vTest.begin();//iter为迭代器类型
decltype的出现是为了补齐auto 不支持对于表达式的类型推导的缺陷的, 经常适用于后置返回类型的推导,如下:
template<class T, class U>
auto Add(T&& t, U&& u)
->decltype(std::forward<T>(t) +std::forward<T>(u)) {
return std::forward<T>(t) +std::forward<T>(u);
}
int main() {
auto func = [](int a, double b)->decltype(a + b){ return a + b; };
cout << Add(2, 5);
while (1);
return 0;
}