在日常的写代码过程中,我们会发现 printf() 函数可以实现任意个数的数字的打印:
#include <stdio.h>
int main()
{
printf("%d,%d\n", 1, 2);
printf("%d,%d,%d,%d\n", 1, 2, 3, 4);
}
但是让我们自己写一个函数,例如:求两个数的平均值的函数 Avg(int num1,int num2) 这个函数仅仅只能实现两个数求平均值的功能,如果要实现一个三个数或者四个数求平均值的函数,我们就要重写 Avg() 函数,但是求平均值的这一类问题的解决方法又是一样的,重写多个Avg() 函数实现任意个数数字的平均值无疑是非常的麻烦,而且技术含量不高,那么我们可不可以向 printf() 函数那样写一个可以求任意个数数字的平均值函数Avg() ?我们可以了解一下printf() 函数为什么可以实现任意个数的数字的打印功能,先看一下 printf() 函数 的函数声明:
printf(_In_z_ _Printf_format_string_ const char * _Format, ...)
这个函数和我们平时写的函数最大的不同就是它的参数列表里出现了个" ... " 那我们也可以考虑利用这个不同来实现任意个数数字的平均值函数 Avg() 代码如下:
#include <stdio.h>
#include <stdarg.h>
int Avg(int n, ...)
{
int sum = 0; //个个数求和的标记,将其初始化为0
va_list list; //游标(特殊的指针),相当于char *list
va_start(list, n); //找到list的开头
for (int i = 0; i < n; i++)
{
sum += va_arg(list, int); //在list中取一个int型数据,加到sum上
}
va_end(list); //关闭list,也就是将游标置空
return sum/n; //和对n个数求的平均值并返回
}
int main()
{
printf("%d\n", Avg(1, 10));
printf("%d\n", Avg(2, 10, 20));
printf("%d\n", Avg(3, 10, 20, 30));
printf("%d\n", Avg(4, 10, 20, 30, 40));
}
//注:本例可实现不同个数的int型数字的求平均值功能,且第一个参数必须是准确的需要求和的数字的个数!
虽然这个函数可以实现我们想要的功能,但是我们必须要了解原理才能实现更多的诸如此类的函数的实现,接下来我们可以在看 一个代码:
#include <stdio.h>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int i = 1;
printf("%d,%d\n", arr[i], arr[i + 1]);
printf("%d,%d\n", arr[i], arr[i++]);
}
/*
注:输出结果分别为:
2,3
3,2
为什么是这样?
*/
函数中的普通的变量是在 栈结构 内保存的,当我们在传入参数时,参数的入栈方式是自右往左入栈,再根据栈的 先进后出 的特性我们就能将传进去的参数一一对应,如图:
接下来我们在回过头看之前代码中的函数 int Avg(int n, ...) 自己画个图来看看参数是否按照我们预期的那样对应传递,在这个函数中,参数列表里的 int n 必须有,它的作用有两个(可变参数编程必须有类似 int n 这样的线索,比如 printf("%d",5)中的 "%d" 就起到了和 int n 一样的作用):
(1)找到 ... (找到输入的其它数)
(2) 记录 ... 中数据的个数 (记录输入其它数的个数)
接下来搞一个稍微有意思的,写一个MyPrint()实现简单版的printf()函数的功能:
首先我们要清楚的是:能在屏幕上打印出来的都是字符的形式
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
void MyPrint(char *str, ...)
{
char get_ch;//保存得到的字符数据
int get_int;//保存的得到的整形数据
double get_double;//保存的得到的浮点型数据
char *get_string;//保存得到的字符串数据
char buf[100];//定义一个数组用来保存将double/int/字符串转化为字符串的结果
va_list list;
va_start(list, str);
while (*str != '\0')//如果str不为空
{
if (*str == '%')//判断当前位置是否为标记 ‘%’,是则继续
{
switch(*(str+1)) //分类判断 % 一位后是不是c/s/f/d
{
case 'c':
get_ch = va_arg(list, char);
putchar(get_ch);
str += 2; //操作结束后将‘%’和‘c’两个字符跳过
break;//结束退出switch
case 'd':
get_int = va_arg(list, int);
//将整数转化为字符串
sprintf(buf, "%d", get_int);
//输出buf中保存的数据
fwrite(buf, sizeof(char), strlen(buf), stdout);
str += 2;//操作结束后将‘%’和‘d’两个字符跳过
break;//结束退出switch
case 'f':
get_double = va_arg(list, double);
//将浮点数转化为字符串
sprintf(buf, "%f", get_double);
//输出buf中保存的数据
fwrite(buf, sizeof(char), strlen(buf), stdout);
str += 2;//操作结束后将‘%’和‘f’两个字符跳过
break;//结束退出switch
case 's':
get_string = va_arg(list, char*);
//将字符串转化为字符串
sprintf(buf, "%s", get_string);
//输出buf中保存的数据
fwrite(buf, sizeof(char), strlen(buf), stdout);
//本来可以使用puts()函数输出,但此函数自带回车,不推荐使用
str += 2;//操作结束后将‘%’和‘s’两个字符跳过
break;//结束退出switch
}
}
else//不是‘%’则直接输出
{
putchar(*str);//输出
str += 1;//指针后移一位方便下一步判断
}
}
va_end(list);
}
int main()
{
//printf("%c,%d,%f,%s\n", 'a',20, 25.6,"hello world");
MyPrint("%c,%d,%f,%s\n", 'a',20, 25.6,"hello world");
return 0;
}
注:水平有限,如果有错误请指教。