我们在C语言中定义一个函数,通常都是需要在函数原型中规定这个函数需要提供什么类型的参数以及需要提供多少个。也就是,你的参数必须明确。但是我们调用函数库中的printf和scanf函数会发现,它们似乎是可以根据我们自己任给的参数类型与参数个数来操作,那它们是怎么实现的呢?

  在《C程序设计语言》中我找到了相关的描述,内容位于第七章输入与输出中的7.3节:可变参数表。

  书中指出:我们想使用可变参数的函数时,首先应当在函数声明中用三个‘.’来代替将来使用可能会出现的参数,且省略号必须位于参数表的尾部。如:

int Test(char *format,...);

  而非是

int Test(...,char *format);

  当然,除了格式控制符,你还可以提供更多的参数表,以完成你需要的操作。

  上面的操作我们只是完成了函数的声明,而函数体的完成,我们需要引入标准头文件<stdarg.h>。里面提供的一些宏可以帮助我们实现可变参数表的遍历。

  首先,它提供了一种参数指针的类型:va_list。我们可以使用它来定义一个指针遍历参数表。如

va_list ap;

  在我们指针正式工作前,还必须使用宏va_start对ap进行初始化,结果是使它指向第一个无名参数。(事实上更准确的描述应该是:以最后一个有名参数为起点,因为事实上是可以不传入无名参的。) 我们需要遍历参数表时,使用宏va_arg来实现。它会将指针在参数表往后挪一个,并给你返回一个值。它需要你自己提供一个参数类型,并把指的这个参数内容按你提供的类型解析。这个参数类型需要你自己对format进行解析得到。当我们完成遍历工作后,还需要使用宏va_end来完成系列清理工作。注意:va_start、va_arg和va_end在头文件中都是采用宏的方式,不信你#undef试试。

  好啦,下面就是我们自写的一个myPrintf函数。

  

#include<stdio.h>
#include<stdarg.h>
//#undef va_end
void myPrint(char *fmt,...);
int main(void)
{
int i = ;
double d = 23.1;
char str[] = "HelloWorld";
myPrint("this is a intVal:%d\n",i);
myPrint("this is a doubleVal:%f\n",d);
myPrint("this is a intVal:%d\n,this is a char *info:%s\n",i,str);
myPrint("this is a test\n");
return ;
}
void myPrint(char *fmt,...)
{
//使用va_list类型声明变量 它会依次引用各个参数
va_list ap;
char *p,*sval;
int ival;
double dval;
int count = ;
va_start(ap,fmt); //宏va_start将ap指向第一个无名参数 在使用ap前该宏必须被调用一次 参数表至少包含一个有名参数
for(p = fmt;*p;p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
count++;
//如果是碰到了% 则判断后一个字符是什么
switch(*++p)
{
case 'd':
ival = va_arg(ap,int);
printf("%d %d",count,ival);
break;
case 'f':
dval = va_arg(ap,double);
printf("%d %f",count,dval);
break;
case 's':
printf("%d ",count);
//先把ap中的对应信息取出来放这里:s_val 然后对这个s_val遍历输出
for(sval = va_arg(ap,char *);*sval;++sval)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap); //结束需要清理工作
}

  当然,我们只是实现了阉割版的printf,其中还缺乏很多功能呢。比如宽度控制、对齐控制以及更多种类型的输出等等都有待完善。

C语言不定型参数函数定义的更多相关文章

  1. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  2. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  3. 【嵌入式开发】C语言 命令行参数 函数指针 gdb调试

    . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/21551397 | http://www.hanshul ...

  4. C语言 命令行参数 函数指针 gdb调试

    . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/21551397 | http://www.hanshul ...

  5. python 可变参数函数定义* args和**kwargs的用法

    python函数可变参数 (Variable Argument) 的方法:使用*args和**kwargs语法.其中,*args是可变的positional arguments列表,**kwargs是 ...

  6. JavaScript 带参数函数定义

    函数的参数parameters在函数中充当占位符(也叫形参)的作用,参数可以为一个或多个.调用一个函数时所传入的参数为实参,实参决定着形参真正的值. 这是带有两个参数的函数, param1 和 par ...

  7. C语言可变参数函数实现原理

    一.可变参数函数实现原理 C函数调用的栈结构: 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 本 ...

  8. c语言可变参数函数

    c语言支持可变参数函数.这里的可变指,函数的参数个数可变. 其原理是,一般情况下,函数参数传递时,其压栈顺序是从右向左,栈在虚拟内存中的增长方向是从上往下.所以,对于一个函数调用 func(int a ...

  9. Python函数定义和使用

    函数是一段可以重复多次调用的代码,通过输入的参数值,返回需要的结果.通过使用函数,可以提高代码的重复利用率.本文主要介绍Python函数的定义.调用和函数参数设置方法. 函数的定义 Python函数定 ...

随机推荐

  1. 关于Angular中时间戳的计算

    前言 使用的是Moment.js 插件,插件的安装详情请参考官方网址(https://momentjs.com/) 正文 步骤一:引用import * as moment from 'moment'; ...

  2. vs 编译error1083

    1)右键查看该项目的属性 2)点击配置属性——〉  C/C++  ——〉  常规  ——〉 附加包含目录——〉将报错文件所在目录添加进去 3) 将项目的本地路径替换为工程相对路径 一般来说,打不开文件 ...

  3. Exchange Server 2016 管理邮箱收发限制

    备注:本文是Exchange Server 2016管理系列的配套课件,更加详细的讲解请参考视频课程,文章结尾有视频课程主页的链接. 进行收发邮件大小的限制是很有必要的,因为邮件服务器不能当作文件服务 ...

  4. Hadoop Spark 基础教程

    0x01  Hadoop 慕课网 https://www.imooc.com/learn/391 Hadoop基础 慕课网 https://www.imooc.com/learn/890 Hadoop ...

  5. CNN识别验证码2

    获得验证码图片的俩个来源: 1.有网站生成验证码图片的源码 2.通过python的requests下载验证码图片当我们的训练样本 我们通过第一种方式来得到训练样本,下面是生成验证码的php程序: &l ...

  6. Kali-linux使用假冒令牌

    使用假冒令牌可以假冒一个网络中的另一个用户进行各种操作,如提升用户权限.创建用户和组等.令牌包括登录会话的安全信息,如用户身份识别.用户组和用户权限.当一个用户登录Windows系统时,它被给定一个访 ...

  7. Kali-linux免杀Payload生成工具Veil

    Veil是一款利用Metasploit框架生成相兼容的Payload工具,并且在大多数网络环境中能绕过常见的杀毒软件.本节将介绍Veil工具的安装及使用. 在Kali Linux中,默认没有安装Vei ...

  8. centos上nginx的安装

    安装步骤:   1.下载nginx,执行:wget http://nginx.org/download/nginx-1.10.2.tar.gz 2.解压,执行:tar vxzf nginx-1.10. ...

  9. 使用Azcopy在Azure上进行HBase的冷热备份还原

    场景 HBase表TaskLog中有20.55G数据(20553078551Byte),目前存放在热存储中,现在要移至冷热储,并进行还原. HBase目录:hbase/data/default 冷目录 ...

  10. SDOI2018 一轮培训划水祭

    \(\mathcal{Day \ \ -3}\) 作为前言来讲,我对于过几天的省选培训还是很期待的--就算我的实力根本不够,名额是学校推荐的,但是能见到\(\mathcal{cwbc}\)以及一众大佬 ...