C++11

目录

引言

C++11简介

1. {} 初始化

2. std::initializer_list

变量类型推导

1. auto

2. decltype

3. nullptr

右值引用和移动语义

1. 左值引用和右值引用

2. 左值引用与右值引用比较

3. 右值引用使用场景和意义

 完美转发

lambda表达式

1. C++98中的排序示例对比

2. lambda表达式的语法与示例

3. 函数对象与lambda表达式对比

可变参数模板 1. 基本概念与展开方式

2. STL容器中的 emplace 相关接口函数

新的类功能

1. 默认成员函数

2. 类成员变量初始化

3. 强制生成默认函数的关键字 default 和禁止生成默认函数的关键字 delete 

4. 继承和多态中的 final 与 override 关键字

包装器(function包装器)

1. 为什么需要function包装器

2. function包装器的使用

1. 解决模板效率问题

2.  std::function 包装不同类型可调用对象的示例

 std::bind 函数适配器

1.  std::bind 的基本概念

2.  std::bind 的使用示例

在实际问题中的应用——以逆波兰表达式求值为例1. 传统解法

2. 使用包装器的解法


引言

C++作为一门强大的编程语言,在不断地进化与发展。C++11标准的推出,为C++带来了诸多令人瞩目的新特性,极大地提升了语言的表达能力与开发效率。今天,就让我们一起深入探索C++11那些实用且有趣的特性。

C++11简介

C++11可谓是历经“十年磨一剑”。早在2003年,C++标准委员会提交了技术勘误表(TC1),C++03逐渐取代C++98成为新的标准,但它主要是对C++98的漏洞进行修复,核心部分改动不大 ,人们常将C++98/03合称为旧标准。而从C++0x(当时不确定最终版本号,所以用x代替)到C++11,才是真正意义上的重大革新,包含了约140个新特性以及对C++03中约600个缺陷的修正。

C++11在系统开发和库开发等领域表现出色,语法更加灵活、简化,稳定性和安全性也得到增强,成为了众多公司实际项目开发中的得力工具。例如,在一些高性能的游戏引擎开发中,C++11的新特性就被广泛应用,优化了代码结构与运行效率。

统一的列表初始化

1. {} 初始化

在C++98中,花括号{}就已被用于数组和结构体元素的初始化。比如:

C++11进一步扩大了大括号初始化列表的应用范围,使其适用于所有内置类型和用户自定义类型。而且使用时等号“=”可加可不加。示例如下:

甚至在创建对象时,也能利用列表初始化来调用构造函数:

2. std::initializer_list

 std::initializer_list 是C++11引入的一个很实用的类型。它一般作为构造函数的参数,让STL容器的初始化更加便捷。

我们可以通过以下代码查看它的类型:

在实际使用中,许多STL容器都增加了以 std::initializer_list 为参数的构造函数。比如:

#include <vector>
#include <list>
#include <map>
#include <string>

int main() {
    std::vector<int> v = { 1, 2, 3, 4 };
    std::list<int> lt = { 1, 2 };
    std::map<std::string, std::string> dict = { { "sort", "排序" }, { "insert", "插入" } };
    v = { 10, 20, 30 };
    return 0;
}

如果想要让自定义的 vector 也支持这种初始化方式,可以参考以下代码:

namespace bit {
    template<class T>
    class vector {
    public:
        typedef T* iterator;
        vector(std::initializer_list<T> l) {
            _start = new T[l.size()];
            _finish = _start + l.size();
            _endofstorage = _start + l.size();
            iterator vit = _start;
            typename std::initializer_list<T>::iterator lit = l.begin();
            while (lit != l.end()) {
                *vit++ = *lit++;
            }
        }
        vector<T>& operator=(std::initializer_list<T> l) {
            vector<T> tmp(l);
            std::swap(_start, tmp._start);
            std::swap(_finish, tmp._finish);
            std::swap(_endofstorage, tmp._endofstorage);
            return *this;
        }
    private:
        iterator _start;
        iterator _finish;
        iterator _endofstorage;
    };
}

变量类型推导

1. auto

在C++98中, auto 主要用于表示变量是局部自动存储类型,但在局部域中定义变量默认就是自动存储类型,所以 auto 意义不大。C++11对其进行了革新,使其用于自动类型推断。不过使用 auto 时必须进行显式初始化,编译器会根据初始化值来确定变量的类型。

示例代码如下:

#include <iostream>
#include <map>
#include <string>

int main() {
    int i = 10;
    auto p = &i; 
    auto pf = strcpy; 
    std::cout << typeid(p).name() << std::endl;
    std::cout << typeid(pf).name() << std::endl;
    std::map<std::string, std::string> dict = { { "sort", "排序" }, { "insert", "插入" } };
    auto it = dict.begin();
    return 0;
}

2. decltype

 decltype 关键字用于将变量的类型声明为表达式指定的类型。以下是一些使用场景:

#include <iostream>
template<class T1, class T2>
void F(T1 t1, T2 t2) {
    decltype(t1 * t2) ret;
    std::cout << typeid(ret).name() << std::endl;
}

int main() {
    const int x = 1;
    double y = 2.2;
    decltype(x * y) ret; 
    decltype(&x) p; 
    std::cout << typeid(ret).name() << std::endl;
    std::cout << typeid(p).name() << std::endl;
    F(1, 'a');
    return 0;
}

3. nullptr

在C++中,之前用 NULL 表示空指针,但 NULL 本质是被定义为字面量0,这可能会导致一些混淆,因为0既可以表示指针常量,又能表示整形常量。C++11引入了 nullptr 来专门表示空指针,让代码更加清晰和安全。

比如在一些条件判断中:

#include <iostream>
int main() {
    int* ptr = nullptr;
    if (ptr == nullptr) {
        std::cout << "ptr is nullptr" << std::endl;
    }
    return 0;
}

右值引用和移动语义

1. 左值引用和右值引用

在C++11之前,我们所学的引用其实是左值引用。左值是可以获取地址且能被赋值的表达式,比如变量、解引用的指针等;左值引用就是给左值取别名。

int main() {
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}

C++11新增的右值引用,是给右值取别名。右值是不能取地址的表达式,像字面常量、表达式返回值、函数返回值(非左值引用返回 )等。

int main() {
    double x = 1.1, y = 2.2;
    int&& r1 = 10;
    double&& r2 = x + y;
    // 编译报错,右值不能出现在赋值符号左边
    // 10 = 1;
    // x + y = 1;
    return 0;
}

2. 左值引用与右值引用比较

- 左值引用只能绑定左值,不过 const 左值引用既可以绑定左值,也能绑定右值。

int main() {
    int a = 10;
    int& ra1 = a; 
    // int& ra2 = 10; 编译失败,10是右值
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

- 右值引用只能绑定右值,但可以绑定 move 以后的左值。

int main() {
    int&& r1 = 10;
    int a = 10;
    // int&& r2 = a; 编译失败,a是左值
    int&& r3 = std::move(a);
    return 0;
}

3. 右值引用使用场景和意义

场景一:移动赋值

左值引用在作为函数参数和返回值时能提高效率,避免不必要的拷贝。但当函数返回一个局部变量时,由于局部变量出了函数作用域就会销毁,不能用左值引用返回,只能传值返回,这会导致拷贝构造开销。

例如:

这里的三次深拷贝代价太大。

解决方法:

这使得将亡值进行移动拷贝。--资源交换,垃圾也扔给将亡值(不道德哦盆友!!但我喜欢),这降低了许多成本。

场景二:移动拷贝

此处调用赋值拷贝进行两次深拷贝,只需重构拷贝构造:

此处调用赋值拷贝进行一次深拷贝,一次浅拷贝。当有移动构造函数时,在函数返回右值时,编译器会优先调用移动构造,避免深拷贝,提高效率。

场景三:自定义类型中深拷贝的类,必须传值返回的场景

 完美转发

在模板中, && 不代表右值引用,而是万能引用,它既能接收左值又能接收右值。但引用类型唯一作用是限制接收的类型,后续使用中都退化成左值。如果希望在传递过程中保持对象原生类型属性(左值或右值),就需要完美转发。
示例代码如下:

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {
    Fun(t);
}
int main() {
    PerfectForward(10); 
    int a;
    PerfectForward(a); 
    PerfectForward(std::move(a)); 
    const int b = 8;
    PerfectForward(b); 
    PerfectForward(std::move(b)); 
    return 0;
}

而如果void PerfectForward(int&& t)写死了,是右值引用,如果原文中实参是左值则&&则爹成&,如果是右值则不变。

而结果是:

首先我们怀疑是不是全折叠了?下图结果显示并不是。

这并不符合预期,这里涉及到右值不可变,但右值的引用可变,右值的引用例外开空间,把右值储存,属性为左值,这也是移动构造的基础。原因:

在C++中,当函数模板将参数传递给其他函数时,无论传入的是左值还是右值,参数都会变成左值,因为参数本身是具名变量。这就导致无法根据原始参数的类型选择最优的重载函数。为了解决这个问题,C++11引入了 std::forward  。

lambda表达式

1. C++98中的排序示例对比

在C++98中,使用 std::sort 对数组排序,如果是内置类型,默认按小于比较升序排列,如需降序,需改变比较规则,引入 greater<int>() 。对于自定义类型 Goods ,还需用户定义比较规则类,如 ComparePriceLess 和 ComparePriceGreater 。

#include <algorithm>
#include <functional>
int main() {
    int array[] = { 4,1,8,5,3,7,0,9,2,6 };
    // 默认按小于比较,排出来结果是升序
    std::sort(array, array + sizeof(array) / sizeof(array[0]));
    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), std::greater<int>());
    return 0;
}
struct Goods {
    std::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& g1, const Goods& g2) {
        return g1._price < g2._price;
    }
};
struct ComparePriceGreater {
    bool operator()(const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;
    }
};

而在C++11中,lambda表达式让这一过程更加简洁。

2. lambda表达式的语法与示例

lambda表达式书写格式为: [capture-list] (parameters) mutable -> return-type { statement } 
 
- 各部分说明:
-  [capture-list] :捕捉列表,用于捕捉上下文中的变量供lambda函数使用,捕捉方式有值传递和引用传递等。例如 [=] 表示值传递捕捉所有变量, [&] 表示引用传递捕捉所有变量 。
-  (parameters) :参数列表,与普通函数参数列表一致,可省略。
-  mutable :默认lambda函数是常量函数,使用 mutable 可取消其常量性,此时参数列表不可省略。
-  ->return-type :返回值类型,可省略,由编译器推导。
-  {statement} :函数体,可使用参数和捕获的变量。
- 示例:

int main() {
    // 最简单的lambda表达式,无实际意义
    []{};
    int a = 3, b = 4;
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    [=]{return a + 3; };
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;
    // 各部分都很完善的lambda函数
    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. 函数对象与lambda表达式对比

函数对象(仿函数)是在类中重载了 operator() 运算符的类对象。对比函数对象和lambda表达式:

class Rate {
public:
    Rate(double rate) : _rate(rate) {}
    double operator()(double money, int year) { return money * _rate * year; }
private:
    double _rate;
};
int main() {
    // 函数对象
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);
    // lambda
    auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
    r2(10000, 2);
    return 0;
}
 

从使用方式看,二者类似。函数对象将 rate 作为成员变量,在定义对象时初始化;lambda表达式通过捕获列表直接捕获该变量。底层编译器对lambda表达式的处理方式是按照函数对象的方式,定义lambda表达式会自动生成一个类,重载 operator() 。

4.lambda的底层逻辑

可变参数模板

1. 基本概念与展开方式

C++11的可变参数模板允许创建可以接受可变参数的函数模板和类模板,这是对C++98/03中固定数量模板参数的重大改进。
- 递归函数方式展开参数包:定义一个基本的可变参数函数模板 ShowList ,通过递归终止函数和展开函数来处理参数包。

// Args是一个模板参数包,args是一个函数形参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template <class...Args>
void ShowList(Args... args) {}
// 递归终止函数
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;
}

- 逗号表达式展开参数包:这种方式不需要递归终止函数,直接在 expand 函数体中利用逗号表达式展开参数包。

template <class T>
void PrintArg(T t) {
    cout << t << " ";
}
// 展开函数
template <class...Args>
void ShowList(Args... args) {
    int arr[] = { (PrintArg(args), 0)... };
    cout << endl;
}

//简化
template <class T>
int PrintArg(T t) {
    cout << t << " ";
    return 0;
}
// 展开函数
template <class...Args>
void ShowList(Args... args) {
    int arr[] = { PrintArg(args)... };
    cout << endl;
}

int main() {
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', std::string("sort"));
    return 0;
}

2. STL容器中的 emplace 相关接口函数

 emplace 系列接口支持模板的可变参数,并且是万能引用。以 std::vector 和 std::list 的 emplace_back 为例:

template <class... Args>
void emplace_back (Args&&... args);

它的优势在于可以在容器内部直接构造对象,避免了临时对象的拷贝或移动。比如在 std::list 中插入 std::pair<int, char> 对象:

 
int main() {
    std::list<std::pair<int, char>> mylist;
    // emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
    mylist.emplace_back(10, 'a');
    mylist.emplace_back(20, 'b');
    mylist.emplace_back(std::make_pair(30, 'c'));
    mylist.emplace_back(std::make_pair(40, 'd'));
    mylist.push_back({ 50, 'e' });
    for (auto e : mylist) {
        cout << e.first << ":" << e.second << endl;
    }
    return 0;
}

新的类功能

1. 默认成员函数

在C++中,类有6个默认成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值重载、取地址重载、 const 取地址重载。C++11新增移动构造函数和移动赋值运算符重载。
关于移动构造函数和移动赋值运算符重载,有以下规则:
 
- 若未实现移动构造函数,且未实现析构函数、拷贝构造、拷贝赋值重载中的任何一个,编译器会自动生成默认移动构造函数。对于内置类型成员执行逐成员字节拷贝,自定义类型成员看其是否实现移动构造,实现则调用移动构造,未实现则调用拷贝构造。
- 若未实现移动赋值重载函数,且未实现析构函数、拷贝构造、拷贝赋值重载中的任何一个,编译器会自动生成默认移动赋值函数。内置类型成员逐成员字节拷贝,自定义类型成员看其是否实现移动赋值,实现则调用移动赋值,未实现则调用拷贝赋值。
- 若提供了移动构造或移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
示例代码如下:
 

class Person {
public:
    Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
    // 未实现移动构造和移动赋值相关函数
    /*Person(const Person& p)
        :_name(p._name)
       ,_age(p._age)
    {}*/
    /*Person& operator=(const Person& p) {
        if (this != &p) {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }*/
    /*~Person() {}*/
private:
    bit::string _name;
    int _age;
};
int main() {
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);
    Person s4;
    s4 = std::move(s2);
    return 0;
}

2. 类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。

3. 强制生成默认函数的关键字 default 和禁止生成默认函数的关键字 delete 


 -  default :当我们希望使用某个默认函数,但由于自定义了其他函数导致编译器不自动生成时,可使用 default 关键字显式指定生成。比如提供了拷贝构造,想让编译器生成移动构造,可如下定义:

class Person {
public:
    Person(const char* name = "", int age = 0) 
        : _name(name)
        , _age(age) 
    {}
    Person(const Person& p)
        :_name(p._name)
       ,_age(p._age) {}
    Person(Person&& p) = default;
private:
    bit::string _name;
    int _age;
};

-  delete :在C++11中,若想限制某些默认函数生成,只需在函数声明后加上 =delete ,该函数称为删除函数。例如,禁止拷贝构造函数:

class Person {
public:
    Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
    Person(const Person& p) = delete;
private:
    bit::string _name;
    int _age;
};

4. 继承和多态中的 final 与 override 关键字

 final 用于修饰虚函数,表示该虚函数不能被派生类重写;修饰类,表示该类不能被继承。 override 用于显式表明派生类中的函数是重写基类的虚函数,若基类不存在对应的虚函数,编译器会报错,从而避免一些隐藏的错误。这部分在继承和多态章节有详细讲解。

包装器(function包装器)

1. 为什么需要function包装器

在C++中,我们常常会遇到需要处理多种可调用类型的情况,比如函数名、函数指针、函数对象(仿函数对象)以及lambda表达式对象等。例如在下面的函数模板 useF 中:

在这个例子中, useF 函数模板实例化了三份,因为编译器需要为不同类型的可调用对象分别生成代码。当可调用类型非常丰富时,这可能会导致模板的效率低下。而 function 包装器可以很好地解决这个问题。

2. function包装器的使用

 std::function 是定义在 <functional> 头文件中的一个类模板,它本质是一个包装器,也叫适配器。其类模板原型如下:

template <class T> function; // undefined

template <class Ret, class... Args>

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

其中 Ret 是被调用函数的返回类型, Args... 是被调用函数的形参。

使用示例如下:

 std::function 可以统一包装不同类型的可调用对象,使得代码更加简洁和灵活,提高了代码的可维护性和复用性。
包装器(function包装器)的进一步探讨

1. 解决模板效率问题

在之前提到的 useF 函数模板示例中,当面对多种可调用类型时,会导致模板实例化多份,影响效率。而 std::function 包装器可以统一处理这些可调用类型。
以如下代码为例:

#include <functional>
template<class F, class T>
T useF(F f, T x) {
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}
double f(double i) {
    return i / 2;
}
struct Functor {
    double operator()(double d) {
        return d / 3;
    }
};
int main() {
    // 函数名
    std::function<double(double)> func1 = f;
    cout << useF(func1, 11.11) << endl;
    // 函数对象
    std::function<double(double)> func2 = Functor();
    cout << useF(func2, 11.11) << endl;
    // lambda表达式
    std::function<double(double)> func3 = [](double d)->double{ return d / 4; };
    cout << useF(func3, 11.11) << endl;
    return 0;
}

通过 std::function 将不同类型的可调用对象统一包装, useF 函数模板在处理这些对象时,不再需要为每种类型单独实例化,提高了代码的效率和简洁性。

2.  std::function 包装不同类型可调用对象的示例

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(1, 2) << endl;
    // lambda表达式
    std::function<int(int, int)> func3 = [](const int a, const int b) { return a + b; };
    cout << func3(1, 2) << endl;
    // 类的静态成员函数
    std::function<int(int, int)> func4 = &Plus::plusi;
    cout << func4(1, 2) << endl;
    // 类的非静态成员函数
    std::function<double(Plus, double, double)> func5 = &Plus::plusd;
    cout << func5(Plus(), 1.1, 2.2) << endl;
    return 0;
}

这里展示了 std::function 包装函数名、函数对象、lambda表达式、类的静态成员函数以及类的非静态成员函数的方式,体现了其强大的通用性和灵活性。

 std::bind 函数适配器

1.  std::bind 的基本概念


 std::bind 定义在 <functional> 头文件中,是一个函数模板,它如同一个函数包装器(适配器)。它接受一个可调用对象(如函数、函数对象、lambda表达式等),生成一个新的可调用对象来“适应”原对象的参数列表。
一般调用形式为: auto newCallable = bind(callable, arg_list) 。其中, newCallable 本身是一个可调用对象, arg_list 是一个逗号分隔的参数列表,对应给定的 callable 的参数。 arg_list 中的参数可能包含形如 _n 的名字( n 是一个整数),这些参数是“占位符”,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的“位置”。数值 n 表示生成的可调用对象中参数的位置, _1 为 newCallable 的第一个参数, _2 为第二个参数,以此类推。


2.  std::bind 的使用示例

#include <functional>
int Plus(int a, int b) {
    return a + b;
}
class Sub {
public:
    int sub(int a, int b) {
        return a - b;
    }
};
int main() {
    // 表示绑定函数Plus,参数分别由调用func1的第一、二个参数指定
    std::function<int(int, int)> func1 = std::bind(Plus, std::placeholders::_1, std::placeholders::_2);
    // auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
    // func2的类型为function<void(int, int, int)> 与func1类型一样
    // 表示绑定函数Plus的第一、二为:1、2
    auto func2 = std::bind(Plus, 1, 2);
    cout << func1(1, 2) << endl;
    cout << func2() << endl;
    Sub s;
    // 绑定成员函数
    std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, std::placeholders::_1, std::placeholders::_2);
    // 参数调换顺序
    std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, std::placeholders::_2, std::placeholders::_1);
    cout << func3(1, 2) << endl;
    cout << func4(1, 2) << endl;
    return 0;
}
 

在这个示例中,通过 std::bind 对函数 Plus 和类 Sub 的成员函数 sub 进行了不同方式的绑定。可以指定固定参数,也可以使用占位符灵活地调整参数顺序,展示了 std::bind 在参数绑定和调整方面的强大功能。

在实际问题中的应用——以逆波兰表达式求值为例
1. 传统解法


对于逆波兰表达式求值问题(LeetCode相关题目),传统解法通常使用栈来处理。代码如下:

class Solution {
public:
    int evalRPN(std::vector<std::string>& tokens) {
        std::stack<int> st;
        for (auto& str : tokens) {
            if (str == "+" || str == "-" || str == "*" || str == "/") {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch (str[0]) {
                case '+':
                    st.push(left + right);
                    break;
                case '-':
                    st.push(left - right);
                    break;
                case '*':
                    st.push(left * right);
                    break;
                case '/':
                    st.push(left / right);
                    break;
                }
            }
            else {
                st.push(std::stoi(str));
            }
        }
        return st.top();
    }
};
 

这种解法通过遍历逆波兰表达式的字符串数组,利用栈来存储操作数,遇到运算符时进行相应的运算。

2. 使用包装器的解法

利用 std::function 和 std::bind 等包装器机制,可以让代码更加简洁和灵活。

class Solution {
public:
    int evalRPN(std::vector<std::string>& tokens) {
        std::stack<int> st;
        std::map<std::string, std::function<int(int, int)>> opFuncMap = {
            { "+", [](int i, int j){return i + j; } },
            { "-", [](int i, int j){return i - j; } },
            { "*", [](int i, int j){return i * j; } },
            { "/", [](int i, int j){return i / j; } }
        };
        for (auto& str : tokens) {
            if (opFuncMap.find(str) != opFuncMap.end()) {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(opFuncMap[str](left, right));
            }
            else {
                st.push(std::stoi(str));
            }
        }
        return st.top();
    }
};

在这个解法中,使用 std::map 结合 std::function 将运算符与对应的运算逻辑(以lambda表达式表示)进行映射。在遍历过程中,当遇到运算符时,直接从 map 中获取对应的可调用对象进行运算,使得代码逻辑更加清晰,同时也体现了C++11包装器特性在实际问题中的应用优势。
 
通过对 std::function 和 std::bind 等包装器相关内容的深入探讨以及在实际问题中的应用展示,我们能更全面地掌握C++11在处理可调用对象方面的强大功能,这些特性为我们编写高效、简洁、灵活的代码提供了有力支持。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值