C++ primer Plus 第十四章 C++中的代码重用

 

14.1包含对象成员的类

当我们在描述has-a问题时,我们可以使用类里包含一个类的方法来描述这种问题。

例如,student类中有学生的姓名和分数,

我们可以在student类创建一个string类的name对象,和valarray类(这个类类似于数组,但是功能比数组强大)的scores对象

这种方法比较简单,可以直接在student类的成员函数里使用string类和valarray的方法(valarray用法)来实现相应的操作,但是这种方法也有其缺点,无法使用虚函数

14.2私有继承

另一种方法是使用私有继承。

使用私有继承,基类的公有成员和保护成员将称为派生类的私有成员,这意味着基类方法不会称为派生类对象的公有接口的一部分,但是派生类的成员函数里可以使用它

下面给出使用私有继承来实现student类的代码

1.基本框架

class student:private string,private valarray<double>
{
private:
    typedef valarray<double> arrayDb;//这里用typedef简化一下
public:
    
};

基本框架如上,使用private关键字,由于成绩是浮点数,所以继承的是valarray<double>类,<double>指的是存储double类型数据。新的student类不需要提供私有数据,因为私有继承已经提供了两个无名称的子对象成员,这是与第一个方法的第一个主要区别

2.初始化基类组件

如果我们是students里包含着一个 stirng类的name对象,和valarray的scores对象

我们完全可以这样来初始化

student(const char *str,const double *pd,int n):name(str),scores(pd,n){}

 由于我们是私有继承,并没有提供名称,所以我们不能用name 和scores来初始化,这也是第二个主要区别

student(const char *str,const double *pd,int n):string(str),arrayDb(pd,n){}

 

3.访问基类的方法

私有继承只能在派生类的成员函数中使用基类的方法,由于没有名称,所以要用类名和作用域解析运算符来调用基类的方法

double student::Average()const
{
    if(arrayDb::size()>0)
    {
        return arrayDb::sum()/arrayDb::size();
    }
    else
    {
        return 0;
    }
}

4.访问基类的对象

使用私有继承时,string对象没有名称,那么student类的代码如何访问内部的string对象呢?

答案是使用强制类型转换。

const string& student::Name()const
{
    return (const string &)*this;
}

可以使用这个返回的引用来访问基类的对象

5.访问基类的友元函数

通过显式地转换位基类来调用正确的函数

ostream &operator <<(ostream &os,const student &stu)
{
    os<<(const string &)stu<<endl;
}

显式地将stu转换为string对象引用,进而调用函数operator<<(ostream&os,const stirng&)

引用stu不会隐式的转换为string引用,因为私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针。

14.2.2使用包含还是私有继承

通常使用包含来建立has-a关系,如果新类需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承

14.2.3保护继承

使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员,保护成员在类外无法使用。

 

14.2.4使用using重新定义访问权限

我们希望在派生类的成员函数中像基类一样访问成员函数,我们可以使用using声明

class student:private string,private valarray<double>
{
private:
    typedef valarray<double> arrayDb;
    using valarray<double>::min;
    using valarray<double>::max;
public:
    student(const char *str,const double *pd,int n):string(str),arrayDb(pd,n) {}
};

这样可以使min和max像student的公有方法一样,可以直接调用。

注意事项:using声明只适用于继承,并不适用于包含

14.3多重继承

多重继承意味着我们可以继承多次,只需要将继承的类用逗号分隔开即可

14.3.1有多少父类

但是可能存在下面这种情况

这种情况子类C中就会有两部分父类中的成员,这将引发一个问题,如下

D x;
A *p = &x;
//这时候会发生二义性问题,因为有两部分A的成员,p不知道该指向哪一个了

我们可以通过显式类型转换解决这个问题

D x;
A *p = (B*)&x;
A *q = (C*)&x;

虚基类

虚基类可以使得从多个类中派生出的对象只继承一个基类对象,例如,如果A是虚基类,那么D中只会有一部分A中的成员。

基类是虚基类的时候,C++禁止信息通过中间类自动传递给基类,并且虚基类的构造函数先于派生类的构造函数执行。

所以我们在最终派生类的构造函数中,可以直接在初始化列表里给虚基类的构造函数传值,这一点对于非虚基类是非法的

#include <iostream>

using namespace std;
class A
{
    int a;
public:
    A();
    A(int aa):a(aa){}
    void display()
    {
        cout<<a<<endl;
    }
};
class B:virtual public A
{
    int b;
public:
    B();
    B(int aa = 0,int bb = 0):A(aa),b(bb){}

};
class C:virtual public A
{
    int c;
public:
    C();
    C(int aa = 0,int cc = 0):A(aa),c(cc){}
};
class D:public B,public C
{
public:
    D();
    D(int aa = 0,int bb = 0,int cc = 0):A(aa),B(aa,bb),C(aa,cc){}

};
int main()
{
    D x(1,2,3);
    x.display();
    return 0;
}

其他的注意事项

当B类被用作C和D的虚基类,同时被用做X和Y的非虚基类,此时M类是从C、D、X和Y那派生来的,那么M类中有3个B部分成员

14.4类模板

类模板与函数模板类似,对于处理不同数据类型的类,我们可以给它编写一个泛型的栈,然后将具体类型作为参数传递给这个类。

模板提供了参数化类型,可以将类型名作为参数传递给接收方建立类或者函数,例如我们将int传递给Queue模板,Queue模板将创建一个对int类型进行排队的Queue类。

14.4.1定义类模板

使用template<class Type>来定义一个类模板

具体语法如下

template<class T>
class Stack
{
private:
    const static int MAX = 10;//或 enum{MAX = 10}
    T items[MAX];
    int top;
public:
    Stack();
};

template<class T>
Stack<T>::Stack()
{

......

}

需要在类的声明前和类的成员函数前加上template<class T>,作用域解析运算符前需要使用Stack<T>的形式

14.4.2使用模板类

仅在程序包含模板并不能生成没模板类,必须请求实例化

例如: Stack<int> fun  Stack<long long> happy

看到声明后,编译器将按照Stack<T>模板来生成独立的类声明和两组独立的类方法,届时将用int来替换类里所有的T

14.4.5模板的多功能性

模板类可以作为基类,也可以作为组件类,还可以作为其他模板的类型参数。

1.递归使用模板

Array<Array<int,5>10> a;

就相当于 

int a[10][5];

2.使用多个类型参数

可以使用多个类型参数,例如

template<class T,class V>

3.默认类型模板参数

template <class T = int>

class Stack

则可以这样做: Stack<>  a;//创建一个int类型的栈

14.4.6模板的具体化

1.隐式实例化

模板类默认就是进行的隐式实例化,即编译器碰到创建具体类型对象时,才进行创建相应类型的类声明

Stack<int> a;//会创建int类型的栈的类
Stack<double> *p;//不会创建
p = new Stack<double> //会创建int类型的栈的类

2.显式实例化

使用关键字指出所需类型来声明类的时候,编译器将生成类声明的显式实例化,这种情况下,虽然没有创建和提及类对象,但是编译器也将生成类声明

template<class T= int>
class Stack
{
private:
    const static int MAX = 10;
    T items[MAX];
    int top;
public:
    Stack();
};

template class Stack<double>;//显式实例化

3.显式具体化

对于特定类型,模板中的某些地方需要修改,我们可以使用显式具体化,例如,比较字符串不能通过简单的重载运算符比较,而是要使用strcmp函数,这时候需要用到显示具体化

template<> 
class Stack<const char* >
{
    ......
}

当编译器遇到char类型时,将使用这个具体化的模板,而不是泛式的模板

4.部分具体化

部分具体化即部分限制模板的通用性

1.给类型参数指以指定具体的类型

//隐式实例化
template<class T1,class T2> 
class car
{


}
//部分显示具体化
template<class T1> 
class car<T1,int>
{


}

这里注意,<>里放的是没有被具体化的参数,如果<>里为空,部分显式具体化就变成显式具体化了。

如果有多个模板可供选择,编译器将选择具体化程度最高的模板。

2.部分具体化的其他操作

template<class T1,class T2> car<T1,T2,T2>
{

........

}
template<class T1> car<T1,T1*,T1*>
{

........

}

14.4.7成员模板

模板可用做结构 类或模板类的成员。

template<class T>
class fun
{
private:
    template<class V>
    class happy
    {
    private:
        V a;
    public:
    }
public:
    fun();
};

很老的编译器不接受模板成员,而另一些编译器接受模板成员,但是不接受类外面的定义,如果你的编译器接受类外面的定义,则可以使用下列语法来在类外使用模板成员

#include <iostream>

using namespace std;

template<class T>
class fun
{
private:
    template<class V>
    class happy;

public:
    fun();
};

template<class T>
template<class V>
class fun<T>::happy
{
private:
    V a;
public:
    happy(V aa);
};


template<class T>
template<class V>
class fun<T>::happy(V aa)
{
    a = aa;
};

 

14.4.8将模板作为参数

模板可以包含类型参数(typename T)和非类型参数(int n 用于传数组元素个数),还可以包含模板的参数

如 

#include <iostream>

using namespace std;
template <class T>
class king//一个模板类
{

};

template<template<class T> class Ting>
class crab
{
private:
    Ting<int> s1;
    Ting<double> s2;
public:
    carb();
};
int main()
{
    crab<king> s;
    //king会通过Ting传进类内,于是 Ting<int> s1 -->King<int> s1;
    //Ting<double> s2 -->King<double> s2
    crab<stack> s2;
    return 0;
}

未完待续

发布了37 篇原创文章 · 获赞 3 · 访问量 2364

猜你喜欢

转载自blog.csdn.net/Stillboring/article/details/105430081
今日推荐