跳到主要内容

C++ 常量成员

在C++面向对象编程中,常量成员是一种重要的特性,它可以帮助我们创建更加安全和可靠的类设计。常量成员包括常量成员变量和常量成员函数,它们在不同场景下发挥着重要作用。本文将详细介绍C++中常量成员的定义、使用方法及其实际应用场景。

常量成员变量

什么是常量成员变量?

常量成员变量是指在类中声明为const的成员变量,它的值在对象的生命周期内保持不变。通常情况下,常量成员变量需要在构造函数的初始化列表中进行初始化。

声明和初始化常量成员变量

cpp
class Circle {
private:
const double PI; // 常量成员变量
double radius;

public:
// 使用初始化列表初始化常量成员变量
Circle(double r) : PI(3.14159), radius(r) {
// PI = 3.14; // 错误!常量成员变量不能在构造函数体内赋值
}

double getArea() const {
return PI * radius * radius;
}
};

int main() {
Circle c(5.0);
std::cout << "圆的面积: " << c.getArea() << std::endl;
return 0;
}

输出:

圆的面积: 78.5398
备注

常量成员变量必须在构造函数的初始化列表中初始化,而不能在构造函数体内进行赋值操作。

静态常量成员变量

静态常量成员变量是属于类而非对象的常量,可以在类内直接初始化(如果是整型或枚举类型):

cpp
class MathConstants {
public:
static const int MAX_ITERATIONS = 1000; // 整型静态常量可以在类内初始化
static const double E; // 非整型静态常量需要在类外定义
};

// 类外定义非整型静态常量
const double MathConstants::E = 2.71828;

int main() {
std::cout << "最大迭代次数: " << MathConstants::MAX_ITERATIONS << std::endl;
std::cout << "自然对数底数: " << MathConstants::E << std::endl;
return 0;
}

输出:

最大迭代次数: 1000
自然对数底数: 2.71828

常量成员函数

什么是常量成员函数?

常量成员函数是指在函数声明后加上const关键字的成员函数。这类函数不允许修改类的成员变量,也不能调用类中的非常量成员函数。

常量成员函数的主要作用是:

  1. 表明该函数不会修改对象的状态
  2. 让常量对象能够调用这些函数
  3. 提高代码的安全性和可读性

声明和使用常量成员函数

cpp
class Student {
private:
std::string name;
int age;
double score;

public:
Student(std::string n, int a, double s) : name(n), age(a), score(s) {}

// 常量成员函数
std::string getName() const {
// name = "Test"; // 错误!常量成员函数不能修改成员变量
return name;
}

int getAge() const {
return age;
}

double getScore() const {
return score;
}

// 非常量成员函数
void setScore(double s) {
score = s;
}

// 常量成员函数中不能调用非常量成员函数
void printInfo() const {
std::cout << "姓名: " << getName() << std::endl; // 可以调用常量函数
std::cout << "年龄: " << getAge() << std::endl;
std::cout << "分数: " << getScore() << std::endl;
// setScore(100); // 错误!常量成员函数中不能调用非常量成员函数
}
};

int main() {
const Student s("张三", 20, 85.5); // 常量对象
// s.setScore(90); // 错误!常量对象不能调用非常量成员函数
s.printInfo(); // 常量对象可以调用常量成员函数

Student s2("李四", 19, 92.0); // 非常量对象
s2.setScore(95); // 非常量对象可以调用非常量成员函数
s2.printInfo(); // 非常量对象也可以调用常量成员函数

return 0;
}

输出:

姓名: 张三
年龄: 20
分数: 85.5
姓名: 李四
年龄: 19
分数: 95
提示

将那些不修改对象状态的成员函数声明为常量成员函数是一种好习惯,这样可以提高代码的安全性,并且使常量对象能够调用这些函数。

常量对象和常量成员的关系

  1. 常量对象只能调用常量成员函数,不能调用非常量成员函数
  2. 常量成员函数不能修改成员变量的值(除非该成员变量被声明为mutable
  3. 常量成员函数不能调用非常量成员函数

mutable关键字

如果确实需要在常量成员函数中修改某些成员变量,可以使用mutable关键字:

cpp
class Counter {
private:
int value;
mutable int accessCount; // 可变成员变量

public:
Counter(int v) : value(v), accessCount(0) {}

int getValue() const {
accessCount++; // 即使在常量成员函数中,也可以修改mutable成员变量
return value;
}

int getAccessCount() const {
return accessCount;
}
};

int main() {
const Counter c(10);
std::cout << "值: " << c.getValue() << std::endl;
std::cout << "值: " << c.getValue() << std::endl;
std::cout << "访问次数: " << c.getAccessCount() << std::endl;

return 0;
}

输出:

值: 10
值: 10
访问次数: 2

实际应用场景

场景一:设计只读属性

在银行账户系统中,账号一旦创建就不应被修改,但需要被读取:

cpp
class BankAccount {
private:
const std::string accountNumber; // 常量成员变量(账号不可更改)
double balance;

public:
BankAccount(const std::string& accNum, double initialBalance)
: accountNumber(accNum), balance(initialBalance) {}

// 只读属性的getter函数
std::string getAccountNumber() const {
return accountNumber;
}

double getBalance() const {
return balance;
}

void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}

bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
};

场景二:设计不可变对象

不可变对象在多线程编程中特别有用,因为它们天然是线程安全的:

cpp
class Point {
private:
const double x;
const double y;

public:
Point(double xCoord, double yCoord) : x(xCoord), y(yCoord) {}

double getX() const { return x; }
double getY() const { return y; }

// 创建新对象而不是修改当前对象
Point add(const Point& other) const {
return Point(x + other.x, y + other.y);
}

double distanceTo(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
};

场景三:接口设计

在接口设计中,常量成员函数让调用方明确知道哪些操作不会修改对象状态:

cpp
class Shape {
public:
virtual double getArea() const = 0; // 纯虚常量成员函数
virtual double getPerimeter() const = 0;
virtual void display() const = 0;
virtual ~Shape() {}
};

class Rectangle : public Shape {
private:
double width;
double height;

public:
Rectangle(double w, double h) : width(w), height(h) {}

double getArea() const override {
return width * height;
}

double getPerimeter() const override {
return 2 * (width + height);
}

void display() const override {
std::cout << "矩形 - 宽: " << width << ", 高: " << height << std::endl;
}
};

总结

  1. 常量成员变量

    • 必须在构造函数的初始化列表中初始化
    • 在对象的生命周期内不能修改
    • 静态常量整型成员可以在类内初始化
  2. 常量成员函数

    • 在函数声明后添加const关键字
    • 不能修改成员变量(除非是mutable
    • 不能调用非常量成员函数
    • 常量对象只能调用常量成员函数
  3. 使用建议

    • 将不需要修改的成员变量声明为const
    • 将不修改对象状态的成员函数声明为常量成员函数
    • 使用mutable关键字允许在常量成员函数中修改特定成员变量

常量成员是C++面向对象编程中重要的安全机制,合理使用它们可以创建更加健壮、安全的类设计,避免意外修改对象状态,同时提高代码的可读性和可维护性。

练习

  1. 设计一个Time类,包含表示小时、分钟和秒的常量成员变量,以及计算与另一个时间对象的时间差的常量成员函数。
  2. 创建一个ImmutableString类,保存一个不可修改的字符串,但提供各种字符串操作(如截取子串、连接等)方法,这些方法应返回新的ImmutableString对象而不是修改现有对象。
  3. 设计一个Configuration类,包含各种应用程序配置项,这些配置项在程序运行期间不应被修改,但需要可以被读取。
警告

常量成员函数中调用非常量成员函数是一个常见的错误,编译器会报错。确保在常量成员函数中只调用其他常量成员函数。