一个问题是,我们想用一个函数来对函数外的变量v进行操作,比如,我想在函数里稍微改变一下这个变量v的值,我们应该怎么做呢?又或者一个常见的例子,我想利用swap()函数交换两个变量a,b的值,我们应该怎么做呢(好吧,博主是觉得这个问题十分经典)。
如果你真的理解C语言中【函数】这个工具的本质,我想你稍微仔细的思考一下,可能就不会来查看博主的这篇文章,对函数来说,它所传递的任何参数仅仅是原来参数的一个拷贝,所以,对任何企图通过void swap(int a,int b)来交换a,b值或者想通过void alter(int v)来改变v的值,都是徒劳的。
C语言里,改变值只能通过指针(地址)方式进行传递,或许你会说传递数组不是也可以改变值么,实际上,传递数组就是传递指针(或许对数组来说,这个指针有点特别)//注意:C里没有引用,C++里才有
我们先来看一下有趣的swap函数,它用于交换a,b两个变量
code case 1
#include <stdio.h>
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;
}
不出意料的,我们会知道这段代码其实并不能得到我们想要的结果,它并不能交换两个变量a,和b,的值,这是为什么?
我们不妨修改这段代码,在main()和swap()里分别打印a和b的地址,看看到底发生了什么;我们修改代码如下:
#include <stdio.h>
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;
}
运行结果为:
address in main():0061FF2C 0061FF28
address in swap():0061FF10 0061FF14
a = 4 ,b = 5
显然,在两个函数里,它们的地址并不相同,这意味着,它们并不是相同的存储空间,改变swap里的值,实际上仅仅只改变了swap()里面的a和b的值罢了,一旦swap执行完,swap里的a和b的储存空间立即释放掉,对于main()里的a和b,没有半点影响。
那么在C语言里如何才能交换两个变量的值呢?
方法是通过指针传参,看下面的代码
code case 3
#include <stdio.h>
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;
}
运行结果为
address in main():0061FF2C 0061FF28
address in swap():0061FF2C 0061FF28
a = 5 ,b = 4
这样,就把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和地址就明白了;
#include <stdio.h>
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;
}
运行结果
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
问:该代码能否正确初始化一个链表头结点?
#include <stdio.h>
#include <malloc.h>
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;
}
我想,如果你能正确理解前面的几个例子,那么,你的答案一定回答的是NO,该InitLinkList并不能真正初始化一个链表头结点,在函数里我们的确是给L分配了内存,初始化了结点,但是,InitLinkList()里的L并不是main()里的L,虽然名称是一样的,但是InitLinks()的L是局部的(所以,其实你写成a,b,c,d都没关系),传进来的只是一个LNode*副本,这个副本和外面的L的内容是一样的,但是变量不是同一个,当这个子函数执行完后,main()里的L还是原来的L。
(注意!在InitLinkList函数中通过malloc分配的内存是通过堆来划分的,这意味着函数调用完毕后,内存不能自动释放,将会造成内存泄漏,并且,此代码中malloc申请的内存是悬浮的)
但是,在大多数时候,我们却的确是需要这样一个函数来为我们做这些事情,那么,应该怎么修改呢?
#include <stdio.h>
#include <malloc.h>
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;
}
或者我们可以使用二级指针
#include <stdio.h>
#include <malloc.h>
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;
}