C++ Primer Plus 14章 代码重用

C++ Primer Plus 14章 代码重用

主要内容:

has-a 关系

包含对象成员的类

模板类valarray

私有和保护继承

多重继承

虚基类

创建类模板

使用类模板

模板的具体化

接口与实现的关系

​ 使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。

​ 使用组合时(包含对象成员的类),类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。

1.包含对象成员的类

假设有两个类,B 和A,且 B has A

上面就是has-a关系,即B类中含有A类的成员

举个具体的代码例子:

class Student
{
private:
    string name;
    valarray<double> scores;
}

从上面可以看出,Student类中,包含着string类成员 name 和valarray类的成员 score,这就叫包含对象成员的类。

在上述类中,通过name可以调用string类的公有方法,而通过scores可以调用valarray类的公有方法。但在类的外面不能这样做,即我们不能通过student类对象去调用string类和valarray类的方法。

(1)但是怎么去初始化这种包含对象成员的类对象呢?

​ 答案:还是使用构造函数的初始化成员列表。

​ 对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。

​ 对于成员对象,构造函数则使用成员名

​ 初始化顺序是声明的顺序,而非列表中的顺序。

//使用成员名
Student(const char * str,const double *pd, int n)
    :name(str),scores(pd,n){}

(2)使用被包含对象的接口

被包含的对象的接口不是公有的,但可以在类方法中使用它

例如:

double Student::Average() const
{
    if(scores.size()>0)
        return scores.sum()/scores.size();
    else
        return 0;
}

Student对象调用Student方法,而Student方法使用被包含的valarray对象来调用valarray类的方法。

valarray类

#include支持

valarray name;

类似于vector和array类;

valarray v1;//创建长度为0的空数组

valarray v2(8);//指定长度的空数组

valarray v3(10,8);//所有元素度被初始化为指定值的数组,8个int元素,每个的值都是10

2.私有继承

使用private关键字就可以实现私有继承

例如:

Class Student:private std::string, private std::valarray<double>
{
    public:
}

(1)初始化:

//使用类名,这是和包含对象的区别之一
Student(const char * str,const double *pd, int n)
    : std::string(str), std::valarray<double> scores(pd,n){}

(2)访问基类方法:

使用私有继承时,只能在派生类的方法中使用基类的方法。但是可以使用类名和作用域解析符类调用基类

例子:

dobule Student::Average() const
{
    if(valarray<double>::size()>0)
        return valarray<double>::sum()/valarray<double>::size();
    else
        return 0;
}

使用包含对象的类时,通过对象名来调用方法;

使用私有继承时,通过里类名和作用域解析运算符来调用方法;

(3)访问基类对象

Student类的代码如何访问内部的string对象?

使用强制类型转换:

Name()是string对象的函数

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

(4)访问基类的友元函数

显示的转换为基类

ostream & operator<<(ostream &os, const Student & stu)
    //stu 是派生类引用,但是要访问string类的友元函数,所以要强制转换为基类引用
{
    os<<"Scores for"<<(const String &)stu<<":\n";
}
包含和私有继承的选择

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

3.保护继承

使用关键字protected

使用私有继承时,第三代类将不能使用基类的接口

使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。

4.多重继承

使用书上的例子演示:

class worker
{}

class waiter:public worker
{}

class singer:public worker
{}

class singingwaiter:public singer, public waiter
{}

因为singer和waiter都继承了一个worker组件,所以singingwaiter将包含两个worker组件。

通常可以将派生类对象的地址赋值给基类指针,但是会出现二义性:

singingwaiter ed;

worker * pw = &ed;

因为ed中包含两个worker对象,你并不知道指向或者使用的是那个对象。

此时可以使用类型转换来指定对象:

worker * pw1 = (waiter *) &ed;

worker * pw2 = (singer *) &ed;

但我们只需要一个worker对象时,应该怎么办?

引入虚基类

虚基类

例如:

class singer:virtual public worker{};
class waiter:public virtual worker{};//这个次序时无关紧要的
class singingwaiter:public singer,public waiter{};

新的构造函数规则:

singingwaiter(const worker &wk, int p=0,int v=singer::other)
				   :worker(wk),waiter(wk,p),singer(wk,v){}

其他问题:

1.混合使用虚基类和非虚基类

​ 如果基类是虚基类,派生类将包含基类的一个子对象;

​ 如果基类不是虚基类,派生类将包含多个子对象;

2.虚基类和支配

​ 即如果类从不同的类哪里继承了两个或更多的同名成员,则使用该成员名时,如果没有用类名进行限定,将导致二义性。

​ 虚基类会定义优先规则来解决二义性

5.类模板

和函数模板差不多

用以下方式开头

template<class Type>;

函数模板如下:

template<class Type>
void Stack<Type>::push(const Type &item)
{}

隐式实例化:

ArrayTP<int, 100> stuff;

显示实例化

template class ArrayTP<string,100>;

显示具体化

template <> class SortedArray<const char *>

部分具体化

template <class T1,class T2>class Pair{};
template<class T1> class Pair<T1,int>{};

模板类和友元暂时不是很理解,就不放上来了。

6.模板类别名

template<typename T>
	using arrtype = std::array<T,12>;

arrtype<double> gallons; //=== double gallons[12]
arrtype<int> days;		//=== int days[12]
arrtype<std::string> months;	//=== string months[12];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值