首页 > 编程语言 >移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.C++11(2)

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

时间:2024-11-30 18:33:42浏览次数:7  
标签:11 15 函数 int 移情别恋 参数 表达式 return 模板

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

在这里插入图片描述

标签:11,15,函数,int,移情别恋,参数,表达式,return,模板
From: https://blog.csdn.net/2301_80374809/article/details/143760724

相关文章

  • mysql基础11.28
    系统数据库information-schema信息数据库mysql核心数据库performance_schema存储数据库服务器的性能参数sakila存放数据库样本sys系统的元数据信息world提供关于城市,国家和语言的相关信息创建数据库:createdatabase或者createschemacharacterset:指定数据库的字符......
  • 基本的select语句(11.30)
    SQL的分类:DDL:数据定义语言。create、alter、drop、rename、truncate(清空)、DML:数据操作语言。insert、delete、update、selectDCL:数据控制语言。commit、rollback(撤销,回滚)、savepoint、grant、revokeSQL的规则:(必须遵守)SQL可以写在一行或者多行。为了提高可读性,各子句分行......
  • Unity类银河战士恶魔城学习总结(P153 Audio Manager音频)
    【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/本章节添加了循环播放的BGM,并且给武器攻击加上了音效音频管理器AudioManager.cs1.类的功能核心目标:管理游戏中的音频播放,包括:播放和停止音效......
  • Unity类银河战士恶魔城学习总结(P154 Audio Distance Limite音频距离限制)
    【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/本章节给骷髅战士加上了音频,并且限制了产生音效的范围可爱的骷髅战士AudioManager.cs      关键字段AudioManagerinstance实现单例模式,确......
  • 11.30
    本月读书笔记如下阅读笔记一:自我提升的重要性核心观点:本书强调程序员持续学习与自我提升的重要性。随着技术的不断变化与进步,只有保持学习的态度和行动,才能从一个初级开发者成长为技术专家。技术的提升固然重要,但软技能(如沟通能力、团队协作等)的培养同样不可忽视。实践建议:设......
  • debian 11 修改终端颜色
     在Linux中,可以通过修改终端的配置文件来修改用户名之后当前路径的颜色。在“~/.bashrc”文件中添加下面这句话:PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$' ls显示颜色最终文件:#~/.bashrc:execut......
  • 11.19
    创建表CAEATETABLE表名(字段名1数据类型1,字段名2数据类型2);删除表DROPTABLE表名修改表ALTERTABLE表名RENAMETO新的表名ALTERTABLE表名ADD列名数据类型ALTERTABLE表名MODIFY列名新数据类型DML给指定的列添加数据INSERTINTO表名(列名1,列名2,)VALUE......
  • Java基础语法练习题11
    进一步深入算法思维!习题:打出实心金字塔和空心金字塔本人代码心得:这题所用时间大概25分钟,总的来说就是找规律。publicclassStars{publicstaticvoidmain(String[]args){//实心金字塔intm=5;for(inti=1;i<=m;i++){......
  • 高性能计算-bgra2rgb转换-向量化优化(15)
    1.这是一个对图像通道bgra转换为rgb的示例程序。转换方式有普通写法、openmp-simd编译指导语句、neonintrinsic函数三种实现方式2.源码#include<stdio.h>#include<stdlib.h>#include<iostream>#include<arm_neon.h>#include<omp.h>usingnamespacestd;voidb......
  • 11.28
    1.将123的第三位取反并打印取反后的值 程序代码及结果显示:(123=0b01111011)注:“^”表示按位异或,属于双目运算符,对应位上相同为0,不同为1。 拓展:将123的第三位取反并打印取反后的值01111011程序代码及结果显示:2.定义一个整形变量:intnum=123;将该数据组成倒序数......