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

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

派生类覆盖基类成员

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

在C++中,派生类可以覆盖(重写)基类的成员函数或成员变量,从而实现自己的功能。这种覆盖的方式也称为重载。

下面以一个简单的例子说明派生类如何覆盖基类的成员。

#include <iostream>
#include <string>
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 <iostream>
#include <string>
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 <iostream>
#include <string>
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 <iostream>
#include <string>
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 <iostream>
#include <string>
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 <iostream>
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,并调用了它的成员函数。可以看到,只有直接基类的成员函数和自身的成员函数可以被调用,间接基类的成员函数无法被直接调用。需要通过直接基类的成员函数来间接调用。

目录
相关文章
|
13天前
|
负载均衡 算法 安全
探秘:基于 C++ 的局域网电脑控制软件自适应指令分发算法
在现代企业信息化架构中,局域网电脑控制软件如同“指挥官”,通过自适应指令分发算法动态调整指令发送节奏与数据量,确保不同性能的终端设备高效运行。基于C++语言,利用套接字实现稳定连接和线程同步管理,结合实时状态反馈,优化指令分发策略,提升整体管控效率,保障网络稳定,助力数字化办公。
47 19
|
17天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
43 5
|
17天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
40 2
|
26天前
|
存储 算法 安全
基于红黑树的局域网上网行为控制C++ 算法解析
在当今网络环境中,局域网上网行为控制对企业和学校至关重要。本文探讨了一种基于红黑树数据结构的高效算法,用于管理用户的上网行为,如IP地址、上网时长、访问网站类别和流量使用情况。通过红黑树的自平衡特性,确保了高效的查找、插入和删除操作。文中提供了C++代码示例,展示了如何实现该算法,并强调其在网络管理中的应用价值。
|
24天前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
1月前
|
算法 安全 C++
用 C++ 算法控制员工上网的软件,关键逻辑是啥?来深度解读下
在企业信息化管理中,控制员工上网的软件成为保障网络秩序与提升办公效率的关键工具。该软件基于C++语言,融合红黑树、令牌桶和滑动窗口等算法,实现网址精准过滤、流量均衡分配及异常连接监测。通过高效的数据结构与算法设计,确保企业网络资源优化配置与安全防护升级,同时尊重员工权益,助力企业数字化发展。
55 4
|
3月前
|
存储 算法 C++
高精度算法(加、减、乘、除,使用c++实现)
高精度算法(加、减、乘、除,使用c++实现)
1022 0
高精度算法(加、减、乘、除,使用c++实现)
|
3月前
|
算法 数据处理 C++
c++ STL划分算法;partition()、partition_copy()、stable_partition()、partition_point()详解
这些算法是C++ STL中处理和组织数据的强大工具,能够高效地实现复杂的数据处理逻辑。理解它们的差异和应用场景,将有助于编写更加高效和清晰的C++代码。
73 0
|
3月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
226 0
|
3月前
|
存储 算法 决策智能
【算法】博弈论(C/C++)
【算法】博弈论(C/C++)