在c语言中,传递在函数间的变量有普通变量和指针变量,在被调函数中想要修改主调函数传来的变量,是我们在实际开发中经常用用到的技术。
1.普通变量的传递(值拷贝)
int test_func(int test_a)
{
test_a = 5;
return 0;
}
int main(void)
{
int main_a = 1;
test_func(main_a);
printf("main_a = %d\n", main_a);
return 0;
}
运行结果:
十分简单,test_func函数并不能修改main函数传来的main_a变量。这是因为函数间的传参是值拷贝的方式,main函数在调用test_func函数的时候,把变量main_a拷贝了一份作为副本,并把副本传递给test_func函数,也就是说test_func修改的是main_a的副本,自然,这个修改操作并不能修改到main_a。
2.普通变量的传递(传递值的地址)
上面代码,要实现被调函数能够修改主调函数传来的值,就需要让被调用函数传递待修改的变量的地址
int test_func(int *test_a)
{
*test_a = 5;
return 0;
}
int main(void)
{
int main_a = 1;
test_func(&main_a);
printf("main_a = %d\n", main_a);
return 0;
}
运行结果:
同理,函数间的传参是采用拷贝的方式,只不过这里拷贝的是变量的地址,当main函数调用test_func函数时,test_func函数实现的过程为:
(1)定义一个指针变量test_a,它用来存放main函数传来的变量main_a的地址;
(2)对指针变量test_a解引用,并为其赋值为5;
(3)由于test_a指向的地址等于main_a的地址,所以就修改了main_a的值。
3.指针变量在函数间的传递(指针变量拷贝)
int test_func(int *test_p)
{
int b = 100;
test_p = &b;
return 0;
}
int main(void)
{
int *main_p = NULL;
int a = 5;
main_p = &a;
printf("*main_p = %d\n", *main_p); //打印p=5
test_func(main_p);
printf("*main_p = %d\n", *main_p);
return 0;
}
运行结果:
运行结果表明test_func函数并没有修改main函数中main_p指针变量所指向的值。跟之前的代码类似,分析test_func的执行过程:
(1)main函数调用test_func函数并传递指针变量main_p给它时,它会定义一个指针变量test_p用于接收main_p;
(2)main_p和test_p都是指针变量,根据参数在函数间传递的拷贝原则,test_p存放的是main_p的副本,所以test_func对指针的副本的操作,不会影响到main中指针变量main_p的值,main_p还是等于5。
4.指针变量在函数间的传递(传递指针变量的地址)
我们知道,主调函数想要传递变量的地址给被调函数时,被调函数时用一级指针(*p)来接收;然而,传递指针的地址时,形参则是采用二级指针(**p)来接收。
//**test_p是二级指针,它用于存放一级指针的地址
int test_func(int **test_p)
{
int b = 100;
*test_p = &b;
return 0;
}
int main(void)
{
int *main_p = NULL;
int a = 5;
main_p = &a;
printf("*main_p = %d\n", *main_p); //打印p=5
test_func(&main_p);
printf("*main_p = %d\n", *main_p);
return 0;
}
运行结果:
test_func函数中,**test_p是一个二级指针,
test_p = &main_p, *test = main_p, **test = *main_p
test_func的执行过程为:
(1)test_func被调用时,它会定义一个二级指针用来接收main函数传来的一级指针main_p的地址,注意,它接收的是main_p的地址的副本;
(2)对二级指针解引用得到main函数main_p的指针值,(它是a变量的地址值)并将其改为b的地址值;
(3)所以在main函数中得到的*main_p就是test_func中变量b的值了。
5.小结
指针变量在函数间的传递跟普通变量在函数间传递是一样的性质的,想要在被调函数中修改主调函数传来的变量值,即必须传递该变量的地址,另外,注意指针变量的地址是用二级指针接收。
6.二级指针在动态分配中的使用
malloc:
void MallocMem(int *p)
{
p = (int* )malloc(sizeof(int) * 10);
}
int main(void)
{
int *p = NULL;
MallocMem(p);
if (!p)
printf("malloc failed!!\n");
else
{
printf("malloc successful!\n");
free(p);
}
return 0;
}
运行结果:
malloc函数实现在堆空间的动态分配,完毕后会将分配到的堆空间的首地址赋给一个指针,也就是说它会修改指针变量的值。而在main函数中传来的是一个指针变量,根据拷贝原则,MallocMem接收到的指针变量是main函数传来的指针变量的副本,在MallocMem中是副本去接收malloc分配到的堆空间的首地址,所以main中的指针p还是为NULL。
应该修改为:
void MallocMem(int **p) //二级指针
{
*p = (int* )malloc(sizeof(int) * 10); //*p = main函数中的p
}
int main(void)
{
int *p = NULL;
MallocMem(&p);
if (!p)
printf("malloc failed!!\n");
else
{
printf("malloc successful!\n");
free(p);
}
return 0;
}
运行结果:
free:
void FreeMem(int *p)
{
free(p);
p = NULL; //不报错,但是是无作用的代码
}
int main(void)
{
int *p = NULL;
p = (int* )malloc(sizeof(int) * 10);
if (p == NULL)
{
printf("malloc failed!!\n");
return -1;
}
FreeMem(p);
return 0;
}
在FreeMem函数中为了防止内存泄漏的出现,需要将free后的指针置为NULL。如上代码即想实现这样的操作,同样的道理,将指针置为NULL也是在修改指针变量,所以不能单纯的传递指针变量,而是要传递指针变量的地址,修改如下:
void FreeMem(int **p)
{
int *tmp = *p; //tmp等于main中的p
free(tmp);
*p = NULL; //*p = main中的p,这样才起到避免野指针的效果
}
int main(void)
{
int *p = NULL;
p = (int* )malloc(sizeof(int) * 10);
if (p == NULL)
{
printf("malloc failed!!\n");
return -1;
}
FreeMem(&p);
return 0;
}
变量在函数间的传递基本就是这些知识点,有需完善的后面会补充。