C之函数参数(三十九)

        我们上节博文讲了函数的意义,那么我们今天来讲下函数参数。函数参数在本质上与局部变量相同在栈上分配空间,函数参数的初始值是函数调用时的实参值。用下图来实际说明

在芜湖县等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供网站设计、成都网站建设 网站设计制作按需定制,公司网站建设,企业网站建设,品牌网站设计,成都全网营销,成都外贸网站建设,芜湖县网站建设费用合理。

C之函数参数(三十九)

        函数参数的求值顺序依赖于编译器的实现,我们来看看下面代码的输出是什么?为什么呢?

#include 

int 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。我们来看看编译结果是否如我们所分析的那样,编译结果如下

C之函数参数(三十九)

        我们看到 i 和 j 和我们所分析的正好相反,那么这是怎么回事呢?原来在gcc 编译器中,函数参数的实现是从右向左进行操作的,并非是我们所想的从左向右进行计算的。我们再在 BCC 编译器中进行编译,看看结果是怎样?

C之函数参数(三十九)

        那么我们看到在 BCC 编译器中也是这样实现的。函数参数的操作是从右向左的,在现代的编译器中,基本上是按照从右向左的顺序进行函数参数的操作的。在一些古老的编译器中,也有从左向右的实现,这个的实现就依赖于具体的编译器的实现了。

        下来我们来讲一个 C 语言中的知识点:顺序点!那么在程序中存在一定的顺序点,顺序点是指执行过程中修改变量值的最晚时刻,在程序到达顺序点的时候,之前所做的一切操作必须完成。那么 C 语言中的顺序点都在那些时刻呢?a> 每个完整表达式结束时,即分号处;b> &&,||,?: 以及逗号表达式的每个参数计算之后;c> 函数调用时所有实参求值完成后(即进入函数之前);

        下面我们以代码为例进行分析,代码如下

#include 

int 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;我么来看看结果是这样吗?

C之函数参数(三十九)

        那么我们看到我们分析的第一个是正确的,但是 a = 0 并没有打印出来,我们再来看看 BCC 编译器是多少

C之函数参数(三十九)

        我们看到竟然 k = 6,a = 0 依然没有打印出来。我们再来看看 VS 编译器

C之函数参数(三十九)

        我们进到反汇编看看它是怎么执行的

C之函数参数(三十九)

        我们看到它是这样执行的,先是进行相加操作,这时的++操作被悬挂起来,程序看到;才意识到到了顺序点了,所以执行完那两次++操作,所以最后 k 的值为6。我们再来看看第14行怎么执行的

C之函数参数(三十九)

        我们看到它是执行完 a-- 后看到 && 操作便意识到顺序点到了,便返回了。那么这时 a 的值已经变为 0 了,此时 if 语句条件为假,所以不会执行到它里面的打印语句。

        下来我们再来看看参数入栈的顺序,函数参数的计算次序是依赖编译器实现的。那么函数参数的入栈次序是如何确定的呢?这块就涉及到里一个概念:调用约定。当函数调用发生时:a> 参数会传递给被调用的函数;b> 而返回值会被返回给函数调用者;调用约定描述参数如何传递到栈中以及栈的维护方式,参数传递顺序,调用栈清理。

        调用约定是预定义的可理解为调用协议,调用约定通常用于库调用和库开发的时候。我们来看看一些常用的操作:a>从右到左依次入栈:__stfcall, __cdecl, __thiscall;b> 从左到右依次入栈:__pascall, __fastcall;那么我们一般的 C 程序开发遵循的就是上面的 __cdecl 这种方式的。

        那么我们如果要编写一个计算平均数的函数,我们肯定首先想到的是下面这种

#include 

float 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 行的这样的定义可行吗?来看看编译结果

C之函数参数(三十九)

        结果已经正确实现了,这样是不是很方便呢?我们可以随时定义它的大小和内容。那么可变参数也有限制:a> 可变参数必须从头到尾按照顺序逐个访问;b> 参数列表中至少要存在一个确定的命名参数;c> 可变参数函数无法确定实际存在的参数的数量,同样也无法确定参数的实际类型,只能我们手动指定;注意:va_arg 中指定了错误的类型,那么结果是不可预测的!

        通过对函数参数的学习,总结如下:1、函数的参数在栈上分配空间;2、函数的实参并没有固定的计算次序;3.顺序点是 C 语言中变量修改的最晚时机;4、调用约定指定了函数参数的入栈顺序以及栈的清理方式;5、可变参数的函数提供了一种函数设计技巧,提供了一种更方便的函数调用方式;6、可变参数必须顺序的访问,无法直接访问中间的参数值。

        欢迎大家一起来学习 C 语言,可以加我QQ:243343083。


分享名称:C之函数参数(三十九)
网页地址:http://scyanting.com/article/jhoppp.html