指针与函数传参
普通变量作为函数形参
- 函数传参时,普通变量作为参数时,形参和实参名字可以相同也可以不同,实际上都是用实参来替代相对应的形参的。
- 在子函数内部,形参的值等于实参。原因是函数调用时把实参的值赋值给了形参。
- 这就是很多书上写的“传值调用”(相当于实参做右值,形参做左值)
/ &a和&b不同,说明a和b不是同一个变量(在内存中a和b是独立的2个内存空间)
// 但是a和b是有关联的,实际上b是a赋值得到的。
void func1(int b)
{
// 在函数内部,形参b的值等于实参a
printf("b = %d.\n", b);
printf("in func1, &b = %p.\n", &b);
}
int main(void){
int a = 4;
printf("&a = %p.\n", &a);
func1(a);
return 0;
}
数组作为函数形参
- 函数名作为形参传参时,实际传递是不是整个数组,而是数组的首元素的首地址(也就是整个数组的首地址。因为传参时是传值,所以这两个没区别)。
- 所以在子函数内部,传进来的数组名就等于是一个指向数组首元素首地址的指针。所以sizeof得到的是4.
- 在子函数内传参得到的数组首元素首地址,和外面得到的数组首元素首地址的值是相同的。
- 很多人把这种特性叫做“传址调用”(所谓的传址调用就是调用子函数时传了地址(也就是指针),此时可以通过传进去的地址来访问实参。)
- 数组作为函数形参时,[ ]里的数字是可有可无的。
- 为什么?因为数组名做形参传递的实际只是个指针,根本没有数组长度这个信息。
void func2(int a[])
{
printf("sizeof(a) = %d.\n", sizeof(a));
printf("in func2, a = %p.\n", a);
}
int main(void)
{
int a[5];
printf("a = %p.\n", a);
func2(a);
return 0;
}
指针作为函数形参
- 只有一句话:和数组作为函数形参是一样的.
- 这就好像指针方式访问数组元素和数组方式访问数组元素的结果一样是一样的。
void func3(int *a)
{
printf("sizeof(a) = %d.\n", sizeof(a));
printf("in func2, a = %p.\n", a);
}
int main(void)
{
int a[5];
printf("a = %p.\n", a);
func3(a);
return 0;
}
结构体变量作为函数形参
- 结构体变量作为函数形参的时候,实际上和普通变量(类似于int之类的)传参时表现是一模一样的。
- 所以说结构体变量其实也是普通变量而已。
- 因为结构体一般都很大,所以如果直接用结构体变量进行传参,那么函数调用效率就会很低。
- 因为在函数传参的时候需要将实参赋值给形参,所以当传参的变量越大调用效率就会越低
- 怎么解决?思路只有一个那就是不要传变量了,改传变量的指针(地址)进去。
- 结构体因为自身太大,所以传参应该用指针来传(但是可以自己决定,你非要传结构体变量过去C语言也是允许的,只是效率低了);回想一下数组,为什么C语言设计的时候数组传参默认是传的数组首元素首地址而不是整个数组?
struct A { char a; // 结构体变量对齐问题 int b; // 因为要对齐存放,所以大小是8 }; void func4(struct A a1) { printf("sizeof(a1) = %d.\n", sizeof(a1)); printf("&a1 = %p.\n", &a1); printf("a1.b = %d.\n", a1.b); } int main(void) { struct A a = { .a = 4, .b = 5555, }; printf("sizeof(a) = %d.\n", sizeof(a)); printf("&a = %p.\n", &a); printf("a.b = %d.\n", a.b); func4(a); return 0; }
void func5(struct A *a1) { printf("sizeof(a1) = %d.\n", sizeof(a1)); // 4 printf("sizeof(*a1) = %d.\n", sizeof(*a1)); // 8 printf("&a1 = %p.\n", &a1); // 二重指针 printf("a1 = %p.\n", a1); printf("a1->b = %d.\n", a1->b); } int main(void) { struct A a = { .a = 4, .b = 5555, }; printf("sizeof(a) = %d.\n", sizeof(a)); // 4 printf("&a = %p.\n", &a); printf("a.b = %d.\n", a.b); func5(&a); return 0; }
传值调用与传址调用
- 传值调用描述的是这样一种现象:
- x和y作为实参,自己并没有真身进入swap1函数内部,而只是拷贝了一份自己的副本(副本具有和自己一样的值,但是是不同的变量)进入子函数swap1
- 然后我们在子函数swap1中交换的实际是副本而不是x、y真身。所以在swap1内部确实是交换了,但是到外部的x和y根本没有受影响。
- 在swap2中x和y真的被改变了(但是x和y真身还是没有进入swap2函数内,而是swap2函数内部跑出来把外面的x和y真身改了)。
- 实际上实参x和y永远无法真身进入子函数内部(进去的只能是一份拷贝),但是在swap2我们把x和y的地址传进去给子函数了,于是乎在子函数内可以通过指针解引用方式从函数内部访问到外部的x和y真身,从而改变x和y。
- 结论:这个世界上根本没有传值和传址这两种方式,C语言本身函数调用时一直是传值的,只不过传的值可以是变量名,也可以是变量的指针。
void swap1(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
printf("in swap1, a = %d, b = %d.\n", a, b);
}
void swap2(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
printf("in swap1, *a = %d, *b = %d.\n", *a, *b);
}
int main(void)
{
int x = 3, y = 5;
swap2(&x, &y);
printf("x = %d, y = %d.\n", x, y); // 交换成功
/*
int x = 3, y = 5;
swap1(x, y);
printf("x = %d, y = %d.\n", x, y); // x=3,y=5,交换失败
*/
return 0;
}