「C++系列」继承

news2024/11/15 11:41:41

文章目录

  • 一、继承
    • 1. 基本概念
    • 2. 继承类型
      • ①公有继承(Public Inheritance)
      • ②私有继承(Private Inheritance)
      • ③保护继承(Protected Inheritance)
    • 3. 继承的语法
    • 4. 构造函数和析构函数
      • ①构造函数案例
      • ②析构函数案例
    • 5. 访问控制
    • 6. 继承与多态
  • 二、相关链接

一、继承

C++ 中的继承(Inheritance)是面向对象编程(OOP)的一个核心概念,它允许我们定义一个基于现有类的新类,即子类(派生类)可以继承父类(基类)的属性和方法。继承机制支持代码的重用,并且使得类的层次结构更加清晰。

1. 基本概念

  • 基类(Base Class)
    基类是一个可以被其他类继承的类。它通常包含了一些通用的属性和方法,这些属性和方法对于所有派生类都是共有的或者可以被派生类所利用。在定义基类时,你可以指定哪些成员是公有的(public)、保护的(protected)还是私有的(private),这将影响派生类对这些成员的访问权限。
  • 派生类(Derived Class)
    派生类是通过继承一个或多个基类而创建的类。派生类可以继承基类的公有成员和保护成员(私有成员总是不可继承的),但它可以添加自己的新成员或重写从基类继承来的成员。在派生类中,你可以通过指定继承类型(公有继承、私有继承或保护继承)来控制基类成员的访问权限。
  • 继承:派生类获得基类属性和方法的过程。
class Base {  
public:  
    void show() {  
        // 显示信息的函数  
    }  
};  
  
// 公有继承  
class Derived : public Base {  
    // 派生类可以添加新的成员或重写Base的成员  
};  
  
// 私有继承(较少使用)  
class DerivedPrivate : private Base {  
    // Base的公有和保护成员在DerivedPrivate中变为私有  
};  
  
// 保护继承(也很少使用)  
class DerivedProtected : protected Base {  
    // Base的公有和保护成员在DerivedProtected中变为保护  
};

2. 继承类型

C++ 支持多种继承类型,但最常见的是公有继承(Public Inheritance)和私有继承(Private Inheritance)。

①公有继承(Public Inheritance)

公有继承是最常用的继承类型,它表示一种“是一个”的关系。在公有继承中,基类的公有成员和保护成员在派生类中保持原有的访问级别(公有成员仍为公有,保护成员仍为保护),但基类的私有成员在派生类中仍然不可访问。

#include <iostream>

class Base {
public:
    void showPublic() { std::cout << "Public function in Base\n"; }
protected:
    void showProtected() { std::cout << "Protected function in Base\n"; }
private:
    void showPrivate() { std::cout << "Private function in Base\n"; } // 不可在Derived中访问
};

class Derived : public Base {
public:
    void test() {
        showPublic();  // 可访问
        showProtected(); // 可访问
        // showPrivate(); // 编译错误,不可访问
    }
};

int main() {
    Derived d;
    d.showPublic();  // 错误,showPublic() 是Derived的私有成员函数,这里不能直接访问
    d.test();  // 正确,通过test()访问Base的公有和保护成员
    return 0;
}

// 注意:main中直接调用d.showPublic()是错误的,因为showPublic()不是Derived的公有成员函数。
// 正确的调用方式是通过Derived的公有成员函数(如test())来间接调用Base的公有成员函数。

②私有继承(Private Inheritance)

私有继承中,基类的公有成员和保护成员在派生类中都会变成私有成员。这意味着派生类的对象不能直接访问这些成员,甚至不能通过派生类的成员函数来访问(除非这些函数是友元函数或在派生类内部定义)。

#include <iostream>

class Base {
public:
    void show() { std::cout << "Function in Base\n"; }
};

class Derived : private Base {
public:
    void accessBase() {
        show(); // 可访问,因为show()在Derived内部
    }
    // void exposeShow() { return show(); } // 错误,show()现在是Derived的私有成员
};

int main() {
    Derived d;
    // d.show(); // 错误,show()是Derived的私有成员函数
    d.accessBase(); // 正确,通过Derived的公有成员函数访问
    return 0;
}

③保护继承(Protected Inheritance)

保护继承中,基类的公有成员和保护成员在派生类中都会变成保护成员。这意味着派生类的对象不能直接访问这些成员,但派生类的派生类(即孙子类)可以访问。

#include <iostream>

class Base {
public:
    void show() { std::cout << "Function in Base\n"; }
};

class Derived : protected Base {
    // ...
};

class GrandDerived : public Derived {
public:
    void test() {
        show(); // 可访问,因为show()在GrandDerived中是保护的
    }
};

int main() {
    GrandDerived gd;
    gd.test(); // 正确,通过GrandDerived的公有成员函数访问
    // gd.show(); // 错误,show()是GrandDerived的保护成员函数
    return 0;
}

3. 继承的语法

继承的语法很简单,只需在派生类的定义中使用冒号:后跟基类名即可。

class Base {
public:
    void show() {
        // ...
    }
};

class Derived : public Base {
    // ...
};

在上面的例子中,Derived类公有继承自Base类,因此Derived类的对象可以使用show()成员函数。

4. 构造函数和析构函数

在C++中,构造函数和析构函数是特殊的成员函数,它们分别在对象创建和销毁时自动调用。构造函数用于初始化对象,而析构函数用于清理对象占用的资源。以下是构造函数和析构函数的基本案例。

①构造函数案例

#include <iostream>
using namespace std;

class MyClass {
private:
    int value;

public:
    // 构造函数
    MyClass(int val) : value(val) {
        cout << "构造函数被调用,value = " << value << endl;
    }

    // 默认构造函数(如果需要的话)
    // MyClass() : value(0) {
    //     cout << "默认构造函数被调用,value = " << value << endl;
    // }

    // 其他成员函数...
};

int main() {
    MyClass obj1(10); // 调用带参数的构造函数
    // MyClass obj2; // 如果定义了默认构造函数,这行代码会调用它;否则,编译错误

    return 0;
}

在这个例子中,MyClass类有一个私有成员变量value和一个公有的构造函数,该构造函数接受一个整型参数来初始化value。当在main函数中创建MyClass的实例obj1时,会调用构造函数,并输出相应的信息。

②析构函数案例

析构函数在对象生命周期结束时自动调用,用于执行清理工作,如释放分配的内存等。析构函数的名称由波浪线~后跟类名组成,且不接受任何参数和返回类型(连void都不写)。

#include <iostream>
using namespace std;

class MyClass {
private:
    int* pValue;

public:
    // 构造函数
    MyClass(int val) {
        pValue = new int(val); // 分配内存
        cout << "构造函数被调用,pValue = " << *pValue << endl;
    }

    // 析构函数
    ~MyClass() {
        delete pValue; // 释放内存
        cout << "析构函数被调用" << endl;
    }

    // 其他成员函数...
};

int main() {
    MyClass obj(10); // 调用构造函数
    // 当obj的生命周期结束时(离开作用域),析构函数会自动调用

    // 注意:在main函数结束时,所有局部变量(包括obj)的生命周期都会结束,
    // 因此它们的析构函数会被自动调用。但在这个简单的例子中,你可能看不到析构函数的输出,
    // 因为程序会立即退出。为了看到输出,可以在析构函数中添加一个暂停点(如cin.get()),
    // 或者在析构函数中写入日志文件。

    // cin.get(); // 暂停程序,以便查看析构函数的输出(可选)

    return 0;
}

在这个例子中,MyClass类有一个指向整型的指针pValue作为成员变量。构造函数使用new操作符为pValue分配内存,并初始化它指向的值。析构函数则使用delete操作符释放pValue指向的内存。当MyClass的实例objmain函数的末尾离开其作用域时,其析构函数会自动被调用,执行清理工作。

5. 访问控制

继承中的访问控制规则决定了派生类如何访问基类的成员。这些规则基于基类中成员的访问级别和继承的类型。

访问控制是通过关键字publicprotectedprivate来实现的,这些关键字用于指定类成员的访问级别。下面是一个包含这些访问控制关键字的C++案例,展示了它们是如何工作的。

#include <iostream>
using namespace std;

class MyClass {
private:
    // 私有成员,只能在类内部访问
    int privateVar;

protected:
    // 保护成员,可以在类内部、派生类(子类)中访问,但不能通过类的对象直接访问
    int protectedVar;

public:
    // 公有成员,可以通过类的对象直接访问
    int publicVar;

    // 构造函数
    MyClass(int pv, int ptv, int ppv) : publicVar(pv), protectedVar(ptv), privateVar(ppv) {
        // 构造函数体可以访问所有成员
    }

    // 公有成员函数,可以访问类的所有成员(包括私有和保护成员)
    void showVars() {
        cout << "publicVar = " << publicVar << endl;
        cout << "protectedVar = " << protectedVar << endl; // 公有成员函数可以访问保护成员
        // cout << "privateVar = " << privateVar << endl; // 这行代码通常不会出现在这里,因为私有成员不应直接暴露给外部
    }

    // 尝试从外部访问私有和保护成员(这将导致编译错误)
    // void externalAccess() {
    //     MyClass obj;
    //     cout << obj.privateVar << endl; // 编译错误:私有成员不能从类外部访问
    //     cout << obj.protectedVar << endl; // 编译错误(在严格意义上),但技术上可以通过派生类访问
    // }

    // 友元函数或友元类(示例略)可以访问类的私有和保护成员
};

// 派生类示例(用于展示保护成员的访问)
class DerivedClass : public MyClass {
protected:
    // 可以在派生类中访问基类的保护成员
    void accessProtectedVar() {
        cout << "Derived class accessing protectedVar = " << protectedVar << endl;
        // cout << "Derived class cannot access privateVar = " << privateVar << endl; // 编译错误
    }
};

int main() {
    MyClass obj(1, 2, 3); // 创建MyClass对象,初始化公有、保护和私有成员
    obj.showVars(); // 调用公有成员函数显示变量值

    // 尝试直接访问私有和保护成员(这将导致编译错误)
    // cout << obj.privateVar << endl; // 编译错误
    // cout << obj.protectedVar << endl; // 编译错误(虽然技术上可以通过派生类访问,但这里直接访问会失败)

    // 派生类使用示例
    DerivedClass derivedObj(4, 5, 6);
    derivedObj.accessProtectedVar(); // 派生类成员函数访问基类的保护成员

    return 0;
}

在这个案例中,MyClass类有三个成员变量(公有、保护和私有各一个)以及一个构造函数和一个公有成员函数showVars。构造函数用于初始化所有成员变量,而showVars函数则展示了如何在类的成员函数内部访问所有类型的成员变量。

main函数中展示了如何创建MyClass的对象,并通过其公有成员函数访问公有成员变量。尝试直接访问私有和保护成员变量会导致编译错误,因为它们的访问权限被限制在类内部(对于私有成员)和类内部及派生类内部(对于保护成员)。

此外,还展示了如何通过派生类DerivedClass访问基类MyClass的保护成员变量。这说明了保护成员在继承关系中的访问特性。

6. 继承与多态

继承通常与多态(Polymorphism)一起使用,以实现接口的重用和动态绑定。多态允许通过基类的指针或引用来调用派生类对象的成员函数,这增加了程序的灵活性和可扩展性。

继承和多态是面向对象编程的两大核心概念。继承允许我们定义一个类(称为派生类或子类)来继承另一个类(称为基类或父类)的属性和方法。多态则允许我们通过基类的指针或引用来调用派生类的成员函数,这种调用在运行时确定实际调用的函数版本,这通常通过虚函数实现。

下面是一个C++中继承与多态的简单案例:

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    // 虚函数
    virtual void speak() const {
        cout << "Some animal sound" << endl;
    }

    // 虚析构函数,确保通过基类指针删除派生类对象时正确调用析构函数
    virtual ~Animal() {}
};

// 派生类 Dog
class Dog : public Animal {
public:
    void speak() const override { // 使用override关键字明确表示这是一个重写函数
        cout << "Woof!" << endl;
    }
};

// 派生类 Cat
class Cat : public Animal {
public:
    void speak() const override {
        cout << "Meow!" << endl;
    }
};

// 多态演示函数
void makeItSpeak(Animal* animal) {
    animal->speak(); // 运行时确定调用哪个版本的speak()
}

int main() {
    Dog myDog;
    Cat myCat;

    // 尝试使用基类指针指向派生类对象
    Animal* myAnimal1 = &myDog; // 向上转型(隐式)
    Animal* myAnimal2 = &myCat;

    // 调用多态函数
    makeItSpeak(myAnimal1); // 输出: Woof!
    makeItSpeak(myAnimal2); // 输出: Meow!

    // 注意:虽然下面的代码在技术上可行,但它违反了多态的初衷
    // 直接通过派生类类型的指针调用函数将不会展示多态性
    myDog.speak(); // 输出: Woof!
    myCat.speak(); // 输出: Meow!

    return 0;
}

在这个例子中,Animal是基类,它有一个虚函数speak()和一个虚析构函数。DogCat是派生自Animal的类,它们各自重写了speak()函数。makeItSpeak函数接受一个指向Animal的指针,并调用该指针所指向对象的speak()函数。由于speak()是虚函数,因此调用哪个版本的speak()是在运行时根据指针实际指向的对象类型确定的,这展示了多态性。

请注意,虽然可以通过派生类类型的指针直接调用speak()函数(如myDog.speak();),但这并不会展示多态性,因为编译器在编译时就已经确定了调用哪个函数版本。多态性的真正展示是在通过基类指针或引用调用虚函数时。

此外,虚析构函数的使用是处理通过基类指针删除派生类对象时资源释放问题的关键。如果基类析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数,这可能导致资源泄漏。
在这里插入图片描述

二、相关链接

  1. Visual Studio Code下载地址
  2. Sublime Text下载地址
  3. 「C++系列」C++简介、应用领域
  4. 「C++系列」C++ 基本语法
  5. 「C++系列」C++ 数据类型
  6. 「C++系列」C++ 变量类型
  7. 「C++系列」C++ 变量作用域
  8. 「C++系列」C++ 常量知识点-细致讲解
  9. 「C++系列」C++ 修饰符类型
  10. 「C++系列」一篇文章说透【存储类】
  11. 「C++系列」一篇文章讲透【运算符】
  12. 「C++系列」循环
  13. 「C++系列」判断
  14. 「C++系列」函数/内置函数
  15. 「C++系列」数字/随机数
  16. 「C++系列」数组
  17. 「C++系列」字符串
  18. 「C++系列」指针
  19. 「C++系列」引用
  20. 「C++系列」日期/时间
  21. 「C++系列」输入/输出
  22. 「C++系列」数据结构
  23. 「C++系列」vector 容器
  24. 「C++系列」类/对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2083783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单链表的问题(2)

1.对于一个链表&#xff0c;请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法&#xff0c;判断其是否为回文结构。 给定一个链表的头指针A&#xff0c;请返回一个bool值&#xff0c;代表其是否为回文结构。保证链表长度小于等于900。 这个我们可以运用双指针来解决这个…

爆改YOLOv8|利用全新的聚焦式线性注意力模块Focused Linear Attention 改进yolov8(v1)

1&#xff0c;本文介绍 全新的聚焦线性注意力模块&#xff08;Focused Linear Attention&#xff09;是一种旨在提高计算效率和准确性的注意力机制。传统的自注意力机制在处理长序列数据时通常计算复杂度较高&#xff0c;限制了其在大规模数据上的应用。聚焦线性注意力模块则通…

EmguCV学习笔记 C# 7.1 角点检测

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

Excel中让第一行始终显示

要在Excel中让第一行始终显示&#xff0c;你可以使用冻结窗格功能。具体步骤如下&#xff1a; 打开需要设置第一行一直显示的工作表。将光标定位在工作表内任意一个单元格内。选择“视图”菜单&#xff0c;单击工具栏中的“冻结窗格”命令。在弹出的下拉菜单中选择“冻结首行”…

字母的大小写转换(tolower、toupper、transform)

字母的大小写转换&#xff08;tolower、toupper、transform&#xff09; 1. tolower&#xff08;&#xff09;、toupper&#xff08;&#xff09;函数 &#xff08;这个在之前的一篇文章 “字符串中需要掌握的函数总结&#xff08;1&#xff09;”中有较为详细的介绍。&#…

时利和:如何提升工装夹具的加工质量?

在机械加工领域&#xff0c;工装夹具起着至关重要的作用。它不仅能够提高生产效率&#xff0c;还能保证加工精度&#xff0c;确保产品质量的稳定性。那么&#xff0c;如何提升工装夹具的加工质量呢?以下是时利和整理分享的几个关键因素。 一、精准的设计 工装夹具的设计是决定…

使用物联网卡访问萤石云的常见问题

使用物联网卡接入萤石开放平台时经常遇到各种问题&#xff0c;这边总结了常见的一些 用的是哪家运营商的卡&#xff1f; 电信 移动 联通&#xff08;申请的时候可以自主选择&#xff09; 卡有什么限制&#xff1f; 定向流量卡&#xff0c;只能访问萤石云平台&#xff0c;只能…

完美解决Jenkins重启后自动杀掉衍生进程(子进程)问题

完美解决Jenkins重启后自动杀掉衍生进程(子进程)问题 本文中使用的Jenkins版本为Version 2.452.3 先罗列一下前置问题&#xff1a;Jenkins任务构建完成自动杀掉衍生进程 用过Jenkins的都知道&#xff0c;Jenkins任务构建完成后&#xff0c;是会自动杀掉衍生进程&#xff0c;这…

安卓AppBarLayout与ViewPager2里的fragment里的webview滑动冲突

今天开发遇见一个头痛的问题&#xff0c;就是AppBarLayout和webview会存在一个冲突问题。如图下 问题出现在webview推到顶端的时候&#xff0c;AppBarLayout并不会跟着响应伸缩&#xff0c;解决办法是 在 webview 包 一个 父的 NestedScrollView 就能解决了。 运行效果入下 更改…

单向链表和双向链表的一些基本算法

单向链表头插尾插 单向链表的销毁与反转 反转原理&#xff1a;将头节点与后面的节点分开&#xff0c;然后从第一个节点开始对每个节点使用头插法 冒泡排 选排 链表环&#xff1a; 判断是否有环&#xff1a;弗洛伊德快慢指针&#xff08;快指针一般是慢指针的2倍&#xff0c;差为…

Selenium(HTML基础)

一、HTML基础 &#xff08;在学习自动化时&#xff0c;保证能看懂&#xff09; 1.1.HTML介绍 英文是HyperText Markup Language&#xff0c;译为:超文本标记语言是Internet上用于设计网页的主要语言&#xff0c;2008年发布了HTML5.0,是目前互联网的标准&#xff0c;并作为互联…

数据结构之 “单链表“

&#xff08;1&#xff09;在顺表表中&#xff0c;如果是头插/删的时间复杂度是O(1)&#xff1b;尾插/删的时间复杂度是O(N) &#xff08;2&#xff09;增容一般是呈2倍的增长&#xff0c;势必会有一定的空间浪费。比如&#xff1a;申请了50个空间&#xff0c;只用了两个&#…

【Matlab】SSA-BP麻雀搜索算法优化BP神经网络回归预测 可预测未来(附代码)

资源下载&#xff1a; 资源合集&#xff1a; 目录 一&#xff0c;概述 传统的BP神经网络存在一些问题&#xff0c;比如容易陷入局部最优解、训练速度慢等。为了解决这些问题&#xff0c;我们引入了麻雀算法作为优化方法&#xff0c;将其与BP神经网络相结合&#xff0c;提出了…

精准高效,省时省力——2024年翻译工具新趋势

如果你收到一份外文文档&#xff0c;但是你看不懂急需翻译成中文&#xff0c;将文档内容复制粘贴到翻译的的窗口获得中文内容是不是很麻烦。我了解到了deepl翻译文档之后还认识了不少的翻译神器&#xff0c;这次分享给你一起试试吧。 第一款福晰在线翻译 链接直达>>htt…

3D工艺大师:精准助力医疗设备远程维修

医疗设备是现代医疗体系中不可或缺的重要工具&#xff0c;它们帮助医生更准确地诊断病情&#xff0c;更有效地进行治疗。但随着技术的进步&#xff0c;这些设备变得越来越复杂&#xff0c;维修起来也面临诸多挑战。 首先&#xff0c;医疗设备结构复杂&#xff0c;零件众多&…

【Material-UI】Rating组件中的Rating precision属性

文章目录 一、Rating组件概述1. 组件简介2. precision属性的作用 二、Rating precision的基本用法三、Rating precision属性详解1. 精度选择的意义2. 如何在项目中选择合适的精度 四、Rating precision属性的实际应用场景1. 电商平台中的应用2. 电影评分应用3. 专业评测网站 五…

[Tomcat源码解析]——热部署和热加载原理

热部署 在Tomcat中可以通过Host标签设置热部署,当 autoDeploy为true时,在运行中的Tomcat中丢入一个war包,那么Tomcat不需要重启就可以自动加载该war包。 <Host name="localhost" appBase="webapps"unpackWARs="true" autoDeploy="…

Ubuntu18.04 下安装CUDA

安装步骤 1.查看是否安装了cuda # 法1 cat /usr/local/cuda/version.txt # 法2 nvcc --version 2.若没有安装&#xff0c;则查看是否有N卡驱动&#xff0c;若无N卡驱动&#xff0c;则到软件与更新 -> 附加驱动中安装驱动 3.查看N卡驱动支持的cuda版本 nvidia-smi 如下…

RabbitMQ 集群与高可用性

目录 单节点与集群部署 1.1. 单节点部署 1.2. 集群部署 镜像队列 1.定义与工作原理 2. 配置镜像队列 3.应用场景 4. 优缺点 5. Java 示例 分布式部署 1. 分布式部署的主要目标 2. 典型架构设计 3. RabbitMQ 分布式部署的关键技术 4. 部署策略和实践 5. 分布式部署…

图像变换——等距变换、相似变换、仿射变换、投影变换

%%图像变换 % I imread(cameraman.tif); I imread(F:\stitching\imagess\or\baiyun2.jpg); figure; imshow(I); title(原始图像); [w,h]size(I); thetapi/4;%旋转角 t[200,80];%平移tx,ty s0.3;%缩放尺度 %% 等距变换平移变换旋转变换 H_eprojective2d([cos(theta) sin(theta…