【C语言深入】带你了解C语言中的可变参数列表

news2024/10/4 9:36:28

【C语言深入】带你了解C语言中的可变参数列表

  • 一、可变参数函数的使用方式
    • 1、使用方式
    • 2、自定义可变把参数函数
      • 2.1、三个宏一个类型
      • 2.2、实现方式
  • 二、可变参数列表的原理
    • 1、va_start
      • 1.1、_ADDRESSOF
      • 1.2、关于临时拷贝的一个小知识点
      • 1.3、_INTSIZEOF
    • 2、va_arg
    • 3、va_end

一、可变参数函数的使用方式

1、使用方式

我们在编写程序的时,有时候可能需要用到一些参数不确定的函数,以应对各种场景。最好的一个例子就是我们在学习C语言时所使用的第一个函数printf:

#include <stdio.h>
int main() {
	printf("hello world!");
	return 0;
}

在使用时我们可以只传递一个参数,也可以传递多个参数:

#include <stdio.h>
int main() {
	printf("hello world!");
	printf("%d %d %d %d ……", a, b, c, d, ……);
	return 0;
}

所以我们在使用参数可变的函数的时候,有多少个参数就传递多少个参数即可。

2、自定义可变把参数函数

name我们是否能自定义一个参数可变的函数呢?
当然可以,其实可变参数函数的实现主要依赖的就是以下这三宏和一个类型:

2.1、三个宏一个类型

我们先来粗略的认识一下:
一个类型指的是va_list,而它的本质其实就是一个char指针,我们可以转到它的定义来看一看:
在这里插入图片描述
其实它的作用就是用于创建一个字符指针的。
第一个宏是va_start:
在这里插入图片描述
这个宏需要传入两个参数,第一个参数就是我们在上面用va_list创建的char
指针,第二个参数是一个标志着该函数有多少个参数的整型。
该宏的功能我们可以选粗略的理解为能使我们定义好的char指针指向我们的第一个参数(从左往右)
第二个宏是va_arg:
在这里插入图片描述
该宏在使用时也是需要传入两个参数,第一个就是我们前面多定义的char
类型的指针,第二个就是我们每个参数的类型。
该宏的功能我们可以先粗略地理解为能让能让char指针移动一个类型长度的地址,并取得当前(未移动之前)指针所指向的一个参数。
第三个宏是va_end:
在这里插入图片描述
该宏只需要传递一个参数,那就是char
的指针。
该宏的功能就很简单了,就是把我们char*指针置为空指针。

当然啦,这里也只是粗略的认识一下,至于细节后面也还会讲到。

2.2、实现方式

在C语言的函数定义中,使用三个点来表示可变参数列表,这就像是一个省略号,表示参数的个数是未知的。比如printf就是这样定义的:
在这里插入图片描述
所以,如果我们想自定义一个参数可变的函数,就直接用三个点来代替它的参数部分,例如我们现在要定义一个求多个整数中最大值的函数,就可以先这样声明:

int Max(int num, ...);

其中num这个参数是一定要有的,它的作用就是标记该函数有多少个参数,以供后面使用。
也就是说,可变参数函数至少得要有一个明确的参数。

该函数的具体实现代码如下所示:

int  Max(int num, ...) {
	va_list p; // 创建一个char*类型的指针
	va_start(p, num); // 让指针p指向第一个参数
	int max = va_arg(p, int); // 让max等于第一个参数
	int i = 0;
	int x = 0; // 保存每一次取到的参数
	for (i = 0; i < num - 1; i++) {
		x = va_arg(p, int);
		if (max < x) {
			max = x;
		}
	}
	va_end(p); // 将p置为空指针
	return max;
}

但是大家可能还有会有点儿懵,想知道具体的实现步骤。
想要理解步骤,就要用到一点儿函数栈帧的知识,我现在利用函数栈帧的知识来给大家画一下大致地图解过程,比如我们传递的参数如下:

int main() {
	int max = Max(4, 3, 5, 2, 4);
	return 0;
}

我们知道调用一个函数时候,实参的临时拷贝其实是在被调用函数的栈帧形成之前就已经形成了的,而且顺序是从右向左,所以我们的栈帧结构图大概如下:
在这里插入图片描述
而我们的第一个宏va_start所做的就是将我们的p指针指向第一个参数:
在这里插入图片描述
而后,我们令max = max = va_arg(p, int)其实做的就是先将指针当前指向的参数的值赋给max,并且让指针指向下一个参数:
在这里插入图片描述
然后我们在循环中一直重复va_arg就可以一直取出参数并进行比较,执行num - 1此后就可以遍历完所有的参数,也就得到了最大值。

二、可变参数列表的原理

上面只是简单地将用法和实现方法给大家介绍了一下,但要想灵活的应用可变参数列表,我们还是得知道它的实现原理,而可变参数列表的原理究其根本也就是那三个宏的实现原理,所以我们就要对那三个宏再进行深度剖析。

1、va_start

我们可以先到该宏的定义处去看看该宏是怎么定义的:
在这里插入图片描述
想必大家看到这就又该晕了,没想到这个va_start的实现里有潜逃了两个非常奇怪的宏,这也太复杂了吧。
但复杂归复杂,我们还得来好好的看看va_start里包含的这两个宏:

1.1、_ADDRESSOF

我们还是先转到该宏的定义处看一看:
在这里插入图片描述
当我们看过定义后就会发现这个宏其实非常简单,就像它的名字一样,它所做的就是对传入的参数进行取地址操作。
那么取的是谁的地址呢?
着我们需要到函数的定义里看看:
在这里插入图片描述
很明显这里取的就是num的地址:
在这里插入图片描述
然后我们就将num的地址转化成va_list类型(指针):
在这里插入图片描述
然后再加上一个整数(_INTSIZEOF(v)),就可以使得p指针指向第一个参数了。
在讲_INTSIZEOF这个宏之前需要先插播一个小的知识点:

1.2、关于临时拷贝的一个小知识点

我们先来想一想一个问题,若是我想传入的参数不是int类型而是char类型,那这个函数还能否达到痛痒的效果呢?
我们可以先来试验一下,我们把参数就改成char类型试试:
在这里插入图片描述
结果发现我们还是能达到效果的。但是有的朋友可能就会疑惑了,因为在Max函数里提取参数时候用的类型分明是int啊,为什么类型不相同但却没出现问题:
在这里插入图片描述

要解释这个现象,就得先补充一个传参时的小特性:
我们的实参在形成临时拷贝的时候,一般都是以4字节或8字节进行拷贝的。也就是说如果传入的实参大小不足4字节时(例如char类型),那形成临时拷贝的时候就会先将它提升为4字节。
要证明这个特性,我们就要进到汇编中去看看:
在这里插入图片描述
我们知道,随着参数的不断压入,栈顶寄存器esp的只应该是不断减小的,那么如果这里的拷是按照参数的本来大小拷贝,那当我们执行到下一条指令后,寄存器esp的值应该只是减小1,但如果是按照4字节拷贝,那就将减少4,我们来看看结果:
在这里插入图片描述
从结果就可以看出,确实是减小了4字节。

理解了这个特性,我们从才能更好地解释下面要讲到的_INTSIZEOF这个宏所有做的工作。

1.3、_INTSIZEOF

我想经过上面的描述大家应该就能知道这个宏所做的其实就是把参数的大小以4字节向上取整。
那到底是怎么做到的呢?我们还是先到该宏的定义处去看看:
在这里插入图片描述
想要理解这个宏,就必须分析清楚括号里那一大串有sizeof组成的表达式。我们知道sizeof(int)就是4,所以该表达式就可以转化为:

(sizeof(n) + 3) & ~(3)

这样好像也看不出什么端倪,因为这里的运算用到了位运算符,所以我们还是要从二进制位入手。
我们可以从结果倒推出结论:
我们知道整型4的二进制序列为(我这里只写出后8位):

00000100

也就是说如果我们想得到的是按4字节向上取整的数(也就是4的整数倍),那么结果的二进制序列的最后两位一定是0。
而对整型3按位取反的~(3)的二进制序列为:

11111100

它的后两位都是0,前面的都是1,所以~(3)与任何数按位与都能使结果的后两位都为0,而前面的位都不变,也就是保证结果为4的整数倍。
而它前面所做的sizeof(n) + 3就是为了向上取整,因为如果一个数不是4的整数倍,那它的二进制序列的后两位必定有有一位是1,那次是加上3,那就先使得该数字先向上超出或等于4的整数倍,然后如果加3后的结果后面的两位还有1,例如6的二进制序列00000110加3后变成00001001。不用担心这个1会在按位与~(3)的时候被消掉的。
而最后的这个转化成void的操作其实可以忽略掉,因为void是通用的嘛~:
在这里插入图片描述
所以,这个神奇的宏就是这样使类型以4字节向上取整的。

2、va_arg

这个宏就比上一个要简单一点了,这一点我们从它的定义就能看出来:

在这里插入图片描述
前面的*(t*)我们可以先不管,我们首先看后面括号里的表达式的内容。
我们看到括号里首先做的就是让ap(就是指针p)先移动到下一个参数,也就是先让p指针指向下一个参数:
在这里插入图片描述
但我们发现它后来好像又减了回去:
在这里插入图片描述
但这里的ap + _INTSIZEOF(t)只是产生了一个值,并没有改变ap的内容,所以ap的指向并没有变。
所以这个括号里的表达式的值就是ap原本的值,但这个表达式产生的一个副作用是让ap指向下一个参数。
所以外边的*(t*)解引用所得到的也就是,ap原本指向的值。

3、va_end

这个宏其实没什么好说的,就是将指针p置成空指针:
在这里插入图片描述

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

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

相关文章

23种设计模式总结(大白话,适合小白)

文章目录什么是设计模式&#xff1f;设计模式的分类创建型模式创建型类类型工厂方法模式创建型对象型抽象工厂模式生成器模式原型模式单例模式结构型模式结构型类类型适配器模式结构型对象型桥接模式组合模式装饰器模式外观模式享元模式代理模式行为型模式行为型对象型命令模式…

【C++PrimerPlus】第五章 循环和关系表达式

文章目录5.1 for循环5.1.1 for循环的组成部分5.1.2 回到for循环5.1.3 修改步长5.1.4 使用for循环访问字符串5.1.5 递增运算符 ()和递减运算符(--)5.1.6 副作用和顺序点5.1.7 前缀格式与后缀格式5.1.8 递增/递减和指针5.1.9 组合赋值运算符5.1.10 复合语句![](https://img-blog.…

Qt Quick - ToolTip

Qt Quick - ToolTip使用总结一、概述二、附带的ToolTip三、延迟和超时四、自定义ToolTip五、定制化一、概述 ToolTip 其实就是ToolTip&#xff0c;所谓ToolTip其实就是一段简短的文本&#xff0c;告知用户控件的功能。它通常置于父控件之上或之下。提示文本可以是任何富文本格…

常用异常检测模型的应用

常用异常检测模型的应用 描述 异常数据检测不仅仅可以帮助我们提高数据质量&#xff0c;同时在一些实际业务中&#xff0c;异常数据往往包含有价值的信息&#xff0c;如异常交易、网络攻击、工业品缺陷等&#xff0c;因此异常检测也是数据挖掘的重要手段。常用的异常检测模型…

【通过Cpython3.9源码看看python字符串拼接:“+”为什么比join低效】

基本说明 Python字符串拼接中&#xff0c;使用join()方法比运算符更高效&#xff0c;主要原因在于字符串对象的不可变性和内存分配策略。 首先&#xff0c;我们要知道Python字符串是不可变的对象。这意味着&#xff0c;每次使用运算符进行字符串拼接时&#xff0c;Python需要…

Vue2-黑马(四)

目录&#xff1a; &#xff08;1&#xff09;axios-响应格式 &#xff08;2&#xff09;axios-拦截器 &#xff08;3&#xff09;vue2-条件渲染 &#xff08;4&#xff09;vue2-列表渲染 &#xff08;1&#xff09;axios-响应格式 下面看axios的返回响应对象的内部组成 后…

【grpc02】安装protobuf和protoc

目录 Windows环境 下载通用编译器 配置环境变量 安装go专用的protoc的生成器 GoLang中安装插件 如何使用protobuf呢&#xff1f; Mac环境 Protoc安装 Protoc-gen-go的安装 Windows环境 下载通用编译器 下载地址&#xff1a;v3.20.1 Releases protocolbuffers/pr…

【优化算法】使用遗传算法优化MLP神经网络参数(TensorFlow2)

文章目录任务查看当前的准确率情况使用遗传算法进行优化完整代码任务 使用启发式优化算法遗传算法对多层感知机中中间层神经个数进行优化&#xff0c;以提高模型的准确率。 待优化的模型&#xff1a; 基于TensorFlow2实现的Mnist手写数字识别多层感知机MLP # MLP手写数字识别…

Java支付SDK接口远程调试 - 支付宝沙箱环境【公网地址调试】

文章目录1.测试环境2.本地配置3. 内网穿透3.1 下载安装cpolar内网穿透3.2 创建隧道4. 测试公网访问5. 配置固定二级子域名5.1 保留一个二级子域名5.2 配置二级子域名6. 使用固定二级子域名进行访问转发自CSDN远程穿透的文章&#xff1a;Java支付宝沙箱环境支付&#xff0c;SDK接…

Linux命令·traceroute

通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点&#xff08;source&#xff09;到达某一同样的目的地(destination)走的路径可能会不一样&#xff0c;但基本上来说大部分时候所走的路由是相同的。linux系统…

移动端项目开发总结(一)

移动端项目开发总结&#xff08;一&#xff09; 前阵子做租赁项目&#xff0c;风风火火的上线&#xff0c;趁现在还没忘&#xff0c;把用到的东西整理以下&#xff0c;算是对于这个项目的回顾吧。 特效一 &#xff1a; 移动端适配 需求 移动端适配&#xff0c;采用rem单位。…

深入理解Java虚拟机——Java内存区域

1.前言 Java内存区域也叫运行时数据区域&#xff0c;要记得把Java内存模型&#xff08;JMM区分开来&#xff09;。 根据线程是否共享可以把运行时数据区如上图所分。 线程共享 堆内存方法区 线程私有 栈内存 本地方法栈虚拟机栈 程序计数器 接下来&#xff0c;将逐个介绍…

什么是文件传输协议,文件传输协议又是怎么工作的

文件传输协议FTP是一种仍在使用的协议&#xff0c;在上载和下载文件时仍然比较流行&#xff0c;通常是那些太大的文件&#xff0c;需要花费很长时间才能通过常规电子邮件程序作为附件下载进行传输。 从技术上讲&#xff0c;它是“文件传输实用程序”&#xff0c;是许多TCP / I…

腾讯云4核8G12M轻量服务器配置性能评测

腾讯云轻量4核8G12M服务器&#xff0c;之前是4核8G10M配置&#xff0c;现在公网带宽和月流量包整体升级&#xff0c;12M公网带宽下载速度可达1536KB/秒&#xff0c;系统盘为180GB SSD盘&#xff0c;每月2000GB免费流量&#xff0c;腾讯云百科来详细说下4核8G12M轻量应用服务器配…

碳化硅材料在功率半导体中的优劣

开关电源工作频率的提高受到开关损耗的制约 开关电源的工作频率是指开关变换器操作的频率。在开关电源中&#xff0c;一个开关变换器被用来将直流&#xff08;DC&#xff09;能源转换为可用于电子设备的交流&#xff08;AC&#xff09;能源。开关变换器的基本原理是通过对开关…

3.4 函数的单调性和曲线的凹凸性

学习目标&#xff1a; 如果我要学习函数的单调性和曲线的凹凸性&#xff0c;我会采取以下几个步骤&#xff1a; 理解概念和定义&#xff1a;首先&#xff0c;我会学习单调性和凹凸性的定义和概念。单调性是指函数的增减性质&#xff0c;可以分为单调递增和单调递减&#xff1b…

Python使用PyQt5实现指定窗口置顶

文章目录前言一、网上找到的代码二、尝试与借鉴后的代码——加入PyQt界面1.引入库2.主代码3.完整主代码4.UI界面代码总结前言 工作中&#xff0c;同事随口提了一句&#xff1a;要是能让WPS窗口置顶就好了&#xff0c;老是将窗口切换来切换去的太麻烦了。 然后&#xff0c;这个…

docker-compose 安装nginx php mysql phpadmin

一 摘要 本文主要介绍基于docker docker-compose 安装 lnmp 三件套&#xff0c;以及用phpmysadmin 验证下部署可正确。 二 环境信息 2.1 操作系统 [root2023001 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (Core) [root2023001 ~]#2.2 docker [root20230…

【opencv】图像数字化——认识OpenCV中的Mat类( 7 访问多通道Mat对象中的值)

7 访问多通道Mat对象中的值 7.1使用成员函数at() #include <opencv2/core/core.hpp> #include<iostream> using namespace std; using namespace cv; int main() {Mat mm (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 32), Vec3f(3, …

C++【深入理解多态】

文章目录一、多态概念与实现&#xff08;1&#xff09;多态的概念&#xff08;2&#xff09;怎么构成多态&#xff08;3&#xff09;虚函数重写的2个例外&#xff08;4&#xff09;经典剖析巩固知识点&#xff08;5&#xff09; override 和 final&#xff08;6&#xff09;小总…