【可变参数列表如何可变?】

可变参数列表

本章重点

学会使用可变参数列表的使用与原理

函数传参补充知识

  • 如果函数没有形式参数,仍然可以给函数传递参数。
  • 在c语言中,只要发生了函数调用并且传递了函数,必定形成临时变量。
  • 所谓的临时拷贝本质就是在栈帧内部形成的,从右向左依次形成临时拷贝(变量)。

求两个数据中的最大值

#include <stdio.h>
//求两个数据中的最大值
int FindMax(int x, int y)
{
    if (x > y) {
        return x;
    }
    return y;
}
int main()
{
    int x = 0;
    int y = 0;
    printf("Please Eneter Two Data# ");
    scanf("%d %d", &x, &y);
    int max = FindMax(x, y);
    printf("max = %d\n", max);
    return 0;
}
  • 如果未来我们的需求变了,不再求固定的数据个数的最大值
  • 而是求任意多个数据中的最大值(至少一个),要求不能使用数组
  • 因为目前参数个数不确定,那么函数编写的时候,参数个数也无法确定,换句话说,函数也就没法编写
  • 不过,C提供了满足该场景的解决方案:可变参数列表

#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
//可变参数列表至少有一个参数
int FindMax(int num, ...)//可变参数列表
{
	va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
	va_start(arg, num); //使arg指向可变参数部分
	int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
	int i = 0;
	for (i = 0; i < num - 1; i++) {//获取并比较其他的
		int curr = va_arg(arg, int);
		if (max < curr) {
			max = curr;
		}
	}
	va_end(arg); //arg使用完毕,收尾工作。本质就是将arg指向NULL
	return max;
}
int main()
{
	int max = FindMax(5, 11, 22, 33, 44, 55);
	printf("max = %d\n", max);//55
	system("pause");
	return 0;
}
  1. 使用va_list类型的变量声明一个可变参数列表的访问指针,通常命名为arg

  2. 使用va_start(arg, last_fixed_param)宏来初始化可变参数列表。last_fixed_param是最后一个固定参数的名称,也就是num,在这个参数之后是可变数量的参数。这个宏会将arg指针指向可变参数列表的起始位置。

  3. 使用va_arg(arg, type)宏来获取可变参数列表中的参数值。type是参数的数据类型。该宏从可变参数列表中获取下一个参数的值,并将arg指针移动到下一个参数的位置。

  4. 使用va_end(arg)宏来结束对可变参数列表的访问。这个宏执行一些必要的清理工作,并将arg指针置为NULL

如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗? 

#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
int FindMax(int num, ...)
{
	va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
	va_start(arg, num); //使arg指向可变参数部分
	int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
	for (int i = 0; i < num - 1; i++) {//获取并比较其他的
		int curr = va_arg(arg, int);//va_arg(arg, char),这是不正确的
		if (max < curr) {
			max = curr;
		}
	}
	va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULL
	return max;
}
int main()
{
	char a = '1'; //ascii值: 49
	char b = '2'; //ascii值: 50
	char c = '3'; //ascii值: 51
	char d = '4'; //ascii值: 52
	char e = '5'; //ascii值: 53
	int max = FindMax(5, a, b, c, d, e);
	printf("max = %d\n", max);
	system("pause");
	return 0;
}

先来解释一下汇编代码中的movsx是什么意思?

通过查看汇编,我们看到,在可变参数场景下:

1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过movsx指令进行到当前计算机寄存器的位数)

2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行。

注意事项

  • 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
  • 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
  • 这些宏是无法直接判断实际存在参数的数量。
  • 这些宏无法判断每个参数的类型。
  • 如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。

原理

  1. 可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧。
  2. 栈帧形成前,临时变量是要先入栈的,根据之前所学,参数之间位置关系是固定的。
  3. 通过上面汇编的学习,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确。

我们接下来就看看这几个宏的含义:

1、va_list arg;

 2、va_start(arg, num);

3、va_end(arg);

现在我们来重点了解一下这个宏#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

前提: 为了后面方便表述,我们假设sizeof(n)的值是n(char 1,short 2, int 4)

        我们在32位平台,vs2013下测试,sizeof(int)大小是4,其他情况我们不考虑

根据上面学到的知识,_INTSIZEOF(n)中的参数可以是变量类型或者变量名。

我们来计算一下参数为char和short,该宏的值为多少?

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

结果都是4个字节 

_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,其实就是一种4字节对齐的方式

是什么: 比如n是:1,2,3,4 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 4

                比如n是:5,6,7,8 对n进行向 sizeof(int) 的最小整数倍取整的问题 就是 8

为什么:要有这个4字节对齐:

              结合之前栈帧的学习、上面代码的测试结果和movsx指令我们就可以知道

怎么办到的

 

本章结束啦,再见啦。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值