C++11 新特性

C++11




列表初始化


C++11里扩展了大括号 {} 的初始化,基本所有类型都可以使用它来初始化。

class Person {
public:
	Person(const string& name, int age) :_name(name), _age(age) {}
private:
	string _name;
	int _age;
};

int main()
{
	int i = { 1 };
	vector<int> v = { 1,2,3 };
	vector<int> v2{ 1,2,3 };
	vector<int>{1, 2, 3};
	//比较实用还是这种方式
	Person* p_Arr = new Person[2]{ {"张三",31},{"李四",22} };
}

自定义类型调用{}初始化,本质是调用对应的构造函数。自定义类型对象可以使用 {} 初始化,必须要要有对应参数类型和个数的构造函数。STL容器支持{}初始化,因为STL容器支持一个initializer_list作为参数的构造函数。

在这里插入图片描述

	initializer_list<int> l1{ 1,2,3 };
	initializer_list<string> l2{ "1","2","3" };

如果需要自定义类型支持花括号初始化,需要支持这样的构造函数。

template <class T>
class my_vector
{
	my_vector(initializer_list<T> li) {
		for (const T& x : li) {
			this->push_back(x);
		}
	}
	//...
	//...
	//...
	//...
private:

};
int main()
{
	my_vector<int> v = { 1,2,3,4 };


范围for

范围for本质还是由迭代器实现。需要注意,取出的元素是值传递,如不需要修改最好加上const &

int main()
{
	vector<string> v = { "1","6" ,"54" ,"3" };
	for (const string& i : v) {
		cout << i;
	}
}


decltype

仅做了解

// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	vector<decltype(T1 * T2)> v;
	decltype<T1* T2> var;	//进行类型推导,定义类型

}



STL中的新容器

在这里插入图片描述


array

一个存在栈上的静态数组,除了内部对下标访问做了检查,防止越界好像就没什么用。。



forward_list

底层是单链表,比list 少了尾插和尾插。唯一的优点就是少了一个存储前一个节点的指针节省了几字节。。



unordered_map/set

非常好用的容器,底层为hash表,但为什么到C++11 才支持。。



右值引用 移动语义


C++11 之前就有引用,应该称为左值引用,而C++11中新增了的右值引用语法特性,无论左值引用还是右值引用,都是给对象取别名。



什么是左值、右值


左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

这些都是左值

	int i = 1;
	const int i2 = 1;
	int* pa = new int[2]{ 1,2 };

左值的共同点:它们都可以被取地址,也基本可以修改(除了被const修饰)


右值可以是一个表达式,如:字面常量、表达式返回值,传值返回函数的返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

这些都是右值

string func2()
{
	string s1 = "12";
	return s1;
}

int main()
{
	int x, y;
	x + y;
	10;
	1 + 2;
	func2();
}

右值的共同点:它们都不能出现在赋值运算符的右边和被取地址,也不能被修改

右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&&rr1去引用,这个特性不重要。

	int&& rr1 = 123;
	rr1 = 2;	//此时rr1已经是一个左值了,可以被修改


左值引用


左值引用已经很熟悉了,很大程度的减少了深拷贝,比如使用在构造函数里,防止深拷贝。

左值引用小结:

左值引用只能引用左值,不能引用右值。

但是const左值引用既可引用左值,也可引用右值,例如:

const int i = 1;

在没有右值引用之前这种场景非常常用:
当模板类型为int、double 这种内置类型,就相当引用了右值

template <class T>
class my_vector
{
	void push_back(const T& x) {
		//....
	}
}

左值引用的短板

当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如: 标准库中的to_sting函数

在这里插入图片描述

to_string 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。



右值引用


移动构造

右值引用解决了上面左值引用的缺点,本质也是为了减少拷贝。


只有拷贝构造情况下,依旧会产生一次深拷贝。
在这里插入图片描述

但如果提供了移动构造,就不会产生深拷贝,同时有拷贝构造和移动构造的情况下,优先选择移动构造。
在这里插入图片描述

func函数中的临时对象出了作用域就即将销毁了,有些地方叫将亡值,使用移动构造相当于废物利用,直接将临时对象中的数据交换到新对象上来,减少了深拷贝,提高效率,这是右值引用的一个使用场景。


移动赋值

一般在重载赋值运算符函数内部都要构造一个临时对象,用来初始化。

		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				vector<T> tmp(v);
				swap(tmp);
			}

			return *this;
		}

这种情况下还是会产生一次深拷贝。
在这里插入图片描述

但如果提供了移动赋值版本的赋值运算符重载就只会发生一次移动构造和移动赋值,不会产生任何深拷贝。
在这里插入图片描述

右值引用,并不是直接使用右值引用去减少拷贝,提高效率。而是支持深拷贝的类,提供移动构造和移动赋值,这时这些类的对象进行传值返回或者是参数为右值时,则可以用移动构造和移动赋值,转移资源,避免深拷贝,提高效率。


一些特殊场景


正常情况下,右值引用只能引用右值,当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

int main()
{
	string s1("123");
	string s2(s1);	//s1是左值,调用拷贝构造
	
	//这里我们把s1 move处理以后,会被当成右值,调用移动构造
	string s3(std::move(s1));
	return 0;
}

需要注意:s1在move之后内部数据已经被交换了,不能再使用。



右值引用的其他使用场景

右值引用,还可以使用在容器插入接口函数中,如果实参是右值,则可以转移它的资源,减少拷贝

在这里插入图片描述



完美转发


模板的万能引用

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。

模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。上面说过,右值引用相等于在内存特定位置存储了右值,例如 int&& rr1 = 10; 此时rr1是个左值,可以被取地址和修改。

我们希望能够在传递过程中保持它的左值或者右值的属性,就需要用到完美转发

在这里插入图片描述

使用完美转发
在这里插入图片描述



final & override 关键字

final修饰类,这个类不能被继承
final修饰虚函数,这个虚函数不能被重写
override修饰子类重写的虚函数,检查是否完成重写,未重写编译器报错。

一般纯虚函数,才是要求子类强制重写,如果子类不重写。子类依旧是抽象类,不能实例化对象



delete 关键字

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

class A
{
private:
	A() = delete; 
	A(const A& a) = delete; //禁止外部创建对象
};


可变参数模板

C++11的新特性可变参数模板提供了可以接受可变参数的函数模板和类模板,相比C++98和 C++03,类模版和函数模版中只能含固定数量的模版参数,可变模版是一个巨大的改进。

参数args前面有省略号,他是一个可变模版参数,带省略号的参数称为"参数包"”,它里面包含了0到N(N>=0)个模版参数。但是无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。但是语法不支持使用args[i]这样方式获取可变参数,因为模板的实例化是在编译时。

递归方式展开参数包(需要提供一个递归终止函数)

void func()
{
	cout << "null" << endl;
}

template <class T, class ...Args>
void func(T t, Args... args)
{
	cout << t << endl;
	func(args...);
}

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

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式: (printarg(args),0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, 《(printarg(args),0)…]将会展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0), etc…),最终会创建一个元素值都为O的数组int arr[sizeof….(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

逗号表达式展开参数包

template <class T>
void PrintArg(T t)
{
cout << t << " "; 
}

template <class ...Args>
void showList(Args... args) {
	// 列表初始化
	//{ (printarg(args),0)... }被展开成((printarg(arg1), 0),(printarg(arg2), 0),(printarg(arg3)
	int arr[] ={ (PrintArg(args), 0)... };
	cout << endl;
}



STL容器中的emplace接口


C++11中,对STL容器新增了emplace接口,支持模板的万能引用和可变参数包。

在这里插入图片描述

普通的的push_back也支持了右值引用版本,在这种情况下没有什么效率区别,传递左值需要一次深拷贝,传递右值需要一次移动拷贝。

在这里插入图片描述

但是emplace还支持这样插入元素,它相当于直接在内部已申请的空间上调用定位new。直接构造对象,没有任何的性能损耗。
在这里插入图片描述



lambda 表达式


lambda表达式格式:[capture-list](parameters) mutable -> return-type { statement }
	int a = 1, b = 2;
	auto add = [](int& a, int& b) {
		return a + b;
	};

	cout << add(a, b);

lambda表达式各部分说明

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


lambda表达式可以理解为无名函数,该函数无法直接调用,想要直接调用可使用auto将其赋值给—个变量。

捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • 【param】:表示值传递方式捕捉变量param
  • 【=】:表示值传递方式捕获所有父作用域中的变量(包括this)
  • 【&var】:表示引用传递捕捉变量var
  • 【&】:表示引用传递捕捉所有父作用域中的变量(包括this)
  • 【this】:表示值传递方式捕捉当前的this指针

注意点:

A.父作用域指包含lambda函数的语句块

B.语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

C.捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]: =已经以值传递方式捕捉了所有变量,捕捉a重复

D.在块作用域以外的lambda函数捕捉列表必须为空。

E.在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

F.lambda表达式之间不能相互赋值,即使看起来类型相同



包装器 function


class F
{
public:
	int operator()(int a, int b) {return a + b;}
};

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

int main()
{
	std::function<int(int, int)> f1 = [](int a, int b) {return a + b; };
	std::function<int(int, int)> f2 = add;
	std::function<int(int, int)> f3 = F();


	cout << f1(1, 2) << endl;
	cout << f2(10, 20) << endl;
	cout << f3(100, 200) << endl;
}

function 可调用对象的类型:函数指针、仿函数(函数对象)、lambda


std:function包装各种可调用的对象,统一可调用对象类型,并且指定了参数和返回值类型为什么有std:function,因为不包装前可调用类型存在很多问题:
1、函数指针类型太复杂,不方便使用和理解
2、仿函数类型是一个类名,没有指定调用参数和返回值。得去看operator()的实现才能看出来。
3、lambda表达式在语法层,看不到类型。底层有类型,也都是lambda_uuid



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

在这里插入图片描述



4种类型转换

static_cast

一般用于相近类型的转换

	double d1 = 1.1;
	float f1 = static_cast<float>(d1);

reinterpret_cast

一般用于无关类型的转换,功能强大

int* p1 = reinterpret_cast<int*>(0x99999999);

const_cast

用于取消变量的 const 属性

	const int* p1 = new int(10);
	*p1 = 2; //此时报错,表达式必须是可修改的左值

正确修改

	int* p2 = const_cast<int*>(p1);
	*p2 = 2;

dynamic_cast

dynamic_cast用于将一个父类对象的指针或者引用转换为子类对象的指针或引用(动态转换)。

  • 向上转型:子类对象指针或者引用 -> 父类指针/引用(不需要转换,赋值兼容规则)
  • 向下转型:父类对象指针/引用- >子类指针/引用(用dynamic_cast转型是安全的)

dynamic_cast只能用于含有虚函数的类
dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

necesse

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

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

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

打赏作者

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

抵扣说明:

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

余额充值