C++11

这里写目录标题

  • [<font color="FF00FF"> 1. C++11中的{}](# 1. C++11中的{})
  • [<font color="FF00FF"> 2. C++11中的std::initializer_list](# 2. C++11中的std::initializer_list)
  • [<font color="FF00FF">3. 左值和右值](#3. 左值和右值)
  • [<font color="FF00FF">4. 左值引用和右值引用](#4. 左值引用和右值引用)
  • [<font color="FF00FF">5. 引用延长生命周期](#5. 引用延长生命周期)
  • [<font color="FF00FF">6. 左值和右值的参数匹配](#6. 左值和右值的参数匹配)
  • [<font color="FF00FF">7. 移动构造和移动赋值](#7. 移动构造和移动赋值)
  • [<font color="FF00FF">8. 引用折叠](#8. 引用折叠)
  • [<font color="FF00FF">完美转发](#完美转发)
  • [<font color="FF00FF"> 10. lambda](# 10. lambda)
  • [<font color="FF00FF">11. lambda的应用](#11. lambda的应用)
  • [<font color="FF00FF">12. 捕捉列表](#12. 捕捉列表)
    • [<font color="FF00FF">3.1 隐式值捕捉](#3.1 隐式值捕捉)
    • [<font color="FF00FF">3.2 隐式引用捕捉](#3.2 隐式引用捕捉)
    • [<font color="FF00FF">3.3 混合捕捉](#3.3 混合捕捉)
    • [<font color="FF00FF">3.4 lambda的原理](#3.4 lambda的原理)
  • [<font color="FF00FF">13. 可变参数模板](#13. 可变参数模板)
  • [<font color="FF00FF">14. emplace](#14. emplace)
  • [<font color="FF00FF">15. 默认的移动构造和移动赋值](#15. 默认的移动构造和移动赋值)
  • [<font color="FF00FF">16. defult和delete](#16. defult和delete)
  • [<font color="FF00FF">17. functional](#17. functional)
  • [<font color="FF00FF">18. bind](#18. bind)

1. C++11中的{}

  1. C++11支持⼀切对象皆可用{}初始化

  2. 内置类型和自定义类型都支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造

  3. {}初始化的过程中,可以省略掉=

  4. C++11列表初始化的本意是想实现⼀个大统⼀的初始化方式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便

2. C++11中的std::initializer_list

  1. C++11库中提出了⼀个std::initializer_list的类,这个类的本质是底层开⼀个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束

  2. std::initializer_list支持迭代器遍历

3. 左值和右值

  1. 左值可以出现赋值符号的左边,也可以出现在赋值符号右边
  2. 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边
  3. 左值可以取地址,右值不能,最重要的区别

4. 左值引用和右值引用

  1. 左值引用不能直接引用右值,但是const左值引用可以引用右值
  2. 右值引用不能直接引用左值,但是右值引用可以引用(左值)move

5. 引用延长生命周期

右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生存期

6. 左值和右值的参数匹配

7. 移动构造和移动赋值

  1. 编译器不优化的情况时时是两次拷贝构造,在vs2019下只有一次拷贝构造

    在没有编译器优化的情况下,移动构造和移动赋值效率很高,因为只是交换指针

    在vs2022会优化成引用的形式,直接修改ret

    右值引用属性是左值,也就是z引用右值,属性是左值

8. 引用折叠

只要有一个左值引用就是左值引用,右值引用可以是左值,也可以是右值

c 复制代码
typedef int& lref;
 typedef int&& rref;
 int n = 0;
 lref& r1 = n; // r1 的类型是 int& 
 lref&& r2 = n; // r2 的类型是 int& 
 rref& r3 = n; // r3 的类型是 int& 
 rref&& r4 = 1; // r4 的类型是 int&& 
c 复制代码
template<class T>
void Function(T&& t)
{
int a = 0;
T x = a;
//x++;
cout << &a << endl;
cout << &x << endl << endl;
}
int main()
{
// 10是右值,推导出T为int,模板实例化为void Function(int&& t) 
Function(10); // 右值 

int a;
// a是左值,推导出T为int&,引⽤折叠,模板实例化为void Function(int& t) 
Function(a); // 左值 

// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t) 
Function(std::move(a)); // 右值 

const int b = 8;
// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
// 所以Function内部会编译报错,x不能++ 
Function(b); // const 左值 

// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
// 所以Function内部会编译报错,x不能++ 
Function(std::move(b)); // const 右值 
return 0;
}

完美转发


完美转发是左值的属性不变,把右值的属性变成左值,所以只有forward时才会左值调用左值的函数,右值调用右值的函数

10. lambda

这就是一个最简单的 lambda 表达式,其中 = 右边的整体看作是一个匿名对象,然后auto自动推导类型,调用add(1,2),就可以实现一个加法函数的工作

1.1 使用 lambda 有四个规定

  1. 捕捉为空也不能省略:[ ] 不能省略
  2. 参数为空可以省略
  3. 返回值可以省略,可以通过返回对象自动推导:->int 可以省略
  4. 函数体不能省略 :{ }里面的内容不能省略

    这个代码就是只有[ ]和{ }里面的内容,返回值和参数被省略

11. lambda的应用

复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

   struct Goods
   {
   	string _name; // 名字 
   	double _price; // 价格 
   	int _evaluate; // 评价 
   	// ...
   	Goods(const char* str, double price, int evaluate)
   		:_name(str)
   		, _price(price)
   		, _evaluate(evaluate)
   	{
   	}
   };
   struct ComparePriceLess
   {
   	bool operator()(const Goods& gl, const Goods& gr)
   	{
   		return gl._price < gr._price;
   	}
   };
   struct ComparePriceGreater
   {
   	bool operator()(const Goods& gl, const Goods& gr)
   	{
   		return gl._price > gr._price;
   	}
   };
   int main()
   {
   	vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3   }, { "菠萝", 1.5, 4 } };
   	sort(v.begin(), v.end(), ComparePriceLess());
   	sort(v.begin(), v.end(), ComparePriceGreater());
   	return 0;
   }

这里可以通过仿函数来进行商品的排序,但是按照多种方式排序就要写多个仿函数,那有没有更好的办法呢?

lambda可以很好的解决问题

lambda可以较为简单的写,一目了然,比如按照价格排序,或者按照评价排序,直接在sort内部就可以清楚的知道,我们在做什么

12. 捕捉列表

一般不在全局定义lambda,一般在局部使用

捕捉多个变量用逗号进行分格,值捕捉的变量不能修改,只能读,引用捕捉的变量才可以修改,如果不在捕捉列表里的变量不可以读,所以修改更不可能了,全局变量在任何地方都可以读写,所以全局变量可以使用

3.1 隐式值捕捉

只能捕捉当前函数内部定义的所有变量,但是不可以修改,因为是传值捕捉,像是被const修饰一样

3.2 隐式引用捕捉

只能捕捉当前函数内部定义的所有变量,但是可以修改

3.3 混合捕捉

混合捕捉时,第⼀个元素必须是&或=
并且&混合捕捉时,后面的捕捉变量必须是值捕捉
同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。

这个捕捉的意思就是只有当前函数内部定义的变量a和b传值捕捉,其它的变量都是引用捕捉,其实{ }里面用了哪些变量,就捕捉哪些变量,没用的其实不会捕捉,因为有成本


这里的()必须加,然后mutable只是去掉了const属性,可以修改变量了,但是它不会影响外部的值,就相当于函数的传值传参,并不会改变实参的值,这里就是这样

3.4 lambda的原理

lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会生成⼀个对应的仿函数的类。

仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。

c 复制代码
class Rate
{
public:
 Rate(double rate) 
 : _rate(rate)
 {}
 double operator()(double money, int year)
 {
 return money * _rate * year;
 }
private:
 double _rate;
};
int main()
{
 double rate = 0.49;
 // lambda
 auto r2 = [rate](double money, int year) {
 return money * rate * year;
 };
 // 函数对象 
 Rate r1(rate);
 r1(10000, 2);
 r2(10000, 2);
 auto func1 = [] {
 cout << "hello world" << endl;
 };
 func1();
 return 0;
}

捕捉列表的rate,可以看到作为lambda_1类构造函数的参数传递了,这样要拿去初始化成员变量

c 复制代码
// 下面operator()中才能使用 
00D8295C lea eax,[rate] 
00D8295F push eax 
00D82960 lea ecx,[r2] 
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h) 

 // 函数对象 
 Rate r1(rate);
00D82968 sub esp,8 
00D8296B movsd xmm0,mmword ptr [rate] 
00D82970 movsd mmword ptr [esp],xmm0 
00D82975 lea ecx,[r1] 
00D82978 call Rate::Rate (0D81438h) 

 r1(10000, 2);
00D8297D push 2 
00D8297F sub esp,8 
00D82982 movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)] 
00D8298A movsd mmword ptr [esp],xmm0 
00D8298F lea ecx,[r1] 
00D82992 call Rate::operator() (0D81212h) 

// 汇编层可以看到r2 lambda对象调⽤本质还是调⽤operator(),类型是lambda_1,这个类型名 
// 的规则是编译器⾃⼰定制的,保证不同的lambda不冲突 
 r2(10000, 2);
00D82999 push 2 
00D8299B sub esp,8 
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)] 
00D829A6 movsd mmword ptr [esp],xmm0 
00D829AB lea ecx,[r2] 
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h) 

所以lambda底层还是调用了operator(),就是仿函数

注意:operator()是运算符重载,拥有operator的类是仿函数

13. 可变参数模板


编译时可变参数模板会根据类型推导,形成如下这些函数

c 复制代码
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);

相当于实现了跟下面一样的效果

c 复制代码
void Print();
template <class T1>
void Print(T1&& arg1);
template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);

调用ShowList,参数包的第⼀个传给x,剩下N-1传给第二个参数包

原理:

c 复制代码
template <class T>
const T& GetArg(const T& x)
{
 cout << x << " ";
 return x;
}

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

template <class ...Args>
void Print(Args... args)
{
 // 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments 
 Arguments(GetArg(args)...);
}

int main()
{
 Print(1, string("xxxxx"), 2.2);
 return 0;
}

原理

c 复制代码
void Print(int x, string y, double z)
{
Arguments(GetArg(x), GetArg(y), GetArg(z));
}

14. emplace

这是两者的区别

value_type就是T就是string,这里const char*先隐式类型转换构造出string临时对象,这个过程调用的是string的构造函数,再移动构造给链表里的string val形成结点,只不过在进行链表的移动构造时要先调用string的移动构造,再继续执行list的移动构造,因为链表里存的是自定义string类型


而这里模板直接推导插入的值为const char*,然后直接调用链表的构造函数,然后这个字符串直接隐式类型转换,也就是一次构造直接形成string,然后再继续进行执行list的构造函数

15. 默认的移动构造和移动赋值

  1. 有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意⼀个。那么编译器会自动生成⼀个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造

  2. 拷贝赋值和拷贝构造条件一样

  3. 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

16. defult和delete

如果你写了拷贝构造,编译器就不会生成移动构造了,而default就是强制生成,而delete是禁制生成函数,这里注释的就是禁制生成拷贝构造

17. functional

c 复制代码
int f(int a, int b)
{
	return a + b;
}
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{
	}
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
	 int _n;
};
int main()
{

	// 包装各种可调⽤对象 
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };
	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;


	// 包装静态成员函数 
	// 成员函数要指定类域并且前面加&才能获取地址 
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	// 包装普通成员函数 
	// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以 
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus pd;
	cout << f5(&pd, 1.1, 1.1) << endl;

	function<double(Plus, double, double)> f6 = &Plus::plusd;
	cout << f6(pd, 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;

	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(move(pd), 1.1, 1.1) << endl;
	cout << f7(Plus(), 1.1, 1.1) << endl;
	return 0;
}

18. bind

c 复制代码
int Sub(int a, int b)
{
	return (a - b) * 10;
}
int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	auto sub1 = bind(Sub, _1, _2);
	cout << sub1(10, 5) << endl;

	// bind 本质返回的⼀个仿函数对象 
	// 调整参数顺序(不常⽤) 
	// _1代表第⼀个实参 
	// _2代表第⼆个实参 
	auto sub2 = bind(Sub, _2, _1);
	 cout << sub2(10, 5) << endl;

	// 调整参数个数 (常⽤) 
	auto sub3 = bind(Sub, 100, _1);
	cout << sub3(5) << endl;

	auto sub4 = bind(Sub, _1, 100);
	cout << sub4(5) << endl;
	
	// 成员函数对象进⾏绑死,就不需要每次都传递了 
	
	// bind⼀般⽤于,绑死⼀些固定参数 
	function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;
	return 0;
相关推荐
JMchen1232 小时前
跨技术栈:在Flutter/Compose中应用自定义View思想
java·经验分享·flutter·canvas·dart·自定义view
Java水解2 小时前
RUST异步并发安全与内存管理的最佳实践
java·后端·面试
李白的粉2 小时前
基于springboot的论坛网站
java·spring boot·毕业设计·课程设计·论坛网站
Hvitur2 小时前
eclipse新建SpringBoot项目
java·spring boot·eclipse
2401_900151542 小时前
C++中的桥接模式
开发语言·c++·算法
自动化和Linux2 小时前
windows11安装GCC+安装Visual Studio Code,Dev-C++
c++·ide·vscode·编辑器
生活很暖很治愈2 小时前
Linux——HTTP协议
linux·服务器·c++·网络协议·ubuntu·http
Nandeska2 小时前
6、认识和使用Redis Stack
java·数据库·redis