const、常量指针、指向常量的指针

有点迷迷糊糊的,不过好在,绕出来了

前提

首先要知道,定义了一个 int 类型的数据之后,它的地址的类型为 int* 类型。
定义了一个 const int 类型的数据,它的地址的类型是 const int* 类型。

指向常量的指针

什么叫指向常量的指针?
我们往往会定义const 来修饰的数据,比如:

const int n = 9; // 定义了一个整型常量 n

如果想使用一个指针来指向它,应该是什么类型的指针呢?首先说,下面的指针是不可以的

const int n = 9; // 定义了一个整型常量 n
int* p = &n; // 定义了一个int* 类型的指针
// "const int *" 类型的值不能用于初始化 "int *" 类型的实体

上面的例子中,定义了一个int* 类型的指针p,要用它来指向一个const int类型数据。是不能通过编译的。
这是因为,const int 类型的数据,不能让int* 类型的指针指向,即const int* 类型的地址数据,不可以赋值给int* 类型的变量。即 const int* -> int* 是不可以的。
正确方法:

const int n = 9; // 定义了一个整型常量 n
const int * p = &n; // 定义了一个const int* 类型的指针    正确写法
int const * q = &n; // 正确写法

上面两种写法都可以,都是const 要在 * 号之前。我比较习惯const 在 int 之前的写法。

注意,因为指针指向的是常量,是不可以修改常量的值的。即不可以通过指针修改内部的值,也不可以直接通过对 n 本身赋值来修改 n 的值。

const int n = 9; // 定义了一个整型常量 n
const int * p = &n; // 定义了一个const int* 类型的指针,指向 n
n = 188; // 错误
*p = 188; // 错误

因为 n 本身是const的,所以,其不可以更改,所以,无论是直接修改自身,还是通过指向它的指针修改,都是不可以的。

更进一步

如下所示

int n = 9; // 定义了一个普通整型数据 n
const int * p = &n; // 定义了一个const int* 类型的指针,指向 n

首先定义了一个普通的整型变量 n (这次没有使用const修饰),又定义了一个 const int* 类型的指针 p ,指向了 n。注意这次是使用一个 const int* 类型的数据,指向了一个普通的整型变量。换句话说,将一个int* 的地址,给了const int* 类型的指针变量。通过编译了,是可以的。
即将 int* -> const int *,是可以的。

为什么如此就可以,反过来就不行?答:从int*到const int *是从大范围到小范围,C++进行了隐式的转换,所以能够赋值成功。

前面的从const int * 到 int * 是从小范围到一个大范围,无法进行隐式类型转换,所以必须进行强制(显式)类型转换才能赋值成功就是这个原因了。

const int n = 199;
int * p = (int*)&n; // 正确,显式的进行强制类型转换
int * q = &n; // 错误

如果 n 是普通的 int 类型的数据,使用const int* 的指针 p 来指向它。那么仍然是无法通过指针来修改其中的数据,可能编译器认为 此时 p 指向的就是一个 const 的数据,所以不能通过指针修改。但是可以直接赋值修改 n ,毕竟 n 只是普通的数据。

int n = 0;
const int* p = &n;// 编译器进行了隐式的类型转换
*p = 9; // 错误。不可以通过指向常量的指针修改原数据,虽然这里 p 指向的是普通数据
n = 9;// 正确。因为 n 是普通数据,所以可以直接赋值修改

总结:

一个const int * 类型的指针,既可以指向普通的 int 类型的数据,又可以指向使用const 修饰的常量数据。此时的指针成为指向常量的指针。指针本身可以再指向别的数据。但是不可以通过指针去修改指向的数据。是否可以直接通过给数据赋值的形式,更改数据,就要看 该数据 究竟是不是真的const数据。

指针常量

何谓指针常量,如下:

int n = 0;
int * const p = &n;

可以看到,const 放在了 * 的后面。

int m = 9, n = 188;
int* const p = &m;
p = &n; // 错误。p不可以再指向别处
*p = 999; // 正确与否,要看 n 是不是const数据。此处正确

所以指针常量,即该指针只能指向一处。不能再指向别的位置。可以通过其修改指向的数据。

修改const类型的数据

const int n = 8;
int* const p = &n; // 错误
// "const int *" 类型的值不能用于初始化 "int *const" 类型的实体

n是const int 的数据,其地址类型为 const int*,而指针常量 p 是 int* const类型的,通不过编译。
可以使用强制类型转换解决。

const int n = 8;
int* const p = (int* const)&n; // 强制类型转换
*p = 7; // 

注意看,竟然通过指针修改了所指向的地址里面的数据。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    
    
    const int n = 9;
    
    int* p = (int*)&n;

    *p = 10;

    printf("*p = %d\n",*p);
    // *p = 10
    
    printf("n = %d\n",n);
    // n = 9

    return 0;
}


上例中 n 为const 类型的变量,p 为指向它的一个普通指针变量(如果定义为const int* 类型的指针,是不能通过指针修改指向的内存中的数据的)。
通过 p 修改了内存中的值,但是 n 本身没有被修改。为什么?
这就是C++中的常量折叠:const变量(即常量)值放在编译器的符号表中,计算、访问时编译器直接从表中取值,省去了访问内存的时间,这是编译器进行的优化。n是const变量,编译器对n在预处理的时候就进行了替换。编译器只对const变量的值读取一次。所以访问n,得到的是9。n实际存储的值被指针p所改变。参考文章
另一个例子

#include <bits/stdc++.h>
using namespace std;

int main()
{
    
    
    const int a = 100;

    const int* p = &a;
    int* q =(int*)&a;

    *q = 9; // 修改 q 所指向的位置的数据

    printf("p所指的地址处,数据为%d\n", *p);
    printf("a为%d\n", a);
    printf("q所指的地址处,数据为%d\n", *q);
    
    return 0;
}

运行结果

运行结果
p所指的地址处,数据为9
a为100
q所指的地址处,数据为9

可以看到,a所在地址中的数据被修改了,但是a本身没有被修改,即const变量(即常量)值放在编译器的符号表中,计算、访问时编译器直接从表中取值,省去了访问内存的时间,这是编译器进行的优化。n是const变量,编译器对n在预处理的时候就进行了替换。编译器只对const变量的值读取一次。

猜你喜欢

转载自blog.csdn.net/weixin_44321570/article/details/123370015