继承和派生知识点总结 C++程序设计与算法笔记总结(四) 北京大学 郭炜

继承和派生

在C++中,继承和派生是面向对象编程的两个重要概念,用于实现类与类之间的关系。
继承是指一个类可以从另一个类中继承属性和方法,并且可以在此基础上扩展出自己的属性和方法。被继承的类称为基类(父类),继承的类称为派生类(子类)。在C++中,可以通过以下方式定义一个派生类:

class DerivedClass : public BaseClass {
    // 派生类的成员变量和成员函数
};

在上面的示例中,DerivedClass是派生类,BaseClass是基类。关键字public表示使用公有继承,表示DerivedClass继承了BaseClass的所有public和protected成员,但不继承BaseClass的private成员。
派生类可以访问基类的public和protected成员,但不能访问基类的private成员。当派生类的成员变量或成员函数与基类的成员变量或成员函数同名时,可以使用作用域解析运算符::来指定调用哪个类的成员。
在派生类中,可以通过以下方式调用基类的构造函数:

class DerivedClass : public BaseClass {
public:
    DerivedClass(int x, int y, int z) : BaseClass(x, y), m_z(z) {}
private:
    int m_z;
};

在上面的示例中,调用了BaseClass的构造函数,并将参数x和y传递给它。

派生类中还可以重写(override)基类的成员函数,即在派生类中重新定义一个和基类相同名称、参数列表和返回类型的成员函数。在调用派生类的成员函数时,会优先调用派生类中的函数,如果派生类中没有定义相应的函数,则会调用基类的函数。

继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。

需要继承机制的例子

继承机制可以实现代码的复用和扩展,下面是一些需要继承机制的例子:

  1. 图形类的继承
    在图形类中,可以定义一个基类Shape,包括图形的公共属性和方法,如颜色、位置、面积、周长等。然后可以通过继承机制定义各种具体的图形类,如矩形类、圆形类、三角形类等。这样,可以避免在每个具体的图形类中都定义公共的属性和方法,提高了代码的复用性。
  2. 汽车类的继承
    在汽车类中,可以定义一个基类Vehicle,包括汽车的公共属性和方法,如品牌、型号、颜色、速度、加速度等。然后可以通过继承机制定义各种具体的汽车类,如轿车类、越野车类、卡车类等。这样,可以避免在每个具体的汽车类中都定义公共的属性和方法,提高了代码的复用性。
  3. 员工类的继承
    在员工类中,可以定义一个基类Employee,包括员工的公共属性和方法,如姓名、性别、年龄、职位、工资等。然后可以通过继承机制定义各种具体的员工类,如经理类、销售员类、工人类等。这样,可以避免在每个具体的员工类中都定义公共的属性和方法,提高了代码的复用性。

继承机制可以极大地提高代码的复用性和可维护性,同时也可以实现代码的扩展。在使用继承机制时,需要注意类之间的关系,选择适当的继承方式,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtqB4lzN-1687179456360)(2023-06-19-20-36-13.png)]

派生类的写法

在C++中,可以通过继承来创建派生类。派生类继承了基类的所有成员函数和成员变量,并且可以在此基础上扩展出自己的成员函数和成员变量。
派生类的定义方式如下:

// 基类
class BaseClass {
public:
    int m_varBase;
    void funcBase();
};
// 派生类
class DerivedClass : public BaseClass {
public:
    int m_varDerived;
    void funcDerived();
};

在上面的代码中,DerivedClass是派生类,BaseClass是基类。关键字public表示使用公有继承,表示DerivedClass继承了BaseClass的所有public和protected成员,但不继承BaseClass的private成员。
当派生类的成员变量或成员函数与基类的成员变量或成员函数同名时,可以使用作用域解析运算符::来指定调用哪个类的成员。例如,可以通过DerivedClass::m_varBase来访问基类中的成员变量m_varBase。
派生类中可以重写(override)基类的成员函数,即在派生类中重新定义一个和基类相同名称、参数列表和返回类型的成员函数。在调用派生类的成员函数时,会优先调用派生类中的函数,如果派生类中没有定义相应的函数,则会调用基类的函数。
在派生类中,可以通过以下方式调用基类的构造函数:

class DerivedClass : public BaseClass {
public:
    DerivedClass(int x, int y, int z) : BaseClass(x, y), m_varDerived(z) {}
private:
    int m_varDerived;
};

在上面的代码中,调用了BaseClass的构造函数,并将参数x和y传递给它。

继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。

派生类对象的内存空间

派生类对象在内存中的空间由两部分组成:基类部分和派生类部分。
基类部分是从基类中继承而来的,派生类对象中包含了基类对象的完整副本。在内存中,基类对象的成员变量和成员函数的布局与基类定义的布局相同。
派生类部分是派生类自己定义的成员变量和成员函数,它们被添加到了基类对象的末尾。派生类的成员变量和成员函数的布局与派生类定义的布局相同。
由于派生类对象包含了基类对象的完整副本,因此可以通过派生类对象访问基类对象中定义的成员变量和成员函数。同时,由于基类部分和派生类部分在内存中是连续的,因此派生类对象可以强制转换为基类对象的指针或引用,并且可以在基类对象的范围内使用。
例如,假设有如下的基类和派生类:

class Base {
public:
    int m_varBase;
    void funcBase() {}
};
class Derived : public Base {
public:
    int m_varDerived;
    void funcDerived() {}
};

那么,派生类对象在内存中的布局如下图所示:

|<-- 基类部分 -->|<-- 派生类部分 -->|
| m_varBase       | m_varDerived      |
| funcBase()      | funcDerived()     |

可以看到,派生类对象中包含了基类对象的完整副本,基类部分和派生类部分在内存中是连续的。

需要注意的是,在派生类中访问基类成员时,需要使用作用域解析运算符::来指定基类成员的名称和访问权限,否则可能会产生二义性。例如,可以使用Base::m_varBase来访问基类中的成员变m_varBase。

继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。

示例程序:学籍管理
下面是一个简单的学籍管理的实例程序,展示了如何使用继承来实现不同类型的学生对象。

#include 
#include 
using namespace std;
// 基类:学生
class Student {
public:
    Student(string name, int age, string gender)
        : m_name(name), m_age(age), m_gender(gender) {}
    void display() {
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
        cout << "性别:" << m_gender << endl;
    }
private:
    string m_name;
    int m_age;
    string m_gender;
};
// 派生类1:本科生
class Undergraduate : public Student {
public:
    Undergraduate(string name, int age, string gender, string major)
        : Student(name, age, gender), m_major(major) {}
    void display() {
        Student::display();
        cout << "专业:" << m_major << endl;
    }
private:
    string m_major;
};
// 派生类2:研究生
class Postgraduate : public Student {
public:
    Postgraduate(string name, int age, string gender, string research)
        : Student(name, age, gender), m_research(research) {}
    void display() {
        Student::display();
        cout << "研究方向:" << m_research << endl;
    }
private:
    string m_research;
};
int main() {
    Student s1("张三", 20, "男");
    s1.display();
    Undergraduate s2("李四", 22, "女", "计算机科学与技术");
    s2.display();
    Postgraduate s3("王五", 25, "男", "计算机视觉");
    s3.display();
    return 0;
}

在上面的代码中,Student是基类,包含了学生的姓名、年龄和性别信息。Undergraduate是派生类1,继承了Student的属性,并添加了专业信息。Postgraduate是派生类2,继承了Student的属性,并添加了研究方向信息。在每个派生类中,都重写了基类的display()函数,以便输出自己的属性。
在主函数中,创建了一个基类对象s1,一个Undergraduate对象s2和一个Postgraduate对象s3,分别输出了它们的属性。
继承可以实现代码的复用和扩展,使得程序更加灵活和可维护。在使用继承时,需要注意类之间的关系,选择适当的继承方式,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。

继承关系&复合关系

继承关系和复合关系是面向对象编程中两种不同的关系。它们分别用于描述不同的对象之间的关系和组合方式。

继承关系是一种"is-a"的关系,用于描述一个类是另一个类的一种特殊形式。在继承关系中,子类继承了父类的属性和方法,并且可以在此基础上添加自己的属性和方法,从而实现代码的复用和扩展。例如,可以定义一个Animal类作为基类,然后定义Dog类和Cat类作为Animal类的子类,从而实现复用和扩展。

复合关系是一种"has-a"的关系,用于描述一个类包含另一个类的对象。在复合关系中,一个类实例化了另一个类的对象,并将其作为自己的成员变量使用。例如,可以定义一个Car类,包含了多个Wheel类对象,从而实现复杂的组合关系。

虽然继承关系和复合关系都可以用于实现代码的复用和扩展,但它们的应用场景不同。继承关系适用于描述"is-a"的关系,即一个类是另一个类的一种特殊形式;而复合关系适用于描述"has-a"的关系,即一个类包含另一个类的对象。

需要注意的是,在使用继承和复合时,需要考虑类之间的耦合性问题。继承关系会使得子类与父类之间产生紧密的耦合关系,一旦父类发生改变,子类也需要相应地进行修改。而复合关系则可以降低类之间的耦合度,使得类之间更加独立和灵活。

类之间的两种关系是继承关系和组合关系。
下面分别给出继承关系和组合关系的代码实例。
继承关系:

#include 
#include 
using namespace std;
// 基类:人
class Person {
public:
    Person(string name, int age) : m_name(name), m_age(age) {}
    void display() {
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
    }
private:
    string m_name;
    int m_age;
};
// 派生类:学生
class Student : public Person {
public:
    Student(string name, int age, string school) : Person(name, age), m_school(school) {}
    void display() {
        Person::display();
        cout << "学校:" << m_school << endl;
    }
private:
    string m_school;
};
int main() {
    Person p1("李四", 20);
    p1.display();
    Student s1("张三", 18, "清华大学");
    s1.display();
    return 0;
}

在上面的代码中,Person是基类,Student是派生类。Student继承了Person的属性和方法,并添加了自己的属性m_school。在Student中,重写了基类的display()函数,以便输出自己的属性。
组合关系:

#include 
#include 
using namespace std;
// 基类:轮胎
class Tyre {
public:
    Tyre(int size) : m_size(size) {}
    void display() {
        cout << "轮胎尺寸:" << m_size << endl;
    }
private:
    int m_size;
};
// 派生类:汽车
class Car {
public:
    Car(string brand, int size) : m_brand(brand), m_tyre(size) {}
    void display() {
        cout << "品牌:" << m_brand << endl;
        m_tyre.display();
    }
private:
    string m_brand;
    Tyre m_tyre;
};
int main() {
    Car c1("宝马", 18);
    c1.display();
    return 0;
}

在上面的代码中,Tyre是基类,Car是派生类。Car包含了一个Tyre对象m_tyre,从而实现了复杂的组合关系。在Car中,重写了自己的display()函数,以便输出自己的属性和包含的Tyre对象的属性。

需要注意的是,在使用继承和组合时,需要考虑类之间的耦合性问题。继承关系会使得子类与父类之间产生紧密的耦合关系,一旦父类发生改变,子类也需要相应地进行修改。而组合关系则可以降低类之间的耦合度,使得类之间更加独立和灵活。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMjIzwXI-1687179456362)(2023-06-19-20-43-47.png)]

复合关系的使用

复合关系是面向对象编程中的一种关系,指一个类对象包含了另一个类对象,用于描述对象之间的组合关系。

下面以一个简单的图形类作为例子,说明复合关系的使用。

#include 
#include 
using namespace std;
// 点类
class Point {
public:
    Point(int x, int y) : m_x(x), m_y(y) {}
private:
    int m_x;
    int m_y;
};
// 图形类
class Shape {
public:
    Shape(string type, int width, int height, int x, int y) 
        : m_type(type), m_width(width), m_height(height), m_point(x, y) {}
    void display() {
        cout << "图形类型:" << m_type << endl;
        cout << "宽度:" << m_width << endl;
        cout << "高度:" << m_height << endl;
        cout << "位置:" << m_point.m_x << ", " << m_point.m_y << endl;
    }
private:
    string m_type;
    int m_width;
    int m_height;
    Point m_point; // 复合关系
};
int main() {
    Shape s("矩形", 100, 50, 10, 20);
    s.display();
    return 0;
}

在上述代码中,Point类表示坐标点,Shape类表示图形类,包含了图形类型、宽度、高度和位置信息,其中位置信息通过复合关系包含了一个Point对象。在Shape类中,定义了display()函数以便输出各个属性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3e52S4Z1-1687179456362)(2023-06-19-20-46-31.png)]

复合关系的使用

 正确的写法:
为“狗”类设一个“业主”类的对象指针;
为“业主”类设一个“狗”类的对象指针数组。
class CMaster; //CMaster必须提前声明,不能先
//写CMaster类后写Cdog类
class CDog {
CMaster * pm;
};
class CMaster {
CDog * dogs[10];
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uNQjjlq-1687179456363)(2023-06-19-20-47-14.png)]

派生类覆盖基类成员

派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::

在C++中,派生类可以覆盖(重写)基类的成员函数或成员变量,从而实现自己的功能。这种覆盖的方式也称为重载。
下面以一个简单的例子说明派生类如何覆盖基类的成员。

#include 
#include 
using namespace std;
// 基类:人
class Person {
public:
    Person(string name, int age) : m_name(name), m_age(age) {}
    virtual void display() { // 基类的display()函数可以被派生类覆盖,使用virtual关键字进行声明
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
    }
private:
    string m_name;
    int m_age;
};
// 派生类:学生
class Student : public Person {
public:
    Student(string name, int age, string school) : Person(name, age), m_school(school) {}
    void display() { // 派生类覆盖了基类的display()函数
        cout << "姓名:" << Person::m_name << endl;
        cout << "年龄:" << Person::m_age << endl;
        cout << "学校:" << m_school << endl;
    }
private:
    string m_school;
};
int main() {
    Person* p1 = new Person("李四", 20); // 基类指针指向基类对象
    p1->display();
    Person* p2 = new Student("张三", 18, "清华大学"); // 基类指针指向派生类对象
    p2->display();
    return 0;
}

在上述代码中,Person是基类,Student是派生类。Student覆盖了Person的display()函数,输出了自己的属性和基类的属性。在main()函数中,指针p1指向基类对象,指针p2指向派生类对象,分别调用了它们的display()函数。
需要注意的是,在使用派生类覆盖基类成员时,需要满足以下条件:

  1. 派生类中的函数名和基类中的函数名相同;
  2. 派生类和基类的函数参数列表相同;
  3. 派生类和基类的函数返回类型相同或者是类型兼容的(如派生类可以返回基类的指针);
  4. 派生类的访问级别不能低于基类的访问级别;
  5. 基类的成员函数必须使用virtual关键字进行声明,以便派生类可以覆盖它。

通过派生类覆盖基类成员,可以实现多态性,使得程序更加灵活和可扩展。在实际编程中,也经常会用到这种技术。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJNRidXs-1687179456364)(2023-06-19-20-48-26.png)]

类的保护成员

另一种存取权限说明符:protected
• 基类的private成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数
• 基类的public成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数
– 派生类的成员函数
– 派生类的友元函数
– 其他的函数
• 基类的protected成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数
– 派生类的成员函数可以访问当前对象和其它对象的基类的保护成员

在C++中,类的保护成员是指只能在类的成员函数内部和派生类的成员函数中访问,而不能在类外访问的成员。保护成员的访问级别介于公有成员和私有成员之间。
下面以一个简单的例子说明类的保护成员的使用。

#include 
#include 
using namespace std;
// 基类:动物
class Animal {
public:
    Animal(string name, string color) : m_name(name), m_color(color) {}
    void display() {
        cout << "名称:" << m_name << endl;
        cout << "颜色:" << m_color << endl;
        cout << "年龄:" << m_age << endl; // 可以在基类成员函数中访问保护成员
    }
protected:
    int m_age; // 保护成员
private:
    string m_name;
    string m_color;
};
// 派生类:狗
class Dog : public Animal {
public:
    Dog(string name, string color, int age) : Animal(name, color) {
        m_age = age; // 可以在派生类的成员函数中访问保护成员
    }
    void bark() {
        cout << "汪汪叫!" << endl;
    }
};
int main() {
    Dog d("旺财", "棕色", 3);
    d.display();
    d.bark();
    // cout << d.m_age << endl; // 保护成员不能在类外访问
    return 0;
}

在上述代码中,Animal是基类,Dog是派生类。基类Animal中定义了一个保护成员m_age,派生类Dog中通过继承可以访问到这个保护成员,因此可以在派生类的成员函数中对m_age进行赋值。在main()函数中,通过Dog的对象d调用了它们的成员函数。

需要注意的是,保护成员可以被派生类访问,但是不能被类外访问。如果在类外访问保护成员,编译器会报错。保护成员的作用在于,它可以在保证封装性的同时,让派生类能够访问基类的成员,从而实现更加灵活和可扩展的类设计。

派生类的构造函数

在C++中,派生类的构造函数可以调用基类的构造函数,以初始化基类的成员变量。派生类的构造函数可以有自己的参数列表,但是必须调用基类的构造函数来初始化基类的成员变量。如果派生类没有显式地调用基类的构造函数,则编译器会默认调用基类的默认构造函数。
下面以一个简单的例子说明派生类的构造函数的使用。

#include 
#include 
using namespace std;
// 基类:人
class Person {
public:
    Person(string name, int age) : m_name(name), m_age(age) {
        cout << "调用了Person的构造函数" << endl;
    }
    void display() {
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
    }
private:
    string m_name;
    int m_age;
};
// 派生类:学生
class Student : public Person {
public:
    Student(string name, int age, string school) : Person(name, age), m_school(school) {
        cout << "调用了Student的构造函数" << endl;
    }
    void display() {
        cout << "姓名:" << Person::m_name << endl;
        cout << "年龄:" << Person::m_age << endl;
        cout << "学校:" << m_school << endl;
    }
private:
    string m_school;
};
int main() {
    Student s("张三", 18, "清华大学");
    s.display();
    return 0;
}

在上述代码中,Person是基类,Student是派生类。Student的构造函数中调用了Person的构造函数,以初始化Person的成员变量。在main()函数中,创建了Student的对象s,并调用了它的成员函数。

需要注意的是,派生类的构造函数必须调用基类的构造函数,以初始化基类的成员变量。在派生类的构造函数内部,可以使用初始化列表来调用基类的构造函数,并对派生类的成员变量进行初始化。如果没有使用初始化列表,则编译器会默认调用基类的默认构造函数和派生类的默认构造函数(如果有的话)。在使用派生类的构造函数时,也需要考虑构造函数的重载、默认参数等问题。

在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。

• 调用基类构造函数的两种方式

– 显式方式:在派生类的构造函数中,为基类的构造函数提供参数.
derived::derived(arg_derived-list):base(arg_base-list)
– 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数.
• 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。

包含成员对象的派生类的构造函数写法

class Skill {
public:
Skill(int n) { }
};
class FlyBug: public Bug {
int nWings;
Skill sk1, sk2;
public:
FlyBug( int legs, int color, int wings);
};
FlyBug::FlyBug( int legs, int color, int wings):
Bug(legs,color),sk1(5),sk2(color) ,nWings(wings) {
}

当一个类包含成员对象时,派生类必须在其构造函数中显式地调用成员对象的构造函数来初始化它们。派生类的构造函数必须使用初始化列表来调用基类的构造函数和成员对象的构造函数。
下面以一个简单的例子说明包含成员对象的派生类的构造函数的写法。

#include 
#include 
using namespace std;
// 基类:人
class Person {
public:
    Person(string name, int age) : m_name(name), m_age(age) {
        cout << "调用了Person的构造函数" << endl;
    }
    void display() {
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
    }
private:
    string m_name;
    int m_age;
};
// 成员对象:地址
class Address {
public:
    Address(string province, string city) : m_province(province), m_city(city) {
        cout << "调用了Address的构造函数" << endl;
    }
    void display() {
        cout << "地址:" << m_province << "省" << m_city << "市" << endl;
    }
private:
    string m_province;
    string m_city;
};
// 派生类:学生
class Student : public Person {
public:
    Student(string name, int age, string school, string province, string city) : Person(name, age), m_school(school), m_address(province, city) {
        cout << "调用了Student的构造函数" << endl;
    }
    void display() {
        cout << "姓名:" << Person::m_name << endl;
        cout << "年龄:" << Person::m_age << endl;
        cout << "学校:" << m_school << endl;
        m_address.display();
    }
private:
    string m_school;
    Address m_address; // 包含成员对象
};
int main() {
    Student s("张三", 18, "清华大学", "北京", "海淀");
    s.display();
    return 0;
}

在上述代码中,Person是基类,Address是成员对象,Student是派生类。在Student的构造函数中,需要使用初始化列表来调用Person的构造函数和Address的构造函数,以初始化它们的成员变量。在main()函数中,创建了Student的对象s,并调用了它的成员函数。

需要注意的是,在派生类的构造函数中调用成员对象的构造函数,需要在初始化列表中使用成员对象的名称来进行调用,而不是使用构造函数的名称来进行调用。此外,也需要考虑构造函数的重载、默认参数等问题。

封闭派生类对象的构造函数的执行顺序

在创建派生类的对象时:

  1. 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;

  2. 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。

  3. 最后执行派生类自己的构造函数

在派生类对象消亡时:

  1. 先执行派生类自己的析构函数
  2. 再依次执行各成员对象类的析构函数
  3. 最后执行基类的析构函数
    析构函数的调用顺序与构造函数的调用顺序相反。

public继承的赋值兼容规则

class base { };
class derived : public base { };
base b;
derived d;

• 如果派生方式是 private或protected,则上述三条不可行。
在C++中,公有继承(public inheritance)的赋值兼容规则遵循“is-a”关系,即派生类对象可以赋值给基类对象,但是反过来基类对象不能赋值给派生类对象。
具体来说,如果一个基类指针或引用指向一个派生类对象,那么该指针或引用可以调用基类对象中的成员函数,但是不能调用派生类对象中新增的成员函数。如果需要调用派生类对象中新增的成员函数,可以使用动态类型识别(dynamic_cast)来实现。
下面以一个简单的例子说明公有继承的赋值兼容规则。

#include 
#include 
using namespace std;
// 基类:人
class Person {
public:
    Person(string name, int age) : m_name(name), m_age(age) {}
    void display() {
        cout << "姓名:" << m_name << endl;
        cout << "年龄:" << m_age << endl;
    }
private:
    string m_name;
    int m_age;
};
// 派生类:学生
class Student : public Person {
public:
    Student(string name, int age, string school) : Person(name, age), m_school(school) {}
    void display() {
        cout << "姓名:" << Person::m_name << endl;
        cout << "年龄:" << Person::m_age << endl;
        cout << "学校:" << m_school << endl;
    }
    void study() {
        cout << "学生正在学习" << endl;
    }
private:
    string m_school;
};
int main() {
    Person* p1 = new Student("张三", 18, "清华大学");
    p1->display(); // 调用基类成员函数
    // p1->study(); // 错误,无法调用派生类新增的成员函数
    Person& p2 = *(new Student("李四", 19, "北京大学"));
    p2.display(); // 调用基类成员函数
    // p2.study(); // 错误,无法调用派生类新增的成员函数
    return 0;
}

在上述代码中,Person是基类,Student是派生类。在main()函数中,使用基类指针p1和基类引用p2分别指向了一个派生类对象。通过p1和p2可以调用基类对象中的成员函数,但是不能调用派生类对象中新增的成员函数。

需要注意的是,公有继承的赋值兼容规则只适用于指针和引用,而不适用于对象。如果需要使用基类对象来初始化派生类对象,需要使用转换操作符(static_cast、dynamic_cast等)来进行类型转换。

直接基类和间接基类

在C++中,一个派生类可以从一个或多个基类中继承成员变量和成员函数。基类可以分为直接基类和间接基类两种类型。
直接基类是指在派生类的声明中明确指定的基类。例如:

class B { ... };
class D : public B { ... };

在上述代码中,B是D的直接基类。
间接基类是指在派生类的基类中递归地包含其他基类。例如:

class B { ... };
class C : public B { ... };
class D : public C { ... };

在上述代码中,C是D的直接基类,B是D的间接基类。因为D继承了C中的成员变量和成员函数,而C又继承了B中的成员变量和成员函数。
需要注意的是,派生类在继承基类时,只能通过直接基类来访问其成员函数和成员变量,不能通过间接基类来访问。如果需要访问间接基类中的成员函数和成员变量,可以通过直接基类的成员函数来调用。
下面以一个简单的例子说明直接基类和间接基类的概念。

#include 
using namespace std;
class A {
public:
    void f() {
        cout << "调用了A的f函数" << endl;
    }
};
class B : public A {
public:
    void g() {
        cout << "调用了B的g函数" << endl;
    }
};
class C : public B {
public:
    void h() {
        cout << "调用了C的h函数" << endl;
    }
};
int main() {
    C c;
    c.f(); // 调用直接基类A的成员函数
    c.g(); // 调用直接基类B的成员函数
    c.h(); // 调用自身的成员函数
    return 0;
}

在上述代码中,A是C的间接基类,B是C的直接基类。在main()函数中,创建了C的对象c,并调用了它的成员函数。可以看到,只有直接基类的成员函数和自身的成员函数可以被调用,间接基类的成员函数无法被直接调用。需要通过直接基类的成员函数来间接调用。

你可能感兴趣的:(C++,c++,算法,笔记)