【C++入门】类的继承
@TOC
前言
C++ 是一种功能强大的编程语言,以其面向对象编程(OOP)特性而闻名。类的继承是 C++ 面向对象编程中的一个核心概念,它允许开发人员创建新的类,这些类可以继承现有类的属性和方法。通过继承,代码可以得到复用,结构更加清晰,从而提升软件开发的效率和可维护性。本篇文章将介绍 C++ 中类的继承,包括其定义、使用方法、修饰符及多继承的概念。
继承是什么?
继承是一种面向对象编程的机制,它允许一个类(子类或派生类)继承另一个类(基类或父类)的属性和方法。通过继承,子类可以复用基类的代码,同时还可以添加新的属性和方法,或者重写基类的方法以实现多态。
为什么需要继承?
- 代码重用:继承允许子类复用基类的代码,减少重复代码,提高开发效率。
- 提高可维护性:通过继承,可以更好地组织代码结构,使其更具层次性和可读性。
- 多态性:继承是实现多态的基础,多态允许一个接口多个实现,提高代码的灵活性和可扩展性。
在 C++ 中如何继承另一个类?
在 C++ 中,使用 :
符号来表示类的继承。语法如下:
class 基类 {
// 基类的属性和方法
};
class 子类 : 访问修饰符 基类 {
// 子类的属性和方法
};
访问修饰符
访问修饰符用于控制基类成员在子类中的可访问性。在继承时,C++ 提供了三种访问修饰符:public
、protected
和 private
。
- public 继承:基类的
public
和protected
成员在子类中保持其访问级别不变。 - protected 继承:基类的
public
和protected
成员在子类中都变为protected
。 - private 继承:基类的
public
和protected
成员在子类中都变为private
。
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PublicDerived : public Base {
// publicVar 是 public
// protectedVar 是 protected
// privateVar 不可访问
};
class ProtectedDerived : protected Base {
// publicVar 是 protected
// protectedVar 是 protected
// privateVar 不可访问
};
class PrivateDerived : private Base {
// publicVar 是 private
// protectedVar 是 private
// privateVar 不可访问
};
类的多继承
C++ 允许一个类从多个基类继承,即多继承。多继承可以让子类继承多个基类的属性和方法,但也可能带来复杂性,如命名冲突和菱形继承问题。
class Base1 {
public:
void show() {
std::cout << "Base1 show" << std::endl;
}
};
class Base2 {
public:
void show() {
std::cout << "Base2 show" << std::endl;
}
};
class Derived : public Base1, public Base2 {
public:
void show() {
Base1::show(); // 显式调用 Base1 的 show 方法
Base2::show(); // 显式调用 Base2 的 show 方法
}
};
菱形继承
菱形继承是多继承的一种特殊情况,即多个派生类继承同一个基类,并且又有一个类同时继承这些派生类。这种情况可能导致基类的成员在派生类中存在多个拷贝。为了解决这个问题,可以使用虚继承。
class Base {
public:
void show() {
std::cout << "Base show" << std::endl;
}
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Final : public Derived1, public Derived2 {
// Base 的成员在 Final 中只有一份拷贝
};
继承中的构造函数和析构函数
在继承关系中,构造函数和析构函数的调用顺序如下:
构造函数:先调用基类的构造函数,再调用子类的构造函数。
析构函数:先调用子类的析构函数,再调用基类的析构函数。
无参数的构造函数
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base constructor" << std::endl;
}
~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor" << std::endl;
}
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Derived obj;
return 0;
}
有参数的构造函数
在 C++ 中,当一个子类继承自一个带有参数的构造函数的父类(基类)时,子类必须在其构造函数的初始化列表中显式调用基类的构造函数,并传递适当的参数。这是因为基类的构造函数不会自动被调用,必须通过子类的构造函数显式指定。
以下是一个示例,展示了如何在子类的构造函数中调用带有参数的基类构造函数。
#include <iostream>
// 基类
class Base {
public:
// 带有参数的基类构造函数
Base(int x, int y) : a(x), b(y) {
std::cout << "Base constructor called with a = " << a << " and b = " << b << std::endl;
}
private:
int a, b;
};
// 派生类
class Derived : public Base {
public:
// 派生类构造函数,初始化列表中调用基类构造函数
Derived(int x, int y, int z) : Base(x, y), c(z) {
std::cout << "Derived constructor called with c = " << c << std::endl;
}
private:
int c;
};
int main() {
// 创建派生类对象
Derived obj(1, 2, 3);
return 0;
}
代码解释
-
基类 Base:
Base(int x, int y) : a(x), b(y)
是基类的构造函数,它接受两个整数参数x
和y
,并使用初始化列表将它们分别赋值给成员变量a
和b
。- 在构造函数中打印出
a
和b
的值。
-
派生类 Derived:
Derived(int x, int y, int z) : Base(x, y), c(z)
是派生类的构造函数,它接受三个整数参数x
、y
和z
。- 在派生类构造函数的初始化列表中,调用基类构造函数
Base(x, y)
,并将x
和y
传递给它。 - 同时在初始化列表中,将参数
z
赋值给派生类的成员变量c
。 - 在构造函数中打印出
c
的值。
-
main
函数:- 创建
Derived
类的对象obj
,并传递参数1
、2
和3
。这将调用派生类的构造函数,并依次调用基类的构造函数。
- 创建
输出结果
Base constructor called with a = 1 and b = 2
Derived constructor called with c = 3
继承的好处与问题
继承的好处
-
代码重用:
- 减少重复代码:继承允许子类复用基类的属性和方法,从而减少重复代码,提高开发效率。
- 易于维护:当需要修改通用行为时,只需在基类中进行修改,所有继承该基类的子类都会自动继承这些更改,减少了维护成本。
-
提高可读性和组织性:
- 结构化代码:通过继承,可以将通用功能提取到基类中,使代码更具层次性和组织性,提高代码的可读性。
- 逻辑清晰:继承关系可以清晰地表达类之间的关系和层次结构,使代码逻辑更加清晰。
-
实现多态性:
- 动态绑定:继承和虚函数结合使用,可以实现多态性,即在运行时根据对象的实际类型调用相应的方法,增强了程序的灵活性和可扩展性。
- 统一接口:通过继承,可以为不同的子类提供统一的接口,调用者无需关心对象的具体类型,只需调用统一的接口方法即可。
继承的潜在问题
-
过度继承:
- 复杂性增加:过度使用继承可能会导致复杂的继承层次结构,增加代码的复杂性和理解难度。
- 维护困难:复杂的继承关系会使代码的维护变得困难,尤其是在修改基类时,可能会影响到多个子类的行为。
-
紧耦合:
- 耦合度高:继承使子类和基类之间的耦合度增加,子类依赖于基类的实现,难以独立修改和扩展。
- 修改风险:基类的修改可能会对所有子类产生影响,增加了修改代码的风险。
-
命名冲突:
- 方法重名:在多继承情况下,不同基类中可能存在同名的方法,导致命名冲突,增加了解决冲突的复杂性。
- 数据成员冲突:如果基类中有同名的数据成员,也可能导致冲突,需特别小心处理。
-
菱形继承问题:
- 多重继承:多重继承时,若多个基类继承自同一个基类,可能会导致菱形继承问题,即子类中存在多个基类实例。
- 数据冗余:菱形继承可能导致数据冗余和多义性问题,需使用虚继承来解决。
示例代码
以下示例展示了继承的好处和潜在问题。
#include <iostream>
// 基类
class Base {
public:
Base(int x) : a(x) {
std::cout << "Base constructor called with a = " << a << std::endl;
}
virtual void show() {
std::cout << "Base show method" << std::endl;
}
private:
int a;
};
// 子类1
class Derived1 : public Base {
public:
Derived1(int x, int y) : Base(x), b(y) {
std::cout << "Derived1 constructor called with b = " << b << std::endl;
}
void show() override {
std::cout << "Derived1 show method" << std::endl;
}
private:
int b;
};
// 子类2
class Derived2 : public Base {
public:
Derived2(int x, int z) : Base(x), c(z) {
std::cout << "Derived2 constructor called with c = " << c << std::endl;
}
void show() override {
std::cout << "Derived2 show method" << std::endl;
}
private:
int c;
};
// 子类多继承
class MultiDerived : public Derived1, public Derived2 {
public:
MultiDerived(int x, int y, int z) : Derived1(x, y), Derived2(x, z) {
std::cout << "MultiDerived constructor called" << std::endl;
}
void show() {
Derived1::show(); // 调用 Derived1 的 show 方法
Derived2::show(); // 调用 Derived2 的 show 方法
}
};
int main() {
// 创建对象并调用方法
Derived1 d1(1, 2);
Derived2 d2(3, 4);
MultiDerived md(5, 6, 7);
d1.show(); // 调用 Derived1 的 show 方法
d2.show(); // 调用 Derived2 的 show 方法
md.show(); // 调用 MultiDerived 的 show 方法,分别调用 Derived1 和 Derived2 的 show 方法
return 0;
}
总结
继承是 C++ 面向对象编程中的一个重要特性,通过继承,开发人员可以创建层次分明、结构清晰的代码,从而提高代码的复用性和可维护性。通过合理使用访问修饰符,可以控制基类成员在子类中的可访问性,多继承则提供了更大的灵活性,但也需要注意其复杂性。掌握继承及其相关概念,对于提高 C++ 编程水平和开发效率至关重要。
- 点赞
- 收藏
- 关注作者
评论(0)