从main函数参数,printf多参数来了解C语言可变参数函数


一般在C语言中,我们使用的main函数都是不带参数的,但实际上main函数是可以带参数的。

C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为:

main (argc,argv)

C语言还规定argc(第一个形参)必须是整型变量,argv(第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为:

int main(int argc, char** argv)
或int main(int argc, char* argv[])

由于main函数不能被其它函数调用,因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢?实际上,main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。

例如有命令行为:
C:>E24 BASIC foxpro FORTRAN
由于文件名E24本身也算一个参数,所以共有4个参数,因此argc取得的值为4。argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。 指针数组的长度即为参数个数。数组元素初值由系统自动赋予。其表示如图所示:

image-20210728214948116

main(int argc,char argv){
while(argc–>1)
printf(“%s\n”,
++argv);
}

本例是显示命令行中输入的参数。如果上例的可执行文件名为e24.exe,存放在A驱动器的盘内。因此输入的命令行为:
C:>a:e24 BASIC foxpro FORTRAN
则运行结果为:
BASIC
foxpro
FORTRAN
该行共有4个参数,执行main时,argc的初值即为4。argv的4个元素分为4个字符串的首地址。执行while语句,每循环一次argv值减1,当argv等于1时停止循环,共循环三次,因此共可输出三个参数。在printf函数中,由于打印项*++argv是先加1再打印, 故第一次打印的是argv[1]所指的字符串BASIC。第二、三次循环分别打印后二个字符串。而参数e24是文件名,不必输出。

但是这带来了一个疑问:在C语言中是没有重载的,因此我们在声明定义函数时应该是要确定其有几个函数参数的。那为什么main函数可以不带参数,也可以带两个参数呢?

这个先按下不表,后面一起解答。


C语言中的printf函数应该是我们使用频率最高的库函数了吧?

我们在使用printf函数时,是可以根据需要选择输入参数的。

printf(“%d”,a);//两个参数
printf(“%d %d”,a ,b);//三个参数
printf(“%d %d %d”,a ,b ,c);//四个参数
……

看起来这个函数和我们之前用的函数时不一样的,之前我们用的函数都是必须确定函数参数,那为啥printf函数的参数是可变的呢?

看来这里面应该有一种机制来实现这种函数。先按下不表,我们来看看printf函数的源码实现

int printf
(
const char * fmt, /* format string to write /
… /
optional arguments to format string /
)
{
va_list vaList; /
traverses argument list */
int nChars;

va_start (vaList, fmt);
nChars = fioFormatV (fmt, vaList, printbuf, 1);
va_end (vaList);

return (nChars);
}

typedef struct {
char a0; / pointer to first homed integer argument /
int offset; /
byte offset of next parameter */
} va_list;

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

/*******************************************************************************
*

  • printbuf - printf() support routine: print characters in a buffer
    */

LOCAL STATUS printbuf
(
char *buf,
int nbytes,
int fd
)
{
return (write (fd, buf, nbytes) == nbytes ? OK : ERROR);
}


C++中有函数重载这种方法,以供我们调用时要可以不确定实参的个数,其实 C 语言也可以,而且更高明!

我们在stdio.h 中可以看到 printf() 函数的原型:

int printf(char * format,…)

事实上,我们如果要写这样的函数也可以类似的写,那么在定义函数时用上这个符号“ … ” ,它叫占位符,喊它 “ 三个点 ” 也可以,只要你愿意!那么我可以这样定义我的函数:

fun(int a,…) { }

且听我介绍3 个小东东:

1、 va_list

2、 va_arg()

3、 va_start()

在学习这3 个小东东之前,我们先回忆一下, C 语言是怎么操作文件时,是怎么样处理内存中的数据的呢?学习文件操作时,我们提到了“流”的概念,我们用指针指向数据所在的内存地址,再一个一个的操作。

学习指针时,我们知道有函数指针这个东东,不是指针函数而是函数打针哦!(呵呵,我的同学如果还记得就当复习一下,不要嫌我啰嗦_ )。我们记得程序在执行时,会将函数存储到内存中去。现在深入的讲一点点,存储函数时,参数传递的过程是怎样实现的呢?所谓的形式参数(局部变量)实质上又是什么呢?把这些问题连起来想想,想通了,你的思维势如破竹!

在调用函数时,程序同样会把实参传入,在函数存储区保存起来,如果有很多参数,将一起保存起来。

这时候就要用到va_list 了,这是个类型定义,我们可以把它理解成一个指针,它指向第一个参数的地址。

如果,我们这样定义: va_list pp ;

则pp 就是这样一种变量,它是指向所有参数中的第一个参数的。它不同于一般的指针变量,它是个复合变量,什么是复合变量啊?结构体类型的嘛,呵呵。如果 a 是第一个参数,能不能写成 pp=a 呢?

假设我定义了char d[]=“ruixin”,e[]=“gelin”; 我要把 e 的值赋给 d ,能不能写成 d=e 呢?得用 strcpy() ,是吧!呵呵,一样的道理,这儿我们也用一个函数来实现,它就是 va_start();

如果这样写:va_start(pp,a);

那么pp 就指向第一个参数 a 了,并且可得到 a 的类型 int 。

这时候如果有下一个参数,就需要使pp 指向下一个参数,并且得到它的类型。同样需要使用函数来实现,这个函数是: va_arg()

可以这样写:va_arg(pp, 类型 ) ,这样 pp 就指向一个参数,并且可以得到那个参数的类型了。

注意!类型非常重要,学过指针的都应该清楚,指针的类型如果弄错的话,位置正确,取出来的数可能也是乱七八糟的。

下面我们看一个简单的例子:

#include <stdio.h>
#include<stdarg.h>
void fun(int a,…)
{
va_list pp;
int n=1;//使用 n 计量参数个数
va_start(pp,a);
do {
printf(“第 %d 个参数 =%d/n”,n++,s);
a=va_arg(pp,int);//使 pp 指向下一个参数,将下一个参数的值赋给变量 a
}
while (a!=0);//直到参数为 0 时停止循环
}
main()

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-RGzCrKYE-1712936428124)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值