可变参数(c/c++)

news2024/11/26 19:28:23

        

目录

一、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 的性能差异可能会减小,甚至没有明显的性能差异。在这种情况下,可以选择更符合语义的操作或更易读的代码。

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

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

相关文章

QObject 的拷贝构造和赋值操作

QObject中没有提供一个拷贝构造函数和赋值操作符给外界使用&#xff0c;其实拷贝构造和赋值的操作都是已经声明了的&#xff0c;但是它们被使用了Q_DISABLE_COPY () 宏放在了private区域。因此所有继承自QObject的类都使用这个宏声明了他们的拷贝构造函数和赋值操作符为私有。 …

算法沉淀——BFS 解决 FloodFill 算法(leetcode真题剖析)

算法沉淀——BFS 解决 FloodFill 算法 01.图像渲染02.岛屿数量03.岛屿的最大面积04.被围绕的区域 BFS&#xff08;广度优先搜索&#xff09;解决 Flood Fill 算法的基本思想是通过从起始点开始&#xff0c;逐层向外扩展&#xff0c;访问所有与起始点相连且具有相同特性&#xf…

Python六级考试笔记

Python六级考试笔记【源源老师】 六级标准 一、 掌握文件操作及数据格式化。 二、 掌握数据可视化操作。 三、 理解类与对象的概念&#xff0c;初步掌握类与对象的使用。 四、 掌握SQLite数据库基础编程。 五、 掌握简单的使用tkinter的GUI设计。 ​ 1. 文件操作 &#xff0…

深入了解社区店:定义、模式与优势

在当今的商业环境中&#xff0c;社区店正逐渐成为创业者们关注的热点。本文将以我的鲜奶吧店铺为例&#xff0c;深入探讨社区店的定义、模式和优势&#xff0c;为您提供最有价值的干货信息。 1、社区店的定义 社区店是指位于社区内或周边&#xff0c;以服务社区居民为主要目标…

shell脚本命令:mktemp和install

目录 一、mktemp命令 1、mktemp命令用法和格式 2、mktemp命令的实现原理 3、相关操作 3.1 创建临时文件或目录 3.2 指定临时文件名或目录名的后缀字符位数 3.3 指定临时文件或目录的父目录 3.4 指定临时文件或目录的后缀 4、实现文件独立的目录垃圾箱 二、install命令…

真假难辨 - Sora(OpenAI)/世界模拟器的技术报告

目录 引言技术报告汉译版英文原版 引言 Sora是OpenAI在2024年2月15日发布的世界模拟器&#xff0c;功能是通过文本可以生成一分钟的高保真视频。由于较高的视频质量&#xff0c;引起了巨大关注。下面是三个示例&#xff0c;在示例之后给出了其技术报告&#xff1a; tokyo-wal…

XMall 开源商城 SQL注入漏洞复现(CVE-2024-24112)

0x01 产品简介 XMall 开源电商商城 是开发者Exrick的一款基于SOA架构的分布式电商购物商城 前后端分离 前台商城:Vue全家桶 后台管理:Dubbo/SSM/Elasticsearch/Redis/MySQL/ActiveMQ/Shiro/Zookeeper等。 0x02 漏洞概述 XMall 开源商城 /item/list、/item/listSearch、/sys/…

2024年【广东省安全员C证第四批(专职安全生产管理人员)】考试试卷及广东省安全员C证第四批(专职安全生产管理人员)复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;考试试卷是安全生产模拟考试一点通总题库中生成的一套广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;复审模拟考试&#xf…

【送码】【IOS付费榜92名】春节不打烊,功德加倍攒

整体效果概览图 船新玩法&#xff0c;换个姿势攒功德 竞品玩法 过于简单&#xff1a;都是敲敲&#xff0c;然后设置里换换木鱼样式、音色等 本APP玩法 功德上云&#xff1a;敲击之后&#xff0c;会将所积攒的功德上传至fo祖云端 功德可视化&#xff1a;每日功德、3D功德地…

Panalog 日志审计系统 sessiptbl.php 前台RCE漏洞复现

0x01 产品简介 Panalog是一款日志审计系统,方便用户统一集中监控、管理在网的海量设备。 0x02 漏洞概述 Panalog日志审计系统 sessiptbl.php接口处存在远程命令执行漏洞,攻击者可执行任意命令,接管服务器权限。 0x03 影响范围 version <= MARS r10p1Free 0x04 复现…

Leetcode 452. 用最少数量的箭引爆气球435. 无重叠区间

class Solution {public int findMinArrowShots(int[][] points) {Arrays.sort(points,(o1,o2)->Integer.compare(o1[0], o2[0]));int count1;//箭的数量for(int i1;i<points.length;i) {if(points[i][0]>points[i-1][1]) {count;//边界没重合&#xff0c;又需要一支箭…

【刷题记录】合并两个有序数组、移除元素

本系列博客为个人刷题思路分享&#xff0c;有需要借鉴即可。 1.题目链接&#xff1a; T1&#xff1a;LINK T2&#xff1a;LINK 2.详解思路&#xff1a; T1: 思路1&#xff1a;弄个新数组&#xff0c;比较两个数组中的值&#xff0c;哪个小就把哪个值放到新数组中。 分析1&a…

kafka如何保证消息不丢?

概述 我们知道Kafka架构如下&#xff0c;主要由 Producer、Broker、Consumer 三部分组成。一条消息从生产到消费完成这个过程&#xff0c;可以划分三个阶段&#xff0c;生产阶段、存储阶段、消费阶段。 产阶段: 在这个阶段&#xff0c;从消息在 Producer 创建出来&#xff0c;…

Godot 游戏引擎个人评价和2024年规划(无代码)

文章目录 前言Godot C# .net core 开发简单评价Godot相关网址可行性 Godot(GDScirpt) Vs CocosGodot VS UnityUnity 的裁员Unity的股票Unity的历史遗留问题&#xff1a;Mono和.net core.net core的开发者&#xff0c;微软 个人的独立游戏Steam平台分成说明独立游戏的选题美术风…

面试经典150题——无重复字符的最长子串

我生来就是高山而非溪流&#xff0c;我欲于群峰之巅俯视平庸的沟壑 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力解法 看到这个题目&#xff0c;我们是不是发现和上一篇内容刚刚讲过的长度最小的子数组题目很像&#xff1f;首先自然的暴力解法&#xff0c;就是遍历字符…

Linux线程 分离和同步与互斥 条件变量

Linux线程 分离和同步与互斥 条件变量 1. 分离线程2. 线程互斥与互斥量3. 线程同步与竞态条件4. pthread库与条件变量5. 生产者-消费者 1. 分离线程 什么是线程分离&#xff1f; 线程分离是指线程在结束时&#xff0c;操作系统会自动回收其资源&#xff0c;而无需其他线程显式地…

SpringMVC-入门

1.概念 SpringMVC是一种软件架构思想&#xff0c;把软件按照模型(Model)、视图(View)、控制器(Controller)这三层来划分。Model&#xff1a;指的是工程中JavaBean&#xff0c;用来处理数据View&#xff1a;指的是工程中的html、jsp等页面&#xff0c;用来展示给用户数据Control…

LGAMEFI基于BPL公链开发的第一生态:开启RWA游戏娱乐与DeFi融合的新纪元

在去中心化金融&#xff08;DeFi&#xff09;与游戏娱乐的结合趋势中&#xff0c;BPL公链上的LGAMEFI项目代表了前沿的技术革新和市场领导。这种将web2上成熟页游进行RWA链改&#xff0c;不仅仅是将游戏热门领域融合&#xff0c;更是在寻找一种全新的参与者经验&#xff0c;将玩…

Linux网络编程——tcp套接字

文章目录 主要代码关于构造listen监听accepttelnet测试读取信息掉线重连翻译服务器演示 本章Gitee仓库&#xff1a;tcp套接字 主要代码 客户端&#xff1a; #pragma once#include"Log.hpp"#include<iostream> #include<cstring>#include<sys/wait.h…

C# CAD2016 判断多边形的方向正时针或逆时针旋转

方法一&#xff1a;基于相邻顶点相对位置判断顺时针排列 // 计算当前子序列是否为顺时针排列 for (int i 1; i < outerPoints.Count; i) {int index (startVertexIndex i) % outerPoints.Count;int prevIndex (startVertexIndex i - 1) % outerPoints.Count;Point2d c…