c++提供的代码重用方法:
1.包含;
2.继承;
3.类模板;
包含
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;
}
}
私有继承
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
包含将对象作为一个命令的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。因此私有继承提供的特性与包含相同:获得实现,但不获得接口。
使用多个基类的继承称为多重继承。
1.初始化基类组件
Student(const char * str, const double * pd, int n) : std::string(str), ArrayDb(pd, n) {}
它使用类名而不是成员名来标识构造函数。
2.访问基类的方法
使用私有继承时,只能在派生类的方法中使用类名和作用域解析运算符来调用基类的方法。
double Student::Average() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum()/ArrayDb::size();
else
return 0;
}
3.访问基类对象
如果要使用基类对象本身,需要使用强制类型转换将派生类转换为基类。
4.访问基类的友元函数
通过显式的转换为基类来调用友元函数。
友元函数
ostream & operator<<(ostream & os, const Student & stu)
{
os << "Scores for " << (const String &) stu << ":\n";
}
Student plato;
有代码如下:
os << "Scores for " << (cosnt String &) stu << ":\n";
引用stu不会自动转换为string引用。根本原因在于,
在私有继承中,在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
如果不使用显式类型转换,os << stu;将与友元函数原型匹配,从而导致递归调用。
另一个原因是,由于这个类使用的是多重继承,编译器无法确定应转换成哪个基类。
使用包含还是私有继承
一般情况下倾向于使用包含,使用私有继承的情况有以下两点:
1.新类需要访问原有类的保护成员;
2.需要重新定义虚函数;
保护继承
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员,基类的接口在派生类中是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别便呈现出来了。使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。
使用using重新定义访问权限
要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类方法的派生类方法。
double Student::sum const
{
return std::valarray<double>::sum();
}
另一种方法是将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用的特定的基类成员,即使采用的是私有继承。
class Student : private std::string, private std::valarray<double>
{
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
};
使用using声明之后就可以像使用公有方法一样使用它们。using声明只适用于继承,不适合包含。
多重继承(MI)
class Worker
{
};
class Singer : public Worker
{
};
class Waiter : public Worker
{
};
class SingerWaiter : public Singer, Waiter
{
};
多重继承存在的问题
SingerWaiter ed;
Worker * pw = &ed;
因为Singer和Waiter都继承了一个Worker组件,因此SingerWaiter将包含两个Worker组件。通常可以将派生类对象的地址赋给基类指针,但上面的赋值将出现二义性。
通常这种赋值将把基类指针设置为派生类对象中的基类对象的地址。但ed包含两个Worker对象。所以应该使用类型转换来指定对象。
Worker *pw1 = (Waiter *)ed;
Worker *pw2 = (Singer *)ed;
多重继承将使得使用基类指针来引用不同的对象复杂化(多态性)
虚基类
虚基类是为解决多重继承而产生的新技术。虚基类使得从多个类派生出的对象只继承一个基类对象。
class Singer : virtual public Worker {...};
class Waiter : public virtual Worker {...};
1.新的构造函数规则
使用虚基类时,需要对类构造函数采用一种新的方法。
class Worker
{
int a;
public:
Worker(int n = 0) : a(n) {};
show();
};
class Singer : public Worker
{
int b;
public:
Singer(int m = 0, int n = 0) : Worker(n), b(m) {};
};
class Waiter : public Worker
{
};
class SingerWaiter : public Singer, Waiter
{
int c;
public:
SingerWaiter(int q = 0, int m = 0, int n = 0) : Singer(m, n), c(q) {}
};
SingerWaiter类的构造函数只能调用Singer类的构造函数,而Singer类的构造函数只能调用Worker类的构造函数。这里SingerWaiter将m,n传递给Singer类,而Singer类将n传递给Worker类。
如果Worker是虚基类,这种信息的自动传递将不起作用,存在的问题是,存在两条不同的途径将n传递个Worker。为避免冲突,C++在基类是虚的时,禁止信息通过中间类自动传递给基类。
SingerWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Waiter(wk, p), Singer(wk, v) {}
在上面的代码中,编译器将使用Worker的默认构造函数。如果不希望默认构造函数来构造基类对象,则需要显式的调用所需的基类构造函数。
SingerWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk, p), Singer(wk, v) {}
上述代码对于虚基类是合法的,而对于非虚基类是非法的。
2.改写方法
在多重继承中,如果派生类没有重新定义基类的方法,那么基类的方法会造成二义性。比如Singer和Waiter都会包含方法show(),
SingerWaiter newhire;
newhire.show();//二义性,不知道具体是调用哪一个show
但是可以使用作用域解析符来指出
SingerWaiter newhire;
newhire.Singer::show();
更好的方法是在SingerWaiter中重新定义show(),并指出使用哪个show()
void SingerWaiter::show()
{
Singer::show();
}
总之,在祖先相同时,使用MI必须引入虚基类,并修改构造函数初始化列表的规则。另外需要针对一些方法进行改写。
**
类模板
**
继承和包含并不总是能够满足重用代码的需要。比如Stack和Queue类都是容器类,容器类设计用来存储其它对象或数据类型。它们除了保存的对象类型不同外,这两种Stack类的代码是相同的,与其编写新的类声明,不如编写个泛型栈,然后将具体的类型作为参数传递给这个类。
模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或函数。
1.定义类模板
和模板函数一样,模板类以下面的代码开头:
template <class Type>
template <typename Type>
可以使用自己的泛型名代替Type,当前流行的选项包括T和Type。
template <class Type>
bool Stack<Type>::push(const Type & item)
{
...
}
如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。
模板不是类也不是成员函数定义,它们是C++编译器指令,说明了如何生成类和成员函数定义——被称为实例化或具体化。
不能将模板成员函数放在独立的实现文件中,一般将所有的模板信息放在头文件中。由于模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。
2.使用模板类
仅在程序中包含模板并不能生成模板类,而必须请求实例化。
Stack<int> kernels;
Stack<string> colonels;
泛型标识符——Type——称为类型参数。这意味着它们类似于变量,但赋给它们的不能是数字,而只能是类型。注意:必须显式的提供所需的类型,这与常规的函数模板时不同的,因为编译器可以根据函数的参数类型来确定要生成哪种函数。
–可能自己忘了保存,后面一半关于模板更深入的内容前天晚上写好了准备留着今天调整一下格式再发布。结果今天来看就没了,没了,没了… 留着以后复习到这里再写一遍吧,也可能它认为我需要复习…