引言:C++的一个主要目的是代码重用,提高效率,公有继承是实现这个目的的一种机制。还有其他的机制,本部分主要介绍其他代码重用方法,一种是包含、组合或层次化,另一种是私有或保护继承,通常组合、私有继承和保护继承哟国语实现has-a关系,即新的对类将包含另一个类的对象。还有一种就是和函数模板对应的类模板。
1.包含对象成员的类:
包含对象的类就是这样一种类:类中包含了这样的一个类成员:本身是另一个类的对象。这种方法称为包含、组合或层次化。
C++和约束:C++包含让程序员能够限制程序结构的特性——使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据等等。这样做的根本原因是:在编译阶段出现错误要比在运行阶段出现错误要好,要优。
初始化顺序:当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。例如:
Student(const char *str,const double *pd,int n):scores(pd,n),name(str){}
则name成员仍将首先初始化,因为在类定义中它首先被声明。
接口和实现:使用公有继承是,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是is-a关系的组成部分。而是用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。
2.私有继承和保护继承:
私有继承:
私有继承,也是实现has-a关系的一种途径。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这就意味着基类方法将不会成为派生类对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
接口问题:使用公有继承,基类的公有方法将成为派生类的公有方法,。简而言之,派生类将继承基类的接口;这是is-a关系的一部分。使用私有继承,基类的公有方法就成为派生类的私有方法,简而言之,派生类不继承基类的接口。这种不完全继承是has-a的关系的一部分。
使用私有继承类将继承实现。
包含是将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。一般用术语子对象来表示通过继承或包含添加的对象。
从上面可知,私有继承的特性和包含(组合)相同,所以,私有继承也可以用来实现has-a关系。
访问基类的方法:使用包含时将使用对象名来调用方法,而是用私有继承时将使用类名和作用域解析操作符::来调用方法
访问基类对象:一般使用强制类型转换。例如:Student类是string类派生而来的:
cosnt string & Student::Name()const
{
return (const string &) *this;
}
访问基类的友元函数:用类名显示地限定函数名不适合与友元函数,因为友元不是类成员,不过,可以通过显式地转换为基类来调用正确的函数。例如,对于下面的友元函数定义:
ostream & operator<<(ostream & os,Student & stu)
{
os <<"Scores for " <<(const string &) stu <<":\n";
...
}
如果pstu是一个Student对象,则下面的语句:
cout<<pstu;将调用上述函数,stu将是指向pstu的引用,而os将是指向cout的引用。下面的代码:
os<<"Scores for " << (const string &) stu <<":\n"; 显式地将stu转换为string对象的引用,这与operator<<(ostream &,cosnt String &)函数匹配
引用stu不会自动转换为sting引用。根本原因在于,在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类应用或指针。
不过,即使这个例子使用的是公有继承,也必须使用显示类型转换。原因之一是,如果不使用类型转换,下面代码将与友元函数原型匹配,从而导致递归调用:os<<stu; 另一个原因是,如果使用的多重继承,编译器无法确定应转换为哪个基类,如果两个基类都提供了函数operator<<()。
3.多重继承:
多重继承,顾名思义,就是使用多个基类的继承(multiple inheritance,MI),描述的是有多个直接基类的类。
4.类模板:
定义:开头用 template <class Type> 或者 template <typename Type>,模板的具体实现就是实例化或具体化。
例如创建一个堆栈类模板示例:
#pragma once//
#include <iostream>
using namespace std;
template <class Type>
class Stack
{
private:
enum{MAX = 10};
Type items[MAX];
int top;
public:
Stack();
bool IsEmpty();
bool IsFull();
bool Push(const Type &item);
bool Pop(Type & item);
};
template <class Type>
Stack<Type>::Stack()
{
top = 0;
}
template<class Type>
bool Stack<Type>::IsEmpty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::IsFull()
{
return top == MAX;
}
template <class Type>
bool Stack<Type>::Push(const Type &item)
{
if(top < MAX)
{
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,int n>,里面的 int n称为非类型或表达式参数,但是表达式参数有一定的限制。只可以是整型、枚举、引用或指针。还可以使用多个类型参数,例如 template <class T1,class T2>
默认类型模板参数:类模板的一项特性是,可以为类型参数提供默认值:
temp <class T1,class T2 = int>
class Topo
{
...
};
这样,如果省略了T2的值,编译器将使用int:
Topo<double,double> m1; //T1 is double,T2 is double
Topo<double> m2; //T1 is double ,T2 is int
标准模板库将此使用该特性,将默认类型设置为类
虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值。不过,可以为非类型参数提供默认值,这对于函数模板和类模板都都是适用的。
模板的其他特性:可用作结构、类或模板类的成员;可将模板用作参数
模板类和友元:模板声明也可以有友元。模板的友元分为3类:
● 非模板友元
● 约束(bound)模板友元,即友元的类型取决于类被实例化时的类型
● 非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元
来源:https://www.cnblogs.com/JczmDeveloper/archive/2013/03/17/2964826.html