文章参考: C++学习笔记——CODspielen

文件操作

文本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <string>
#include <fstream>//包含读写文件的头文件
using namespace std;

//ofstream,写文件;
//ifstream,读文件;
//fstream,读写文件;

//文件类型分为文本文件和二进制文件;

void test01()//写文件
{
//1, 包含头文件fstream;
//2, 创建流对象;
ofstream ofs;//写文件;

//3, 指定打开方式;
ofs.open("text01.txt", ios::out);//ios::out是写文件的打开方式

//打开方式:
//ios::in 读文件
//ios::out 写文件
//ios::trunc 如果文件存在,先删除,再创建
//ios::binary 二进制方式
//可配合使用,中间加|; 如用二进制写文件,ios::binary | ios::out

//4, 往文件里写内容;
ofs << "张三" << endl;//左移运算符<<意思就是输出,这里是往文件里输出,cout是往屏幕上输出;
ofs << "男" << endl;
ofs << "18岁" << endl;

//5,关闭文件
ofs.close();
}

void test02()//读文件
{
ifstream ifs;//读文件的流对象
ifs.open("text01.txt",ios::in);//ios::in是读文件
if (!ifs.is_open())//判断文件是否打开成功
{
cout << "文件打开失败" << endl;
return;
}

//读数据有4种方式
//第1种:
//char buf[1024] = { 0 };//初始化字符数组
//while (ifs>>buf)
//{
// cout << buf << endl;
//}

//第2种:
/*char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}*/

//第3种:
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}

//第4种(不推荐):
//char c;
//while ((c = ifs.get()) != EOF)//每次只读一个字符, EOF是文件尾部 end of file
//{
// cout << c;
//}

ifs.close();
}

int main()
{
test01();
test02();

return 0;
}

二进制操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <string>
#include <fstream>//包含读写文件的头文件
using namespace std;

class Person
{
public:
char m_Name[64];//姓名//用C语言中的字符数组,不要用C++的字符串string,容易出问题!
int m_Age;
};

void test03()//写文件
{
//1,包含头文件fstream;
//2,创建流对象,同时打开文件;
//ofstream ofs;
ofstream ofs("person.txt", ios::out | ios::binary);

//3,打开文件;
//ofs.open("person.txt",ios::out | ios::binary);//用二进制写文件;

//4,写文件;
Person p = { "李四", 28 };
ofs.write((const char*)&p, sizeof(Person));//person.txt文件里会有乱码或遗漏,不过能读回来就行;

//5,关闭文件;
ofs.close();
}

void test04()//读文件
{
ifstream ifs;
ifs.open("person.txt", ios::in | ios::binary);
if (!ifs.is_open())//判断文件是否打开成功
{
cout << "文件打开失败" << endl;
return;
}

Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << "\t" << "年龄:" << p.m_Age << endl;

ifs.close();
}

新特性

易用性的改进

auto

不建议滥用,方便直接写出类型的就直接写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
double func01()
{
return 1.1;
}

struct Test01
{
int m_a;
string m_b;
};

void test01_01()
{
int a = 1;
auto b = 1;//自动推导b为int类型;
auto c = func01();//自动推导c为double类型;

Test01 str;
auto d = str;//自动推导d为自定义的Test01类型;
}

void func02(vector<int> &v)
{
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)//传统写法
{

}

for (auto ii = v.begin(); ii != v.end(); ii++)//用auto自动推导ii为vector<int>的迭代器类型
{

}
}

//C++14中也可作为函数返回类型的推导
auto func14(int i)
{
//如果函数实现中含有多个return语句,这些表达式必须可以推断为相同的类型;

//如果return语句返回初始化列表,返回值类型推导也会失败;
//return { 1, 2, 3 }; // error

//如果函数是虚函数,不能使用返回值类型推导;
/*virtual auto func()
{
return 1;
}*/

//返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导;
if (i == 1)
{
return i; // return int
}
else
{
return func14(i - 1) + i; // ok
}
}

//auto使用注意事项:

/*
1,定义变量时必须初始化:
auto a;//没有初始化a,无法推导a的类型;
a = 10;
*/

/*
2, VS编译器不支持函数形参是auto变量;
void func(auto a)
{

}
*/

/*
3,auto变量不能作为自定义类型结构体或者类的成员变量;
class A
{
public:
//auto a; //不可以作为成员变量;
//auto b = 0; //即便初始化了,也不可以;
};
*/

/*
4,不可以定义一个auto数组;
auto arr[] = {1, 2, 3};//不允许;
*/

/*
5,模板实例化类型不能是auto类型;
vector<int> a;
//vector<auto> b;//不可以;
//vector<auto> c = { 1 };//即便初始化了,也不可以;
*/

decltype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
#include<string>
#include<typeinfo>//typeid用
#include<vector>
using namespace std;

/*
decltype类似auto的反函数,auto可以声明一个变量,而decltype可以从一个变量或表达式中得到其类型,再定义其他变量;
*/

enum //匿名枚举变量
{
OK,
ERROR
} flag;

void test02_01()
{
int i;
decltype(i) j = 0;//decltype(i)就是从i获取到i的类型为int,然后用这个类型定义变量j;
cout << typeid(j).name() << endl;//输出int,也就是j的类型也是int,是从i获取的;
cout << typeid(j).hash_code() << endl;

float a;
double b;
decltype(a + b) c;
cout << typeid(c).name() << endl;//输出double

vector<int> tmp;
decltype(tmp.begin())k; //定义k为迭代器类型
for (k = tmp.begin(); k != tmp.end(); k++)//也可以直接用for(auto k = tmp.begin(); k != tmp.end(); k++)
{

}

decltype(flag) flag2;//可以用decltype获取到类型,再定义其他变量;
cout << typeid(flag2).name() << endl;
cout << typeid(flag2).hash_code() << endl;
}



int main()
{
test02_01();

return 0;
}

追踪函数的返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
#include<string>
#include<typeinfo>
using namespace std;

int func03(int a, int b) //传统写法
{
return a + b;
}

auto func04(int a, double b)->int //指定函数返回值类型,前面换成auto,这样更灵活
{
return a + b;
}

auto func05(int a, double b)->decltype(a + b) //借助decltype,指定函数返回值类型
{
return a + b;
}

void test03_01()
{
int a = 10;
double b = 9.8;
auto c = func04(a, b);
auto d = func05(a, b);
cout << "c= " << c << " typeid= " << typeid(c).name() << endl; //19, int
cout << "d= " << d << " typeid= " << typeid(d).name() << endl; //19.8, double
}

//配合模板使用:
template<class T1, class T2>
auto mul(const T1 &t1, const T2 &t2)->decltype(t1 * t2) //乘法运算
{
return t1 * t2; //t2和t2类型是不确定的,是泛型的,所以要用decltype来指定类型,用auto自动推导;
}

void test03_02()
{
auto m = 8;
auto n = 12.6;
auto k = mul(m, n);
cout << "k= " << k << " typeid= " << typeid(k).name() << endl; //100.8, double
}

初始化列表和成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class A
{
public:
int m_a;
int m_b;

public:
/*A(int i, int j) //传统有参构造写法
{
m_a = i;
m_b = j;
}*/

/*A() :m_a(1), m_b(2) //初始化列表,利用有参构造直接赋值,但不可传参
{

}*/

A(int i, int j) :m_a(i), m_b(j) //初始化列表,等价于有参构造
{

}
};

class B
{
public:
int m_a{1}; //直接赋值初始化,不依赖构造函数
int m_b = 2; //直接赋值初始化,不依赖构造函数
string m_name { "Tom" }; //直接赋值初始化,不依赖构造函数

A tmp{ 10, 20 };//实例化对象时直接赋值初始化,需要存在有参构造或初始化列表
};

void test04_01()
{
B obj;
cout << obj.m_a << " " << obj.m_b << " " << obj.m_name << " " << obj.tmp.m_a << " " << obj.tmp.m_b << endl;
//1 2 Tom 10 20
}



//列表初始化:
struct Test04_01
{
int a;
int b;
string name;
};

void test04_02()
{
Test04_01 t = { 1, 2, "Jerry" };//用{}的方式赋值就叫列表初始化

int a1 = 2;//传统写法
int a2 = { 3 };//也可以直接这样写,也叫列表初始化
int a3{ 4 };//也可以直接这样写,也叫列表初始化

int arr1[] = { 1, 2, 3 };//传统写法
int arr2[]{ 1, 2, 3, 4 };//列表初始化
vector<int> arr3{ 1, 2, 3 };
}

//💭防止类型收窄:
//类型收窄指的是导致数据内容发生变化或者精度丢失的隐式类型转换。使用列表初始化可以防止类型收窄;
void test04_03()
{
const int a = 1024;
char b = a;//可编译通过,但数据会丢失,收窄,因为放不下
//cout << b << endl;//显示不出内容,要用C语言的打印
printf("%d \n", b); //输出0,数据丢失了

//char c = { a };//用列表初始化,不会收窄,但是类型转换错误
}

基于范围的for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
template<class T>
void func05_01(T &a)// 原写法func05_01(int *a)是把数组a的元素地址传入,形参中的数组是指针变量,不是数组,无法确定元素个数
{
for (auto temp : a)//基于范围的for循环,数组的范围需要是确定的;改成模板后可以使用;
{
cout << temp << " ";
}
cout << endl;
}

void test05_01()
{
int a[]{ 1,2,3,4,5 };
int len = sizeof(a) / sizeof(a[0]);

//#if 0
for (int i = 0; i < len; i++)//传统写法
{
int temp = a[i];
temp *= 2;
cout << temp << " ";
}
cout << endl;

for (auto temp : a) //等价于上述写法
{
temp *= 2;
cout << temp << " ";
}
cout << endl;
//#endif //条件编译

for (int i = 0; i < len; i++)//传统写法
{
int &temp = a[i]; //引用,可以改数组元素的值
temp *= 2;
cout << temp << " ";
}
cout << endl;

for (auto &temp : a) //引用
{
temp *= 2;
cout << temp << " ";
}
cout << endl;

//基于范围的for循环,数组的范围需要是确定的
func05_01(a);//把数组a的元素地址传入
}

静态断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
#include<string>
#include<cassert> //断言头文件
using namespace std;

/*
C++提供了调试工具assert,是一个宏,用于在运行阶段对断言进行检查,如果条件为真,执行程序,否则调用abort();
C++11新增了static_assert,静态断言,不用等到运行阶段检查,在编译阶段就检查,可以更早报告错误,同时减少运行时的开销;
*/

void test06_01()
{
bool flag = false;
//assert(flag);
assert(!flag);//断言,如果flag == true,就继续往下执行;否则中断,提示错误
cout << "hello world" << endl;
}

void test06_02()
{
//静态断主语法:static_assert(条件(常量表达式), "提示的字符串"); //因为是编译阶段就检查,所以条件是常量,不能是变量;
static_assert(sizeof(void*) == 4, "64位系统不支持"); //指针占4位,说明是32位系统
//static_assert(sizeof(void*) == 8, "32位系统不支持"); //指针占8位,说明是64位系统
cout << "hello C++ 11" << endl;
}

noexcept修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
C++11使用noexcept替代throw();
*/

void func07_01()//可抛出异常
{
throw 1;
}

void func07_02() throw() //不可抛出任何异常,但C++11已经不用throw()
{
throw 1;
}

void func07_03() noexcept //不可抛出任何异常,相当于noexcept(true)
{
throw 1;
}

🚀noexcept 和 try/catch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <stdexcept>

// 示例1:noexcept用法
class SafeDivide {
public:
static double divide(double numerator, double denominator) noexcept {
if (denominator == 0) {
// 如果分母为0,不抛出异常,而是返回0
// 因为函数声明为noexcept,如果有异常将调用std::terminate
return 0;
}
return numerator / denominator;
}
};

// 示例2:try-catch用法
class UnsafeDivide {
public:
static double divide(double numerator, double denominator) {
if (denominator == 0) {
// 如果分母为0,抛出异常
throw std::invalid_argument("Denominator cannot be zero.");
}
return numerator / denominator;
}
};

int main() {
// 使用noexcept函数
double result1 = SafeDivide::divide(10, 0);
std::cout << "Result with SafeDivide: " << result1 << '\n';

// 使用try-catch捕获UnsafeDivide函数的异常
double result2;
try {
result2 = UnsafeDivide::divide(10, 0);
} catch (const std::invalid_argument& e) {
std::cout << "Caught exception: " << e.what() << '\n';
// 处理异常,例如给result2赋值
result2 = 0;
}
std::cout << "Result with UnsafeDivide: " << result2 << '\n';

return 0;
}

nullptr的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
nullptr是为了解决以前C++中NULL的二义性(0和空指针)而引进的一种新的类型,因为NULL实际上代表的是0;
*/

void func08_01(int a)
{
cout << __LINE__ << endl;//打印行数
}

void func08_01(int *a)//重载
{
cout << __LINE__ << endl;//打印行数
}


void test08_01()
{
int *p = NULL;
int *p2 = 0;
int *p3 = nullptr;//空指针,nullptr只能给指针赋值,不能给int变量赋值

func08_01(0);//现在是默认用C++11,没有歧义,会调用值传递那个函数,但是以前的版本会有歧义;
func08_01(NULL);//报错:还是调用值传递那个函数,产生歧义;
func08_01(nullptr);//这样就会调用指针传递那个函数;

func08_01(p);
func08_01(p2);
func08_01(p3);

int a = NULL;//警告:将NULL转换为非指针的int类型
//int b = nullptr;//不能给int变量赋值

if (p == p3)
{
cout << "equal" << endl;
}
}

🚩强类型枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
传统枚举中,即使枚举名不同,但是如果变量相同,也会判定为重定义;
也不能指定成员变量的类型;
*/

//enum Status
//{
// OK,
// Error
//};
//
//enum Status2
//{
// OK,
// Error
//};

/*
强类型枚举:在enum后加上class或struct修饰;
*/

enum class Status
{
OK,
Error
};

enum struct Status2
{
OK,
Error
};

enum class Status3 : char //强类型枚举,可以指定成员变量的类型;
{
OK = 3,
Error = 4 //可以赋值
};

enum class Status4 : long long //强类型枚举,可以指定成员变量的类型;
{
OK,
Error
};

void test09_01()
{
//需要加枚举类型的作用域;
//Status flag = OK;
Status flag = Status::OK;
Status2 flag2 = Status2::Error;
Status3 flag3 = Status3::OK;
Status3 flag4 = Status3::Error;

printf("%d %d %d %d\n", flag, flag2, flag3, flag4);

//成员变量类型可被指定
cout << sizeof(Status::OK) << endl;
cout << sizeof(Status2::OK) << endl;
cout << sizeof(Status3::OK) << endl;
cout << sizeof(Status4::OK) << endl;
}

优点:

  1. 类型安全:强类型枚举提供了更强的类型安全保障,确保了不同枚举类型的值不能隐式转换,减少了类型混淆的错误。
  2. 作用域限制:强类型枚举不会污染外围作用域,枚举值被限定在枚举类的作用域内。
  3. 可读性:强类型枚举通过明确的名称限定提供了更好的代码可读性,避免了枚举值在同一作用域下的潜在冲突。
  4. 命名空间支持:强类型枚举可以包含在命名空间中,避免枚举值与全局变量冲突。
  5. 与类集成:可将强类型枚举作为类成员或与类其他成员一起使用,提高类的封装性和数据完整性。
  6. constexpr支持:强类型枚举可以与constexpr结合使用,使得枚举值可以在编译时进行计算。

缺点:

  • 隐式转换限制:缺少了隐式转换的能力,这是在需要将强类型枚举值转换为整数类型时的一个不便。

常量表达式 constexpr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/*
常量表达式允许一些计算发生在编译阶段,而不是运行阶段;
如果有些事可以在编译时做,并且只需要做一次,那么就不需要每次程序运行时都计算,可节省运行资源,提高程序运行效率;
*/

int getNum()
{
return 3;
}

const int getNum2()
{
return 3;
}

constexpr int getNum3() //常量表达式
{
return 3;
}

enum class Num_e
{
//e1 = getNum(),//枚举成员初始化必须是整型常量
//e1 = getNum2(),//就算用const修饰的函数,也是不允许的
e1 = getNum3(),//用常量表达式就可以 //在编译阶段就执行,运行时就不用执行了
e2
};

void test10_01()
{
constexpr int tmp = getNum3(); //常量
const int tmp1 = getNum3(); //也是常量
cout << tmp << endl;

enum class Num_e1
{
e1 = tmp,
e2 = tmp1//也是可以的,tmp必须是constexpr或者const修饰的变量
};

printf("%d, %d, %d, %d\n", Num_e::e1, Num_e::e2, Num_e1::e1, Num_e1::e2);
}

/*
constexpr函数的限制:
1,函数中只能有一个return语句,但在C++14中大幅放松;
2,函数必须有返回值,不能是void函数;
3,在使用前必须已经有定义;
4,return返回语句表达式中,不能使用到非常量表达式的函数、全局数据,且必须是一个常量表达式,或者就是一个常量,但在C++14中大幅放松;
*/
constexpr int func10_01()
{
//允许包含typedef, using , 静态断言;
static_assert(1, "fail");

constexpr int a = 1;
return a;//新编译器可以
}

int a_10 = 12;//全局变量
constexpr int func10_02()
{
return a_10;//新编译器可以
}

constexpr int func10_03()
{
//return getNum();//这样不行,因为getNum()是在运行时才调用,而constexpr函数要在编译时就调用,所以无法赋值;
return getNum3();//getNum3()也是在编译时被调用,这样可以
}

void test10_02()
{
int m_a = func10_01();
int m_b = func10_02();
int m_c = func10_03();
cout << m_a << " " << m_b << " " << m_c << endl;
}

//类中函数成员是常量表达式
class Date
{
public:
//constexpr修饰构造函数,那么这个构造函数体必须为空,但是可以通过初始化列表的方式初始化;
constexpr Date(int y, int m, int d) : year(y), month(m), day(d)
{

}

//constexpr int GetYear() 这样写会报错,因为year不是一个常量或者常量表达式
int GetYear() const//对外接口,加上constexpr后面会提示类型不兼容,需要使用常函数
{
return year;
}

int GetMonth() const
{
return month;
}

int GetDay() const
{
return day;
}

private:
int year;
int month;
int day;
};

void test10_03()
{
constexpr Date obj(2077, 8, 8);//必须用常量或常量表达式给构造函数传参数
cout << obj.GetYear() << "年" << obj.GetMonth() << "月" << obj.GetDay() << "日" << endl;
}

//C++14支持变量模板:
template<class T>
constexpr T pi = T(3.1415926535897932385L);
void test10_04()
{
cout << pi<int> << endl; // 3
cout << pi<double> << endl; // 3.14159
//pi<int>是在用int类型实例化模板
//(int)pi_variable则是在对pi_variable(如果它是一个变量或对象)进行类型转换
}

原生字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//原生字符串使用户所见即所得,会保留用户输入的格式输出;格式:R"()";
int main()
{
cout << R"(hello world)" << endl;

cout << R"(hello \n
world)" << endl;

string str = R"(hello \4 \r
abc, mike
hello\n)";
cout << endl;
cout << str << endl;

return 0;
}

输出结果:

1
2
3
4
5
6
7
hello world
hello \n
world

hello \4 \r
abc, mike
hello\n

🚩类的改进

继承构造

格式 using Base::Base;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
继承构造:
C++11允许子类继承父类的构造函数,但默认构造、拷贝构造、移动构造除外;
继承构造只能初始化父类中的参数;
父类中的构造函数不能是私有的;
继承不能是虚继承;
使用了继承构造后,子类将不会再提供默认构造了;
*/

class A1
{
public:
A1(int x, int y) //有参构造
{
a = x;
b = y;
}

protected:
int a;
int b;
};

class B1 : public A1
{
public:
#if 0
B12(int x, int y):A12(x, y)//通过参数列表给父类构造函数传参 //C++11里不用这样写,会自动继承
{

}
#endif

using A1::A1; //继承构造

void show()
{
cout << "a= " << a << ", b= " << b << endl;
}

//没有增加新的成员变量
};

void test12_01()
{
B1 obj(77, 44);
obj.show();
}

委托构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
委托构造:
如果一个类包含多个构造函数,C++11允许在一个构造函数的定义中使用另一个构造函数,但必须通过初始化列表操作;
*/

class A2
{
public:
/*A2() //传统默认构造
{

}*/

A2() :A2(1, 'a') //委托构造,在默认构造中调用其他构造函数,必须用初始化列表 //通过形参自动识别为调用A2(int x, char y)这个
{
//两个参数都写死
//被调用的构造函数如果是private,也可以调用成功
}

A2(int x) :A2(x, 'b')//委托构造
{
//只有一个是变量,另一个写死
}

A2(char x) :A2(16, x)
{

}

A2(int x, char y) :a(x), b(y)//如果是private,也可以被调用成功
{

}

public:
int a;
char b;
};

void test12_02()
{
A2 obj;//没有传参,也就是调默认构造A2() :A2(1, 'a'),而默认构造中委托调用了其他构造,所以也发生了传参;
cout << obj.a << obj.b << endl;//在委托构造中已经传了参数,所以已经被赋值了;//1a

A2 obj1(5);//传一个参数,会自动识别为调用A2(int x) :A2(x, 'b')这个构造函数,而这个函数只委托了另一个构造函数;
cout << obj1.a << obj1.b << endl;//5b

A2 obj2('c');
cout << obj2.a << obj2.b << endl;//16c
}

继承控制:final和override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
继承控制:final和override
final:阻止类的进一步派生和虚函数的进一步重写;
override:确保在子类中声明的函数跟基类的虚函数一模一样;
*/

class A3 final //加了final不能被继承
{
public:
int a;
};

//class A4 : public A3 //不能继承
//{
//
//};

class A5
{
public:
virtual void func() final = 0;//纯虚函数 //加了final 虚函数不能被重写
};

class A6 : public A5
{
public:
//virtual void func() //子类重写,如果父类中有final就不能被重写
//{

//}
};

class B2
{
public:
virtual void func(int a) = 0; //纯虚函数
};

class B3 : public B2
{
public:
virtual void func(int a) override //子类重写,如果漏了父类中的形参,那这个函数就被重定义了,如果不希望重定义,就加上override;
{
//加了override,如果子类重写和父类不同,就会提示错误
}
};

类默认函数的控制:=default和=delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/*
类默认函数的控制:=default和=delete函数

如果程序员显式定义了有参构造,编译器将不会自动再隐式生成默认构造了;
=default函数:让编译器提供默认构造,不用程序员手写;
C++有4类特殊的成员函数,分别是默认构造、拷贝构造、析构、赋值运算符重载函数,default只能修饰这4类函数;
可以类内声明、类外实现;

=delete函数:只需在函数声明后加上=delete,就可将该函数禁用;
delete可以修饰普通函数和有参构造,不限于默认的4类;
*/
class X
{
public:
/*X() //为了可以创建对象,手写默认构造,但是手写的没有编译器提供的效率高,
{

}*/

X() = default;//让编译器提供默认构造,不是手写;
//X(int a) = default; //不可用default修饰
//int func() = default; //不可用default修饰

X(int i)
{
a = i;
}

int a;
};

class X1
{
public:

X1(); //类内声明

X1(int i)
{
a = i;
}

int a;
};

X1::X1() = default;//类外实现,要加作用域

class X2
{
public:
X2()//默认构造
{
cout << "X2的默认构造的调用" << endl;
}

//X2(const X2 &x)//拷贝构造
//{
// cout << "X2的拷贝构造的调用" << endl;
//}

X2(const X2 &x) = delete;//禁用拷贝构造,类似写进private //如果什么都不写,编译器会默认提供,如果想禁用还是要加=delete;

//X2 &operator =(const X2 &x) //赋值运算符重载函数,重载"="
//{
// cout << "X2的重载=运算符函数的调用" << endl;
// return *this;
//}

X2 &operator= (const X2 &x) = delete; //禁用赋值运算符重载,类似写进private //如果什么都不写,编译器会默认提供,如果想禁用还是要加=delete;

int func() = delete;//可以修饰普通函数,让func()不可被调用;
X2(int x) = delete;//可以修饰有参构造;

//只在栈上创建对象、禁止在堆区创建对象,可以禁用重载new和delete的函数:
void *operator new(size_t t) = delete; //禁用new,也可以写进private;
void *operator new[](size_t t) = delete;//禁用new一个数组
void operator delete(void *ptr) = delete; //禁用delete;
};

void test12_03()
{
X2 obj1;//调用默认构造
//X2 obj2 = obj1;//调用拷贝构造

X2 obj3;//调用默认构造
//obj3 = obj1;//调用赋值运算符重载函数

//obj3.func();//无法调用
//X2 obj4(6);//无法调用

//X2 *p = new X2; //无法在堆区创建对象
//X2 *p = new X2[10]; //无法在堆区创建数组
}

模板的改进

右尖括号>改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
右尖括号>改进:
C++11中,要求编译器对模板的>做单独处理,能够判断出">>"是一个右移操作符还是模板参数表的结束标记;
*/
template<int i>
class C1
{

};

template<class T>
class C2
{

};

void test13_01()
{
C2<C1<8>> obj1; //模板实例化对象,模板嵌套模板//旧版本C++要求C2<C1<8> >中的> >之间必须有空格,而C++11不需要;
}

模板的别名 using

格式 using 别名 = 原类型;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
模板的别名:
传统方法:typedef起别名;
C++11:using ..= ..
*/
typedef int int32; //传统方法
using my_int = int; //C++11

void test13_02()
{
//is_same 判断类型是否相同,如果相同就返回true
cout << is_same<int32, my_int>::value << endl;//1,说明int32和my_int类型相同
cout << typeid(int32).name() << endl;
cout << typeid(my_int).name() << endl;

int32 a = 1;
my_int b = 2;
}

函数模板的默认模板参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
函数模板的默认模板参数:
*/

//普通函数带默认参数;
void test13_03(int a = 3)
{
cout << a << endl;
}

//类模板默认模板参数;
template<class T = int>
class C3
{
public:
T a;
};

//函数模板默认模板参数;
template<class T = int>
void test13_04(T a)
{
cout << a << endl;
}

//类模板的默认模板参数必须从右往左定义:
template<class T1 = int, class T2 = double> //两个都给默认参数,支持
class C4
{
public:
T1 t1;
T2 t2;
};

template<class T1, class T2> //两个都不给默认参数,支持
class C5
{
public:
T1 t1;
T2 t2;
};

//template<class T1 = int, class T2> //左边给默认参数,右边不给,不支持
//class C6
//{
//public:
// T1 t1;
// T2 t2;
//};

template<class T1, class T2 = int> //左边不给默认参数,右边给,支持
class C7
{
public:
T1 t1;
T2 t2;
};

//函数模板则没有必须从右往左的限制:
template<class T1 = int, class T2 = double> //两个都给默认参数,支持
void test13_05(T1 a, T2 b)
{
cout << a << b << endl;
}

template<class T1, class T2>//两个都不给默认参数,支持
void test13_06(T1 a, T2 b)
{
cout << a << b << endl;
}

template<class T1 = int, class T2>//左边不给默认参数,右边给,支持
void test13_07(T1 a, T2 b)
{
cout << a << b << endl;
}

template<class T1, class T2 = int>//左边给默认参数,右边不给,支持
void test13_08(T1 a, T2 b)
{
cout << a << b << endl;
}

可变参数模板(难点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
可变参数模板:是C++11新增的最强大的特性之一,它对参数进行了高度泛化;
*/

template<class ... T> //在class和T中间加... //T叫作模板参数包,可传多个参数
void func001(T ... a)//a叫作函数参数包,可传多个参数
{
cout << "num= " << sizeof...(a) << endl;//查看a内参数的个数,sizeof...
}

void test13_09()
{
func001<int>(10);//只传一个参数
func001<int, double, char>(3, 3.14, 'd'); //可以同时传多个不同类型参数
func001(11, 13.5, 34, 'c');//自动类型推导,可以同时传多个不同类型参数
}

递归展开模板参数包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//递归展开模板参数包(较常用):

//#if 0
void debug()//递归终止函数,也就是参数包里没有数据了会终止
{
cout << "empty" << endl;
}
//#endif

#if 0
template<class T>
void debug(T t)//递归终止函数,参数包里只有1个元素时就终止
{
cout << "last = " << t << endl;
}
#endif

template<class T1, class ... T2>
void debug(T1 t1, T2 ... t2)//参数包展开函数
{
cout << t1 << " ";
debug(t2...); //t2...每调一次就减少一个元素,直到为空,才调用终止函数
}

void test13_10()
{
debug(1, 2, 3, 4);
cout << endl;
/*
递归调用过程:
debug(1, 2, 3, 4);
debug(2, 3, 4);
debug(3, 4);
debug(4);
debug();
然后再回溯;
*/

debug(22, "Tom", 3.14, 'c'); //可以同时传不同类型
cout << endl;
}

非递归展开模板参数包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//非递归展开模板参数包:
template<class T>
void print(T x)
{
cout << x << " ";
}

template<class ... T>
void expand(T ... t)
{
//借助逗号运算符和初始化列表
int a[] = { (print(t), 0)... };//...表示每调用一次就减少一个元素,直到为空,才调用终止函数,当前相当于t2=(print(t), 0)
//最后a[] = {0, 0, 0, ... ,0}
}

void test13_11()
{
expand(5, 6, 7, 8); //必须传相同类型
cout << endl;
//输出结果:5 6 7 8
}

注:关于 (print (t), 0),每个逗号运算符中的 print(t) 调用都会打印一个参数,然后返回0(因为这个数组 a 的成员元素类型为 int)。数组中的每个元素都使用一个 0 进行初始化(为1为2都可以,数组仅作存储用途)。括号用来确保逗号运算符的作用。

继承方式展开模板参数包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//继承方式展开模板参数包:
//1,可变参数模板声明;
//2,递归继承模板类;
//3,递归终止条件;

template<class ... T>
class Car//前置声明,不然后面会报错
{

};

template<>
class Car<>//递归终止函数
{

};

template<class Head, class ... Tail>
class Car<Head, Tail...> : public Car<Tail...> //递归继承自己
{
public:
Car()
{
cout << "type= " << typeid(Head).name() << endl;
}
};

void test13_12()
{
Car<int, double, char*, float> Ferrari;
}

模板类递归和特化方式展开参数包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//模板类递归和特化方式展开参数包:
//1,变长模板声明;
//2,变长模板类定义;
//3,递归终止条件;

template<int ... i>
class Test//前置声明
{

};

template<>
class Test<>//递归终止函数
{
public:
static const int val = 1;//最后没有可变参数了,就*1
static const int sum = 0;//最后没有可变参数了,就+0
};

template<int first, int ... last>
class Test<first, last...>
{
public:
static const int val = first * Test<last...>::val; //递归求积
static const int sum = first + Test<last...>::sum; //递归求和
};

void test13_13()
{
Test<2, 3, 4, 5 ,6> t;
cout << t.val << endl; //720
cout << t.sum << endl; //20

cout << Test<2, 3, 4, 5, 6>::val << endl; //720
cout << Test<2, 3, 4, 5, 6>::sum << endl; //20
}

变参模板的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//变参模板的应用:

//1,print函数:
void printX() //递归终止函数
{

}

template<class T, class...Types>
void printX(const T &firstArg, const Types &...args)
{
cout << firstArg << " ";
printX(args...);
}

void test13_14()
{
printX(45, 9.8, "abc", 'm');
cout << endl;
}


//2,max函数:
int maximun(int n) //递归终止函数
{
return n;
}

template<class...T>
int maximun(int n, T...args)
{
return max(n, maximun(args...));
}

void test13_15()
{
cout << maximun(57, 48, 60, 100, 20, 18) << endl; //100

//C++11之后已经支持了直接求多个数求最大值的操作:
cout << max({ 57, 48, 60, 100, 20, 18 }) << endl; //100
}

//3,sum函数:
template<class T>
int sum(T t) //递归终止函数
{
return t;
}

template<class T1, class ... T2>
int sum(T1 first, T2...last)
{
return first + sum(last...);
}

void test13_16()
{
cout << sum(33, 16, 28, 55, 100) << endl; //232
}

//C++14支持别名模板:
template<class T, class U>
struct A13
{
T t;
U u;
};

template<class T>
using B13 = A13<T, int>;

右值引用及其应用

右值引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
C语言中,赋值表达式中,=左边的叫左值,=右边的叫右值;
而C++中,可以取地址、有名字的就是左值,反之就是右值;
例如,
int b = 1;
int c = 2;
int a = b + c;
&a是可以操作的,而&(b+c)不可以,所以a是左值,b+c是右值;
右值表示字面常量、表达式、函数的非引用返回值等;

左值引用和右值引用都必须马上初始化;引用就是起别名;
左值引用是有名字变量的别名,而右值引用是匿名变量的别名;
*/

int &func01_01() //返回值为引用
{
static int tmp;//静态变量不会释放
return tmp; //返回的是左值
}

void test01_01() //左值引用
{
int a;
//int &b; //没有马上初始化
int &b = a; //左值引用,起别名
const int &c = a; //左值引用

//int &d = 1; //error,右值
const int &d = 1; //常引用,可以,右值

int &e = func01_01(); //左值引用
const int &f = func01_01(); //左值引用

const int tmp = 10;
const int &g = tmp; //左值引用

//const int & 万能引用
}

int func01_02()
{
return 8; //返回的是右值
}

void test01_02() //右值引用
{
int &&a = 1; //1这块内存本来没名字,给这块内存起个名字叫a,以后就可以用a操作这块内存;
//如果直接用1,使用完了就释放了,用右值引用可以继续使用这块内存;

int &&b = func01_02(); //右值引用

int i = 2;
int j = 3;
int &&c = i + j; //右值引用

int k = 6;
//int &&d = k; //error,不能把一个左值赋值给一个右值引用
}

void func01_03(int &tmp)
{
cout << "左值 = " << tmp << endl;
}

void func01_03(int &&tmp)
{
cout << "右值 = " << tmp << endl;
}

void test01_03()
{
int a = 10;
func01_03(a);//a是左值,调用左值那个
func01_03(11); //11是常量,是右值,调用右值那个
}

返回值优化技术与移动语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
移动语义:
右值引用是用来支持移动语义的,移动语义可以将资源(堆,系统对象等)从一个对象转移到另一个对象,
这样可以减少不必要的临时对象的创建、拷贝、析构,可以大幅提高程序的效率;临时对象的创建和析构会拖慢程序的速度;
移动语义和拷贝语义是相对的,类似文件的剪切与复制;
通过移动语义,临时对象中的资源可以转移到其他对象里;

移动语义的定义:
现有的C++可以定义拷贝构造和赋值函数,但要实现转移语义,需要定义转移构造函数,也可以定义转移赋值操作符;
对于右值的拷贝和赋值,会调用转移构造函数和转移赋值操作符;
普通函数的操作符也可以通过右值引用操作符实现转移语义;
即便不做优化,使用移动构造函数也可以减少临时对象的创建、拷贝、析构操作,提高程序效率;

移动构造和移动赋值注意点:
参数为非const右值,用&&;
参数不可以是常量,因为需要修改右值;
参数的资源链接和标记必须修改,也就是把原来的地址置空,否则会导致浅拷贝的问题;

对于需要动态申请大量资源的类,应该加上移动构造和移动赋值函数,提高程序效率;
*/

class MyString
{
public:
MyString(const char *tmp = "abc") //有参构造
{
len = strlen(tmp); //长度
str = new char[len + 1]; //堆区申请空间
strcpy_s(str, len + 1, tmp); //将tmp中字符串复制到str

cout << "有参构造的调用:str =" << str << endl;
}

MyString(const MyString &tmp) //拷贝构造
{
len = tmp.len;
str = new char[len + 1]; //深拷贝,防止堆区内存重复释放(浅拷贝)
strcpy_s(str, len + 1, tmp.str);

cout << "拷贝构造的调用:str =" << tmp.str << endl;
}

MyString(MyString &&t) //移动构造 //参数是非const的右值引用 //加了移动构造就不会调用拷贝构造,可以减少临时变量的操作
{
len = t.len;
str = t.str; //拷贝地址,没有重新申请内存!就是浅拷贝!
cout << "移动构造的调用:str =" << t.str << endl;

t.str = nullptr;//要把原来的指针置空,否则会两次释放同一块堆区的地址,产生浅拷贝的问题!
}

MyString &operator = (const MyString &tmp) //运算符重载
{
if (&tmp == this)
{
return *this;
}

//先释放原来的内存
len = 0;
delete[]str;

//在堆区重新申请内存
len = tmp.len;
str = new char[len + 1];
strcpy_s(str, len + 1, tmp.str);

cout << "运算符重载的调用:str =" << tmp.str << endl;

return *this;
}

MyString &operator = (MyString &&t)//移动赋值 //参数是非const的右值引用 //加了移动赋值就不会调用普通运算符重载函数,可提高效率
{
if (&t == this)
{
return *this;
}

//先释放原来的内存
len = 0;
delete[]str;

//不需要在堆区重新申请内存
len = t.len;
str = t.str;//浅拷贝
cout << "移动赋值函数的调用:str =" << t.str << endl;

t.str = nullptr;//要把原来的指针置空,否则会产生浅拷贝的问题

return *this;
}

~MyString() //析构函数
{
cout << "析构函数的调用,";

len = 0;

if (str != nullptr) //移动构造中t.str置空后,就不会跑进这个分支,也能提升效率;
{
cout << "已操作delete,str = " << str;
delete[]str;
str = nullptr;
}
cout << endl;
}

private:
char *str = nullptr; //初始化
int len = 0; //初始化
};

MyString func() //返回普通对象,不是引用,返回的是右值
{
MyString obj("CODspielen");
cout << "&obj (取地址) = " << (void *)&obj << endl;
return obj; //内存中生成一个临时对象,放在内存中一个临时区域内,由旧对象初始化新对象,所以调用了拷贝构造;
//这里返回的其实是临时创建的对象,不是obj;
//如果使用了移动构造,这里调用移动构造函数,不调用拷贝构造;
//返回后,调用析构函数;
}

void test02_01()
{
MyString s = func(); //临时对象直接给s;
//这里做了一次优化,如果没优化,会再调用一次拷贝构造,再调用一次析构;
//优化就是直接把临时对象给到s,不再经历临时变量;
//当main函数结束后,s释放了,再调用析构函数;
cout << "&s (取地址) = " << (void *)&s << endl;
//在QT里,这里S的地址会等于obj的地址,也就是不使用临时变量了,所以只会调用一次有参构造、一次析构,
//而VS里要调用拷贝构造和另一次析构;

//是否开启返回值优化选项:在cmd命令行里添加语句;
//g++ xxx.cpp -fno-elide-constructors -std = c++11
}

void test02_02()
{
MyString &&s1 = func(); //右值引用,调用移动构造函数;
cout << "&s1 (取地址) = " << (void *)&s1 << endl;
}

void test02_03()
{
MyString s2("CODplay"); //实例化对象
s2 = func(); //调用赋值运算符;
cout << "&s2 (取地址) = " << (void *)&s2 << endl;
}

move和完美转发forward

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//move和完美转发forward:
void test02_04()
{
int a = 9; //a为左值
//int &&b = a; //error, 不能把一个左值赋值给一个右值引用

int &&b = move(a); //这样就可以把左值a赋值给右值b,将左值转化成右值
cout << "b= " << b << endl;
}

//完美转发forward适用于:需要将一组参数原封不动地传递给另一个函数,
//包括参数值、左右值、是否是const;
//完美转发过程中,所有这些属性都不变,同时不产生额外的开销;
//在泛型函数中,这样的需求非常普遍;

//传统写法,需要重载:
template<class T>
void func02_01(const T &)
{
cout << "const T &" << endl;
}

template<class T>
void func02_01(T &)
{
cout << "T &" << endl;
}

template<class T>
void func02_01(T &&) //这里会发生引用折叠
{
cout << "T &&" << endl;
}

template<class T>
void forward_val(const T &tmp) //const版本
{
func02_01(tmp);
}

template<class T>
void forward_val(T &tmp) //普通版本
{
func02_01(tmp);
}

void test02_05()//传统方法,需要重载const和普通两个版本
{
int a = 0;
const int &b = 1;
forward_val(a);//左值
forward_val(b);//右值
forward_val(12);//右值
}

//c++中,利用引用折叠,结合新的模板推导规划,可以完成完美转发;
typedef const int T;
typedef T & TR;
TR &v = 1;//引用折叠 //等于是const int &&v = 1;

//C++11引用折叠规则:
//一旦定义中出现了左传引用,引用折叠总是先将其折叠为左值引用
/*
TR的类型定义 声明v的类型 v的实际类型
T & TR T &
T & TR & T &
T & TR && T &
T && TR T &&
T && TR & T &
T && TR && T &&
*/

//forward写法:
template<class T>
void forward_val1(T &&tmp) //右值引用 //只需要写一个,不需要重载
{
func02_01(forward<T>(tmp));//保持原参数tmp的左右值属性
}

void test02_06()//传统方法,需要重载const和普通两个版本
{
int a = 0;
const int &b = 1;
forward_val1(a);//左值
forward_val1(b);//右值
forward_val1(12);//右值
}

🚩智能指针

C++11中有unique_ptr, shared_ptr, weak_ptr等智能指针,定义在<memory>中;
可以对动态资源进行管理,在任何情况下,已构造的对象会销毁,即它的析构函数最终会被调用。

unique_ptr

  • unique_ptr 是一个模板,创建的是对象,不用加 *
  • 持有对对象的独有权,同一时刻只能有一个 unique_ptr 绑定给定的对象(通过禁止拷贝语义,只用移动语义 move 来实现)。
  • unique_ptr 生命周期:从指针创建开始,直到离开作用域就释放
  • 离开作用域时,若其指向对象,则这个对象被销毁,默认使用 delete 操作,用户也可以指定其他操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void test03_01()
{
unique_ptr<int> up1(new int(11));//创建智能指针对象up1,指向new出来的堆区空间;
cout << "*up1= " << *up1 << endl; //11 //重载了operator*();

//unique_ptr<int> up2 = up1; //原来要用拷贝构造,但是使用了智能指针后,up1就是唯一的能指向堆区这块空间的指针,自动禁用了拷贝构造,不能拷贝一个up2也指向这块空间;
unique_ptr<int> up2 = move(up1); //但是可以使用移动构造,相当于把这块内存给了up2指针,而up1就不能再使用了,还是有唯一性的;
cout << "*up2= " << *up2 << endl;
//cout << "*up1= " << *up1 << endl;//转移给up2后,up1就变成野指针了,不能再使用
}

class Test03_01
{
public:
~Test03_01()
{
cout << "析构函数的调用" << endl;
}
};

void test03_02()
{
unique_ptr<Test03_01> up3(new Test03_01); //无需手动释放
up3 = nullptr; //也可以手动释放,直接指向空;
up3.reset();//手动释放,用reset()实现;//可以连续手动释放多次,只会析构一次,不会出现多次释放同一个空间
cout << endl;
//如果是自动释放,在这里要离开这个作用域了,此时才会调用析构函数;
}

void test03_03()
{
unique_ptr<int> up1(new int(11));
up1.reset();//如果是无参,作用是显式释放堆区内存
up1.reset(new int(22)); //如果有参,就是先释放原来的内存,再重新给up1绑定一块新的堆区内存;
cout << "*up1= " << *up1 << endl;

int *p = up1.release();//释放控制权,但不释放堆区内存,就是把这块内存给到*p,up1就不再有控制权了,就变成野指针了;
cout << "*p= " << *p << endl;
delete p;//*p不是智能指针,需要手动释放了;
}

shared_ptr

unique_ptr 相反,shared_ptr 允许多个智能指针的对象共享同一块堆区分配的内存,通过引用计数实现。
如果最后一个这样的指针被销毁,也就是计数变成 0,这个对象才会被自动删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test03_04()
{
shared_ptr<int> sp1(new int(8));
shared_ptr<int> sp2 = sp1; //可以调用拷贝构造,sp2和sp1都绑定堆区空间;
cout << "计数器1 = " << sp1.use_count() << " 计数器2 = " << sp2.use_count() << endl; //2 2
cout << "*sp1 = " << *sp1 << " *sp2 = " << *sp2 << endl; //8 8

sp1.reset();//sp1被释放,只是释放sp1和堆区空间的绑定,计数器减1,只要没有到0,堆区空间就不会被释放;
cout << "计数器1 = " << sp1.use_count() << " 计数器2 = " << sp2.use_count() << endl; //0 1
cout << "*sp2 = " << *sp2 << endl; //8

sp2.reset();//sp1被释放,计数器减到0,堆区空间也被释放
cout << "计数器1 = " << sp1.use_count() << " 计数器2 = " << sp2.use_count() << endl; //0 0
}

weak_ptr

  • 配合 shared_ptr 使用,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造,它的构造和析构不会引起引用计数的变化
  • 没有重载 *->,但可以使用 lock 获得一个可用的 shared_ptr 对象。
  • weak_ptr 可以指向 shared_ptr 指针指向的内存空间,但是并不拥有该内存
  • 如果使用 weak_ptr 的成员函数 lock,则可以返回其指向内存的一个 shared_ptr 对象,且在所指向内存已经无效时,返回空值 nullptr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void test03_05()
{
shared_ptr<int> sp1(new int(6));
shared_ptr<int> sp2 = sp1; //2个指针变量绑定同一块内存
weak_ptr<int> wp = sp1; //3个指针变量绑定同一块内存
cout << "计数器1 = " << sp1.use_count()
<< " 计数器2 = " << sp2.use_count()
<< " 计数器3 = " << wp.use_count() << endl; //2 2 2 //wp不绑定堆区内存,所以只有2个计数器

shared_ptr<int> sp3 = wp.lock(); //使用lock获得一个可用的shared_ptr对象
cout << "计数器1 = " << sp1.use_count()
<< " 计数器2 = " << sp2.use_count()
<< " 计数器3 = " << wp.use_count()
<< " 计数器4 = " << sp3.use_count() << endl; //3 3 3 3

cout << "*sp1 = " << *sp1 << " *sp2 = " << *sp2 << " *sp3 = " << *sp3 << endl; //6 6 6
//cout << "*wp = " << *wp << endl; //wp没有和堆区内存绑定,不能用wp操作内存;

sp1.reset();
cout << "计数器1 = " << sp1.use_count()
<< " 计数器2 = " << sp2.use_count()
<< " 计数器3 = " << wp.use_count()
<< " 计数器4 = " << sp3.use_count() << endl; //0 2 2 2

sp2.reset();
cout << "计数器1 = " << sp1.use_count()
<< " 计数器2 = " << sp2.use_count()
<< " 计数器3 = " << wp.use_count()
<< " 计数器4 = " << sp3.use_count() << endl; //0 0 1 1

sp3.reset();
cout << "计数器1 = " << sp1.use_count()
<< " 计数器2 = " << sp2.use_count()
<< " 计数器3 = " << wp.use_count()
<< " 计数器4 = " << sp3.use_count() << endl; //0 0 0 0

wp.reset();//上述操作已经可以使得堆区空间被释放,这里可加也可不加;

shared_ptr<int> tmp = wp.lock(); //如果堆区空间已经释放,则用wp.lock()创建的对象返回nullptr;
if (tmp == nullptr)
{
cout << "堆区空间已经释放" << endl;
}
}

闭包

闭包定义:带有上下文的函数,有状态的函数,也是一个

  • 带有状态,即拥有自己的变量,而不是全局变量,也不是传进来的变量。
  • 函数和状态捆绑,就形成了闭包。
  • 闭包的状态捆绑,必须发生在运行时
  • 闭包的实现:仿函数、bind 绑定器、Lambda 表达式

    仿函数

    仿函数 functor (不是 C++11 的新内容)就是使一个类的使用看上去像一个函数,其实现就是在类中实现一个 operator(),这个类就有了类似的函数行为,就是一个仿函数类了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class MyFunc
    {
    public:
    MyFunc(int i) :r(i) //构造
    {

    }

    int operator()(int tmp) //仿函数,重载operator()
    {
    return tmp + r;
    }

    bool operator()(int a, int b) //仿函数,重载operator()
    {
    return (a > b && b > r);
    }

    private:
    int r; //class自己带的变量,也就是有状态
    };

    void test04_01()
    {
    MyFunc obj(8);//创建变量,并且给r赋值
    cout << obj(5) << endl; //return 5 + 8

    cout << obj(23, 44) << endl; //false

    cout << obj(77, 44) << endl; //true
    }

    function 与 bind 的使用

    function

  • C++中,可调用的实体主要包括:函数、函数指针、函数引用、可隐式转换为函数指定的对象,或者实现了 operator() 的对象。
  • C++11 中,新增了 std::function 类模板(头文件 <functional>),是对 C++中所有可调用实体的一种案例类型的打包。
  • 通过指定参数模板,可以统一处理函数、函数对象、函数指针,并允许保存和延迟执行它们。
  • function 对象最大的作用:实现函数回调,但是不能用来检查相等/不相等,不过可以与 NULL / nullptr 比较。
  • 格式 function<void(int)> 表示返回类型为 void,接受一个 int 参数的可调用对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//普通函数
void func04_01()
{
cout << __func__ << "的调用" << endl; //__func__就是输出函数名
}

//类中静态函数
class Test41
{
public:
static int test_func(int a)
{
cout << __func__ << "的调用," << a << endl;;
return a * 2;
}
};

//仿函数
class Test42
{
public:
int operator()(int a)
{
cout << __func__ << "的调用," << a << endl;;
return a * 3;
}
};

void test04_02()
{
function<void(/*没参数就不用写,或者写void; */)> f1 = func04_01; //创建function对象,和可调用的实体绑定,这里绑定普通函数
f1(); //等价于直接调用func04_01();

function <int(int)> f2 = Test41::test_func; //绑定类内静态函数;//静态函数才可以直接这样写;
cout << f2(19) << endl;; //等价于直接调用Test41::test_func();

Test42 obj;
function<int(int)> f3 = obj; //绑定仿函数,也就是绑定对象,因为仿函数本身调用是obj(),相当于函数名;
cout << f3(7) << endl;
}

🤔注:可调用实体

1
2
3
4
void myFunction(int x) {
// 函数体
}
myFunction(10); // 直接调用函数
  1. 函数指针
    函数指针是指向函数的指针,可以通过函数指针来调用函数。函数指针的类型必须与目标函数的类型相匹配。
1
2
void (*funcPtr)(int) = myFunction;	// void为返回类型,int为接收参数类型
funcPtr(10); // 通过函数指针调用函数
  1. 函数引用
    函数引用是函数的别名,可以通过函数引用来调用函数。函数引用在声明时必须初始化,并且不能更改引用目标
1
2
void (&funcRef)(int) = myFunction;
funcRef(10); // 通过函数引用调用函数
  1. 可隐式转换为函数指针的对象:
    某些对象可以隐式转换为函数指针,例如通过 std::functionstd::mem_fn 包装的函数对象。
1
2
std::function<void(int)> funcObj = myFunction;
funcObj(10); // 通过std::function对象调用函数
  1. 实现了 operator() 的对象
    这些对象被称为函数对象或仿函数(Functor)。它们通过重载 operator() 来实现类似函数调用的行为。
1
2
3
4
5
6
7
8
struct MyFunctor {
void operator()(int x) {
// 函数对象的实现
}
};

MyFunctor functor;
functor(10); // 通过函数对象调用

bind

  • 预先把可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,在回调函数中常用。
  • C++11 中,bind 的参数个数不受限,绑定具体哪些参数也不受限,由用户指定,非常灵活。绑定的参数可以是占位符(比如 placeholder::_2
  • 同样在头文件 <functional> 中,基本语法为 std::bind(callable, arg1, arg2, ..., argN),其中 callable 表示可调用对象,函数返回类型为 function<RetType>(ArgsType)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void func04_02(int x, int y)
{
cout << x << " " << y << " " << endl;
}

void test04_03()
{
bind(func04_02, 11, 22)(/*这里面可以额外传参,也可以不加*/); //11 22 //把func04_02和11,22绑定,等价于直接调用func04_02(11, 22);

bind(func04_02, placeholders::_1, placeholders::_2)(5, 6); //5 6 //placeholders::_1,占位符,调用函数时,被后面括号中的第1个参数替换

//placeholders::写起来比较麻烦,使用命名空间,using namespace placeholders; //写在头文件处
bind(func04_02, _1, _2)(17, 18); //17 18 //这样就可以直接写_1, _2
bind(func04_02, 33, _3)(77, 88, 44, 55); //33 44 //_3是占位符,先占位,会被后面括号中的第3个参数替换
bind(func04_02, _2, _1)(99, 77); //77 99
}

//bind与function结合使用:
class Test43
{
public:
void func(int x, int y)//非静态成员函数
{
cout << "x = " << x << ",y = " << y << " " << endl;
}

int a; //成员变量
};

using namespace placeholder;
void test04_04()
{
//绑定非静态成员函数:
Test43 obj; //需要先创建对象,而静态成员函数不需要创建对象;
function<void(int, int)> f1 = bind(&Test43::func, &obj, _1, _2); //bind绑定成员函数后,传给function绑定
f1(98, 87); // x = 98, y = 87 // 相当于调用obj.func(98, 87);

//绑定成员变量:
function<int& ()> f2 = bind(&Test43::a, &obj); //bind绑定变量后,传给function绑定
f2() = 111; //等价于obj.a = 111;
cout << "obj.a = " << obj.a << endl; // obj.a = 111
}

🚩Lambda 表达式

lambda表达式是一个匿名函数,基于数学中的λ演算得名。
C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作;通常配合回调函数使用。

💭Lambda 表达式的结构

1
2
3
4
[]()mutable exeption ->int
{

};

[]:捕获列表,标识一个 Lambda 表达式的开始,不能省略。可认为是函数名。捕获外面的参数属性(而非参数本身),相当于类的成员变量。
():没有参数时可以省略,有参数时写在 () 里,和普通函数的参数写法一致。
mutable:用于修饰类的成员变量,允许在 const 成员函数中修改被 mutable 修饰的成员变量。


💭 [] 内通常有:

  1. 空,无任何对象参数
  2. =,表示全部值传递
  3. &,表示全部引用传递
  4. this,类内成员变量,和全局变量
  5. a,将 a 按值传递进行传递,默认为 const,如果要修改可以加上 mutable()
  6. &a,将 a 按引用传递进行传递
  7. a, &b,a 值传递,b 引用传递
  8. =, &a, &b,除了 a 和 b 引用传递外,其余值传递(= 要放在前面)
  9. &, a, b,除了 a 和 b 值传递外,其余引用传递(& 要放在前面)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
int tmp0 = 9; //全局变量

class Test44
{
public:
int i = 5;

void func()
{
auto f1 = [=]()
{
cout << i << endl;
cout << tmp0 << endl;
};
f1();

auto f2 = [&]()
{
cout << i << endl;
cout << tmp0 << endl;
};
f2();

auto f3 = [this]() //this可以捕获类内成员变量,和全局变量
{
cout << i << endl;
cout << tmp0 << endl; //虽然是this,全局变量也可以捕获
};
f3();
}
};

void test04_05()
{
//外部变量
int a = 0;
int b = 1;
int c = 2;

auto f1 = []() //可以用指针接收;
{

};//需要加;
f1();

auto f2 = [a, b]() //a,b用值传递
{
cout << a << " " << b << endl;
};
f2();

auto f3 = [a, b](int x, int y)
{
cout << a << " " << b << endl;
cout << x << " " << y << endl;
};
f3(14, 16);

auto f4 = [=]() //外面变量全部以值传递,传给Lambda表达式;
{
cout << a << " " << b << endl;
};
f4();

auto f5 = [&]() //外面变量全部以引用传递,传给Lambda表达式;
{
cout << a << " " << b << endl;
};
f5();

auto f6 = [&, a]() //&要放前面,a用值传递,其他用引用传递;
{
cout << a << " " << b << " " << c << endl;
};
f6();

auto f7 = [=, &b]() //=要放前面,b用引用传递,其他用值传递;
{
//a++; //默认是const,是右值,不可修改;
cout << a << " " << b << " " << c << endl;
};
f7();

auto f8 = [=]() mutable //默认是const修饰函数体,想改需要加mutable;
{
a++;
cout << a << " " << b << " " << c << endl;
cout << tmp0 << endl; //全局变量

//[]捕获的变量是同一个作用域下的变量,和全局变量;
};
f8();

Test44 t;
t.func();
}

🚀值传递和引用传递的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void test04_06()
{
auto a = 88;
auto f1 = [=]() mutable //传进来的a给了新建的一个变量,mutable改的只是这个新建的变量,而a本身不会被改,操作的不是同一个地址的内存;
{
a += 10;
cout << "a = " << a << endl; //98, 这里是捕获的副本a_ = 98;
};
f1();
cout << "a2 = " << a << endl; //88, 还是没有改变;相当于普通函数值传递不会改变实参,只改变形参;

a = 222;
f1();//108, 和222没关系,f1()内的临时变量被操作;修改的是捕获的副本a_,而不是原始变量; 由于并没有再次使用Lambda捕获,所以还是使用之前的副本a_来做更改

auto b = 66;
auto f2 = [&]() //引用传递不会新建变量,而是取地址,操作的是同一块内存;
{
b += 10;
cout << "b = " << b << endl;//76
};
f2();
cout << "b2 = " << b << endl;//76,外面的也被改了;相当于普通函数引用传递,可以改变实参;

b = 333;
f2(); //343, 和333有关系了;
}

Lambda 表达式比仿函数更简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void test04_07()
{
int tmp = 10;
MyFunc obj(tmp);//调用仿函数构造函数
cout << obj(7) << endl;//调用仿函数
cout << obj(18, 14) << endl;//调用仿函数

auto f = [tmp](int r) //用lambda表达式,不需要写class和operator()重载了
{
return tmp + r;
};
cout << f(8) << endl;

int r = 8;
auto f1 = [r](int a, int b)
{
return (a > b && b > r);
};
cout << f1(19, 16) << endl;
}

仿函数是编译器实现 Lambda 的一种方式,通过编译器将 Lambda 表达式转化为一个仿函数的对象。
Lambda 表达式可以视为仿函数的一种等价形式


💭Lambda 表达式的类型:

  • 在 C++11 中是闭包类型,每个 Lambda 表达式会产生一个临时对象(右值)。因此严格来说,Lambda 表达式并非函数指针。
  • C++11 允许 Lambda 表达式向函数指针转换,但前提是 Lambda 表达式没有捕获任何变量,且函数指针指向的函数原型必须跟 Lambda 表达式有着相同的签名(返回类型、参数列表)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void test04_08()
{
// std::function可以在任何情况下包装Lambda表达式
function<void(int)> f1 = [](int a) //没有返回值,直接绑定lambda表达式
{
cout << a << endl;
};
f1(100); //100,f1是function对象

function<int(int)> f2 = [](int a) //有返回值,直接绑定lambda表达式
{
return a + 5;
};
cout << f2(110) << endl; //115

function<int(int, int)> f3 = bind //bind先绑定lambda表达式,再传给function去绑定f3
(
[](int a, int b)
{
return a * 2 + b;
},
_2, //把传入的第2个值用于a
_1 //把传入的第1个值用于b
);
cout << f3(10, 8) << endl; //26 //把8用于int a,把10用于int b

auto f4 = [](int x, int y) -> int //[]内没有捕获任何外部变量时,才能转化为函数指针
{
return x + y;
}; // auto等价于int (*)(int,int),而不是function<int(int, int)>
cout << f4(3, 5) << endl; //8

decltype(f4) tmp = f4; //用decltype推导出f4的类型,用这个类型创建对象tmp,把f4赋值给tmp;
cout << tmp(6, 8) << endl;//14

// 推荐使用using,等价于using PFUC = int (*)(int, int);
typedef int(*PFUC)(int, int);//定义函数指针,参数类型、返回值与lambda表达式相同
PFUC p1 = f4; //lambda表达式转化为函数指针; //等价于int (*p1)(int, int) = f4;
cout << p1(10, 19) << endl;//29

//decltype(f4) tmp = p1;//不可把函数指针转化为lambda表达式
}

✅注:易错点区分
使用函数指针(隐式转换,使用 auto 时结果一致 ):

1
2
3
4
5
6
7
8
9
10
11
12
void TestTransform()  
{
int(*f4)(int,int) = [](int x, int y) -> int //[]内不能捕获外部变量,才能转化为函数指针
{
return x + y;
};
int(*p1)(int, int) = f4;
p1(9,9);
using funcPtr = int(*)(int, int);
funcPtr p2 = f4;
p2(9,9);
}

直接使用 std::function 对象存储 Lambda 表达式(较复杂):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void TestTransform()  
{
function<int(int, int)> f4 = [](int x, int y) -> int //[]内不能捕获外部变量,才能转化为函数指针
{
return x + y;
};
//int (*p1)(int, int) = f4; //不能直接将function对象赋值给函数指针,二者无法转换
function<int(int, int)> p1 = f4;
p1(9,9);
using FuncObj = function<int(int, int)>;
FuncObj p2 = f4;
p2(9,9);
//使用 std::function::target<T>() 方法获取 std::function 对象内部的可调用对象。
//如果 std::function 对象内部存储的是一个函数指针,则可以将其转换为函数指针类型。
int (*p3)(int, int) = *f4.target<int(*)(int, int)>(); //target返回函数
p3(9,9);
}

💡lambda 表达式与 for_each(需要头文件 <algorithm> 结合使用):
for_each(InputIterator first, InputIterator last, Function func)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
int tmp01 = 5;//全局变量
vector<int> nums;
vector<int> largeNums; //大于tmp的数

void pushLargeNums(int &n) //回调函数
{
if (n > tmp01)
{
largeNums.push_back(n);
}
}

void printNums(int &n) //回调函数
{
cout << n << " ";
}

void test04_09()
{
for (int i = 0; i < 10; i++)
{
nums.push_back(i + 1);
}

//传统写法:
for (auto it = nums.begin(); it != nums.end(); it++)
{
if (*it > tmp01)
{
largeNums.push_back(*it);
}
}

cout << "传统方法打印1:";
for (int i = 0; i < largeNums.size(); i++)
{
cout << largeNums[i] << " ";
}
cout << endl;

cout << "传统方法打印2:";
for (auto it = largeNums.begin(); it != largeNums.end(); it++)
{
cout << *it << " ";
}
cout << endl;

//用lambda表达式和for_each:

//打印方式1:for_each + 回调函数
cout << "for_each + 回调函数:";
largeNums.clear();
for_each(nums.begin(), nums.end(), pushLargeNums);
for_each(largeNums.begin(), largeNums.end(), printNums);
cout << endl;

//打印方式2:for_each + lambda表达式:
cout << "for_each + lambda表达式:";
largeNums.clear();
for_each
(
nums.begin(),
nums.end(),
[&](int &n)
{
if (n > tmp01)
{
largeNums.push_back(n);
}
}
);

for_each
(
largeNums.begin(),
largeNums.end(),
[](int &n)
{
cout << n << " ";
}
);
cout << endl;
}

💭C++14 中的更新(泛型 Lambda):

  • 在 C++11 中,Lambda 表达式的形式参数需要被声明为具体的类型,比如 (int x)
  • C++14 中引入了泛型 Lambda,允许形式参数声明中使用类型说明符 auto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
auto f = [](auto x, auto y) 
{
return x + y;
};

//泛型lambda函数遵循模板参数推导的规则:
struct unnamed_lambda
{
template<class T, class U>
auto operator()(T x, U y) const
{
return x + y;
}
};
auto lambda = unnamed_lambda{};

//Lambda捕获参数中使用赋值表达式:
auto lambda1 = [value = 1]()
{
return value;
};

//lambda表达式参数可以用auto:
auto lambda2 = [](auto a)
{
return a;
};