第十三章 类继承

继承的基本概念

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

表 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. 数据结构08——Trie

    一.什么是Trie? Trie树,一般被称为字典树.前缀树等等,Trie是一种多叉树,这个和二分搜索树.堆.线段树这些数据结构不一样,因为这些都是二叉树.,Trie树除了是一种多叉树,它是一种哈希树的 ...

  2. [译]C语言实现一个简易的Hash table(3)

    上一章,我们讲了hash表的数据结构,并简单实现了hash表的初始化与删除操作,这一章我们会讲解Hash函数和实现算法,并手动实现一个Hash函数. Hash函数 本教程中我们实现的Hash函数将会实 ...

  3. Vue基础学习(纯属个人学习的笔记,慢慢新增)

       1.在html文件中,声明了template对象,那么在 data对象中的v-html和v-text的绑定数据是不起作用的 2.v-的几个常用绑定 v-html和v-text:引用的conten ...

  4. P2P借款人信用风险实时监控模型设计

    P2P借款人信用风险实时监控模型设计 P2P网络贷款(“peer-to-peer”)为中小企业和个人提供了便利的融资渠道.近年来,随着互联网金融的逐步发展,P2P网贷已成为时下炙手可热的互联网金融新模 ...

  5. IOTutility 一个轻量级的 IOT 基础操作库

    IOTutility 一个轻量级的 IOT 基础操作库 Base utility for IOT devices, networking, controls etc... IOTutility 的目的 ...

  6. 多线程深入理解和守护线程、子线程、锁、queue、evenet等介绍

    1.多线程类的继承 import threading import time class MyThreading(threading.Thread): def __init__(self,n): su ...

  7. hello,Python

    Python无疑是近年来程序语言届最闪亮的明星.2018年Python被TIOBE授予年度编程语言称号,在一月的排行榜中也雄踞第三位,打破了Java C C++长期以来所保持的三强局面 对比笔者以前学 ...

  8. Kettle-6.1安装部署及使用教程

    一.Kettle概念 Kettle是一款国外开源的ETL工具,纯java编写,可以在Window.Linux.Unix上运行,绿色无需安装,数据抽取高效稳定. Kettle 中文名称叫水壶,该项目的主 ...

  9. 20155301第十二周java课程程序

    20155301第十二周java课程程序 内容一:在IDEA中以TDD的方式对String类和Arrays类进行学习 测试相关方法的正常,错误和边界情况 String类 charAt split Ar ...

  10. Mysql:查询当天、今天、本周、上周、本月、上月、本季度、本年的数据

    1. 今天 select * from 表名 WHERE TO_DAYS(时间字段名) = TO_DAYS(NOW()); 2. 昨天 3. 本周 SELECT * FROM 表名 WHERE YEA ...