可变参数(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中我们就知道第一个参数也可以是字符串。
在实现可变参数函数时,并不一定需要传递一个表示参数个数的额外参数。额外的参数可以帮助函数确定参数的数量,但并不是必须的。实际上,很多情况下都可以通过其他方式来确定参数的数量。
以下是一些确定参数数量的方法:
约定特定的参数结尾标志:例如,C 标准库中的
printf函数就是通过字符串中的格式化标志(例如%d、%s等)来确定参数的数量的。利用特定的参数类型:例如,如果所有的参数都是相同类型的,你可以在函数中使用特定的参数类型来确定参数的数量。
使用额外参数传递参数数量:虽然不是必须的,但在某些情况下,通过额外的参数传递参数的数量是一种方便的做法。
在实际应用中,选择哪种方法取决于函数的使用场景和需求。如果函数的参数数量不固定,并且无法通过其他方式确定参数的数量,那么传递一个表示参数数量的额外参数是一种常见的做法。但在某些情况下,其他方法可能更加合适。
总的来说,并不是一定要传递表示参数个数的额外参数,具体是否需要取决于函数的设计和实现需求。
而va_start其实就是将自己定义的va_list 类型的参数向后移动一个位置
在上面的代码中其实就是让args指向如图所示位置。
而va_arg就是将后面的参数从其相应的类型提取出来。这下,你就知道为什么printf中为什么要有传入%d%f这些东西了吧。(当然这些东西也有确定参数个数的作用)。
最后只剩下va_end,用于标记可变参数列表的结束。它的存在是为了确保在使用完可变参数列表后正确释放资源,以避免内存泄漏和其他潜在的问题。
在可变参数函数中,通常会使用
va_start来初始化va_list对象,然后使用va_arg来逐个读取参数,直到参数列表的末尾。一旦处理完所有参数,就应该调用va_end来清理va_list对象,以释放相关资源。
va_end的作用包括:
清理资源:
va_list对象可能会占用一些资源,例如在某些实现中可能分配了内存。调用va_end可以释放这些资源,避免内存泄漏。标记列表的结束:调用
va_end可以显式地标记可变参数列表的结束,使得程序能够正确地识别参数列表的边界,避免访问超出列表范围的参数。与平台相关的清理工作:
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_back和push_back的性能差异可能会减小甚至消失。
emplace_back和push_back的主要性能差异在于:
emplace_back在容器中直接构造元素,避免了创建临时对象和拷贝/移动操作。push_back在容器中插入一个已经构造的元素的拷贝或移动。但是,如果元素类型具有移动语义(即具有移动构造函数和/或移动赋值运算符),那么在
push_back中插入一个临时构造的元素,并在插入过程中执行移动操作,性能损失会相对较小。因此,在元素类型允许移动拷贝时,
emplace_back和push_back的性能差异可能会减小,甚至没有明显的性能差异。在这种情况下,可以选择更符合语义的操作或更易读的代码。
相关文章:
可变参数(c/c++)
目录 一、C语言版本 二、C的实现方法 2.1数据包 2.2sizeof...运算符 2.3可变参数模板的使用 2.4emplace_back() 有时候我们在编写函数时,可能不知道要传入的参数个数,类型 。比如我们要实现一个叠加函数,再比如c语言中的printf,c中的emp…...
【数据结构】图
文章目录 图1.图的两种存储结构2.图的两种遍历方式3.最小生成树的两种算法(无向连通图一定有最小生成树)4.单源最短路径的两种算法5.多源最短路径 图 1.图的两种存储结构 1. 图这种数据结构相信大家都不陌生,实际上图就是另一种多叉树&…...
32.3K Star,再见 Postman,这款开源 API 客户端更香
Hi,骚年,我是大 G,公众号「GitHub指北」会推荐 GitHub 上有趣有用的项目,一分钟 get 一个优秀的开源项目,挖掘开源的价值,欢迎关注。 使用 API 工具来调试接口是后端开发经常会使用的,之前一直…...
Python循环语句——continue和break
一、引言 在Python编程中,循环是常见的控制流语句,它允许我们重复执行一段代码,直到满足某个条件为止。而在循环中,continue和break是两个非常重要的控制语句,它们可以帮助我们更加灵活地控制循环的行为。 二、contin…...
C++面向对象程序设计-北京大学-郭炜【课程笔记(三)】
C面向对象程序设计-北京大学-郭炜【课程笔记(三)】 1、构造函数(constructor)1.1、基本概念 2、赋值构造函数2.1、基本概念2.1、复制构造函数起作用的三种情况2.2、常引用参数的使用 3、类型转换构造函数3.1、什么事类型转换构造函…...
Linux:搭建docker私有仓库(registry)
当我们内部需要存储镜像时候,官方提供了registry搭建好直接用,废话少说直接操作 1.下载安装docker 在 Linux 上安装 Docker Desktop |Docker 文档https://docs.docker.com/desktop/install/linux-install/安装 Docker 引擎 |Docker 文档https://docs.do…...
用HTML、CSS和JS打造绚丽的雪花飘落效果
目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetGBK"><style>* {margin: 0;padding: 0;}#box {width: 100vw;heig…...
订餐|网上订餐系统|基于springboot的网上订餐系统设计与实现(源码+数据库+文档)
网上订餐系统目录 目录 基于springboot的网上订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户功能模块的实现 (1)用户注册界面 (2)用户登录界面 (3)菜品详情界面 (…...
从零开始学howtoheap:解题西湖论剑Storm_note
how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:从零开始配置pwn环境:从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指…...
Rust 基本环境安装
rust 基本介绍请看上一篇文章:rust 介绍 rustup 介绍 rustup 是 Rust 语言的安装器和版本管理工具。通过 rustup,可以轻松地安装 Rust 编译器(rustc)、标准库和文档。它也允许你切换不同的 Rust 版本或目标平台,以及…...
【电源】POE系统供电原理(二)
转载本博客文章,请注明出处 上一篇文章中,有提到POE系统工作原理及动态检测机制,下面我们继续介绍受电端PD技术及原理。POE供电系统包含PSE、PD及互联接口部分组成,如下图所示。 图1 POE供电系统 PSE控制器的主要作用ÿ…...
GPU独显下ubuntu屏幕亮度不能调节解决方法
GPU独显下屏幕亮度不能调节(假设你已经安装了合适的nvidia显卡驱动),我试过修改 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT"quiet splash acpi_backlightvendor" ,没用。修改和xorg.conf相关的文件,…...
Linux篇:网络基础1
一、网络基础:网络本质就是在获取和传输数据,而系统的本质是在加工和处理数据。 1、应用问题: ①如何处理发来的数据?—https/http/ftp/smtp ②长距离传输的数据丢失的问题?——TCP协议 ③如何定位的主机的问题&#…...
RK3568笔记十七:LVGL v8.2移植
若该文为原创文章,转载请注明原文出处。 本文介绍嵌入式轻量化图形库LVGL 8.2移植到Linux开发板ATK-RK3568上的步骤。 主要是参考大佬博客: LVGL v8.2移植到IMX6ULL开发板_lvgl移植到linux-CSDN博客 一、环境 1、平台:rk3568 2、开发板:…...
C#系列-C#访问MongoDB+redis+kafka(7)
目录 一、 C#中访问MongoDB. 二、 C#访问redis. 三、 C#访问kafka. C#中访问MongoDB 在C#中访问MongoDB,你通常会使用MongoDB官方提供的MongoDB C#/.NET Driver。这个驱动提供了丰富的API来执行CRUD(创建、读取、更新、删除&#x…...
(12)Hive调优——count distinct去重优化
离线数仓开发过程中经常会对数据去重后聚合统计,count distinct使得map端无法预聚合,容易引发reduce端长尾,以下是count distinct去重调优的几种方式。 解决方案一:group by 替代 原sql 如下: #7日、14日的app点击的…...
记录 | 验证pytorch-cuda是否安装成功
检测程序如下: import torchprint(torch.__version__) print(torch.cuda.is_available()) 或者用终端 Shell,运行情况如下...
LeetCode 239.滑动窗口的最大值 Hot100 单调栈
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输…...
463. Island Perimeter(岛屿的周长)
问题描述 给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] 1 表示陆地, grid[i][j] 0 表示水域。 网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有…...
如何解决缓存和数据库的数据不一致问题
数据不一致问题是操作数据库和操作缓存值的过程中,其中一个操作失败的情况。实际上,即使这两个操作第一次执行时都没有失败,当有大量并发请求时,应用还是有可能读到不一致的数据。 如何更新缓存 更新缓存的步骤就两步࿰…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

