[C语言]可变参数函数
引言
在C语言中,函数可以定义固定数量和类型的参数,但是编程过程种可能会遇到函数传入的参数个数是不确定的情况,例如printf
,sprintf
等,这时可变参数是非常有用的。通过使用可变参数功能,可以编写出更灵活和通用的函数。
C语言中的可变参数
C语言中,可变参数是通过标准库的 stdarg.h
头文件提供的宏来实现的。stdarg.h
定义了一组宏,用于处理可变参数的读取。
可变参数函数的定义方式如下:
#include <stdarg.h>
bool function(int a, ...);
bool function(int a,float b, ...);
-
1、需要包含
stdarg.h
头文件。头文件中定义了一些宏和类型,用于操作可变参数列表。 -
2、其中,可变参数函数必须至少有一个固定参数(即 int a,这里也可以写其他类型),用于计算可变参数列表的起始地址。
-
3、而第二个参数使用“…”表示可变参数,作为参数占位符。这个时候我们需要在函数体内使用
stdarg.h
头文件提供的宏来访问可变参数列表。
可变参数的内存结构图,在调用可变参数函数时,编译器会将所有参数依次压入栈中,并记录参数的个数和类型:
下面是一些常用的宏,以下的宏的源码是x86平台VC6.0编译器中的,不同的编译器实现方法可能不同,但是思想是一致的:
va_list
类型
typedef char* va_list;
va_list
是一个用于存储可变参数的类型。我们可以将 va_list
变量作为函数参数来接收可变参数,从源码上来看其实是一个char类型指针。
va_start
宏
//stdarg.h 中
#define va_start __crt_va_start
//vadefs.h 中
#define _ADDRESSOF(v) (&(v)) //对变量v取地址
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) //将n的数据内存大小化为int长度的整数倍
将_INTSIZEOF分成两部分 (sizeof(n) + sizeof(int) - 1) 和 ~(sizeof(int) - 1)
(sizeof(n) + sizeof(int) - 1) => (sizeof(n) + 3)
sizeof(n)是计算变量内存大小 最小单位是1字节
sizeof(int)是4字节
所以(sizeof(n) + sizeof(int) - 1)最小是4字节 转成二进制就是 bit1以上位一定有值
~(sizeof(int) - 1) //注意 ~ 是位操作符 取反的意思
~(4 - 1)
~(3)
~(11b) //3 的二进制是 11
11111100b
(sizeof(n) + 3) & 11111100b
两者相与 11111100b的低两位是0 结果值说明低两位为0
而前面说了前部分最小值为4,bit1以上位一定有值,所以得出结果值一定是 xxxxxx00 ,一定是4的倍数
因此_INTSIZEOF(n) 是将n的数据内存大小化为int长度(4)的整数倍
//stdarg.h 中
#define va_start __crt_va_start
//vadefs.h 中
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)
ap = 变量v首地址 + 变量v压栈进入的内存大小
ap 得到可变参数的第一个参数的首地址
va_start(va_list ap, last_arg);
va_start
宏用于初始化 va_list
变量,以便访问可变参数。它接受两个参数,
-
第一个是
va_list
变量,指向可变参数列表中的第一个参数, -
第二个是固定参数的最后一个参数变量,上述例子种,例子1即是 int a ,例子2即是 float b。
va_arg
宏
//stdarg.h 中
#define va_arg __crt_va_arg
//vadefs.h 中
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)
(ap = (ap + _INTSIZEOF(t))) - _INTSIZEOF(t)
(ap = (ap + _INTSIZEOF(t))) ap是当前参数的地址,用ap加上当前参数的内存大小计算出可变参数列表中下一个参数的地址并记录下在ap 方便下次用
(ap - _INTSIZEOF(t)) 再用下一个参数地址减去本次参数内存大小,又计算出当前的参数地址
(*(t*) (ap - _INTSIZEOF(t)) ) 最后强制类转换,重新计算返回当前参数的值
//va_arg(va_list ap, type);
//例子
va_arg(ap,int);
因此va_arg
宏用于获取可变参数列表中下一个参数,并将其转换为指定的类型type
。所以它接受两个参数,第一个是 va_list
变量,第二个是要获取的参数类型,每次调用va_arg
,都会自动将指针移动到下一个参数的位置,即执行完后宏返回下一参数值,而ap指向下一次参数
va_end
宏
//stdarg.h 中
#define va_end __crt_va_end
//vadefs.h 中
#define __crt_va_end(ap) ((void)(ap = (va_list)0)) //结束可变参数的访问 将指针置0
va_end(va_list ap);
va_end
宏用于清理 va_list
变量。在使用完 va_list
变量后,应该调用 va_end
来释放资源。
以上是对 x86平台VC6.0编译器中的源码分析,在vadefs.h
中也可以看到其他的编译器的实现方案,但是他们统一封装成一样的接口使用方法一致。
示例代码 求整数和
#include <stdio.h>
#include <stdarg.h>
int int_sum(int cnt, ...)
{
va_list arg_list;//声明存储可变参数的列表变量
int total = 0;
va_start(arg_list, cnt);//初始化变量
for (int i = 0; i < cnt; i++)
{
int num = va_arg(arg_list, int);//循环遍历获取参数,并强转
total += num;
}
va_end(arg_list);//结束可变参数列表的使用,释放空间
return total;
}
int main()
{
int result = int_sum(3, 1, 2, 3);
printf("Sum1: %d\n", result);//打印结果:Sum1: 6
result = int_sum(5, 2, 0, 3,-1,3);
printf("Sum2: %d\n", result);//打印结果:Sum2: 8
result = int_sum(1, 9);
printf("Sum3: %d\n", result);//打印结果:Sum3: 9
return 0;
}
上面的示例代码中,我们定义了一个 int_sum
函数,它接受可变数量的整数参数。在 int_sum
函数内部,我们使用 va_list
变量 arg_list
来存储可变参数。通过调用 va_start
宏,我们初始化了 arg_list
,然后使用 va_arg
宏来获取每个参数的值,并将它们相加。最后,我们调用 va_end
宏来清理 arg_list
。
在 main
函数中,我们调用了 三次int_sum
函数,并传递了不同个数的整数参数。int_sum
函数将这些参数相加,并返回结果。然后,在 main
函数中将结果打印出来。
示例代码 实现一个my_printf
#include <stdarg.h>
#include <stdio.h>
int my_printf(const char *format, ...)
{
int count = 0;
va_list args;
va_start(args, format);
while (*format != '\0') {
if (*format == '%') {
format++;
switch (*format) {
case 'd': {
int value = va_arg(args, int);
int divisor = 1;
// 处理负数情况
if (value < 0) {
putchar('-');
value = -value;
}
// 计算除数,确定最高位的位置
int temp = value;
while (temp > 9) {
temp /= 10;
divisor *= 10;
}
// 逐位输出数字字符
while (divisor > 0) {
int digit = value / divisor;
putchar('0' + digit);
value %= divisor;
divisor /= 10;
}
break;
}
case 's': {
char* str = va_arg(args, char*);
while (*str != '\0') {
putchar(*str);
str++;
}
break;
}
case 'c':{
char c = (char)va_arg(args, int);
putchar(c);
break;
}
default:
break;
}
} else {
putchar(*format);
}
format++;
}
va_end(args);
return count;
}
int main()
{
my_printf("abcd %d %s %c\n",10,"we",49);//abcd 10 we 1
}
在上述示例中,我们通过循环遍历字符串 format
,并根据格式化字符 %
的后续字符类型进行不同类型的输出。实现和printf类似的功能。需要注意的是,上述实现只是一个简单的示例,实际的 printf 函数实现可能更加复杂。建议查阅你所使用的编译器的文档以了解更多关于 printf 函数的具体实现细节。
可变参数函数的注意事项
在编写可变参数函数时,需要注意以下几个问题:
- 可变参数函数必须至少有一个固定参数,用于计算可变参数列表的起始地址。
- 在可变参数列表中,除了float类型转换为
double
和long double
类型以外,其他类型都可以自动转换为int
类型 ,即不能出现 char short float 等类型,且若int类型大小不满足,则提升为unsigned int。 - 可变参数函数中不能直接修改可变参数列表中的参数。如果需要修改参数,可以先将其复制到一个局部变量中,然后再进行修改。
总结
本文介绍了C语言中的可变参数。可变参数是指函数能够接受不确定数量的参数。在C语言中,可变参数是通过 stdarg.h
头文件提供的宏来实现的。使用可变参数的基本步骤包括定义 va_list
类型的变量、使用 va_start
宏初始化 va_list
变量、使用 va_arg
宏获取参数的值、使用 va_end
宏清理 va_list
变量。
可变参数在编写灵活、通用的函数时非常实用。通过使用可变参数,我们可以编写能够接受不确定数量的参数的函数,并对这些参数进行相应的操作。
希望本文对你理解和使用C语言中的可变参数有所帮助。欢迎讨论。
下一篇宏的可变参数和应用
参考资料:
- C语言可变参数详解
详解-优快云博客](https://blog.youkuaiyun.com/d137578736/article/details/100568750?ops_request_misc=&request_id=&biz_id=102&utm_term=c语言可变形参&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-100568750.142v96pc_search_result_base9&spm=1018.2226.3001.4187))