可变参数(c/c++)

        

目录

一、C语言版本

 二、C++的实现方法

2.1数据包

2.2sizeof...运算符

2.3可变参数模板的使用

2.4emplace_back()


        有时候我们在编写函数时,可能不知道要传入的参数个数,类型 。比如我们要实现一个叠加函数,再比如c语言中的printf,c++中的emplace_last()。

那么这些函数是如何实现的呢?

一、C语言版本

在 C 中,可变参数通过 <stdarg.h> 头文件中的宏来处理。最常用的宏是 va_list、va_start、va_arg 和 va_end。以下是这些宏的简要说明:

va_list:用于声明一个可变参数列表的类型。

其实va_list就是一个char*类型,但具体实现取决于编译器和平台。它的内部结构是由编译器实现的,对于程序员来说是不透明的。

 va_start:用于初始化一个可变参数列表,将其与函数参数列表中的最后一个固定参数关联。(因为形参是从右往左入参的,也就是右边的参数是高地址,左边的函数是低地址)

va_arg:用于从可变参数列表中读取一个参数,并指定其类型。

va_end:用于清理可变参数列表,结束可变参数的使用 

 下面我们将结合一段代码来简单的讲解

#include<iostream>
#include<stdarg.h>
int  addsum(int num, ...)
{
    va_list args;
    va_start(args, num);
    int ret=0;
    for (int i = 0; i < num; i++)
    {
        int temp = va_arg(args, int);
        ret+=temp;
    }
    va_end(args);
    return ret;
}

int main()
{
    std::cout<<addsum(5, 1, 2, 3, 4, 5);
    
    return 0;
}

在 C 语言中,如果你使用了 va_start 宏来初始化可变参数列表,那么你至少需要传递一个参数作为固定参数,以便确定可变参数列表的起始位置。这个固定参数通常被称为 "sentinel" 或 "sentinel value"。

那么这个"sentinel" 或 "sentinel value"。一定要是参数个数吗?

当然不是,从printf中我们就知道第一个参数也可以是字符串。

在实现可变参数函数时,并不一定需要传递一个表示参数个数的额外参数。额外的参数可以帮助函数确定参数的数量,但并不是必须的。实际上,很多情况下都可以通过其他方式来确定参数的数量。

以下是一些确定参数数量的方法:

  1. 约定特定的参数结尾标志:例如,C 标准库中的 printf 函数就是通过字符串中的格式化标志(例如 %d%s 等)来确定参数的数量的。

  2. 利用特定的参数类型:例如,如果所有的参数都是相同类型的,你可以在函数中使用特定的参数类型来确定参数的数量。

  3. 使用额外参数传递参数数量:虽然不是必须的,但在某些情况下,通过额外的参数传递参数的数量是一种方便的做法。

在实际应用中,选择哪种方法取决于函数的使用场景和需求。如果函数的参数数量不固定,并且无法通过其他方式确定参数的数量,那么传递一个表示参数数量的额外参数是一种常见的做法。但在某些情况下,其他方法可能更加合适。

总的来说,并不是一定要传递表示参数个数的额外参数,具体是否需要取决于函数的设计和实现需求。

而va_start其实就是将自己定义的va_list 类型的参数向后移动一个位置

在上面的代码中其实就是让args指向如图所示位置。

而va_arg就是将后面的参数从其相应的类型提取出来。这下,你就知道为什么printf中为什么要有传入%d%f这些东西了吧。(当然这些东西也有确定参数个数的作用)。

最后只剩下va_end,用于标记可变参数列表的结束。它的存在是为了确保在使用完可变参数列表后正确释放资源,以避免内存泄漏和其他潜在的问题。

在可变参数函数中,通常会使用 va_start 来初始化 va_list 对象,然后使用 va_arg 来逐个读取参数,直到参数列表的末尾。一旦处理完所有参数,就应该调用 va_end 来清理 va_list 对象,以释放相关资源。

va_end 的作用包括:

  1. 清理资源va_list 对象可能会占用一些资源,例如在某些实现中可能分配了内存。调用 va_end 可以释放这些资源,避免内存泄漏。

  2. 标记列表的结束:调用 va_end 可以显式地标记可变参数列表的结束,使得程序能够正确地识别参数列表的边界,避免访问超出列表范围的参数。

  3. 与平台相关的清理工作va_end 可能会执行与平台相关的清理工作,以确保系统资源得到正确的释放。

在使用可变参数函数时,特别是在处理可变参数列表的末尾时,始终记得调用 va_end 是很重要的。不调用 va_end 可能会导致资源泄漏和未定义的行为,因此要确保在使用完可变参数列表后及时调用 va_end

 二、C++的实现方法

2.1数据包

在 C++ 中,也可以使用可变参数模板来实现类似的功能,这种技术更加灵活,并且不需要使用宏。C++11 引入了新的语法和标准库支持,使得可变参数模板更加易用和安全。

c++在c++11中提出了可变参数模板的概念,所谓可变参数模板就是一个接受可变数目参数模板的函数或模板类。可变数目的参数被称作参数包。存在两种参数包:

1.模板参数包:表示0或多个模板参数

2.函数参数包:表示0或多个函数参数

我们使用“...”来表示一个包,在一个模板参数列表中,class..或typname...表示接下来 的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。例如:

//Args是一个模板参数包;rest是一个函数参数包
//Args表示零个或多个模板类型参数//rest表示零个或多个函数参数
template <typename T,typename...Args>
void foo(const T 6t,const Args6 ..  rest);

声明了foo是一个可变参数函数模板,它有一个名为T的类型参数,和一个名为Args的模板参数包。这个包表示零个或多个额外的类型参数。foo的函数参数列表包含一个const s类型的参数,指向T的类型,还包含一个名为rest的函数参数包,此包表示零个或多个函数参数。

与往常一样,编译器从函数的实参推断模板参数类型。对于一个可变参数模板,编译器还会推断包中参数的数目。例如,给定下面的调用

int i= 0;
double d=3.14;
string s="how now brown cow";
foo(i,s,42,d); //包中有三个参数
fog(s.42,"hi");//包中有两个参数
foo(d,s);      //包中有一个参数
foo("hi");      //空包

编译器会为foo实例化出四个不同的版本:

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]&);

在每个实例中,T的类型都是从第一个实参的类型推断出来的。剩下的实参(如果有的话)提供函数额外实参的数目和类型。

2.2sizeof...运算符

当我们需要知道包中有多少元素时,可以使用sizeof...运算符。类似sizeof返回一个常量表达式

template<typename ...Args>
void g(Args .args)
{
    cout <<sizeof...(Args)<<end1;//类型参数的数目
    cout <<sizeof...(args)<<endl;//函数参数的数目
}

2.3可变参数模板的使用

void _ShowList()
{
    // 结束条件的函数
    std::cout << std::endl;
}

template <class T, class... Args>
void _ShowList(T val, Args... args)
{
    std::cout << val << " ";
    _ShowList(args...);
}

// args代表0-N的参数包
template <class... Args>
void CppPrint(Args... args)
{
    _ShowList(args...);
}
int main()
{
    CppPrint(1, 2, 2.2, string("xxxx"));
}

一般来说我们是使用递归的方式来将参数全部使用,当函数全部使用后就会匹配到结束函数。

template <class T>
void PrintArg(T t)
{
    std::cout << t << " ";
}
// args表示0-N的参数包
template <class... Args>
void CppPrintf(Args... args)
{
    int a[] = {0, (PrintArg(args), 0)...};
    cout << endl;
}

c++在编译时要确定数组a的大小来给空间,所以他会将里面的那个数据包展开,如图()中是一个逗号表达式,也就是有几个参数就会调用几下PrintArg。

2.4emplace_back()

emplace_back 是 C++ 中标准库容器 std::vector 的一个成员函数,用于在容器的尾部直接构造一个新元素,而不是先创建一个临时对象再拷贝或移动到容器中.

使用 emplace_back 可以直接在容器的尾部构造一个新元素,而不需要手动创建该元素的实例。emplace_back 接受任意数量的参数,这些参数会被传递给元素类型的构造函数,用于直接在容器中构造新元素。

所以网上有人说emplace_back代价更小,但是事实上移动拷贝代价更小,所以这句话应该有前提就是当元素类型是不可拷贝的时候。

在元素类型允许移动构造或移动赋值的情况下,emplace_backpush_back 的性能差异可能会减小甚至消失。

emplace_backpush_back 的主要性能差异在于:

  1. emplace_back 在容器中直接构造元素,避免了创建临时对象和拷贝/移动操作。
  2. push_back 在容器中插入一个已经构造的元素的拷贝或移动。

但是,如果元素类型具有移动语义(即具有移动构造函数和/或移动赋值运算符),那么在 push_back 中插入一个临时构造的元素,并在插入过程中执行移动操作,性能损失会相对较小。

因此,在元素类型允许移动拷贝时,emplace_backpush_back 的性能差异可能会减小,甚至没有明显的性能差异。在这种情况下,可以选择更符合语义的操作或更易读的代码。

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

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

相关文章

WebGPT与WebGLM

WebGPT paper: WebGPT&#xff1a;Browser-assisted question-answering with human feedbackDemo: https://openaipublic.blob.core.windows.net/webgpt-answer-viewer/index.html webgpt的论文发表最早&#xff0c;但论文本身写的比较"高山仰止"&#xff0c;可能先…

汇报工作时,你的工作会让领导满意吗?

当前你正在做的事 众所周知&#xff0c;跟领导汇报&#xff0c;第一件事需着重汇报你正在做的事&#xff0c;否则领导会感觉你无所事事。 举个例子&#xff1a; 完成了某某项目&#xff0c;在这项目中我负责&#xff1a;协调不同科室之间的纠纷&#xff0c;并把问题集中上报给…

《春山》中的贝叶斯统计——白敬亭衣服合理概率及决策比重。

目录 1. 全身黑衣服合理概率2. 真的是导演组允许&#xff1f;3. 粉丝的证据是否站得住&#xff1f;4.总结 感谢up主链接: 【理工春山学】只谈事实 从统计角度深度剖析春山学&#xff0c;她使用贝叶斯统计合理分析了在舞台中白敬亭、双魏、导演组出错的概率。接下来我采用一个新…

Acwing---846. 树的重心

树的重心 1.题目2.基本思想3.代码实现 1.题目 给定一颗树&#xff0c;树中包含 n n n 个结点&#xff08;编号 1 ∼ n 1∼n 1∼n&#xff09;和 n − 1 n−1 n−1 条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 …

百度云AI

百度云AI概述 Face腾讯优图科大讯飞 百度人脸识别基于深度学习的人脸识别方案&#xff0c;准确识别图片中的人脸信息&#xff0c;提供如下功能&#xff1a; 人脸检测&#xff1a;精准定位图中人脸&#xff0c;获得眼、口、鼻等72个关键点位置&#xff0c;分析性别、年龄、表…

【JAVA-Day89】Java字符串和XML数据结构的转换

Java字符串和XML数据结构的转换 Java字符串和XML数据结构的转换&#xff0c;高效灵活转变数据摘要引言一、什么是XML二、XML格式的应用场景三、XML字符串转对象3.1 使用 DOM 解析器实现 XML 字符串转对象3.2 使用 JAXB 实现 XML 字符串转对象 四、XML对象转字符串4.1 使用 DOM …

【实战】一、Jest 前端自动化测试框架基础入门(一) —— 前端要学的测试课 从Jest入门到TDD BDD双实战(一)

文章目录 一、前端要学的测试课1.前端要学的测试2.前端工程化的一部分3.前端自动化测试的例子4.前端为什么需要自动化测试&#xff1f;5.课程涵盖内容6.前置技能7.学习收获 二、Jest 前端自动化测试框架基础入门1. 自动化测试背景及原理前端自动化测试产生的背景及原理 2.前端自…

Linux中sigaction函数和SIGCHLD信号的使用

sigaction函数&#xff1a; 函数说明&#xff1a;注册一个信号处理函数 函数原型&#xff1a;int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 函数参数&#xff1a; signum:捕捉的信号act:传入参数&#xff0c;…

IDEA工程与模块管理

一、IDEA项目结构 层级关系&#xff1a; project(工程) - module(模块) - package(包) - class(类)具体的&#xff1a; 一个project中可以创建多个module一个module中可以创建多个package一个package中可以创建多个class二、Project和Module的概念 在 IntelliJ IDEA 中&…

HTTP特性

大家好我是苏麟 , 今天说说HTTP特性. 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) 到目前为止&#xff0c;HTTP 常见到版本有 HTTP/1.1&#xff0c;HTTP/2.0,HTTP/3.0&#xff0c;不同版本的 HTTP 特性是不一样的。 这里先用 HTTP/1.1 版本给大家介…

第6讲自定义icon实现

自定义icon实现 component下新建SvgIcon目录&#xff0c;再新建index.vue 定义svg-icon组件 <template><svg class"svg-icon" aria-hidden"true"><use :xlink:href"iconName"></use></svg> </template>&…

Kafka King 推荐一款漂亮、现代、实用的kafka客户端

Kafka King 一个漂亮、现代、实用的kafka客户端&#xff0c;使用python flet、flutter构建。 Github主页&#xff1a;https://github.com/Bronya0/Kafka-King 下载&#xff1a;https://github.com/Bronya0/Kafka-King/releases 功能清单 查看集群节点列表创建主题&#xf…

Spring Resource

java.net.URL 类可用于访问带有各种URL前缀的资源&#xff0c;但是对于访问一些资源还是不够方便。比如不能从类路径或者相对于ServletContext来获取资源。而Spring 的Resource接口&#xff0c;则可以通过类路径等方式来访问资源。 1 Resource接口 图 Resource接口及方法 getI…

Java 和 JavaScript 的奇妙协同:语法结构的对比与探索(上)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

数学建模:BP神经网络(含python实现)

原理 BP 神经网络&#xff0c;也称为多层感知机&#xff08;Multilayer Perceptron&#xff0c;MLP&#xff09;&#xff0c;是一种常见的神经网络模型&#xff0c;用于解决各种机器学习问题&#xff0c;包括分类和回归。BP 代表“反向传播”&#xff08;Backpropagation&#…

使用redis-insight连接到服务器上的redis数据库

一、安装redis&#xff1a; 安装 Redis。你可以通过运行下面的命令来使用 yum 安装 Redis&#xff1a; sudo yum install redis 启动 Redis 服务。安装完成后&#xff0c;你可以通过运行下面的命令来启动 Redis 服务&#xff1a; sudo systemctl start redis 设置 Redis 服务…

【C++】static静态关键字

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Rust 数据结构与算法:4栈:用栈实现进制转换

2、进展转换 将十进制数转换为二进制表示形式的最简单方法是“除二法”&#xff0c;可用栈来跟踪二进制结果。 除二法 下面实现一个将十进制数转换为二进制或十六进制的算法&#xff0c;代码如下&#xff1a; #[derive(Debug)] struct Stack<T> {size: usize, // 栈大…

【机器学习笔记】5 机器学习实践

数据集划分 子集划分 训练集&#xff08;Training Set&#xff09;&#xff1a;帮助我们训练模型&#xff0c;简单的说就是通过训练集的数据让我们确定拟合曲线的参数。 验证集&#xff08;Validation Set&#xff09;&#xff1a;也叫做开发集&#xff08; Dev Set &#xf…

【C++】 为什么多继承子类重写的父类的虚函数地址不同?『 多态调用汇编剖析』

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章主要是为了解答有…