目录
目录
一、定义
二、声明方式
三、使用
3.1用指针的方式
3.2用宏定义的方式
四、printf的实现
一、定义
一般函数的参数列表是固定的,所以在调用时传入的实参的个数和格式必须和实参匹配;在函数式中,不需要关心实参,直接调用形参即可。
变参函数,就是参数的个数及类型都不确定的函数,常见变参函数如printf、scanf。因为传入的参数列表是不确定的,所以在函数实现时要对传入的参数的个数以及类型进行判断乃至处理。
二、声明方式
声明方式:返回值 函数名(第一个参数, ...);
注:声明或定义变参函数时,第一个参数必须要有,其主要作用是明确到底有多少个参数,以及参数类型,后面的参数用“...”代替。
三、使用
3.1用指针的方式
很多教材以及网上给出的示例代码:
void print_num(int count, ...)
{
int *args;
args = &count + 1;
for( int i = 0; i < count; i++)
{
printf("*args: %d\n", *args);
args++;
}
}
int main(void)
{
print_num(5,1,2,3,4,5);
return 0;
}
这是有问题的!
运行结果:
打印出来的结果是混乱的。
函数实现思路是定义一个变参函数print_num,在函数内部先取得第一个参数的地址赋值给一指针,然后将指针后移,取得后面的参数并打印出来。
错误原因:
1.指针在64位系统中大小为8byte,示例代码中的“args++;”偏移的是一个int数据类型的大小,即4byte。
2.第一个参数和第二个该参数之间的距离为28byte。通过“gcc -S 源文件”命令,生成后缀为“.s”的汇编代码文件,在汇编文件中找到的这个信息,具体原因我也还不知道。
从上图可以看到在函数print_num中,第一个参数位置为196,第二个参数位置为168,二者之间相差28byte。从第二个参数开始,后续参数之间的距离为8byte。
关于这个示例代码的错误,这篇文章很有参考价值:https://www.cnblogs.com/lularible/p/15129183.html
正确代码:
void print_num(int count, ...)
{
char *args;
args = (char *)&count + 28;
for( int i = 0; i < count; i++)
{
// printf("addr:%p\n",args);
printf("*args: %d\n", *(int*)args);
args+=8;
}
}
运行结果:
3.2用宏定义的方式
头文件:#include <stdarg.h>
头文件中定义的常用的宏:
va_list:定义在编译器头文件中 typedef char* va_list;
void va_start(va_list ap, last);提取第一个参数last后面的第一个参数的地址,并赋值给va_list类型的指针变量ap;
type va_arg(va_list ap, type); 返回下一个参数的地址,返回类型为type,每次执行时是返回的上一次返回的参数的下一个参数,而不是每次都是第二个参数;
注意:
type传char型的时候会自动转换为int类型;
type传float类型的时候会自动转换为double类型。
void va_end(va_list ap); 销毁va_list类型的指针变量ap,并将其赋值为空。
函数实现:
void print_num(int count, ...)
{
va_list arg; //声明一个va_list类型的指针变量
va_start(arg,count);//初始化指针变量arg为count的下一个参数的地址
int temp;
for(int i=0;i<count;i++)
{
temp = va_arg(arg,int); //返回后续的参数
printf("*args: %d\n", temp);
}
va_end(arg); //销毁这个指针变量
}
关于使用宏来做变参函数,这篇博客具有很好的参考价值,但使用指针实现部分也是有一点问题的。https://blog.csdn.net/sinat_31039061/article/details/128338331
更新:
四、printf的实现
printf作为两个最常用的变参函数之一,简直是变参函数的标准范例。
函数原型:
int printf(const char *format, ...);
功能:将指定格式数据输出到屏幕终端上(先将数据发送到标准输出缓冲区)
实现思路:
- 可以先写一个模拟打印一个字符的底层API:send;
- 对于普通字符,直接调用send打印即可;
- 对于指定格式的数据,通过'%'检测,当遇到‘%’时根据后续的字符来判断数据格式;
- 对于不同格式的数据,虽然实现过程和方法不一致,但其核心思路就是将其拆解字符来打印。
整形数据的打印:
1、如果是负数,就先打印负号,并将其变为正数。
2、先计算这个数字是几位数
3、将每一位数字拆解出来打印
//打印整数
void my_printf_int(int num)
{
int len ,temp;
if(num < 0) //负数
{
send('-');
num = -num;
}
//计算数字的位数
len=0;
temp = num;
do
{
len++;
temp/=10;
}while (temp > 0);
//将每一位数字拆解出来打印
for (int i = len-1; i >=0; i--)
{
send('0'+num/((int)pow(10,i))%10);
}
}
浮点数需要先用两个整形数来分别保存其整数部分和小数部分,再分别进行打印。文章来源:https://www.toymoban.com/news/detail-410621.html
//浮点型数据分别用两个整形数据表示整数和小数部分
void ftoi(float n,int *integer,int *decimal)
{
//取出整数部分
*integer = (int) n;
//取出小数部分
float temp = n - *integer;
*decimal =(int) (temp *pow(10,6));
for (int i = 0; i < 6; i++) //去掉无效数据
{
if(*decimal%10 == 0)
{
*decimal /=10;
}
else
break;
}
// printf("integer=%d\tdecimal=%d\n",*integer,*decimal);
}
完整代码:文章来源地址https://www.toymoban.com/news/detail-410621.html
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
//printf的实现
int my_printf(const char *format, ...);
// 模拟发送一个字符
void send(char c);
//浮点型数据,分别用两个整形数据存储整数和小数部分
void ftoi(float n,int *integer,int *decimal);
//打印整数
void my_printf_int(int num);
int main(void)
{
char c='a';
int n=1234;
char *str = "hello world";
float pi=3.1415926;
my_printf("c:%c\td:%d\ts:%s\tf:%f\n",c,n,str,pi);
return 0;
}
int my_printf(const char *format, ...)
{
va_list args; // 声明一个va_list类型的指针变量
va_start(args, format); // 将args指向format的下一个参数
int count = 0;
char ch;
int num,integer,decimal;
char *str;
double f;
while (*format != '\0')
{
if (*format == '%') // 遇到%
{
format++; // 跳过%
switch (*format)
{
case 'c': //字符型
{
ch = (char)va_arg(args , int);
send(ch);
break;
}
case 'd': //整形
{
num = va_arg(args,int);
my_printf_int(num);
break;
}
case 's': //字符串
str = va_arg(args,char *);
while (*str != '\0')
{
send(*str++);
}
break;
case 'f': //float类型
{
f = (float)va_arg(args,double);
//将浮点型转换成两个整形
ftoi(f,&integer,&decimal);
//打印整数部分
my_printf_int(integer);
//打印小数点
send('.');
//打印小数部分
my_printf_int(decimal);
break;
}
default:
break;
}
count++;
format++;
}
else //遇到普通字符,直接打印
send(*format++);
}
return count;
}
// 发送一个字符
void send(char c)
{
printf("%c", c);
}
//打印整数
void my_printf_int(int num)
{
int len ,temp;
if(num < 0) //负数
{
send('-');
num = -num;
}
//计算数字的位数
len=0;
temp = num;
do
{
len++;
temp/=10;
}while (temp > 0);
for (int i = len-1; i >=0; i--)
{
send('0'+num/((int)pow(10,i))%10);
}
}
//浮点型数据分别用两个整形数据表示整数和小数部分
void ftoi(float n,int *integer,int *decimal)
{
//取出整数部分
*integer = (int) n;
//取出小数部分
float temp = n - *integer;
*decimal =(int) (temp *pow(10,6));
for (int i = 0; i < 6; i++)
{
if(*decimal%10 == 0)
{
*decimal /=10;
}
else
break;
}
// printf("integer=%d\tdecimal=%d\n",*integer,*decimal);
}
到了这里,关于C语言——变参函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!