类的基本定义
定义与作用
类(Class)是 C++ 面向对象编程的核心机制,它将数据(成员变量)和操作(成员函数)封装为一个逻辑单元。类的设计目标是封装——隐藏内部实现细节,只暴露必要的公共接口。
class Student {
public: // 公共接口
void enroll(); // 成员函数
private: // 内部实现
std::string name_;
int id_;
};
核心原理
访问控制与对象布局
const 成员函数与 this 指针
this 是指向当前对象的隐式指针:
- 非 const 成员函数中:
this类型为T* const(顶层 const,指针本身不可改) - const 成员函数中:
this类型为const T* const(顶层 + 底层 const)
完整示例
示例一:学生信息管理类
场景说明:设计一个带数据校验的学生类,展示访问控制和 const 正确性。
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
class Student {
public:
// 构造函数
Student(std::string name, int id, double gpa)
: name_(std::move(name)), id_(id) {
setGPA(gpa); // 通过 setter 复用校验逻辑
}
// ---- const 成员函数(只读接口) ----
const std::string& getName() const { return name_; }
int getID() const { return id_; }
double getGPA() const { return gpa_; }
// 返回等级评定
std::string getGradeLevel() const {
if (gpa_ >= 3.7) return "优秀";
if (gpa_ >= 3.0) return "良好";
if (gpa_ >= 2.0) return "及格";
return "不及格";
}
// ---- 非 const 成员函数(修改接口) ----
void setName(std::string name) {
if (name.empty()) {
throw std::invalid_argument("姓名不能为空");
}
name_ = std::move(name);
}
void setGPA(double gpa) {
if (gpa < 0.0 || gpa > 4.0) {
throw std::out_of_range("GPA 必须在 0.0 到 4.0 之间");
}
gpa_ = gpa;
}
// 打印信息
void print() const {
std::cout << "姓名: " << name_
<< " | 学号: " << id_
<< " | GPA: " << gpa_
<< " | 等级: " << getGradeLevel() << std::endl;
}
private:
std::string name_;
int id_;
double gpa_ = 0.0;
};
int main() {
std::cout << "===== 学生信息管理 =====" << std::endl;
// 创建对象
Student alice("张三", 2024001, 3.8);
alice.print();
// 修改数据(通过 setter,有校验)
try {
alice.setGPA(3.9);
alice.print();
alice.setGPA(5.0); // 触发异常
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
// const 对象只能调用 const 成员函数
const Student const_student("李四", 2024002, 2.5);
std::cout << "\nconst 学生: " << const_student.getName()
<< " | 等级: " << const_student.getGradeLevel() << std::endl;
// const_student.setGPA(3.0); // 编译错误
return 0;
}
预期输出:
===== 学生信息管理 =====
姓名: 张三 | 学号: 2024001 | GPA: 3.8 | 等级: 优秀
姓名: 张三 | 学号: 2024001 | GPA: 3.9 | 等级: 优秀
错误: GPA 必须在 0.0 到 4.0 之间
const 学生: 李四 | 等级: 及格
逐段分析:
getName()返回const std::string&:避免拷贝,同时防止外界修改内部数据setGPA()内置范围校验:封装让数据约束集中管理,不依赖调用者自觉getGradeLevel()是 const 成员函数——它只读取数据,不修改状态const Student对象只能调用const成员函数——编译器强制执行只读约束- 构造函数使用
setGPA()而非直接赋值——DRY 原则,校验逻辑只写一次
示例二:this 指针与返回 *this 的链式调用
场景说明:设计支持链式调用的 Builder 类,展示 this 指针的实际用法。
#include <iostream>
#include <string>
#include <sstream>
class TableBuilder {
public:
// 每个 setter 返回 *this,实现链式调用
TableBuilder& setTitle(std::string title) {
title_ = std::move(title);
return *this; // this 指向当前对象
}
TableBuilder& addHeader(std::string header) {
headers_.append("| ").append(header).append(" ");
return *this;
}
TableBuilder& endHeader() {
headers_.append("|\n");
return *this;
}
TableBuilder& addRow(std::string cell) {
rows_.append("| ").append(cell).append(" ");
return *this;
}
TableBuilder& endRow() {
rows_.append("|\n");
return *this;
}
std::string build() const {
std::ostringstream oss;
oss << title_ << "\n";
oss << headers_.str();
oss << rows_.str();
return oss.str();
}
private:
std::string title_;
std::ostringstream headers_;
std::ostringstream rows_;
};
int main() {
std::cout << "===== Builder 模式(链式调用) =====" << std::endl;
// 链式调用:每个方法返回 *this
TableBuilder builder;
builder.setTitle("学生成绩表")
.addHeader("姓名").addHeader("学号").addHeader("GPA").endHeader()
.addRow("张三").addRow("2024001").addRow("3.8").endRow()
.addRow("李四").addRow("2024002").addRow("4.0").endRow();
std::cout << builder.build() << std::endl;
// 验证 this 指针的值
std::cout << "===== this 指针验证 =====" << std::endl;
TableBuilder tb;
std::cout << "对象地址: " << &tb << std::endl;
// 每个返回 *this 的方法,返回值地址应该等于 &tb
return 0;
}
预期输出:
===== Builder 模式(链式调用) =====
学生成绩表
| 姓名 | 学号 | GPA |
| 张三 | 2024001 | 3.8 |
| 李四 | 2024002 | 4.0 |
===== this 指针验证 =====
对象地址: 0x7fff...
逐段分析:
- 每个 setter 返回
*this的引用(TableBuilder&),实现方法链(Method Chaining) return *this;是直接的 this 指针解引用,返回对象自身- Builder 模式中链式调用让配置代码紧凑、可读
build()是 const 成员函数——产生最终结果,不修改 Builder 本身
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
| const 对象调用非 const 成员函数 | 编译错误 | 将不需要修改状态的成员函数标记为 const |
| const 成员函数中修改 mutable 成员 | 违反逻辑 const | 谨慎使用 mutable |
忘记 return *this 的引用类型 | 返回值拷贝而非引用 | 返回 T& 而非 T |
| 类声明后忘记分号 | 编译错误 | class Foo { }; 注意结尾分号 |
| 成员函数与自由函数命名冲突 | 意外调用 | 注意作用域,必要时用 this-> 显式调用 |
常见面试问题
this指针是什么?类型是什么?何时使用?——指向当前对象的隐式指针。非 const 成员函数中类型T* const,const 成员函数中const T* const。用于区分成员变量与局部变量、返回*this、获取对象地址。const成员函数能修改mutable成员变量吗?——可以。mutable成员即使在 const 成员函数中也可修改。典型用途:缓存、互斥锁、引用计数。什么函数应该标记为 const?——任何不修改对象逻辑状态的成员函数都应标记为 const。这是 const 正确性(const-correctness)的核心准则。
struct和class的区别?——唯一的区别是默认访问控制:struct默认 public,class默认 private。习惯上struct用于纯数据聚合,class用于有封装逻辑的类。成员函数可以重载为 const 和非 const 两个版本吗?——可以。例如
T& operator[](int)和const T& operator[](int) const。const 对象自动调用 const 版本。
小结
- 类 = 数据 + 操作的封装单元,核心价值是封装和信息隐藏
- const 成员函数不修改对象状态,const 对象只能调用 const 成员函数
this是指向当前对象的隐式指针,返回*this实现链式调用- setter 中加数据校验,让封装保护数据完整性
- const 正确性是 C++ 代码质量的基石——能 const 就 const