C++ Primer Plus学习:第十三章
第十三章 类继承
继承的基本概念
类继承是指从已有的类派生出新的类。例:
表 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学习:第十三章的更多相关文章
- C++ Primer Plus学习:第二章
C++入门第二章:开始学习C++ 进入C++ 首先,以下是一个C++程序: //myfirst.cpp 显示一行文字 #include<iostream> //预处理器编译指令 int m ...
- C++ Primer Plus学习:第九章
C++第九章:内存模型与名称空间 C++在内存中存储数据方面提供了多种选择.可直接选择保留在内存中的时间长度(存储持续性)以及程序哪一部分可以访问数据(作用域和链接)等. 单独编译 程序分为三个部分: ...
- C++ Primer Plus学习:第一章
C++入门第一章:预备知识 C++简介 C++融合了三种不同的编程方式: C语言代表的过程性语言. C++在C语言基础上添加的类代表的面向对象语言. C++模板支持的泛型编程. C++简史 20世纪7 ...
- C Primer Plus 学习 第四章
字符串与格式化输入/输出 函数 strlen() 关键字 const 利用#define 和 const创建符号常量 #include <stdio.h> #include <str ...
- C Primer Plus 学习 第三章
这里只记录我自己以前不懂得地方,明白的地方就略过了 位 字节 字 位 0,1 字节 8位 也就有8位0,1的组合 2的8次方的组合 字 设计计算机时给定的自然存储单元.8位 ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画 学习目标 熟悉蒙皮动画的术语: 学习网格层级变换 ...
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader)
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十三章:计算着色器(The Compute Shader) 代码工程 ...
- C Primer Plus 学习笔记 -- 前六章
记录自己学习C Primer Plus的学习笔记 第一章 C语言高效在于C语言通常是汇编语言才具有的微调控能力设计的一系列内部指令 C不是面向对象编程 编译器把源代码转化成中间代码,链接器把中间代码和 ...
- 《Linux命令行与shell脚本编程大全》 第二十三章 学习笔记
第二十三章:使用数据库 MySQL数据库 MySQL客户端界面 mysql命令行参数 参数 描述 -A 禁用自动重新生成哈希表 -b 禁用 出错后的beep声 -B 不使用历史文件 -C 压缩客户端和 ...
随机推荐
- Redis之配置文件redis.conf
解读下 redis.conf 配置文件中常用的配置项,为不显得过于臃长,已选择性删除原配置文件中部分注释. # Redis must be started with the file path as ...
- 用GO写一个后台权限管理系统
最近用GO写了一个后台权限管理系统,在WIN10和ubuntu下部署,在win系统下编译ububtu的部署文件要先做如下配置 set GOARCH=amd64 set GOOS=linux go bu ...
- Postgresql 远程连接配置
原文地址:http://blog.chinaunix.net/uid-20684384-id-1895247.html 1. 设置远程访问认证机制 编辑 $POSTGRES/data/pg_hba.c ...
- day2 Ubuntu配置源
1.寻找国内镜像源 https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ 2.配置source list源 sources.list系统自带的,源是来U ...
- 【HNOI2014】世界树
题面 题解 虚树好题(只是细节太多) 构出虚树后,一定要仔细梳理关键点之间的点是上面属于父亲,下面属于儿子. 然后二分出所有的点的所属就可以了 代码 #include<cstdio> #i ...
- SRM 563 500pts SpellCards
SpellCards 题意: 有n张符卡排成一个队列,每张符卡有两个属性,等级li和伤害di. 两种操作: 1.把队首的符卡移动到队尾:2.使用队首的符卡,对敌人造成di点伤害,并丢弃队首的li张符卡 ...
- P3374 【模板】树状数组 1(单点增减,区间求和)
P3374 [模板]树状数组 1 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别表示 ...
- android 图片二维码识别和保存(一)
最新业务开发二维码识别的功能,这个功能,在很多应用上都有,比如微信长按图片识别二维码,如果图片中存在可以识别的二维码时,可以增加一个选项 识别二维码.那么如何去实现这个功能呢.这里其实也非常简单,首先 ...
- xshell连接虚拟机linux系统失败问题
问题:在xshell新建对话弹出的对话框中输入ip地址后,确定并没有弹出输入用户名和密码对话框 直接显示连接失败 Could not connect to ): Connection failed. ...
- PHP序列化serialize()和反序列化unserialize()
所谓的序列化,就是把保存在内存中的各种对象状态或属性保存起来,在需要时可以还原出来. serialize() 可处理除了 resource 之外的任何类型返回字符串,此字符串包含了表示 value 的 ...