C++启蒙课程:第10章  对象和类

我们将学会创建“智能对象”,它们不仅能存数据,还能自己处理数据,让代码升级为“高科技机器人军团”!

🤖

类的诞生:从指令到对象

学习“超级蓝图”`class`的定义,以及`public`和`private`的“智能包裹”封装术。

🚀

对象的生命之旅

掌握“诞生机”`constructor`、“清场队”`destructor`和“自我身份卡”`this`。

🔒

进阶对象魔法

学会`const`成员函数的“安全锁”和抽象的“冰山模型” ADT。

课时一:从指令到对象:类的诞生

(覆盖知识点 10.1 - 10.2.6)

10.1 厨师 vs. 机器人

过程性编程像“厨师拿食谱”(关注步骤)。OOP像“智能机器人”(数据和操作捆绑)。

Data data;
cook(data);
serve(data);
// 过程性 (数据和函数分离) struct Data { ... }; void cook(Data d); // OOP (数据和函数封装) class Robot { Data data; // 私有 public: void cook(); // 公有 };

10.2 “智能包裹” (封装)

`public` 是包裹上的“按钮”(接口),任何人都能按。`private` 是内部的“精密电路”(数据),禁止外界直接触碰。

BankAccount 包裹
class BankAccount { public: // 公有按钮 void deposit(double cash); private: // 私有电路 double balance; }; // sally.deposit(100); // OK! // sally.balance = 5; // 错误!

10.2.4 “超级蓝图” (类)

`class` 是一张蓝图。对象 (sally, john) 是根据蓝图造出来的、各自独立的“实例”。

Sally (对象1)
Shares: 0
John (对象2)
Shares: 0
// Stock 是蓝图 class Stock { ... }; int main() { Stock sally; // sally 是一个实例 Stock john; // john 是另一个实例 sally.acquire(10); // 只影响 sally john.acquire(5); // 只影响 john }

10.2.3 成员函数实现

在类外定义成员函数时,必须使用“作用域解析运算符” (::) 来指明这个函数属于哪个类。

// --- Stock.h (蓝图) --- class Stock { public: void update(double price); // 原型 }; // --- Stock.cpp (实现) --- #include "Stock.h" // 10.2.3 使用 Stock:: 指明归属 void Stock::update(double price) { // ... }

课时二:对象的生命之旅:构造与析构

(覆盖知识点 10.3 - 10.5)

10.3 对象的“诞生机” (构造)

构造函数 (Constructor) 是在创建对象时自动调用的“诞生机”,负责初始化所有内部零件。

// 10.3.1 构造函数: 与类同名, 无返回 BankAccount::BankAccount(const string & name, const string & num, double bal) { // 自动运行, 初始化私有成员 depositor_name = name; balance = bal; }

10.3.4 对象的“清场队” (析构)

析构函数 (~) 是在对象生命周期结束时自动调用的“清场队”,负责释放资源。

Object obj;
🧹
// 10.3.4 析构函数: 名称是 ~类名 // 没有参数, 没有返回 Stock::~Stock() { // 在对象销毁时自动调用 // (例如用于 delete 动态内存) }

10.4 “自我身份卡” (this)

`this` 是一个隐藏指针,指向“调用我”的那个对象。*this 就是对象本身。

Stock A (val=50)
Stock B (val=100)
// 比较 s 和 "我自己" const Stock& Stock::topval(const Stock & s) const { if (s.total_val > total_val) return s; // s 更大 else // 10.4 *this 就是 "我自己" (调用对象) return *this; }

10.5 对象数组

创建对象数组时,编译器会为数组中的“每个”元素调用**默认构造函数** (无参数的)。

// 10.5 对象数组 BankAccount accounts[3]; // 编译器会查找 BankAccount() // 如果你只定义了 BankAccount(name...) // 而没有定义 BankAccount() // 这一行代码将编译失败!

课时三:进阶对象魔法:作用域与常量

(覆盖知识点 10.3.6, 10.6, 10.7)

10.3.6 “安全锁” (const)

在函数头后加 const,就像给函数上了一把“安全锁” 🔒。它承诺**不会修改**调用它的对象。

const Stock s; (只读对象)
🔒
class Stock { public: // 10.3.6 const 安全锁 void show() const; // 承诺不修改 void acquire(); // (没有 const, 可能会修改) }; // ... const Stock sally; // sally 是一个只读对象 sally.show(); // OK! (show 是 const) // sally.acquire(); // 错误!(acquire 不是 const)

10.7 “冰山模型” (ADT)

类是实现“抽象数据类型”(ADT)的工具。`public` 是水面上的“接口”,`private` 是水面下隐藏的“实现”。

🌊 Public (接口) 🌊
🏔️
deposit(), withdraw(), show()
Private (隐藏的实现)
double balance;
string account_number;
// ADT (抽象) // 1. 我需要一个 "BankAccount" // 2. 它需要能 deposit() 和 show() // Class (具体实现) class BankAccount { public: void deposit(double cash); // 公有接口 void show() const; private: double balance; // 隐藏的实现 };

编程实践与作业 (10.9)

是时候检验你作为“智能对象设计师”的实力了!

练习 1:BankAccount 类声明 (10.9 复习题 5)

任务:

定义一个 BankAccount 类,包含姓名、账号和存款数据成员,以及创建、显示、存入和取出的成员函数。只提供类声明(接口)。

点击查看参考答案
#include <string> using namespace std; class BankAccount { private: // 私有数据成员 (数据隐藏) string depositor_name; string account_number; double balance; public: // 公有成员函数 (接口) // 构造函数 BankAccount(const string & name, const string & num, double bal); // 默认构造函数 (为对象数组准备) BankAccount(); void show() const; void deposit(double cash); void withdraw(double cash); };
练习 2:BankAccount 构造函数实现 (10.9 复习题 7)

任务:

为第一课时定义的 BankAccount 类提供带参数的构造函数实现,并演示如何创建对象数组(需要默认构造函数)。

点击查看参考答案
#include <iostream> #include <string> using namespace std; // (假设 BankAccount 类的声明已存在) // 10.3.1 构造函数定义 BankAccount::BankAccount(const string & name, const string & num, double bal) { depositor_name = name; account_number = num; balance = bal; } // 10.3.3 默认构造函数定义 BankAccount::BankAccount() { depositor_name = "未设置"; balance = 0.0; } // 10.3.4 析构函数定义 (即使为空) BankAccount::~BankAccount() {} int main() { // 演示参数化构造 BankAccount alice("Alice", "12345", 500.0); // 10.5 演示对象数组 (必须有默认构造) BankAccount accounts[3]; return 0; }
练习 3:const 成员函数 (10.9 复习题 9)

任务:

假设有一个 Stock 类,添加返回各个私有数据成员值的 const 成员函数 (Getter)。

点击查看参考答案
#include <string> using namespace std; class Stock { private: string company; long shares; public: Stock(const string & co, long n = 0) : company(co), shares(n) {} // 10.3.6 const 成员函数 (Getter) // 它们承诺不修改对象 string get_company() const { return company; } long get_shares() const { return shares; } // 错误示例: // void error_func() const { shares = 0; } // 编译器会报错! };

本章知识点总结与复习

核心概念回顾

核心概念 解释/功能 关键用法/示例
类 (Class)用户定义的类型规范,“超级蓝图”。class Stock { ... };
对象 (Object)类的实例,根据蓝图创建的实体。Stock my_stock;
封装/隐藏`public` (接口) 和 `private` (隐藏实现)。private: double balance;
构造函数“诞生机”,创建对象时自动调用,用于初始化。Stock(const string & co);
默认构造函数无参数或所有参数都有默认值。创建对象数组时必须。Stock() {}
析构函数“清场队”,对象销毁时自动调用,用于清理。~Stock() {}
`this` 指针“自我身份卡”,指向调用该方法的对象。return *this;
`const` 成员函数“安全锁”,承诺不修改调用对象。void show() const;
ADT抽象数据类型。“冰山模型”,类是其C++实现。Public 接口 / Private 实现

编程练习与代码实现 (10.10 练习 2)

任务: 编写一个 Person 类,实现其三种构造函数调用(无参数、一个参数、两个参数),并使用不同的方法显示结果。

#include <iostream> #include <string> #include <cstring> // for strncpy using namespace std; class Person { private: static const int LIMIT = 25; string lname; // 姓氏 char fname[LIMIT]; // 名字 (C-风格字符串) public: // 1. 默认构造函数 (10.3.3) Person() { lname = ""; fname[0] = '\0'; // 初始化 C-style 字符串 } // 2. 一个参数的构造函数 (10.3.2) Person(const string & ln) { lname = ln; fname[0] = '\0'; } // 3. 两个参数的构造函数 Person(const string & ln, const char * fn) { lname = ln; strncpy(fname, fn, LIMIT - 1); fname[LIMIT - 1] = '\0'; } // 10.3.6 const 成员函数 void show() const { if (fname[0] != '\0') { cout << fname << " " << lname << endl; } else { cout << "No name set (" << lname << ")" << endl; } } // 10.3.6 const 成员函数 void FormalShow() const { if (fname[0] != '\0') { cout << lname << ", " << fname << endl; } else { cout << "No name set (" << lname << ")" << endl; } } }; int main() { Person one; // 默认构造 Person two("Smythecraft"); // 一个参数 Person three("Duda", "Felicia"); // 两个参数 one.FormalShow(); two.show(); three.FormalShow(); return 0; }