您的位置:首页 > 编程语言 > C语言/C++

C++ 学习笔记之代码重用

2016-01-27 23:54 561 查看
 

C++代码重用

是使用包含还是继承?

首先,包含(组合)易于理解,类声明中包含被包含类的显式命名对象,代码可以通过名称引用这些对象,,而使用继承将使关系更复杂,更抽象,问题也更多。

但是,私有继承所提供的特性确实比包含多。如果包含保护成员,我们知道保护成员只有在继承体系中才能使用,如果此时使用的是组合模式,则不能访问类中包含有保护方法/数据的类中的保护方法/数据。并且需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义续汉书,但是包含不能,使用私有继承,重新定义的函数只能在类中使用,而不是公有的。

总的来说,通常,应使用包含来建立has -a 的关系;如果新类需要访问原有类的成员,或需要重新定义虚函数,则应使用私有继承。

保护继承

保护继承是私有继承的变体。保护进程在列出基类时使用关键字protected.使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但是在继承层次结构外却是不可用的。另外,使用私有继承第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中变成了私有方法;使用保护继承时,基类的公有方法在第二代变成了保护方法,所以第三代派生类也是可以使用的。

小结继承

特征 公有继承 保护继承 私有继承

公有成员 派生类的公有成员 派生类的保护成员 派生类的私有成员

保护成员 派生类的保护成员 派生类的保护成员 派生类的私有成员

私有成员 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问

是否隐式向上 是 是(只能在派生类中) 否

可以使用using 重新定义访问权限,using 只适合继承,而不适合包含。

多重继承(MI)、

举如下例子:
class singer :public worker{...}
class waiter :public worker{...}
class singwait:public Singer,public waiter{...}

此时,将派生类对象的地址赋给基类指针,但是现在将出现二义性,但是暂时还可以使用强制类型转换来解决问题,但是会大符增加复杂性。
所以一种新的技术出现,虚基类。
s使用MI必须引入虚基类,并修改构造函数初始化列表规则,如果在编写这些类的时候没有考虑到MI,则可能还需要重新编写他们。
***可以增加保护属性的方法来提供特殊的需要实现的方法。
***虚基类的核心功能在于只需要继承一个基类的实例信息。

一些与MI有关的问题

 

1.混合使用虚拟基类和非虚拟基类

这里说一个言简意赅的问题:

假设类B被用作类C和类D的虚基类,同时被用作X,Y的非虚基类,而类M是从C,D,X和Y派生来的,在这种情况下,类M从虚拟派生祖先哪里一共继承了一个B类子对象,并且从每一个非虚拟派生祖先分别继承a了一个B类子对象,因此他包含三个B类子对象。当类通过多条虚拟途径和非虚拟途径继承某一个特定的类时,该类将包含一个表示所有的虚拟途径的基类子对象和分别表示各条非虚拟途径的多个基类子对象。

2.虚基类和支配

使用虚基类将改变C++解析二义性的方式。使用非基类时,规则简单,如果继承的类中包含标签相同的函数,直接导致二义性,但是在虚基类中,不一定会导致二义性,在这种情况下如果某个名称优先于其他所有的名称明则使用时,即使不使用限定符,也不会导致二义性。(优先:派生类中的名称优先于直接或间接祖先类中的相同名称)

MI 小结

 

如果使用非虚基类实现多继承,则需要使用派生类相应的限定符来指定方法。否则编译器会指出其中的二义性。当他从多种途径继承来时,则从每种途径都继承来了一个非虚拟基类的基类对象。

当使用虚基类时,当派生时使用关键字virtual指示派生时,基类就变成了虚拟基类;

主要变化:

从虚拟基类的一个或者多个实例派生来的类只继承了一个基类对象。为了实现这种效果需要满足一些其他的要求

有间接虚拟基类的派生类包含直接调用间接类构造函数的构造函数,对与间接非虚拟基类来说是非法的。需要调用到基类级构造函数。

通过优先规则解决名称二义性。

类模板

为了使C++的代码更加的通用,仅仅使用继承是不够的,我们现在使用一种叫作类模板的东西增强代码重用。像valarray 这种类。

采用模板时,使用模板定义替换类名的声明,使用模板成员函数替换类的成员函数。和模板类函数一样,模板类以下面这样的代码开头。

template<class Type> 现在也有 template<typename Type> 这种写法的

关键字template 告诉编译器,这是要定义一个模板。但是这里使用class 并不意味着类型必须是一个类。而只是表明这是一个通用的类型说明符,在使用模板的时候,将使用实际的类型替换它。

模板不是类和成员函数,他们其实时C++编译器指令,模板不是函数,它们不能单独编译,模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模版信息放在一个头文件中,并在要使用这些模板的头文件中包含该头文件。

注意在使用模板的时候一定要显示的提供所需的类型,因为编译器可以根据不同的参数类型来确定需要哪种函数。

使用模板类:

仅在程序包含模板并不能生成模板类时,必须请求实例化。模板代码不能修改参数的值,也不能使用参数的地址,所以诸如n++,&n(n 是参数)是不能够在模板内使用的,另外,在实例化模板时,用作表达式参数的值必须是常量表达式。

通过new delete 管理堆内存,而表达式参数方法使用的是自动变量维护内存栈,这样执行速度将更快,尤其使用许多小型数组。

表达式参数的主要缺点是,每种数组大小都将生成自己的模板,就是说如果有以下:

ArrayTP<double,12> egg;

ArrayTp<double,13> dounkers;

这将生成两个类声明,但是如下:

Stack<int> eggs(10);

Stack<int> dunkers(12);

只能生成一个类声明,并将数组大小信息传递给类的构造函数。构造函数方法一般情况下更通用,这是因为数组大小是作为类成员(不是硬编码)存储在定义中的,这样可以将一种尺寸的数组赋值给另一个数组明也可以创建允许数组大小可变的类。

递归使用模板:

模板时可以递归使用的,例子如下:

Array<Array<int ,5>,10> twoArr;

等价于

int twoArr[10][5];

模板也可以包含多个类型参数

默认类型模板参数:

这也是C++的一个新特性吧,如下:

template<class T1, class T2 = int>class Top{...};

则再不指定T2的类型的情况下默认为int ,虽然可以为类模板类型参数提供默认值,但不能为函数模板参数提供默认值,不过可以为非类型参数提供默认值,这是对于类模板和函数模板都是适用的。

模板的具体化:

1.隐式实例化

隐式实例化就是类型名 + 变量名,编译器在需要对象之前,不会生成类的隐实例化。如果使用new 则定义即实例化。

2.显示实例化

当使用template 并指出所需类型来声明类时,编译器将生成类声明的显示实例化。在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法和定义)。

3.显示具体化

显示具体化是特定类型的定义,在这种情况下,可以提供一个显示模板具体化,这将采用为具体类型定义的模板,而不是为通用类型定义的模板。当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。

4.部分具体化

C++还允许部分具体化,即部分的限制模板的通用性。部分具体化可以给类型参数之一指定具体类型。如果有多个模板可供选择,则编译器将使用具体化程度最高的模板。也可以通过为指针提供特殊版本来部分具体化现有的模板。部分具体化特性使得能够设置各种匹配限制。

成员模板:

C++模板支持的另一个特性是:模板可以用作结构,类或模板类的成员。相当于就是模板的嵌套使用。注意作用域解析符来完成。

将模板用作参数:

这是STL库实现的一种手段,当然也可以混用模板参数和常规参数。

模板类和友元

非模板友元,约束模板友元,非约束模板友元。

1.模板类的非模板友元函数

先说下友元函数可以做的事情:可以访问全局对象,可以使用全局指针访问非全局对象,可以创建自己的对象,可以访问独立于对象的模板类的静态数据成员。

2.模板类的约束模板友元函数

这就是将友元函数变成一个模板,主要有以下三个步骤:

在类定义的前面声明每个模板函数。

再在类中再次将模板声明为友元。

为友元提供模板定义。

3.模板类的非约束模板友元函数

通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。

查看原文:http://zmrlinux.com/2016/01/23/c-%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%e4%b9%8b%e4%bb%a3%e7%a0%81%e9%87%8d%e7%94%a8/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: