关于C++中宏定义、const与constexpr的区别

2022/07/28

在C++中#defineconstconstexpr三者都可以用于定义常量,但是三者并不是等效的,它们各有各的优点,也有自己的不足,需要在合适的场合选用合适的表示。

1. #define

#define是编译预处理指令,在编译阶段直接进行替换,比如下面这部分代码

#define a 3
int main(){
    int b = a + 3;
}

在编译预处理阶段就会被替换从

int main(){
    int b = 3 + 3;
}

2. const

在C++中const可以用来修饰不同类型变量还可用用来修饰函数的参数、指针变量和类的成员变量。

2.1 常量与常量指针

const修饰普通类型变量表示该变量为常量,如果在后面直接对常量进行修改,在编译阶段就会报错,比如下面这段代码

const int a = 3;

int main(){
   a = 4;
   return 0;
}

定义一个指针指向常量,这个指针被称为常量指针,如果用常量指针间接对常量进行修改,比如下面这部分代码,编译可能会产生一些警告,还是可以通过的,但是在运行的时候会产生segmentation fault,表示修改了不能修改的内存地址

const a = 3;

int main(){
   int *b;
   b = &a;
   *b = 4;
   return 0;
}

还有一点值得注意的是const修饰的全局变量是存放内存的.text区也是常说的Code区,该区域的数据为只读的,而普通的未初始化的全局变量存放在.data区,初始化的全局变量存放在.bass区。

image-20220723095219062

2.2 指针常量

const修饰的指针变量就被称为指针常量,指针常量与普通常量一样,是不可以被赋值,不过需要注意的是虽然指针常量本身不可以被赋值但是指针所指向的如果是变量,这个变量是可以被修改的,比如下面这段代码

int main(){
    int a = 3;
    int *const p = &a;
    *p = 4;         // correct
    int b = 4;
    *p = &b;        // error
    return 0;
}

在申明指针常量的时候需要注意*应该被放在const的左边,如果是下面这样就是常量指针了

const int *p = 3;

2.2 const修饰函数参数

const修饰函数传入参数一般用于传入指针或者引用时,这样可以保证传入的参数不被函数体修改。如果传入的是形参的话是直接将原来的参数复制了一份,即使在函数中修改了也没啥问题所以一般不用const来修饰。

2.3 const修饰类成员函数

const修饰类的成员函数表示在这个成员函数内this指针变成了常量指针,也就是说这个成员函数无法改变该类的成员变量。

class Demo{
    int a = 0;
public:
    void foo() const{
        a = 3;      // 会发生编译错误
    }
};

3. constexpr

const表示一个变量为常量,常量在完成定义之后就不可以修改了

int main(){
    int a = 3;
    const int b = a + 3;
}

constexpr具有与const相同的性质,但是它更强调是在编译的时候常量的值就已经确定,比如把上面的const换成constexpr就会发生编译错误

int main(){
    int a = 3;
    constexpr int b = a + 3;
}

因为这里b的值是后面赋予的而不是编译的时候就已经确立的。

4. 总结

#defineconst的区别:

  1. #define是在编译预处理期间进行处理,是直接替换,无法进行类型检查,而const定义的常量要求确定类型,在编译过程中会进行类型检查,所以const#define更加安全。
  2. #define是将值直接替换,多次引用会重复占用内存,而const多次引用不会重复占用内存
  3. #define定义的宏常量不可以调试,而const定义的常量可以调试

constconstexpr

  1. constexpr强调在编译阶段就确定对应常量的值,const只是要求在确定常量值之后不能进行修改