跳到主要内容

C++ 基类与派生类

引言

面向对象编程的三大核心特性是封装、继承和多态。其中继承是实现代码重用和构建类层次结构的重要机制。在C++中,通过基类(父类)和派生类(子类)的概念来实现继承关系,这使得我们可以创建一个从一般到特殊的类层次结构,增强代码的可维护性和可扩展性。

本文将详细介绍C++中的基类和派生类的概念、语法以及使用方法,帮助你掌握这一重要的面向对象编程特性。

基类与派生类的基本概念

什么是基类和派生类?

  • 基类(Base Class):也称为父类或超类,是被继承的类。基类包含了可以被其他类继承的属性和方法。
  • 派生类(Derived Class):也称为子类,是从基类继承属性和方法的类。派生类不仅继承了基类的特性,还可以添加自己的特性或修改继承的特性。

继承的语法

在C++中,定义派生类的基本语法如下:

cpp
class 基类名 {
// 基类成员...
};

class 派生类名 : 继承方式 基类名 {
// 派生类成员...
};

其中,继承方式可以是:

  • public:基类的公有成员成为派生类的公有成员,保护成员保持保护属性
  • protected:基类的公有和保护成员都成为派生类的保护成员
  • private:基类的公有和保护成员都成为派生类的私有成员

继承方式与访问控制

公有继承(Public Inheritance)

公有继承是最常用的继承方式,表示"是一种"(is-a)关系。

cpp
#include <iostream>
using namespace std;

class Animal {
public:
void eat() {
cout << "动物在进食" << endl;
}
protected:
int age;
private:
string name;
};

class Dog : public Animal {
public:
void bark() {
cout << "狗在汪汪叫" << endl;
cout << "年龄: " << age << endl; // 可以访问基类的protected成员
// cout << name << endl; // 错误:不能访问基类的private成员
}
};

int main() {
Dog myDog;
myDog.eat(); // 可以访问继承自基类的公有方法
myDog.bark(); // 可以访问派生类自己的方法
// myDog.age = 5; // 错误:不能从外部访问protected成员
return 0;
}

输出:

动物在进食
狗在汪汪叫
年龄: 0

保护继承(Protected Inheritance)

保护继承使基类的公有成员在派生类中变为保护成员,这意味着它们不能被派生类的对象直接访问。

cpp
class Bird : protected Animal {
public:
void fly() {
cout << "鸟在飞翔" << endl;
eat(); // 可以访问继承自基类的方法(现在是protected)
}
};

int main() {
Bird myBird;
// myBird.eat(); // 错误:基类的public方法在派生类中变为protected
myBird.fly(); // 正确
return 0;
}

私有继承(Private Inheritance)

私有继承使基类的所有成员在派生类中都变为私有成员。

cpp
class Fish : private Animal {
public:
void swim() {
cout << "鱼在游泳" << endl;
eat(); // 可以访问继承自基类的方法(现在是private)
}
};

int main() {
Fish myFish;
// myFish.eat(); // 错误:基类的public方法在派生类中变为private
myFish.swim(); // 正确
return 0;
}
备注

大多数情况下,我们会使用公有继承,因为它最符合面向对象设计中的"是一种"关系。保护继承和私有继承在特定场景下有用,但使用频率较低。

构造函数和析构函数

派生类对象的构造与析构顺序

当创建一个派生类对象时:

  1. 先调用基类的构造函数
  2. 然后调用派生类的构造函数

当销毁一个派生类对象时:

  1. 先调用派生类的析构函数
  2. 然后调用基类的析构函数

这个顺序与构造顺序相反。

cpp
#include <iostream>
using namespace std;

class Base {
public:
Base() {
cout << "基类构造函数" << endl;
}
~Base() {
cout << "基类析构函数" << endl;
}
};

class Derived : public Base {
public:
Derived() {
cout << "派生类构造函数" << endl;
}
~Derived() {
cout << "派生类析构函数" << endl;
}
};

int main() {
cout << "创建派生类对象..." << endl;
Derived d;
cout << "程序结束..." << endl;
return 0;
}

输出:

创建派生类对象...
基类构造函数
派生类构造函数
程序结束...
派生类析构函数
基类析构函数

派生类构造函数初始化列表

派生类可以通过初始化列表来调用基类的特定构造函数:

cpp
#include <iostream>
using namespace std;

class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
cout << "Person构造函数" << endl;
}
void introduce() {
cout << "我是 " << name << ",今年 " << age << " 岁。" << endl;
}
};

class Student : public Person {
private:
string school;
public:
// 通过初始化列表调用基类构造函数
Student(string n, int a, string s) : Person(n, a), school(s) {
cout << "Student构造函数" << endl;
}
void study() {
cout << name << " 在 " << school << " 学习" << endl;
}
};

int main() {
Student s("小明", 15, "第一中学");
s.introduce();
s.study();
return 0;
}

输出:

Person构造函数
Student构造函数
我是 小明,今年 15 岁。
小明 在 第一中学 学习

继承中的方法覆盖(重写)

派生类可以重写(override)基类中的方法,提供特定于派生类的实现。

cpp
#include <iostream>
using namespace std;

class Shape {
public:
void draw() {
cout << "绘制形状" << endl;
}
};

class Circle : public Shape {
public:
// 重写基类的draw方法
void draw() {
cout << "绘制圆形" << endl;
}
};

class Rectangle : public Shape {
public:
// 重写基类的draw方法
void draw() {
cout << "绘制矩形" << endl;
}
};

int main() {
Shape shape;
Circle circle;
Rectangle rectangle;

shape.draw(); // 调用Shape::draw()
circle.draw(); // 调用Circle::draw()
rectangle.draw(); // 调用Rectangle::draw()

return 0;
}

输出:

绘制形状
绘制圆形
绘制矩形
警告

在C++中,普通方法的覆盖并不需要特殊的关键字。但为了代码清晰,C++11引入了override关键字,可以明确表示一个方法是对基类方法的重写,如果不是重写会导致编译错误。

cpp
class Circle : public Shape {
public:
void draw() override { // 使用override关键字
cout << "绘制圆形" << endl;
}
};

基类指针和引用

C++中,可以使用基类的指针或引用来引用派生类对象,这是实现多态的基础。

cpp
#include <iostream>
using namespace std;

class Animal {
public:
void speak() {
cout << "动物在发声" << endl;
}
};

class Dog : public Animal {
public:
void speak() {
cout << "汪汪!" << endl;
}
};

class Cat : public Animal {
public:
void speak() {
cout << "喵喵!" << endl;
}
};

int main() {
Dog dog;
Cat cat;

// 基类引用引用派生类对象
Animal& animalRef1 = dog;
Animal& animalRef2 = cat;

// 基类指针指向派生类对象
Animal* animalPtr1 = &dog;
Animal* animalPtr2 = &cat;

// 使用基类引用调用方法
animalRef1.speak(); // 动物在发声(非虚函数)
animalRef2.speak(); // 动物在发声(非虚函数)

// 使用基类指针调用方法
animalPtr1->speak(); // 动物在发声(非虚函数)
animalPtr2->speak(); // 动物在发声(非虚函数)

return 0;
}

输出:

动物在发声
动物在发声
动物在发声
动物在发声
注意

注意,上面的例子中没有实现多态,因为speak()不是虚函数。要实现多态,需要在基类方法前加上virtual关键字。这部分内容将在"C++虚函数与多态"章节详细介绍。

实际应用案例:图形绘制系统

下面是一个简单的图形绘制系统示例,展示了基类和派生类在实际应用中的作用:

cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 基类:图形
class Shape {
protected:
string color;
public:
Shape(string c) : color(c) {}

// 设置颜色
void setColor(string c) {
color = c;
}

// 获取颜色
string getColor() {
return color;
}

// 计算面积的虚函数(将在派生类中实现)
virtual double area() = 0;

// 绘制图形的虚函数(将在派生类中实现)
virtual void draw() = 0;
};

// 派生类:圆形
class Circle : public Shape {
private:
double radius;
public:
Circle(double r, string c) : Shape(c), radius(r) {}

double area() override {
return 3.14159 * radius * radius;
}

void draw() override {
cout << "绘制" << color << "圆形,半径为" << radius << endl;
}
};

// 派生类:矩形
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h, string c) : Shape(c), width(w), height(h) {}

double area() override {
return width * height;
}

void draw() override {
cout << "绘制" << color << "矩形,宽为" << width << ",高为" << height << endl;
}
};

// 派生类:三角形
class Triangle : public Shape {
private:
double base;
double height;
public:
Triangle(double b, double h, string c) : Shape(c), base(b), height(h) {}

double area() override {
return 0.5 * base * height;
}

void draw() override {
cout << "绘制" << color << "三角形,底为" << base << ",高为" << height << endl;
}
};

int main() {
// 创建不同类型的图形
Circle circle(5.0, "红色");
Rectangle rectangle(4.0, 6.0, "蓝色");
Triangle triangle(3.0, 4.0, "绿色");

// 使用容器存储不同类型的图形(使用基类指针)
vector<Shape*> shapes;
shapes.push_back(&circle);
shapes.push_back(&rectangle);
shapes.push_back(&triangle);

// 绘制所有图形并计算面积
cout << "图形绘制:" << endl;
for (auto shape : shapes) {
shape->draw();
cout << "面积: " << shape->area() << endl;
cout << endl;
}

return 0;
}

输出:

图形绘制:
绘制红色圆形,半径为5
面积: 78.5398

绘制蓝色矩形,宽为4,高为6
面积: 24

绘制绿色三角形,底为3,高为4
面积: 6

这个例子展示了:

  1. 使用抽象基类定义共同接口
  2. 派生类实现特定功能
  3. 使用基类指针实现多态
  4. 通过继承实现代码重用

总结

本文详细介绍了C++中基类和派生类的概念及其实现。主要内容包括:

  1. 基类和派生类的基本概念和语法
  2. 三种继承方式及其访问控制
  3. 构造函数和析构函数的调用顺序
  4. 派生类中方法的覆盖
  5. 基类指针和引用的使用
  6. 实际应用案例

通过继承机制,我们可以建立类的层次结构,实现代码重用,为多态提供基础。掌握好基类和派生类的使用,是面向对象编程的重要一步。

练习

  1. 创建一个Vehicle基类,包含属性如brandyear,以及方法如start()stop()。然后创建派生类CarMotorcycle,添加特定属性和方法。
  2. 实现一个员工管理系统,创建Employee基类,然后派生出ManagerEngineerSalesperson类。每个类都应有适当的属性和方法。
  3. 修改上面的图形绘制系统,添加一个新的图形类型Ellipse(椭圆)。

进一步学习资源

  • 《C++ Primer》中关于继承的章节
  • 《Effective C++》中关于继承与面向对象设计的条款
  • 在线C++参考: cppreference.com中的继承相关内容
提示

继承是强大的工具,但不应过度使用。请记住"组合优于继承"的设计原则,在适当的场景选择继承或组合。