类是 C++ 中实现面向对象编程的核心机制,它是用户自定义的数据类型,用于描述对象的属性(数据成员)和行为(成员函数)。对象是类的实例。
类通常包含数据成员和成员函数,使用关键字 class
定义。访问修饰符(如 `public`, `private`)控制成员的可见性。
// 定义一个 Person 类
class Person {
private: // 私有成员,只能在类内部访问
std::string name;
int age;
public: // 公有成员,可以在类外部访问
// 构造函数:用于初始化对象
Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person 对象被创建: " << name << std::endl;
}
// 析构函数:在对象销毁时调用
~Person() {
std::cout << "Person 对象被销毁: " << name << std::endl;
}
// 成员函数(方法)
void sayHello() {
std::cout << "你好,我是 " << name << ",今年 " << age << " 岁。" << std::endl;
}
// 获取年龄的方法 (Getter)
int getAge() const { // const 表示此方法不修改对象状态
return age;
}
};
内存中的对象:
Person p1 ("张三", 25);
p1: { name: "张三", age: 25 }
p1.sayHello();
👉 输出问候语
封装是将数据(属性)和操作数据的方法(函数)捆绑在一起,并对数据的访问进行限制。C++通过访问修饰符 `public`, `protected`, `private` 实现封装。
任何地方都可以访问(类内部、派生类、类外部)。通常用于类的接口。
void setAge(int a);
int getAge() const;
只能被该类的成员函数和其派生类的成员函数访问。
std::string internalID;
只能被该类的成员函数访问。提供最高级别的数据保护。
double salary;
void calculateBonus();
继承允许一个类(派生类/子类)获得另一个类(基类/父类)的属性和方法。这促进了代码重用,并建立了类之间的 "is-a" 关系(例如,`Student` is a `Person`)。
public: string name;
public: void sayHello();
protected: int age;
// 继承自 Person
public: string studentID;
public: void study();
// 可以访问 Person 的 public 和 protected 成员
// 1. 公有继承 (public inheritance): is-a 关系
// 基类的 public -> 派生类的 public
// 基类的 protected -> 派生类的 protected
// 基类的 private -> 派生类不可访问
class Student : public Person { /* ... */ };
// 2. 保护继承 (protected inheritance):
// 基类的 public, protected -> 派生类的 protected
// 基类的 private -> 派生类不可访问
class SpecialStudent : protected Person { /* ... */ };
// 3. 私有继承 (private inheritance): has-a / implemented-in-terms-of
// 基类的 public, protected -> 派生类的 private
// 基类的 private -> 派生类不可访问
class Worker : private Person { /* ... */ };
// 4. 多重继承 (multiple inheritance):
// 一个类可以从多个基类继承
class TeachingAssistant : public Student, public Worker { /* ... */ };
// 注意:可能导致菱形继承问题 (Dreaded Diamond Problem)
// 5. 虚继承 (virtual inheritance):
// 用于解决菱形继承问题,确保共享基类只有一个实例
class A { public: int val; };
class B : virtual public A { /* ... */ };
class C : virtual public A { /* ... */ };
class D : public B, public C { /* D 中只有一份 A::val */ };
多态(Polymorphism)意为“多种形态”,允许我们使用基类的指针或引用来调用派生类中重写的方法。C++ 主要通过 **虚函数 (virtual functions)** 和 **动态绑定 (dynamic binding)** 来实现运行时多态。
Shape* ptr;
ptr = new Circle();
// 基类指针指向派生类对象 ptr->draw();
// 调用哪个 draw() ?
点击按钮查看效果
抽象是关注对象的关键特征而忽略次要细节的过程。在 C++ 中,抽象通常通过**抽象类 (Abstract Classes)** 和**纯虚函数 (Pure Virtual Functions)** 来实现,用于定义接口规范。
包含至少一个纯虚函数的类是抽象类。抽象类**不能被实例化**,它定义了一个契约,要求派生类必须实现这些纯虚函数。
// 定义图形接口
class IShape {
public:
// 纯虚函数 (Pure Virtual Function)
// 没有函数体,赋值为 0
virtual void draw() const = 0;
virtual double area() const = 0;
// 虚析构函数(好习惯,即使是空的)
// 确保通过基类指针删除派生类对象时,派生类的析构函数被调用
virtual ~IShape() { std::cout << "IShape destroyed\n"; }
};
派生类必须实现基类中所有的纯虚函数,才能成为具体类并被实例化。
#include // For M_PI
class Circle : public IShape {
private:
double radius;
std::string color;
public:
Circle(double r, std::string c) : radius(r), color(c) {}
// 实现纯虚函数 draw()
void draw() const override {
std::cout << "Drawing a " << color << " circle with radius " << radius << std::endl;
}
// 实现纯虚函数 area()
double area() const override {
return M_PI * radius * radius;
}
~Circle() override { std::cout << "Circle destroyed\n"; }
};
正在加载题目...
尝试根据描述思考如何实现,然后点击查看参考答案。
假设已有 `Person` 类(包含 `name`, `age` 和虚函数 `virtual void printInfo()`)。请定义一个 `Student` 类,继承自 `Person`,添加 `studentID` 成员。重写 `printInfo()` 方法,使其能打印学生特有的信息(包括学号)。编写测试代码,使用 `Person*` 指针指向 `Student` 对象,并调用 `printInfo()` 来验证多态。
设计一个抽象基类 `Logger`,它包含一个纯虚函数 `virtual void log(const std::string& message) = 0;`。然后,实现两个具体的日志记录器类:`ConsoleLogger`(将消息打印到控制台)和 `FileLogger`(将消息写入指定文件)。编写测试代码,演示如何使用 `Logger*` 指针来调用不同日志记录器的 `log` 方法。
在使用 C++ 面向对象特性时,需要留意一些常见问题:
当把派生类对象**按值**赋给基类对象或按值传递给期望基类对象的函数时,派生类特有的部分(成员变量和方法)会被“切掉”,只保留基类部分。这会丢失多态行为。
class Base { public: virtual void func() { /* ... */ } };
class Derived : public Base { public: int extra; void func() override { /* ... */ } };
Derived d;
Base b = d; // 对象切片发生!b 只包含 Base 部分,d.extra 被丢失
b.func(); // 调用的是 Base::func(),而不是 Derived::func()
// 正确做法:使用指针或引用
Base* b_ptr = &d;
Base& b_ref = d;
b_ptr->func(); // 正确调用 Derived::func()
b_ref.func(); // 正确调用 Derived::func()
避免方法: 始终通过基类的指针或引用来操作派生类对象,以保持多态性。
继承类型 (`public`, `protected`, `private`) 会影响基类成员在派生类中的访问权限。`private` 成员永远不能被派生类直接访问。选择合适的继承类型很重要。
使用 `using` 声明可以在 `protected` 或 `private` 继承时,有选择地将基类的某些 `public` 或 `protected` 成员提升到派生类的 `public` 或 `protected` 域中。
多重继承可能导致“菱形继承”问题:一个类通过两条或多条路径继承自同一个间接基类,导致该基类的成员在最终派生类中出现多份副本,引发二义性。
解决方案: 在中间基类继承共同的间接基类时使用 `virtual` 关键字进行**虚继承**。这确保最终派生类中只有一份共享基类的实例。
class A { /* ... */ };
class B : virtual public A { /* ... */ }; // B 虚继承 A
class C : virtual public A { /* ... */ }; // C 虚继承 A
class D : public B, public C { /* ... */ }; // D 中只有一份 A 的子对象
虚继承会带来一些额外的运行时开销(通常通过虚基类表指针实现),应在确实需要解决菱形继承问题时使用。
掌握了 OOP 的基础特征后,可以进一步探索以下相关领域:
一套面向对象设计的指导原则,旨在使软件设计更易于理解、灵活和维护:
遵循 SOLID 原则有助于编写高质量的面向对象代码。
在特定情境下解决常见软件设计问题的可复用解决方案。许多设计模式都基于 OOP 特征实现:
统一建模语言 (UML) 中的一种图形表示法,用于描述系统的静态结构,包括类、接口、它们的属性、方法以及它们之间的关系(如继承、关联、聚合、组合)。学习阅读和绘制类图有助于理解和设计复杂的面向对象系统。
类图能清晰地展示封装、继承、关联等关系,是 OOP 设计沟通的重要工具。
本次学习我们回顾了 C++ 面向对象编程的核心概念:
将数据和操作数据的方法捆绑在一起,并通过访问修饰符 (`public`, `protected`, `private`) 控制访问权限,实现数据隐藏和接口分离。
允许一个类(子类)继承另一个类(父类)的属性和方法,实现代码重用和类层次结构。"is-a" 关系。注意继承类型和菱形继承问题。
“多种形态”,允许使用基类指针/引用调用派生类重写的方法。主要通过虚函数和动态绑定实现运行时多态。注意对象切片和虚析构函数。
关注对象的关键特征,忽略次要细节。通过抽象类和纯虚函数定义接口规范,强制派生类实现特定功能,实现 “接口与实现分离”。