您的位置:首页 > 其它

C 语言6之指针与函数传参

2018-02-28 21:30 267 查看
时间:2018.2.28 作者:Tom 工作:HWE 说明:如需转载,请注明出处。
说明:本文主要参考朱有鹏老师linux嵌入式C语言高级篇笔记,已注明转载。
1.函数传参
这里主要讨论普通变量、数组、结构体变量作为函数传参的类型!
1.1普通变量作为函数的传参1)函数传参时,普通变量作为参数时,形参和实参名字可以相同也可以不同,实际上都是用实参来替代相对应的形参的。




从结果中可以看出,当形参和实参名字相同时,两个&a的地址不同,说明两个a不是一回事,在内存中是两个内存空间。但两个a是有关联的,实参把值赋值给形参。2)在子函数内部,形参的值等于实参。原因是函数调用时把实参的值赋值给了形参。3)这就是很多书上写的"传值调用"(相当于实参做右值,形参做左值) 
1.2.数组作为函数传参1)数组名作为形参传参时,实际传递不是整个数组,而是数组的首元素的首地址(也就是整个数组的首地址。因为传参时是传值,所以这两个没区别)。所以在子函数内部,传进来的数组名就等于是一个指向数组首元素首地址的指针,所以sizeof得到的是4。 



2)在子函数内传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。很多人把这种特性叫做"传址调用"(所谓的传址调用就是调用子函数时传了地址(也就是指针),此时可以通过传进去的地址来访问实参。)3)数组作为函数形参时,[]里的数字是可有可无的。为什么?因为数组名做形参传递的实际只是个指针,根本没有数组长度这个信息。 
1.3.数组作为函数传参
只有一句话:和数组作为函数形参是一样的。这就好像指针方式访问数组元素和数组方式访问数组元素的结果一样是一样的。
1.4.结构体变量作为函数传参1)结构体变量作为函数形参的时候,实际上和普通变量(类似于int之类的)传参时表现是一模一样的。所以说结构体变量其实也是普通变量而已。



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



&a1指的是二重指针。3)结构体因为自身太大,所以传参应该用指针来传(但是程序员可以自己决定,你非要传结构体变量过去C语言也是允许的,只是效率低了);回想一下数组,为什么C语言设计的时候数组传参默认是传的数组首元素首地址而不是整个数组?因为C语言帮你决定了。 
2.传值调用与传址调用(可随意使用网上的经典例子)1)传值调用描述的是这样一种现象:x和y作为实参,自己并没有真身进入swap1函数内部,而只是拷贝了一份自己的副本(副本具有和自己一样的值,但是是不同的变量)进入子函数swap1,然后我们在子函数swap1中交换的实际是副本而不是x、y真身。所以在swap1内部确实是交换了,但是到外部的x和y根本没有受影响。



2)在swap2中a和b真的被改变了(但是a和b真身还是没有进入swap2函数内,而是swap2函数内部跑出来把外面的a和b真身改了)。实际上实参a和b永远无法真身进入子函数内部(进去的只能是一份拷贝),但是在swap2我们把a和b的地址传进去给子函数了,于是乎在子函数内可以通过指针解引用方式从函数内部访问到外部的a和b真身,从而改变a和b。3)结论:这个世界上根本没有传值和传址这两种方式,C语言本身函数调用时一直是传值的,只不过传的值可以是变量名,也可以是变量的指针。
 
3.输入型参数与输出型参数
3.1函数为什么需要形参与返回值1)函数名是一个符号,表示整个函数代码段的首地址,实质是一个指针常量,所以在程序中使用到函数名时都是当地址用的,用来调用这个函数的。2)函数体是函数的关键,由一对{}括起来,包含很多句代码,函数体就是函数实际做的工作。3)形参列表和返回值。形参是函数的输入部分,返回值是函数的输出部分。对函数最好的理解就是把函数看成是一个加工机器(程序其实就是数据加工器),形参列表就是这个机器的原材料输入端;而返回值就是机器的成品输出端。4)其实如果没有形参列表和返回值,函数也能对数据进行加工,用全局变量即可。用全局变量来传参和用函数参数列表返回值来传参各有特点,在实践中都有使用。总的来说,函数参数传参用的比较多,因为这样可以实现模块化编程,而C语言中也是尽量减少使用全局变量。5)全局变量传参最大的好处就是省略了函数传参的开销,所以效率要高一些;但是实战中用的最多的还是传参,如果参数很多传参开销非常大,通常的做法是把很多参数打包成一个结构体,然后传结构体变量指针进去。 
3.2函数传参中使用const指针1)const一般用在函数参数列表中,用法是const int *p;(意义是指针变量p本身可变的,而p所指向的变量是不可变的)。2)const用来修饰指针做函数传参,作用就在于声明在函数内部不会改变这个指针所指向的内容,所以给该函数传一个不可改变的指针(char *p = "linux";这种)不会触发错误;而一个未声明为const的指针的函数,你给他传一个不可更改的指针的时候就要小心了。以下转自朱老师的博客:// 此函数有以下几点值得注意:// 1、第一个参数中的const。一般在函数的形参中,如果我们只是希望调用者使用该参数,而不会去改变该// 参数内容(一般是指针指向的内容),则可以声明为const。// 2、第二个参数。C语言中函数只能有一个返回值,但是有时候我们希望从函数中得到不止一个返回内容怎// 么办呢?只能通过参数了。一般的参数是用来向函数输入信息的,但是指针型参数可以间接用来从函数// 输出信息。使用时用户只需传入一个相应的指针,函数中会把需要输出的信息地址传给这个指针,这样// 在函数调用完成后,用户即可到endp指针处去取函数传出的值了。这就是用参数实现返回值的方式。但是// 要注意,在函数中千万不可把局部变量的地址传给输出型指针,因为局部变量存在栈里,函数调用结束后// 即释放了,传出的指针指向的内容是栈上已经被释放的部分,因此是无效的。(PS:高级语言中// 譬如C#有ref,out关键字,以明确指明该引用为输出型)// 3、第三个参数base。要注意这个base的机制,本函数中是按照这样的理念来设计的。即如果str中有0x// 开头且接下来是数字则忽略用户调用时输入的base,强制为16进制。如果str不是上面情况然后才看用户// 输入的base。用户输入非0则使用用户指定的base,若用户输入0则自动判断是8进制还是10进制。// 需要强调的是:使用习惯决定理念,理念决定代码逻辑。// 4、注意 if(endp)这里,在输出型参数使用中,函数内首先判断endp是否为NULL,并以此来决定是否输出// 这个机制来自于一个理念:这个输出参数有可能是用户关注的,也有可能是用户不在意的。这样处理可以// 给用户自由,即用户如果在意就传一个有效指针过来接收;如果不在意调用时直接给个NULL就行。提供// 服务但不强迫,这样很好。在OS的API中很多时候都有类似的技巧,请注意体会。  unsigned long simple_strtoul(const char *cp,char **endp,unsigned int base){ unsigned long result = 0,value;// 对于base,首先如果*cp以0x开头且下来是数字,则一定为16进制。// 然后如果用户指定了一个非0的进制,则遵循用户输入的进制。如果用户输入了0进制,则根据是否0开头来// 确定是8进制还是10进制 if (*cp == '0') { cp++; if ((*cp == 'x') && isxdigit(cp[1])) { base = 16; // 如果cp是0x开头的,下面一个又是数字,那么base一定为16.此时即使 cp++; // 用户输入了一个base,也忽略这个base而强制其为16 } if (!base) { base = 8; // 如果用户输入base为0,cp是0开头,接下来不是x, } // 那么强制为8进制 } if (!base) { base = 10; // 如果用户输入base为0,cp是非0开头,则使用10进制。 } // while循环里value<base是精髓所在。这个解析停止的条件不能是null,而应该是str中第一个不是 // 数字的字母。这个字母的范围取决于base,譬如如果是16进制那么f也算是数字。而如果是8进制那么 // 9都不算是数字了。因此解析结束的范围只能和base比较来限定。 while (isxdigit(*cp) && (value = isdigit(*cp) ? *cp-'0' : (islower(*cp) ? toupper(*cp) : *cp)-'A'+10) < base) { result = result*base + value; cp++; } if (endp) // 此处if判断的作用是:用户在使用这个函数时,如果不关心endp则可以直接使用 *endp = (char *)cp; // null,而不必担心程序运行会出错。这种处理技巧很实用,用户可以 return result; // 自行决定是否使用这个函数提供的参数式返回值。} 
3.3函数需要向外部返回多个值时怎么办?1)一般来说,函数的输入部分就是函数参数,输出部分就是返回值。问题是函数的参数可以有很多个,而返回值只能有1个。这就造成我们无法让一个函数返回多个值。2)现实编程中,一个函数需要返回多个值是非常普遍的,因此完全依赖于返回值是不靠谱的,通常的做法是用参数来做返回(在典型的linux风格函数中,返回值是不用来返回结果的,而是用来返回0或者负数用来表示程序执行结果是对还是错,是成功还是失败)。3)普遍做法,编程中函数的输入和输出都是靠函数参数的,返回值只是用来表示函数执行的结果是对(成功)还是错(失败)。如果这个参数是用来做输入的,就叫输入型参数;如果这个参数的目的是用来做输出的,就叫输出型参数。4)输出型参数就是用来让函数内部把数据输出到函数外部的。 
3.4总结
看到一个函数的原型后,怎么样一眼看出来哪个参数做输入哪个做输出?函数传参如果传的是普通变量(不是指针)那肯定是输入型参数;如果传指针就有2种可能性了,为了区别,经常的做法是:如果这个参数是做输入的(通常做输入的在函数内部只需要读取这个参数而不会需要更改它)就在指针前面加const来修饰;如果函数形参是指针变量并且还没加const,那么就表示这个参数是用来做输出型参数的。譬如C库函数中strcpy函数:char *strcpy(char *dest, const char *src);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息