C语言和C++中不定参数解析

news2024/11/17 1:30:27

在这里插入图片描述

你所看到的惊艳,都曾被平庸历练

文章目录

  • 简介
  • C语言不定参数
    • 不定参宏函数
  • C++不定参数
    • 方法一:递归展开
    • 方式二:折叠表达式
    • 方法三:逗号表达式
  • 总结


简介

相信学过c/c++的佬们都知道,如果一个函数将自己的参数确定好后,要想使用该函数就必须传递合适的数量以及类型的参数,然而这样的函数非常具有局限性。例如有一个求和函数,我们并不知道我们要求几个数的和,这就导致在确定函数参数时无从下手,而这时候不定参数就派上了用场。

不定参数是指在函数定义时,允许接收可变数量的参数。这种机制允许函数在调用时接受不同数量的实际参数,使得函数更加灵活,能够适应不同的使用场景。例如C语言中的printf函数,scanf函数等。在C和C++中,不定参数的实现方式主要依赖于标准库中的stdarg.h头文件。

在这里插入图片描述

在使用不定参数后,sum函数可以接受不同数量的参数,并计算它们的总和。这种灵活性使得函数能够适应不同的求和需求,而不需要为每种情况都定义一个单独的函数。这样的设计使代码更简洁,更容易扩展,同时提供了更大的通用性。

不定参数的优势:

优势说明
灵活性不定参数函数允许在调用函数时传递不同数量的参数,提高了函数的灵活性和通用性。
适应变化在处理不确定数量的参数或者未来可能会新增参数的情况下,不定参数函数能够更好地适应变化。
简化代码使用不定参数可以简化函数定义,避免定义多个函数处理不同数量的参数情况。
可读性在某些情况下,使用不定参数可以使函数调用更加清晰和可读,特别是当参数的数量可能变化时。

C语言不定参数

在C语言中,不定参数是通过stdarg.h头文件提供的一组宏来实现的。这些宏主要包括va_listva_startva_argva_end

在这里插入图片描述

  1. va_list va_list是一个类型,用于声明一个变量,该变量将用于存储不定参数的信息。
    在这里插入图片描述

  2. va_start va_start宏用于初始化va_list,使其指向不定参数的起始位置。它接受两个参数,第一个是va_list对象,第二个是最后一个确定的参数,用于确定不定参数的起始位置。
    在这里插入图片描述

    void function_with_varargs(int fixed_arg, ...) {
        va_list args;
        va_start(args, fixed_arg); // 现在args指向第一个可变参数
        // ...
        va_end(args);
    }
    
  3. va_arg va_arg宏用于获取当前位置的值,并且在每次调用后,会将指针移动到下一个可变参数的位置。它接受两个参数,第一个是va_list对象,第二个是要获取的参数的类型。
    在这里插入图片描述

    int value = va_arg(args, int);
    
  4. va_end va_end宏用于清理va_list对象,确保在使用完不定参数后正确释放资源。
    在这里插入图片描述

    va_end(args);
    

如下是一个在C语言中使用不定参数的求和函数:

#include <stdio.h>
#include <stdarg.h>

int sum(int n, ...) {
    va_list args;   // 定义用于存储不定参数信息的变量
    va_start(args, n);   // 初始化 va_list,使其指向不定参数列表的起始位置
    int ret = 0;

    for (int i = 0; i < n; i++) {
        int tmp = va_arg(args, int);   // 获取当前位置的参数值
        ret += tmp;
    }

    va_end(args);   // 清理 va_list,确保资源正确释放
    return ret;
}

int main() {
    int ret = sum(5, 10, 24, 10, 34, 0);   // 调用 sum 函数,传递5个整数
    printf("%d\n", ret);   // 打印计算结果
    return 0;
}

sum 函数中,va_start 初始化了 va_list,并使其指向传递给函数的不定参数的起始位置。然后,通过使用 va_arg 获取每个参数的值,并将它们累加到 ret 变量中。最后,通过 va_end 清理 va_list,确保资源得到正确释放。

main 函数中,调用了 sum 函数,并将 5 个整数作为参数传递给它,然后打印计算结果。

在这里插入图片描述

不定参宏函数

除此之外,还可以在C语言中使用宏来创建带有不定参数的宏函数。不定参数的宏函数通常使用__VA_ARGS__来表示可变数量的参数。

#include <stdio.h>
#include <stdarg.h>

// 定义 LOG 宏,使用 __VA_ARGS__ 来表示可变参数
#define LOG(fmt, ...) printf("[%s : %d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

int main() {
    // 使用 LOG 宏输出日志信息,自动包含文件名和行号
    LOG("登陆失败");
    return 0;
}

在这个例子中,LOG 宏使用了 __VA_ARGS__ 来表示可变参数。在调用 LOG 时,传递的字符串会被输出,并自动包含文件名和行号。这样,通过这个宏,您可以轻松地在代码中输出带有文件名和行号的日志信息。

需要注意的是,在 ##__VA_ARGS__ 中的 ## 用于处理不带参数时的情况,以防止在宏展开时产生语法错误。在可变参数为空的情况下,##__VA_ARGS__ 将会被移除,避免了末尾逗号的问题。

在这里插入图片描述

其中 filename.c 是当前文件名,7 是当前行号,登陆失败 是通过 LOG 宏传递的字符串。不定参数宏函数的使用可以帮助简化代码,但也需要小心使用,确保在宏展开时没有意外的副作用。

C++不定参数

C++中的不定参函数通常使用可变参数模板来实现,这使得函数能够接受不定数量和类型的参数。使用可变参数模板的不定参函数是 C++11 引入的特性。如果你使用的是更早版本的 C++,可能需要使用 <cstdarg> 头文件中的宏来处理不定参数。

基本模板:

template<typename ...Args>
void print(Args... args) {}

又由于无法直接获取参数包args中的每个参数,所以只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以只能想一些特殊的办法来获取参数包的值。

方法一:递归展开

递归展开可变参数包是使用可变参数模板的一种常见技术,通常用于在编译时展开不定数量的参数。下面是一个递归展开可变参数包的示例:

#include <iostream>

void print() {
    // 终止条件,处理空参数包
}

template <typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << "Cur : " << first << " args size : " << sizeof...(args) << std::endl;
    print(args...);  // 递归调用,展开参数包
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

这个程序定义了一个函数模板 print,该函数接受任意数量和类型的参数,并递归展开这些参数。

  • print 函数有两个版本:一个是终止条件版本(空参数包时调用),另一个是递归版本。

  • 在递归版本中,首先打印第一个参数 first 和参数包的大小(使用 sizeof...(args)),然后通过 print(args...) 递归地调用自己,展开参数包。

  • main 函数中,你调用了 print 函数,传递了不同类型的参数。

这样,当你运行程序时,print 函数将递归展开参数包,打印每个参数的值和参数包的大小。

在这里插入图片描述

这样,通过递归调用,编译器会在编译时展开参数包,使得函数能够处理不定数量和类型的参数。这是一种在编译时进行模板展开的方式,相比运行时的展开更加高效。

方式二:折叠表达式

除了递归展开参数包的方式,C++17 引入了折叠表达式(fold expressions)的特性,这是一种更简洁的方式来展开参数包。折叠表达式在处理可变参数包时可以提供更紧凑的语法。如下代码:

#include <iostream>

// 使用折叠表达式展开参数包
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // 折叠表达式展开参数包
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

在这里插入图片描述

在这个例子中,(std::cout << ... << args) 是一个折叠表达式,用于展开参数包 args。这个表达式实际上将所有参数连接到 std::cout 流中,然后通过 << std::endl 添加换行符。

当需要使用折叠表达式求和时,也可以如下代码:

#include <iostream>

template<class ...Args>
int sum(Args... args) {
    return (args + ...);
}

int main() {
    std::cout << sum(25, 34, 59) << std::endl;
    return 0;
}

代码中使用了 C++17 中引入的折叠表达式 (args + ...) 来计算参数包中所有参数的和。在这个例子中,折叠表达式展开了 args 中的参数,并使用 + 运算符对它们求和。

在这里插入图片描述

使用折叠表达式的好处是语法更为紧凑,而且可以在一个表达式中完成参数包的展开。这是在 C++17 及更高版本中的一种更现代的方式。

总体而言,递归展开和折叠表达式是两种常见的方式,具体选择取决于个人或团队的偏好以及编译器的支持版本。在 C++17 及更高版本中,折叠表达式通常被认为是更好的选择。

方法三:逗号表达式

除了递归展开和折叠表达式,还有一种使用逗号表达式的方式展开参数包。这种方式通常结合逗号运算符 , 和初始化列表来实现。

以下是一个示例:

#include <iostream>

// 使用逗号表达式展开参数包
template <typename... Args>
void print(Args... args) {
    int dummy[] = {(std::cout << args << " ", 0)...};  // 逗号表达式展开参数包
    (void)dummy;  // 防止编译器警告未使用的变量
    std::cout << std::endl;
}

int main() {
    print("你干嘛", "哎呦", 100, 345);
    return 0;
}

在这个例子中,逗号表达式 (std::cout << args << " ", 0) 被用于展开参数包 args。逗号表达式的返回值是初始化列表中的最后一个表达式的值(在这里是 0),而初始化列表的目的是为了创建一个临时数组 dummy

这种方式相对较独特,通常在一些特殊情况下使用。一般而言,递归展开和折叠表达式更为常见和直观,但了解这种逗号表达式的方法也是有益的。选择展开参数包的方法通常取决于个人或团队的偏好、代码可读性和编译器支持。

总结

在C语言和C++中,可变参数是一种强大的特性,它允许函数接受可变数量和类型的参数。在C语言中,我们通常使用<stdarg.h>头文件提供的宏,如va_startva_arg、和va_end,来处理可变参数函数。而在C++中,我们有更灵活和类型安全的可变参数模板。总结起来,可变参数的使用有以下关键点:

  1. C语言中的可变参数:

    • 使用<stdarg.h>头文件提供的宏,例如va_startva_arg、和va_end
    • 缺点是类型不安全,需要手动管理参数的类型和数量。
  2. C++中的可变参数模板:

    • 引入了可变参数模板,允许在编译时处理可变数量和类型的参数。
    • 提高了类型安全性,无需手动管理参数的类型和数量。
    • 可以使用递归展开、折叠表达式、逗号表达式等多种方式展开可变参数包。
  3. 递归展开:

    • 使用递归函数模板,逐一处理参数包中的每个参数。
    • 通过终止条件和递归调用来展开参数包。
  4. 折叠表达式:

    • 在C++17及以后版本引入,提供了更紧凑的语法。
    • 使用(expression op ...)的形式,其中op可以是二元运算符。
  5. 逗号表达式:

    • 使用逗号表达式展开参数包时,返回值不能直接用于数组初始化,通常使用0作为占位符。
    • 可以通过逗号表达式在一行中执行多个操作。

在选择可变参数的处理方式时,应根据具体情况和编程需求来选择最合适的方法。C++中的可变参数模板提供了更强大、类型安全和灵活的处理方式,但对于特定情况,C语言中的可变参数也可能是一种合适的选择。最终,可变参数的使用应该以代码的清晰性、可读性和维护性为重要考虑因素。

最后,码文不易,如果文章对你有所帮助的话,不妨点上一个👍,感谢大佬的支持!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1410515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

泛微智能公文,为党政机关配备一位7*24小时公文助手,办文更高效

政务数字化智能水平明显提升是数字中国建设目标之一&#xff0c;平台化、智能化已成为政务协同办公的未来发展趋势&#xff0c;政府机关持续加强新技术全流程应用。 公文办理作为党政机关单位日常工作任务中重要的一环&#xff0c;泛微在全程数字化的公文管理体系中融入智能化技…

【GAMES101】Lecture 09 纹理贴图 点查询与范围查询 Mipmap

目录 纹理贴图 纹理放大-双线性插值 点采样纹理所带来的问题 Mipmap 各向异性过滤 纹理贴图 我们在之前的着色里面说过如何给物体上纹理&#xff0c;就是对于已经光栅化的屏幕点&#xff0c;就是每个像素的中心&#xff0c;去寻找对应纹理的映射位置的纹理颜色&#xff0…

SWMM模型INP解析

.INP文件解析 [OPTIONS]&#xff1a;SWMM软件运行前需要设置的参数 [RAINGAGES]雨水节点&#xff0c;核心设置雨水时间序列&#xff0c;可为INP内部数据也可为外部txt数据&#xff0c;TIMESERIES对应【TIMESERIES】模块&#xff0c;TS_1为时间序列名称 [TIMESERIES]&#xff0…

ELK之Grafana添加钉钉告警信息

Grafana版本如下&#xff1a; [roottest data]# grafana-server -v Version 8.4.6 (commit: c53173ff6, branch: HEAD)一、新建钉钉群&#xff0c;并自定义一个机器人 点击右上角设置 ------》 智能群助手 ------》 添加机器人 ------》右侧设置按钮 ------》点击自定义&…

记一次SPI机制导致的BUG定位【不支持:http://javax.xml.XMLConstants/property/accessExternalDTD】

1、前因 今天在生产环境启用了某个功能&#xff0c;结果发现有个文件上传华为云OBS失败了&#xff0c;报错如下&#xff1a; Caused by: java.lang.IllegalArgumentException: 不支持&#xff1a;http://javax.xml.XMLConstants/property/accessExternalDTDat org.apache.xal…

ctfshow信息收集(web1-web20)

目录 web1 web2 web3 web4 web5 web6 web7 web9 web10 web11 web14 web15 web16 web17 web18 web19 web20 web1 根据提示的孩子开发的时候注释没有被及时删除 web2 js原因无法查看源代码 第一种方法 在url前加入 view-source&#xff1a; 会显示页面源代…

获取双异步返回值时,如何保证主线程不阻塞?

目录 一、前情提要二、JDK8的CompletableFuture1、ForkJoinPool2、从ForkJoinPool和ThreadPoolExecutor探索CompletableFuture和Future的区别 三、通过CompletableFuture优化 “通过Future获取异步返回值”1、通过Future获取异步返回值关键代码&#xff08;1&#xff09;将异步…

springboot-mybatis项目

一、后端开发环境搭建 1、File->New->Projet 2选择 Spring Initializr &#xff0c;然后选择默认的 url 点击next 3勾选Spring Web、SQL模板&#xff0c;next 4点击finish&#xff0c;搭建完成 二 数据库 1 新建数据库 2 执行sql建表 SET NAMES utf8mb4; SET FOREIGN…

嵌入式学习-C++-Day2

嵌入式学习-CDay2 一、思维导图 二、作业 1.封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c;定义公有成员函数:初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函…

ActiveMQ|01-ClassicArtemis功能介绍

接上篇-MQ消息队列主流消息服务规范及代表产品&#xff0c;ActiveMQ就是基于JMS消息服务规范的消息中间件组件&#xff0c;主要应用在分布式系统架构中&#xff0c;帮助构建高可用、 高性能、可伸缩的企业级面向消息服务的系统 本文速览&#xff1a; JMS对象模型ActiveMQ的功…

elementplus Dialog 对话框设置距离页面顶部的距离

默认为 15vh&#xff0c;当弹窗过于高的时候&#xff0c;这个距离其实是不合适的 <el-dialogv-model"dialogVisible"title"Tips"width"30%":before-close"handleClose"top"6vh"><span>This is a message</s…

LeetCode刷题---旋转链表

解题思路: 首先创建辅助节点cur遍历指向链表最后一个节点&#xff0c;并将cur的下一个节点指向head头节点&#xff0c;形成闭环&#xff0c;并且在遍历的过程中计算链表的长度n。 n-k表示当前节点&#xff08;当前节点指向未反转节点的最后一个节点&#xff09;到反转后的链表的…

输入框限制输入两位小数 输入金额限制 双向绑定输入框能继续输入但是变量的数据不变解决方案 input 保留两位小数

移动端项目 需求是 输入框只能输入1000以内的数字保留两位小数 开发中发现 用vue开发双向绑定 不管是用value还是v-model 在输入时用input监听输入框变化 校验是否匹配 当不匹配是修改绑定的变量 inputValue时 打印inputValue符合预期 但是input输入框中还是原来输入的值 没有…

对话框与多窗体设计 —— 自定义对话框及其调用

3.2 自定义对话框及其调用3.3 Splash与登录窗口 3.2 自定义对话框及其调用 在一个应用程序设计中&#xff0c;为了实现一些特定的功能&#xff0c;必 须设计自定义对话框。自定义对话框的设计一般从 QDialog继承&#xff0c;并且可以采用UI设计器可视化地设计对 话框。对话框的…

发送消息提醒学习使用

效果图 数据库表及实体模型参考 创建消息事件模型 public class SystemMsgSendEvent implements DomainEvent {private String eventId;//事件idprivate ExecutePoint executePoint;//执行方式 事务提交后当前线程private OperateInfo operateInfo;//操作信息private String …

【AIGC】Diffusers:AutoPipeline自动化扩散生图管道

前言 &#x1f917; 扩散器能够完成许多不同的任务&#xff0c;并且您通常可以将相同的预训练权重用于多个任务&#xff0c;例如文本到图像、图像到图像和修复。但是&#xff0c;如果您不熟悉库和扩散模型&#xff0c;可能很难知道将哪个管道用于任务。例如&#xff0c;如果您…

【博客搭建记录贴】day4_Hexo基本操作,添加草稿并发布

目录 1.将项目导入到开发环境1.1 先把项目导入到IDEA中1.2 确认IDEA中服务器启动正常 2.Hexo基本操作: 添加草稿并发布2.1 生成一个草稿文件2.2 在页面上查看草稿3.3 将草稿正式发布 1.将项目导入到开发环境 我本地已经安装了 IntelliJ IDEA&#xff08;版本&#xff1a;社区版…

web蓝桥杯真题--13、水果摆盘

背景介绍 目前 CSS3 中新增的 Flex 弹性布局已经成为前端页面布局的首选方式&#xff0c;这次试题将利用 Flex 实现经典布局效果。 准备步骤 在开始答题前&#xff0c;你需要在线上环境终端中键入以下命令&#xff0c;下载并解压所提供的文件。 wget https://labfile.oss.a…

某马头条——day11+day12

实时计算和定时计算 流式计算 kafkaStream 入门案例 导入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-streams</artifactId><exclusions><exclusion><artifactId>connect-json</artifactId&…

仿真机器人-深度学习CV和激光雷达感知(项目2)day04【简单例程】

文章目录 前言简单例程运行小海龟仿真启动节点查看计算图发布 Topic调用 Serviece 用 Python 发布和接收 Topic创建工作空间创建功能包&#xff0c;编译编写 Topic Publisher 节点编写 Topic Subscriber 节点运行节点 自定义消息类型用 Python 注册和调用 Serviece新建功能包在…