对指针和引用的深入理解
1 引言
经常在函数的定义中,看到有些参数是指针,有些参数是引用。
指针还好理解,但引用的使用不大理解。既然,引用跟变量本身是一回事,为何要使用引用呢?不是多此一举么?
带着这个问题,开始探索二者之间的差别,以及定义引用的目的。
2 函数参数中指针的含义
查资料说,指针是对变量的拷贝。
这句话有几个意思。
一是,函数调用时,会先创建一个地址空间(一般为4字节),然后把变量的地址值拷贝过来。
例如:
void test(int *p)
{
int a = 1;
p = &a;
cout << p << "" << *p << endl;
}
int main(void)
{
int *p = NULL;
test(p);
if(p == NULL)
cout << "指针p为NULL" << endl;
system("pause");
return 0;
}
按照常规理解,main函数中的p指针为NULL,传递到test函数以后,该指针值似乎被修改了。
p = &a;
那么在main函数中,理论上下面的语句不会执行到。
if(p == NULL)
但,实际结果与我们想象的不一样,这段代码会执行到。
刚开始我也不理解。
后来,当我知道了“指针是变量地址的拷贝”这个意思后,才明白了。
原来,函数被调用的时候,对于指针参数,首先会在堆栈中创建一个地址,test函数中的p,就是一个新的地址,也是新的内存空间。
这个内存空间,与main函数中的指针p,不是同一个内容。
也就是说:
这两个p,是两个不同的地址。
main函数中的地址p,是在main函数堆栈中的一块内存,只是因为它为NULL,所以这块内存的值为0(多数编译系统中,NULL值都为0,但不一定,所以,这里就算是示意吧)。
打个比方,我们电脑上的内存是一间超大的酒店,有好多好多房间,内存空间就是这些房间(主流的8G内存,就好比有80亿个房间)。
房间的作用有两个:
一些房间用于客人居住,这里,客人就好比实际的数据。
另外一些房间不住人,只用来放一块大牌子,这个牌子上写着,xx客人住在yy房间。
按照这个说法,main函数中定义指针p,相当于服务员给开了一个房间,假设房间号为A,放标牌,只是,标牌啥也没写(或者是打叉,这个跟酒店的管理规定有关)。
当调用test函数时,参数为p指针,该函数告诉服务员又开一个房间,假设这个房间号为B,这个房间也放标牌,标牌上的内容,跟调用该函数之前定义的标牌的内容完全一致,也就是说,目前也是空(或者打叉)。
好了,这里是关键。
在test函数中,p值会修改,变成a变量的指针,也就是说,B房间里的标牌上不再是空的,而是写有内容的了。
但是,但是,A房间里标牌,还!是!空!的!
到这里,终于明白“指针是拷贝”的真正含义了。
其实,test函数定义也让人稍微有些容易混淆。
如果test函数是下面的这个样子。
void test(int* ptr)
{
int a = 1;
ptr = &a;
cout << ptr << "" << *ptr << endl;
}
定义成ptr,跟main函数中的p不一样,有可能会更好理解一点。
但是,关键还在于要彻底理解上面所讲的“拷贝”的含义。
第二个意思是,既然要开新的房间,那么,费时(虽然这点时间对于电脑来说不算什么),费空间(虽然也不算什么,不就4个字节么)。
3 引用的含义
又查资料,得到如下信息:
引用,就是变量的别名,操作引用就是操作变量本身。
举例。
int a = 1;
int *pA = &a;
int &b = a;
a是一个实实在在的变量,按照上边的例子,a是住在酒店的客人,&a表示该客人所在的房间号,pA也是一个房间,里面住的不是客人,而是有一块牌子,上面写明了客人a所在的房间号。
![[Pasted image 20240620112255.png]]
源内容参考:
![[240531-1656-指针和引用示意图.pptx]]
于是,pA就应该是301房间号,pA本身也在房间201中。
b是a的引用,相当于别称,就像,张明明是大名,小明是他的小名,妈妈说小明赶紧去上学,老师说张明明你来回答问题,其实说的是同一个人。
好了,据此再来理解函数参数中的引用。
//形参是引用类型
void swap(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int i = 3, j = 5;
swap(i,j);
cout << "i=" << i << "" << "j=" << j << endl;
return 0;
}
这段代码,把变量i和j的值交换。
4 函数参数中为什么要引用?
还是以交换两个变量的值为例。
4.1 第一种情况,直接使用形参,试试看
//交换变量的值
void swap(int a,int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int i=3,j=5;
swap(i, j);
cout << i << "" << j << endl;
return 0;
}
执行的结果,与我们的想象不一样。
还按照上边酒店房间的例子。
假设,客人i是一名女客人,小红,住在301房间。
客人j是一名男客人,小明,住在302房间。
函数swap在被调用时,在栈里创建两份内存。相当于服务员开了两个空房间,假设为401和402,并把小红和小明各克隆一份,叫做小红1号和小明1号,并分别安排到401和402房间。
注意:这里i(小红1号)和j(小明1号)不是原来的,是克隆人。
把401和402房间里的人交换,变成402住着小红1号,401住着小明1好。
但是,但是,原来的301和302房间的人并不变化,小红还在301,小明也还在302住着。
任务失败。
4.2 第二种方式,使用指针
void swap(int* p1,int* p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
void swap(int*, int*);
int i = 3, j = 5;
swap(&i, &j);
cout << i << "" << j << endl;
return 0;
}
房间301住着小红,房间302住着小明。
在swap函数中,p1是个指针,就是个标牌,标明301房间,即小红住的房间号。同样地,p2表示小明住的房间号。
*p1
,取地址里的值,相当于说,先找301的客人,就是小红。同样地,*p2
是客人小明。
第一步,把小红克隆一份到temp(想起了阿凡达),外表、思想完全一样的。
再把小明克隆一份,放到小红身上,这个时候,小红也不再是小红,而是小明了。
同样地,把temp(克隆后的小红)又克隆一份,放到原来的小明身上,这个后来者就是小红。
克隆后的小红temp在swap函数调用完毕就销毁了。
最后的结果就是,房间还是那两个房间,但通过神秘的克隆和销毁技术,神不知鬼不觉的把两个房间里的人更换了。
于是,301房间不再住着小红,而是住着小明。302房间则住着小红。
任务完成。甭管克隆有没有副作用,反正是完成预定的目标。
美中不足的是,这中间总是得通过房间号来完成。有没有更好的方式呢?
4.3 第三种方式,通过引用来完成
这就是上上上边的例子了。
不用通过房间号(指针)来操作,直接指示。
克隆小红(a)到阿凡达(temp),克隆小明(b)到小红身上,再把阿凡达克隆一份到小明身上。
完成任务。
是不是简洁多了?
这,就是引用的用途。
5 再来一个引用的例子
void test(int* &ptr)
{
int a = 1;
ptr =& a;
cout << ptr << "" << *ptr << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
int *p = NULL;
test(p);
if(p != NULL)
{
cout << "指针p不为NULL" << endl;
}
system("pause");
return 0;
}
test参数,*&ptr
又是如何理解?
我的理解是,如果没有&符号,如下:
void test(int* ptr)
则函数在调用的时候,ptr就是拷贝出的一个新地址,就是服务员开的新房间,结果前边已经分析过。
带&符号,如下:
void test(int* &ptr)
可以把&ptr整体当成一个指针,但是,注意了,这个指针不再是创建出来的一个新内存地址,而是跟原来的地址一模一样。
或者说,只是表明参数是个指针而已。
int* p = NULL;
test(p);
操作ptr,就是操作main函数中的p。
继续酒店房间的例子。
main函数中的p,假设是501房间里的标牌,标牌上目前啥也没写。
调用test函数,其参数是*&ptr,表示要操作的是房间的标牌(指针类型),而且是,只能是501房间的那块标牌,因为有&引用符号。
好了,下面的代码:
p = &a;
就是改写了501房间的那块标牌,这个时候,标牌上写着905。
于是,改写了。
于是p不是NULL。