【C++】C++11

C++学习阶段的参考文档:
Reference - C++ Reference

这个文档在C++98、C++11时候还行,之后就完全没法用了......

还可以看语法------准官方文档(同步更新),下面这个包括C++26都同步了,我们以后会看这个。

cppreference.com

有大佬------官方文档(类似论坛):

Standard C++

1. C++发展的历史

2. 列表初始化

2.1 C++98传统的{}

C++98中⼀般数组和结构体可以用{}进行初始化。

2.2 C++11中的{}

  • C++11以后想统⼀初始化方式,试图实现⼀切对象皆可⽤{}初始化,{}初始化也叫做列表初始化。
  • 内置类型支持,⾃定义类型也⽀持,⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化了以后变成直接构造。
  • {}初始化的过程中,可以省略掉=
  • C++11列表初始化的本意是想实现⼀个大统⼀的初始化方式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便
cpp 复制代码
struct Point
{
	int _x;
	int _y;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

	Date(const Date& d)
		:_year(d._year)
		,_month(d._month)
		,_day(d._day)
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


Date func()
{
	//Date d(2025, 11, 15);
	//return d;

	//return { 2025, 11, 15 };

	//Date d;
	//return d;

	return {};
}
int main()
{
	// C++98
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = {0};
	Point p = { 1, 2 };

	Date d1(2025, 11, 15);
	
	Date d2 = 2025;
	Date d5 = { 2025, 11, 15 };
	Date d6{ 2025, 11, 15 };
	Date d7{};

	Insert(2025);
	Insert({ 2025, 11, 15 });

	int i = 0;
	int j = { 1 };
	int k{ 2 };
	int m{};

	return 0;
}

2.3 C++11中的std::initializer_list

cpp 复制代码
int main()
{
	vector<int> v1 = { 1,2,3,4 };
	vector<int> v2{ 1,2,3,4,5,6,6,7,7 };

	map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };

	v1 = { 10, 20, 30 };

	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;

	std::initializer_list<int> mylist;
	mylist = { 10, 20, 30 };
	cout << sizeof(mylist) << endl;

	// 这里begin和end返回的值initializer_list对象中存的两个指针
	// 这两个指针的值跟i的地址更接近,说明数组存在栈上
	int i = 0;
	cout << mylist.begin() << endl;
	cout << mylist.end() << endl;
	cout << &i << endl;

	return 0;
}

3. 右值引用和移动语义

C++98的C++语法中就有引用的语法,⽽C++11中新增了的右值引用语法特性,C++11之后我们之前学习的引用就叫做左值引⽤引用。⽆论左值引用还是右值引用,都是给对象取别名。

3.1 左值和右值

  • 左值是⼀个表示数据的表达式(如变量名或解引用的指针 ),⼀般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。
  • 右值也是⼀个表示数据的表达式,要么是字面值常量 、要么是表达式求值过程中创建的临时对象等 ,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
  • 值得⼀提的是,左值的英⽂简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left
    value、right value 的缩写。现代C++中,lvalue 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,⽽ rvalue 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等,也就是说左值和右值的核心区别就是能否取地址。
cpp 复制代码
int main()
{
	// 左值:可以取地址
	// 以下的p, b, c, *p, s, s[0]就是常见的左值
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("11111111111111");
	s[0] = 'x';

	cout << &p << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &(*p) << endl;
	cout << &s << endl;
	cout << (void*)&s[0] << endl;

	// 右值:不能取地址
	double x = 1.1, y = 2.2;
	// 以下几个10、x + y、fmin(x, y)、string("11111")都是常见的右值
	10;
	x + y;
	fmin(x, y);
	string("1111");

	// 编译报错
	//cout << &10 << endl;
	//cout << &(x + y) << endl;
	//cout << &(fmin(x + y)) << endl;
	//cout << &string("1111") << endl;
}

3.2 左值引用和右值引用

  • Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引用,左值引⽤就是给左值取别
    名,第⼆个就是右值引用,同样的道理,右值引用就是给右值取别名。
  • 左值引用不能直接引用右值,但是const左值引用可以引用右值
  • 右值引用不能直接引用左值,但是右值引用可以引用move(左值)
  • template <class T> typename remove_reference<T>::type&& move (T&&
    arg);
  • move是库⾥⾯的⼀个函数模板,本质内部是进⾏强制类型转换,当然他还涉及⼀些引⽤折叠的知识,这个我们后⾯会细讲
  • 需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引用变量变量表达式的属性是左值
  • 语法层⾯看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下⾯代码中r1和rr1汇编层实现,底层都是⽤指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要然到⼀起去理解,互相佐证,这样反⽽是陷⼊迷途。
cpp 复制代码
int main()
{
	// 左值:可以取地址
	// 以下的p, b, c, *p, s, s[0]就是常见的左值
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("11111111111111");
	s[0] = 'x';
	double x = 1.1, y = 2.2;

	// 左值引用给左值取别名
	int& r1 = b;
	int*& r2 = p;
	int& r3 = *p;
	string& r4 = s;
	char& r5 = s[0];

	// 右值引用给右值取别名
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	string&& rr4 = string("1111");
	
	// 左值引用不能直接引用右值,但是const左值引用可以引用右值
	const int& rx1 = 10;
	const double& rx2 = x + y;
	const double& rx3 = fmin(x, y);
	const string& rx4 = string("11111");

	// 右值引用不能直接引用左值,但是右值引用可以引用move(左值)
	int&& rrx1 = move(b);
	int*&& rrx2 = move(p);
	int&& rrx3 = move(*p);
	string&& rrx4 = move(s);
	string&& rrx5 = (string&&)s;

	int&& rrrx1 = move(rrx1);  // rrx1本身是左值

	return 0;
}

3.3 引用延长生命周期

右值引用可用于为临时对象延长生命周期,const 的左值引⽤也能延长临时对象⽣存期,但这些对象无法被修改。

cpp 复制代码
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};

int main()
{
	A aa1;
	// const 延长匿名对象的生命周期
	const A& ref1 = A();
	A&& ref2 = A();

	cout << "main end" << endl;

	return 0;
}

原来的匿名对象在当前行初始化当前行销毁

3.4 左值和右值的参数匹配

  • C++98中,我们实现⼀个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。
  • C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。
  • 右值引用变量在用于表达式时属性是左值,这个设计这里会感觉很怪,下⼀小节我们讲右值引用的使用场景时,就能体会这样设计的价值了
cpp 复制代码
void f(int& x)
{
	cout << "左值引用重载 f(" << x << ")\n";
}

void f(const int& x)
{
	cout << "const的左值引用重载 f(" << x << ")\n";
}

void f(int&& x)
{
	cout << "右值引用重载 f(" << x << ")\n";
}

int main()
{
	f(10);

	int a = 20;
	f(a);

	const int b = 20;
	f(b);

	// 右值引用本身的属性是左值
	int&& x = 1;
	f(x);
	f(move(x));

	return 0;
}

3.5 右值引用和移动语义的使用场景

3.5.1 左值引用主要使用场景回顾

左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如addStrings和generate函数,C++98 中的解决方案只能是被迫使用输出型参数解决。那么C++11以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是⼀个局部对象,函数结束这个对象就析构销毁了,右值引用返回也⽆法概念对象已经析构销毁的事实。

3.5.2 移动构造和移动赋值

  • 移动构造函数是一种构造函数,类似拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用,如果还有其他参数,额外的参数必须有缺省值。
  • 移动赋值是一个赋值运算符的重载,他跟拷贝赋值构成函数重载,类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但是不同的是要求这个参数是右值引用。
  • 对于像string/vector这样的深拷贝的类或者包含深拷贝的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第一个参数都是右值引用的类型,他的本质是要"窃取"引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从提高效率。下面的bit::string样例实现了移动构造和移动赋值,我们需要结合场景理解。
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
using namespace std;

namespace bit
{
    class string
    {
    public:
        typedef char* iterator;
        typedef const char* const_iterator;

        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }

        const_iterator begin() const
        {
            return _str;
        }

        const_iterator end() const
        {
            return _str + _size;
        }

        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            cout << "string(char* str)-构造" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // 拷贝构造
        string(const string& s)
        {
            cout << "string(const string& s) -- 拷贝构造" << endl;

            reserve(s._capacity);
            for (auto ch : s)
            {
                push_back(ch);
            }
        }

        // 移动构造
        string(string&& s)
        {
            cout << "string(string&& s) -- 移动构造" << endl;
            swap(s);
        }

        string& operator=(const string& s)
        {
            cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
            if (this != &s)
            {
                _str[0] = '\0';
                _size = 0;

                reserve(s._capacity);
                for (auto ch : s)
                {
                    push_back(ch);
                }
            }

            return *this;
        }

        // 移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }

        ~string()
        {
            //cout << "~string() -- 析构" << endl;
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                if (_str)
                {
                    strcpy(tmp, _str);
                    delete[] _str;
                }
                _str = tmp;
                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }

        size_t size() const
        {
            return _size;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
    };

    // 传值返回需要拷贝
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        // 进位
        int next = 0;
        while (end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;
            str += ('0' + ret);
        }
        if (next == 1)
            str += '1';
        reverse(str.begin(), str.end());
        cout << &str << endl;

        return str;
    }
}
cpp 复制代码
int main()
{
    bit::string s1("xxxxx");
    // 拷贝构造
    bit::string s2 = s1;
    // 构造+移动构造,优化后直接构造
    bit::string s3 = bit::string("yyyyy");
    // 移动构造
    bit::string s4 = move(s1);
    cout << "******************************" << endl;
    return 0;
}

3.5.3 右值引用和移动语义解决传值返回问题

cpp 复制代码
    // 传值返回需要拷贝
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        // 进位
        int next = 0;
        while (end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret = ret % 10;
            str += ('0' + ret);
        }
        if (next == 1)
            str += '1';
        reverse(str.begin(), str.end());
        cout << &str << endl;

        return str;
cpp 复制代码
#include "List.h"
int main()
{
    bit::list<bit::string> lt;
    cout << "*************************" << endl;

    bit::string s1("111111111111111111111");
    lt.push_back(s1);
    cout << "*************************" << endl;

    lt.push_back(bit::string("22222222222222222222222222222"));
    cout << "*************************" << endl;

    lt.push_back("3333333333333333333333333333");
    cout << "*************************" << endl;

    // 左值move本质授予别人转移你数据资源权限,所以要谨慎
    lt.push_back(move(s1));
    cout << "*************************" << endl;

    return 0;
}

右值对象构造,只有拷贝构造,没有移动构造的场景

  • 图1展示了vs2019 debug环境下编译器对拷贝的优化,左边为不优化的情况下,两次拷贝构造,右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次拷贝构造。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下面代码优化为非常恐怖,会直接将str对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为一,变为直接构造。变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解,如图3所示。
  • linux下可以将下面代码拷贝到test.cpp文件,编译时用 g++ test.cpp -fno-elideconstructors
    的方式关闭构造优化,运行结果可以看到图1左边没有优化的两次拷贝。

图 1

右值对象构造,有拷贝构造,也有移动构造的场景

  • 图2展示了vs2019 debug环境下编译器对拷贝的优化,左边为不优化的情况下,两次移动构造,右边为编译器优化的场景下连续步骤中的拷贝合二为一变为一次移动构造。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下面代码优化为非常恐怖,会直接将str对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为一,变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解,如图3所示。
  • linux下可以将下面代码拷贝到test.cpp文件,编译时用 g++ test.cpp -fno-elideconstructors
    的方式关闭构造优化,运行结果可以看到图1左边没有优化的两次移动。

图 2

图 3

右值对象赋值,只有拷贝构造和拷贝赋值,没有移动构造和移动赋值的场景

  • 图4左边展示了vs2019 debug和g++ test.cpp -fno-elide-constructors 关闭优化环境
    下编译器的处理,一次拷贝构造,一次拷贝赋值。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下面代码会进一步优化,直接构造要返回的临时对象,str本质是临时对象的引用,底层角度用指针实现。运行结果的角度,我们可以看到str的析构是在赋值以后,说明str就是临时对象的别名。

图 4

右值对象赋值,既有拷贝构造和拷贝赋值,也有移动构造和移动赋值的场景

  • 图5左边展示了vs2019 debug和g++ test.cpp -fno-elide-constructors 关闭优化环境
    下编译器的处理,一次移动构造,一次移动赋值。
  • 需要注意的是在vs2019的release和vs2022的debug和release,下面代码会进一步优化,直接构造要返回的临时对象,str本质是临时对象的引用,底层角度用指针实现。运行结果的角度,我们可以看到str的析构是在赋值以后,说明str就是临时对象的别名。

图 5

3.6 类型分类

  • C++11以后,进一步对类型进行了划分,右值被划分纯右值(pure value,简称prvalue)和将亡值(expiring value,简称xvalue)。
  • 纯右值是指那些字面值常量或求值结果相当于字面值或是一个不具名的临时对象。如: 42、
    true、nullptr 或者类似str.substr(1, 2)、str1 + str2 传值返回函数调用,或者整形a、b,a++,a+b 等。纯右值和将亡值C++11中提出的,C++11中的纯右值概念划分等价于
    C++98中的右值。
  • 将亡值是指返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达,如
    move(x)、static_cast<X&&>(x)
  • 泛左值(generalized value,简称glvalue),泛左值包含将亡值和左值。
  • 值类别-cppconference.comValue categories - cppreference.com这两个关于值类型的中文和英文的官方文档,有兴趣可以了解细节。

3.7 引用折叠

  • C++中不能直接定义引用的引用如int& && r = i; ,这样写会直接报错,通过模板或 typedef
    中的类型操作可以构成引用的引用。
  • 通过模板或 typedef 中的类型操作可以构成引用的引用时,这时C++11给出了一个引用折叠的规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用。
  • 下面的程序中很好的展示了模板和typedef时构成引用的引用时的引用折叠规则,大家需要一个一个仔细理解一下。
  • 像f2这样的函数模板中,T&& x参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值时就是左值引用,传递右值时就是右值引用,有些地方也把这种函数模板的参数叫做万能引用。
  • Function(T&& t)函数模板程序中,假设实参是int右值,模板参数T的推导int,实参是int左值,模板参数T的推导int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参的Function,实参是右值,实例化出右值引用版本形参的Function。
cpp 复制代码
// 由于引用折叠限定,f1实例化以后总是一个左值引用
template<class T>
void f1(T& x)
{}

// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x)
{}

int main()
{
    // int&& & r = i;

    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&&

    // 没有折叠->实例化为void f1(int& x)
    f1<int>(n);
    f1<int>(0);   //报错

    // 折叠->实例化为void f1(int& x)
    f1<int&>(n);
    f1<int&>(0);  //报错

    // 折叠->实例化为void f1(int& x)
    f1<int&&>(n);
    f1<int&&>(0);  //报错

    // 折叠->实例化为void f1(const int& x)
    f1<const int&>(n);
    f1<const int&>(0);

    // 折叠->实例化为void f1(const int& x)
    f1<const int&&>(n);
    f1<const int&&>(0);

    // 没有折叠->实例化为void f2(int&& x)
    f2<int>(n); // 报错
    f2<int>(0);

    // 折叠->实例化为void f2(int& x)
    f2<int&>(n);
    f2<int&>(0); // 报错

    // 折叠->实例化为void f2(int&& x)
    f2<int&&>(n); // 报错
    f2<int&&>(0);

    return 0;

}
cpp 复制代码
// 万能引用
// 传左值,实例化出左值引用的Function函数
// 传右值,实例化出右值引用的Function函数
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);
    
    // std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
    // 所以Function内部会编译报错,x不能++
    Function(std::move(b)); // const 右值

    return 0;
}

3.8 完美转发

  • Function(T&& t)函数模板程序中,传左值实例化以后是左值引用的Function函数,传右值实例化以后是右值引用的Function函数。
  • 但是结合我们在5.2章节的讲解,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说Function函数中t的属性是左值,那么我们把t传递给下一层函数Fun,那么匹配的都是左值引用版本的Fun函数。这里我们想要保持t对象的属性,就需要使用完美转发实现。
  • template <class T> T&& forward (typename remove_reference<T>::type& arg);
  • template <class T> T&& forward (typename remove_reference<T>::type&& arg);
  • 完美转发forward本质是一个函数模板,他主要还是通过引用折叠的方式实现,下面示例中传递给Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引用返回;传递给Function的实参是左值,T被推导为int&,引用折叠为左值引用,forward内部t被强转为左值引用返回。
cpp 复制代码
// 万能引用
template<class T>
void Function(T&& t)
{
    Fun(forward<T>(t));
}

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;
    // a是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int& t)
    Function(b); // const 左值

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

    return 0;
}

4. 可变参数模板

4.1 基本语法及原理

  • C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零或多个函数参数。
  • 我们用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class...或
    typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
  • 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
  • 这里我们可以使用sizeof...运算符去计算参数包中参数的个数。

模板只做到了参数类型可变

但是可变参数模板做到了参数类型可变、个数也可变

4.2 包扩展(了解)

  • 对于一个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放一个省略号(...)来触发扩展操作。底层的实现细节如图1所示。
  • C++还支持更复杂的包扩展,直接将参数包依次展开依次作为实参给一个函数去处理。

上面只是C++11标准,到了C++17已经弃用,了解即可

C++17的折叠表达式:

cpp 复制代码
// 折叠表达式 C++17
template <class ...Args>
void Print(Args... args)
{
    ((cout << args << " s"), ...);

    cout << "\n";
}

// Print(1, string("xxxxx"), 2.2);
//实例化为下面的函数
//void Print(int x, string y, double z)
//{
//    //((cout << args << " "), ...);
//    ((cout << x << " "), (cout << y << " "), (cout << z << " "));
//
//    cout << "\n";
//}

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

    return 0;
}

4.3 emplace接口

  • C++11以后STL容器新增了emplace系列的接口,emplace系列的接口均为模板可变参数,功能上兼容push和insert系列,但是empalce还支持新玩法,假设容器为container<T>,emplace还支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
  • emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列
  • 我们模拟实现了list的emplace和emplace_back接口,这里把参数包不段往下传递,最终在结点的构造中直接去匹配容器存储的数据类型T的构造,所以达到了前面说的empalce支持直接插入构造T对象的参数,这样有些场景会更高效一些,可以直接在容器空间上构造T对象。
  • 传递参数包过程中,如果是Args&&... args 的参数包,要用完美转发参数包,方式如下
    std::forward<Args>(args)... ,否则编译时包扩展后右值引用变量表达式就变成了左值。
cpp 复制代码
int main()
{
    list<bit::string> lt;

    // 传左值,跟push_back一样,走拷贝构造
    bit::string s1("111111111111");
    lt.emplace_back(s1);
    cout << "*********************************" << endl;

    // 右值,跟push_back一样,走移动构造
    lt.emplace_back(move(s1));
    cout << "*********************************" << endl;

    // 直接把构造string参数包往下传,直接用string参数包构造string
    // 这里达到的效果是push_back做不到的
    lt.push_back("111111111111");
    cout << "*********************************" << endl;

    lt.emplace_back("111111111111");
    cout << "*********************************" << endl;
}
cpp 复制代码
struct Date
{
    int _y;
    int _m;
    int _d;
    Date(int y = 1, int m = 1, int d = 1)
        :_y(y)
        , _m(m)
        , _d(d)
    {}
};

list.h

cpp 复制代码
#pragma once

namespace bit
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		//list_node(const T& x = T())
		//	:_next(nullptr)
		//	, _prev(nullptr)
		//	, _data(x)  // 拷贝构造
		//{}

		//list_node(T&& x)
		//	:_next(nullptr)
		//	, _prev(nullptr)
		//	, _data(move(x))  // 移动构造或拷贝构造
		//{}

		template<class X>
		list_node(X&& x = X())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(forward<X>(x))
		{}

		template<class ...Args>
		list_node(Args&& ...args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(forward<Args>(args)...)
		{}
	};

	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		using Self = list_iterator<T, Ref, Ptr>;
		using Node = list_node<T>;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{}

		// *it = 1
		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		// ++it
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		// --it
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		bool operator!=(const Self& s) const
		{
			return _node != s._node;
		}

		bool operator==(const Self& s) const
		{
			return _node == s._node;
		}
	};

	template<class T>
	class list
	{
		using Node = list_node<T>;
	public:
		using iterator = list_iterator<T, T&, T*>;
		using const_iterator = list_iterator<T, const T&, const T*>;

		// using iterator = list_iterator<T>;
		// using const_iterator = list_const_iterator<T>;

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}

		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

		// 9:09
		list(initializer_list<T> il)
		{
			empty_init();
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		list(size_t n, T val = T())
		{
			empty_init();
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		list(int n, T val = T())
		{
			empty_init();
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
			_size = 0;
		}

		// ͳд
		// lt2(lt1)
		list(const list<T>& lt)
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

		// lt1 = lt3;
		list<T>& operator=(const list<T>& lt)
		{
			if (this != &lt)
			{
				clear();
				for (auto& e : lt)
				{
					push_back(e);
				}
			}

			return *this;
		}

		// ִд
		//list(list<T>& lt)
		//list(const list& lt)
		//{
		//	empty_init();

		//	list tmp(lt.begin(), lt.end());
		//	swap(tmp);
		//}

		//// lt1 = lt3;
		////list<T>& operator=(list<T> tmp)
		//list& operator=(list tmp)
		//{
		//	swap(tmp);

		//	return *this;
		//}

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		//void push_back(const T& x)
		//{
		//	insert(end(), x);
		//}

		//// 不是万能引用
		//// 因为T是list的参数,list<bit::string>实例化时,T就确定了。
		//void push_back(T&& x)
		//{
		//	insert(end(), move(x));
		//}

		template<class...Args>
		void emplace_back(Args&& ... args)
		{
			//emplace(end(), args...);
			emplace(end(), forward<Args>(args)...);
		}

		template<class...Args>
		void emplace(iterator pos, Args&& ... args)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(forward<Args>(args)...);

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		void push_back(T&& x)
		{
			insert(end(), forward<T>(x));
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		void insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		void insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(forward<T>(x));

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;
		}

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;
			delete cur;
			--_size;

			//return iterator(next);
			return next;
		}

		size_t size() const
		{
			/*size_t n = 0;
			for (auto& e : *this)
			{
				++n;
			}
			return n;*/
			return _size;
		}

	private:
		Node* _head;
		size_t _size = 0;
	};
}
cpp 复制代码
int main()
{
    list<pair<bit::string, int>> lt1;

    // 跟push_back一样
    // 构造pair + 拷贝/移动构造pair到list的节点中data上
    pair<bit::string, int> kv("苹果", 1);
    lt1.emplace_back(kv);
    cout << "*********************************" << endl;

    // 跟push_back一样
    lt1.emplace_back(move(kv));
    cout << "*********************************" << endl;

    // 直接把构造pair参数包往下传,直接用pair参数包构造pair
    // 这里达到的效果是push_back做不到的
    lt1.emplace_back("苹果", 1);
    //lt1.push_back("苹果", 1); // 错误,要传pair或者{}隐式转换pair的值
    //lt1.push_back({"苹果", 1}); // 要传pair或者{}隐式转换pair的值
    cout << "*********************************" << endl;

    list<Date> lt;
    // 构造 + 拷贝构造
    Date d1{ 2025,11,18 };
    lt.push_back(d1);

    lt.push_back({ 2025,11,18 });

    // 传构造Date的参数,传给形参参数包,参数包往下不断传递,最后直接构造到链表节点上
    // 直接构造
    lt.emplace_back(2025, 11, 18);

    return 0;
}
cpp 复制代码
int main()
{
    list<pair<bit::string, int>> lt1;
    cout << "*********************************" << endl;

    // 跟push_back一样
    // 构造pair + 拷贝/移动构造pair到list的节点中data上
    pair<bit::string, int> kv("苹果", 1);
    lt1.emplace_back(kv);
    cout << "*********************************" << endl;

    // 跟push_back一样
    lt1.emplace_back(move(kv));
    cout << "*********************************" << endl;

    // 直接把构造pair参数包往下传,直接用pair参数包构造pair
    // 这里达到的效果是push_back做不到的
    lt1.emplace_back("苹果", 1);  // 推荐
    //lt1.emplace_back({ "苹果", 1 }); // 错误
    //lt1.push_back("苹果", 1); // 错误,要传pair或者{}隐式转换pair的值
    lt1.push_back({"苹果", 1}); // 要传pair或者{}隐式转换pair的值
    cout << "*********************************" << endl;


    bit::list<Date> lt;
    // 构造 + 拷贝构造
    Date d1{ 2025,11,18 };
    lt.push_back(d1);
    lt.push_back({ 2025,11,18 });

    // 传构造Date的参数,传给形参参数包,参数包往下不断传递,最后直接构造到链表节点上
    // 直接构造
    lt.emplace_back(2025,11,18);

    return 0;
}

5. 新的类功能

5.1 默认的移动构造和移动赋值

  • 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
  • 如果你没有自己实现移动构造函数 ,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数 ,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
cpp 复制代码
class Person
{
public:
    Person(const char* name = "张三yyyyyyyyyyy", int age = 18)
        :_name(name)
        , _age(age)
    {}
    

private:
    bit::string _name;
    int _age;
};

int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);

    Person s4("xxxxxxxxxxxxxxxxxxxxxxx", 1);
    s4 = std::move(s2);

    return 0;
}

5.2 成员变量声明时给缺省值

成员变量声明时给缺省值是给初始化列表用的,如果没有显示在初始化列表初始化,就会在初始化列表用这个缺省值初始化,这个我们在类和对象部分讲过了,忘了就去复习吧。

5.3 defult和delete

  • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成
  • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明不实现,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
cpp 复制代码
class Person
{
public:
    Person(const char* name = "张三yyyyyyyyyyy", int age = 18)
        :_name(name)
        , _age(age)
    {}

    // C++11
    Person(const Person& p) = delete;
    Person(Person&& p) = default;
    
    ~Person()
    {}

private:
    // C++98
    // Person(const Person& p);

    bit::string _name;
    int _age;
};

int main()
{
    Person s1;
    // Person s2 = s1;
    Person s3 = std::move(s1);

    //Person s4("xxxxxxxxxxxxxxxxxxxxxxx", 1);
    //s4 = std::move(s2);

    return 0;
}

5.5 final与override

在继承与多态章节介绍过,可以复习

5.6 委托构造函数

cpp 复制代码
class Example {
public:
    Example(int a, int b)
        :_x(a)
        , _y(b)
    {
        cout << "目标构造函数\n";
    }

    Example(int a)
        : Example(a, 0)
    {
        cout << "委托构造函数\n";
    }

    int _x;
    int _y;
};

class Time {
public:
    Time(int h, int m)
        :_hour(h)
        , _minute(m)
    {}

    // error C3511: "Time": 对委托构造函数的调用应仅为成员初始值设定项
    // error C2437 : "_second": 已初始化
    Time(int h, int m, int s)
        :Time(h, m)
       // , _second(s)
    {}

private:
    int _hour;
    int _minute;
    int _second = 0;
};

int main()
{
    Example(1, 2);
    Example(1);

    return 0;
}

5.7 继承构造函数

cpp 复制代码
class Base {
public:
    Base(int x, double d)
        :_x(x)
        , _d(d)
    {}

    Base(int x)
        :_x(x)
    {}

    Base(double d)
        :_x(d)
    {}

protected:
    int _x = 0;
    double _d = 0;
};

// 传统的派生类实现构造
//class Derived : public Base {
//public:
//    Derived(int x) : Base(x) {}
//    Derived(double d) : Base(d) {}
//    Derived(int x, double d) : Base(x, d) {}
//};

// C++11继承基类的所有构造函数
class Derived : public Base {
public:
    using Base::Base;

    /*protected:
        int _i = 0;
        string _s;*/
};

//std::map<std::string, std::pair<std::string, std::string>>::iterator func();
//auto func()->std::map<std::string, std::pair<std::string, std::string>>::iterator;

int main()
{
    Derived d1(1);
    Derived d2(1.1);
    Derived d3(2, 2.2);

    return 0;
}

6. STL中一些变化

7. lambda

7.1 lambda表达式语法

cpp 复制代码
int main()
{
    // 一个简单的lambda表达式
    //auto add1 = [](int x, int y)->int {return x + y; };
    auto add1 = [](int x, int y){return x + y; };
    cout << add1(1, 2) << endl;

    // 1、捕捉为空也不能省略
    // 2、参数为空可以省略
    // 3、返回值可以省略,可以通过返回对象自动推导
    // 4、函数题不能省略
    auto func1 = []
    {
        cout << "hello bit" << endl;
        return 0;
    };
    func1();

    int a = 0, b = 1;
    auto swap1 = [](int& x, int& y)
    {
        int tmp = x;
        x = y;
        y = tmp;
    };
    swap1(a, b);
    cout << a << ":" << b << endl;

    return 0;
}

7.2 lambda的应用

cpp 复制代码
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;
    }
};

struct CompareEvaluateLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._evaluate < gr._evaluate;
    }
};

struct CompareEvaluateGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._evaluate > gr._evaluate;
    }
};

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

    // 类似这样的场景,我们实现仿函数对象或者函数指针支持商品中
    // 不同项的比较,相对还是比较麻烦的,那么这里lambda就很好用了
  /*  sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
    sort(v.begin(), v.end(), CompareEvaluateLess());
    sort(v.begin(), v.end(), CompareEvaluateGreater());*/

    /*auto priceLess = [](const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    };

    sort(v.begin(), v.end(), priceLess);*/
 
    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._price < gr._price;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._price > gr._price;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._evaluate < gr._evaluate;
        });

    sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
            return gl._evaluate > gr._evaluate;
        });

    return 0;
}

7.3 lambda的原理

7.4 捕捉列表

cpp 复制代码
int x = 0;
// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []()
    {
        x++;
    };

class A
{
public:
    void func()
    {
        int x = 0, y = 1;

        auto f1 = [=]
            {
                _a1++;
                return x + y + _a1 + _a2;
            };

        cout << f1() << endl;

        auto f2 = [&]
            {
                x++;
                _a1++;
                return x + y + _a1 + _a2;
            };

        cout << f2() << endl;

        // 捕捉this本质是可以访问成员变量
        auto f3 = [x, this]
            {
                _a1++;
                return x + _a1 + _a2;
            };

        cout << f3() << endl;
    }

private:
    int _a1 = 0;
    int _a2 = 1;
};

int main()
{
    // 只能用当前lambda局部域捕捉的对象和全局对象
    // 捕获列表的意义,本质更方便的使用当前局部域的对象
    int a = 0, b = 1, c = 2, d = 3;
    //auto func1 = [a, &b] () mutable
    auto func1 = [a, &b]
        {
            // 值捕捉的变量不能修改,引用捕捉的变量可以修改
            // a++;
            b++;
            int ret = a + b;
            x++;
            return ret;
        };
    cout << func1() << endl;

    // 隐式值捕捉
    // 用了哪些变量就捕捉哪些变量
    auto func2 = [=]
        {
            int ret = a + b + c;
            return ret;
        };
    cout << func2() << endl;

    // 隐式引用捕捉
    // 用了哪些变量就捕捉哪些变量
    auto func3 = [&]
        {
            a++;
            c++;
            d++;
        };
    func3();
    cout << a << " " << b << " " << c << " " << d << endl;

    // 混合捕捉1
    auto func4 = [&, a, b]
        {
            //a++;
            //b++;
            c++;
            d++;
            return a + b + c + d;
        };
    func4();
    cout << a << " " << b << " " << c << " " << d << endl;

    /*  class lambda5
      {
      public:
          lambda5(int a_, int& b_)
              :a(a_)
              , b(b_)
          {}

          int operator()(int x)
          {
              ++b;
              return a + b + x;
          }
      private:
          const int a;
          int& b;
      };*/

    auto func5 = [a, &b](int x)
        {
            ++b;
            return a + b + x;
        };
    // 等价于
    // lambda5 func5(a, b);
    func5(1);

    return 0;
}

8. 包装器

8.1 function

std::function 是 C++11 引入的标准库中的一个通用多态函数包装器 (general-purpose polymorphic function wrapper),定义在 <functional> 头文件中。它的主要作用是对可调用对象(callable objects)进行类型擦除和统一接口封装,使得不同类型的可调用实体可以被统一存储、传递和调用。

function可以包装下面的可调用对象:

函数指针

仿函数

lambda

cpp 复制代码
#include<functional>

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;


	vector<function<int(int, int)>> v;
	v.push_back(f);
	v.push_back(Functor());
	v.push_back([](int a, int b) {return a + b; });

	for (auto& f : v)
	{
		cout << f(1, 1) << endl;
	}

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

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

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

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

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

	auto pf1 = &Plus::plusd;
	Plus* ptr = &ps;
	cout << (ps.*pf1)(1.1, 1.1) << endl;   // 对象调用成员函数指针时,使用.*运算符
	cout << (ptr->*pf1)(1.1, 1.1) << endl; // 指针调用成员函数时,使用->运算符

	return 0;
}

150. 逆波兰表达式求值 - 力扣(LeetCode)

传统解法:

改进:

使用map映射string和function的方式实现

这种方式的最大优势之一是方便扩展,假设还有其他运算,我们增加map中的映射即可

8.2 bind

bind主要用来调整参数个数和参数顺序,placeholders命名空间的_1 _2分别表示实参的第一个参数和第二个参数
bind一般用于,绑死一些固定参数

cpp 复制代码
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

int main()
{
	// bind 本质返回的一个仿函数对象
	// 调整参数顺序(不常用)
	// _1代表第一个实参
	// _2代表第二个实参
	// ...
	auto f1 = bind(Sub, _1, _2);
	auto f2 = bind(Sub, _2, _1);

	// _1代表第一个实参
	// _2代表第二个实参
	cout << f1(10, 5) << endl;
	cout << f2(10, 5) << endl;

	// 调整参数个数
	auto f3 = bind(SubX, 10, _1, _2);
	cout << f3(15, 5) << endl;
	// _1代表第一个实参
	// _2代表第二个实参
	// 底层operator(),调用SubX,第一个参数10,15, 5

	auto f4 = bind(SubX, _1, 10, _2);
	cout << f4(15, 5) << endl;
	// 底层operator(),调用SubX,第一个参数15,10, 5

	auto f5 = bind(SubX, _1, _2, 10);
	cout << f5(15, 5) << endl;
	// 底层operator(),调用SubX,第一个参数15,5, 10

	function<double(Plus, double, double)> f7 = &Plus::plusd;
	cout << f7(Plus(), 1.1, 1.1) << endl;
	cout << f7(Plus(), 2.2, 1.1) << endl;
	cout << f7(Plus(), 3.3, 1.1) << endl;

	function<double(double, double)> f8 = bind(&Plus::plusd,Plus(),_1,_2);
	cout << f8(1.1, 1.1) << endl;
	cout << f8(2.2, 1.1) << endl;
	cout << f8(3.3, 1.1) << endl << endl;

	// 计算复利的lambda
	auto func1 = [](double rate, double money, int year)->double {
		double ret = money;
		for (int i = 0; i < year; i++)
		{
			ret = ret + ret * rate;
		}
		return ret - money;
	};

	function<double(double)> func_r1_5_y3 = bind(func1, 0.015, _1, 3);
	function<double(double)> func_r1_5_y5 = bind(func1, 0.015, _1, 5);
	function<double(double)> func_r1_5_y20 = bind(func1, 0.015, _1, 20);

	cout << func_r1_5_y3(100000) << endl;
	cout << func_r1_5_y5(100000) << endl;
	cout << func_r1_5_y20(100000) << endl;

	function<double(double)> func_r10_y3 = bind(func1, 0.1, _1, 3);
	function<double(double)> func_r10_y5 = bind(func1, 0.1, _1, 5);
	function<double(double)> func_r10_y20 = bind(func1, 0.1, _1, 20);
	cout << func_r10_y3(100000) << endl;
	cout << func_r10_y5(100000) << endl;
	cout << func_r10_y20(100000) << endl;

	return 0;
}
相关推荐
lsx2024062 小时前
CSS 图片廊
开发语言
雾岛听蓝2 小时前
C++优选算法 | 双指针篇(一)
开发语言·c++
byzh_rc2 小时前
[微机原理与系统设计-从入门到入土] 微型计算机基础
开发语言·javascript·ecmascript
编程大师哥2 小时前
Java web
java·开发语言·前端
书中藏着宇宙2 小时前
CornerNet的续篇(数据处理与训练)
开发语言·python
你怎么知道我是队长2 小时前
C语言---预处理器
c语言·开发语言·chrome
JAVA+C语言2 小时前
Java ThreadLocal 的原理
java·开发语言·python
穿小甲的技术笔记2 小时前
C++ static_cast 解析:零成本的类型安全转换
c++
精神小伙就是猛2 小时前
C# Task/ThreadPool async/await对比Golang GMP
开发语言·golang·c#