目录
写在前面
供学习使用,如有问题,欢迎指出
一、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
常量可以有不同的存储类别(如static
,extern
等),这决定了它们的链接性和生命周期。
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 引入的新特性,因此在使用时需要考虑编译器的兼容性。