C++ 11 初识2

一.新的类功能

默认成员函数

原来 C++ 类中,有 6 个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个 。那么编译器会自动生成一个默认移动构造。 默认生成的移动构造函数,对于内置类

型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,

如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中

的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内

置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋

值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。 ( 默认移动赋值跟上面移动构造

完全类似 )

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

二. 可变参数模板

2.1 可变参数模板的定义

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板。

cpp 复制代码
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为 " 参数包 " ,它里面包含了 0 到 N ( N>=0 )个模版参数。我们无法直接获取参数包 args 中的每个参数的,

只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特

点,也是最大的难点,即如何展开可变模版参数.

但这里可变参数有点抽象,我们只讲用法,不再深入了解。

先看一个简单的例子:

cpp 复制代码
#include<iostream>
using namespace std;

template <class ...Args>
void ShowList1(Args... args)
{
	// 参数个数
	cout << sizeof...(args) << endl;//计算的是参数包中形参的个数
}

int main()
{
	ShowList1(1, 2, 3, 'x', 1.1);
	return 0;
}

输出结果:

2.2 参数包的展开

2.2.1 递归方式展开

通过递归方式展开参数包,需要提供一个参数包展开的函数和一个递归终止函数。

cpp 复制代码
#include<iostream>
using namespace std;


// 递归终止函数
template <class T>
void ShowList(const T& t)
{
	cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
	cout << value << " ";
	ShowList(args...);
}
int main()
{
	ShowList(1);
	ShowList(1, 'A');
	ShowList(1, 'A', std::string("sort"));
	return 0;
}

结果为:

2.2.2 逗号表达式展开参数包

cpp 复制代码
#include<iostream>

template<class T>
void print(T tmp){
    std::cout<<tmp<<std::endl;
}
//可变参数函数模板
template<class...T>
void expand(T...args){
    //逗号运算符
    //初始化列表
    int a[]={(print(args),0)...};
}

int main(){
    expand(1,2,3,4);
    return 0;
}

expand 函数的逗号表达式: (print(args), 0), 也是按照这个执行顺序, 先执行 print(args), 再得到逗号表达式的结果 0。

同时, 通过初始化列表来初始化一个变长数组, { (print(args), 0)... }将会展开成( (print(args1),0), (print(args2), 0), (print(args3), 0), etc...), 最终会创建一个元素只都为 0 的数组 intasizeof...(args)

三.lambda表达式

3.1 lambda的概念

函数指针是在C语言中,不过因为过于麻烦,因此C++中新出了仿函数。sort这个函数就可以根据我们自定义的仿函数来进行对不同的元素排序,不过如果我们需要对很多种自定义类型进行排序,那么就会很麻烦,因此C++11中就又出现了lambda表达式,使之进一步简化。而lambda的底层其实就是仿函数,其出现主要是为了更近一步的简化用法,方便程序员。

3.2 lambda的使用

假设我们要对一个自定义类型进行排序,其中有三个元素,分别是名字,价格,评价等,

现在我们要对他进行排序,但是我们不知道该如何排序,显然,在不同场景中,我们有不同的排序需求,一般而言,我们可以定义三个仿函数。

但lambda表达式可以这样使用:

cpp 复制代码
#include<iostream>
#include<string>
#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)
	{}
};

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

	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._price > g2._price; 
	});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate; 
	});
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate;
	});

	return 0;
}

上述代码的排序问题就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函数。

3.3 lambda表达式语法

lambda表达式书写格式:

capture-list (parameters) mutable -> return-type { statement}

lambda表达式各部分说明

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

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为

空。因此C++11中最简单的lambda函数为:\[\]{}; 该lambda函数不能做任何事情。

3.4 捕获列表说明

捕获列表决定了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。

  1. var:表示值传递方式捕捉变量var
  2. =:表示值传递方式捕获所有父作用域中的变量(包括this)
  3. \&var:表示引用传递捕捉变量var
  4. \&:表示引用传递捕捉所有父作用域中的变量(包括this)
  5. this:表示值传递方式捕捉当前的this指针

注意:

a. 父作用域只包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。

比如:=, \&a, \&b:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量\&,a, this:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。

比如:=, a:=已经以值传递方式捕捉了所有变量,捕捉a就重复传递了

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

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

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

例如:

cpp 复制代码
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;


int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};
	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {
		return a + 3; 
	};
	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {
		b = a + c; 
	};
	fun1(10);
	cout << a << " " << b << endl;

	// 各部分都很完善的lambda函数 额外取地址使用b
	auto fun2 = [=, &b](int c)->int {
		return b += a + c; 
	};
	cout << fun2(10) << endl;
	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable { 
		x *= 2; 
		return a + x; 
	};
	cout << add_x(10) << endl;
	return 0;
}

代码结果为:

3.5 lambda的底层逻辑

在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator。

四.包装器

4.1.function包装器

function包装器也叫适配器。C++中的function本质是一个类模板,也是一个包装器。

在C++中,有很多类型,既可以是函数,也可以是成员函数、静态成员函数、仿函数,又或者是lambda表达式等等,如此多的类型使用模板就会导致效率低下,因此有了function包装器。

4.1.1包装器语法

std::function在头文件<functional>

// 类模板原型如下

cpp 复制代码
template <class T> function; // undefined


template <class Ret, class... Args>
class function<Ret(Args...)>;

模板参数说明:

Ret: 被调用函数的返回类型

Args...:被调用函数的形参

4.1.2 包装器使用

cpp 复制代码
int f(int a, int b)
{
	return a + b;
}
 
struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
 
class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
 
	double plusd(double a, double b)
	{
		return a + b;
	}
};
 
int main()
{
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
 
	std::function<int(int, int)> func2 = Functor();
	cout << func2(10, 20) << endl;
 
	std::function<int(int, int)> func3 = &Plus::plusi;
	cout << func3(100, 200) << endl;
 
	// 非静态成员函数包装
	std::function<double(Plus, double, double)> func4 = &Plus::plusd;
	cout << func4(Plus(), 100.11, 200.11) << endl;
 
	std::function<int(int, int)> func5 = [](int a, int b) {return a + b; };
	cout << func5(100, 200) << endl;
 
	return 0;
}

代码结果为:

上面的包装器就分别有函数、仿函数、静态成员函数、成员函数和lambda表达式。

4.2 实战价值

逆波兰表达式求值

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st;
        map<string, function<int(int, int)>> opFuncMap = 
        {
            { "+", [](long long x, long long y){ return x + y; }},
            { "-", [](long long x, long long y){ return x - y; }},
            { "*", [](long long x, long long y){ return x * y; }},
            { "/", [](long long x, long long y){ return x / y; }}
        };
 
        for(auto& str : tokens)
        {
            // 操作符
            if(opFuncMap.count(str))
            {
                long long right = st.top();
                st.pop();
                long long left = st.top();
                st.pop();
 
                st.push(opFuncMap[str](left, right));
            }
            // 操作数
            else
            {
                st.push(stoll(str));
            }
        }
 
        return st.top();
    }
};
相关推荐
怕浪猫5 分钟前
Electron 开发实战(十六):总结与展望|生态现状、框架对比、行业趋势与学习指南
前端·javascript·electron
夜悊9 分钟前
C++代码示例:进制数简单生成工具
c++
怕浪猫12 分钟前
Electron 系列文章封面图
算法·架构·前端框架
plainGeekDev20 分钟前
null 判断 → Kotlin 可空类型
android·java·kotlin
糖拌西瓜皮22 分钟前
Java开发者视角:深入理解Node.js异步编程模型
java·后端·node.js
plainGeekDev23 分钟前
getter/setter → Kotlin 属性
android·java·kotlin
一线大码42 分钟前
Smart-Doc 的简单使用
java·后端·restful
ZengLiangYi1 小时前
批量导入 1000 条对话的性能优化实战
javascript·后端·架构
竹林8181 小时前
用 wagmi v2 + viem 监听合约事件时踩的坑,我花了两天才把"遗漏事件"修好
javascript
郝学胜_神的一滴1 小时前
CMake 021: IF 条件判据详诠
c++·cmake