详细理解 C++ const与constexpr及区别

目录

  • 写在前面
  • 一、const
    • 1.常量与符号常量
    • 2.const用法
    • 3.const 和 #define 区别
      • (1)用法和类型安全
      • (2)作用域
      • (3)存储类别
      • (4)调试和可读性
      • (5)推荐的用法
  • 二、constexpr
  • 三、const与constexpr区别

写在前面

供学习使用,如有问题,欢迎指出

一、const

1.常量与符号常量

在了解const之前,先了解一下常量与符号常量。
常量包括整型常量、实型常量、字符常量、字符串常量、布尔常量。
整形常量:
如:0、494、053(八进制)、0XA3(十六进制)、433L(至少是long)、8484U(unsigned类型)等。
实型常量:
如:12.4、-33.5、0.342E+2、-32.33E-3、.213E-1、1.E-4、12.3F等。
字符常量:
如:‘a’、‘\x61’、‘!’、'\n’等。
字符串常量:
如:“boy”,“I am a “good” boy”。
布尔常量:
如:false、true。

前面说的都是直接用文字表示常量,也可以为常量命名,这也就是符号常量,其中const用于声明常量。
符号常量在声明时必须初始化!!!

const 数据类型说明符 常量名 = 常量值;或
数据类型说明符 const 常量名 = 常量值;如:

const float PI = 3.1415;

目的:
1)与直接使用文字量相比,给常量起一个有意义的名字有利于提高程序的可读性;
2)当多处用到同一个文字常量时,使用符号常量,方便统一修改。

2.const用法

const 用于声明常量,即一旦赋值后就不能改变的量。在 C++ 中,const 可以用于修饰变量指针函数参数函数返回值以及类的成员函数
1)修饰变量:声明一个常量,其值在初始化后不能再被修改。

const int a = 10; // a 是一个常量,其值不能被修改

2) 修饰指针:可以修饰指针本身或者指针所指向的内容。

const int *p = &a; // 指针 p 可以改变,但 *p(即指针指向的内容)不能改变
int *const q = &a; // 指针 q 不能改变,但 *q(即指针指向的内容)可以改变
const int *const r = &a; // 指针 r 和 *r 都不能改变

3) 修饰函数参数:保证函数体内不会修改该参数的值。

void func(const int b) {
    // ...
}

4)修饰函数返回值:如果函数返回一个值或者对象,加 const 可以防止返回值被意外修改。

const int func() {
    return 42;
}

5)修饰类的成员函数:表明该函数不会修改类的任何成员变量(除了被声明为 mutable 的成员)。这通常用于提高代码的可读性和可维护性。

3.const 和 #define 区别

在 C++ 中,const 和 #define 都用于定义常量,但它们在用法类型安全作用域存储类别等方面有着显著的区别。

(1)用法和类型安全

  • const
    • const 是一个关键字,用于声明一个常量。它必须指定类型,并且遵循 C++ 的作用域规则。
    • const 常量有类型,因此是类型安全的。编译器会检查是否对其进行了合法的操作。
    • const 常量可以有复杂的类型和类类型。
const int a = 10; // 正确,int 类型常量
const std::string s = "hello"; // 正确,std::string 类型常量
  • #define
    • #define 是预处理器指令,用于定义宏。它不指定类型,只是进行简单的文本替换。
    • #define 宏没有类型,因此不是类型安全的。编译器在预处理阶段进行宏替换,不会检查类型错误。
    • #define 通常用于定义简单的常量和简单的函数宏。
#define PI 3.14159 // 正确,但无类型
#define MAX(a, b) ((a) > (b) ? (a) : (b)) // 函数宏

(2)作用域

  • const
    • const 常量的作用域遵循 C++ 的规则,可以是局部作用域、全局作用域或类作用域。
    • const 常量可以使用 static 修饰符来定义具有文件作用域的常量。
const int globalConst = 10; // 全局作用域
void func() {
    const int localConst = 20; // 局部作用域
}
  • #define
    • #define 宏的作用域从定义它的地方开始,直到文件结束或遇到 #undef 指令。
    • 宏没有块作用域或局部作用域的概念。

(3)存储类别

  • const
    • const 常量可以有不同的存储类别(如 staticextern 等),这决定了它们的链接性和生命周期。
static const int staticConst = 30; // 具有文件作用域的常量
  • #define
    • #define 宏没有存储类别的概念。它们只是简单的文本替换,不占用存储空间。

(4)调试和可读性

  • const

    • const 常量在调试时更容易跟踪,因为它们有明确的名称和类型。
    • 编译器可以优化 const 常量的使用。
  • #define

    • 由于 #define 宏只是文本替换,调试时可能更难以跟踪其使用。
    • 宏的过度使用可能导致代码难以阅读和维护。

(5)推荐的用法

在 C++ 中,通常推荐使用 const 而不是 #define 来定义常量,因为 const 提供了类型安全和更好的作用域控制。然而,在某些情况下,例如定义复杂的宏或条件编译时,仍然需要使用 #define。但应尽量避免在可以使用 const 的地方使用 #define

二、constexpr

constexpr 是 C++11 引入的一个新关键字,用于定义常量表达式(const expression)。 常量表达式是一类值不能发生改变的表达式,其值在编译阶段确定,而const 变量的值是可以在运行时确定的,因此 constexpr 可以用于优化性能和内存使用。上面介绍的常量就是常量表达式,由常量表达式初始化的const对象也是常量表达式。

const int max_size = 100;       // max_size 是常量表达式
const int limit = max_size + 1; // limit 是常量表达式
int student_size = 30;          // student_size 不是常量表达式
const int size = get_size();    // size 不是常量表达式

max_size 就是之前介绍的常量,limit 是const int 类型,且初始值是一个常量,两者都是常量表达式;
student_size 尽管初始值是一个常量,但不具有const属性,故不是常量表达式;
size 是const类型,但是get_size()的具体值需要到运行时才能确定,所以也不是常量表达式。

在实际编程中,很难确定初始值是否是常量,为此引入constexpr关键字,以便由编译器来验证变量值是否是一个常量表达式。constexpr修饰的变量暗含了const属性,并且必须由常量表达式初始化。
如:

constexpr int size = get_size();

尽管不能用普通函数初始化constexpr变量,但当get_size()是constexpr函数时,上述声明能编译通过,这样就能确保size是常量表达式了。

1)修饰变量:声明一个编译时常量,其值在编译时必须能确定。

constexpr int x = 2 * 3; // x 是一个编译时常量,其值为 6

2)修饰函数:声明一个函数在编译时就能确定返回值,这要求函数体内只能有简单的计算,不能包含任何运行时才能确定的操作(如输入输出、动态内存分配等)。这样的函数通常用于初始化 constexpr 变量或者作为模板参数。

constexpr int square(int n) {
    return n * n;
}

三、const与constexpr区别

1) 计算时机const 变量的值可以在运行时确定,而 constexpr 变量的值必须在编译时就能确定。

2) 用途const 主要用于声明不可变的变量或保证函数不会修改其参数或返回值。而 constexpr 主要用于优化性能和内存使用,通过允许在编译时计算常量表达式的值。

3) 函数修饰:虽然 const 可以修饰成员函数以保证其不会修改类的状态,但 constexpr 还可以修饰函数本身,要求函数在编译时就能计算出结果。

4) 灵活性const 更加灵活,可以用于各种场景,包括指针、函数参数、返回值等。而 constexpr 则主要用于常量表达式和函数的优化。

5) 版本兼容性const 是 C++ 早期版本就有的关键字,而 constexpr 是 C++11 引入的新特性,因此在使用时需要考虑编译器的兼容性。

你可能感兴趣的:(c++,开发语言)