文章目录
前言:
一、继承
1、概念
- 基类与派生类: 基类是一个已经定义好的类,包含了一些属性和行为。派生类是在基类的基础上进行扩展,可以添加新的属性和行为,或者重写基类的方法。
- 派生类的定义: 在C++中,使用
:
运算符来定义一个派生类。
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
};
示例代码:
#include <iostream>
#include <string>
// 基类 Animal
class Animal {
public:
// 基类的构造函数
Animal(const std::string& name) : name_(name) {}
// 基类的成员函数
void eat() const {
std::cout << name_ << " is eating." << std::endl;
}
// 基类的虚函数,用于后续的多态性
virtual void sound() const {
std::cout << "Some generic animal sound." << std::endl;
}
// 基类的析构函数(虚析构函数是良好的实践,尤其是当基类有虚函数时)
virtual ~Animal() {}
private:
std::string name_; // 基类的私有数据成员
};
// 派生类 Dog,继承自 Animal
class Dog : public Animal {
public:
// 派生类的构造函数,调用基类的构造函数
Dog(const std::string& name, const std::string& breed)
: Animal(name), breed_(breed) {}
// 重写基类的虚函数
void sound() const override {
std::cout << name_ << " barks." << std::endl;
}
// 派生类特有的成员函数
void fetch() const {
std::cout << name_ << " is fetching the ball." << std::endl;
}
private:
std::string breed_; // 派生类的私有数据成员
};
int main() {
// 创建 Dog 对象
Dog myDog("Buddy", "Golden Retriever");
// 调用派生类的成员函数
myDog.fetch();
// 调用基类的成员函数(通过派生类对象)
myDog.eat();
// 调用重写后的虚函数
myDog.sound();
// 基类的虚析构函数确保当派生类对象被销毁时,基类部分也被正确清理
return 0;
}
2、继承类型
- 公有继承: 在这种类型的继承中,基类的公有成员在派生类中仍然是公有的,而基类的保护成员在派生类中仍然是保护的。这是最常用的继承类型。
- 私有继承: 当使用私有继承时,基类的所有公有成员和保护成员在派生类中都会变成私有的。这意味着派生类的对象不能直接访问这些成员,但派生类内部可以访问。
- 保护继承: 保护继承与私有继承类似,但基类的公有成员在派生类中会变成保护的。这意味着派生类的对象不能直接访问这些成员,但派生类及其派生类可以访问。
3、继承中的构造函数与析构函数
3.1、构造函数
- 创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。
- 派生类的构造函数,如果没有主动调用基类的构造函数,当创建派生类对象时,编译器会自动调用基类的默认构造函数。
- 可以在派生类的初始化列表中调用基类的构造函数。
示例代码:
#include "stdafx.h"
using namespace std;
class Base
{
public:
Base(int age) {
m_age = age;
}
private:
int m_age;
};
class Derived : public Base
{
public:
Derived(int age, int grade) : Base(age) {
m_grade = grade;
}
private:
int m_grade;
};
int main()
{
return 0;
}
3.2、析构函数
- 派生类对象声明周期结束时,先调用基类的析构函数再调用派生类的析构函数。这种顺序确保了对象从派生类到基类的逐层销毁。
示例代码:
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor called" << std::endl; }
virtual ~Base() { std::cout << "Base destructor called" << std::endl; } // 虚析构函数
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor called" << std::endl; }
~Derived() { std::cout << "Derived destructor called" << std::endl; }
};
int main() {
Base* basePtr = new Derived(); // 多态:基类指针指向派生类对象
delete basePtr; // 调用的是 Derived 类的析构函数,然后是 Base 类的析构函数
return 0;
}
4、成员隐藏
class Base {
public:
void func() { std::cout << "Base func" << std::endl; }
};
class Derived : public Base {
public:
void func() { std::cout << "Derived func" << std::endl; }
};
int main() {
Derived d;
d.func(); // 输出 "Derived func"
return 0;
}
5、多重继承
5.1、定义
class Base1 {
public:
void func1() { /* ... */ }
};
class Base2 {
public:
void func2() { /* ... */ }
};
class Derived : public Base1, public Base2 {
public:
void func3() { /* ... */ }
};
5.2、多继承带来的问题
- 二义性问题: 如果多个基类有同名的成员函数或数据成员,子类在访问这些成员时可能会产生二义性。为了解决这个问题,C++提供了作用域解析运算符(
::
),允许子类明确指定要访问的基类成员。 - 菱形继承问题: 当两个基类都继承自同一个更基础的类,并且一个子类同时继承这两个基类时,就形成了菱形继承结构。这可能导致基类成员在子类中有多个副本,从而引发数据不一致的问题。为了解决这个问题,C++引入了虚继承的概念。
- 复杂性和维护难度: 多重继承增加了类的复杂性和维护难度。由于子类可能依赖于多个基类的实现细节,因此当基类发生变化时,子类可能也需要进行相应的修改。
示例:二义性问题
class Base
{
public:
void show() { cout << "I am Base class" << endl; }
};
class Person
{
public:
void show() { cout << "I am Person class" << endl; }
};
class Student : public Base, public Person
{
public:
void show()
{
Person::show();
}
};
int main()
{
Student obj;
obj.show(); // 输出结果 I am Person class
return 0;
}
5.3、虚继承
class Base {
public:
void func() { /* ... */ }
};
class Intermediate1 : virtual public Base {
// ...
};
class Intermediate2 : virtual public Base {
// ...
};
class Derived : public Intermediate1, public Intermediate2 {
// 在这里,Base的func()函数只会有一个实例被调用
};