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];