C_函数传参归纳

来源:互联网 发布:mac os 下载速度慢 编辑:程序博客网 时间:2024/06/12 00:27

C_函数传参归纳


阅读目录:

1、 函数形参类别

2、 传值调用与传址调用

3、 输入型参数与输出型参数


1、函数形参类别:

1.1、普通变量作为函数形参

(1)函数传参时,普通变量作为参数时,形参和实参名字可以相同也可以不同,实际上都是用实参来替代相对应的形参的。

(2)在子函数内部,形参的值等于实参。原因是函数调用时把实参的值赋值给了形参。即“传值调用”(相当于实参做右值,形参做左值)

代码段:

#include<stdio.h>void func1(int b);int main(void){    int a = 4;    printf("&a = %p.\n", &a);     // &a = 0x7ffca00bdbac    func1(a);       //b = 8, in func1, &b = 0x7ffca00bdb8c/*    &a和&b不同,说明在内存中a和b是独立的2个内存空间,不是同一个变量    但b是a赋值得到的,普通变量作为函数形参,传值调用*/    return 0;}void func1(int b){    // 在函数内部,形参b的值等于实参a    printf("b = %d.\n", b);    printf("in func1, &b = %p.\n", &b);}

1.2、数组作为函数形参

(1)函数名作为形参传参时,实际传递是不是整个数组,而是数组的首元素的首地址(也就是整个数组的首地址。因为传参时是传值,所以这两个没区别)。

(2)在子函数内传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。很多人把这种特性叫做“传址调用”(所谓的传址调用就是调用子函数时传了地址(也就是指针),此时可以通过传进去的地址来访问实参。)

数组作为函数形参时,[]里的数字是可有可无的。为什么?因为数组名做形参传递的实际只是个指针,根本没有数组长度这个信息。

代码段:

#include<stdio.h>void func2(int a[]);int main(void){    int a[5] = {0};    printf("sizeof(a) = %lu.\n", sizeof(a));    //sizeof(a) = 20;    printf("a = %p.\n", a);                    //a = 0x7ffc474dafco0    func2(a);          //sizeof(a) = 8, in func2, a =0x7ffc474dafco0       /*    子函数内部,传进来的数组名是一个指向数组首元素首地址的指针。所以sizeof得到的是8    对比可得,数组作为形参,传递的是整个数组首地址*/    return 0;}void func2(int a[]){    printf("sizeof(a) = %lu.\n", sizeof(a));    printf("in func2, a = %p.\n", a);}

1.3、指针作为函数形参

(1)和数组作为函数形参是一样的.可类比指针方式和数组方式访问数组元素的结果一致性。

代码段:

#include<stdio.h>void func3(int *a);int main(void){    int a[5] = {0};    printf("a = %p.\n", a);   //a = 0x7fffdcaecf60    func3(a);                //sizeof(a) = 8, in func3, a = 0x7fffdcaecf60}void func3(int *a){    printf("sizeof(a) = %d.\n", sizeof(a));    printf("in func3, a = %p.\n", a);}

结论地址相同,操作同一个变量


1.4、结构体变量作为函数形参

(1)结构体变量作为函数形参的时候,实际上和普通变量(类似于int之类的)传参时表现是一模一样的。所以说结构体变量其实也是普通变量而已。

(2)因为结构体一般很大,如果直接用结构体变量进行传参,那么函数调用效率很低。(因为在函数传参的时候需要将实参赋值给形参,所以当传参的变量越大调用效率就会越低)。解决办法就是不要传变量了,改传变量的指针(地址)进去。

代码段1:结构体变量作为参数

#include<stdio.h>struct A{    char a;             // 结构体大小与结构体对齐相关    int b;                  double c;};void func4(struct A s);int main(void){    struct A s1 =     {        .a = 4,        .b = 5555,        .c = 1.23    };    printf("sizeof(s1) = %lu.\n", sizeof(s1));       //结构体大小       printf("&s1 = %p.\n", &s1);                      //结构体地址          printf("s1.b = %d.\n", s1.b);                        func4(s1);                                       //结构体变量作为参数    return 0;}void func4(struct A s)        //结构体变量作为参数{    printf("in func4():\n");    printf("sizeof(s) = %lu.\n", sizeof(s));    printf("&s = %p.\n", &s);    printf("s.b = %d.\n", s.b);}

这里写图片描述

执行结果来看, 内存地址不同,结构体变量作为参数,类似普通变量作为函数形参,拷贝使用

代码段2:传递结构体指针

#include<stdio.h>struct A{    char a;             // 结构体大小涉及结构体变量对齐问题    int b;                  double c;};void func5(struct A *);int main(void){    struct A a1 =     {        .a = 4,        .b = 5555,        .c = 1.23    };    printf("sizeof(a1) = %lu.\n", sizeof(a1));                  printf("&a1 = %p.\n", &a1);                       printf("a1 = %p.\n", a1);                                      func5(&a1);        return 0;}void func5(struct A *a1){    printf("**********in func5()*********\n");    printf("sizeof(a1) = %d.\n", sizeof(a1));       printf("&a1 = %p.\n", &a1);         printf("a1 = %p.\n", a1);}

这里写图片描述

在func5()内部,sizeof(a1) = 8, a1是一个指针变量,指向结构体


2、传值调用与传址调用:

2.1、传值调用

传值调用
描述这样一种现象:x和y作为实参,自己并没有真身进入swap1函数内部,而只是拷贝了一份自己的副本(副本具有和自己一样的值,但是是不同的变量)进入子函数swap1,然后我们在子函数swap1中交换的实际是副本而不是x、y真身。所以在swap1内部确实是交换了,但是到外部的x和y根本没有受影响。

代码段:

int x = 3, y = 5;swap1(x, y);     //等价于swap1(3, 5) 对xy没影响printf("x = %d, y = %d.\n", x, y);      // x=3,y=5,交换失败void swap1(int a, int b){    int tmp;    tmp = a;    a = b;    b = tmp;    printf("in swap1, a = %d, b = %d.\n", a, b);}

2.2、传址调用

传址调用:在swap2中x和y真的被改变了(但是x和y真身还是没有进入swap2函数内,而是swap2函数内部跑出来把外面的x和y真身改了)。实际上实参x和y永远无法真身进入子函数内部(进去的只能是一份拷贝),但是在swap2我们把x和y的地址传进去给子函数了,于是乎在子函数内可以通过指针解引用方式从函数内部访问到外部的x和y真身,从而改变x和y。

代码段:

int x = 3, y = 5;swap2(&x, &y);printf("x = %d, y = %d.\n", x, y);      // 交换成功void swap2(int *a, int *b){    int tmp;    tmp = *a;    *a = *b;    *b = tmp;    printf("in swap1, *a = %d, *b = %d.\n", *a, *b);}

2.3、结论:这个世界上根本没有传值和传址这两种方式,C语言本身函数调用时一直是传值的,只不过传的值可以是变量名,也可以是变量的指针。


3、输入型参数与输出型参数:

3.1、函数为什么需要形参与返回值
(1)函数名是一个符号,表示整个函数代码段的首地址,实质是一个指针常量,所以在程序中使用到函数名时都是当地址用的,用来调用这个函数的。

(2)函数体是函数的关键,由一对{}括起来,包含很多句代码,函数体就是函数实际做的工作。

(3)形参列表和返回值。形参是函数的输入部分,返回值是函数的输出部分。对函数最好的理解就是把函数看成是一个加工机器(程序其实就是数据加工器),形参列表就是这个机器的原材料输入端;而返回值就是机器的成品输出端。

(4)其实如果没有形参列表和返回值,函数也能对数据进行加工,用全局变量即可。用全局变量来传参和用函数参数列表返回值来传参各有特点,在实践中都有使用。总的来说,函数参数传参用的比较多,因为这样可以实现模块化编程,而C语言中也是尽量减少使用全局变量。

代码段:// 程序要完成功能是:对一个数乘以5// 第一种方法:函数传参int a = 3;int b;b = multip5(a);printf("result = %d.\n", b);int multip5(int a){    return a*5;}//第二种方法:全局变量传参x = 2;multip5_2();printf("y = %d.\n", y);void multip5_2(void){    y = 5 * x;   //这里x是全局变量}

(5)全局变量传参最大的好处就是省略了函数传参的开销,所以效率要高一些;但是实战中用的最多的还是传参,如果参数很多传参开销非常大,通常的做法是把很多参数打包成一个结构体,然后传结构体变量指针进去。


3.2、函数传参中使用const指针
(1)const一般用在函数参数列表中,用法是const int *p;(意义是指针变量p本身可变的,而p所指向的变量是不可变的)。

int a = 1;func1(&a);              //允许void func1(int *p)        //*p内容可更改{    *p = 5;}func2(&a);   /*void func2(const int *p)    //*p不可更改{    *p = 5;                //编译时候就会报错:error:assignment of read-only }*/

(2)const用来修饰指针做函数传参,作用就在于声明在函数内部不会改变这个指针所指向的内容,所以给该函数传一个不可改变的指针(char *p = “linux”;这种)不会触发错误;而一个未声明为const的指针的函数,你给他传一个不可更改的指针的时候就要小心了。

char *pStr = "linux";               // 不可更改//char pStr[] = "linux";            // 可更改的func3(pStr);printf("%s.\n", pStr);void func3(char *p){    *p = 'a';}

3.3、函数如何向外返回多个值?
(1)一般来说,函数的输入部分就是函数参数,输出部分就是返回值。问题是函数的参数可以有很多个,而返回值只能有1个。这就造成我们无法让一个函数返回多个值。

(2)现实编程中,一个函数需要返回多个值是非常普遍的,因此完全依赖于返回值是不靠谱的,通常的做法是用参数来做返回(在典型的linux风格函数中,返回值是不用来返回结果的,而是用来返回0或者负数用来表示程序执行结果是对还是错,是成功还是失败)。

(3)普遍做法,编程中函数的输入和输出都是靠函数参数的,返回值只是用来表示函数执行的结果是对(成功)还是错(失败)。如果这个参数是用来做输入的,就叫输入型参数;如果这个参数的目的是用来做输出的,就叫输出型参数。

(4)输出型参数就是用来让函数内部把数据输出到函数外部的。

//*p 为输出型参数调用:int a, b = 0, ret = -1;    a = 30;    ret = multip5_3(a, &b);    if (ret == -1)    {        printf("出错了\n");    }    else    {        printf("result = %d.\n", b);    }定义int multip5_3(int a, int *p){    int tmp;    tmp = 5 * a;    if (tmp > 100)    {        return -1;    }    else    {        *p = tmp;        return 0;    }}

如何判断函数原型中输入参数与输出参数?

函数传参如果传的是普通变量(不是指针)那肯定是输入型参数;如果传指针就有2种可能性了,为了区别,经常的做法是:如果这个参数是做输入的(通常做输入的在函数内部只需要读取这个参数而不会需要更改它)就在指针前面加const来修饰;如果函数形参是指针变量并且还没加const,那么就表示这个参数是用来做输出型参数的。