c++11里面有很多的新特性,之前在项目代码中也使用过很多次,但是没有系统的整理过,在这里整理一下我特别喜欢的一些c++11的新特性,这些也是我使用频率比较高的特性嘿嘿!
auto表达式
我一般在容器迭代的时候使用auto,这样就不需要定义迭代器的类型。
for(auto it = vec.begin(); it != vec.end(); it++)
{
cout<<*it<<endl;
}
for(auto ele:vec)
{
cout<<ele<<endl;
}
使用nullptr而不是NULL|或者0
nullptr的基础是c++定义的一个类。
NULL在c++里面就是数字0。
NULL在纯c当中是void*。
函数可能在整型和指针型之间重载,为了避免调用到错误的函数,使用nullptr。
很多时候通不过编译,如果通过编译也没办法调用int*的函数,使用nullptr可以避免这个问题。
override关键字
由于子类重写父类的虚函数经常会出现错误,所以c++11加了这个override声明,子类重写父类虚函数的时候有以下的要求必须满足
- 基类的函数必须是虚函数
- 函数名字必须一样,析构函数除外
- 基类和派生类的函数的参数形参必须是一致的。
- 基类和派生类的函数常量性必须完全相同。
- 返回值和异常必须是可以兼容的
- 函数的引用修饰词必须完全相同。
加入override关键字之后可以检查出为什么重载没有成功,是哪个地方出了问题。
delete关键字
有的时候,我们的类是不支持拷贝的,不支持赋值操作的,这个时候c++98的做法是将拷贝构造函数和赋值构造函数设置为private类型的,但是成员函数,友员函数还是可以访问这些函数,只是链接阶段是会报错的。
c++11提供了更好的方法,使用“=delete”来将赋值函数和拷贝构造函数设置为删除函数。这样的话无论什么函数都无法调用删除函数。
delete关键字相当于private还有一个好处是因为 private只针对于类成员函数,但是delete可以delete成员函数和普通函数。
智能指针
智能指针 | 备注 |
---|---|
auto_ptr | 可以赋值,前者失去所有权,无法放置在容器中 |
unique_ptr | 适用于专属所有权的资源,不允许拷贝,不允许赋值,转换成shared_ptr是很简单的 |
shared_ptr | 共享所有权,使用引用计数,引用计数为0的时候释放资源,问题:循环引用导致资源没有办法释放 |
weak_ptr | 配合shared_ptr使用,解决循环引用的问题,控制块当中的弱引用实现 |
使用智能指针需要注意以下几点
- 拒绝使用裸指针变量(裸指针变量可能会被给到两个智能指针,二次释放等出现未定义的错误。)
- 用make_shared来取代new出来的结果,原因如下
(1)减少代码的冗余
//使用make_shared函数
auto p(std::make_shared<asr>());
//不使用make_shared函数
std::shared_ptr<asr> p(new asr);
(2)资源的泄漏
(3)减少一次内存的分配
std::shared_ptr p(new asr);
这个 new对象的时候要分配一次内存 构造智能指针的时候要分配控制块的内存一次,这里就涉及到了两次内存的分配。
auto p(std::make_shared());
而使用make函数可以一次分配,这样的话减少了内存的分配,可以提高性能。
move语意
move实际不做任何搬运的操作,能够实现move是因为类中重载了参数为右值引用的拷贝构造函数,而move函数做的工作就是将左值转换成右值引用,这样的话我们就会优先选择具有move语意的拷贝构造函数!!!
具有move语意的拷贝构造函数实际上没有做深拷贝的动作,而是通过交换指针来将右值的东西偷走。
左值和右值
- 右值是指只能在等式右边的值,而左值是指可以在等式左边和等式右边的值。
- 右值一般指的是匿名对象和临时值,通常是字面量(数字、字符串)、临时对象或者表达式,类似于
2*10
和Demo()
这种。而左值指的是有名的变量和有名的对象。 - 生命周期 对于左值而言,语句之外还存在 而对于右值而言 语句之外就不存在了。
- 取地址操作 左值是可以取地址的,但是右值是不可以取地址的 但是字符串字面量除外 &“abc”是可以取到地址的 但是字符串常量是左值!!!
左值引用 右值引用
- 在c++11之前的概念当中,没有右值引用的概念,引用的话只代表左值引用,那个时候就出现了问题。
如下图所示:
这个时候我们发现这个函数是没发打印出abc字符串字面量的。
自定义对象的时候也会出现这个问题。
因为这个时候我们的函数是接受左值引用的,但是构造的匿名对象是右值,所以没办法正常调用函数。
那么,之前为什么很多应用过程中没有大规模的出现这个问题?
因为C++对const&添加特殊技能:既能接受左值又能接受右值。上面两个例子把函数参数加上const,即void Print(const string& s)和void Func(const Demo &),问题解决了。
void Func(const Demo &)类型匹配解决了,为什么void Print(const string& s)也可以了呢?因为编译器自动把根据函数参数类型string,自动为"abc"构造一个临时对象string(“abc”),这是默认类型转换。
- 后来c++11中引入了右值引用,
&&
来和const &
区分开来,因为const&
虽然可以接受右值引用了,但是却限制了修改,传入的对象是不能修改的。
看下面这段代码
#include <iostream>
using namespace std;
class Demo{};
void Func(const Demo &){
cout << "Func(const Demo &)" << endl;
}
void Func(Demo &){
cout << "Func(Demo &)" << endl;
}
void Func(Demo &&){
cout << "Func(Demo &&)" << endl;
}
int main(){
Demo d;
Func(d);
Func(Demo());
const Demo cd;
Func(cd);
}
运行结果如下:
Func(Demo &)
Func(Demo &&)
Func(const Demo &)
但是我们为了支持一个接口可以传递左值引用,也可以传递右值引用,需要重载函数,这样的话未免太麻烦了 所以c++11又引入了一个新的特性,叫做完美转发
完美转发
&&在模板参数中表示万能引用,在非模板参数中表示右值引用。
看下面这段代码
#include <iostream>
using namespace std;
class Demo{};
template<typename T>
void Func(T &&){
cout << "Func()" << endl;
}
int main(){
Demo d;
Func(d);
Func(Demo());
const Demo cd;
Func(cd);
}
运行结果如下
Func()
Func()
Func()
这样就可以解决不用写那么多函数的问题啦!!!!
当传入右值时,模板特化成void Func(T &&),当传入左值时,模板特化成void Func(T &)。瞬间解决上面的问题。