C++11特性详解(万字)

个人主页:Lei宝啊 

愿所有美好如期而遇


目录

统一的初始化列表

{}初始化

内置类型初始化

自定义类型初始化

容器对象的初始化(原理是std::initializer_list)

 以vector为例:

以map为例:

声明

3.1 auto

3.2 decltype

3.3 nullptr

右值引用和移动语义

模板中的万能引用

​编辑

完美转发

新的类功能

举例一: 

举例二:

可变模板参数

lambda表达式

举例一:

​编辑

举例二:

举例三:

举例四:

举例五:

举例六:

解决排序:

包装器

function

普通函数:

成员函数:

bind


统一的初始化列表

{}初始化

这种初始化方式我们建议用第一种,但是以后看见下面两种也不要感到疑惑,是可以这样初始化的。

内置类型初始化
int main()
{
 
	int a = 1;
	int b = { 1 };
	int c{ 1 };
 
	return 0;
}
自定义类型初始化

我们简单实现一个日期类

class Date
{
public:
	Date(){}
 
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
 
private:
	size_t _year;
	size_t _month;
	size_t _day;
 
};
 
int main()
{
 
	Date a;
	Date b(1, 1, 1);
	Date c{ 1,1,1 };
 
	//多参数的隐式类型转换,{1,1,1}构造一个Date类型的临时对象,去拷贝构造d对象。
	Date d = { 1,1,1 };
 
	return 0;
}
容器对象的初始化(原理是std::initializer_list)
 以vector为例:
#include <vector>
using namespace std;
 
int main()
{
 
    //与Date是不同的,这里不是多参数隐式类型转换,
	//是C++11特性,添加了initializer_list构造函数

	vector<int> v = { 1,2,3,4,5,6 };

    /*
		底层是这样的
		vector(initializer_list<value_type> il)
		{
			for(auto &e : il)
			{
				*finish = e;
				finish++;
			}
		}	
	*/

	vector<int> v{ 1,2,3,4,5,6 };
    
	return 0;
}

initializer_list是个模板类,只要我们使用{},像下面那样,那么类型就是initializer_list。 

auto il = { 10, 20, 30 };  // the type of il is an initializer_list

所有容器在C++11都新增了这样的构造函数。 

所以我们可以这样使用:

vector<int> v = { 1,2,3,4,5,6 };

原理就是{1,2,3,4,5,6}先构造一个initializer_list对象,再使用这个对象进行拷贝构造,或者我们可以直接这样:

//直接构造
vector<int> v({ 1,2,3,4,5,6 });


//其实就类似于这样:

//单参数隐式类型转换,先构造再拷贝构造
//string s = "hello world !";

//直接构造
//string s("hello world !");

 那么我们走一遍这个代码的流程:vector<int> v = { 1,2,3,4,5,6 };

首先v需要进行拷贝构造,而构造他的对象的类型应该是initializer_list<int>,所以使用{1,2,3,4,5,6}构造一个Initializer_list<int>的对象,然后对v进行拷贝构造。

在这个拷贝构造中,将initializer_list对象中的元素全部取出挨个赋给vector。

以map为例:
map<string, int> m3 = {{"1",1},{"2",2}};

map元素的类型为pair<string,int>,并且需要进行拷贝构造,这样的一个对象类型为initializer_list<pair<string,int>>,于是需要使用{"1",1},{"2",2}构造一个pair<string,int>类型的对象,我们看pair:


所以构造pair时first_type的类型应该是const char*,而不是string,所以这里还需要多一层构造。

C++11于是新增了这样的构造函数:

我们写代码来理解这个构造函数:

        template<T1, T2>
		struct pair
		{
			
            //C++98
			pair(const T& key, const T& value)
				:fisrt(key)
				,second(value)
			{}
			
            //C++11
			template<U, V>
			pair(U&& a, V&& b)
				:first(a)
				,second(b)
			{}
		
			T1 first;
			T2 second;
		}

之前,C++98是这样的,pair(const string& key, const int& value)

现在,C++11是这样的,pair(const char*&& a, int&& b),于是这里进行了直接构造,省却了一层拷贝构造,节省了资源。

声明

3.1 auto

C++98 auto 是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以 auto 就没什么价值了。
C++11 中废弃 auto 原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。

3.2 decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。

3.3 nullptr

由于 C++ NULL 被定义成字面量 0 ,这样就可能回带来一些问题,因为 0 既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑, C++11 中新增了 nullptr ,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define   NULL      0
#else
#define   nullptr   ((void *)0)
#endif
#endif

这样nullptr就表示空指针。

右值引用和移动语义

左值引用、右值引用及移动语义-CSDN博客,这个是博主单独写的(所以也可以看出右值引用的移动语义在C++11中的地位,很重要),这里不再重复去写了。

模板中的万能引用

#include <iostream>
#include <string>
using namespace std;

void Fun(int& x)
{
	cout << "左值引用" << endl;
}

void Fun(const int& x)
{
	cout << "const 左值引用" << endl;
}

void Fun(int&& x)
{
	cout << "右值引用" << endl;
}

void Fun(const int&& x)
{
	cout << "const 右值引用" << endl;
}

//万能引用
/*
	模板中T&& 不代表右值引用,而是万能引用
	如果传值为左值,那么就是左值引用
	如果传值为右值,那么就是右值引用
*/
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

int main()
{
	PerfectForward(10);            // 右值

	int a;
	PerfectForward(a);             // 左值
	PerfectForward(std::move(a));  // 右值

	const int b = 8;
	PerfectForward(b);	           // const 左值
	PerfectForward(std::move(b));  // const 右值

	return 0;
}

但是为什么全成了左值引用呢?我们看PerfectForward函数,我们传右值过去时,也就是右值引用,而右值引用 引用右值后,右值引用属性变为左值,所以调用的全是左值的函数。

于是我们提到完美转发。

完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性,也就是说,当右值引用 引用右值后,右值引用属性变为左值,但他引用的对象的类型仍然为右值,只是我们可以通过右值引用操作他,而完美转发将会使得右值引用保持引用对象的类型,即右值属性。(左值引用也是同理)
template<typename T>
void PerfectForward(T&& t)
{
	//forward<T>(val) 完美转发
	Fun(forward<T>(t));
}

新的类功能

默认成员函数
原来 C++ 类中,有 6 个默认成员函数:
  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载
最后重要的是前 4 个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载
注意:
  1. 如果你没有实现移动构造,析构函数,拷贝构造,以及拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造,否则,不生成。
  2. 如果你没有实现移动赋值重载,析构函数,拷贝构造,以及拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值重载,否则,不生成。
  3. 如果你提供了移动赋值或移动构造,那么编译器不会提供默认拷贝构造和默认拷贝赋值重载。
  • 如果你提供了某些让编译器不愿意为你主动生成默认函数的函数,那么你可以使用default让他生成默认成员函数。
  • 如果你什么函数也不提供,但是也不愿意让编译器提供默认函数,那么你可以使用delete不让他以及所有人生成。
举例一: 

 ​​​

于是我们使用default:

举例二:

可变模板参数

可变参数模板是模板编程时,模板参数(template parameter)的个数可变的情形。这种模板类型支持一个函数或模板类可以接受任意多个参数,且这些参数可以是任何类型。

可变参数模板是C++ 11新增的最强大的特性之一,它对参数进行高度泛化,能表示0到任意个数、任意类型的参数。

下面就是一个基本可变参数的函数模板
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为 参数
,它里面包含了 0 N N>=0 )个模版参数。我们无法直接获取参数包 args 中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。
递归函数方式展开函数包
#include <iostream>
#include <string>
using namespace std;

//终止函数
template<class T>
void _ShowList(T value)
{
	//...
	cout << value << endl;
}

template<class T, class ...Args>
void _ShowList(T value, Args... args)
{
	//...
	cout << value << " ";
	_ShowList(args...);
}

template <class ...Args>
void ShowList(Args... args)
{
	_ShowList(args...);
}

int main()
{
	ShowList("string", 1, '?');
	return 0;
}

lambda表达式

在C++98中,如果我们使用sort进行排序,那么我们可以通过控制传给sort的比较对象来进行不同的排序方式:

即不同的comp对象,他可以是静态成员函数名,也可以是重载了operator()的类的对象。

如果我们要对这样的一组数据按照不同方式进行比较:

struct Goods
{
     string _name;  // 名字
     double _price; // 价格
     int _evaluate; // 评价
     Goods(const char* str, double price, int evaluate)
         :_name(str)
         , _price(price)
         , _evaluate(evaluate)
     {}
};

那么我们要去写多个不同的仿函数吗?这是不是很麻烦?

所以我们使用lambda表达式就会比较简单,现在我们先介绍lambda表达式:

lambda 表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }

   捕捉列表          参数列表      修饰符           返回值          函数体 

参数解释说明:

[capture-list] 捕捉列表该列表总是出现在lambda函数的开始位置,编译器根据[]判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda 函数使用

捕获列表说明
捕捉列表描述了上下文中那些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

(parameters) 参数列表:类似于普通函数的参数列表,相同用法。(若不需要参数传递,可以省略)

mutable:当我们使用捕捉列表以值传递方式进行捕捉,使用mutable取消他的常量性,使其在函数体中可以进行修改。(其实用处不大)

return-type:返回值类型,没有返回值时默认为void,此时可省略返回值类型,当返回值类型明确时,返回值类型也可以省略。

statement:函数体,不可省略,在函数体内,不仅可以使用参数,还可以使用捕获到的变量。

举例使用:

举例一:
#include <iostream>
using namespace std;

int main()
{

	int a = 0, b = 1;

	//  [](int a, int b){return a + b; };
	auto add = [](int a, int b) ->int {return a + b; };
	cout << add(1, 2) << endl;

	return 0;
}
举例二:
int main()
{

	int a = 0, b = 1;

	auto add = [a, b]() {return a + b; };
	cout << add() << endl;

	return 0;
}

举例三:
int main()
{

	int a = 1, b = 2;

	auto add = [](int& a, int& b) {
		int temp = a;
		a = b;
		b = temp;
		};

	add(a, b);
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	return 0;
}

举例四:
int main()
{

	int a = 0, int b = 1;

	auto add = [a, b]() {
		int temp = a;
		a = b;
		b = temp;
		};

	return 0;
}

int main()
{
	int a = 0, b = 1;

	auto add = [a, b]()mutable {
		int temp = a;
		a = b;
		b = temp;
		};

	add();
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	return 0;
}

int main()
{
	int a = 0, b = 1;

	auto add = [&a, &b](){
		int temp = a;
		a = b;
		b = temp;
		};

	add();
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	return 0;
}

int main()
{
	int a = 0, b = 1;

	auto add = [&](){
		int temp = a;
		a = b;
		b = temp;
		};

	add();
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	return 0;
}

int main()
{
	int a = 0, b = 1;

	auto add = [=]()mutable{
		int temp = a;
		a = b;
		b = temp;
		};

	add();
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	return 0;
}

举例五:

混合使用

int main()
{

	int a = 0, b = 1, c = 2;

	//所有变量以值传递方式捕捉,除了c,c以引用传递方式捕捉
	auto add1 = [=, &c]() {return a + b + c; };
	//所有变量以引用传递方式捕捉,除了c,c以值传递方式捕捉
	auto add2 = [&, c]() {return a + b + c; };

	return 0;
}
举例六:
int main()
{

	int a = 1, b = 2;
	auto print = [] {cout << "hello world !\n"; };

	print();

	return 0;
}

 lambda表达式的底层其实就是仿函数。

解决排序:
#include <vector>
#include <algorithm>

struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		,_price(price)
		,_evaluate(evaluate)
	{}
};

struct compare
{
	bool operator()(const Goods& a, const Goods& b)
	{
		return a._price > b._price;
	}	
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };

	//sort的compare对象要进行排序的是v的元素,也就是Goods的对象
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price;
		});

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate;
		});

	sort(v.begin(), v.end(), compare());

	return 0;
}

包装器

function

function 包装器 也叫作适配器。 C++ 中的 function 本质是一个类模板,也是一个包装器
普通函数:
double func(double a)
{
	return a / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;

	//f是什么?仿函数?函数名?lambda表达式?
	return f(x);
}

int main()
{
	/*
		下面这些代码会实例化出三份useF函数
	*/

	// 函数名
	cout << useF(func, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lamber表达式
	cout << useF([](double d){ return d / 4; }, 11.11) << endl;

	return 0;
}

#include <functional>

double func(double a)
{
	return a / 2;
}

struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};

template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;

	//f是什么?仿函数?函数名?lambda表达式?
	return f(x);
}

int main()
{

	// 函数名
	std::function<double(double)> func1 = func;
	cout << useF(func1, 11.11) << endl;

	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;

	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double {return d /4;};
	cout << useF(func3, 11.11) << endl;

	return 0;
}

我们发现function函数只实例化出一份,并且我们上面的这些只是针对于普通函数而言,成员函数我们还没有说过。

成员函数:
#include <functional>

int Plus(int a, int b)
{
	return a + b;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{

	function<int(Sub, int, int)> f = &Sub::sub;
	cout << f(Sub(), 1, 2) << endl;

	return 0;
}

注意:静态成员函数包装同普通函数。

bind

std::bind 函数定义在头文件中, 是一个函数模板,它就像一个函数包装器 ( 适配器 ) 接受一个可
调用对象( callable object ),生成一个新的可调用对象来 适应 原对象的参数列表 。一般而
言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M
可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺
序调整等操作。
可以将 bind 函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来 适应 原对象的参数列表。
调用 bind 的一般形式: auto newCallable = bind(callable,arg_list);
其中, newCallable 本身是一个可调用对象, arg_list 是一个逗号分隔的参数列表,对应给定的
callable 的参数。 当我们调用 newCallable 时, newCallable 会调用 callable, 并传给它 arg_list
的参数
arg_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数,这些参数是 占位符 ,表示
newCallable 的参数,它们占据了传递给 newCallable 的参数的 位置 。数值 n 表示生成的可调用对
象中参数的位置: _1 newCallable 的第一个参数, _2 为第二个参数,以此类推
#include <iostream>
#include <functional>

using namespace std;

int Plus(int a, int b)
{
	return a - b;
}

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};


int main()
{
	/*
		一般函数
	*/

	function<int(int, int)> f1 = Plus;

	//调换参数顺序
	auto func = bind(Plus, placeholders::_2, placeholders::_1);
	cout << func(3, 4) << endl;

	//调整参数个数
	auto func1 = bind(Plus, 20, placeholders::_1);
	cout << func1(10) << endl;

	/*
		成员函数:
			1:静态(同一般)
			2:非静态
	*/

	function<int(Sub, int, int)> f2 = &Sub::sub;
	f2(Sub(), 2, 3);

	//改变参数数量
	auto func2 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	function<int(int, int)> f3 = func2;
	cout << f3(3, 4) << endl;
	cout << func2(3, 4) << endl;


	return 0;
}

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lei宝啊

觉得博主写的有用就鼓励一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值