第14章 C++的代码重用
C++和约束
限制程序结构的特性——使用explici防止单参数构造函数的隐式转换,使用const限制方法修改数据,等等。这样做的原因是:在编译阶段出现错误优于在运行阶段出现错误。
14.2 私有继承
1、访问基类的方法
使用私有继承时,只能在派生类的方法中使用基类的方法。
私有继承使得能够使用类名和作用域解析运算符来调用基类的方法。
而使用包含时,将使用对象名来调用方法。
2、访问基类对象
在派生类中访问基类对象本身,需要使用强制类型转换。例如,Student类是从string类派生而来的,因此可以通过强制类型转换,将Student对象转换为string对象;结果为继承而来的string对象。例子如下,(为了避免调用构造函数创建新的对象,可使用强制类型转换来创建一个引用):
const string & Student::Name() const
{return (const string &) *this;
}
3、访问基类的友元函数
用类名显式地限定函数名不适合于友元函数,因为友元函数不属于类。然而,可以通过显式地转换为基类来调用正确的函数。
ostream & operator <<(ostream &os,const Student &stu){os<<" scores for "<<(const String &)stu<<":\n";
}
Student的基类是String,
os<<" scores for “<<(const String &)stu<<”:\n"; 代码显式地将stu转换为String对象引用,进而调用函数operator<<(ostream &,const String &)。
引用stu不会自动转换为String引用。根本原因在于,在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。
7、保护继承
14.2.4 使用 using 重新定义访问权限
使用保护派生类或私有派生时,基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。
double Student::sum() const //Student的 公有方法
{return std::valarray<double >::sum();//使用私有继承方法
}
另一种方法是,将函数调用包装在另一个函数调用中,即使用一个using声明(就像名称空间那样)来指出派生类可以使用特定的基类成员,即采用的是私有派生。例如,假设希望通过Student类能够使用valarray的方法min()和max(),可以在studenti.h的公有部分加入如下using声明:
class Studen:private std::string,private std::valarray<double>{public:using std::valarray<double>::min;using std::valarray<double>::max;
}
using声明只使用成员名——没有圆括号、函数特征和返回类型。
14.3 多重继承
1、虚基类
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。例如,通过在类声明中使用关键字virtual,可以使Worker被用作Singer和Waiter的虚基类(virtual 和public 的次序无关紧要):
class Singer:virtual public Worker{...};
class Waiter:virtual public Worker{...};
然后,可以将SingingWaiter类定义为:
class SingingWaiter :public Singer,public Waiter{...};
现在,SingingWaiter对象将只包含Worker对象的一个副本。从本质上说,继承的Singer和
Waiter对象共享一个Worker对象,而不是各自引入自己的Worker对象副本。因为SingingWaiter现在只包含了一个Worker对象,所以可以使用多态。
2、新的构造函数规则
使用虚基类时,需要对类构造函数采用一种新的方法。对于非虚基类,唯一可以出现在初始化列表中的构造函数是基类构造函数。但这些构造函数可能需要将信息传递给其基类。例如,可能有下面一组构造函数:
class A{int a;
public:A(int n=0):a(n){} ...
};
class B:class A
{int b;
public:B(int m=0,int n=0):A(n),b(m){} ...
};
class C:class B
{int c;
public:C(int q=0;int m=0,int n=0):B(m,n),c(q){} ...
};
C 类的构造函数只能调用B类的构造函数,而B类的构造函数只能调用A类的构造函数。这里,C类的构造函数使用值q,并将值m和n传递给B类的构造函数;而B类的构造函数使用值m,并将值n传递给A类的构造函数。
如果Worker是虚基类,则这种信息自动传递将不起作用,例如,对于下面的多重继承构造函数:
SingingWaiter(const Worker &wk,int p=0,int v=Singer::other) : Waiter(wk,p),Singer(wk,v){...};
存在的问题是,自动传递信息时,将通过2条不同的途径(Waiter和Singer)将wk传递给Worker对象。为了避免这种冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。
如果不希望默认构造函数来构造虚基类对象,则需要显式地调用所需的基类构造函数。因此,构造函数应该是这样的
SingingWaiter(const Worker &wk,int p=0,int v=Singer::other) : Worker(wk),Waiter(wk,p),Singer(wk,v){...};
上诉代码将显式地调用构造函数worker(const Worker &)。请注意,这种用法是合法的。对于虚基类必须这样做,但是对于非虚基类这样做是非法的。
在祖先相同时,使用多重继承必须引入虚基类,并修改构造函数初始化列表的规则。
下面介绍一些有关多继承的问题
1、混合使用虚基类和非虚基类
如果基类是虚基类,派生类将包含基类的一个子对象;如果基类不是虚基类,派生类将包含多个子对象。当类通过多条途径和非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象。
2、虚基类和支配
使用虚基类将改变C++解析二义性的方式。
14.3.3 多继承小结
如果一个类从两个不同的类那里继承了两个同名的成员,则需要在派生类中使用类限定符来区分它们。
如果一个类通过多种途径继承了一个非虚基类,则该类从每种途径分别继承非虚基类的一个实例。
当派生类使用关键字virtual来指示派生时,基类就成为虚基类:
class marketing :public virtual reality{...};
主要变化(同时也是使用虚基类的原因是)是,从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。为实现这种特性,必须满足其他要求:
1、有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的;
2、通过优先规则解决名称二义性。
14.4.3 深入探讨模板类
1、正确使用指针栈
让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。把这些指针放在栈中是有意义的,因为每个指针都将指向不同的字符串。注意,创建不同指针是调用程序的职责,而不是栈的职责。栈的任务是管理指针,而不是创建指针。
Stack类需要包含一个析构函数、一个复制构造函数和一个赋值运算符。另外,通过将多个方法作为内联函数,精简了代码。
stcktp1.h
#ifndef STCKTP1_H
#define STCKTP1_Htemplate<class Type>
class Stack{private:enum {SIZE=10};//默认大小int stacksize;Type * items;//它保存堆的元素int top;// 栈顶元素的索引
public:explicit Stack(int ss=SIZE);Stack(const Stack &st);~Stack(){delete [] items;}bool isempty(){ return top==0;}bool isfull(){return top==stacksize;}bool push(const Type &item);//添加元素到栈bool pop(Type &item);//弹出元素Stack &operator =(const Stack &st);
};
template<class Type>
Stack<Type>::Stack(int ss):stacksize(ss),top(0){items=new Type[stacksize];
}
template<class Type>
Stack<Type>::Stack(const Stack &st)
{stacksize=st.stacksize;top=st.top;items=new Type[stacksize];for(int i=0;i<top;++i){items[i]=st.items[i];}
}template<class Type>
bool Stack<Type>::push(const Type &item)
{if(top<stacksize){items[top++]=item;return true;}else{return false;}}template<class Type>
bool Stack<Type>::pop( Type &item)
{if(top>0){item=items[--top];return true;}else{return false;}}template<class Type>
Stack<Type> & Stack<Type>::operator =(const Stack &st)
{if(this==&st)return *this;delete [] items;stacksize=st.stacksize;top=st.top;items=new Type[stacksize];for(int i=0;i<top;i++)items[i]=st.items[i];return *this;}#endif // STCKTP1_H
main.cpp
#include <iostream>
#include<cstdlib>
#include<ctime>
using namespace std;#include"stcktp1.h"
const int Num=10;
int main()
{//cout << "Hello World!" << endl;std::srand(std::time(0));//随机生成0、1cout<<"please enter stack size:";int stacksize;cin>>stacksize;//创建一个带有stacksize空间的空的栈Stack<const char *> st(stacksize);//in basketconst char *in[Num]={" 1:Hank Gilgamesh"," 2:Kiki Ishatar"," 3:Betty"," 4:Ian"," 5:Kibble"," 6:Koop"," 7:Joy"," 8:Xavarie"," 9:Juan"," 10:Misha",};//out basketconst char * out[Num];int processed =0;int nextin=0;while(processed<Num){if(st.isempty()){st.push(in[nextin++]);}else if(st.isfull()){st.pop(out[processed++]);}else if(std::rand()%2&&nextin<Num)//50 -50 的机会{st.push(in[nextin++]);}else{st.pop(out[processed++]);}}for(int i=0;i<Num;i++){std::cout<<out[i]<<std::endl;}cout<<"bye\n";// return 0;return 0;
}
注意:由于使用了随机特性,每次运行时,文件最后的顺序都可能不同,即使栈大小保持不变。
在上面的程序中,字符串本身永远不会移动。把字符串压入栈实际上是创建一个指向该字符串的指针,即创建一个指针,该指针的值是现有字符串的地址。从栈弹出字符串将把地址值复制到out数组中。
该程序使用的类型是const char *,因为指针数组将被初始化为一组字符串常量。
栈的析构函数对字符串没有影响。构造函数使用New创建一个用于保存指针的数组,析构函数删除该数组,而不是数组元素指向的字符串。
14.4.9 模板类与友元
模板类声明也可以有友元,模板的友元分为3类:
1、非模板友元;
2、约束模板友元,即友元的类型取决于类被实例化时的类型;
3、非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。
1、模板类的非模板友元函数
在模板类中将一个常规函数声明为友元
template<class T>
class HasFriend
{public:friend void counts();//所有HasFriend实例的友元friend void report(HasFriend<T> &);// bound template friend}
counts函数可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象;可以访问独立于对象的模板类的静态数据成员。
report函数本身并不是模板函数,而只是使用一个模板作参数。这意味着必须为要使用的友元定义显式具体化;
void report(HasFriend<short> &){...};//
void report(HasFriend<int> &){...};//
2、模板类的约束模板友元函数
为约束模板作准备,要使类的每一个具体化都获得与友元配匹的具体化
template<typename T> void counts();//模板声明
template<typename T> void report(T &);
//模板类 声明具体化
template<typename TT>
class HasFriend
{public:friend void counts<TT>();//声明具体化friend void report<> (HasFriend<TT> &);// 声明具体化//也可以这样使用 friend void report<HasFriend<TT>> (HasFriend<TT> &);// 声明具体化
}
声明中的<>指出这是模板具体化,对于 report,<>可以为空,因为可以从函数参数推断出如下模板参数:
HasFriend
也可以这样使用 friend void report<HasFriend> (HasFriend &);// 声明具体化
但counts函数没有参数,因此必须使用模板参数语法()来指明其具体化。还需要注意的是,TT是HasFriend类的参数类型。
3、模板类的非约束模板友元函数
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:
template<typename T>
class ManyFriend{...
template<typename C,typename D> friend void show2(C &,D &);
}
仅供学习,侵删。
来源:C++ primer plus
第14章 C++的代码重用相关推荐
- 《代码大全2》第14章 组织直线型代码
目录 前言 14.1 必须有明确顺序的语句 14.1.1 组织语句的原则 14.2 顺序无关的语句 14.2.1 使代码自上而下的阅读 14.2.2 把相关的语句组织到一起 <Code_Comp ...
- 读书笔记_代码大全_第14章_组织直线型代码_第15章_使用条件语句
组织直线型代码 + 使用条件语句 希望我的读书笔试能带你翻过18页的书 http://www.cnblogs.com/jerry19880126/ <代码大全>第14章和第15章的内容比较 ...
- 信安教程第二版-第14章恶意代码防范技术原理
第14章恶意代码防范技术原理 14.1 恶意代码概述 261 14.1.1 恶意代码定义与分类 261 14.1.2 恶意代码攻击模型 262 14.1.3 恶意代码生存技术 263 14.1.4 恶 ...
- 第十九章 19 利用私有继承来实现代码重用
//19 利用私有继承来实现代码重用 //我们还有一种方法来实现这种包含式的代码重用,那就是私有继承,派生类从基类私有继承后,其类的公有成员和保护成员在派生类中都是私有成员,私有成员成为不可访问的成员 ...
- 【C++ Primer】第十四章 C++中的代码重用
序:C++的一个主要目标是促进代码重用,其中包含公有继承.包含.使用私有或保护继承 一,包含对象成员的类 1)valarray类简介 #include <valarray> ...
- Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理
Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 Oracle PL/SQL 程序设计读书笔记 - 第14章 DML和事务管理 ACID原则:即一个事务具有原子性.一致性. ...
- 一起来学C++:C++中的代码重用
目录 14.1 包含对象成员的类 14.1.1 valarray类简介 14.1.2 Student类的设计 14.1.3 Student类示例 1.初始化被包含的对象 2.使用被包含对象的接口 3. ...
- 第五章 函数和代码复用
第五章 函数和代码复用 5.1 函数的基本使用 5.1.1 函数的定义 定义:函数是一段具有特定功能的.可重用的语句组,用函数名来表示并通过函数名进行功能调用. 使用函数的目的:降低编程难度和代码重用 ...
- 《JavaScript权威指南第7版》第14章 元编程
第14章 元编程 14.1 属性特性 (Property Attributes) 14.2 对象扩展性 14.3 prototype特性(原型特性) 14.4 内置Symbol 14.4.1 Symb ...
最新文章
- 【D3】transition API
- The j.u.c Synchronizer Framework翻译(三)使用、性能与总结
- Kali Linux缺少ifconfig命令
- c与python的区别-c语言和python的区别是什么
- oracle排序后的第一条记录
- C语言 03-第一个C程序代码分析
- C语言再学习 -- 存储类、链接
- ESFramewor使用技巧(2)-- 在插件中使用NHibernate
- ストアドプロシージャ(存储过程)
- java代码杨辉三角_用java实现杨辉三角的示例代码
- LeetCode 1124. 表现良好的最长时间段(单调栈/哈希)
- 2.图像作为函数 | 生成高斯噪音_8
- java mysql tomcat my_Java、Tomcat 及 MySQL 环境配置
- 数据算法可视化学习网站
- 如果彩虹QQ算非法外挂,那么运行在windows上的非微软开发的程序算什么?
- java 登陆拦截_登录拦截 - java代码库 - 云代码
- 虚拟机Windows10下载安装保姆级教程
- 3D人体骨架检测(mediapipe)
- 何同学采访苹果CEO库克上热搜,网友表示自愧不如
- 【Android工具】音频频率发生器,声音测试,音响测试,各种频率声音合成工具...
热门文章
- thinkphp6 用PHPMailer实现邮箱发送功能
- org.apache.commons.lang3.StringUtils.isNotBlank和isEmpty方法
- 十大高性价比运动蓝牙耳机盘点,2020高性价比蓝牙耳机走心推荐
- 黑白激光打印机打印时出现黑道,怎么办?
- [渝粤教育] 武汉体育学院 中国少数民族神话赏析 参考 资料
- csstable跨列居中_HTML表格跨行、跨列操作(rowspan、colspan)
- 嵌入式面经汇总(一)
- 电脑一键重装系统卡在正在准备就绪怎么办
- 基于MDKA5D31-EK_T70开发板的QT示例-demo06:软键盘
- pdb跳出for循环