14.1.1
一,接口和实现
使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而使用组合(或者叫包含、层次化,即成员本身是另一个对象),类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
class Student
{//包含版本
private:
typedef std::valarray<double> ArrayDb;
std::string name; //contain object
ArrayDb scores; //contain object
public:
Student(const char* str, const double *pd, int n):name(str),scores(pd,n){}
double Average() const;
const std::string & Name() const;
~Student(){}
};
二,初始化顺序
当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是他们在初始化列表中的顺序。如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。
14.2
三,私有继承
私有继承是另一种实现has-a关系的途径。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着,基类方法不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
因此私有继承提供的特性与包含相同:获得实现,但不获得接口。
包含是将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未命名的继承对象添加到类中。
class Student: private std::string, private: std::valarray<double>//
{//私有继承版本
public:
//...
};
四,多重继承
使用多个基类的继承被称为多重继承(multiple inheritance, MI)。
14.2.1
五,包含与私有继承的区别
1,包含提供被显示命名的对象成员,而私有继承提供无名称的子对象成员;
2,初始化基类组件:在继承类的构造中,需使用成员初始化列表的方式来初始化基类。包含使用的是成员名来标识构造函数,而私有继承使用的是类名来标识构造函数;
Student(const char* str, const double *pd, int n):name(str),scores(pd,n)
{//包含版本,初始化基类组件
}
Student(const char* str, const double *pd, int n):std::string(str), ArrayDb(pd, n)
{//私有继承版本,初始化基类组件
}
3,访问基类的方法:包含使用对象名来调用方法,而私有继承使用类名和作用域解析运算符来调用方法;
double Student:Average() const
{//包含版本,访问基类方法
if (score.size() > 0)
{
return score.sum()/score.size();
}
else
{
return 0;
}
}
double Student:Average() const
{//私有继承版本,访问基类方法
if (ArrayDb::size() > 0)
{
return ArrayDb::sum()/ArrayDb::size();
}
else
{
return 0;
}
}
4,访问基类对象:使用强制类型转换,将派生类对象,转换为基类对象。由于this指向用来调用方法的对象,因此*this为用来调用方法的对象。故一般在派生类内使用*this将派生类强制转换为基类的引用。
const string & Student::Name() const
{//包含版本,访问基类对象
return name;
}
const string & Student::Name() const
{//私有继承版本,访问基类对象
return (const string &) *this;
}
4,访问基类的友元函数
可以将派生类显示的转换为基类,再调用基类的友元。
注意,在私有继承中,未进行显示类型转换的派生类引用或指针,无法赋值给基类的引用或者指针。但公有继承是可以自动的转换。
14.2.2
六,使用包含还是私有继承
由于既可以使用包含或者私有继承建立has-a的关系,但二者区别如下:
1,包含容易理解,私有继承更抽象
2,私有继承会引起很多问题,尤其是从多个基类继承时,可能必须处理很多问题。
3,包含能够拥有多个同类的子对象,而继承只能使用一个这样的对象。
4,私有继承可以访问保护成员,但包含不可以。
5,私有继承可以重新定义虚方法,但包含类不可以。
通常,应使用包含来建立has-a关系;如果新类需要访问原有类的保护成员,或者需要重新定义虚函数,则应使用私有继承。
14.2.3
七,保护继承
保护继承是私有继承的变体。使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代类中将变成受保护的,因此可以在第三代派生类中使用它们
八,隐式向上转换
无需进行显示类型转换,就可以将基类指针或引用指向派生类对象。
九,使用using重新定义访问权限
在使用保护派生或私有派生时,基类的公有成员将成为保护成员或者私有成员。
有两种方式可以使基类的方法在派生类外面可用:
1,定义一个使用该基类方法的派生类方法。
double Student::sum() const //public student method
{
return std::valarray<double>::sum(); // use privately-inherited method
}
2,将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员。
class Student: private std::string, private: std::valarray<double>//
{
//...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
//...
};
注意:using声明只使用成员名----没有圆括号、函数特征和返回类型。
同时using只适用于继承,不适用于包含。
十,各种继承与访问权限
特征 | 公有继承 | 保护继承 | 私有继承 |
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
14.4.1
十二,类模板
在声明时,模板类的代码开头为:
template<class Type>
template<typename Type> //newer choice
在定义类方法时,应使用模板成员函数替换原有类的方法,每个函数头都将以相同的模板声明打头。如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
14.4.2
十三,使用类模板
泛型标识符(如上面的Type)称为类型参数,这意味着它们类似于变量,但赋给它们的不能是数字,而只能是类型。
注意,必须显示地提供所需的类型,这与常规的函数模板不同,因为编译器可以根据函数的参数类型来确定要生成那种函数。
//declaration
template<typename Type>
class Stack
{
private:
//...
public:
Stack();
Stack& operator=(const Stack & st);
bool isempty()const;
};
//definition
template<typename Type>
Stack<Type>::Stack(){}
template<typename Type>
bool Stack<Type>::isempty()
{
//...
}
template<typename Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
//...
}
注意,上述代码中的重载赋值运算符操作,原型中赋值运算符的返回类型声明为Stack引用,而实际的模板函数定义将类型定义为Stack<Type>。前者是后者的缩写,但只能在类中使用。
即:可以在模板声明或模板函数定义内使Stack,但在类外面,即:指定返回类型或使用作用域解析运算符时,必须使用完整的Stack<Type>。
14.4.4
十四,非类型(表达式)参数
如下声明,n为指定类型int
template<class T, int n>
表达式参数有一些限制:
1,只能为整型,枚举,引用或指针。因此double n是不合法的。
2,模板不能修改参数的值,也不能使用参数的地址。如不能使用n++或者&n等。
3,实例化模板时,用作表达式参数的值必须是常量表达式
14.4.5
十五,默认类型模板参数
可以为类型参数提供默认值:
template <class T1, class T2 = int>
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。然而,可以为非类型参数参数提供默认值,这适用于类模板与函数模板。
14.4.6
十六,部分具体化
相对于显示具体化来说,c++存在部分具体化,即,部分限制模板的通用性。如:
//general template
template <class T1, class T2>
class pair{//...};
//specialization with T2 set to int
template <class T1> class pair <T1, int>{//...};
关键字template后面的<>内声明的是没有被具体化的类型参数。因此上述第二个声明中,T2具体化为int,但T1保持不变。注意,如果指定所有类型,则<>为空,则变成显示具体化。
14.4.7
成员模板
14.4.8
将模板用作参数
14.4.9
模板类和友元
14.4.10
十七,模板别名(C++11)
可以使用typedef为模板具体化指定别名:
typedef std::array<double 12> arrd;
typedef std::array<int 12> arri;
typedef std::array<std::string 12> arrst;
arrd gallons1;
arri gallons2;
arrst gallons3;
C++11新增了一项功能----使用模板提供一系列别名:
template<typename T>
using arrtype = std::array<T, 12>;
这将arrtype定义为一个模板别名,可以使用它来指定类型,如:
arrtype<double> gallons1;
arrtype<int> gallons2;
arrtype<std::string> gallons3;
总之arrtype<T>表示类型std::array<T, 12>。