前言
传参避免不了,用倒是会用, 但是让深入解析一下,倒说不出个一二三了,今儿就再整理一下。
传参
传参分为:值传递、指针传递、引用传递。 事实上,关于 C 函数的参数传递规则可以表述如下:
所有传递给函数的参数都是按值传递的。
值传递
当函数被调用的时候,形参被创建,调用时带的参数被拷贝到刚创建好的形参,函数结束时,形参被摧毁。由于是参数的一个副本被传递到被调用的函数。所以,原始的参数不会被函数修改。
值传递的优点: 通过值来传递的参数可以是数字,变量,表达式。参数的值不会被“被调用的函数”修改。
值传递的缺点: 当函数被多次调用,值传递结构体和类会带来性能上的损害(耗时),给调用者返回值只能通过被调用函数的返回值。 例子:
#include <stdio.h>
// 函数声明
void modifyValue(int x);
int main() {
int num = 10;
printf("Before function call: num = %d\n", num);
// 调用函数,传递num的值
modifyValue(num);
printf("After function call: num = %d\n", num);
return 0;
}
// 函数定义
void modifyValue(int x) {
printf("Inside function: Received x = %d\n", x);
// 修改局部变量x的值
x = 20;
printf("Inside function: Modified x = %d\n", x);
}
输出:
Before function call: num = 10
Inside function: Received x = 10
Inside function: Modified x = 20
After function call: num = 10
请注意:
- main函数中modifyValue(num)中的num是实参;
- 调用函数modifyValue(int x)中的x是形参;
- 调用时num值给了x,函数体内没有对num操作,操作的是x,所以num的值并没有变。 特点:
-
参数复制: 当你调用一个函数并传递参数时,这些参数的值被复制到函数的局部变量。这意味着函数内部使用的是参数的副本,而不是原始参数本身。
-
保持原始值不变: 由于函数操作的是参数的副本,因此任何在函数内部对参数的修改都不会影响原始参数的值。原始参数的值在函数调用前后保持不变。
- 独立性: 值传递确保函数内部的操作不会影响函数外部的数据。这使得程序更加可控,因为你可以准确知道哪些函数可能会修改传递给它们的参数,哪些不会。
指针传递
它允许函数访问和修改原始数据,而不是只使用数据的副本。以下是指针传递的详细解释:
-
传递指针地址: 在指针传递中,函数参数接受一个指向某个数据的指针(内存地址)。这个指针指向原始数据的存储位置,而不是数据的副本。因此,函数可以通过该指针访问和修改原始数据。
-
允许修改原始数据: 与值传递不同,指针传递允许函数修改原始数据的内容。因为函数操作的是原始数据,所以对数据的修改在函数调用后仍然保持有效。
-
传递引用: 指针传递可以看作是传递了数据的引用(地址)。这使得函数能够有效地处理大型数据结构,而无需复制整个数据。
-
注意空指针: 在使用指针传递时,需要小心处理空指针(指向空内存的指针),以避免访问未分配的内存而导致程序崩溃。
示例1
以下是一个简单的C示例,演示了指针传递的过程,其中函数接受一个指向整数的指针,并修改了原始整数的值。
#include <stdio.h>
// 函数声明,接受整数指针
void modifyValue(int *x);
int main() {
int num = 10;
printf("Before function call: num = %d\n", num);
// 调用函数,传递num的地址
modifyValue(&num); // 传递指针
printf("After function call: num = %d\n", num);
return 0;
}
// 函数定义,接受整数指针
void modifyValue(int *x) {
printf("Inside function: Received x = %d\n", *x); // 访问原始数据
*x = 20; // 修改原始数据
printf("Inside function: Modified x = %d\n", *x);
}
上述程序中,modifyValue函数接受一个整数指针,并通过该指针访问和修改原始整数的值。函数调用后,原始整数的值被成功修改。
总之,指针传递是一种强大的参数传递方式,允许函数直接访问和修改原始数据。 实际上可以按照值传递的思路去理解:
- 参数是地址的副本:就像值传递是的值被复制到函数的局部变量一样,指针传递中参数的值也被复制到形参中,这个形参指针变量保存的是地址。
- *x=20,实际上是对那块地址的内容做的修改。
- 这是值传递和指针传递之间的关键区别。在值传递中,传递的是数据的副本,而在指针传递中,传递的是地址的副本。
- 示例中,可以将&num 视为传递了num的地址值给函数,然后函数内部使用这个地址引用来访问和修改原始数据,就像按值传递中函数内部使用局部变量来操作参数的副本一样。
做题加深下理解:
代码1:
void do_malloc(char *p, int size)
{
p = (char *)malloc(size + 1);
memset(p, 0, size + 1);
}
int main(int argc, char *argv[])
{
char *pData = 0;
do_malloc(pData, 128);
sprintf(pData, "%s", "abc");
printf(pData);
return 0;
}
结论:传入的是地址,对该地址内的值操作,会改变内存内的值 传入未申请空间的变量,在局部函数中对其申请空间,但是并没有改变main函数中指针的空间。所以在主函数中使用会报错;
代码2:
void do_malloc(char **p, int size)
{
*p = (char *)malloc(size + 1);
memset(*p, 0, size + 1);
}
int main(int argc, char *argv[])
{
char *pData = 0;
do_malloc(&pData, 128);
sprintf(pData, "%s", "abc");
printf(pData);
return 0;
}
结果:正常。
在do_malloc函数中对*p指针的值即main函数中pData地址申请空间, 所以在main函数中pData最后是有地址的。
代码1如果不想用二级指针的话,还可以用返回值的方式解决:
示例3:代码1另一种解决方式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* fa(char* p) //主要还是指针的问题
{
p=(char* )malloc(100);
return p;
}
int main()
{
char* str=NULL;//这块没问题的
str = fa(str);
strcpy(str,"hello");
printf("%s\n",str);
free(str);
str=NULL;
return 0;
}
输出结果:
hello
上述代码 return p 返回了 fa 函数中分配的内存的地址。然后再 main函数中str指向了 一片空间。
示例4:示例3的延伸
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
结果可能是乱码:因为 GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。
引用传递
C程序中并没有 引用传递的概念,C++中才有。
所以不管是值传递还是指针传递 , 都可以用值传递的思路去解释。