继承和派生知识点总结 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,并调用了它的成员函数。可以看到,只有直接基类的成员函数和自身的成员函数可以被调用,间接基类的成员函数无法被直接调用。需要通过直接基类的成员函数来间接调用。

目录
相关文章
|
4月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
103 2
|
5月前
|
存储 算法 C++
Windows共享文件:探秘C++实现的B树索引算法奇境
在数字化时代,Windows共享文件的高效管理至关重要。B树算法以其自平衡多路搜索特性,在文件索引与存储优化中表现出色。本文探讨B树在Windows共享文件中的应用,通过C++实现具体代码,展示其构建文件索引、优化数据存储的能力,提升文件检索效率。B树通过减少磁盘I/O操作,确保查询高效,为企业和个人提供流畅的文件共享体验。
|
6月前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
171 15
|
6月前
|
运维 监控 算法
解读 C++ 助力的局域网监控电脑网络连接算法
本文探讨了使用C++语言实现局域网监控电脑中网络连接监控的算法。通过将局域网的拓扑结构建模为图(Graph)数据结构,每台电脑作为顶点,网络连接作为边,可高效管理与监控动态变化的网络连接。文章展示了基于深度优先搜索(DFS)的连通性检测算法,用于判断两节点间是否存在路径,助力故障排查与流量优化。C++的高效性能结合图算法,为保障网络秩序与信息安全提供了坚实基础,未来可进一步优化以应对无线网络等新挑战。
|
6月前
|
存储 算法 数据处理
公司局域网管理中的哈希表查找优化 C++ 算法探究
在数字化办公环境中,公司局域网管理至关重要。哈希表作为一种高效的数据结构,通过哈希函数将关键值(如IP地址、账号)映射到数组索引,实现快速的插入、删除与查找操作。例如,在员工登录验证和设备信息管理中,哈希表能显著提升效率,避免传统线性查找的低效问题。本文以C++为例,展示了哈希表在局域网管理中的具体应用,包括设备MAC地址与IP分配的存储与查询,并探讨了优化哈希函数和扩容策略,确保网络管理高效准确。
|
2月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
77 0
|
3月前
|
存储 机器学习/深度学习 算法
基于 C++ 的局域网访问控制列表(ACL)实现及局域网限制上网软件算法研究
本文探讨局域网限制上网软件中访问控制列表(ACL)的应用,分析其通过规则匹配管理网络资源访问的核心机制。基于C++实现ACL算法原型,展示其灵活性与安全性。文中强调ACL在企业与教育场景下的重要作用,并提出性能优化及结合机器学习等未来研究方向。
97 4
|
4月前
|
监控 算法 数据处理
基于 C++ 的 KD 树算法在监控局域网屏幕中的理论剖析与工程实践研究
本文探讨了KD树在局域网屏幕监控中的应用,通过C++实现其构建与查询功能,显著提升多维数据处理效率。KD树作为一种二叉空间划分结构,适用于屏幕图像特征匹配、异常画面检测及数据压缩传输优化等场景。相比传统方法,基于KD树的方案检索效率提升2-3个数量级,但高维数据退化和动态更新等问题仍需进一步研究。未来可通过融合其他数据结构、引入深度学习及开发增量式更新算法等方式优化性能。
139 17
|
3月前
|
机器学习/深度学习 存储 算法
基于 C++ 布隆过滤器算法的局域网上网行为控制:URL 访问过滤的高效实现研究
本文探讨了一种基于布隆过滤器的局域网上网行为控制方法,旨在解决传统黑白名单机制在处理海量URL数据时存储与查询效率低的问题。通过C++实现URL访问过滤功能,实验表明该方法可将内存占用降至传统方案的八分之一,查询速度提升约40%,假阳性率可控。研究为优化企业网络管理提供了新思路,并提出结合机器学习、改进哈希函数及分布式协同等未来优化方向。
87 0
|
3月前
|
存储 安全 Java
c++--继承
c++作为面向对象的语言三大特点其中之一就是继承,那么继承到底有何奥妙呢?继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用,继承就是类方法的复用。
88 0

热门文章

最新文章