图例实解:C++中类的继承特性

  整个c++程序设计全面围绕面向对象的方式进行,类的继承特性是c++的一个非常非常重要的机制,继承特性可以使一个新类获得其父类的操作和数据结构,程序员只需在新类中增加原有类中没有的成分。

  可以说这一章节的内容是c++面向对象程序设计的关键。

  下面我们简单的来说一下继承的概念,先看下图:

上图是一个抽象描述的特性继承表

  交通工具是一个基类也称做父类),通常情况下所有交通工具所共同具备的特性是速度与额定载人的数量,但按照生活常规,我们来继续给交通工具来细分类的时候,我们会分别想到有汽车类和飞机类等等,汽车类和飞类同样具备速度和额定载人数量这样的特性,而这些特性是所有交通工具所共有的,那么当建立汽车类和飞机类的时候我们无需再定义基类已经有的数据成员,而只需要描述汽车类和飞机类所特有的特性即可,飞机类和汽车类的特性是由在交通工具类原有特性基础上增加而来的,那么飞机类和汽车类就是交通工具类的派生类也称做子类)。以此类推,层层递增,这种子类获得父类特性的概念就是继承。

  下面我们根据上图的理解,有如下的代码:

#include <iostream>   
using namespace std; 
 
class Vehicle 

public: 

    void EditSC(float speed,int total); 
protected: 
    float speed;//速度 
    int total;//最大载人量 
}; 
void Vehicle::EditSC(float speed,int total) 

    Vehicle::speed = speed; 
    Vehicle::total = total; 

class Car:public Vehicle//Car类继承Vehicle的特性,Car类是Vehicle的派生类 

public: 
    Car() 
    { 
        aird=0; 

    } 
protected: 
    int aird;//排量 
}; 
 

class plane:public Vehicle 


protected: 
    float wingspan;//翼展 
}; 
 

void main() 


    Car a; 
    a.EditSC(150,4); 
    cin.get(); 
}

  派生类的定义可以在类名称后加冒号public空格加基类名称进行定义,如上面代码中的class Car:public
Vehicle。

  一旦成功定义派生类,那么派生类就可以操作基类的所有数据成员包括是受保护型的,上面代码中的a.EditSC(100,4);
就是例子,甚至我们可以在构造派生类对象的时候初始化他们,但我们是不推荐这么做的,因为类于类之间的操作是通过接口进行勾通的,为了不破坏类的这种封专装特性,即使是父类于子类的操作也应按遵循这个思想,这么做的好处也是显而易见的,当基类有错的时候,只要不涉及接口,那么基类的修改就不会影响到派生类的操作。

  至于为什么派生类能够对基类成员进行操作,我们上图可以简单的说明基类与子类在内存中的排列状态。

  我们知道,类对象操作的时候在内部构造的时候会有一个隐的this指针,由于Car类是Vehicle的派生类,那么当Car对象创建的时候,这个this指针就会覆盖到Vehicle类的范围,所以派生类能够对基类成员进行操作。

  笔者写到这里的时候不得不提一下,我有开发c#与java的经验,就这两种语言来说,学到这里的时候很多人很难理解继承这一部分的内容,或者是理解的模糊不清,其实正是缺少了与this指针相关的c++知识,多数高级语言的特性是不涉及内存状态的操作,java与c#是接触不到这些知识的,所以理解起这部分内容就更抽象更不具体。

  下面我们来说一下,派生类对象(子类对象)的构造

  由上面的例程我们知道Car类是Vehicle类的派生类(子类),c++规定,创建派生类对象的时候首先调用基类的构造函数初始化基类成员,随后才调用派生类构造函数

  但是要注意的是,在创建派生类对象之前,系统首先确定派生类对象的覆盖范围(也可以称做大小尺寸),上面代码的派生类对象a就覆盖于Vehicle类和Car类上,至于派生类对象的创建是如何构造基类成员的,我们看如下代码,随后再进行分析:

#include <iostream>   
using namespace std; 
 
class Vehicle 

public: 

    Vehicle(float speed=0,int total=0) 
    { 
        cout<<"载入Vehicle类构造函数"<<endl; 

        Vehicle::speed = speed; 

        Vehicle::total = total; 
    } 

    Vehicle(Vehicle &temp) 
    { 
        Vehicle::speed = temp.speed; 
        Vehicle::total = temp.total; 
    } 
    ~Vehicle() 
    { 

        cout<<"载入Vehicle类析构函数"<<endl; 

        cin.get(); 
    } 
protected: 
    float speed;//速度 
    int total;//最大载人量 
}; 
class Car:public Vehicle 

public: 

    Car(float aird=0,float speed = 0,int total = 0):Vehicle(speed,total) 

    { 
        cout<<"载入Car类构造函数"<<endl; 

        Car::aird = aird; 
    } 

    Car(Car &temp):Vehicle(temp.speed,temp.total) 
    { 

        cout<<"载入Car类拷贝构造函数"<<endl; 

        Car::aird = temp.aird; 
    } 

    ~Car() 
    { 
        cout<<"载入Car类析构函数"<<endl; 
        cin.get(); 
    } 
protected: 
    float aird;//排量 
}; 
void main() 

    Car a(250,150,4); 
    Car b = a; 
    cin.get(); 
}

  对象a创建的时候通过Car(float aird = 0,float speed = 0,int total =
0):Vehicle(speed,total),也就是Car类构造函数,来构造Car类对象成员,但按照c++的规定首先应该调用基类构造函数构造基成员,在这里基类成员的构造是通过Vehicle(speed,total),来实现的。

  但值得注意的是Vehicle(speed,total)的意义并不是对Vehicle类的个个成员的初始化,事实上是利用它创建了一个Vehicle类的无名对象,由于Car类对象的覆盖范围是覆盖于Vehicle类和Car类上,所以系统在确定了派生类对象a的空间范围后,确定了this指针位置,这个this指针指向了Vehicle类的那个无名对象,这个成员赋值过程就是,this->speed=无名对象.speed。

  其实这里概念比较模糊,笔者因为个人能力的原因暂时也无法说的更明确了,读者对此处知识点的学习,应该靠自己多对比多练习,进行体会理解。

  许多书籍对于派生类对象的复制这一知识点多是空缺的,为了教程的易读性,我还是决定说一下在复制过程中容易出错的地方,Car
b=a;是派生类对象复制的语句,通过前面教程的学习我们我们知道,类对象的复制是通过拷贝构造函数来完成的,如果上面的例子我们没有提供拷贝构造函数不完整如下:

Car(Car &temp) 

    cout<<"载入Car类拷贝构造函数"<<endl; 

    Car::aird = temp.aird; 
}

  那么iostream> 
using namespace std; 
 
class Vehicle 

public: 

    Vehicle(float speed=0,int total=0) 
    { 
        Vehicle::speed = speed; 
        Vehicle::total = total; 
    } 
protected: 

    float speed;//速度 

    int total;//最大载人量 

}; 
 
class Motor 

public: 
    Motor(char *motor) 

    { 
        Motor::motortype = motor; 

    } 
 
    char* SMT(Motor &temp); 

protected: 
    char *motortype;//发动机型号 
}; 

char* Motor::SMT(Motor &temp) 


    return temp.motortype; 

 
class Car:public Vehicle//继承的体现 

public: 

    Car(float speed,int total,int aird,char *motortype):Vehicle(speed,total),motor(motortype) 

    { 
        Car::aird = aird; 
    } 

 
    Motor rm(Car &temp); 
protected: 

    int aird;//排量 

    Motor motor;//类组合的体现 
}; 
 

Motor Car::rm(Car &temp) 

    return temp.motor; 

 
//-------------------------------------------------------------- 

void test1(Vehicle &temp) 

    //中间过程省略 
}; 
void test2(Motor &temp) 

    cout<<temp.SMT(temp);//读者这里注意一下,temp既是对象也是对象方法的形参 

//-------------------------------------------------------------- 

 
void main() 


    Car a(150,4,250,"奥地利AVL V8"); 
    test1(a); 
    //test2(a);//错误,Car类与Motor类无任何继承关系 

    test2(a.rm(a));//如果Car类成员是public的那么可以使用test2(a.motor) 
    cin.get(); 
}

  在上面的代码中我们新增加了发动机类Motor,Car类增加了Motor类型的motor成员,这就是组合,拥有继承特性的派生类可以操作基类的任何成员,但对于与派生类组合起来的普通类对象来说,它是不会和基类有任何联系的。

  函数调用:test1(a);,可以成功执行的原因就是因为Car类对象在系统是看来一个Vehicle类对象,即Car类是Vehicle类的一种Car类的覆盖范围包含了Vehicle

  函数调用:test2(a);,执行错误的原因是因为Motor类并不认可Car类对象a与它有任何关系,但我们可以通过使用Car类对象a的Motor类成员motor,作为函数形参的方式来调用test2函数(test2(a.motor)),在这里由于类的成员是受保护的所以我们利用a.rm(a)来返回受保护的motor,作为函数参数进行调用。

 
 

《挑战30天C++入门极限》图例实解:C++中类的继承特性的更多相关文章

  1. 《挑战30天C++入门极限》入门教程:实例详解C++友元

        入门教程:实例详解C++友元 在说明什么是友元之前,我们先说明一下为什么需要友元与友元的缺点: 通常对于普通函数来说,要访问类的保护成员是不可能的,如果想这么做那么必须把类的成员都生命成为pu ...

  2. 《挑战30天C++入门极限》C++面向对象编程入门:构造函数与析构函数

        C++面向对象编程入门:构造函数与析构函数 请注意,这一节内容是c++的重点,要特别注意! 我们先说一下什么是构造函数. 上一个教程我们简单说了关于类的一些基本内容,对于类对象成员的初始化我们 ...

  3. 《挑战30天C++入门极限》图文例解C++类的多重继承与虚拟继承

        图文例解C++类的多重继承与虚拟继承 在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念 ...

  4. 《挑战30天C++入门极限》C++类静态数据成员与类静态成员函数

        C++类静态数据成员与类静态成员函数 在没有讲述本章内容之前如果我们想要在一个范围内共享某一个数据,那么我们会设立全局对象,但面向对象的程序是由对象构成的,我们如何才能在类范围内共享数据呢? ...

  5. 《挑战30天C++入门极限》C++类对象的复制-拷贝构造函数

        C++类对象的复制-拷贝构造函数 在学习这一章内容前我们已经学习过了类的构造函数和析构函数的相关知识,对于普通类型的对象来说,他们之间的复制是很简单的,例如: int a = 10; int ...

  6. 《挑战30天C++入门极限》理解C++面向对象程序设计中的抽象理论

        理解C++面向对象程序设计中的抽象理论 很多书在一开始就开始学习josephus问题,为了让大家前面学起来较为容易我把前面涉及到此问题的地方都故意去掉了,现在我们已经学习过了结构体和类,所以放 ...

  7. 《挑战30天C++入门极限》C++面向对象编程入门:类(class)

        C++面向对象编程入门:类(class) 上两篇内容我们着重说了结构体相关知识的操作. 以后的内容我们将逐步完全以c++作为主体了,这也意味着我们的教程正式进入面向对象的编程了. 前面的教程我 ...

  8. 《挑战30天C++入门极限》新手入门:C/C++中的结构体

        新手入门:C/C++中的结构体 什么是结构体? 简单的来说,结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构 ...

  9. 《挑战30天C++入门极限》新手入门:C++中堆内存(heap)的概念和操作方法

        新手入门:C++中堆内存(heap)的概念和操作方法 堆内存是什么呢? 我们知道在c/c++中定义的数组大小必需要事先定义好,他们通常是分配在静态内存空间或者是在栈内存空间内的,但是在实际工作 ...

随机推荐

  1. WPF 不要给 Window 类设置变换矩阵(分析篇):System.InvalidOperationException: 转换不可逆。

    原文:WPF 不要给 Window 类设置变换矩阵(分析篇):System.InvalidOperationException: 转换不可逆. 最近总是收到一个异常 "System.Inva ...

  2. .NET Window服务启动又马上停止,报错IO.FileNotFoundException

    最近公司需要开发一个Window服务推送系统,读取MongoDB写入消息队列,推送到各终端平台 但是在开发完成,最后的部署阶段,选中服务右击启动 看似正常,服务显示已启动(但实质已经被终止,因为Win ...

  3. [winfrom]C#中使用SendMessage

    在C#中,程序采用了的驱动采用了事件驱动而不是原来的消息驱动,虽然.net框架提供的事件已经十分丰富,但是在以前的系统中定义了丰富的消息对系统的编程提供了方便的实现方法,因此在C#中使用消息有时候还是 ...

  4. 【洛谷 P3193】 [HNOI2008]GT考试(KMP,dp,矩阵乘法)

    题目链接 \(f[i][j]\)表示准考证号到第\(i\)位,不吉利数字匹配到第\(j\)位的方案数. 答案显然是\(\sum_{i=0}^{m-1}f[n][i]\) \(f[i][j]=\sum_ ...

  5. mybatis返回map类型数据空值字段不显示的解决方法

    在日常开发中,查询数据返回类型为map,数据库中有些自动值为null,则返回的结果中没有值为空的字段,则如何显示值为空的字段呢? Spring boot + MyBatis返回map中null值默认不 ...

  6. Centos 7.6 双网卡绑定实现高可用

    Centos 7.6 双网卡绑定实现高可用 作者:尹正杰 版权声明:原创作品, 谢绝转载!否则将追究法律责任. 一.Bond模式概述 当linux系统上有多个单独网卡,又想充分利用这些网卡,同时对外提 ...

  7. ISCC之msc5他们能在一起吗?

    如题,开始分析,附件是一个二维码,读出来一串base64 拿去解密,PASS{0K_I_L0V3_Y0u!} 提交几次不对, 继续hxd分析图片,最后在文件末尾发现一个txt 修改后缀,是加密的, 拿 ...

  8. K-D树详解

    K-D树最近邻算法https://blog.csdn.net/image_fzx/article/details/80624968 一般说来,索引结构中相似性查询有两种基本的方式: 一种是范围查询,范 ...

  9. 非正常卸载Chrome浏览器导致无法重新安装

    昨晚在使用selenium的时候,Chrome浏览器和Chromedriver.exe不兼容,前几天使用的时候都没问题,查看浏览器版本后发现浏览器已经自动升级为67版本(我之前安装的是61版本). 于 ...

  10. EF映射——从数据库更新实体

    最近在做ITOO项目,由于更新了数据库,需要重新从数据库映射到实体,本来看过关于EF的学习资料,直接可以从数据库更新到实体,但这种小事也是有很多问题的,必须在更新的时候做好选择.下面分享一下如何从数据 ...