[C++] C++11新增

一、列表初始化

C++98:

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。

struct Simple1
{
	int _a;
	int _b;
};

//C++98
int main()
{
	int a1[] = { 1,2,3,4,5,6 };
	int a2[7] = { 0 };

    //本质是类型转换(构造+拷贝构造 -> 优化 直接构造)
	Simple1 s1 = { 1,2 };

	return 0;
}

C++11:

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。(列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别。)

实际是因为C++11新增了一个initializer_list,initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()。

多个对象想要支持列表初始化,只需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。


//C++11
//一切都可以用列表初始化
int main()
{
	// 内置类型变量
	int x1 = { 1 };
	int x2{ 1 };
	int x3 = 1 + 1;
	int x4 = { 1 + 1 };
	int x5{ 1 + 1 };

	// 数组
	int arr1[5]{ 1,2,3,4,5 };
	int arr2[]{ 1,2,3,4,5 };

	// 动态数组,在C++98中不支持
	int* arr3 = new int[5]{ 1,2,3,4,5 };

	// 标准容器
	vector<int> v1 = { 1,2,3,4,5 };
	vector<int> v{ 1,2,3,4,5 };
	map<int, int> m1 = { {1,1}, {2,2,},{3,3},{4,4} };
	map<int, int> m{ {1,1}, {2,2,},{3,3},{4,4} };

	//标准库支持单个对象的列表初始化
	Simple1 s{ 1, 2 };

	return 0;
}

二、声明

1、auto

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方便。将程序中it的类型换成auto,程序可以通过编译,而且更加简洁。

int main()
{
	map<string, string> m{ {"menu", "菜单"}, {"delete","删除"} };

	// 使用迭代器遍历容器, 迭代器类型太繁琐
	//map<string, string>::iterator it = m.begin();

	//使用auto
	auto it = m.begin();

	while (it != m.end())
	{
		cout << it->first << " " << it->second << endl;
		++it;
	}
	return 0;
}

2、decltype

auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力。

而decltype是根据表达式的实际类型推导出定义变量时所用的类型。这个类型可以用来做实例化模版实参或者再定义对象。

int main()
{
	int a = 11;
	float b = 10.11;

	auto ret = a * b;
	vector<decltype(ret)> v{ 12,12.12,14.1 };

	for (auto e : v)
	{
		cout << e << endl;
	}

	return 0;
}

三、范围for

范围for在底层实际是被替换成了迭代器。

int main()
{
	vector<int> v{ 1,4,6,2,5,11 };
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

 

四、智能指针

由于智能指针内容较多,请查看智能指针

五、新增容器

静态数组array

array<int,10> arr = { 1,2,3,4,5 };

forward_list

forward_list实际上就是单链表,区别于双向链表list

unordered系列

(unordered_set/unordered_map)

参考:哈希

六、右值引用

1、左值与右值:

一般认为:

  •  普通类型的变量,因为有名字,可以取地址,都认为是左值。
  • const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间),C++11认为其是左值。
  • 如果表达式的运行结果是一个临时变量或者对象,认为是右值。
  • 如果表达式运行结果或单个变量是一个引用则认为是左值。

C++11对右值进行了严格的区分:

  • C语言中的纯右值,比如:a+b, 100
  • 将亡值。比如:表达式的中间结果、函数按照值的方式进行返回。

2、左值引用与右值引用

左值引用:

int main()
{
	int* p1 = new int[10]{ 1,2,3,4,5,6,7,8,9,99 };
	int*& r1 = p1;

	int a = 1;
	int& r2 = a;
    
    const int& r3 = 10;

	return 0;
}

右值引用:

int main()
{
	int&& r1 = 10;
	int&& r2 = 1 + 3;

	int i = 11;
	int&& r3 = i + 11;

	int&& rr = move(i);


	return 0;
}

注:右值引用可以给move(左值)取别名。不过,不要轻易使用move,有时候会产生意想不到的结果。

3、移动构造/移动赋值

C++11中新增了两个默认成员函数:移动构造函数移动赋值运算符重载。

如果我们没有实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个(都没有写),那么编译器会 自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员,会逐成员按字节拷贝,自定义类型成员,则看它是否实现了移动构造,若实现了则调用,否则调用拷贝构造。(移动赋值重载与其完全类似

不过,在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。(强制编译器生成)

此外,如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中。

这样就解决了 由于函数返回值的生命周期(函数返回时,一般是创建一个临时对象,用该临时对象构造接收函数返回值的变量)问题,无法使用引用返回 的问题。

而在C++11中如果需要实现移动语义,必须使用右值引用。

如:

String(String&& s)
: _str(s._str)
{
    s._str = nullptr;
}

注:

  • 移动构造函数的参数不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
  • 在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。
  • 右值被右值引用引用后的属性是左值。因为右值不能直接修改,但是右值被右值引用后,需要被修改,否则无法实现移动构造和移动赋值。

当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

4、完美转发

完美转发:

在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数:

函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相
应实参是右值,它就应该被转发为右值。

C++11通过forward函数来实现完美转发:

void Fun(int& x) 
{
	cout << "lvalue ref" << endl;
}

void Fun(int&& x) 
{ 
	cout << "rvalue ref" << endl;
}

void Fun(const int& x)
{
	cout << "const lvalue ref" << endl; 
}

void Fun(const int&& x)
{ 
	cout << "const rvalue ref" << endl; 
}

template<typename T>
void PerfectForward(T&& t)
{ 
	Fun(forward<T>(t)); 
}
int main()
{
	PerfectForward(10); // rvalue ref
	int a;
	PerfectForward(a); // lvalue ref
	PerfectForward(move(a)); // rvalue ref
	const int b = 8;
	PerfectForward(b); // const lvalue ref
	PerfectForward(move(b)); // const rvalue ref
	return 0;
}

 七、可变参数模版

比如list中的emplace_back就是使用了它。

 输出大小:

//Args是一个模版参数包,args是一个函数形参参数包
//声明一个模版参数包Args...args,这个参数包中可以包含0到任意个模版参数
template<class ...Args>
void show_list(Args... args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	show_list(1, 1, 1);
	show_list('a',"aa");
	show_list(1.1, 'a');
	show_list(1, 1.1, 'a', "xx");

	return 0;
}

输出每个元素: 

void _show_list()
{
	cout << endl;
}
//编译时推演
//第一个模版参数依次解析获取参数值
template<class T,class ...Args>
void _show_list(const T& val, Args ...args)
{
	cout << val << " ";
	_show_list(args...);
}

template<class ...Args>
void show_list(Args... args)
{
	_show_list(args...);
}

int main()
{
	show_list(1, 1, 1);
	show_list('a',"aa");
	show_list(1.1, 'a');
	show_list(1, 1.1, 'a', "xx");

	return 0;
}

八、 lambda表达式

lambda表达式解决了需要写仿函数重载operator()的问题,尤其每次比较的逻辑不一样,需要去实现多个类,特别是相同类的命名。

语法:

lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }
 lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

  • 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
  • 捕捉列表描述了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。
    [var]:表示值传递方式捕捉变量var
    [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
    [&var]:表示引用传递捕捉变量var
    [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
    [this]:表示值传递方式捕捉当前的this指针
  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。 
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同

使用:

int main()
{
	int a = 1;
	int b = 2;
	cout << a << " " << b << endl;

	auto f1 = [](int& a, int& b)
	{
		int tmp = a;
		a = b;
		b = tmp;
	};

	f1(a, b);
	cout << a << " " << b << endl;

	return 0;
}

int main()
{
	int a = 1;
	int b = 2;
	cout << a << " " << b << endl;

//通过捕捉列表,传引用
	auto f2 = [&a, &b] 
	{
		int tmp = a;
		a = b;
		b = tmp;
	};

	f2();
	cout << a << " " << b << endl;

	return 0;
}
class Test
{
public:
	void func()
	{
		auto f = [=]
		{
			cout << _a << " " << _b << endl;
		};
	}

private:
	int _a = 2;
	int _b = 4;
};

int main()
{
	Test t;
	t.func();

	return 0;
}

九、包装器(适配器)

1、function

function包装器包装的是:函数指针(类型用起来反人类)、仿函数(需在全局定义)、lambda(类型对我们是匿名的)中的任意一个。

bool Comp(int& a, int& b)
{
	return a < b;
}

struct Comps
{
	bool operator()(int& a, int& b)
	{
		return a < b;
	}
};

#include<functional>
#include<map>
int main()
{
	auto complambda = [](int& a, int& b) ->bool
	{
		return a < b;
	};

	map < string, function<bool(int&, int&)>> m
	{
		{"函数指针",Comp},
		{"仿函数",Comps()},
		{"lambda",complambda}
	};

	int x = 1;
	int y = 2;
	cout << m["函数指针"](x, y) << endl;
	cout << m["仿函数"](x, y) << endl;
	cout << m["lambda"](x, y) << endl;


	return 0;
}

包装成员函数:

静态成员函数(static)可以不用加“&”。

成员函数取地址比较特殊,需要加上类域和&。

struct AAA
{
public:
	static void A(int a)
	{
		a = 0;
	}

	void AA(float a)
	{
		a = 1.1;
	}


};

int main()
{

	function<void(int)> f1 = AAA::A;
	f1(1);

	function<void(AAA*, float)> f2 = &AAA::AA;
	AAA a;
	f2(&a, 2.2); //注意不能使用匿名对象,因为右值不可以取地址

	function<void(AAA, float)> f3 = &AAA::AA;
	f3(AAA(), 2.2);



	return 0;
}

2.bind

bind是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。

struct AAA
{
public:
	static void A(int a)
	{
		cout << a << endl;
	}

	void AA(float a)
	{
		cout << a << endl;
	}


};

void aaa(int a, int b)
{
	cout << a << " " << b << endl;
}

int main()
{

	function<void(int)> f1 = AAA::A;
	f1(1);

	function<void(AAA*, float)> f2 = &AAA::AA;
	AAA a;
	f2(&a, 2.2); //注意不能使用匿名对象,因为右值不可以取地址

	function<void(AAA, float)> f3 = &AAA::AA;
	f3(AAA(), 2.2);

	//调整传参顺序
	function<void(int, int)> ff1 = bind(aaa, placeholders::_2, placeholders::_1);
	ff1(3, 5);

	//调整参数个数
	function<void(int)> ff2 = bind(aaa, 22, placeholders::_1);
	ff2(5);

	function<void(float)> ff3 = bind(&AAA::AA, AAA(), placeholders::_1);
	ff3(100.1);

	return 0;
}

可以将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为第二个参数,以此类推。

十、线程库

C++11中最重要的特性就是对线程进行支持,使得C++在并行编程时不需要依赖第三方库。

线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。

  • 带参构造,创建可执行线程;
  • 先创建空线程对象,使用移动构造/移动赋值把右值线程对象转移过去。
#include<thread>
#include<Windows.h>
#include<mutex>

void Test(int data, const string& s, mutex& m)
{
	for (int i = data; i < data + 10; i++)
	{
		//锁 线程安全
		m.lock();
		cout << s << this_thread::get_id() <<" : " << i << endl;
		//Sleep(400);
		m.unlock();
	}

}

int main()
{
	mutex m;
	thread t1(Test,11,"线程1:",ref(m));
	thread tt(move(t1)); 
	//cout << "线程1:"<< t1.get_id() << endl;
	cout << "tt:" << tt.get_id() << endl;

	//t1.join();
	tt.join();

	size_t j = 2;
	vector<thread> v;
	v.resize(10);
	for (auto& e : v)
	{
		e = thread(Test, 1, "线程" + to_string(j++) + ": ", ref(m));
		e.join();
	}

	return 0;
}

 

当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照以下三种方式提供:

  1. 函数指针
  2. lambda表达式
  3. 函数对象

thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:

  • 采用无参构造函数构造的线程对象
  • 线程对象的状态已经转移给其他线程对象
  • 线程已经调用jion或者detach结束
int main()
{

	thread t1([]() {
		cout << this_thread::get_id() << endl;
		});

	t1.join();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杯酒问苍天

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值