指针是C/C++语言中最重要的概念之一,之前也已经介绍过了(指针(一)指针(二)),这里重述几个要点。

  1. 指针,从设计上讲,是一种变量类型,只是有点特殊在于这个指针变量的内存地址存储的是另一个变量的内存地址

  2. 为了实现这种指针设计,C/C++实现了一系列语法来让开发人员高效地使用指针,这其中最重要的是,如何定义指针和使用指针

    • 定义指针的语法是,数据类型 * 指针名称,例如int * p,这里的语法对于指针多了一层设计,既要求明确指针变量所存地址的变量的类型,这样编译器才能在获得变量的地址后得到正确的变量类型。

    • 使用指针的语法包括赋值和取值两种

      • 指针赋值,int a = 10; int *p; p = &a;,前面说过,指针变量存储的是变量的地址,那么怎么获得变量的地址呢,C/C++语法就又实现了另一个操作,引用[reference],符号&。这样,p = &a就意味着把整数变量a的地址赋值给指针变脸p

      • 指针取值,*p = 100; println(a); println(*p);也叫解引用[dereference],也就是说通过内存地址来获得对应的值。注意这里要和指针定义的语法区别,指针定义前面还有一个数据类型,而且*并不严格要求紧挨变量名称。通过指针取值可以实现对指针变量中所存储的内存地址的变量读写。

  3. const和指针结合使用,有三种方式

    • const修饰指针,又称常量指针,const int * p = &a,这种情况下,指针指向可以改,但指针指向的值不可以修改。意思是,该指针存储的内存地址可以修改,但是内存地址对应的值无法修改,所以叫常量指针。

    • const修饰变量名称,又称指针常量,int * const p = &a, 这种情况下,指针指向不可以改,但指针指向的值可以修改。意思是,该指针存储的内存地址已经固定,但是内存地址对应的值可以修改,所以叫指针常量,因为指针永远指向一个内存地址。

    • const修饰指针和变量名称,const int * const p = &a, 这种情况下,指针指向不可以改,同时指针指向的值也不可以改

  4. 利用指针取值数组,int arr[3] = {1,2,3}; int * p = arr;,此时p指向的是数组首地址也就是第一个元素,如果要访问下一个元素,就让指针偏移一个位置p++

  5. 当指针作为函数的形参时,函数的实参必须是变量的地址。比如函数定义为void func(int * p),函数调用时int a = 10; func(&a),此时我们就可以理解成,当a的地址传入时,func为它创见了一个名为p的指针变量,所以就是int * p = &a,这就和前面的指针定义和赋值对应上了。所以此时在函数内部操作的是一个指向外部变量a的指针变量。

  6. 指针可以用来访问结构体的每个成员,通过->来实现。

引用是和指针紧密结合的方法,这里也简明扼要的介绍引用:

  1. 引用的核心作用是给变量起别名,对应的语法为数据类型 &别名 = 原名,例如,int &a = p,这里pa的类型当然要保持一致。

  2. 使用引用时,首先要初始化一个引用,int a = 10; int &b = a;,这里就初始化了引用b如果只写了int &b;则无法初始化,因为引用是起别名,所以一个引用必须知道它是哪个变量的别名。同时,引用一旦初始化后就不可更改,比如前面b已经是a的引用[别名]了,不能再让b成为其他变量的别名了,而int c = 20; b = c这样的操作,只是对b这个别名赋值,也就是对作为b本体的变量a赋值。

  3. 引用可以作为参数传递给函数。函数定义为void func(int &p),函数调用为int a = 10, func(a)。此时我们就可以理解成,当a的地址传入时,func为它创见了一个名为p的引用,所以就是int &p = a,所以此时再函数操作的并不是一个函数内的新变量,而是一个外部变量a的别名变量p

  4. 不要返回局部变量的引用,这会产生内存泄漏风险,因为局部变量的生命周期相对较短,而引用对应的局部变量的内存可能很快被释放[虽然编译器可能临时存储一次这些被释放的变量的内存地址,但是多次访问一定会产生问题],从而引发内存泄漏风险。

  5. 引用本质,或者说实现方式,是一个指针常量,也就是说该指针指向不可以改[所以引用不可修改],但指针指向的值可以修改[所以可以通过引用修改变量]。例如,int &ref = a编译器会转换成int * const ref = &a

  6. 引用并不是指针的替代或者补充,而它是和指针相辅相成,并且C++推荐更多的使用引用,因为可以在大多数场景下简化不必要的指针操作