我们上节博文讲了函数的意义,那么我们今天来讲下函数参数。函数参数在本质上与局部变量相同在栈上分配空间,函数参数的初始值是函数调用时的实参值。用下图来实际说明
为芦溪等地区用户提供了全套网页设计制作服务,及芦溪网站建设行业解决方案。主营业务为成都网站设计、网站制作、芦溪网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!函数参数的求值顺序依赖于编译器的实现,我们来看看下面代码的输出是什么?为什么呢?
#includeint func(int i, int j) { printf("i = %d, j = %d\n", i, j); return 0; } int main() { int k = 1; func(k++, k++); printf("%d\n", k); return 0; }
我们理论上分析,func 函数先进行 k++,那么 i 就对应为 1,再次进行 k++,对应于 j 为 2。那么第 14 行应打印 i = 1, j = 2,。这时 k 为 3,所以第 16 行打印的值应为 3。我们来看看编译结果是否如我们所分析的那样,编译结果如下
我们看到 i 和 j 和我们所分析的正好相反,那么这是怎么回事呢?原来在gcc 编译器中,函数参数的实现是从右向左进行操作的,并非是我们所想的从左向右进行计算的。我们再在 BCC 编译器中进行编译,看看结果是怎样?
那么我们看到在 BCC 编译器中也是这样实现的。函数参数的操作是从右向左的,在现代的编译器中,基本上是按照从右向左的顺序进行函数参数的操作的。在一些古老的编译器中,也有从左向右的实现,这个的实现就依赖于具体的编译器的实现了。
下来我们来讲一个 C 语言中的知识点:顺序点!那么在程序中存在一定的顺序点,顺序点是指执行过程中修改变量值的最晚时刻,在程序到达顺序点的时候,之前所做的一切操作必须完成。那么 C 语言中的顺序点都在那些时刻呢?a> 每个完整表达式结束时,即分号处;b> &&,||,?: 以及逗号表达式的每个参数计算之后;c> 函数调用时所有实参求值完成后(即进入函数之前);
下面我们以代码为例进行分析,代码如下
#includeint main() { int k = 2; int a = 1; k = k++ + k++; printf("k = %d\n", k); if( a-- && a ) { printf("a = %d\n", a); } return 0; }
我们看到第 8 行的进行两次 k++ 的相加,我们分析结果应该为 5;第 12 行的 a-- 执行完之后 a 为 0,但是此时它和 a 相与之后条件仍然为真,所以 第14行应该打印出 a = 0;我么来看看结果是这样吗?
那么我们看到我们分析的第一个是正确的,但是 a = 0 并没有打印出来,我们再来看看 BCC 编译器是多少
我们看到竟然 k = 6,a = 0 依然没有打印出来。我们再来看看 VS 编译器
我们进到反汇编看看它是怎么执行的
我们看到它是这样执行的,先是进行相加操作,这时的++操作被悬挂起来,程序看到;才意识到到了顺序点了,所以执行完那两次++操作,所以最后 k 的值为6。我们再来看看第14行怎么执行的
我们看到它是执行完 a-- 后看到 && 操作便意识到顺序点到了,便返回了。那么这时 a 的值已经变为 0 了,此时 if 语句条件为假,所以不会执行到它里面的打印语句。
下来我们再来看看参数入栈的顺序,函数参数的计算次序是依赖编译器实现的。那么函数参数的入栈次序是如何确定的呢?这块就涉及到里一个概念:调用约定。当函数调用发生时:a> 参数会传递给被调用的函数;b> 而返回值会被返回给函数调用者;调用约定描述参数如何传递到栈中以及栈的维护方式,参数传递顺序,调用栈清理。
调用约定是预定义的可理解为调用协议,调用约定通常用于库调用和库开发的时候。我们来看看一些常用的操作:a>从右到左依次入栈:__stfcall, __cdecl, __thiscall;b> 从左到右依次入栈:__pascall, __fastcall;那么我们一般的 C 程序开发遵循的就是上面的 __cdecl 这种方式的。
那么我们如果要编写一个计算平均数的函数,我们肯定首先想到的是下面这种
#includefloat average(int array[], int size) { int i = 0; float avr = 0; for(i=0; i 我们利用一个数组就完成这个功能,那么我们还得去定义一个数组。有什么办法可以使我们不用定义数组就可以完成这个功能呢?答案就是我们可以利用可变参数的函数来实现这个功能。在 C 语言中可以定义参数可变的函数,参数可变函数的实现依赖于 stdarg.h 头文件。我们得介绍几个概念:a> va_list -- 参数集合;b> va_arg -- 取具体参数值;c> va_start -- 标识参数访问的开始;d> va_end -- 标识参数访问的结束;
下来我们来看看可变参数版的程序是怎样实现的,代码如下
#include#include float average(int n, ...) { va_list args; int i = 0; float sum = 0; va_start(args, n); for(i=0; i 我们在第 6 行定义了 args 参数,在第 10 行开始,14 行进行参数的相加,在 17 行结束。我们来看看第24, 25 行的这样的定义可行吗?来看看编译结果
结果已经正确实现了,这样是不是很方便呢?我们可以随时定义它的大小和内容。那么可变参数也有限制:a> 可变参数必须从头到尾按照顺序逐个访问;b> 参数列表中至少要存在一个确定的命名参数;c> 可变参数函数无法确定实际存在的参数的数量,同样也无法确定参数的实际类型,只能我们手动指定;注意:va_arg 中指定了错误的类型,那么结果是不可预测的!
通过对函数参数的学习,总结如下:1、函数的参数在栈上分配空间;2、函数的实参并没有固定的计算次序;3.顺序点是 C 语言中变量修改的最晚时机;4、调用约定指定了函数参数的入栈顺序以及栈的清理方式;5、可变参数的函数提供了一种函数设计技巧,提供了一种更方便的函数调用方式;6、可变参数必须顺序的访问,无法直接访问中间的参数值。
欢迎大家一起来学习 C 语言,可以加我QQ:243343083。
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。
文章名称:C之函数参数(三十九)-创新互联
本文地址:http://kswsj.cn/article/cogppe.html