C++ const在常量表达式中的使用
2022-08-025.4k 阅读
C++ const在常量表达式中的使用
const简介
在C++ 中,const
关键字用于指定一个变量的值是不可变的。一旦一个变量被声明为const
,就不能再对其进行修改。这种特性在很多场景下都非常有用,例如定义程序中的常量,防止变量被意外修改,从而提高程序的健壮性和可读性。
const int num = 10;
// num = 20; // 这行代码会导致编译错误,因为num是const,不能被修改
常量表达式的概念
常量表达式是指在编译时就能计算出结果的表达式。常量表达式在很多地方都有应用,比如数组的大小定义、模板参数等。
const int size = 10;
int arr[size]; // 这里size是常量表达式,所以可以用来定义数组大小
template <int N>
class MyArray {
int data[N];
};
MyArray<size> myArr; // size作为常量表达式,可以作为模板参数
const与常量表达式的关系
- 基本类型的const变量与常量表达式
当一个基本类型(如
int
、double
等)的变量被声明为const
,并且在声明时就进行初始化,那么这个变量可以作为常量表达式。
const int a = 5;
const double b = 3.14;
const int sum = a + b; // sum也是常量表达式,因为a和b都是常量表达式
- 指针与const
- 指向常量的指针: 声明一个指向常量的指针,意味着不能通过这个指针来修改所指向的值,但指针本身的值(即所指向的地址)是可以改变的。
const int num1 = 10;
const int *ptr = &num1;
// *ptr = 20; // 这行代码会导致编译错误,因为ptr指向常量,不能通过ptr修改值
int num2 = 20;
ptr = &num2; // 合法,ptr可以指向其他地址
- 常量指针: 常量指针是指指针本身的值不能改变,即始终指向同一个地址,但所指向的值是可以修改的(如果所指向的值不是常量)。
int num3 = 30;
int *const cptr = &num3;
*cptr = 40; // 合法,可以通过cptr修改num3的值
// cptr = &num2; // 这行代码会导致编译错误,因为cptr是常量指针,不能改变其指向
- 指向常量的常量指针: 这种指针既不能改变所指向的地址,也不能通过指针修改所指向的值。
const int num4 = 50;
const int *const ccptr = &num4;
// *ccptr = 60; // 编译错误,不能通过ccptr修改值
// ccptr = &num3; // 编译错误,ccptr不能改变其指向
- 数组与const
- 指向常量数组的指针:
const int arr1[3] = {1, 2, 3};
const int *ptr1 = arr1;
// ptr1[0] = 4; // 编译错误,arr1是常量数组,不能通过ptr1修改其元素
- 常量指针指向数组:
int arr2[3] = {4, 5, 6};
int *const ptr2 = arr2;
ptr2[0] = 7; // 合法,可以通过ptr2修改arr2的元素
// ptr2 = arr1; // 编译错误,ptr2是常量指针,不能改变其指向
- 常量数组:
const int arr3[3] = {7, 8, 9};
// arr3[0] = 10; // 编译错误,arr3是常量数组,不能修改其元素
const在函数中的使用
- 函数参数中的const
- 当函数参数为基本类型时,使用
const
修饰参数通常没有实际意义,因为基本类型是按值传递,函数内部对参数的修改不会影响到外部变量。
- 当函数参数为基本类型时,使用
void func1(int a) {
a = 20; // 这里修改的是a的副本,不影响外部变量
}
void func2(const int a) {
// a = 30; // 编译错误,a是const,不能修改
}
- 当函数参数为指针或引用类型时,使用
const
可以防止函数内部通过指针或引用修改外部变量。
void func3(int *ptr) {
*ptr = 40; // 可以通过ptr修改外部变量
}
void func4(const int *ptr) {
// *ptr = 50; // 编译错误,ptr指向常量,不能通过ptr修改外部变量
}
void func5(int &ref) {
ref = 60; // 可以通过ref修改外部变量
}
void func6(const int &ref) {
// ref = 70; // 编译错误,ref是const引用,不能通过ref修改外部变量
}
- 函数返回值中的const
- 对于按值返回的函数,使用
const
修饰返回值通常没有实际意义,因为返回的是一个临时副本。
- 对于按值返回的函数,使用
const int func7() {
return 80;
}
int result1 = func7();
// result1 = 90; // 这里修改的是result1,与func7返回值的const无关
- 对于返回指针或引用的函数,使用
const
可以防止调用者通过返回的指针或引用修改对象。
class MyClass {
public:
int value;
};
const MyClass& func8() {
static MyClass obj;
return obj;
}
const MyClass* func9() {
static MyClass obj;
return &obj;
}
int main() {
const MyClass &ref = func8();
// ref.value = 100; // 编译错误,ref是const引用,不能通过ref修改obj的值
const MyClass *ptr = func9();
// ptr->value = 110; // 编译错误,ptr指向常量对象,不能通过ptr修改obj的值
return 0;
}
const成员函数
- 定义与特点
在类中,
const
成员函数保证不会修改对象的成员变量(除了mutable
修饰的成员变量)。在函数声明和定义时,都需要在参数列表后加上const
关键字。
class Rectangle {
private:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() const {
// width = 10; // 编译错误,在const成员函数中不能修改非mutable成员变量
return width * height;
}
};
const
对象与const
成员函数const
对象只能调用const
成员函数,因为非const
成员函数可能会修改对象的状态,这与const
对象的不可变性质相矛盾。
int main() {
const Rectangle rect(5, 10);
int area = rect.getArea(); // 合法,getArea是const成员函数
// rect.setWidth(15); // 编译错误,假设存在setWidth非const成员函数,const对象不能调用
return 0;
}
mutable
关键字与const
成员函数mutable
关键字用于修饰类的成员变量,使得即使在const
成员函数中也可以修改该成员变量。
class Counter {
private:
int count;
mutable int accessCount;
public:
Counter() : count(0), accessCount(0) {}
int getCount() const {
accessCount++; // 合法,accessCount是mutable的,在const成员函数中可以修改
return count;
}
};
const与常量表达式在模板中的应用
- 模板参数中的const与常量表达式
模板参数可以是常量表达式,
const
变量如果满足常量表达式的条件,也可以作为模板参数。
template <int N>
class MyContainer {
int data[N];
public:
MyContainer() {
for (int i = 0; i < N; i++) {
data[i] = i;
}
}
};
const int size1 = 5;
MyContainer<size1> container1; // size1是常量表达式,可以作为模板参数
- 模板函数中的const
在模板函数中,
const
的使用规则与普通函数类似,但要注意模板函数的类型推导。
template <typename T>
void printValue(const T &value) {
std::cout << value << std::endl;
}
int main() {
int num = 10;
printValue(num);
const int num2 = 20;
printValue(num2);
return 0;
}
const与常量表达式在编译期计算中的应用
- 编译期常量计算的优势 在编译期进行常量计算可以提高程序的运行效率,因为一些计算结果在编译时就确定了,而不需要在运行时进行重复计算。
constexpr int factorial(int n) {
return (n == 0 || n == 1)? 1 : n * factorial(n - 1);
}
const int result2 = factorial(5); // 在编译期计算5的阶乘
constexpr
与const
的关系constexpr
关键字用于声明常量表达式函数或变量。constexpr
变量一定是const
的,但const
变量不一定是constexpr
的。constexpr
函数要求函数体必须是一个常量表达式,并且在编译期就能计算出结果。
const int num3 = 5; // num3是const变量,但不是constexpr变量
constexpr int num4 = 10; // num4是constexpr变量,也是const变量
constexpr int add(int a, int b) {
return a + b;
}
const int sum1 = add(3, 4); // 在编译期计算3 + 4
const在不同作用域中的表现
- 全局作用域中的const
在全局作用域中声明的
const
变量默认具有内部链接性,即该变量只能在当前文件中访问。如果希望在多个文件中共享一个const
变量,可以使用extern
关键字。
// file1.cpp
extern const int globalConst; // 声明外部的const变量
int main() {
std::cout << globalConst << std::endl;
return 0;
}
// file2.cpp
const int globalConst = 100; // 定义全局const变量
- 局部作用域中的const
在局部作用域(如函数内部)声明的
const
变量只在该局部作用域内有效,其生命周期与该局部作用域相同。
void localFunction() {
const int localVar = 200;
// localVar = 300; // 编译错误,localVar是const,不能修改
std::cout << localVar << std::endl;
}
const与类型转换
const
对象的类型转换const
对象在进行类型转换时需要注意,const_cast
是专门用于去除const
属性的类型转换操作符。但使用const_cast
去除const
属性后修改对象可能会导致未定义行为,除非对象原本就不是const
的。
const int num5 = 10;
int *ptr3 = const_cast<int*>(&num5);
// *ptr3 = 20; // 未定义行为,num5原本是const对象
class MyClass2 {
public:
int value;
};
const MyClass2 obj2;
MyClass2 *ptr4 = const_cast<MyClass2*>(&obj2);
// ptr4->value = 30; // 未定义行为,obj2原本是const对象
- 向
const
类型的转换 可以将非const
对象转换为const
对象,这种转换是安全的。
int num6 = 40;
const int &ref2 = num6; // 合法,将非const变量转换为const引用
const在代码优化中的作用
- 编译器优化
编译器可以对
const
变量和const
表达式进行优化。例如,对于const
变量,编译器可能会将其值直接嵌入到使用它的代码中,而不是通过内存访问。
const int num7 = 50;
int result3 = num7 + 10; // 编译器可能会直接将50 + 10计算出来,而不是先从内存读取num7的值
- 代码可读性与可维护性优化
使用
const
可以提高代码的可读性和可维护性。通过明确标识哪些变量是不可变的,程序员可以更清楚地理解代码的逻辑,减少错误的发生。
void calculateArea(const int width, const int height) {
int area = width * height;
std::cout << "Area: " << area << std::endl;
}
const在面向对象编程中的设计模式应用
- 单例模式中的const
在单例模式中,可以使用
const
来确保单例对象的状态不被意外修改。
class Singleton {
private:
static Singleton *instance;
int data;
Singleton() : data(0) {}
public:
static const Singleton& getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return *instance;
}
int getData() const {
return data;
}
};
Singleton *Singleton::instance = nullptr;
int main() {
const Singleton &singleton = Singleton::getInstance();
int value = singleton.getData();
// singleton.data = 10; // 编译错误,singleton是const对象,不能修改其成员变量
return 0;
}
- 策略模式中的const
在策略模式中,不同的策略类可能有一些不变的属性,可以使用
const
来表示。
class Strategy {
public:
virtual void execute() const = 0;
};
class ConcreteStrategyA : public Strategy {
public:
void execute() const override {
std::cout << "Executing Strategy A" << std::endl;
}
};
class Context {
private:
const Strategy *strategy;
public:
Context(const Strategy *s) : strategy(s) {}
void performAction() const {
strategy->execute();
}
};
总结const在常量表达式中的使用要点
- 基本规则
- 基本类型的
const
变量且初始化时的值为常量表达式,该变量本身也是常量表达式。 - 指针和数组与
const
结合时,要清楚区分指向常量、常量指针、常量数组等不同情况,以及它们在常量表达式中的应用。
- 基本类型的
- 函数相关
- 在函数参数和返回值中合理使用
const
,可以提高函数的安全性和健壮性。 const
成员函数保证不修改对象的非mutable
成员变量,const
对象只能调用const
成员函数。
- 在函数参数和返回值中合理使用
- 模板与编译期计算
const
变量若满足常量表达式条件,可作为模板参数。constexpr
用于声明常量表达式函数和变量,提高编译期计算能力。
- 作用域与类型转换
- 全局作用域
const
变量默认内部链接性,局部作用域const
变量作用域和生命周期局限于局部。const_cast
用于去除const
属性,但需谨慎使用。
- 全局作用域
- 优化与设计模式
const
有助于编译器优化,提高代码可读性和可维护性。在面向对象设计模式中,const
可确保对象状态的稳定性。
通过深入理解和合理运用const
在常量表达式中的各种特性,程序员可以编写出更高效、更健壮、更易维护的C++ 代码。无论是在简单的基础编程,还是复杂的大型项目中,const
的正确使用都是C++ 编程的重要组成部分。