c++11/14 新标准概述和例子
1.1 概述
相对于c++98/99 版本,c11/14增加了许多新特性,其目标主要有两个
1.更适合系统编程和构建程序库
2. 更加容易学习
下面来介绍一些主要改变
1.2左值与右值
1.2.1定义
c++11/14标准描述了左值和右值的定义,但是比较难以理解:
以赋值符号 = 为界,= 左边的就是左值,= 右边就是右值。
下面简略说一下摘要:
1.所有表达式的结果不是左值就是右值
2.左值:一个函数或者对象的实例
3.失效值:生命周期即将结束的对象
4.广义左值 :左值和失效值
5.右值:失效值,临时对象
6.纯右值 : 非失效值的右值
注:
左值:是一个可以用来存储数据的变量,有实际的内存地址,表达式结束后依然存在
右值: 临时变量,表达式结束,变量销毁,不能取地址 (返回值不能返回局部变量的应用就是这个道理)
1.2.2右值引用 &&
对一个对象使用右值引用:相当于添加了一个临时名字,生命周期得到延长(主要用来支持move语义)
1.2.3 move语义
move语义是指将一个同类型的对象A中的资源(可能是在堆上分配,也可能是一个文件句柄或者其他系统资源)搬移到另一个同类型的对象B中,解除对象A对该资源的所有权。这样可以减少不必要的临时对象的构造、拷贝以及析构等动作。
比如我们经常使用的std::vector,当两个相同的std::vector类型赋值时,一般的步骤如下:
1.内部的赋值构造函数一般是先分配指定大小的内存,
2.从源std::vector中拷贝到新申请的内存,
3.再把原有的对象实例析构掉,最后接管新申请的数据。
这就是c++11之前的深拷贝,move语义与拷贝语义相对,类似于浅拷贝,但是资源的所有权发生了转移。move语义的实现可以减少拷贝动作,大幅提高程序的性能
为了实现move语义的构造,就需要对应的语法来支持。原有的拷贝构造函数等不能够满足该需求。最典型的例子就是C++11废弃的std::auto_ptr,其构造函数会产生不明确的拥有权关系,很容易滋生BUG。这也是很多人不喜欢std::auto_ptr的原因。C++11为此增加了相应的构造函数。
这里可以明显看到两个函数中的参数类型是Foo&&。这就是右值引用的基本语法
c++ 11 swap函数实现
1.2.4 完美转发 forward()
可以把函数的参数原封不动的转发给其他函数
1.3 自动类型推导
新增两个两个关键字 auto / decltype 他们可以自动推导表达式的信息,可以大量简化代码
1.3.1 auto
c++作为一种强类型的静态语言,任何变量和表达式都要有明确的类型。例如:
int a ;
std::map< std::string,std::string >::iterator iter = m.begin();
上面例子中对于map的一个迭代器声明就很麻烦,对应的就可以使用auto简化代码
auto iter = m.begin();
注 : auto不会有任何的效率的损失,可以放心使用,而且带来了更好的安全性,不在为类型纠结
1.3.2 decltype
返回表达式的类型,例:
int x =10;
long y =11000;
decltype(x + y) dest =x+y;
使用: 1.decltype(e) 获得表达式计算结果的值类型。 2. decltype((e))获得表达式计算结果的引用类型
1.3.3 decltypr(auto)
auto 只能用于赋值语句,用途有限,decltype()可以推导任意表达式类型,但是必须在()写全表达式,故而引申出decltypr(auto), 例:
*decltypr(auto) z =x+y <====> decltype(x + y) dest =x+y;*
1.4 面向过程
c++继承了c的传统,支持最基本的面向过程,在c11/c14中的变化不多,但是增加的新特性可以很好的改进程序
1.4.1 空指针
在c11/14之前空指针都是用宏NULL表示,NULL的定义通常为0,是一个宏定义:
#define NULL 0
但是NULL有着严重的缺陷,他是一个实际的数,而不是一个真的指针,而c11/14增加了关键字:nullptr ,他明确的表示空指针,并且可以隐式转换成任意类型的指针,也可与指针进行比较运算,但是绝不能转换成非指针的其他类型,例:
class A
{.......};
A* a=new A();
if ( a == nullptr)
{......}
1.4.2 统一初始化(initialization)
在c++中初始化是一个基本操作,但是对于不同结构的数据初始化没有统一的语法,c11/14 标准给出了完美的解决方案,统一使用 {} 初始化变量,称为初始化列表,例:
int x{};//缺省值为0
double y{ 1.213 };
string s{ "hello world" };
vector<int> vec={ 1,2,3,4 };
实际上的语法为std:initialization_list 的对象来实现统一初始化,在标准库一书中(侯捷大师有介绍),当然有时间也可以看一下大师的公开课说的很明白侯捷,c++2.0 公开课
1.4.3 新的for循环
说到新的for 循环,这个搭配auto使用是我现在超级喜欢的东西,不说别的直接上代码:
c98/99的for循环:
vector<int> vec={ 1,2,3,4 };
for (vector<int>::iterator iter=vec.begin();iter != vec.end();iter++)
{
cout << *iter << endl;
}
c11/14 新for循环:
vector<int> vec={ 1,2,3,4 };
for (auto i : vec)
{
cout << i << endl;
}
1.4.4 新式函数声明(在泛型,lambda 里面还是很有用的)
c11/14 新增了函数语法,可以允许返回值 后置 ,他使用了auto/decltype 的类型推导能力
auto func(…) ->type {…}
这里返回值必须要用auto 来占位,然后要在“->”后声明真正的返回值类型,可以用decltype类型推导,例:
auto func(int a) ->decltype(a)
{
return a * a;
}
泛型的示例 :
template<typename T,typename U>
auto func(T a, U b)->decltype(a + b)
{
return a + b;
}
1.5 面对对象
1.5.1 default
允许程序员,显示的声明类的缺省构造/析构等特殊成员
class A
{
public:
A() =default;//显示使用默认构造
A(int a) { x = a; };//不影响其他构造的使用
~A();
int x;
};
1.5.2 delete
显示的禁用某些函数
class A
{
public:
A() =default;//显示使用默认构造
A(int a) =delete ;// 这样就显示的禁用了这个构造
~A();
int x;
};
** 注意:**
delete不仅仅可以作用于类的成员函数,也可以用于普通函数,禁用某些重载
1.5.3 final
c11/14新增关键字 “final”,可以用来控制类禁止被继承,也可以控制虚函数禁止重载
使用:在类名 或者虚函数后直接使用,例子:
class A final //禁止被继承
{
public:
A() =default;//显示使用默认构造
A(int a)=delete ;//不影响其他构造的使用
~A();
int x;
};
class B
{
public:
B();
~B();
virtual int func(int a) final {}//这个虚函数禁止被重载
};
1.5.4 成员变量初始化
c11/14 放松了对类成员变量初始化的要求,允许在类声明时使用赋值或者 {} 初始化,无须构造中特别指定:
class C
{
public:
C();
~C();
int a = 10;
string s{ "hello" };
vector<int> vec = { 1,2,3,43,5 };
static int c;
};
C::C()
{
c = 10;
}
如上代码,需要特别注意的是,静态变量不能直接赋值的,因为静态变量需要分配实际的内存空间,还有就是这种初始化不能用 auto 进行类型推导,因为类型推导也要基于真正存在在内存中的内容推导
1.5.6 委托构造
对于一个类,我们经常会有很多个构造函数,用于不同情况下构造对象,这些代码大多都是初始化成员变量,仅有少量不同,导致代码冗余,所以可以构建一个特殊的初始化函数(一般为init()函数),在构造中调用,这个就是委托构造:
class D
{
public:
int x, y;
void init(int a, int b)
{
x = a; y = b;
}
D(){init(0, 0);}
D(int a) { init(a, 0); }
D(int a,int b) { init(a, b); }
//D(int a, int b) :x(a), y(b) {}; 构造函数初始化成员列表 这只是个写法
~ D();
};
D::~ D()
{
}
1.6 泛型编程(很多很多厉害的特性)
1.6.1 类型别名
新增关键字 “using”,可以完成"typedef" 相同的工作,例:
using int_64=long;//long的类型别名就是int64
typedef long long int64_t; // long long 类型为int64_t
同时using也可以结合template 一起使用为模板类(某种特质化的)声明别名,例 :
template<typename T>
using int_map = std::map<int, T>;
int_map<string> m;
1.6.2 静态断言 static_assert()
assert() 断言是一个宏,可以在运行时断言某些条件,但是在泛型编程的主要工作在编译期,assert(),无法使用,故而增加静态断言 static_assert(),他在编译期断言。
使用 static_assert(bool , string);bool类型的判断条件,string 抛出的消息
static_assert(sizeof(int) == 4, "int must be 32bit");
1.6.3 可变模板参数(很牛逼的东西)
说句实话我写的肯定不好,下面只是给个例子,看不懂的朋友可以去看一下侯捷的c++2.0公开课,我会给出链接的:
侯捷c++20
也可以看看这个博客:
泛型之美
例子
template <class... T>
void f(T... args)
{
cout << sizeof...(args) << endl; //打印变参的个数
}
/*
f(); //0
f(1, 2); //2
f(1, 2.5, ""); //3
*/
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
cout << "parameter " << head << endl;
print(rest...);
}
int main(void)
{
print(1,"hello",false,4);
return 0;
}
1.7 lambda表达式
不用羡慕go,python之类的语言有lambda,c11就已经加入了
1.7.1 使用
lambda实际上是对函数对象的强化和扩展,可以直接定义匿名对象,lambda的基本表达形式:
[ ] (参数){…}
lambda 表达式类型称为 “闭包” ,无法直接写出来,需要aotu推导,所以:
auto f1 =[](int x ){
return x*x;};
cout <<f1(10)<<endl;
lambda 表达式类似函数对象,可以如上代码直接用operator()调用,也可以用于各种标准算法,而不用预定义函数对象和绑定器,而且更加具有可读性:
vector<int > vec = { 1,2,3,4,5,6,7 };
std::for_each(vec.begin(), vec.end(), [](int X) {
cout << X << endl;
});
指定返回类型
[] () -> type {…};
auto f1 = [](int x) ->long {
return x * x; };
cout << f1(10) << endl;
1.7.2 捕获外部变量
lambda表达式可以通过捕获列表捕获一定范围内的变量:
[]不捕获任何变量;[&]捕获外部作用域所有变量,并作为引用在函数体使用(按引用捕获);
[=]捕获外部作用域作用变量,并作为副本在函数体使用(按值捕获);
[=,&foo]按值捕获外部作用域所有变量,并按引用捕获foo变量;
[bar]按值捕获bar变量,同时不捕获其他变量;
[this]捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限,如果已经使用了 &或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。
class A
{
public:
int mi = 0;
void func(int x, int y)
{
auto x1 = []{return mi;}; //error,没有捕获外部变量
auto x2 = [=] {return mi + x + y;}; //ok,按值捕获所有外部变量
auto x3 = [&] {return mi + x + y;}; //ok,按引用捕获所有外部变量
auto x4 = [this] {return mi;}; //ok,捕获this指针
auto x5 = [this] {return mi + x + y;}; //error,没有捕获x,y
auto x6 = [this,x,y] {return mi + x + y;}; //ok,捕获this,x,y
auto x7 = [this] {return mi++;}; //ok,捕获this指针,并修改成员的值
}
};
int a = 0, b = 2;
auto f1 = [] {return a;} ; //error,没有捕获外部变量
auto f2 = [&] {return a++;}; //ok,按引用捕获所有外部变量,并对a执行自加运算
auto f3 = [=] {return a;}; //ok,按值捕获所有外部变量,并返回a
auto f4 = [=] {return a++;}; //error,按值引用不能改变值
auto f5 = [a] {return a + b;}; //error,没有捕获b
auto f6 = [a, &b] {return a + (b++);}; //ok,捕获a和b的值,并对b做自加运算
auto f7 = [=, &b] {return a + (b++);}; //ok,捕获所有外部变量和b的引用,并对b做自加运算