C++学习随笔之十一:C++中的代码重用

引言: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/p/2964826.html