目录
3.2. 保护继承(Protected Inheritance)
3.3. 私有继承(Private Inheritance)
4.8. 覆盖(Overriding)和隐藏(Hiding)
C++中的继承特性是面向对象编程中非常重要的概念,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,从而产生新的类,即派生类。继承是代码复用的一种重要手段,也是实现多态性的基础。
1. 继承的定义
- 继承是类与类之间的关系,允许一个类(派生类)继承另一个类(基类)的属性和方法。
- 基类:被继承的类,也称为父类或超类。
- 派生类:继承自基类的类,也称为子类或继承类。
2. 继承的语法
继承的语法主要通过在派生类定义时,使用冒号:
后跟基类名和继承类型(如果未指定继承类型,则默认为私有继承)来指定。
class Base {
// 基类成员
};
class Derived : public Base { // 公有继承
// 派生类成员
};
3. 继承的类型
继承的类型主要指的是子类(派生类)如何继承基类(父类)的公有成员(public members)、保护成员(protected members)和私有成员(private members)。然而,需要注意的是,继承类型(如公有继承、保护继承和私有继承)实际上是指定了基类成员在派生类中的访问权限,而不是指基类成员本身的类型。基类成员的类型(如int、float、自定义类等)在继承过程中保持不变。
下面再次明确C++中的三种继承类型。
3.1. 公有继承(Public Inheritance)
- 公有继承是最常用的继承方式,基类的公有成员和保护成员在派生类中保持其原有的访问级别(公有成员仍为公有,保护成员仍为保护)。但是,基类的私有成员在派生类中仍然是不可见的,即派生类无法直接访问基类的私有成员。
- 公有继承通常用于表示“是一个”的关系,即派生类是基类的一个特殊类型。例如,
Dog
类是Animal
类的一个公有派生类,表示“狗是一种动物”。 -
语法示例:
class Base {
public:
void publicFunction() { /* ... */ }
protected:
void protectedFunction() { /* ... */ }
private:
void privateFunction() { /* ... */ }
};
class Derived : public Base {
// Derived类可以访问Base类的publicFunction()和protectedFunction(),但不能访问privateFunction()
};
3.2. 保护继承(Protected Inheritance)
- 在保护继承中,基类的公有成员和保护成员在派生类中都被视为保护成员。这意味着这些成员在派生类内部是可访问的,但在派生类的外部(包括派生类的派生类)是不可访问的。
- 保护继承的用途相对较少,主要用于特定的设计场景,其中子类需要访问基类的受保护成员,但又不希望这些成员在更远的派生类中被访问。
- 语法示例:
class Base {
public:
void publicFunction() { /* ... */ }
protected:
void protectedFunction() { /* ... */ }
private:
void privateFunction() { /* ... */ }
};
class Derived : protected Base {
// Derived类内部可以访问Base类的publicFunction()和protectedFunction(),但它们被视为protected
// 在Derived类的外部,这些函数都是不可访问的
};
3.3. 私有继承(Private Inheritance)
- 在私有继承中,基类的公有成员和保护成员在派生类中都被视为私有成员。这意味着这些成员在派生类内部是可访问的,但在派生类的外部(包括派生类的成员函数、派生类的派生类等)都是不可访问的。
- 私有继承主要用于实现接口继承的场合(尽管这不是C++推荐的做法,因为C++有更明确的接口概念,如纯虚函数和抽象类)。然而,在实际应用中,私有继承更多地被用作一种实现技术,用于组合或嵌入一个类的实例到另一个类中,而不是用于表示“是一个”的关系。
- 语法示例:
class Base {
public:
void publicFunction() { /* ... */ }
protected:
void protectedFunction() { /* ... */ }
private:
void privateFunction() { /* ... */ }
};
class Derived : private Base {
// Derived类内部可以访问Base类的publicFunction()和protectedFunction(),但它们被视为private
// 在Derived类的外部,这些函数都是不可访问的
};
注意事项
- 在设计类继承关系时,应谨慎选择继承方式,以确保类成员的正确访问权限和程序的合理结构。
- 公有继承通常用于表示“是一个”(is-a)的关系,即派生类是基类的一个特殊类型。
- 保护继承和私有继承的用途相对较少,它们主要用于特定的设计场景,如实现特定的封装层次或控制访问权限。
- 继承也涉及到构造函数、析构函数、拷贝构造函数、赋值运算符等的特殊处理,这些都需要在设计派生类时予以考虑。
4. 继承的特性
继承的特性主要体现在以下几个方面:
4.1. 代码复用
- 继承允许派生类直接继承基类的属性和方法,从而避免了代码的重复编写。这不仅可以减少代码量,还可以提高代码的可维护性和可重用性。
4.2. 扩展性
- 通过继承,可以在不修改基类代码的情况下,为基类添加新的功能或特性。这通常是通过在派生类中定义新的成员(包括数据成员和成员函数)或重写基类的虚函数来实现的。
4.3. 多态性
- 继承与多态性紧密相关。通过公有继承,派生类对象可以被视为基类对象(向上转型),并通过基类指针或引用来调用派生类中的函数(如果该函数在基类中为虚函数)。这种能力使得程序更加灵活和强大,能够处理不同类型的对象而无需编写大量的条件判断语句。
4.4. 访问权限控制
- C++中的继承支持三种访问权限:公有继承(public inheritance)、保护继承(protected inheritance)和私有继承(private inheritance)。这些访问权限决定了基类成员在派生类中的可见性和可访问性。
4.5. 层次结构
- 继承可以表达类之间的层次关系,即“是一个”(is-a)的关系。这种层次结构有助于理解和组织复杂的系统,使得系统的各个部分之间关系清晰、易于管理。
4.6. 构造函数和析构函数
- 在继承中,派生类继承基类的成员,但不继承构造函数和析构函数。派生类必须定义自己的构造函数,但可以通过成员初始化列表调用基类的构造函数。析构函数也是同样的情况,派生类的析构函数会在基类的析构函数之前被调用(析构顺序与构造顺序相反)。
4.7. 继承构造函数(C++11及以后)
- 从C++11开始,派生类可以使用
using
声明来继承基类的构造函数,但这仅适用于默认、复制和移动构造函数。
4.8. 覆盖(Overriding)和隐藏(Hiding)
- 派生类可以定义与基类同名的成员函数,这称为覆盖(对于虚函数)或隐藏(对于非虚函数)。覆盖是面向对象多态性的基础,而隐藏则可能导致意外的行为。
4.9. 作用域和隐藏
- 在继承体系中,基类和派生类都有独立的作用域。如果子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况称为隐藏或重定义。
4.10. 静态成员
- 基类的静态成员被所有的派生类共享,无论是哪个派生类的实例,访问的都是同一份静态成员。
4.11. 对象赋值
- 派生类对象可以赋值给基类对象(或基类的指针、引用),这被称为切片或切割,但基类对象不能赋值给派生类对象。
4. 继承的用途
继承的用途广泛,主要包括以下几个方面:
4.1. 减少代码冗余
- 通过继承,可以重用基类中已经定义好的属性和方法,避免在多个派生类中重复编写相同的代码。
4.2. 实现接口
- 在C++中,虽然没有像Java或C#那样的显式接口概念,但可以通过继承纯虚基类(即只包含纯虚函数的类)来模拟接口。派生类通过继承这样的纯虚基类并实现其中的纯虚函数来提供具体的实现。这种方式有助于定义一组规范,确保所有实现该接口的类都遵循相同的接口规范。
4.3. 实现多态
- 继承是实现多态性的基础。通过继承,可以定义一组具有共同特征的类,并通过基类指针或引用来调用派生类中的函数,实现运行时多态。
4.4. 组织类结构
- 继承有助于组织类的结构,使得类的设计更加清晰和合理。通过继承,可以将具有共同属性和方法的类组织在一起,形成类的层次结构。
4.5. 实现模板和泛型编程
- 虽然继承与模板和泛型编程在概念上有所不同,但在某些情况下,继承可以用于实现模板或泛型编程中的某些特性。例如,可以通过继承来定义一组具有共同特征的类,并使用模板来编写能够处理这些类的通用代码。
4.6. 类的扩展
- 通过继承,可以在不修改基类代码的情况下,为基类添加新的功能或特性。这通常是通过在派生类中定义新的成员(包括数据成员和成员函数)或重写基类的虚函数来实现的。这种扩展方式使得类的设计更加灵活和可扩展。
4. 7. 类的层次结构
- 继承可以表达类之间的层次关系,即“是一个”(is-a)的关系。例如,一个“汽车”类可以作为一个基类,而“轿车”、“卡车”等类可以作为派生类继承自“汽车”类。这种层次结构有助于理解和组织复杂的系统,使得系统的各个部分之间关系清晰、易于管理。
注意事项
- 继承并非总是最佳的设计选择。在决定使用继承之前,应该仔细考虑是否有其他更合适的设计模式(如组合、代理等)可以实现相同的功能。
- 过度使用继承可能会导致类层次结构过于复杂,难以理解和维护。因此,在设计类继承关系时应该保持简洁和清晰。
- 在使用继承时,应该注意基类成员的访问权限,确保派生类能够正确地访问和使用这些成员。同时,也应该注意派生类对基类成员的隐藏和覆盖问题。
5. 使用示例
下面是一个简单的C++继承使用示例,展示了如何定义一个基类(Vehicle
)和一个继承自该基类的派生类(Car
)。
5.1. 基类定义(Vehicle)
#include <iostream>
#include <string>
// 定义基类Vehicle
class Vehicle {
protected:
std::string brand; // 保护成员,派生类可以访问
public:
Vehicle(std::string b) : brand(b) {} // 构造函数
void display() {
std::cout << "Brand: " << brand << std::endl;
}
// 虚析构函数,确保删除指向派生类对象的基类指针时能够正确析构
virtual ~Vehicle() {}
};
5.2. 派生类定义(Car)
// 定义派生类Car,继承自Vehicle
class Car : public Vehicle {
private:
int wheels; // 派生类特有的成员
public:
Car(std::string b, int w) : Vehicle(b), wheels(w) {} // 派生类构造函数
void display() override { // 重写display方法
Vehicle::display(); // 调用基类的display方法
std::cout << "Wheels: " << wheels << std::endl;
}
};
5.3. 使用示例
int main() {
Car myCar("Toyota", 4);
myCar.display(); // 输出:Brand: Toyota 和 Wheels: 4
return 0;
}
在这个示例中,Car
类继承自Vehicle
类。Car
类有一个自己的成员变量wheels
,并且重写了Vehicle
类的display
方法,以便在显示品牌信息的同时也显示轮子数量。注意,Vehicle
类中的display
方法被声明为virtual
,这是为了确保当通过基类指针或引用来调用display
时,能够执行到派生类中的重写版本(多态性)。此外,基类中的析构函数也被声明为virtual
,这是处理通过基类指针删除派生类对象时的一个重要安全措施,以防止资源泄露。
通过继承,我们可以构建出更加复杂、功能丰富的类体系,同时保持代码的复用性和可维护性。