在 C++ 的面向对象编程中,类的继承是一项极为重要的特性,它为代码的复用与拓展提供了强大的支持。通过继承,我们可以创建出具有层次结构的类,让新类能够自动获得已有类的属性和行为,并在此基础上进行扩展和修改。接下来,我们就深入探讨类的继承相关内容。
目录
一、确定类的层次
1.1 类层次的概念
类的层次结构就像是一棵家族树,顶层是基类(也称为父类),从基类派生出的类称为派生类(也称为子类)。派生类可以继承基类的成员变量和成员函数,并且可以添加自己特有的成员。合理确定类的层次结构,能够让代码的组织更加清晰,便于理解和维护。
1.2 如何确定类层次
在确定类层次时,需要从实际需求出发,找出具有共性的属性和行为,将它们抽象到基类中。例如,在一个图形绘制程序中,“图形” 可以作为基类,它包含一些通用的属性(如颜色)和行为(如绘制)。而 “圆形”“矩形” 等则可以作为派生类,它们继承 “图形” 基类的属性和行为,同时又有自己特有的属性(如圆形的半径、矩形的长和宽)和行为(如计算各自的面积)。
代码示例
// 基类:图形
class Shape {
protected:
std::string color;
public:
Shape(const std::string& c) : color(c) {}
virtual void draw() const {
std::cout << "绘制一个" << color << "的图形" << std::endl;
}
};
// 派生类:圆形
class Circle : public Shape {
private:
double radius;
public:
Circle(const std::string& c, double r) : Shape(c), radius(r) {}
void draw() const override {
std::cout << "绘制一个半径为" << radius << "的" << color << "圆形" << std::endl;
}
};
// 派生类:矩形
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(const std::string& c, double l, double w) : Shape(c), length(l), width(w) {}
void draw() const override {
std::cout << "绘制一个长为" << length << ",宽为" << width << "的" << color << "矩形" << std::endl;
}
};
在这个例子中,Shape
是基类,Circle
和Rectangle
是派生类。Circle
和Rectangle
继承了Shape
的color
属性和draw
函数,并且各自进行了扩展。
二、继承的类型
2.1 公有继承
公有继承是最常见的继承方式,其定义语法为:
class 派生类名 : public 基类名 {
// 派生类的成员
};
在公有继承中,基类的公有成员在派生类中仍然是公有成员,基类的保护成员在派生类中仍然是保护成员。基类的私有成员在派生类中不可直接访问。
公有继承的特点
- 派生类的对象可以直接访问从基类继承过来的公有成员。
- 派生类的成员函数可以访问从基类继承过来的公有和保护成员。
- 公有继承建立了 “是一种”(is-a)的关系,即派生类对象可以被视为基类对象使用。
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived : public Base {
public:
void accessMembers() {
publicMember = 10; // 合法,派生类成员函数可访问继承的公有成员
protectedMember = 20; // 合法,派生类成员函数可访问继承的保护成员
// privateMember = 30; // 错误,派生类不能直接访问基类的私有成员
}
};
int main() {
Derived d;
d.publicMember = 5; // 合法,派生类对象可访问继承的公有成员
// d.protectedMember = 6; // 错误,派生类对象不能直接访问继承的保护成员
return 0;
}
公有继承常用于实现 “是一种” 的关系,比如 “圆形是一种图形”,“汽车是一种交通工具” 等。在这种情况下,派生类完全继承基类的接口,并且可以扩展自己的功能,同时保持与基类的类型兼容性。例如,在一个多态应用中,可以通过基类指针或引用操作派生类对象。
2.2 私有继承
私有继承的定义语法为:
class 派生类名 : private 基类名 {
// 派生类的成员
};
在私有继承中,基类的公有成员和保护成员在派生类中都变为私有成员。基类的私有成员在派生类中仍然不可直接访问。
私有继承的特点
- 派生类的对象不能直接访问从基类继承过来的任何成员。
- 只有派生类的成员函数可以访问从基类继承过来的公有和保护成员。
- 私有继承建立了 “根据某种实现”(is-implemented-in-terms-of)的关系,派生类复用了基类的实现细节,但不继承其接口。
class Base {
public:
int publicMember;
protected:
int protectedMember;
};
class Derived : private Base {
public:
void accessMembers() {
publicMember = 10; // 合法,派生类成员函数可访问继承的公有和保护成员
protectedMember = 20;
}
};
int main() {
Derived d;
// d.publicMember = 5; // 错误,派生类对象不能访问继承的公有成员
// d.protectedMember = 6; // 错误,派生类对象不能访问继承的保护成员
return 0;
}
私有继承常用于实现 “根据某种实现” 的关系,即派生类只是利用基类的实现细节,而不希望对外暴露基类的接口。它更像是一种实现层面的复用,而不是类型的继承。例如,当一个类需要复用另一个类的某些功能,但又不想让用户看到基类的接口时,可以使用私有继承。
2.3 保护继承
保护继承的定义语法为:
class 派生类名 : protected 基类名 {
// 派生类的成员
};
保护继承的特点
- 派生类的对象不能直接访问从基类继承过来的任何成员。
- 派生类的成员函数可以访问从基类继承过来的公有和保护成员。
- 派生类的派生类(即孙子类)的成员函数可以访问从基类继承过来的公有和保护成员(这些成员在派生类中变为保护成员)
class Base {
public:
int publicMember;
protected:
int protectedMember;
};
class Derived : protected Base {
public:
void accessMembers() {
publicMember = 10; // 合法,派生类成员函数可访问继承的公有和保护成员
protectedMember = 20;
}
};
class GrandDerived : public Derived {
public:
void accessBaseMembers() {
publicMember = 30; // 合法,孙子类成员函数可访问继承的公有和保护成员
protectedMember = 40;
}
};
保护继承的使用场景相对较少,但在一些复杂的类层次结构中,当希望派生类的派生类能够访问基类的部分成员时,保护继承就可以发挥作用。例如,在一个多层继承的系统中,某些基类的成员需要对其直接派生类和间接派生类可见,但对外部不可见时,可以使用保护继承。
三、总结
类的继承是 C++ 面向对象编程中实现代码复用和拓展的重要手段。通过合理确定类的层次结构,以及选择合适的继承类型(公有继承、私有继承、保护继承),我们能够编写出结构清晰、可维护性强且功能丰富的程序。在实际编程中,需要根据具体的需求和设计目标来灵活运用类的继承特性,充分发挥其优势,让代码更加简洁高效。