一、C 语言 const 关键字简介 - 左数右指原则



【C 语言】const 关键字用法 ( 常量指针 - const 在 * 左边 - 修饰数据类型 - 内存不变 | 指针常量 - const 在 * 右边 - 修饰变量 - 指针不变 )


1、const 关键字左数右指原则


普通类型数据的常量定义时 , const 关键字 在 数据类型 的 左边 和 右边 其作用 是相同的 ;

    // 下面两种 const 用法效果相同
    // 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
    const int a = 10;
    int const b = 20;

指针数据的相关常量类型 :

  • const 关键字在 指针符号 * 左侧 表示该定义的事 常量指针 ( 指向的内存不能修改 )
    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
    //      但是 , c 和 d 指针的指向可以修改
    // 下面两种情况是相同的
    const int* c;
    int const* d;
  • const 关键字在 指针符号 * 右侧是 表示定义的事指针常量 ( 指针本身不能被修改 ) ;
    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改 
    //      但是 , 指针指向的内存中的数据可以修改
    int* const e = (int*)malloc(10);

指针常量与常量指针 : 需要查看 const 修饰的是 指针变量 , 还是 修饰 指针变量 指向的内存空间 ;

  • const 在 * 右边 ( 指针常量 | const 修饰的是变量 ) : 如果 const 修饰的是 指针变量 , 如 char * const d , const 修饰的是 char * , 指针不能被修改 ; 这是 指针常量 ;

  • const 在 * 左边 ( 常量指针 | const 修饰的是数据类型 ) : 如果 const 修饰的是 指针变量 指向的内存空间 ,const char *c , const 修饰的是 char , char 数据不能被修改 , 这是 常量指针 , 指向常量的指针 ;


2、代码示例 - const 关键字左数右指原则


下面的代码中 , 列出了 const 关键字的所有情况 , 看注释即可理解左数右指原则 ;


代码示例 :

// 导入标准 io 流头文件
// 其中定义了 std 命名空间
//#include <iostream>
// 导入 std 命名空间
//using namespace std;

#include <stdio.h>
#include <malloc.h>

int main() {

    // 下面两种 const 用法效果相同
    // 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
    const int a = 10;
    int const b = 20;

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
    //      但是 , c 和 d 指针的指向可以修改
    // 下面两种情况是相同的
    const int* c;
    int const* d;

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改 
    //      但是 , 指针指向的内存中的数据可以修改
    int* const e = (int*)malloc(10);

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面两种情况 const 在指针左边和右边 , 数据和指针是常量 , 都不能修改
    // 下面两种情况是相同的
    const int* const f = (int*)malloc(10);
    int const* const g = (int*)malloc(10);

    return 0;
}

3、const 关键字使用场景


const 关键字 一般用于修饰 函数参数 , 给函数传入的参数 如果不想 用户在方法中 修改 数据 或 指针 , 可以使用 const 关键字修饰 形参 ;

定义结构体 :

struct Student
{
    char name[64];
    int age;
};

函数接收上述结构体类型变量作为参数 , 如果参数中 const 在 * 左边 , const Student *pS , 根据 左数右指原则 , 指针指向的数据是常量 , 不能被修改 ;

下面是错误示范 :

// 左数右指 , const 在指针左边 , 指针指向的数据不能被修改
int fun0(const Student *pS) {
    pS->age = 20;
    return 0;
}

如果强行修改指针指向的数据值 , 就会在编译时报错 :

表达式必须是可修改的左值

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP

函数接收上述结构体类型变量作为参数 , 如果参数中 const 在 * 右边 , Student* const pS , 根据 左数右指原则 , 指针本身是常量 , 指针指向不能被修改 ;

下面是错误示范 :

// 左数右指 , const 在指针右边 , 指针本身的指向不能被修改
int fun2(Student* const pS) {
    pS = NULL;
    return 0;
}

如果强行修改指针指向 , 就会在编译时报错 :

表达式必须是可修改的左值

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP


上述完整代码示例 :

// 导入标准 io 流头文件
// 其中定义了 std 命名空间
//#include <iostream>
// 导入 std 命名空间
//using namespace std;

#include <stdio.h>
#include <malloc.h>

struct Student
{
    char name[64];
    int age;
};

// 左数右指 , const 在指针左边 , 指针指向的数据不能被修改
int fun0(const Student *pS) {
    //pS->age = 20;
    return 0;
}

// 左数右指 , const 在指针右边 , 指针本身的指向不能被修改
int fun2(Student* const pS) {
    //pS = NULL;
    return 0;
}


int main() {

    // 下面两种 const 用法效果相同
    // 定义普通类型 ( 非指针类型 ) 的常量 const 在 类型左右 都是相同的
    const int a = 10;
    int const b = 20;

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面两种情况 const 在指针左边 , 数据是常量 , 内存中的数据不能修改
    //      但是 , c 和 d 指针的指向可以修改
    // 下面两种情况是相同的
    const int* c;
    int const* d;

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面的情况 const 在指针右边 , 指针是常量 , 指针地址不能修改 
    //      但是 , 指针指向的内存中的数据可以修改
    int* const e = (int*)malloc(10);

    // 左数右指 : const 在指针左边 数据是常量 , const 在指针右边 指针是常量
    // 下面两种情况 const 在指针左边和右边 , 数据和指针是常量 , 都不能修改
    // 下面两种情况是相同的
    const int* const f = (int*)malloc(10);
    int const* const g = (int*)malloc(10);


    return 0;
}




二、C 语言 const 关键字原理分析




1、C 语言中常量的原理和缺陷


C 语言中的 const 关键字 并不是 真正的 " 常量 " , 是一个 " 冒牌货 " ;

C 语言中的 const 关键字定义的常量 , 其本质是在 内存 中分配的空间 ;

C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;


2、代码示例 - C 语言中直接改变常量值报错


定义一个常量 const int a = 10; , 为该常量值 a 赋值 , 会报错 error: assignment of read-only variable 'a' ;

代码示例 :

#include <stdio.h>

int main() {

    // 定义常量
    const int a = 10;

    // 下面的代码会报错 , 貌似 a 是常量
    a = 20;

    return 0;
}

编译时的报错信息 :

C:\Users\octop\Desktop>gcc hello.c
hello.c: In function 'main':
hello.c:9:7: error: assignment of read-only variable 'a'
     a = 20;
       ^

C:\Users\octop\Desktop>

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP


3、代码示例 - C 语言中使用常量地址修改常量值


如果使用 指针 变量 , 接收 常量 a 的地址 , 然后通过该指针修改 指针指向的 内存空间的值 , 然后再打印 常量 a 的值 , 发现 常量 a 的值发生了改变 ;

因此 , C 语言中的常量 , 是可以通过指针进行修改的 ;

代码示例 :

#include <stdio.h>

int main() {

    // 定义常量
    const int a = 10;

    // 下面的代码会报错 , 貌似 a 是常量
    //a = 20;

    // 定义一个指针
    int* p = NULL;
    // 将 常量 a 的地址赋值给指针
    p = (int *)&a;

    // 通过指针修改 常量 a 的值
    *p = 20;

    // 打印 a 的值
    printf("a = %d\n", a);

    return 0;
}

执行结果 :

C:\Users\octop\Desktop>gcc hello.c

C:\Users\octop\Desktop>a.exe
a = 20

C:\Users\octop\Desktop>

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP


将相同的代码 , 拷贝到 C++ 环境中 , 编译运行的结果 , 与 C 语言环境中的编译运行结果不同

a = 10

Y:\002_WorkSpace\002_VS\HelloWorld\HelloWorld\Debug\HelloWorld.exe (进程 18604)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP

出现上述问题 , 是因为 C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;





三、C++ 语言 const 关键字 - 符号表存储常量




1、C++ 语言中常量原理


C++ 语言中 使用 const 关键字 定义的常量 , 是真正的 " 常量 " ;

C++ 编译器 对 const 关键字 修饰 的常量 , 进行了 特殊处理 ;

C++ 编译器 扫描到 const int a = 10; 代码后 , 发现 const 常量 , 不会为其单独分配内存 , 而是 将 常量 a 放在 符号表 中 ,

符号表 中的数据是以 " 键值对 " 的形式存在的 , 一个 键 Key , 对应一个值 Value ;

反映到 const int a = 10; 代码中 , 键 Key 是 a , 值 Value 是 10 , 在之后的代码 使用 常量 a 时 , 会直 从 符号表 中取出 10 ;

在下面的代码中 , 使用指针 p 获取 常量 a 的地址 , 获取的并不是 符号表 中 常量 a 的地址 , 而是 从 符号表中 取出常量 const int a = 10 , 为其 分配一个内存空间 , 将 10 存进去 , 然后将首地址返回 赋值给指针 p ;

实际上 指针 p 指向的是一个内存空间 , 内存空间中的值是 常量 a 的值 , 但是此时与常量 a 没有关系了 , 该值可以被修改 ;

    // 定义常量
    const int a = 10;

    // 下面的代码会报错 , 貌似 a 是常量
    //a = 20;

    // 定义一个指针
    int* p = NULL;
    // 将 常量 a 的地址赋值给指针
    p = (int *)&a;

    // 通过指针修改 常量 a 的值
    *p = 20;

对比 C 语言 中 , 会为 const 常量 单独分配内存 , 导致 用户可以 通过取地址符 & 获取该内存的地址指针 , 通过该指针可以修改内存中的数据 ;


2、代码示例 - 分析指针指向的值和实际常量值


修改上述代码 , 在不同的时间获取 *p 指向的内存空间值 和 常量 a 的值 ;

发现 使用指针 接收 常量 a 的地址 , 是在内存中重新分配内存并赋值为 10 , 并没有获取到符号表的内存地址 ;

修改内存中的值 , 不会影响到 符号表 中常量 a 的值 ;

代码示例 :

#include <stdio.h>

int main() {

    // 定义常量
    // 该常量定义在了 符号表 中
    // 符号表 不在内存四区中 , 是另外一种机制
    const int a = 10;

    // 下面的代码会报错 , 貌似 a 是常量
    //a = 20;

    // 定义一个指针
    int* p = NULL;
    // 将 常量 a 的地址赋值给指针
    // 在 堆内存中重新 分配一个 4 字节的空间 
    // 将 常量 a 的值 10 存储进去
    p = (int *)&a;

    // 打印 a 和 *p 的值
    // 此时 还没有修改 *p 的值 , 两个值都是 10
    printf("a = %d , *p = %d\n", a, *p);

    // 通过指针修改 常量 a 的值
    *p = 20;

    // 打印 a 和 *p 的值
    // 此时 通过你指针修改了 *p 的值 , *p 是 20 , 常量 a 仍为 10
    printf("a = %d , *p = %d\n", a, *p);

    return 0;
}

执行结果 :

【C++】C 语言 和 C++ 语言中 const 关键字分析 ( const 关键字左数右指原则 | C 语言中常量的原理和缺陷 | C++ 语言中常量原理 - 符号表存储常量 )-LMLPHP

10-16 19:52