C++ 11 C++14 C++17新功能

一、C++11的新功能:

1.使用nullptr代表空指针:

主要是为了解决C++中NULL带来的二义性问题(因为NULL的值实际是0)

2.自动类型推断关键字auto和decltype()得到类型

①使用auto进行自动类型推导:

使用auto进行类型的自动推导可以大大简化编程工作;auto是在编译期执行,所以不会影响程序运行速度;由于C++编译的特性是从右向左推断类型,所以不会影响编译速度。

//auto的使用示例
//auto tmp; // 错误,auto是通过初始化表达式进行类型推导,如果没有初始化表达式,就无法确定tmp的类型  
auto tmp = 1;//等价于int tmp = 1;  
auto tmp = 1.0; //等价于 double tmp = 1.0;
auto tmp = "Hello World";  //等价于 char *tmp = "Hello World";
auto tmp  = 'A';  //等价于 char tmp  = 'A';
auto func = less<int>();  
vector<int> arr;  
auto it = arr.begin();  //等价于vector<int>::iterator it   = arr.begin(); 
auto p = new foo(); // 对自定义类型进行类型推导

另外auto可以应用于模版:

//auto应用于模版
        //1.不使用auto
	template <class Student, class Teacher>
	void levelUp(const Teacher& th)
	{
		Student * addSalary = th.levelPlus();
		// do somthing with addSalary
	}

	//2.使用auto
	template <class Teacher>
	void levelUp(const Teacher& th)
	{
		auto * addSalary = th.levelPlus();
		// do somthing with addSalary
	}

②使用decltype()得到类型:

//使用decltype()得到变量类型
int x = 3;  
decltype(x) y = x; 

template <class Teacher>
void levelUp(const Teacher& th)->decltype(th.levelPlus();)
{
	auto * addSalary = th.levelPlus();
	// do somthing with addSalary
}

3.使用默认构造函数和删除(default:A()=default; A()=delete;)

为了避免手动编写空默认构造函数,C++11引入了显示默认构造函数的概念,从而仅仅需在类的定义中编写空默认构造函数而不须要在实现文件里提供事实上现,相同的,C++还支持显式删除构造函数的概念。

//默认构造函数和删除
启用默认构造函数A()=default;
删除默认构造函数A()=delete;

4.匿名函数lambda表达式

lambda表达式类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。

Lambda语法:[函数对象参数](操作符重载函数参数)->返回值类型{函数体}

//lambda表达式
vector<int> arr{5, 4, 3, 2, 1};  
int a = 2, b = 1;  


for_each(arr.begin(), arr.end(), [b](int &x){cout<<(x + b)<<endl;});
//b就是指函数可以得到在Lambda表达式外的全局变量
for_each(arr.begin(), arr.end(), [=](int &x){x *= (a + b);});    
 //在[]中传入=的话,即是可以取得所有的外部变量
for_each(arr.begin(), arr.end(), [=](int &x)->int{return x * (a + b);});

//①[]内的参数指的是Lambda表达式可以取得的全局变量
//②()内的参数是每次调用函数时传入的参数
//③->后接的是Lambda表达式返回值的类型

5.智能指针:

shared_ptr、unique_ptr、weak_ptr、auto_ptr,主要为了解决内存泄露问题

详见:http://x.wolfmark.org/x-select_2018-09-10_481.html

(中第23②)

6.右值引用T&&与移动构造函数:

a.函数参数为什么要使用常量引用:

因为函数在参数传递(实参—>形参)的时候会有两种形式:值传递和引用传递。若使用值传递,在遇到大的对象作为参数时会耗费大量时间与内存空间。采用引用就不会有这个拷贝过程。所以使用引用。而我们一般又希望作为参数的引用不要改变原来的对象的内容,所以采用常量引用(const &)。

 b.拷贝构造函数为什么使用常量引用:

拷贝构造函数,首先是一个函数,其次它的任务就是借用一个本类型的对象,拷贝创建一个新的对象。
所以有如下的过程:实参—>形参—>新创建的对象
其中,形参—>新创建的对象,是拷贝构造函数这个函数所赋予的功能(新创建的对象要和作为参数的对象内容相同但是拥有独立的内存空间)。
类的构造函数是没有返回值的,所以实参—>形参之后,还是需要一个形参—>拷贝初始化新对象的过程。

 c.移动构造函数为什么使用右值引用:

首先还是为了不拷贝大对象,所以使用引用。

但是由于引用对象是右值(如字面值或者临时对象),因为这类值本身就是用于临时存储,所以窃取其值不影响程序正确性。

所以这样我们如果确实需要一个单独的传入的参数也不需要拷贝了,就直接将这个传入的参数“据为己有”即可。

总结:移动构造函数就是实参—>形参,形参—>新创建的对象,这两个过程的拷贝都省了。

//(1)声明一个右值引用
int&& i = 42;
或者
class X
{
private:
int* data;
public:X():data(new int[1000000]){}~X(){delete [] data;}
X(const X& other):data(new int[1000000]){std::copy(other.data,other.data+1000000,data);}
//拷贝构造函数
X(X&& other):data(other.data){other.data=nullptr;}};
//移动构造函数

//(2)使用在拷贝构造函数
X x1;
X x2 = std::move(x1);
X x3 = static_cast<X&&>(x2);

//(3)使用在函数模板
//填入左值,推断为左值引用;填入右值,推断为普通无修饰类型。
foo(42);foo(3.14159);foo(std::string());
//填入右值,推断为普通无修饰类型情况
int i=42;foo(i);
//填入左值,推断为左值引用情况

7.初始化列表(更加优雅的初始化方法):

//初始化列表(更加优雅的初始化方法)

//引入C++11之前,只有数组能使用初始化列表,
//其他容器想要使用初始化列表,只能用以下方法:
int arr[3] = {1, 2, 3}  
vector<int> arr(arr, arr + 3); 

//C++11中,我们可以使用以下语法来进行替换:
int arr[3]{1, 2, 3};  
vector<int> arr{1, 2, 3};  
map<int, string>{{1, "a"}, {2, "b"}};  
string str{"Hello World"};

8.加长参数模板:

//加长参数模板

//在C语言中printf可以传入多个参数,在C++11中,
//可以用加长参数模板实现更简洁的Print

template<typename head, typename... tail>  
void Print(Head head, typename... tail) {  
    cout<< head <<endl;  
    Print(tail...);  
}

9.元组tuple:

//C++中的pair可以使用make_pair构造,构造一个包含两种不同类型的数据的容器
auto p = make_pair(1, "C++ 11"); 

//C++11中引入了变长参数模板(参见8),所以发明了新的数据类型:tuple
//tuple是一个N元组,可以传入1个, 2个甚至多个不同类型的数据
auto t1 = make_tuple(1, 2.0, "C++ 11");  
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2}); 

10.序列for循环:

//C++中for循环可以使用类似java的简化的for循环,
//可以用于遍历数组,容器,string以及由begin和end函数定义的序列
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};  
for (auto p : m){  
    cout<<p.first<<" : "<<p.second<<endl;  
}

c++11部分参考了文章:https://www.cnblogs.com/guxuanqing/p/6707824.html

二、C++14加入的新功能:

1.Lambda函数在C++11的基础上更加方便:

//C++11要求Lambda参数使用具体的类型声明
auto lambda = [](int x, int y) {return x + y;};

//C++14的泛型Lambda使编写如下语句成为可能:
auto lambda = [](auto x, auto y) {return x + y;};

//新标准中的std::move函数可用于捕获Lambda表达式中的变量,
//这是通过移动对象而非复制或引用对象实现的:
std::unique_ptr ptr(new int(10));
auto lambda = [value = std::move(ptr)] {return *value;};

2.constexpr

在C++11中,使用constexpr声明的函数可以在编译时执行,生成一个值,用在需要常量表达式的地方,比如作为初始化模板的整形参数。C++11的constexpr函数只能包含一个表达式,C++14放松了这些限制,支持诸如if 和switch等条件语句,支持循环,其中包括基于区间(range)的for 循环。

3.类型推导:

C++11仅支持Lambda函数的类型推导,C++14对其加以扩展,支持所有函数的返回类型推导:
auto DeducedReturnTypeFunction();
因为C++14是强类型语言,有些限制需要考虑:

①如果一个函数的实现中有多个返回语句,这些语句一定要推导出同样的类型。

②返回类型推导可以用在前向声明中,但是在使用它们之前,翻译单元中必须能够得到函数定义。

③返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。

C++14带来的另一个类型推导方面的改进是decltype(auto)语法,它支持使用与auto同样的机制计算给定表达式的类型。auto和 decltype在C++11中就已经出现了,但是它们在推导类型时使用了不同的机制,这可能会产生不同的结果。
C++14中的其他改变包括可以声明变量模板,支持使用0b或0B前缀来声明二进制字面常量。InfoQ已经介绍过C++14中可能破坏C++11程序的其他小型修改。
主流C++编译器对新语言特性的支持正在有条不紊地开发:Clang“完全实现了当前草案的所有内容”;GCC和Visual Studio也对C++14的新特性提供了一些支持。

三、C++17加入的新功能:

1.使 static_assert 的文本信息可选

2.删除 trigraphs

3.在模板参数中允许使用 typename(作为替代类)

4.来自 braced-init-list 的新规则用于自动推导

5.嵌套命名空间的定义,例如:使用 namespace X::Y { … } 代替 namespace X { namespace Y { … }}

6.允许命名空间和枚举器的属性

7.新的标准属性:[[fallthrough]], [[maybe_unused]] 和 [[nodiscard]]

8.UTF-8 字符文字

9.对所有非类型模板参数进行常量评估

10.Fold 表达式,用于可变的模板

11.A compile-time static if with the form if constexpr(expression)

12.结构化的绑定声明,现在允许 auto [a, b] = getTwoReturnValues();

13.if 和 switch 语句中的初始化器

14.在某些情况下,确保通过编译器进行 copy elision(Guaranteed copy elision by compilers in some cases)

15. 一些用于对齐内存分配的扩展

16.构造函数的模板推导,允许使用 std::pair(5.0, false) 代替std::pair<double,bool>(5.0, false)

17.内联变量,允许在头文件中定义变量

18.__has_include,允许由预处理程序指令检查头文件的可用性

19.__cplusplus 的值更改为 201703L