有时我们无法提前预知应该向函数传递几个实参。例如,我们想要编写代码输出程序产生的错误信息,此时最好用同一个函数实现该项功能,以便对所有错误的处理能够整齐划一。然而,错误信息的种类不同,所以调用错误输出函数时传递的实参也各不相同。编写变长参数的函数有几种方法:

C方法:va_list

stdarg.h头文件提供了C语言中变长参数的功能,但用法较为复杂。

要使用C方法变长参数,首先在函数声明里用...声明列表(必须在最后,且不能只有...):

int f(int n, ...);//合法
int g(int n, ... int m);//不合法
int h(...)//不合法

在调用的时候,我们就可以传入变长参数:f(3, 400, 500, 600);

然后在函数定义里,声明va_list类型的变量(随意取名):va_list myargs;

然后使用va_start将变长参数拷贝进来,(第二个参数通常是最后一个具名参数,代表长度):va_start(myargs, n);

接下来,我们可以不断调用va_arg获取下一个参数:

va_arg(myargs, int); //获取下一个int
va_arg(myargs, double); //获取下一个double

最后使用va_end清理结尾:va_end(myarg)

完整代码:

#include <stdarg.h>
int f(int n,...) {
va_list myarg;
va_start(myarg, 10); int ans(0);
for (int i(0);i<n;i++) ans += va_arg(myarg, int); //仅支持int
va_end(myarg);
return ans;
}

从中就可以看出,这个变长参数列表是直接以获取二进制流,没有包含类型信息,所以做不到处理各种类型。上面的函数只能在传入32位数时工作,传入64位数或者浮点数就会截断成两个数再相加。所以C中的scanf printf两个函数必须传入格式化字符串指定类型,并且不能写错,不然就会有错误的结果。

C++方法:使用initializer_list

initializer_list传参则要简单的多,首先这个东西自己就是一种模板容器,使用方法和vector类似。

不过,它的元素值只作为参数传递,通常不能修改。有了这个东西,我们就可以把多个参数打包成一个参数传入.

#include <initializer_list>

int max(std::initializer_list<int> li) {
int ans = 1 << 31;
for (auto x: li) ans = ans>x ? ans : x;
return ans;
} main() {
printf("%d\n", max({1, 2, 3})); //加上大括号,作为整体调用
}

优点:很灵活,易于理解。

缺点:

  • 只支持单一类型
  • 只能读,不能写
  • 要额外加括号,形式不统一

其实C++11以后标准库就有了变长max了,就是使用的这种括号形式。

C++方法:使用可变参数模板

一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包function parameterpacket),表示零个或多个函数参数。

用下面的方式声明一个可变参数模板:

template<class T, class... Args> //Args:“模板参数包”
void foo(const T &t, const Args&... test); //test:“一个参数包(含有0或多个参数)” foo(i, s, 42, d); //包中有三个参数
foo(s, 42, "hi"); //包中有两个参数
foo(d, s); //包中有一个参数
foo("hi"); //空包

如此,编译器会在编译器就实例化所有出现过的版本:

void foo(const int&,const string&,const int&,const double& );
void foo(const string&,const int&, const char [3]&);
void foo(const double&, const string&);
void foo(const char[3] &);

模板通常采用递归方式展开:

template<class T>
void print(T &t) {cout << t <<'\n';} template<class T, class... Args>
void print(T &t, Args&... rest) {
cout << t << ' ';
print(rest...); // 打印剩余参数,注意省略号必须有
}

在调用print时,首先调用变长版,然后递归调用print,直到剩下最后一个参数。由于Args也能使用零参数,就与第一个函数形成重载,此时会重载第一个更特化函数,结束递归。所以必须有第一个函数,否则会无限递归。

优点:

  • 功能最强大,这里只列出入门知识,模板参数包还有非常多的用法。
  • 编译时展开,效率高。

缺点:

  • 语法复杂繁琐,语义不清晰。省略号的位置我现在都记不清只能现查。更别说进阶语法。
  • 更多是一种炫技。实际应用只需要最基本形式的变长模板就够了。

C++ 实现可变参数的三个方法的更多相关文章

  1. struts2 Action 接收参数的三种方法

    刚学Struts2 时 大家可能遇到过很多问题,这里我讲一下Action 接收参数的三种方法,我曾经在这上面摔过一回.所以要警醒一下自己..... 第一种:Action里声明属性,样例:account ...

  2. SpringBoot接收前端参数的三种方法

    都是以前的笔记了,有时间就整理出来了,SpringBoot接收前端参数的三种方法,首先第一种代码: @RestController public class ControllerTest { //访问 ...

  3. 大数据学习day13------第三阶段----scala01-----函数式编程。scala以及IDEA的安装,变量的定义,条件表达式,for循环(守卫模式,推导式,可变参数以及三种遍历方式),方法定义,数组以及集合(可变和非可变),数组中常用的方法

    具体见第三阶段scala-day01中的文档(scala编程基础---基础语法)  1. 函数式编程(https://www.cnblogs.com/wchukai/p/5651185.html): ...

  4. 为maven插件设置参数的三种方法

    很多的maven插件都提供了丰富的可选参数,用户可以通过设置特定的参数值来控制maven插件的行为.设置插件参数的方法主要有三种,分别是命令行设置,POM文件中为插件设置全局参数和POM文件中为插件设 ...

  5. mfc 在VC的两个对话框类中传递参数的三种方法

    弄了好久,今天终于把在VC中的对话框类之间传递参数的问题解决了,很开心,记录如下: 1. 我所建立的工程是一个基于MFC对话框的应用程序,一共有三个对话框,第一个对话框为主对话框,所对应的类为CTMD ...

  6. Java多线程:向线程传递参数的三种方法

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果.但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别.由于线程 ...

  7. MFC接收命令行参数的三种方法

    方法一: CString sCmdline = ::GetCommandLine(); AfxMessageBox(sCmdline); 将获取到 "C:\test\app.exe -1 - ...

  8. Hive设置参数的三种方法

    Hive提供三种可以改变环境变量的方法,分别是:(1).修改${HIVE_HOME}/conf/hive-site.xml配置文件:(2).命令行参数:(3).在已经进入cli时进行参数声明.下面分别 ...

  9. setintervalue传参数的三种方法

    http://www.cnblogs.com/wkylin/archive/2012/09/07/2674911.html http://www.bhcode.net/article/20110822 ...

随机推荐

  1. 【爬虫+情感判定+Top10高频词+词云图】“谷爱凌”热门弹幕python舆情分析

    一.背景介绍 最近几天,谷爱凌在冬奥会赛场上夺得一枚宝贵的金牌,为中国队贡献了自己的荣誉! 针对此热门事件,我用Python的爬虫和情感分析技术,针对小破站的弹幕数据,分析了众网友弹幕的舆论导向,下面 ...

  2. NLTK基础学习

    学习视频来自:Youtube 学习文档来自:简书 NLTK:自然语言工具包 目的:将段落拆分为句子.拆分词语,识别这些词语的词性,高亮主题,帮助机器了解文本关于什么.这个小节将解决意见挖掘或情感分析的 ...

  3. EF Core 配置模型

    0 前言 本文的第一节,会概述配置模型的作用(对数据模型的补充描述). 第二节描述两种配置方式,即:数据注释(data annotations)和 Fluent API 方式. 第三节开始,主要是将常 ...

  4. CentOS删除编译安装的Python3

    编译安装Python3 # 下载 # wget https://www.python.org/ftp/python/3.6.4/Python-3.6.4.tar.xz wget http://mirr ...

  5. 安装Speedtest到CentOS(YUM)

    Speedtest是一个由Python语言编写的一个网络测速脚本,提供多个外网的测试站点,我们可以使用它测试网络的IO速度. 如果由于网络问题导致无法下载软件包,则可以通过安装模块到Python的方式 ...

  6. 运筹学笔记12 大M法

    引入M,其中M是一个充分大的正数.由此,目标函数也改变为zM. 如此构造的线性规划问题我们记作LPM,称之为辅助线性规划问题,也即在原来的线性规划问题的基础上,改造了其等式约束条件,然后有对目标函数施 ...

  7. 前端工作中用到的openlayers相关的公共方法

    /** * 获取地图上的图层对象 * @param map 地图对象 * @param layerName 实例化图层时的name * @return {null}*/ getLayerByLayer ...

  8. redis入门,linux安装

    1.下载 https://redis.io/download 2.上传到linux服务器tools文件夹下 3.解压到安装目录 tar -zxf /app/redis/redis-5.0.4.tar. ...

  9. CVPR2022 | 弱监督多标签分类中的损失问题

    前言 本文提出了一种新的弱监督多标签分类(WSML)方法,该方法拒绝或纠正大损失样本,以防止模型记忆有噪声的标签.由于没有繁重和复杂的组件,提出的方法在几个部分标签设置(包括Pascal VOC 20 ...

  10. centos 7安装gitlab及使用

    GitLab 概述: 是一个利用 Ruby on Rails 开发的开源应用程序,实现一个自托管的 Git 项目仓库,可通过 Web界面迚行访问公开的戒者私人项目.Ruby on Rails 是一个可 ...