概念 📝

指针是一个变量,里面存储的是指向内存存储单元的一个地址。

int a = 5;
int *ptr = &a;
cout << "变量 a 的内存地址:" << ptr << " 值:" << *ptr;

打印结果:

变量 a 的内存地址: 0x7ffdaea1eb0c 值: 5

a 就是个变量,ptr 是指针变量,ptr 里面存储的是 a 的内存地址。

引用是一种特殊的指针,可以理解为是对一个变量起了个别名。

int b = 5;
int &ref = b;
cout << "变量 b 的地址:" << &b << endl;
cout << "引用 ref 的地址:" << &ref << endl;
cout << "引用 ref 的值:" << ref;

打印结果:

变量 b 的地址:0x7ffdd17efcd4
引用 ref 的地址:0x7ffdd17efcd4
引用 ref 的值:5

不同点 📜

  1. 指针是一个对象,而引用不是。所以引用不可以为空,创建的时候,必须初始化,指针可以在任何时候被初始化。
  2. 指针可以有多级,但是引用只能一级(int **p 正确,而 int &&p 不正确)。
  3. 指针的值初始化之后可以改变,可以指向其他存储单元,引用初始化之后就不能再改变。
  4. sizeof 引用得到的是所指向的变量 (对象) 的大小,而 sizeof 指针得到的是指针本身的大小。
  5. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏。
  6. 指针和引用的自增(++)运算意义不一样。(一个地址 + 1,一个值 + 1)

指向指针的引用 🤔

int i = 42;
int *p;
int *&ref = p; // ref 是一个引用,引用了一个指针
ref = &i; // ref 相当于是 p 的别名,等价于 p = &i
cout << ref << endl;
cout << p << endl;
cout << *ref << endl;
*ref = 100;
cout << i;

打印结果:

0x7ffe7289e394
0x7ffe7289e394
42
100

复杂表达式从右向左一个一个阅读,离变量名最近的符号是对变量类型有直接影响,这里 *(&ref), 因此 ref 是一个引用,然后 (&ref) 是一个指针。

const 结合 🔒

1. const 和引用

常量的引用,必须也是一个常量。

const int ci = 1024; // 这是一个常量
const int &r1 = ci; // 正确,引用及其对应的对象都是常量
int &r2 = ci; // 错误,试图让一个非常量引用指向常量引用。注意这两行代码的区别

变量的引用

int i = 42; // 一个变量,未初始化
const int &r1 = i; // r1 是指向 i 的引用常量
const int &r2 = 42; // int &r2 = 42 就会报错了,因为引用必须初始化指向的是对象
const int &r3 = r1 * 2;
cout << r1 << endl;
cout << r3 << endl;
i = 12;
cout << r3 << endl; // r3 依旧引用当初的那个变量 i
cout << i << endl; // i 却已经变了,这是个忧伤的故事
int &r4 = r1 * 2; // 报错,r4 是非常量引用,不能用常量引用来初始化。

常量引用指向变量的时候,即便变量修改了值,常量引用依旧指向的是当初的那个变量的值,不离不弃。

2. const 和指针

将指针定义为不可更改的常量。

  • 顶层 const:表示地址本身是常量
  • 底层 const:表示数据是常量
int num_b = 2;
int *const p_b = &num_b; // 顶层 const
int num_a = 1;
int const *p_a = &num_a; // 底层 const
double pi = 3.14;
const double *p = &pi; // 底层 const
*p = 30; // 错误,即便 pi 是变量,但是指针是常量,没办法修改数据
double x = 32.2;
cout << *p << endl; // 输出 3.14
p = &x; // 正确, 可以修改地址
cout << *p << endl; // 输出 33.2
double pi = 3.14;
double *const p = &pi; // 顶层 const
*p = 30; // 正确,没办法修改地址,但是数据可以修改
double x = 32.2;
p = &x; // 错误,不能修改地址

从上面的实验可以了解到,底层 const 虽然值是不可以改变的,但是可以通过指针地址的变动,从而修改里面的值。
顶层 const 指向的地址无法改变,那如果我指向一个 const 类型的变量,直接修改它的值,是不是就能变相修改 const 变量的值了呢,答案是不行的。

const double z = 3.14;
double *const y = &z; // 编译报错

作为函数参数 🛠️

让指针或者引用作为函数参数来传递,是为了减少对实参的副本拷贝。
我的理解是指针和引用作为函数参数,都可以达到想要的效果,引用可以看作是对实参起了个别名,操作的是同一个对象。

函数参数是指针的函数

void testPoint(int *p)
{
    int a = 1;
    p = &a; // 获取它的值必须要写成 *p
    cout << p << " " << *p << endl << endl;
}

函数参数是引用的函数

void testRef(int &ref)
{
    int a = 1;
    ref = a; // 直接 ref 拿来使用
    cout << &ref << " " << ref << endl << endl;
}

指针函数和函数指针 🎭

函数指针

一种特殊的指针,指向函数入口

/*
* 定义一个函数指针 p,只能指向返回值为 int,形参为两个 int 的函数
*/
int (*p)(int, int);
int max(int a, int b) {
    return a > b ? a : b;
}
int min(int a, int b) {
    return a < b ? a : b;
}
int main() {
    p = max; // 函数指针 p 指向求最大值的函数 max
    int c = (*p)(1, 2);
    p = min; // 函数指针 p 指向求最小值的函数 min
    c = (*p)(1, 2);
}

指针函数

一个函数,返回值是指针

class Hello {
public:
    string world;
};

Hello *p(string world) {
    Hello *hello = new Hello();
    hello->world = world;
    return hello;
}

int main() {
    Hello *hp = p("name");
    cout << "对象中的值" << hp->world << endl;
}

上面是用 new 的方式生成了一个 hello 实例,然后返回了这个实例的指针,乍看上去,应该出了作用域就应该被回收了,但是没有,并打印出了 name
如果是简单类型,就回收了。

int *function(int a) {
    int temp = 5;
    return &temp;
}

int main() {
    int *hp = function(3);
    cout << "指针的值" << *hp << endl; // 打印了 0
}

原因应该是用了 new,则需要显式 delete,否则内存不释放,就 GG 了。
那不用 new 这种显式声明呢?

class Hello {
public:
    string world;
};

Hello *r(string world) {
    Hello hello;
    hello.world = world;
    return &hello;
}

int main() {
    Hello *hr = r("name");
    cout << "指针的值" << hr->world << endl; // 直接空指针了
}

不声明 new,出了作用域,对象马上灭亡,所以空指针。

指向数组 📚

char x[] = {'a', 'b', 'c', '\\0'};
cout << *x; // 指向第 0 个元素,输入 a

一个 Array 类型的变量,其实本质上是一个指向数组第一个元素的指针。

标签: c++, 指针

添加新评论

😊