移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.C++11(2)

1.可变模板参数

C++11 引入的可变模板参数(variadic templates)使得模板参数的数量可以是任意多个,极大地提升了 C++ 的模板编程能力。以下是 C++11 中可变模板参数的详细总结:

1. 基本语法

在模板参数列表中,通过 ...(三个点)来表示可变参数。常见的使用形式如下:

template <typename... Args>
void func(Args... args);

在这里,Args... 表示一个可变数量的模板参数,args... 是与之对应的函数参数。

2. 可变模板参数的展开

可变模板参数的展开是指将模板参数包展开成实际的参数。展开的方式有两种:

  • 直接展开
  • 递归展开
直接展开

直接展开通常用于函数调用或表达式中。可以通过递归展开实现对每个参数的单独处理。

例如,可以直接在参数包上应用表达式:

template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << '\n'; // C++17 的折叠表达式
}

在这里插入图片描述

可使用sizeof求出传了多少个参数

上面的例子展示了一个 C++17 的特性:折叠表达式。C++11 中,可以用逗号表达式和递归结合实现类似效果。

递归展开

在 C++11 中实现递归展开通常是通过递归调用函数模板实现。例如:

void print() { }

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << " ";
    print(rest...); // 递归展开
}

这段代码中,第一个参数 first 被单独处理,然后剩下的参数 rest... 递归调用 print,直到参数包为空。

在这里插入图片描述

使用这三个函数可以输出各个参数

3. 参数包展开的用途

可变模板参数的引入,让函数、类和类型定义都可以处理任意数量的参数,从而大大增强了 C++ 的泛型编程能力。

(1)函数模板中的应用

在可变参数模板中,可以定义接收任意数量参数的函数。例如,实现一个通用的 print 函数:

template <typename... Args>
void print(Args... args) {
    int unpack[] = { (std::cout << args << " ", 0)... }; // 利用逗号表达式展开
    std::cout << std::endl;
}

通过展开参数包,print 可以接受任意数量的参数。

(2)类模板中的应用

可变参数模板还可以用于定义类模板,例如实现一个元组(Tuple)类型:

template <typename... Types>
class Tuple {
    // 可以定义包含任意类型的元组
};

在此基础上,可以定义不同类型和数量的模板参数,以实现功能更为复杂的泛型数据结构。

(3)转发参数包

C++11 中引入了 std::forward,可以和可变模板参数结合使用,实现完美转发:

template <typename... Args>
void wrapper(Args&&... args) {
    func(std::forward<Args>(args)...); // 完美转发
}

这种用法让我们可以编写包装函数并将参数原样传递给其他函数。

4. 常见的使用模式

可变模板参数在泛型编程中具有以下常见使用模式:

(1)递归终止条件

在递归处理可变模板参数时,通常需要定义一个基函数(或基模板)作为递归终止条件:

void process() { } // 递归终止条件

template <typename T, typename... Args>
void process(T first, Args... rest) {
    std::cout << first << " ";
    process(rest...);
}
(2)逗号表达式展开

逗号表达式是一种展开参数包的技巧,通过将表达式放入数组初始化列表中展开参数包:

template <typename... Args>
void print_all(Args... args) {
    int unpack[] = { (std::cout << args << " ", 0)... };
    std::cout << std::endl;
}
template <class T>
void PrintArg(T t)
{
 cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
 int arr[] = { (PrintArg(args), 0)... };
 cout << endl;
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
 return 0;
}

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… )最终会创建一个 ***元素值都为0*** 的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)
打印出参数

5. push_back和 emplace_back 的比较

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

6. 可变模板参数的限制

虽然可变模板参数提供了灵活性,但它也有一些限制:

  • 参数包的展开顺序是从左至右的,这在某些情况下需要特别注意。
  • 对于参数包的展开,C++11 中没有提供直接的折叠表达式(这是 C++17 才引入的特性),因此在 C++11 中实现复杂的参数展开可能需要使用更多的递归和逗号表达式。

2.lambda 表达式

1.表示形式

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type {
statement }

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

2.C++98中的一个例子

#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]), greater<int>());
return 0;
}

如果待排序元素为 自定义类型 ,需要用户定义排序时的比较规则:

struct Goods
{
 string _name;  // 名字
 double _price; // 价格
 int _evaluate; // 评价
 Goods(const char* str, double price, int evaluate)
 :_name(str)
 , _price(price)
 , _evaluate(evaluate)
 {}
};

struct ComparePriceLess//仿函数<
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price < gr._price;
 }
};


struct ComparePriceGreater//仿函数>
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price > gr._price;
 }
};

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

lambda表达式优化的结果:

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; });
}

可以看出lambda表达式实际是一个 匿名函数

auto fun1 = [](int c){b = a + c; };//最简单的函数调用,注意:fun1是一个函数
fun1(10);

3.捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意

  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:
    [=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
    [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

3.捕捉列表不允许变量重复传递,否则就会导致编译错误。
比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

4.在块作用域以外的lambda函数捕捉列表必须为空
5.lambda表达式之间 不能相互赋值

1.举例说明

在这里插入图片描述
1.这是用lambda表达式写的交换函数,可以进行一定的改变

在这里插入图片描述
2.[var]:表示值传递方式捕捉变量var
(但是这样是无法调用的,因为x,y用【】捕捉后无法改变,默认const修饰)需要加mutable修饰才能改变

在这里插入图片描述
3.特别注意:捕捉到的x和y和原本的x和y不是同一个,因为是 传值传递

在这里插入图片描述

这次就是 引用传递 了,【】只能捕捉对象,不能捕捉对象的地址,要得到地址还得在()中传参

3.function包装器

要包头文件 《functional》

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?

在这里插入图片描述
function可以将三种函数对象包装起来(分别是 函数指针仿函数,和 lambda表达式
在这里插入图片描述
这么做可以利用map中重载的[]的功能,找到对应的函数对象,并进行调用

举例说明:
逆波兰表达式求值

在这里插入图片描述

原先解法:

class Solution {
public:
    int operation(int a, int b, string s) {

        if (s[0] == '+')

            return b + a;

        if (s[0] == '-')
            return b - a;

        if (s[0] == '*')
            return b * a;

        if (s[0] == '/')
            return b / a;
        return 1; // 记得写一个return
                  // 1,因为系统判定如果if都不走,那么就没有返回值
    }

    int evalRPN(vector<string>& tokens) {
        int num = 0;
        int result = 0;
        string j;
        int a;
        int b;
        int end;
        int k;
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" ||
                tokens[i] == "/") {
                brr.push(tokens[i]);
                a = arr.top();
                arr.pop();
                b = arr.top();
                arr.pop();
                end = operation(a, b, brr.top());
                arr.push(end);
                brr.pop();
            } else {
                j = tokens[i];
                if (j[0] == '-') {
                    for (int i = 1; i < j.size(); i++) {
                        num = num * 10 + (j[i] - '0');
                    }
                    arr.push(-num);
                    num = 0;
                } else {
                    for (int i = 0; i < j.size(); i++) {
                        num = num * 10 + (j[i] - '0');
                    }
                    arr.push(num);
                    num = 0;
                }
            }
        }
        end = arr.top();
        return end;
    }

private:
    stack<int> arr;
    stack<string> brr;
};

functional做法:

class Solution {
public:
int evalRPN(vector<string>& tokens) {
  stack<int> st;
     map<string, 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(stoi(str));
         }
     }
     return st.top();
}
};

在这里插入图片描述

评论 54
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值