一 背景
如果问C语言函数传参方式,大多数人答案是值传递和地址传递。值传递不改变原参的值,而地址传递(指针)传递会改变原参的值。
但实际上所有的传参方式有且仅有一种,就是值传递。值传递和地址传递都是通过拷贝实参的值,值传递比较好理解,直接拷贝实参,而地址传递拷贝的值是指针变量所指向变量的地址。
说到指针变量,一瞬间要想到3个概念。1 指针变量所指向变量的值; 2 指针变量所指向变量的地址; 3 指针变量本身自己所占的内存地址。
int a = 2;
int *p;
p = &a;
(对应上述代码: *p 对应第1个概念, p 对应第2个概念, &p 对应第3个概念; )
二 值传递
void swap(int a , int b){
int temp = a;
a = b;
b = temp;
}
int main(void){
int a = 4, b =10;
printf("a=%d,b=%d",a,b);
swap(a,b);
printf("a=%d,b=%d",a,b);
return 0;
}
这种方式形参拷贝了实参,并不能达到交换的目的。
三 地址传递
第一种方式:
void swap(int *a , int *b){
int *temp = a;
a = b;
b = temp;
}
int main(void){
int a = 4, b =10;
printf("a=%d,b=%d",a,b);
swap(&a,&b);
printf("a=%d,b=%d",a,b);
return 0;
}
上述这种方法虽然传入的是指针变量,但是由于函数传递过程中是值传递,传入的也是a和b的地址,只对地址进行了交换。所有也不能对实参的数进行交换。
另外一种方式交换:
void swap(int *a , int *b){
int temp = *a;
*a = *b;
*b = temp;
}
int main(void){
int a = 4, b =10;
printf("a=%d,b=%d",a,b);
swap(&a,&b);
printf("a=%d,b=%d",a,b);
return 0;
}
这种方式能交换成功,思考为什么呢?关键在于传递地址后,进行了取内容(*)操作,此时指针变量指向了原来的值,即从较低级的运算 跳到较高级的运算操作,就达到了交换实参的目的(笔者这样说这样方便理解和记忆)。现在回想值传递和第一种地址传递,就是因为它们缺少了这一跳转操作,无法完成改变实参。
四 进一步思考
在学习初始化链表操作时,往往有2种方法比较常用
直接返回:
#include<stdio.h>
#include<stdlib.h>
struct node{
int data;
struct node *next;
};
typedef struct node* linkList;
//直接返回
linkList create(void){
linkList p;
p = (linkList )malloc(sizeof(node));
p->data = 10;
p->next = NULL;
return p;
}
int main(void){
linkList head = create();
printf("head->data=%d",head->data);
return 0;
}
//输出: head->data= 10
通过传入地址:
#include<stdio.h>
#include<stdlib.h>
struct node{
int data;
struct node *next;
};
typedef struct node* linkList;
//传入链表方式1
create(linkList *p){
( *p) = (linkList )malloc(sizeof(node));
( *p)->data = 10;
( *p)->next = NULL;
}
// 下面这个方法是无法初始化的
//传入链表 方式2
void create2(linkList p){
p = (linkList )malloc(sizeof(node));
p->data = 10;
p->next = NULL;
}
int main(void){
linkList head = NULL;//注意 head是指针,传入是head是地址,为什么要传入&head,地址的地址
create(&head); //方式1有用
//create2(head); //方式2无用
printf("head->data=%d",head->data);
return 0;
}
//输出: head->data= 10
思考为什么2个方式都是传入地址,但方式1有用,但是方式2无用呢。
表象就是笔者前面所述,没有从较低级的运算 跳到较高级的运算操作,只是平级操作,达不到影响实参的目的。
其实质就是:函数传参都是值传递,方式2也只是改变形参(拷贝)的内容,没有影响到实参内容。而加了(*p)操作后,此时相当于不是平级操作,跳到实参,达到了改变实参目的。
五 总结
以后看到值传递或者指针传递,要想到本质是值传递,都是拷贝实参内容,普通变量是拷贝的是变量值,而指针变量是所指向变量的地址。进行平级操作不能改变实参的值,而要想真正改变实参的值,需要(*)取内容跳级操作,才能影响到。对于想影响普通变量,需要一级指针;对于想影响一级指针(即前面笔者表述初始化链表的第2种方式),需要使用二级指针(地址的地址)。在交换时,都使用了跳转操作。