【Linux C/C++开发】第7章:面向对象基础
本文系统介绍了面向对象编程的基础知识,包括三大核心特征(封装、继承、多态)及其应用。重点讲解了类的定义与结构,成员函数设计(构造函数、析构函数、this指针、常量成员函数、静态成员),以及RAII资源管理原则和友元机制。通过实际代码示例阐释了面向对象设计的思想精髓,如封装数据、隐藏实现细节、通过方法操作对象等。文章还提出了面向对象设计的两大原则:单一职责原则和接口清晰原则,强调每个类应聚焦单一功能
第7章:面向对象基础
🎯 学习目标
理解面向对象编程的核心思想,掌握类的设计与实现,学会正确使用对象,建立清晰的OOP编程思维。
📖 面向对象编程思想
什么是面向对象编程?
面向对象编程(OOP)是一种程序设计方法,它将数据和操作数据的方法组织在一起,形成对象。这种方式更符合我们日常思考问题的方式。
举个生活中的例子:
- 汽车是一个对象,它有颜色、品牌等属性(数据)
- 它可以启动、停止、加速等行为(方法)
- 我们不需要知道发动机如何工作,只需要知道踩油门就能加速(封装)
OOP的三大核心特征
1. 封装(Encapsulation)
本质:把相关的数据和方法放在一起,对外隐藏实现细节。
为什么需要封装?
- 保护数据不被随意修改
- 降低使用者的学习成本
- 便于内部实现的修改和优化
实际例子:
class BankAccount {
private: // 私有成员,外部无法直接访问
double balance; // 余额
string password; // 密码
public: // 公有接口,外部可以使用
void deposit(double amount); // 存款
bool withdraw(double amount); // 取款
double getBalance(); // 查询余额
};
2. 继承(Inheritance)
本质:基于已有的类创建新类,实现代码复用。
为什么需要继承?
- 避免重复代码
- 建立类之间的层次关系
- 实现"是一种"的关系(如"狗是一种动物")
3. 多态(Polymorphism)
本质:相同的接口,不同的实现。
为什么需要多态?
- 提高代码的灵活性
- 实现统一的接口
- 支持运行时动态绑定
🏗️ 类的定义与使用
类的基本结构
class 类名 {
private:
// 私有成员:只能被类自己的成员函数访问
数据类型 成员变量;
protected:
// 保护成员:可以被自己和子类访问
数据类型 保护成员;
public:
// 公有成员:可以被任何人访问
返回类型 成员函数(参数);
// 构造函数:创建对象时自动调用
类名(参数);
// 析构函数:销毁对象时自动调用
~类名();
};
访问控制的设计智慧
访问权限矩阵:
| 访问修饰符 | 类内部 | 派生类 | 外部函数 | 说明 |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | 最严格,完全隐藏实现细节 |
| protected | ✓ | ✓ | ✗ | 平衡封装性和继承需求 |
| public | ✓ | ✓ | ✓ | 对外接口,需要保持稳定 |
设计原则:
- 数据成员通常设为private,通过公有方法访问
- 接口方法设为public,提供清晰的功能说明
- 辅助方法可以设为protected,便于子类复用
⚙️ 成员函数的设计
构造函数:对象的"出生证明"
构造函数是特殊的成员函数,名称与类相同,没有返回类型,在创建对象时自动调用。
构造函数的类型
1. 默认构造函数
class Point {
public:
Point() : x(0), y(0) {} // 默认构造函数
private:
int x, y;
};
作用:创建对象时提供合理的初始状态。
2. 参数化构造函数
class Point {
public:
Point(int x_val, int y_val) : x(x_val), y(y_val) {}
private:
int x, y;
};
作用:允许创建对象时指定初始状态。
3. 构造函数重载
class Point {
public:
Point() : x(0), y(0) {} // 默认构造
Point(int x_val) : x(x_val), y(0) {} // 单参数构造
Point(int x_val, int y_val) : x(x_val), y(y_val) {} // 双参数构造
};
作用:提供多种创建对象的方式。
初始化列表的妙用
class Student {
private:
string name;
int age;
double gpa;
public:
// 使用初始化列表(推荐)
Student(const string& n, int a, double g) : name(n), age(a), gpa(g) {}
// VS 构造函数体内赋值(不推荐)
Student(const string& n, int a, double g) {
name = n; // 先默认构造,再赋值
age = a;
gpa = g;
}
};
为什么初始化列表更好?
- 避免重复构造和赋值
- 对于const成员和引用成员是必须的
- 初始化顺序与声明顺序一致,更直观
this指针:对象的"自我介绍"
this指针是编译器自动添加的隐式参数,指向调用成员函数的对象本身。
class Point {
public:
void setX(int x) {
this->x = x; // this指向当前对象
}
private:
int x;
};
编译器视角:
// 你写的代码
void Point::setX(int x) { this->x = x; }
// 编译器看到的代码
void Point_setX(Point* const this, int x) { this->x = x; }
常量成员函数:承诺不修改对象
class Student {
public:
string getName() const; // 承诺不修改对象
void setName(string name); // 可以修改对象
};
好处:
- 可以被const对象调用
- 提高代码的可读性和安全性
- 编译器可以帮助检查错误
静态成员:属于类的"共享财产"
静态成员属于整个类,而不是某个具体的对象。
class BankAccount {
private:
static double interest_rate; // 所有账户共享一个利率
static int account_count; // 账户总数
public:
static double getInterestRate() { return interest_rate; }
static int getAccountCount() { return account_count; }
};
特点:
- 所有对象共享同一个静态成员
- 可以通过类名直接访问:
BankAccount::getInterestRate() - 静态函数没有this指针,不能访问非静态成员
🧹 析构函数:对象的"善后工作"
析构函数在对象销毁时自动调用,负责清理资源。
RAII:资源管理的智慧
RAII(Resource Acquisition Is Initialization)是C++的核心思想:
资源的生命周期 = 对象的生命周期
class FileHandler {
private:
FILE* file;
public:
FileHandler(const string& filename) { // 构造函数获取资源
file = fopen(filename.c_str(), "r");
}
~FileHandler() { // 析构函数释放资源
if (file) fclose(file);
}
};
// 使用示例
void processFile() {
FileHandler handler("data.txt"); // 自动打开文件
// 使用文件...
} // 函数结束时自动调用析构函数,关闭文件
好处:
- 自动管理资源,避免内存泄漏
- 异常安全,即使抛出异常也能正确清理
- 代码简洁,不需要手动释放资源
🤝 友元机制:打破封装的"特例"
友元机制允许特定的函数或类访问私有成员。
class Point {
private:
int x, y;
public:
friend double distance(const Point& p1, const Point& p2); // 友元函数
};
double distance(const Point& p1, const Point& p2) {
// 友元函数可以访问私有成员
int dx = p1.x - p2.x;
int dy = p1.y - p2.y;
return sqrt(dx*dx + dy*dy);
}
使用场景:
- 运算符重载(如<<输出运算符)
- 紧密协作的类(如容器类和迭代器类)
- 需要访问内部状态的辅助函数
**注意:**友元破坏了封装性,应该谨慎使用。
💡 设计原则与最佳实践
1. 单一职责原则
每个类应该只有一个主要职责。
// 不好的设计:一个类承担多个职责
class StudentManager {
void addStudent(); // 学生管理
void calculateGrade(); // 成绩计算
void printReport(); // 报表打印
void saveToFile(); // 文件存储
};
// 好的设计:职责分离
class Student { /* 学生信息 */ };
class GradeCalculator { /* 成绩计算 */ };
class ReportPrinter { /* 报表打印 */ };
class FileManager { /* 文件存储 */ };
2. 接口清晰原则
class BankAccount {
public:
// 清晰的接口:只暴露必要的方法
void deposit(double amount); // 存款
bool withdraw(double amount); // 取款
double getBalance() const; // 查询余额
private:
// 隐藏实现细节
void validateAmount(double amount);
void updateTransactionLog(const string& type, double amount);
double balance;
vector<string> transactionHistory;
};
3. 构造函数应该建立类不变式
class Rectangle {
public:
Rectangle(double width, double height) {
if (width <= 0 || height <= 0) {
throw invalid_argument("Width and height must be positive");
}
this->width = width;
this->height = height;
}
private:
double width, height;
// 类不变式:width > 0 && height > 0
};
🎯 实战练习
练习1:学生类设计
#include <iostream>
#include <string>
#include <stdexcept>
class Student {
private:
std::string name;
int age;
double gpa;
static int totalStudents; // 静态成员
public:
// 构造函数
Student() : name("Unknown"), age(0), gpa(0.0) {
totalStudents++;
}
Student(const std::string& n, int a, double g) : name(n), age(a), gpa(g) {
validateAge(a);
validateGpa(g);
totalStudents++;
}
// 析构函数
~Student() {
totalStudents--;
}
// 访问函数(const)
const std::string& getName() const { return name; }
int getAge() const { return age; }
double getGpa() const { return gpa; }
// 修改函数
void setName(const std::string& n) { name = n; }
void setAge(int a) {
validateAge(a);
age = a;
}
void setGpa(double g) {
validateGpa(g);
gpa = g;
}
// 静态函数
static int getTotalStudents() { return totalStudents; }
private:
void validateAge(int a) {
if (a < 0 || a > 150) {
throw std::invalid_argument("Invalid age");
}
}
void validateGpa(double g) {
if (g < 0.0 || g > 4.0) {
throw std::invalid_argument("Invalid GPA");
}
}
};
// 静态成员初始化
int Student::totalStudents = 0;
练习2:银行账户类
class BankAccount {
private:
std::string accountNumber;
std::string ownerName;
double balance;
static double interestRate;
static int accountCounter;
public:
// 构造函数
BankAccount(const std::string& owner, double initialBalance = 0.0)
: ownerName(owner), balance(initialBalance) {
if (initialBalance < 0) {
throw std::invalid_argument("Initial balance cannot be negative");
}
accountNumber = generateAccountNumber();
accountCounter++;
}
// 存款
void deposit(double amount) {
validateAmount(amount);
balance += amount;
}
// 取款
bool withdraw(double amount) {
validateAmount(amount);
if (amount > balance) {
return false; // 余额不足
}
balance -= amount;
return true;
}
// 查询信息
const std::string& getAccountNumber() const { return accountNumber; }
const std::string& getOwnerName() const { return ownerName; }
double getBalance() const { return balance; }
// 静态函数
static double getInterestRate() { return interestRate; }
static void setInterestRate(double rate) { interestRate = rate; }
private:
std::string generateAccountNumber() {
return "ACC" + std::to_string(1000000 + accountCounter);
}
void validateAmount(double amount) {
if (amount <= 0) {
throw std::invalid_argument("Amount must be positive");
}
}
};
// 静态成员初始化
double BankAccount::interestRate = 0.03; // 3%年利率
int BankAccount::accountCounter = 0;
🎓 学习要点总结
核心概念
- 类是模板,对象是实例 - 理解抽象与具体的关系
- 封装隐藏实现,接口定义行为 - 平衡复杂性和可用性
- 构造函数建立状态,析构函数清理资源 - RAII的核心思想
- 访问控制保护数据,友元机制处理特例 - 封装与功能的平衡
设计原则
- 保持接口简单清晰
- 构造函数要建立类不变式
- 优先使用初始化列表
- 合理使用const保证安全性
- 谨慎使用友元,避免破坏封装
常见错误
- 在构造函数中抛出异常而不处理 - 可能导致资源泄漏
- 忘记初始化静态成员 - 会导致链接错误
- 滥用友元机制 - 破坏封装性
- 忽视const的正确使用 - 降低代码安全性
性能考虑
- 初始化列表比构造函数体内赋值更高效
- 内联小函数可以减少函数调用开销
- 静态成员可以减少内存占用
- const函数可以帮助编译器优化
理解这些概念不仅是学习C++的需要,更是培养良好编程思维的重要一步。面向对象编程让我们能够用更自然的方式思考和解决问题。
更多推荐


所有评论(0)