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






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



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


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


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

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

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

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

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

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

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


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


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


    代码示例 :

    // 导入标准 io 流头文件
    // 其中定义了 std 命名空间
    //#include 
    // 导入 std 命名空间
    //using namespace std;
    
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3、const 关键字使用场景


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

    定义结构体 :

    struct Student
    {
        char name[64];
        int age;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    下面是错误示范 :

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

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

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

    在这里插入图片描述

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

    下面是错误示范 :

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

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

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

    在这里插入图片描述


    上述完整代码示例 :

    // 导入标准 io 流头文件
    // 其中定义了 std 命名空间
    //#include 
    // 导入 std 命名空间
    //using namespace std;
    
    #include 
    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56




    二、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 
    
    int main() {
    
        // 定义常量
        const int a = 10;
    
        // 下面的代码会报错 , 貌似 a 是常量
        a = 20;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编译时的报错信息 :

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述


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


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

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

    代码示例 :

    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    执行结果 :

    C:\Users\octop\Desktop>gcc hello.c
    
    C:\Users\octop\Desktop>a.exe
    a = 20
    
    C:\Users\octop\Desktop>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述


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

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

    在这里插入图片描述

    出现上述问题 , 是因为 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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


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


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

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

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

    代码示例 :

    #include 
    
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    执行结果 :

    在这里插入图片描述

  • 相关阅读:
    软件测试零基础入门好学吗?
    让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示
    泛型的理解和好处
    【@网络工程师:用好这6款工具,让你的工作效率大翻倍!】
    MATLAB算法实战应用案例精讲-【智能优化算法】灰狼算法(GWO) (附MATLAB和Python源码)
    【苏大c++第二次考试模拟】
    docker push image harbor http 镜像
    优化接口性能
    基于ZLMediaKit的GB28181视频平台demo
    Vue环境安装
  • 原文地址:https://blog.csdn.net/han1202012/article/details/132386818