这货写的真详细,并且通俗易懂。看到的有福气了0.0呵呵
一个问题是,我们想用一个函数来对函数外的变量v进行操作,比如,我想在函数里稍微改变一下这个变量v的值,我们应该怎么做呢?又或者一个常见的例子,我想利用swap()函数交换两个变量a,b的值,我们应该怎么做呢(好吧,博主是觉得这个问题是足够的老土)。
如果你真的理解【函数】这个工具的本质,我想你稍微仔细的思考一下,可能就不会来查看博主的这篇文章,对函数来说,它所传递的任何参数仅仅是原来参数的一个拷贝,所以,对任何企图通过void swap(int a,int b)来交换a,b值或者想通过void alter(int v)来改变v的值,都是徒劳的。
C语言里,改变值只能通过指针(地址)方式进行传递,或许你会说传递数组不是也可以改变值么,实际上,传递数组就是传递指针(或许对数组来说,这个指针有点特别)//注意:C里没有引用,C++里才有
我们先来看一下有趣的swap函数,它用于交换a,b两个变量
code case 1
#include
void swap(int a,int b)
{
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=4,b=5;
swap(a,b);
printf("a = %d ,b = %d\n",a,b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
不出意料的,我们会知道这段代码其实并不能得到我们想要的结果,它并不能交换两个变量a,和b,的值,这是为什么?
我们不妨修改这段代码,在main()和swap()里分别打印a和b的地址,看看到底发生了什么;我们修改代码如下:
code case 2
#include
void swap(int a,int b)
{
printf("address in swap():%p %p\n",&a,&b);
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=4,b=5;
printf("address in main():%p %p\n",&a,&b);
swap(a,b);
printf("a = %d ,b = %d\n",a,b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
它的运行结果为:
address in main():0061FF2C 0061FF28
address in swap():0061FF10 0061FF14
a = 4 ,b = 5
1
2
3
1
2
3
显然,在两个函数里,它们的地址并不相同,这意味着,它们并不是相同的存储空间,改变swap里的值,实际上仅仅只改变了swap()里面的a和b的值罢了,一旦swap执行完,swap里的a和b的储存空间立即释放掉,对于main()里的a和b,没有半点影响。
我们举一个简单的例子,你有一个包子,你拿着这个包子给包子师傅看,然后包子师傅照着你这个包子的形状重新做了一个跟你的包子一模一样的包子,然后包子师傅马上吃掉了它,你觉得你的包子被吃掉了吗?当然没有!你的包子还在自己手里。
那么在C语言里如何才能交换两个变量的值呢?
方法是通过指针传参,看下面的代码
code case 3
#include
void swap(int *a,int *b)
{
printf("address in swap():%p %p\n",a,b);
int temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int a=4,b=5;
printf("address in main():%p %p\n",&a,&b);
swap(&a,&b);
printf("a = %d ,b = %d\n",a,b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
运行结果为:
address in main():0061FF2C 0061FF28
address in swap():0061FF2C 0061FF28
a = 5 ,b = 4
1
2
3
1
2
3
这样,就把a,b的值交换了!
等等,我们分析一下它的原理,它究竟做了哪些变化呢,在swap函数里,我们将a和b的地址给了swap函数,作为形参,在swap函数中,a和b是指向两个int 类型的指针,它们接受了main里面a和b的地址,也就是a=&a (in main());b=&b (in main());所以对*a实际上就是对a(in main())操作啦;
那么,聪明的你肯定能想到,在swap()函数里a和b的地址肯定和main里a和b的地址是不同的,swap里的a,b的地址是指针的地址(在swap里a,b是指针),而它们的值是在main()里面a和b的地址;
我们不妨打印一下swap里a,b和地址就明白了;
code case 4
#include
void swap(int *a,int *b)
{
printf("address in swap(),the value of a and b:%p %p\n",a,b);
printf("address in swap(),the address of a and b:%p %p\n",&a,&b);
int temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int a=4,b=5;
printf("address in main(),the address of a and b:%p %p\n",&a,&b);
swap(&a,&b);
printf("a = %d ,b = %d\n",a,b);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
运行结果:
address in main(),the address of a and b:0061FF2C 0061FF28
address in swap(),the value of a and b:0061FF2C 0061FF28
address in swap(),the address of a and b:0061FF10 0061FF14
a = 5 ,b = 4
1
2
3
4
1
2
3
4
通过结果我们知道,在swap里,指针a和b的值和main()里的a和b的地址是一样的,那么对*a进行的各种赋值实际上就是对main()里的a的各种操作,它们代表同一储存空间的的值,是同一个包子;但是对swap里的a,和b的地址,和main里的是不一样的,这是显然的,a只是一个容纳&a地址的容器罢了,它是swap里重新分配的一块内存,并且,它的类型和main里的a,b类型完全不同,它是一个指针类型;
用比喻的方法来讲,main函数里的a,b,就好比是你手中的包子,swap里的a,b就好比是包子师傅拿来用来盛放你手中包子的盘子,而*a和*b是你将自己手中的包子放到包子师傅盘子里的包子;包子师傅吃掉了盘子中的包子,你觉得你还有包子可吃吗?#huakji
当然,我想探讨的并不是只有这些,在文章一开始,我就引入了这样的话题,我们先看一下这段代码问题:
#include
#include
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
void InitLinkList(LNode *L)
{
L=(LNode *)malloc(sizeof(LNode));
L->data=0;
L->next=NULL;
}
int main()
{
LNode *L=NULL;
InitLinkList(L);
printf("%p\n",L);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
问:该代码能否正确初始化一个链表头结点?
我想,如果你能正确理解前面的几个例子,那么,你的答案一定回答的是NO,该InitLinkList并不能真正初始化一个链表头结点,它的工作其实是,在函数里初始化了头结点,但是,随着函数的结束,释放掉了所有的空间,并没有对main里的L起到了任何作用;
但是,在大多数时候,我们却的确是需要这样一个函数来为我们做这些事情,那么,应该怎么修改呢?
修改代码如下
#include
#include
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
LNode * InitLinkList(LNode *L)
{
L=(LNode *)malloc(sizeof(LNode));
L->data=0;
L->next=NULL;
return L;
}
int main()
{
LNode *L=NULL;
L=InitLinkList(L);
printf("%p\n",L);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果
006D1588
1
1
改过后的InitLinkList初始化了头结点,并把该地址传递给上一层的main中的L,所以得到了正确的结果
我们也可以这样改
#include
#include
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
void InitLinkList(LNode **L)
{
(*L)=(LNode *)malloc(sizeof(LNode));
(*L)->data=0;
(*L)->next=NULL;
}
int main()
{
LNode *L=NULL;
InitLinkList(&L);
printf("%p\n",L);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
运行结果
006B1588
1
1
那么,这又是怎么回事呢?
仔细思考一下,我们传递的参数是main里指针L的地址,在InitLinklist里,*L是什么呢?L又是什么呢?如果你认真查看了上面的代码,加上我之前的叙述,你应该会知道,L实际上就是main里&L的拷贝,L的值肯定是和&L是不一样的,但这没关系,因为*L和L是相同的,所以,你了解我这句话的含义了么?