一、继承性的概念
定义
在面向对象编程中,继承性是一种机制,它允许创建一个新的类(称为派生类或子类)从一个现有的类(称为基类或父类)获取成员变量和成员函数。简单来说,子类继承了父类的属性和行为,并且可以在此基础上添加新的特性或者修改从父类继承来的特性。
示例
以交通工具为例,假设Vehicle(交通工具)是一个基类,它有成员变量如speed(速度)和成员函数如move()(移动)。Car(汽车)类可以从Vehicle类继承,这样Car类就自动拥有了speed变量和move()函数,并且还可以添加自己特有的成员,如numberOfDoors(车门数量)和openDoor()(开门)函数。
二、C++ 中实现继承的方式
语法格式
在 C++ 中,通过在派生类定义时使用冒号:和关键字public(公有继承)、private(私有继承)或protected(保护继承)来指定继承方式,后面跟上基类名称。例如:
class Base {
// 基类的成员声明
};
class Derived : public Base {
// 派生类的成员声明
};
三种继承方式的区别
公有继承(public)
基类的公有成员在派生类中仍然是公有成员,基类的保护成员在派生类中仍然是保护成员,基类的私有成员不能被派生类直接访问。这种继承方式保持了基类接口的开放性,使得派生类对象可以像基类对象一样使用基类的公有成员函数。例如:
class Base {
public:
void publicFunction() {}
protected:
int protectedVariable;
private:
int privateVariable;
};
class Derived : public Base {
public:
void accessBaseMembers() {
publicFunction();
// 可以访问基类的公有成员函数
protectedVariable = 10;
// 可以访问基类的保护成员变量
// privateVariable = 20;
// 错误,不能访问基类的私有成员变量
}
};
私有继承(private)
基类的公有成员和保护成员在派生类中都变成私有成员。这意味着派生类对象不能直接访问基类的公有成员函数,外部代码通过派生类对象也无法访问基类的任何成员。例如:
class Base {
public:
void publicFunction() {}
protected:
int protectedVariable;
};
class Derived : private Base {
public:
void accessBaseMembers() {
publicFunction();
// 可以在派生类内部访问基类的公有成员函数,但在外部不可访问
protectedVariable = 10;
// 可以在派生类内部访问基类的保护成员变量,但在外部不可访问
}
};
int main() {
Derived d;
// d.publicFunction();
// 错误,私有继承后基类的公有成员在派生类外部不可访问
return 0;
}
保护继承(protected)
基类的公有成员和保护成员在派生类中都变成保护成员。这使得派生类的派生类(孙类)可以访问这些成员,但外部代码不能通过派生类对象访问基类的成员。例如:
class Base {
public:
void publicFunction() {}
protected:
int protectedVariable;
};
class Derived : protected Base {
public:
void accessBaseMembers() {
publicFunction();
// 可以在派生类内部访问基类的公有成员函数,但在外部不可访问
protectedVariable = 10;
// 可以在派生类内部访问基类的保护成员变量,但在外部不可访问
}
};
class GrandDerived : public Derived {
public:
void accessBaseMembersFurther() {
publicFunction();
// 可以访问,因为是保护继承,孙类可以访问基类的公有成员函数
protectedVariable = 20;
// 可以访问,因为是保护继承,孙类可以访问基类的保护成员变量
}
};
三、继承的好处
代码复用
可以避免重复编写相同的代码。例如,如果有多个类都具有一些相同的基本属性和行为,通过将这些共同的部分放在基类中,然后让其他类继承自这个基类,就可以减少代码量。像前面提到的交通工具的例子,不同类型的交通工具(汽车、自行车、飞机等)都有速度和移动的属性和行为,将这些放在Vehicle基类中,其他交通工具类继承后就不用再次定义这些基本的功能。
层次化设计和建模
能够更好地对现实世界中的对象关系进行建模。例如,在一个图形系统中,可以有一个基类Shape,它具有一些基本的属性如颜色、位置等,以及基本的行为如绘制函数draw()。然后不同的具体形状类(如Circle、Rectangle等)可以继承自Shape类,并且添加自己特有的属性(如Circle的半径)和行为(如根据半径来绘制圆的draw()函数实现)。这样的层次结构使得代码更加清晰,易于理解和维护。
可扩展性
方便对程序进行扩展。当需要添加新的功能或者新的类型时,只需要在派生类中进行添加即可。例如,在游戏开发中,如果有一个基类Character,代表游戏中的角色,具有基本的属性如生命值、攻击力等。当要添加一个新的角色类型,如Magician(魔法师),可以从Character类继承,然后添加魔法师特有的属性如魔法值、魔法技能等,而不需要修改原来的Character类以及其他非魔法师角色类的代码。
四、继承的注意事项
继承层次结构设计
设计继承层次结构时要谨慎,避免创建过于复杂或不合理的层次结构。如果层次结构太深或者太宽,可能会导致代码难以理解和维护。例如,不应该为了一点点差异就创建大量的子类,这样会使类的层次变得混乱。
基类和派生类的耦合性
继承会导致基类和派生类之间有一定的耦合性。当对基类进行修改时,可能会影响到派生类。例如,如果修改了基类的一个成员函数的接口(参数列表或返回值类型),那么所有派生类中重写或者调用这个函数的地方都可能需要修改。所以在设计基类时,要尽量考虑到其稳定性和通用性,避免频繁地修改基类接口。
继承中的函数重写(覆盖)规则
当派生类重写基类的函数时,函数的签名(函数名、参数列表、返回值类型等)需要满足一定的规则。在 C++ 中,函数签名要相同(协变返回类型除外),并且如果基类中的函数是虚函数,派生类中重写的函数也要是虚函数,以保证多态性的正确实现。否则可能会出现意想不到的结果。例如:
class Base {
public:
virtual void print() {
std::cout << "Base class print function" << std::endl;
}
};
class Derived : public Base {
public:
void print() {
std::cout << "Derived class print function" << std::endl;
}
};
在这个例子中,Derived类正确地重写了Base类的print函数,因为Base类中的print函数是虚函数,并且Derived类中的print函数签名和Base类中的相同,这样在使用多态性(通过基类指针或引用调用派生类对象的函数)时就可以正确地调用Derived类的print函数。