文章

C++ const

C++ const

常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。

const作用

  1. 可以定义常量

    1
    
     const int a = 100;
    
  2. 类型检查

    const常量与#define宏定义常量的区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只能简单的字符串替换,不能安全检查

  3. 防止修改,起保护作用,增加程序健壮性

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

    const定义常量从汇编的角度看,只是给出了对应的内存地址,而不是像#define一样给出的立即数,所以const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝

const对象默认为文件局部变量

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

未被const修饰的变量不需要extern显式声明!而const常量需要显式声明extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。

定义常量

1
2
3
4
5
6
const int b = 10;
b = 9;	// error:assignment of read-only variable 'b'

const string s = "helloworld";

const int i, j = 0;		// error:uninitialized const 'i'

指针与const

指针与const主要有四种情况:

1
2
3
4
const char * a;			// a是一个指针,指向const char
char const * a;			// a是一个指针,指向const char
char * const a;			// a是一个const指针,指向char
const char * const a;	// a是一个const指针,指向const char

实际行为差异:

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
// char 类型

// 1. 指向常量的指针
const char * p1 = "hello";
char str[] = "world";
p1 = str;        // ✓ 可以改变指针指向
// *p1 = 'H';    // ✗ 不能修改指向的内容

// 2. 常指针
char * const p2 = str;
*p2 = 'W';       // ✓ 可以修改指向的内容
// p2 = "new";   // ✗ 不能改变指针指向

// 3. 指向常量的常指针
const char * const p3 = "test";
// p3 = str;     // ✗ 不能改变指针指向
// *p3 = 'T';    // ✗ 不能修改指向的内容


// int 类型

int a = 10, b = 20, c = 30;

// 1. 指向const int的指针
const int *p1 = &a;
int const *p1_same = &a;	// 与上面等价

p1 = &b;		// 合法
// *p1 = c;	// 非法


// 2. const指针指向int
int *const p3 = &a;

*p3 = c;		// 合法
// p3 = &b;		// 非法


// 3. const指针指向const int
const int * const p4 = &a;

// &p4 = c;	// 非法
// p4 = &a;		// 非法

助记口诀:

  • const*左边:指向的内容不能改,指针本身可以修改
  • const*右边:指针本身不能改,指针指向的内容可以改
  • const在两边:都不能改

const*左边 地址上的内容只读,但地址可变 const*右边 地址固定,但内容可修改

指向常量的指针

1
2
const int *ptr;
*ptr = 10;	// error!

ptr是一个指向int类型const对象的指针,const定义的是int类型,也就是ptr所指向的对象类型而不是ptr本身,所以ptr可以不用赋初始值。但是不能通过ptr去修改所指对象的值

  • **不能使用void*指针保存const对象的地址,必须使用const void*类型的指针来保存const对象的地址
  • 允许把非cosnt对象的地址赋值给指向const对象的指针,这样只能通过指针对对象进行读取而不能修改,保护了原来的变量,常用于函数中不修改原始数据

常指针

const指针必须进行初始化,且const指针的值不能修改

常指针是指类型 * const这种形式,即指针本身是常量的指针

常见应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
private:
    int * const data_ptr;  // 常指针成员变量

public:
    // 构造函数中初始化常指针
    MyClass(int* ptr) : data_ptr(ptr) {
        // data_ptr在对象生命周期内始终指向同一地址
    }
    
    void setValue(int val) {
        *data_ptr = val;   // 可以修改指向的值
    }
    
    // data_ptr不能重新指向其他地址
};

指向常量的常指针

1
2
const int p = 3;
const int * const ptr = &p;

ptr是一个const指针,然后指向了一个int类型的cosnt对象

函数与const

const修饰函数返回值

  1. const int func1();

    这个本身无意义,因为参数返回本身就是赋值给其他的变量

  2. const int * func2();

    指针指向的内容不变

  3. int * const func2();

    指针本身内容不可变

const修饰函数参数

  1. 传递过来的参数及指针本身在函数内不可变,无意义

    1
    2
    
     void func(const int var);	// 传递过来的参数不可变
     void func(int *const var);	// 指针本身不可变
    

    表明参数在函数体内不能被修改,单此处没有任何意义,var本身就是形参,在函数内部会改变,包括传入的形参是指针也是一样

  2. 参数指针所指向内容为常量不可变

    1
    
     void StringCopy(char *dst, const char *src);
    

    其中str是输入参数,dst是输出参数。给src加长const修饰后,如果函数内的语句试图改动src的内容,编译器会报错

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     // 指向const对象的指针参数
     void print_array(const int* arr, int size) {
         for(int i = 0; i < size; i++) {
             cout << arr[i] << " ";  // ✓ 可以读取
             // arr[i] = 0;          // ✗ 不能修改数组元素
         }
     }
    
     // const指针参数(较少使用)
     void func2(int* const ptr) {
         *ptr = 100;    // ✓ 可以修改指向的值
         // ptr = NULL; // ✗ 不能修改指针本身
     }
    
  3. 参数为引用,为了增加效率同时防止修改

    1
    
     void func(const A &a)
    

    A是一个非内部数据类型,如果使用void func(A a)的声明函数方式效率慢(函数体内将产生A类型的临时对象用于复制参数a),为了提高效率可以将函数声明改为void func(A &a),因为引用传递仅借用以下参数的别名,不需要产生临时对象

    此外,为了解决对原始a对象可能被修改的问题,声明时加上cosnt修饰

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    
     void process_data(const string& str) {  // 避免拷贝,提高效率
         cout << str.length() << endl;  // ✓ 可以调用const成员函数
         // str += "test";              // ✗ 不能修改
     }
    
     void modify_data(string& str) {    // 非const引用,可以修改
         str += " modified";           // ✓ 可以修改
     }
    

    对于非内部数据类型的输入参数,应该将“值传递”的方式改为const引用传递,目的提高效率 对于内部数据类型的输入参数,应该继续使用“值传递”的方式,效率一样但函数的可理解性强

    内部数据类型:char,short,int,long,long long,char,float,double,bool,void

类与const

在一个类中,任何不会修改数据成员的函数都应该声明为const类型

使用const关键字进行说明的成员函数,被成为常成员函数

常成员函数特点

  • 不能修改成员变量
  • 不能调用非常成员函数
  • 常对象只能调用常成员函数
本文由作者按照 CC BY 4.0 进行授权