C语言中的可变参数函数是一种可以接受不定数量和类型的参数的函数,它可以用来实现一些灵活和通用的功能,例如printf, scanf等。本文将介绍C语言中的可变参数函数的原理和实现,以及如何编写自定义的可变参数函数。
可变参数函数的原理
可变参数函数的原理与函数调用的栈结构相关,正常情况下C的函数参数入栈规则为,它是从右到左的,即函数中的最右边的参数最先入栈。例如,对于函数:
void fun(int a, int b, int c) {int d;...
}
其栈结构如下:
0x1ffc-->d
0x2000-->a
0x2004-->b
0x2008-->c
对于编译器来说,每个栈单元的大小都是sizeof(int),而函数的每个参数都至少要占一个栈单元大小,如函数
void fun1(char a, int b, double c, short d) {...
}
对一个32位系统其栈的结构就是
0x1ffc-->a (4字节)(为了字对齐)
0x2000-->b (4字节)
0x2004-->c (8字节)
0x200c-->d (4字节)
由此可以看出,函数的所有参数是存储在连续的栈空间中的,基于这种存储结构,这样就可以从可变参数函数第一个普通参数来寻址后续的所有可变参数的类型及其值。同时也说明即使是可变参数,但也必须有至少一个普通的参数,以便定位可变参数的起始位置。
可变参数函数的实现
C语言中提供了一套用于处理可变参数列表的标准库函数,从而使得我们能够以一种简单的方式实现可变参数函数。这些标准库函数定义在stdarg.h头文件中,主要有以下几个:
- va_list类型:这是一个用于存储可变参数列表信息的数据类型,它实际上是一个指向栈空间中某个位置的指针。
- va_start宏:这个宏用于初始化va_list类型的变量,使其指向第一个可变参数。它有两个参数,第一个是va_list类型的变量名,第二个是可变参数列表前面最后一个普通参数。
- va_arg宏:这个宏用于获取当前指向的可变参数的值,并使va_list类型的变量指向下一个可变参数。它有两个参数,第一个是va_list类型的变量名,第二个是要获取的可变参数的类型。
- va_end宏:这个宏用于结束对可变参数列表的访问,并释放相关资源。它有一个参数,就是va_list类型的变量名。
可变参数函数的编写
利用上述标准库函数,我们可以编写自定义的可变参数函数。下面给出两个例子:
- 例子一:实现一个求平均值的可变参数函数
#include <stdio.h>
#include <stdarg.h>// 定义一个求平均值的可变参数函数
// 第一个参数n表示后面有多少个数参与求平均值
// 后面n个数都是double类型
double average(int n, ...) {// 定义一个va_list类型的变量apva_list ap;// 初始化ap,使其指向第一个可变参数va_start(ap, n);// 定义一个累加和sum和计数器idouble sum = 0.0;int i;// 循环遍历所有可变参数,并累加到sum中for (i = 0; i < n; i++) {sum += va_arg(ap, double);}// 结束对可变参数列表的访问va_end(ap);// 返回平均值return sum / n;
}int main() {// 调用可变参数函数,求三个数的平均值printf("The average of 1.0, 2.0 and 3.0 is %f\n", average(3, 1.0, 2.0, 3.0));// 调用可变参数函数,求五个数的平均值printf("The average of 1.0, 2.0, 3.0, 4.0 and 5.0 is %f\n", average(5, 1.0, 2.0, 3.0, 4.0, 5.0));return 0;
}
输出:
The average of 1.0, 2.0 and 3.0 is 2.000000
The average of 1.0, 2.0, 3.0, 4.0 and 5.0 is 3.000000
- 例子二:实现一个类似printf的可变参数函数
#include <stdio.h>
#include <stdarg.h>// 定义一个类似printf的可变参数函数
// 第一个参数fmt是格式化字符串,用于指定后面可变参数的类型和输出格式
// 后面是可变参数列表,可以是任意类型和数量
void my_printf(char *fmt, ...) {// 定义一个va_list类型的变量apva_list ap;// 初始化ap,使其指向第一个可变参数va_start(ap, fmt);// 定义一个字符型变量c,用于存储格式化字符串中的每个字符char c;// 循环遍历格式化字符串中的每个字符while ((c = *fmt++) != '\0') {// 如果当前字符是%,表示后面跟着一个格式符,用于指定输出的类型和格式if (c == '%') {// 获取下一个字符,即格式符c = *fmt++;// 根据不同的格式符,调用不同的标准输出函数,输出对应的可变参数值switch (c) {case 'd': // 整型printf("%d", va_arg(ap, int));break;case 'f': // 浮点型printf("%f", va_arg(ap, double));break;case 'c': // 字符型printf("%c", (char)va_arg(ap, int));break;case 's': // 字符串型printf("%s", va_arg(ap, char *));break;default: // 其他情况,原样输出字符和格式符printf("%c%c", '%', c);break;}} else {// 如果当前字符不是%,则原样输出该字符printf("%c", c);}}// 结束对可变参数列表的访问va_end(ap);
}int main() {// 调用可变参数函数,输出一些不同类型和格式的数据my_printf("This is a test: %d + %d = %d\n", 1, 2, 3);my_printf("This is another test: %f * %f = %f\n", 1.5, 2.5, 3.75);my_printf("This is a char: %c\n", 'A');my_printf("This is a string: %s\n", "Hello");return 0;
}
输出:
This is a test: 1 + 2 = 3
This is another test: 1.500000 * 2.500000 = 3.750000
This is a char: A
This is a string: Hello
总结
C语言中的可变参数函数是一种可以接受不定数量和类型的参数的函数,它可以用来实现一些灵活和通用的功能。本文介绍了C语言中的可变参数函数的原理和实现,以及如何编写自定义的可变参数函数。