C++11新特性

auto
  • 自动类型推导
1
2
3
std::vector<std::string> vec;
for(auto i = vec.begin(), i != vec.end(), ++i)
{……}
  • 定义模板函数时,推到依赖模板函数的变量类型
1
2
3
4
5
template<_typename _Tx, _typename _Ty>
{
auto v = x * y;
std::cout<<v<<endl;
}
  • 返回值
1
2
3
4
5
auto multipy(_Tx x, _Ty y)->decltype(x * y)
return x * y;

auto func1(int a)->bool
{}
  • 其他
1
auto a=10,b=11.0;//报错,初始化必须统一类型
1
2
3
4
5
6
7
int a=1;
int &b =a;
auto c=b;//c为int,去除&
auto &d=b;//d为引用
const int e=10;
auto f = e;//去除const
auto &g = e;//用&,不去除const
1
2
3
int arr[3] = {1, 2, 3};
auto brr = arr; //brr 类型为int*
auto &crr = arr; //crr为数组,即crr=arr[3]
decltype

从变量或者表达式获取类型

1
2
3
4
int var;
const int&& fx();
struct A { double x; }
const A* a = new A();
1
2
3
4
decltype(var);//int
decltype(fx());//const int&&
decltype(a->x);//double
decltype((a->x));//const double&,内部括号导致语句作为表达式而不是成员访问计算。由于a声明为 const指针,因此类型是对const double的引用。

对于decltype所用的引用来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有所不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。

C++14可以使用不带尾随返回类型的 decltype(auto) 来声明其返回类型取决于其模板参数类型的模板函数。

1
2
3
template<typename T, typename U>
decltype(auto) myFunc(T&& t, U&& u)
{ return forward<T>(t) + forward<U>(u); };
using
  • 命名空间
1
using namespace std;
  • 定义别名,类似typedef
1
using itType=std::vector<std::string>::iterator;

using与typedef的差别是 using可以用于模板部分具体化,但是typedef不能

1
2
template<class T>
using arr12=std::array<T,12>;
  • 当一个派生类私有继承基类时,基类的public和protected数据成员在派生类中是private的形式,如果想让这些继承而来的数据成员作为public或者protected成员,可以用using来重新声明。using声明语句中名字的访问权限由该using声明语句之前的访问说明符决定。
1
2
3
4
5
6
7
8
9
10
11
class Basic{
public:
int a;
int b;
};
class Bulk : private Basic{
public:
using Basic::a;
protected:
using Basic::b;

  • 关于重写重载函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A1
{
public:
void test(){cout<<"A1"; };
void test(int a){cout<<a;};
};

class A2 :public A1
{
public:
void test(){cout<<"A2";};
using A1::test;//如果不加此行,A2将只保留test()部分,导致主函数内报错
};

int main()
{
A2 a2;
a2.test(11);//如果不用using,此行报错
system("pause");
return 0;
}
nullptr

nullptr比0更安全

枚举作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum EnumA
{
P1=0,
P2=1
};
enum EnumB
{
P1,//歧义,编译失败
P2//歧义,编译失败
};

int main()
{
EnumA e1=EnumA::P1;
return 0;
}

所以我们需要设置枚举的作用域,这样才可以编译的过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum class EnumA
{
P1=0,
P2=1
};
enum struct EnumB
{
P1,
P2
};

int main()
{
EnumA e1=EnumA::P1;
EnumB e2=EnumB::P2;
}
Lambda

其实lambda实现的方法是创建一个简略的类。**这个类重载了operator()**,所以表现的像个普通函数。一个lambda函数是这个类的实例。当这个类构造的时候,所有捕获的变量被传送到类中并保存为成员变量。

表达式

1
[capture](parameters)->return-type{body}

几个例子

1
2
3
4
5
[](int x, int y) { return x + y; } // 隐式返回类型
[](int& x) { ++x; } // 没有return语句 -> lambda 函数的返回类型是'void'
[]() { ++global_x; } // 没有参数,仅访问某个全局变量
[]{ ++global_x; } // 与上一个相同,省略了()
[](int x, int y) -> int { int z = x + y; return z; }//指示了返回值int
  • 关于[]的捕获信息
1
2
3
4
5
6
7
[]        //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y] //x 按值捕获, y 按引用捕获.
[&] //用到的任何外部变量都隐式按引用捕获
[=] //用到的任何外部变量都隐式按值捕获
[&, x] //x显式地按值捕获. 其它变量按引用捕获
[=, &z] //z按引用捕获. 其它变量按值捕获
[this] //截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

示例

1
2
3
4
A1* a1=new A1();
int a2=2;
auto lambda1=[&a1,a2](int v){a1->num=v;a2++;};//错误,a2按值传递,不能修改
auto lambda1=[&a1,a2](int v)mutable {a2++;a1->num=v+a2;;};//增加mutable后可以修改,但是对原数据无影响,输出结果a1->num : 15
explicit

显式转换,禁止单参数构造函数导致的自动转换

1
2
3
4
5
6
7
8
9
10
class plebe
{
plebe(int);
explicit plebe(double);
}

plebe a,b;
a=1;//隐式转换
b=0.5;//错误
b=plebe(0.5);//显式转换
default,delete
  • default

4类特殊函数可以用default,即默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符

特殊情况,如果类内有指针成员,特殊函数都用default,以下情况会导致错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class testA
{
public:
testA() =default;
testA(const testA& t)=default;
~testA()=default;
testA& operator=(const testA& t)=default;

int num=1;
int *p=&num;
};

testA *a1= new testA(1);
testA *a2 =a1;
delete a1;//a2的p也被释放了
cout<<*a2->p<<endl;
  • delete
类内成员初始化
1
2
3
4
5
class se
{
int mem1=10;//类内初始化
double mem2{123.65};//同上
}
右值引用
  • 左值、右值

在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)

  • 右值、将亡值
  1. C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。

  2. 将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。

  • 左值引用、右值引用
  1. 左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在
  2. 右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化
  3. 引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
1
2
3
4
5
6
7
8
9
//int &a =2;//左值引用绑定右值,fail
int b=1;//非常量左值绑定右值,ok
const int &c=b;//常量左值引用绑定非常量左值,ok
const int &d=2;//常量左值引用绑定常量左值,ok
int && f=10;//右值引用绑定右值,ok
const int &g=f;//常量左值引用绑定右值引用,ok
//int &&h=b;//右值引用绑定非常量左值,fail
int &&h=std::move(b);//move把左值转换为右值,ok
//int &&i=h;//变量是左值,h是左值,fail

image-20200707151813950

智能指针

智能指针是用对象去管理一个资源指针,同时用一个计数器计算引用当前指针对象的个数,当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,计数器为1,此时在销毁指针管理对象的同时,也对指针管理对象所管理的指针进行delete操作。

  • shared_ptr

std::shared_ptr包装了new操作符动态分配的内存,可以自由拷贝复制,基本上是使用最多的一个智能指针类型。

注意事项

我们尽量使用shared_ptr构造函数或者make_shared的方式创建shared_ptr,禁止使用裸指针赋值的方式,这样会shared_ptr难于管理指针的生命周期。

1
2
3
4
5
6
7
8
// 使用裸指针赋值构造,不推荐,裸指针被释放后,shared_ptr就野了,不能完全控制裸指针的生命周期,失去了智能指针价值
int *p = new int(10);
shared_ptr<int>sp = p;
delete p; // sp将成为野指针,使用sp将crash
// 将裸指针作为匿名指针传入构造函数,一般做法,让shared_ptr接管裸指针的生命周期,更安全
shared_ptr<int>sp1(new int(10));
// 使用make_shared,推荐做法,更符合工厂模式,可以连代码中的所有new,更高效;方法的参数是用来初始化模板类
shared_ptr<int>sp2 = make_shared<int>(10);

禁止使用指向shared_ptr的裸指针,也就是智能指针的指针,使用shared_ptr的指针指向一个shared_ptr时,引用计数并不会加一,操作shared_ptr的指针很容易就发生野指针异常。

1
2
3
4
5
6
shared_ptr<int>sp = make_shared<int>(10);
cout << sp.use_count() << endl; //输出1
shared_ptr<int> *sp1 = &sp;
cout << (*sp1).use_count() << endl; //输出依然是1
(*sp1).reset(); //sp成为野指针
cout << *sp << endl; //crash
  • weak_ptr

与std::shared_ptr最大的差别是在赋值的时候,不会引起智能指针计数增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
public:
shared_ptr<B> b;
};
class B {
public:
weak_ptr<A> a;
};

int main()
{

shared_ptr<A> spa = make_shared<A>();
shared_ptr<B> spb = make_shared<B>();
spa->b = spb; //spb强引用计数为2,弱引用计数为1
spb->a = spa; //spa强引用计数为1,弱引用计数为2

system("pause");
return 0;
}

weak_ptr的一些用法

1
2
3
4
5
6
7
weak_ptr<T> w;  //空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(shared_ptr sp); //与sp指向相同对象的weak_ptr, T必须能转换为sp指向的类型
w = p; //p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
w.reset(); //weak_ptr置为空
w.use_count(); //与w共享对象的shared_ptr的计数
w.expired(); //w.use_count()为0则返回true,否则返回false
w.lock(); //w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
  • unique_ptr

unique_ptr是auto_ptr的继承者,对于同一块内存只能有一个持有者,而unique_ptr和auto_ptr唯一区别就是unique_ptr不允许赋值操作,也就是不能放在等号的右边(函数的参数和返回值例外),这一定程度避免了一些误操作导致指针所有权转移,然而,unique_str依然有提供所有权转移的方法move,调用move后,原unique_ptr就会失效

1
2
3
4
unique_ptr<int> p1=make_unique<int>(11);
cout<<*p1<<endl;//11
auto p2=move(p1);
cout<<*p1<<endl;//访问空指针,崩溃
模板和STL方面的修改
基于范围的for循环
1
2
3
double price[5]={1.1,2.2,3.3,4.4,5.5};
for(double x:prices)//也可以使用auto来申明x,编译器将自动推断
std::cout<<x<<std::endl;
新的STL容器

C++11新增的容器:forward_list,unordered_map,unordered_multimap,unordered_set,unordered_multiset

  • 关于unordered_map与map

两者的接口差不多,基本可以互换。

一般来说unordered_map的综合性能比map要好,因此,通常我们可以使用unorderd_map代替map。

以下情况推荐使用map:

关键字类型的hash函数设计的很差, 或者==运算符的性能极差, 导致hash过程太耗时;
对内存使用有严格要求, 不能接受存储hash table的额外内存开销;
元素要求按顺序存储, 或者常常需要关联访问一个元素的上一个/下一个元素, 或者需要遍历整个map。

新的STL方法

新增了STL方法cbegin()cend(),这些方法也返回一个迭代器,指向容器的第一个元素和最后一个元素的后面,因此可以用于指定包含全部元素的区间;这些新方法将元素视为const。类似的,crbegin()和crend()是rbegin()和rend()的const版本

valarray升级

C++11添加了两个函数begin()和end(),都接受valarray作为参数并且返回迭代器

摒弃export

C++98增加了关键字export,C++11摒弃了这个特性但是保留了关键字export

尖括号

为了避免与运算符>>混淆,C++要求在申明嵌套模板时使用空格将尖括号分开

1
vector<list<int>> vl;//C++11开始不再强求空格