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

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. .NET混合开发解决方案11 WebView2加载的网页中JS调用C#方法

    系列目录     [已更新最新开发文章,点击查看详细] WebView2控件应用详解系列博客 .NET桌面程序集成Web网页开发的十种解决方案 .NET混合开发解决方案1 WebView2简介 .NE ...

  2. linux项目部署(非前后端分离crm)

    参考博客 参考博客2---部署过程 导论:看参考博客1 WSGI是Web服务器网关接口.它是一个规范,描述了Web服务器如何与Web应用程序通信,以及Web应用程序如何链接在一起以处理一个请求,(接收 ...

  3. 124_Power Pivot&Power BI DAX优化计算最大连续次数

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 最大连续次数或者是最大连续子序列问题,在DAX中如何快速计算呢? 思路 1.N-1:按照INDEX错位 2.ST ...

  4. 走进Linux的世界

    开源软件Linux的起源: Linux--操作系统. Linux,1991年Linux之父林纳斯 本纳第克特 托瓦兹,创建了Linux操作系统内核(开源). Linux的发行版和RHCE 1.Linu ...

  5. 爷青回,canal 1.1.6来了,几个重要特性和bug修复

    刚刚在群里看到消息说,时隔一年,canal 1.1.6正式release了,赶紧上去看看有什么新特性. (居然才发布了6个小时,前排围观) 1.什么是canal canal [kə'næl],译意为水 ...

  6. 内网穿透frp教程 windows远程桌面连接

    鉴于ngrok不是特别好用 昨天又发现frp这个神器 在管理端还有图形界面十分友好 话不多说开始 准备工作 1.一个域名 2.一台服务器 一.域名与服务器 域名和服务器直接买就好咯 价格不高 一定要在 ...

  7. 跨域问题和使用 cookie 的限制

    前言 在我的文章 使用 cookie 的身份验证和授权 的最后,讲到了跨域问题,这篇文章就简单介绍跨域的相关知识,并说明在 net core 中怎么设置跨域. 使用的版本为 net6,并使用 Mini ...

  8. JMeter - 生成随机数/随机字符串/随机变量/随机日期

    1. Random - 随机数 1.1 作用 1.2 声明 1.3 例子 2. __RandomDate - 随机日期 2.1 作用 2.2 声明参数 2.3 例子 3. RandomString - ...

  9. Java JavaMail通过SMPT发送邮件

    概述 本讲讲述如何使用JavaMail工具包,通过SMPT协议,在Java代码中发送邮件. 一.JavaMail简介 JavaMail API提供了一个独立于平台且与协议无关的框架来构建邮件和消息传递 ...

  10. 在C#中使用正则表达式最简单的方式

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年5月11日. 在.NET中使用正则表达式与其他语言并无太大差异.最简单的使用就是使用Regex类型自带的静态方法. 注意:在.NET中 ...