MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

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与常量表达式的关系

  1. 基本类型的const变量与常量表达式 当一个基本类型(如intdouble等)的变量被声明为const,并且在声明时就进行初始化,那么这个变量可以作为常量表达式。
const int a = 5;
const double b = 3.14;

const int sum = a + b;  // sum也是常量表达式,因为a和b都是常量表达式
  1. 指针与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不能改变其指向
  1. 数组与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在函数中的使用

  1. 函数参数中的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修改外部变量
}
  1. 函数返回值中的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成员函数

  1. 定义与特点 在类中,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;
    }
};
  1. 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;
}
  1. 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与常量表达式在模板中的应用

  1. 模板参数中的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是常量表达式,可以作为模板参数
  1. 模板函数中的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与常量表达式在编译期计算中的应用

  1. 编译期常量计算的优势 在编译期进行常量计算可以提高程序的运行效率,因为一些计算结果在编译时就确定了,而不需要在运行时进行重复计算。
constexpr int factorial(int n) {
    return (n == 0 || n == 1)? 1 : n * factorial(n - 1);
}

const int result2 = factorial(5);  // 在编译期计算5的阶乘
  1. constexprconst的关系 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在不同作用域中的表现

  1. 全局作用域中的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变量
  1. 局部作用域中的const 在局部作用域(如函数内部)声明的const变量只在该局部作用域内有效,其生命周期与该局部作用域相同。
void localFunction() {
    const int localVar = 200;
    // localVar = 300;  // 编译错误,localVar是const,不能修改
    std::cout << localVar << std::endl;
}

const与类型转换

  1. 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对象
  1. const类型的转换 可以将非const对象转换为const对象,这种转换是安全的。
int num6 = 40;
const int &ref2 = num6;  // 合法,将非const变量转换为const引用

const在代码优化中的作用

  1. 编译器优化 编译器可以对const变量和const表达式进行优化。例如,对于const变量,编译器可能会将其值直接嵌入到使用它的代码中,而不是通过内存访问。
const int num7 = 50;
int result3 = num7 + 10;  // 编译器可能会直接将50 + 10计算出来,而不是先从内存读取num7的值
  1. 代码可读性与可维护性优化 使用const可以提高代码的可读性和可维护性。通过明确标识哪些变量是不可变的,程序员可以更清楚地理解代码的逻辑,减少错误的发生。
void calculateArea(const int width, const int height) {
    int area = width * height;
    std::cout << "Area: " << area << std::endl;
}

const在面向对象编程中的设计模式应用

  1. 单例模式中的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;
}
  1. 策略模式中的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在常量表达式中的使用要点

  1. 基本规则
    • 基本类型的const变量且初始化时的值为常量表达式,该变量本身也是常量表达式。
    • 指针和数组与const结合时,要清楚区分指向常量、常量指针、常量数组等不同情况,以及它们在常量表达式中的应用。
  2. 函数相关
    • 在函数参数和返回值中合理使用const,可以提高函数的安全性和健壮性。
    • const成员函数保证不修改对象的非mutable成员变量,const对象只能调用const成员函数。
  3. 模板与编译期计算
    • const变量若满足常量表达式条件,可作为模板参数。constexpr用于声明常量表达式函数和变量,提高编译期计算能力。
  4. 作用域与类型转换
    • 全局作用域const变量默认内部链接性,局部作用域const变量作用域和生命周期局限于局部。const_cast用于去除const属性,但需谨慎使用。
  5. 优化与设计模式
    • const有助于编译器优化,提高代码可读性和可维护性。在面向对象设计模式中,const可确保对象状态的稳定性。

通过深入理解和合理运用const在常量表达式中的各种特性,程序员可以编写出更高效、更健壮、更易维护的C++ 代码。无论是在简单的基础编程,还是复杂的大型项目中,const的正确使用都是C++ 编程的重要组成部分。