萌新咆哮!C++里引用和指针到底有什么区别?

2022-08-26 15:42:01 浏览数 (1)

作者 | 梁唐

大家好,我是梁唐。

我们在写代码的时候总是离不开函数,我们调用一个函数,得到一个想要的结果。这个过程好像自然而然,但有的时候又会遇到一些意想不到的问题让我们困扰。

尤其是涉及到函数参数的时候,我们经常会搞不清楚,我们在一个函数内部修改它传入的参数,究竟有没有作用?怎么有的时候有作用,有的时候又没有?这背后究竟是程序员的幻觉还是编译器的bug?

今天就和大家聊聊这个问题。

先来看一个开胃菜。

代码语言:javascript复制
void test(int a) {
    a = 5;
}

int main() {
    int a = 3;
    test(a);
    printf("%dn", a);
    return 0;
}

这是一段C 代码,有一个参数a,我们在函数当中修改了a的值,将它改成了5。那么请问,函数调用之前的a有没有发生变化?

我估计这个问题大家都能答得上来,答案是不会变化,外界的a还是3,不会变成5。

原因很简单,因为a虽然传入了函数里,但是传进去的是一个值拷贝。既然是一个拷贝,那么不论我们在函数当中对这个变量做什么,显然拷贝之前的值不会因此发生变化。

接着我们来提升难度,再来看一段代码。

代码语言:javascript复制
void test(int *a) {
    *a = 5;
}

int main() {
    int a = 3;
    test(&a);
    printf("%dn", a);
    return 0;
}

请问test函数执行之后,输出的结果还是3吗?

执行一下就知道了,答案不再是3,而成了5。

如果大家学过指针,这个问题应该都能理解。因为指针传递是会影响原值的,我们可以把指针理解成账号。我们可以把账号拷贝很多次,但是无论是哪一个拷贝对这个账号的内容进行了修改,那么都会影响到账号本身。这就是为什么你女朋友和老妈只要知道了你的账号,都能删除你游戏库的原因。

如果大家还不明白,我们再来看一段代码。

代码语言:javascript复制
void test(int *a) {
    *a = 5;
}

int main() {
    int a = 3;
    int *b = &a;
    test(b);
    printf("%dn", a);
    return 0;
}

这段代码就完美揭示了指针就是一个账号的事实,我们在调用test函数之前,首先创建了一个b指针,指向a。然后我们传入函数的是b指针,并不是a,但运行之后输出的结果仍然是5。

只要你愿意,你也可以试着多创建几个指针,不论创建多少个指针。只要它们都指向a,只要它们发生了改动,a也一样会改动。

到这里应该理解上都没什么问题,我们继续深入,再来看另外一个例子。

请问这段代码的执行结果是什么呢?

代码语言:javascript复制
void test(int &a) {
    a = 5;
}

int main() {
    int a = 3;
    test(a);
    printf("%dn", a);
    return 0;
}

为了防止大家没看清,我来强调一下,在这段代码里,我们传递的不再是指针了,而是在参数的类型int后面加上了一个奇怪的符号&。这个符号我们在刚才创建b指针的时候也用到过,它代表取地址符。使用在参数传递当中表示传递的是一个引用。

如果大家运行这段代码会发现,得到的结果依然是5。

也就是说传递引用之后,我们在函数当中修改了参数值,对参数本身一样起作用。那么问题来了,引用和指针有什么区别呢?

关于指针和引用的区别,往深了讲又可以单独写一篇文章了,为了防止大家一下子记不住,我们今天不聊那么深入,只谈谈原理上的区别。

其实很简单,引用可以理解成别名。就好像现在的明星往往会起个艺名一样,不管是艺名还是原名,都指的是同一个人。比如张三起了个艺名叫李四,某天报纸报道李四明星出车祸了,那么张三会不会出现在医院里?显然会,因为他们是同一个人。

那么引用和指针有什么区别呢?

其实很简单,指针是附加的属性,是一种绑定关系。引用则就是一个账号的别名,就好比我们用手机号绑定账号,我们可以用手机号登陆操作账号。但是这种绑定关系是可以变化的,我们明天可以重新绑定另外一个账号,而引用关系是没办法解绑的。

我们再来看一个例子:

代码语言:javascript复制
void test(int *a) {
    int b = 10;
    a = &b;
}

int main() {
    int a = 3;
    test(&a);
    printf("%dn", a);
    return 0;
}

在这段代码当中,我们传递了a的指针,但是在test函数里,我们给a重新指向了一个新的值。这就像是我们把手机号绑定了一个新的账号,和原先的账号解绑了。显然在这种情况下,原先的值并没有发生变化。所以输出的结果依然还是3。

我们进一步理解一下,指针和引用本身是两个东西,两个完全不同的概念。

指针是一个额外的类型,它可以指向某一个变量的地址。而引用则单纯就是同一个变量,只不过换了个名字。比如int a = 3; int &b = a;,a和b两个变量不仅值相同,而且它们底层的内存也是共享的。所以a改变了b也改变了,b改变了a同样改变,就好像是明星的本名和艺名一样。

可能有人会觉得奇怪,int a = 3; int &b = a;这样写和int a = b = 3;有什么区别?难道不都是等于3吗?

答案是完全不同,后面一种写法a和b虽然都等于3,但是它们的内存是分开的,而前面一种写法则相反,内存也是共享的。

理解了指针和引用是两个概念之后,很多问题也就解开了。

比如sizeof操作,我们来看一个例子:

代码语言:javascript复制
string a = "hello world";
string *b = &a;
string &c = a;
printf("%dn", sizeof c);
printf("%dn", sizeof b);

输出的结果一个是24一个是8,原因很简单,因为sizeof输出的是变量占用的内存大小。如果它对一个指针操作,输出的就是指针这个东西的大小,和它指向的内容没有关系。不论它指向什么,返回的都是8。而引用和本体是一样的,自然返回的就是本身的大小。

再比如自增运算,指针的自增运算和引用也是不同的。因为指针的自增默认是内存地址 1,也就是移动指针指向的内存位置。而引用的自增运算也就是变量的自增运算。

关于指针和引用还有很多很多要说,想要成为代码大牛,对于引用和指针的理解是必不可少的。今天的文章只是一个开胃菜,后面的内容更加精彩。

0 人点赞