第十三章 类继承

继承的基本概念

类继承是指从已有的类派生出新的类。例:

表 0-1 player.h

class player

{

private:

string firstname;

string lastname;

bool hasTable;

public:

player(const string & fn="NULL", const string & ln= "none", bool ht = false);

void Name() const;

bool HasTable() const{return hasTable;};

void ResetTable(bool v) {hasTable = v;};

};

表 0-2 TableTennisClass.h

class TableTennisClass: public Player

{

private:

unsigned int rating;

public:

TableTennisPlayer(unsigned int r=0, const string &fn ="none",

const string &ln ="none",bool ht=false);

unsigned int Rating() const {return rating};

}

以上的方法称为公有继承,派生类对象存储了基类的数据成员和基类的方法。

派生类不能直接访问基类的私有成员,必须通过基类方法访问。创建派生类对象的时候,程序首先创建基类对象。基类对象应在程序进入派生类构造函数前被创建。C++使用成员初始化列表来完成这种工作。

表 0-3 C++初始化列表

TableTennisPlayer::TableTennisPlayer(unsigned int r=0, const string &fn ="none",

const string &ln ="none",bool ht=false):player(fn,ln,ht);

使用派生类构造函数的参数创建基类。

如果不指定调用基类的构造函数,程序将调用默认的基类的构造函数。

派生类对象过期时,程序首先调用派生类的析构函数,再调用基类的析构函数。

is-a继承关系:派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类执行。

多态公有继承

多态:希望同一个方法在基类和派生类中的行为是不同的,我们可以使用虚方法来实现这种功能。

我们使用brass类和brassplus类说明

表 0-4 brass.h

#include<string>

class brass

{

private:

std::string fullName;

long acctNum;

double balance;

public:

brass(const std::string & s= "NullBody", long an=-1,

double bal =0.0,);

void Despoit(double amt);

virtual void Withdraw(double amt);

double Balance() const;

virtual void ViewAcct() const;

 

}

表 0-5 brassplus.h

class brassplus:public brass

{

private:

double maxLoan;

double rate;

double owesBank;

public:

brass(const std::string & s= "NullBody", long an=-1,

double bal =0.0, double ml=500,

double r=0.1125);

brassplus(const brass & ba, double ml=500,double r=0.1125);

virtual void ViewAcct() const;

virtual void Withdraw(double amt);

void ResetMax(double m) {maxLoan=m};

 

}

当定义基类方法为虚方法后,可以在继承类中重写此方法。通过对象调用虚方法,不能显示出虚方法的特性,当使用应用或者指针调用时,会显示出虚方法的多态性。

表 0-6 虚方法特性

cons tint CLIENTS=2;

brass* clients[CLIENTS];

clients[0]=new brass("Tom",123456,1000);

clients[1]=new brassplus(*clients[0],1000,0.0125);

//虚方法的多态性

clients[0]->ViewAcct(); //调用brass类的ViewAcct()函数

clients[1]-> ViewAcct();; //调用brassplus类的ViewAcct()函数

虚析构函数

若基类的析构函数不是虚的,delete clients[1];将执行~brass();这是不对的,若~brass声明为virtual ~brass();将执行brassplus的析构函数。

动态联编与静态联编

函数名联编:将源代码中的函数调用解释为执行特定的函数代码快。

在在编译阶段进行联编。但是,使用虚函数后,不能在编译阶段决定使用哪个函数,所以,编译器必须生成能够在程序运行阶段时选择正确虚方法的代码,这被称为动态联编。

虚函数的工作原理

编译器处理虚函数的方式是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。基类和派生类对象均有一个独立的虚函数表的指针,如果派生类没有重定义虚函数,保存函数原始版本的地址。

虚函数使用注意事项

基类中声明的虚函数在基类和所有派生类中都是虚的。

如果定义的类被用作基类,则应将那些在派生类中需要重定义的类方法声明为虚的。

构造函数不能是虚的。派生类不继承基类的构造函数。

基类的析构函数应该是虚的。

如果派生类没有重新定义函数,将使用该函数的基类版本,如果派生类位于派生链中,则将使用最新的虚函数版本。

如果基类声明重载了,那么派生类中应该重定义所有的基类版本。如果之定义部分版本,其余版本将隐藏。

抽象基类

含有纯虚函数的类,此类不允许实例化,只能用来作为基类。

纯虚函数:声明的结尾加上=0;例:virtual double Area() const=0;

纯虚函数可以在源文件中定义。

继承和动态内存分配

如果基类动态内存分配,并重新定义赋值和复制构造函数。如果派生类不使用动态内存分配,不需要为派生类定义显式析构函数、复制构造函数和赋值运算符。

如果派生类使用动态内存分配,则应为派生类定义显式析构函数、复制构造函数和赋值运算符。如下表所示:

表 0-7 baseDMA.h

//Base Class of Dynamic Memory Allocation

#ifndef BDMA_H_

#define BDMA_H_

#include<iostream>

class baseDMA

{

private:

char *label;

int rating;

public:

baseDMA(const char *l="null", int r = 0);

baseDMA(const baseDMA& rs);

virtual ~baseDMA();

baseDMA & operator=(const baseDMA & rs);

friend std::ostream & operator<<(std::ostream & os, const baseDMA &rs);

};

#endif

表 0-8 base.cpp

#include "baseDMA.h"

#include<cstring>

 

baseDMA::baseDMA(const char *l,int r)

{

label=new char[std::strlen(l)+1];

std::strcpy(label,l);

rating=r;

}

 

//复制构造函数

baseDMA::baseDMA(const baseDMA & rs)

{

label=new char[std::strlen(rs.label)+1];

std::strcpy(label,rs.label);

rating=rs.rating;

}

 

//析构函数

baseDMA::~baseDMA()

{

delete [] label;

}

 

//赋值操作符的重载

baseDMA & baseDMA::operator=(const baseDMA &rs)

{

//若是对象自己赋给自己

if(this==&rs)

return *this;

//否则,逐个属性赋值

delete [] label;

label=new char[std::strlen(rs.label)+1];

std::strcpy(label,rs.label);

rating=rs.rating;

return *this;

}

 

//<<操作符重载(友元函数)

std::ostream & operator<<(std::ostream & os, const baseDMA &rs)

{

os<<"Label"<<rs.label<<std::endl;

os<<"Rating"<<rs.rating<<std::endl;

return os;

}

 

表 0-9 deriveDMA.h

#ifndef DDMA_H_

#define DDMA_H_

class deriveDMA

{

private:

char *style;

public:

deriveDMA(const char * s="none", const char *l="null",int r=0);

deriveDMA(const char *s, const baseDMA & rs);

deriveDMA(const deriveDMA & ds);

~deriveDMA();

deriveDMA & operator=(const deriveDMA &rs);

friend std::ostream & operator<<(std::ostream);

};

#endif

表 0-10 deriveDMA.cpp

#include "deriveDMA.h"

#include <cstring>

 

deriveDMA::deriveDMA(const char *s, const char *l, int r):baseDMA(l,r)

{

style=new char[std::strlen(s)+1];

std::strcpy(style,s);

}

 

deriveDMA::deriveDMA(const char * s,const baseDMA & rs):baseDMA(rs)

{

style=new char[std::strlen(s)+1];

std::strcpy(style,s);

}

 

//重定义复制构造函数

deriveDMA::deriveDMA(const deriveDMA & ds)

{

style=new char[std::strlen(ds.style)+1];

std::strcpy(style,ds.style);

}

 

//析构函数

deriveDMA::~deriveDMA()

{

delete [] style;

}

 

//重定义赋值操作符

//利用派生类对象调用基类赋值操作符初始化派生类中的基类成员

deriveDMA & operator=(const deriveDMA & ds)

{

//如果是自我赋值

if(this==&ds)

return *this;

delete [] style;

//使用派生类对象初始化基类对象

//等价于*this=ds;

baseDMA::operator(ds);

style=new char[std::strlen(ds.style)+1];

std::strcpy(style,ds.style);

return *this

}

 

//重载<<运算符

std::ostream & operator<<(std::ostream & os, const deriveDMA & ds)

{

//由于<<重载通过友元函数定义,而派生类不继承基类的友元函数

//所以使用强制类型转化将派生类对象转化为基类对象的引用

//以调用基类的<<运算符(友元函数)

os<<(const baseDMA &)ds;

os<<"Style"<<ds.style<<std::endl;

return os;

}

总结

派生类不继承基类的构造函数、析构函数和赋值运算符。

如果派生类使用了new运算符,则必须提供显式的赋值运算符,必须为类的每个成员提供赋值运算符。

友元函数并非成员函数,因此不能被继承。

C++ Primer Plus学习:第十三章的更多相关文章

  1. C++ Primer Plus学习:第二章

    C++入门第二章:开始学习C++ 进入C++ 首先,以下是一个C++程序: //myfirst.cpp 显示一行文字 #include<iostream> //预处理器编译指令 int m ...

  2. C++ Primer Plus学习:第九章

    C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...

  3. C++ Primer Plus学习:第一章

    C++入门第一章:预备知识 C++简介 C++融合了三种不同的编程方式: C语言代表的过程性语言. C++在C语言基础上添加的类代表的面向对象语言. C++模板支持的泛型编程. C++简史 20世纪7 ...

  4. C Primer Plus 学习 第四章

    字符串与格式化输入/输出 函数 strlen() 关键字 const 利用#define 和 const创建符号常量 #include <stdio.h> #include <str ...

  5. C Primer Plus 学习 第三章

    这里只记录我自己以前不懂得地方,明白的地方就略过了 位  字节  字 位    0,1 字节  8位  也就有8位0,1的组合   2的8次方的组合 字      设计计算机时给定的自然存储单元.8位 ...

  6. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画 学习目标 熟悉蒙皮动画的术语: 学习网格层级变换 ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 代码工程 ...

  8. C Primer Plus 学习笔记 -- 前六章

    记录自己学习C Primer Plus的学习笔记 第一章 C语言高效在于C语言通常是汇编语言才具有的微调控能力设计的一系列内部指令 C不是面向对象编程 编译器把源代码转化成中间代码,链接器把中间代码和 ...

  9. 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记

    第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...

随机推荐

  1. PhpStorm 查看当前类中所有的方法

    展示当前类中的所有方法        Ctrl + F12 方法之间移动                          alt  +   向上箭头/向下箭头

  2. C++中一个0xC0000005访问冲突问题

    在冯.诺依曼结构CPU(如i386,ARM A8,A9)的保护模式或者哈佛结构(如8051, ARM M0,.. M3)的CPU下,C++编译器将放置常量的内存设置为只读模式或者放入只读内存中,如果出 ...

  3. SQL注入科普

    技术交流,安全交友联系渔夫”小白“,微信号(xz116035) SQL注入介绍 SQL注入攻击是最为常见的Web应用安全漏洞之一,国外知名安全组织OWASP针对web应用安全漏洞进行了一个排名,SQL ...

  4. Linux入门第五天——shell脚本入门(中)基础语法之判断与条件

    一.判断式 利用 test 命令进行执行结果的判断(例如判断是否存在该文件):关于test  test:test 示例:结合回传值 $? 进行判断:关于$?:$? [root@localhost tm ...

  5. Oracle下建立dblink时的权限问题

    如果用普通用户,如果没授权,是无法建立dblink的: [oracle@oracle000 ~]$ sqlplus gao/gao lines) SQL Production :: Copyright ...

  6. [arc082F]Sandglass

    Description 传送门 Solution 这题是真的666啊... 以下是本题最关键最关键的结论:如果ai<=aj,则在某个时间t,前者的A中沙子克数(记为t(ai))一定大于等于t(a ...

  7. python 多线程笔记(4)-- 车站售票模拟

    import threading import time import random class Worker(threading.Thread): '''售票员''' def __init__(se ...

  8. 微服务介绍及Asp.net Core实战项目系列之微服务介绍

    0.目录 整体架构目录:ASP.NET Core分布式项目实战-目录 一.微服务选型 在做微服务架构的技术选型的时候,我们以“无侵入”和“社区活跃”为主要的考量点,将来升级为原子服务架构.量子服务架构 ...

  9. 【excle基础】如何去掉excel某一列中的字段的空格

    如图所示,想要去掉A列的空格: 查找空格,全部替换

  10. 搞懂.NET Framework 历史版本(2017年)

    最近被.NET平台各种名词.以及各种版本弄得有些疑惑,开发和部署,对于开发平台版本选择是个基本问题,因此,花了些时间,学习汇总了有关.NET版本演进的历史. .NET简介 这个平台相信我们都知道,不过 ...