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

在这里插入图片描述

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

文章目录

  • 简介
  • 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语言中的可变参数也可能是一种合适的选择。最终,可变参数的使用应该以代码的清晰性、可读性和维护性为重要考虑因素。

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

想成为offer收割机?收好这份面试实战攻略!

很多小伙伴之所以会屡屡求职失败&#xff0c;是因为他根本就不知道在面试当中面试官到底要的东西是什么。 所以面试时&#xff0c;大家要不断提醒自己&#xff1a;我是来找工作的&#xff0c;必须考虑清楚说什么样的话&#xff0c;做什么样的事&#xff0c;才对找工作有帮助。…

leetcode1237. 找出给定方程的正整数解

1237. 找出给定方程的正整数解https://leetcode.cn/problems/find-positive-integer-solution-for-a-given-equation/ 难度中等 101 给你一个函数 f(x, y) 和一个目标结果 z&#xff0c;函数公式未知&#xff0c;请你计算方程 f(x,y) z 所有可能的正整数 数对 x 和 y。满…

Kali如何启动SSH服务并实现无公网ip环境远程连接

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh 远程连接kali! …

计算机网络 第6章(应用层)

系列文章目录 计算机网络 第1章&#xff08;概述&#xff09; 计算机网络 第2章&#xff08;物理层&#xff09; 计算机网络 第3章&#xff08;数据链路层&#xff09; 计算机网络 第4章&#xff08;网络层&#xff09; 计算机网络 第5章&#xff08;运输层&#xff09; 计算机…

WebSocket服务端数据推送及心跳机制(Spring Boot + VUE):

文章目录 一、WebSocket简介&#xff1a;二、WebSocket通信原理及机制&#xff1a;三、WebSocket特点和优点&#xff1a;四、WebSocket心跳机制&#xff1a;五、在后端Spring Boot 和前端VUE中如何建立通信&#xff1a;【1】在Spring Boot 中 pom.xml中添加 websocket依赖【2】…

Python + Selenium —— 元素定位函数 find_element!

WebDriver 中的 find_element() 方法用来查找元素&#xff0c;并返回 WebElement 对象。是 WebDriver 中最常用的方法。 前面提到的八种定位方式都有对应的方法&#xff0c;如find_element_by_id()。 在 WebDriver 中还有一种用法&#xff0c;就是单纯的find_element()。需要…

年销180万辆的特斯拉,护城河却在崩塌

文&#xff5c;刘俊宏 2023年率先开启汽车价格战的马斯克&#xff0c;伤敌一百自损八千&#xff1f; 在1月25日的特斯拉2023Q4财报电话会上&#xff0c;特斯拉CEO马斯克对中国公司的竞争力如此感叹道&#xff0c;“要是没有贸易壁垒&#xff0c;他们将摧毁&#xff08;destroy…

FastDeploy项目简介,使用其进行(图像分类、目标检测、语义分割、文本检测|orc部署)

FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署工具&#xff0c; 支持云边端部署。提供超过 &#x1f525;160 Text&#xff0c;Vision&#xff0c; Speech和跨模态模型&#x1f4e6;开箱即用的部署体验&#xff0c;并实现&#x1f51a;端到端的推理性能优化。包括 物…

Redis配置类,序列化

解释说明&#xff1a; 当前配置类不是必须的&#xff0c;因为 Spring Boot 框架会自动装配 RedisTemplate 对象&#xff0c;但是默认的key序列化器为JdkSerializationRedisSerializer&#xff0c;导致我们存到Redis中后的数据和原始数据有差别 如&#xff1a; 但是取是可以取出…

Linux第35步_在“移植uboot”前安装“libncurses5-dev,bison和flex”工具

在“移植uboot”前&#xff0c;需要在Ubuntu中安装“libncurses5-dev&#xff0c;bison和flex”工具&#xff0c;否则在“编译uboot”时&#xff0c;会报错。 一、了解相关知识 1、libncurses5-dev库是一个在Linux/Unix下广泛应用的图形函数库。 2、bison是用C编写的语法解析…

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

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

Vue+OpenLayers7入门到实战:快速搭建Vue+OpenLayers7地图脚手架项目。从零开始构建Vue项目并整合OpenLayers7.5.2

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7 前言 本章针对Vue初学者,对Vue不熟悉,甚至还不会Vue的入门学生读者。 本章会详细讲解从NodeJS环境到npm环境的各个步骤,再到使用vue-cli脚手架快速生成项目,以及添加OpenLayers7地图库依赖,编写简单的xyz高德地图显示…

AR 自回归模型

文章目录 总的代码ADF 检验(是否平稳)差分操作拟合AR 模型预测可视化总的代码 import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.ar_model import AutoReg from statsmodels.tsa.stattools import adfuller# 生成一个示例时间序…

DualSPHysics源码结构解读,新手入门

DualSPHysics代码下载&#xff0c;进入官网&#xff1a;https://dual.sphysics.org/ 可以看到下载的地方有①Full package ②Source code&#xff0c;官方的解读是&#xff1a;如果你只是想运行案例的话就下载Full package&#xff0c;如果想要自己进行修改构建的话&#xff0…

【数据结构】72变的双端队列

双端队列 前言一、双端队列1.1 双端队列的定义1.2 输入受限的双端队列1.3 输出受限的双端队列1.5 输入输出都受限的双端队列1.6 小结 二、双端队列的使用2.1 双端队列的出队序列——暴力求解2.1.1 栈的出栈序列2.1.2 输入受限的双端队列2.1.3 输出受限的双端队列2.1.4 输入输出…

Qt基础-项目中添加文件夹及子文件

本文讲解Qt项目中添加文件夹 1、项目中新建文件夹 打开工程目录&#xff0c;在目录下建立文件夹&#xff0c;如建立文件Dialogs 2、移入文件 将需要归类的头文件.h和源文件.cpp放入该文件夹下&#xff1b; 3、4、项目-右键-添加现有文件 查看Pro文件 发生了改变 4、也可以采…

Spring 声明式事务讲解,和 @Transactional注解的用法

目录 一、Spring框架介绍 二、什么是声明式事务 三、如何解决并发性事务问题 四、Transactional注解的用法 一、Spring框架介绍 Spring框架是一个开源的Java应用程序开发框架&#xff0c;旨在简化企业级Java应用程序的开发。它提供了一种轻量级的、全面的编程和配置模型&a…

【第六课课后作业】大模型评测

大模型评测 大模型评测安装环境安装数据准备查看支持的数据集和模型 启动测评评测结果 大模型评测 安装 环境安装 conda create --name opencompass --clone/root/share/conda_envs/internlm-base source activate opencompass git clone https://github.com/open-compass/ope…

一、Lamdba 表达式与函数式接口(最终版)

一、Lamdba 表达式与函数式接口 1.1 Lamdba 表达式与函数式接口 1.1.1 Lambda 表达式概述 Lambda 表达式是 Java 8 引入的一个新特性Lambda 表达式可以被视为匿名函数允许在需要函数的地方以更简洁的方法定义功能Lambda 表达式可以完成简洁的函数定义Stream API 中大量使用了…

基于静态顺序表实现通讯录

目录 一、设计框架 1、功能要求​ 2、菜单函数的实现 二、头文件实现​ Contact.h SeqList.h 三、Test.h 四、通讯录的初始化和销毁 五、增加通讯录 六、在通讯录中查找姓名下标 七、删除通讯录 八、显示通讯录 九、查找通讯录 一、设计框架 test.c&#xff1a;通…