第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_H

template<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、1
    cout<<"please enter stack size:";
    int stacksize;
    cin>>stacksize;
    //创建一个带有stacksize空间的空的栈
    Stack<const char *> st(stacksize);

    //in basket
    const 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 basket
    const 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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值