const 那些事

const 那些事

强烈推荐看:《C++编程思想-8章常量》。

1. const 含义

const=constant,
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。
举个例子,就是 const int test_a = 12, 你是没法给 test_a 赋值的。

const 关键字指定变量的值是常量,并通知编译器防止程序员对其进行修改。

经典一问:const 变量是否可以被修改?

答1:可以。const 修饰的始终是变量,如果const 变量位于栈区,那么是可以通过取地址修改。

但是,如果const 变量位于全局,那么是不可以修改的。

1
2
3
4
5
6
7
8
9
10
// const 变量可以修改的示例
const int kGlobalValue = 312;
int main() {
const int const_value = 100;
int * ptr = (int *)&const_value; // 通过取const 变量的地址,就可以修改const变量的值。
*ptr = 200;

int* ptr_globale = (int*)&kGlobalValue;
*ptr_globale = 400; // 运行的时候会报错哦!!!
}

答2:Linux上 全局常量是放在 .rdata段的,该段只读,所以尝试修改就会报错。
为什么C++无法修改全局静态常量?(Linux平台示例-知乎)

const作用

  • 定义常量

    const int a=100;

  • 类型检查

    const常量有类型,编译器会进行安全检查;#define宏定义只是字符串替换,不进行安全检查;

    #define 也是有类型的,详细见:讨论#define和const的类型问题

  1. const 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。
  2. 其他情况下它只是一个 const 限定的变量,不要将与常量混淆。
  • 防止修改,起保护作用,增加程序健壮性

    修饰函数参数等,如果参数被修改会保存。

    1
    2
    3
    void f(const int i){
    i++; // error!
    }
  • 节省空间,避免不必要的内存分配

    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数。

    const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

理解常量和常量表达式

理解常量和常量表达式——掘金blog

const的存储位置探究(Todo)

  1. const修饰的量不是常量,仅仅是个只读量。在编译的时候全部替换赋值的const常量,类似#define替换;
    在运行的时候该const变量可通过内存进行修改。

    总结来说就是,const修饰的数据,编译的时候会进行替换,这样被赋值的数据运行的时候数据是编译期定好的。
    即使你修改了 const 修饰的变量,被赋值的数据不会因为运行时的修改而变化。
    但是const 能修改的前提必须是可读可写的,比如栈上的变量,全局的就不行。

  2. CSDN关于const存储位置的讨论
    CSDN总结的blog, 我觉得写的还可以

const对象的范围

默认为文件局部变量。
注意: 非const变量默认为extern。要使const变量能够在其他文件中访问,必须在文件中显式地指定它为extern。

非const修饰的变量在不同文件的访问

1
2
3
4
5
6
7
8
9
// file1.cpp
int ext = 132; // 默认都可以访问

// file2.cpp
// 需要指定ext为extern变量,否则找不到。
extern int ext;
int main() {
int test_ext = ext;
}

const 修饰的变量在不同文件的访问
const 修饰的想外部访问,定义的时候就得加 extern;且需要初始化。

1
2
3
4
5
6
7
8
9
10
// file1.cpp
const int ext_const_def = 132; // 默认当前文件可访问
extern const int ext_const_extern = 312; // 想外部访问,需要定义的时候指定为 extern

// file2.cpp
// 需要指定ext为extern变量,否则找不到。
extern const int ext_const_extern;
int main() {
int test_ext = ext_const_extern;
}

extern const 未初始化会报链接错误:无法解析的外部符号;
avatar

const用法

const修饰普通类型的变量

1
2
3
4
5
6
7
8
const int b = 10; 
b = 0; // error: assignment of read-only variable ‘b’

// 等价于
int const b = 10;

const string s = "helloworld";
const int i,j=0 // error: uninitialized const ‘i’

作为常量,直接赋值修改是不行的哦!
作为常量,必须初始化哦!

C2734 “const_value”: 如果不是外部的,则必须初始化常量对象。

const与指针

1
2
3
4
const char * a; // 指向常量的指针,a 指针类型是 const char 的,即指向的内容不能被修改。
char const * a; // 同上
char * const a; // 指针本身是常量即 a 这个地址是常量,不能被修改, 也称常指针、const指针。
const char * const a; // 指向const对象的const指针。

常量指针: 指向的对象是常量。不能修改指针所指向的内容。

const 位于 * 的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

1
2
3
4
5
6
7
8
9
10
    // 编译环境 msvc2017
int main() {
int const_test_a = 123;
const int* const_ptr_a; // 指向常量的指针 不初始化是OK的
const_ptr_a = &const_test_a; // 赋值给常量指针的时候,可以是 const,也可以是非const。

// 可以修改 const_test_a 的值,只是不能通过 *const_ptr_a 来修改
int* ptr_test_a = &const_test_a;
*ptr_test_a = 312; // OK的
}

常量指针的注意点:

  1. const_ptr_a 指针不用赋初值,但不能通过 const_ptr_a 修改所指向的对象的值;
  2. 不能使用void指针保存const对象的地址,必须使用const void类型的指针保存const对象的地址;
  3. 可以把非const对象的地址赋值给 const 对象的指针;但不能通过 const 定义的那个指针修改对象值。

指针常量: 指针本身是常量。不能修改指针的地址。

const 位于 * 的右侧,const就是修饰指针本身,即指针本身是常量。

指针常量的注意点

  1. const指针必须初始化,且const指针的值不能修改。(就是说const指针存储的地址不能动哦)
  2. 1
    2
    3
    4
    5
    6
    7
    8
        // 编译环境 msvc2017
    int main() {
    int num=0;
    int * const ptr=# //const指针必须初始化!且const指针的值不能修改
    int * t = # // 通过取地址得到 num 的地址,就可以修改 num的值。
    *t = 1;
    // 总结一下:*ptr,*t 其实都是保存的 num的地址,只不过 *ptr是const修饰的,不能直接改num的值。
    }

指向常量的常指针

1
2
3
4
5
    // 编译环境 msvc2017
int main() {
const int p = 3;
const int * const ptr = &p; // ptr指向的是常量,且ptr本身是一个const指针,保存的地址不能动。
}

知乎关于const指针讲的比较容易懂的blog
通俗易懂理解什么是常量指针和指针常量——知乎

const应用到函数中

  • const修饰函数返回值
      1. const int —— 本身无意义,函数返回本身就是要赋值给其他变量的。
        1
        2
        // 编译环境 msvc2017
        const int func1();
      1. const int* —— 指针指向的内容不变
        1
        2
        // 编译环境 msvc2017
        const int* func2();
      1. int* const —— 指针本身即指针的地址不能变
        1
        2
        // 编译环境 msvc2017
        int *const func2();
  • const修饰函数参数
      1. 传过来的参数以及指针本身在函数内不可变,没有意义
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // 编译环境 msvc2017
        void func(const int var); // 传递过来的参数不可变
        void func(int *const var); // 指针本身不可变

        void TestConstPtr(int *const var) {
        int test = *var;
        }

        int main() {
        int num = 3211;
        int * const ptr_num = # //const指针必须初始化!且const指针的值不能修改
        TestConstPtr(ptr_num);
        }
        // 确认 结果:
        // &ptr_num:
        // &var:
        // 上面2个地址是不同的,所以 var 是函数参数的临时变量。

        值传递方式, 形参本身会产生临时变量,参数无需保护,所以加不加const没有意义。
        const int var 和 int *const var 都是值传递。
        理解下 int *const var 为什么没有意义?
        答1:const修饰的是指针本身,所以此处只是修饰 var 本身不变;
        答2:另外,指针传递的时候,var 指向的数据依然可能被修改,所以只限制 var 本身没有意义。

      1. 参数指针指向的内容为常量不可变
        1
        void StringCopy(char *dst, const char *src); // 限定 char* 可以保证指向的内容不被修改

        试图修改 src 时,编译会报错。

      1. 参数为引用,为了增加效率同时防止修改
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        void funcA(const A a); // 值传递,效率低,对象的构造、复制、析构都会消耗时间。

        void funcA(const A& a); // 使用引用——变量的别名,避免值传递的开销。

        void funcA(const A& a); // const限定对象的引用
        void funcA(const std::string& value); // 具体例子:std::string

        // 一个疑问: 是否所有的都用引用更好?
        // 答:没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
        void funcA(const int& a);

        1. 引用只是参数的别名而已,不需要产生临 时对象。 2. const 限定的引用,可以避免引用被修改。

    • const 引用传递的使用场景

      • 非内部数据做输入参数:建议使用 const 引用,提高效率;
      • 内部数据类型做输入参数:不建议用,比如 int、double 等类型;

const类中的用法

  • const成员函数:只要不修改数据成员,就应该声明为const;

    const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
    const 成员函数调用非const成员函数的不行的哦!

    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
    // const成员函数调用 非const成员函数示例,编译报错
    // E1086 对象含有与成员 函数 "Apple::FuncB" 不兼容的类型限定符
    // C2662: “int Apple::FuncB(void)”: 不能将“this”指针从“const Apple”转换为“Apple &”
    /*
    解释下 C2662: “int Apple::FuncB(void)”: 不能将“this”指针从“const Apple”转换为“Apple &”

    FunA中调用FuncB的时候,要加入this。
    this->FuncB();

    const Apple* this->FuncB(); // FuncB() 本身是非const的,所以报错了。
    */
    class Apple {
    public:
    Apple(int i) : apple_number(i){
    app_id = i;
    }

    int FuncA() const {
    int rest = 123;
    FuncB(); // error, 不能将“this”指针从“const Apple”转换为“Apple &”
    return rest;
    }

    int FuncB() {
    return 123;
    }

    private:
    int people[100];
    const int apple_number;
    const int app_code = 312;
    int app_id;
    };

    理解下为什么 const 对象只能访问 const 成员函数?为什么const成员函数中调用非const成员就会报错?

    答:理解下 this 指针就能明白为什么了。 this 指针是成员函数默认参数,也是有类型的,const 成员类型就是const的。

  • const成员变量

    • 必须通过初始化列表进行初始化 or 定义的时候初始化。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      class Apple {
      public:
      Apple(int i) {
      apple_number = i; // error,编译错误,『表达式必须是可以修改的左值』。
      apple_number = 123; // 如上一样。
      }

      Apple(int i) : apple_number(i) { // 放到初始化列表是OK的
      }

      Apple(int i) : apple_number(100) { // 不使用构造参数也是OK的
      }

      int FuncA(int posx, int posy) {
      return posx + posy;
      }
      private:
      int people[100];
      const int apple_number;

      const int app_code = 312; // const int 这么初始化也是OK的。

      int appid;
      };

查看C++类的内存结构方法

  • 查看所有类:/d1 reportAllClassLayout
  • 查看单个类:/d1 reportSingleClassLayoutXXX(XXX为类名)

    /d1 参数统一放到 C/C++ → 命令行:其他选项

    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
    // 一个简单的类内存布局,没有虚函数等
    /*
    * 编译的时候会输出布局
    1>class Apple size(412):
    1> +---
    1> 0 | people
    1>400 | apple_number
    1>404 | app_code
    1>408 | app_id
    1> +---
    */
    // 如上,静态成员归属于所有类,所以布局上看不到。
    class Apple {
    public:
    Apple(int i) : apple_number(i){
    app_id = i;
    }

    int FuncA() const {
    int rest = 123;
    FuncB();
    return rest;
    }

    int FuncB() {
    return 123;
    }
    private:
    int people[100];
    const int apple_number;
    const int app_code = 312;
    int app_id;

    static int s_test_a; // 类的static 类内声明,不能初始化哦!
    };
    int Apple::s_test_a = 212; // static 必须类外初始化!!!
    查看C++类的内存结构方法_例子写的很好的blog

理解this指针

先说编译器怎么做的?

  1. this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。

  2. this 形参是隐式的,并不出现在代码中,而是编译阶段由编译器默默地将它添加到参数列表中。且只有非静态成员函数哦!

  3. 成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息。
    所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。

  4. 示例:int FuncA(int posx, int posy); 编译时可能变成了 int FuncA(Apple* this, int posx, int posy) 【不一定是这个结果,只用于示例说明】

总结一下this是什么?

答: this是编译器加给c++类的非static成员函数的额外参数,用于管理类的成员函数和变量。作为参数也有类型限定,比如使用const。

作为参数,this指针也是有类型的。

this的类型取决于成员函数的声明情况。
const 方法时,指针类型就是 const Apple, 非const 就是 Apple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int FuncA(int posx) { 
appid = posx;
return 123;
}
int FuncB(int posx) const {
appid = posx;
FuncA();
return 123;
}

// 经过编译之后,加上 this 指针的结果(个人理解,如有错误请指正)
int FuncA(Apple*this, int posx) {
this->appid = posx;
return 123;
}
int FuncB(const Apple*this, int posx) const {
this->appid = posx;
this->FuncA(); // erro。这儿就相当于是 const 对象 调用 非const 接口
return 123;
}

C++中的this指针_geeksforgeeks
C语言中文对于this指针的讲解_最后的this是什么说的还可以

const和#define区别

  • const

    修饰的是一个真正的变量,对于变量,你当然可以做任何事情。
    因为常量值在编译时已经被替换和折叠,所以不存在性能问题。

  • #define:

    只是字符串替换,
    stackoverflow_difference-between-define-and-const

参考链接

  1. 主要是参考Github上的c++那些事:https://github.com/Light-City/CPlusPlusThings
  2. MSDN对于const的说明:https://learn.microsoft.com/zh-cn/cpp/cpp/const-cpp?view=msvc-170
  3. 极客笔记c++ const的使用:https://deepinout.com/cpp-tutorial/cpp-tutorial-const-usage.html