C++中const关键字的作用

119 阅读9分钟

C++中const关键字的作用

公众号:阿Q技术站

1. const 用于常量

最常见的 const 用法是声明常量变量,它使得变量在初始化后不可再被修改。

1.1 基本常量声明
const int a = 10;  // 常量变量,初始化后不可修改
  • a 被声明为常量,因此在后续代码中不能修改它的值。
  • 如果尝试 a = 20;,编译器会报错。
1.2 常量指针

const 可以用于指针类型,限制指针本身或指针指向的内容不能被修改。具体来说,const 可以出现在两种位置:指针变量本身或指针指向的对象。

1.2.1 指向常量的指针(指向内容是常量)
const int* ptr = &a;  // ptr 是指向常量整数的指针,指针指向的值不能修改
  • 这里,ptr 是指向 const int 类型的指针,即指针指向的内容不能修改,但指针本身是可以指向其他地址的。
  • 你可以修改 ptr 本身指向其他地址,但不能通过 ptr 修改它指向的值。
1.2.2 常量指针(指针本身是常量)
int* const ptr = &a;  // ptr 是常量指针,指针本身不可修改,但可以修改它指向的值
  • 这里,ptr 是一个常量指针,即指针本身的地址不可更改,但指针指向的内容是可以修改的。
  • 你不能让 ptr 指向其他地址,但可以通过 ptr 修改它指向的值。
1.2.3 常量指针常量(指向常量的常量指针)
const int* const ptr = &a;  // ptr 是常量指针,指向的内容也是常量

这里,ptr 是指向常量的常量指针,既不能改变指针指向的地址,也不能通过 ptr 修改它指向的值。

2. const 用于类成员函数

在类成员函数中,const 关键字常常用于标记不会修改类状态的函数。

2.1 常成员函数

常成员函数的声明中,const 位于函数声明的末尾,表示该函数不会修改类的成员变量。

class MyClass {
public:
    int getValue() const { return value; }  // 该函数不会修改类的成员变量
private:
    int value;
};
  • getValue() 被声明为 const 成员函数,表示它不会修改任何成员变量。
  • 这样做的好处是可以让你在 const 对象上调用这些成员函数,即使该对象是常量,仍然能够调用不修改状态的函数。
2.2 this 指针的 const

const 成员函数中,this 指针是 const 的,意味着不能通过 this 指针修改成员变量。

class MyClass {
public:
    void show() const {
        // this->value = 10;  // 错误:不能通过 const 成员函数修改成员变量
    }
private:
    int value;
};

show() 函数中,this 指针指向的是一个常量对象,因此你不能修改它的成员变量。

3. const 用于函数参数

使用 const 关键字声明函数参数,表示函数内部不能修改该参数。常用于传递引用或指针时,保证函数不会修改传入的参数。

3.1 常量引用参数
void printValue(const int& value) {
    // value = 10;  // 错误:不能修改常量引用的值
    std::cout << value << std::endl;
}
  • const int& value 表示 value 是一个常量引用,它指向一个整数值,但不能修改它的值。
  • 常量引用传递的好处是避免了不必要的拷贝,同时保证了参数在函数内部不会被修改。
3.2 常量指针参数
void printValue(const int* ptr) {
    // *ptr = 10;  // 错误:不能修改通过 const 指针访问的值
    std::cout << *ptr << std::endl;
}

const int* ptr 表示指针 ptr 指向一个常量整数,不能通过指针修改其指向的内容。

3.3 作为参数的常量指针常量
void printValue(const int* const ptr) {
    // *ptr = 10;  // 错误:不能修改指针指向的值
    // ptr = nullptr;  // 错误:不能修改常量指针本身
    std::cout << *ptr << std::endl;
}

const int* const ptr 是一个常量指针常量,既不能修改指针的地址,也不能修改指针指向的内容。

4. const 用于常量数组和字符串

常量数组和字符串也可以通过 const 来限制修改。

4.1 常量数组
const int arr[] = {1, 2, 3};
arr[0] = 10;  // 错误:不能修改常量数组元素

arr 是一个常量数组,因此无法修改数组的元素。

4.2 常量字符串
const char* str = "Hello";
str[0] = 'h';  // 错误:不能修改字符串常量

字符串常量本身是不可修改的,因此尝试修改字符串中的内容会导致错误。

5. const 与类型推导

在 C++11 引入的 auto 关键字中,const 也可以与 auto 一起使用来推导常量类型。

5.1 constauto
auto value = 10;        // 推导为 int
const auto cvalue = 10; // 推导为 const int

如果使用 const 修饰 auto,则编译器会推导出常量类型。


6. const 与常量表达式

C++11 引入了 constexpr,它与 const 具有一些相似性,但也有区别。constexpr 是用于声明常量表达式,意味着在编译时可以求值的常量。

6.1 constexprconst 的区别
  • const 声明的是常量,值可以在运行时确定,但声明后不能更改。
  • constexpr 声明的是常量表达式,要求在编译时就能够求值。
const int x = 10;       // x 的值可以在运行时赋值
constexpr int y = 20;   // y 的值必须在编译时已知
  • const 变量不一定是常量表达式,只是值在程序中不可修改。
  • constexpr 强制要求值在编译时已知,常用于数组大小或模板参数等需要编译时求值的场景。
6.2 在函数中使用 constexpr
constexpr int square(int x) {
    return x * x;
}

int main() {
    int arr[square(5)];  // 数组大小可以在编译时求值
}

constexpr 可以用于函数定义,它确保函数在编译时求值并返回常量。

7. const 与模板

const 在模板中的应用可以帮助提高代码的灵活性和类型安全性。

7.1 const 修饰模板类型参数

const 可以修饰模板类型参数,限制模板参数在模板实例化时不可修改。

template <typename T>
void printValue(const T& value) {
    std::cout << value << std::endl;
}

这里 T& 表示 value 是一个对 T 类型的引用,const 修饰 T& 表明引用的值不可修改。

7.2 const 与模板特化

const 可以用于模板特化中,指定不同的行为。

template <typename T>
void printValue(T value) {
    std::cout << value << std::endl;
}

template <>
void printValue<const char*>(const char* value) {
    std::cout << "String: " << value << std::endl;
}

这里我们对 const char* 进行了模板特化,以便提供不同的行为。

8. const 与移动语义(C++11及以后)

C++11 引入了移动语义,const 可以与移动构造函数和移动赋值运算符一起使用来优化性能。

8.1 const 在移动构造函数中的应用

当使用 const 来修饰类的成员时,可以防止对象在移动操作中被意外修改。特别是在移动构造函数和移动赋值运算符中,const 能够确保某些不该改变的对象数据不会被改变。

class MyClass {
public:
    const int x;
    MyClass(int val) : x(val) {}
    // 移动构造函数
    MyClass(MyClass&& other) : x(other.x) {}  // `x` 被移动但不可修改
};

9. const 与 Lambda 表达式

C++11 引入了 Lambda 表达式,const 可以用于限制 lambda 表达式内部的变量修改。

9.1 const 修饰捕获列表
int main() {
    int x = 10;
    auto lambda = [x]() { 
        std::cout << x << std::endl; 
        // x = 20;  // 错误:不能修改捕获的常量变量
    };
    lambda();
}

在捕获列表中指定 const 表示捕获的变量不可修改。

9.2 const 修饰 lambda 函数参数
auto lambda = [](const int& x) {
    // x = 10;  // 错误:参数是常量引用,不能修改
    std::cout << x << std::endl;
};

使用 const 修饰 lambda 参数,确保 lambda 内部不能修改传入的参数。

10. const 与并发编程

在并发编程中,const 可以用于保证线程安全。特别是当多线程访问共享资源时,使用 const 可以帮助避免对共享资源的无意修改,从而减少潜在的竞态条件。

10.1 常量数据与线程
const int MAX_THREADS = 8; // 将 `MAX_THREADS` 声明为常量,确保它的值在程序中不会被修改。
10.2 const 修饰共享资源
void processData(const std::vector<int>& data) {
    // data 内容不可被修改,确保数据的一致性
}

在多线程环境中,通过将共享资源声明为 const,可以防止资源在并发操作中被修改。

11. 使用 const 优化性能

在传递大型对象(如容器、类对象等)时,使用 const 修饰参数,尤其是常量引用,可以避免不必要的复制,提高程序性能。

11.1 常量引用优化
void printVector(const std::vector<int>& vec) {
    for (int i = 0; i < vec.size(); ++i) {
        std::cout << vec[i] << " ";
    }
}

使用常量引用 const std::vector<int>& 可以避免复制整个 vector,提高性能。

12. const 与条件编译

const 也可以在条件编译中使用,来定义根据条件编译不同的常量。

12.1 条件编译常量
#ifdef DEBUG
const int bufferSize = 1024;
#else
const int bufferSize = 512;
#endif

根据 DEBUG 宏的定义,选择不同的常量值。

常量性的重要性

使用const可以提高代码的可读性和安全性,它告诉其他程序员和维护者这些值不应该被修改。此外,const还可以帮助编译器优化代码,因为编译器可以假设const变量的值在声明后不会改变。

总结

用法说明示例代码
const 修饰变量声明常量变量,变量值不可修改const int x = 10;
const 修饰指针定义常量指针(指针不可修改)或指向常量的指针(内容不可修改)const int* ptr; / int* const ptr;
const 修饰成员函数成员函数声明为常量,表示该函数不会修改类的状态void show() const;
const 修饰函数参数传递引用或指针时,保证函数内部不会修改传入的参数void func(const int& value);
const 修饰数组声明常量数组,数组元素不可修改const int arr[] = {1, 2, 3};
const 修饰字符串声明常量字符串,字符串内容不可修改const char* str = "Hello";
constautoauto 一起使用,推导常量类型const auto cvalue = 10;
constconstexpr用于声明常量表达式,要求编译时求值constexpr int y = 20;
const 与模板修饰模板参数,限制模板参数不可修改template <typename T> void func(const T& value);
const 与 Lambda在 Lambda 表达式中限制捕获或参数不可修改auto lambda = [x]() { ... };
const 与并发编程确保在多线程环境中共享资源不可修改,保证数据一致性void processData(const std::vector<int>& data);