可变参数列表

🌈引言

我们可不可以像printf一样任意传递参数,没有个数限制呢?
这就要用到 “可变参数列表”。🤘🤘🤘

在这里插入图片描述

🌈测试代码(32位下)

  • 代码
#include <stdio.h>
//找到最大的数
int FindMaxData(int num, ...)
{
	va_list arg;
	__crt_va_start(arg, num);
	int max = __crt_va_arg(arg, int);
	for (int i = 1; i < num; i++)
	{
		int curr = __crt_va_arg(arg, int);
		if (curr > max)
		{
			max = curr;
		}
	}
	__crt_va_end(arg);
	return max;
}
int main()
{
	int max = FindMaxData(6, 11, 55, 44, 65, 98,550);//6是一共传入6个数据,分别是11,55,44,65,98,550
	printf("max:%d\n", max);
	return 0;
}
  • 执行结果
    在这里插入图片描述
  • 注意事项

1.使用可变参数列表时,必须至少要有一个明确的参数。(比如上面num)
2.上面的代码逻辑是提取最大值,这里有一点要说明一下,就是不建议使用第三方变量:
因为假如我传入的数据全是负数,而你将第三方初始化为0.那么最大值只能是0.
3.

  • 问题

Q1: 如果函数没有形式参数,那么可以给函数传递参数吗?
答:1.只要发生了函数调用并且传递了参数,那么就一定会形成临时变量。
2所谓的临时拷贝就是在栈帧中开辟一块空间暂时保存数据,参数从右向左依次形成临时拷贝。.
Q2: 像va_list,__crt_va_start,__crt_va_end是什么?

答:它们是宏而不是函数,接下来我会具体介绍它们的作用。

Q3:形参中 numz指的是传入数据的个数,…表示的是可变参数。
Q4:这个代码的作用是求最大值。

🌈宏

接下来将会重点围绕代码中出现的宏🐽🐽🐽

🌳代码中的宏

🍁va_list

  • va_list本质就是char*

🤏让我们可以按一字节的方案来进行字节级别的数据读取。

在这里插入图片描述

🍁__crt_va_start(ap, v), __crt_va_arg(ap, t),__crt_va_end(ap)

  • 宏定义
    在这里插入图片描述
  • 小介绍
👉__crt_va_start:通过传来的第一个参数来定位我们当前的arg(定义的指针)指针,让arg指向可变参数部分。

👉__crt_va_arg:来一个个提取可变参数中所有参数。
👉__crt_va_end来将参数置空。

🍁_ADDRESSOF(v), _INTSIZEOF(n)

🫰它们出现在 __crt_va_arg(ap, t),__crt_va_end(ap)中。对其起到很大作用。

  • 宏定义

  • _ADDRESSOF(v)

在这里插入图片描述

✨ _ADDRESSOF(v)本质就是“取地址

  • _INTSIZEOF(n)
    在这里插入图片描述

✨有点复杂,下面会讲!

🍁 注意事项

  • ✨这些宏是无法直接判断实际存在参数的数量。
  • ✨这些宏无法判断每个参数的是类型。
  • ✨如果在 __crt_va_arg 中指定了错误的类型,那么其后果是不可预测的。

🌳重要的宏(不好理解)

🍁 _INTSIZEOF(v)

在这里插入图片描述要想真正取理解这个宏,可以数学证明;

***前提:为了方便证明,假设sizeof(n) == n(char 1,short 2,int 4),n就是实际需要占的字节

*** _INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式,x是4的倍数:4,8,12,16…
比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4
比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8


***怎么办到的:
***第一步理解:4的倍数
既然是4的最小整数倍取整,那么本质是:x=4 * m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8
***而m具体是多少,取决于n是多少
如果n能整除4,那么m就是n/4
如果n不能整除4,那么m就是n/4+1


***上面是两种情况,如何合并成为一种写法呢?
常见做法是 ( n+sizeof(int)-1) )/sizeof(int) -> (n+4-1)/4
***如果n能整除4,那么m就是(n+4-1)/4->(n+3)/4, +3的值无意义,会因取整自动消除,等价于 n/4
如果n不能整除4,那么n=最大能整除4部分+r,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中
4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1


***第二步理解:最小4字节对齐数
搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了
((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/4)*4


*第三步理解:理解源代码中的宏拿出简洁写法:((n+4-1)/4) 4,设w=n+4-1, 那么表达式可以变化成为 (w/4) * 4,而4就是2^2,w/4,不就相当
于右移两位吗?,再次 * 4不就相当左移两位吗?先右移两位,在左移两位,最终结果就是,最后2个比特位被清空为0!
需要这么费劲吗?
w & ~3 不香吗?
所以,简洁版:(n+4-1) & ~(4-1)
原码版:( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ),无需先/,在

🍁__crt_va_start(ap, v)

在这里插入图片描述

在这里插入图片描述

	va_list arg;
	__crt_va_start(arg, num);

根据上面代码可知:ap = arg,v = num
那么化简一下,宏定义就可以写成

arg = (char*)(&num)+4

🍁 __crt_va_arg(ap, t)

在这里插入图片描述

int max = __crt_va_arg(arg, int);
  • ap == arg , t == int

宏定义可以简化成:
* (int*)( ( arg +=4 ) - 4 )
有人会说+4再-4不是抵消了吗,那还有什么意义?
答:+=4使arg向下移动4个字节(本质上arg被改变了),为读取下一个有效数据做准备,
-4是为了回到原来的数据,方便处理该数据,一箭双雕!
在这里插入图片描述

🌈4字节对齐(向上取整)

Q:假如我将代码改变一下?

  • 修改后代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//找到最大的数
int FindMaxData(int num, ...)
{
	va_list arg;
	__crt_va_start(arg, num);
	int max = __crt_va_arg(arg, int);
	for (int i = 1; i < num; i++)
	{
		int curr = __crt_va_arg(arg, int);
		if (curr > max)
		{
			max = curr;
		}
	}
	__crt_va_end(arg);
	return max;
}
int main()
{
	char a = 'a';
	char b = 'b';
	char c = 'c';
	char d = 'd';
	int max = FindMaxData(4, 'a', 'b', 'c', 'd');
	printf("max:%d\n", max);
	return 0;
}
  • 运行结果
    在这里插入图片描述

d的ASCII值是100.

Q:为啥传入的参数是char类型的数据,效果也一样?
答:根本原因是它们被整形提升为int 类型。

  • 证明如下:

在这里插入图片描述

🪁 由图可知,每个参数压入栈中都是以4字节为单位压入的。这里发生的就是“整形提升”
🪁顺便说一句:实际传入的参数是char ,short,float时,编译器在编译的时候会自动进行提升。👀
🪁4字节对齐(向上取整)就是取4的倍数:4,8,12,16…

🌈结尾

今天的分享就到这里啦!如果有不对的地方,还请大家多多包涵。欢迎大家在评论区提出宝贵意见!🥳🥳🥳
在这里插入图片描述

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值