大家好啊!这里是阳阳的博客,一个正在努力学习技术的大学生。

前面两篇我们聊了递归,也用几个例子看到了:有时候代码只是顺序换了一下,结果就完全不一样。

那今天这篇,咱们来聊另一个很多初学者都会遇到的问题:为什么我在函数里面明明改了变量,回到主函数里却一点变化都没有?

这个问题其实特别适合用来引出 C++ 里面几个很重要的概念:值传递、指针和引用。这几个东西刚开始看确实容易绕,但只要从“函数到底拿到的是什么”这个角度去理解,就会清楚很多。

好了,废话不多说,咱们直接开始。


简单案例:交换函数

我们先看一段代码。你觉得它会输出什么?

#include <iostream>
using namespace std;

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 3, y = 5;
    swap(x, y);
    cout << x << " " << y << endl;  // 期待输出 5 3 ?
    return 0;
}

先暂停一下,自己想一想。

运行结果其实是:3 5 —— 完全没有交换。

怎么回事?明明 swap 函数里已经交换了a 和 b,为什么外面的 x 和 y 没变?

这就要引出初学者最容易踩的第一个坑:值传递。下面我们就开始讲讲要怎么理解这部分内容


值传递:函数里的变量是“复印件”

在 C++ 里,如果我们直接把变量传给函数,比如 swap(x, y),函数里面的 ab 并不是原来的 xy,而是它们的 副本

可以这样理解:

你把一份资料复印给别人,别人把复印件改得再多,也不会影响你手里的原件。

所以刚才那段代码真正发生的过程大概是这样:

  1. main 里面有两个变量:x = 3y = 5

  2. 调用 swap(x, y) 时,把 xy 的值复制了一份,分别给了 ab

  3. 函数里面交换的是 ab 这两个副本。

  4. 函数结束后,ab 被销毁。

  5. 原来的 xy 从头到尾都没有被改过。

所以这就是我们常说的:函数里改了,外面却没变

那如果我就是想让函数真的改到外面的变量,该怎么办呢?很自然的想法就是:不要只给函数一份复印件,而是告诉它原变量在哪里。

这就引出了指针。


指针:保存地址的变量

指针这个词刚听起来可能有点抽象,但先不用想得太复杂。咱们可以先记住一句话:

指针就是用来保存地址的变量。

比如:

int x = 10;
int* p = &x;

这里有两个符号需要注意:

  • &x 表示取出变量 x 的地址。

  • int* p 表示 p 是一个指针变量,它里面存的是一个 int 类型变量的地址。

那既然 p 里面存着 x 的地址,我们就可以通过 p 找到 x,这一步叫 解引用

cout << *p << endl;  // 输出 10

这里的 *p 就可以理解成:顺着 p 里面保存的地址,找到那个变量的值

所以如果要真正交换两个变量,就可以把变量的地址传进函数:

void swapByPointer(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
​
int main() {
    int x = 3, y = 5;
    swapByPointer(&x, &y);
    cout << x << " " << y << endl;
    return 0;
}

这次输出就是:

5 3

因为函数拿到的不再是 xy 的复印件,而是它们的地址。函数通过地址找到了原变量,所以修改就真的生效了。

不过大家可能也发现了,指针写起来会多一些 *&,刚开始很容易看晕。那有没有一种写法,既能改到原变量,又看起来更像普通变量呢?

有,这就是引用


引用:给变量起一个“别名”

引用可以简单理解成:给一个变量起了另一个名字

比如:

int x = 10;
int& ref = x;
ref = 20;
​
cout << x << endl;  // 输出 20

这里 refx 的引用,也就是 x 的别名。我们修改 ref,其实就是在改 x

所以交换两个变量时,也可以这样写:

void swapByReference(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
​
int main() {
    int x = 3, y = 5;
    swapByReference(x, y);
    cout << x << " " << y << endl;
    return 0;
}

这次同样会输出:

5 3

引用的好处就是调用的时候很自然,还是直接写 swapByReference(x, y),但是函数内部拿到的不是副本,而是原变量的别名。

所以我觉得初学的时候可以这样记:

值传递看成复印件,指针 = 地址,引用 = 别名。


三种方式简单对比

为了方便理解,咱们放在一起看一下:

方式 函数参数写法 调用时写法 能不能改外面的变量
值传递 int a func(x) 不能
指针传递 int* a func(&x)
引用传递 int& a func(x)

这里最容易混的地方就是两个 &

  • int& ref = x; 里面,& 表示引用。

  • &x 里面,& 表示取地址。

它们长得一样,但出现在不同位置时,意思是不一样的。这个刚开始不用死记硬背,多看几段代码就会慢慢熟悉。


指针和引用还有一个重要区别

引用和指针都能让函数改到外面的变量,但它们并不完全一样。

引用一般定义时就要绑定到一个变量,并且后面不能再改成引用别的变量:

int x = 10, y = 20;
int& ref = x;
ref = y;  // 这不是让 ref 改绑到 y,而是把 y 的值赋给 x

而指针就更灵活一点,它可以先指向 x,后面再指向 y,也可以指向空:

int x = 10, y = 20;
int* p = &x;
p = &y;
p = nullptr;

所以简单来说:

  • 引用更像固定的别名,用起来简洁。

  • 指针更像可以改变方向的地址变量,更灵活,但也更需要小心。

至于什么时候用指针,什么时候用引用,这个后面结合数组、函数返回值、动态内存的时候会更好理解。今天先把最基础的概念弄清楚就行。


写在最后

今天咱们从一个简单交换函数的例子入手,搞清楚了:

  • 值传递:类比复印件,不改原件。

  • 指针传递:传地址,能改原件,但写法稍复杂。

  • 引用传递:别名,能改原件,写法简洁。

所以,只是参数的形式换一下——加个 &*,结果就不一样了。

后面我们会继续聊 数组与指针的关系动态内存分配 等等。如果你对指针还觉得晕,没关系,多练习写几遍自然就熟了。

如果这篇文章对你有帮助,麻烦点赞、关注和收藏吧,谢谢! 😄

有什么问题或者想法,欢迎在评论区留言,我们一起交流!

我们下篇见~

更多推荐