240 likes | 374 Views
第九章 类属机制. 类属的作用 类属类的定义与实例化 类属类的继承关系 类属函数的定义与实例化. §9.1 类属. 类属 是实现 编译时多态性 的另一种机制,这种机制通过 将数据类型参数化 使得程序具有多态性 类属机制的基本思想: 实际应用中,一些函数的功能相同,唯一的区别只在于处理对象的数据类型不同,若用函数重载实现,则需编写多个函数: 例: int max(int i, int j) float max(float i, float j) { return i>j?i:j; } { return i>j?i:j; }
E N D
第九章 类属机制 类属的作用 类属类的定义与实例化 类属类的继承关系 类属函数的定义与实例化
§9.1 类属 • 类属是实现编译时多态性的另一种机制,这种机制通过将数据类型参数化使得程序具有多态性 • 类属机制的基本思想: • 实际应用中,一些函数的功能相同,唯一的区别只在于处理对象的数据类型不同,若用函数重载实现,则需编写多个函数: 例:int max(int i, int j) float max(float i, float j) { return i>j?i:j; } { return i>j?i:j; } • 编程时只提供一套实现该功能的程序实体,然后将数据类型作为参数传递,这就是类属机制的思想
C++语言中,使用模板来实现类属机制 • 模板具有形式类属参数:将数据类型作为模板的参数,参数化的数据类型就是形式类属参数 • 模板须经过实例化后才能使用:实例化是指用某一具体数据类型替代模板中的形式类属参数的过程,该确定的数据类型称为实际类属参数。
C++语言的模板 • 函数模板(类属函数) • 函数的形式参数表中某些形式参数的数据类型被参数化 • 函数模板本身不是函数,经实例化后才得到函数 • 类模板(类属类) • 类的数据成员的类型或成员函数的形参类型被参数化 • 类属类也不是类,经实例化后才得到具体的类
§9.2 类模板 一、类属类的定义 • 一般形式: template <class 类属参数1, class 类属参数2, …> class 类名 { … }; • 形式类属参数表:用尖括号括起来的部分。用逗号隔开不同的形式类属参数,每个类属参数都由class引入 • 类属参数在类的声明中有效,在类外无效 • 在类的定义中,把这些类属参数当数据类型来声明各种变量
例1:用类模板对数组进行排序、检索和求和 #include <iostream.h> template <class T> // 声明一个形式类属参数T class ARRAY { T *set; // 定义数组元素集合 int n; // 定义数组元素个数 public: ARRAY( T *data, int i) { set = data ; n = i; } ~ARRAY() {} void sort(); // 排序 int seek(T key); // 检索 T sum(); // 求和 };
template <class T> // 注意在类外声明类模板的 void ARRAY<T>::sort() // 成员函数的形式 { int i, j; T d; for (i=1; i<n; i++)// 冒泡排序 for (j=n-1; j>=i; j--) { if (set[j-1] > set[j]) {//交换set[j-1]和set[j]的值 d = set[j-1]; set[j-1] = set[j]; set[j] = d ; } } } template <class T> int ARRAY<T>::seek(T key) { for (int i=0; i<n; i++) if (set[i]==key) return i;// 找到,则返回key在数组中的位置 return -1; // 找不到,返回-1 }
template <class T> T ARRAY<T>::sum() { T s=0; for (int i=0; i<n; i++) s += set[i]; return s; }
例2: • 条件编译预处理命令 • #ifdef 标识符1 程序段1 #else 程序段2 #endif • 若标识符1已用define宏定义过,则程序段1参与编译,否则编译程序段2,在编译阶段实现分支结构
// 例: #include <iostream.h> #define MAX void main() { int a, b,t; cout<<“Input two integer: ”; cin>>a>>b; #ifdef MAX t = a>b? a: b; // 如定义了MAX,则求最大值 #else t = a<b? a: b; //如没有定义了MAX,则求最小值 #endif cout<< t; }
条件编译预处理命令的另一种形式 #ifndef 标识符1 若标识符1未用define宏 程序段1 定义过,则程序段1参与 #else 编译,否则编译程序段2 程序段2 #endif • 两种形式的条件编译都可省略else分支 • 例: #ifndef TEST_DEF 若TEST_DEF未定义,则中 #define TEST_DEF 间的这段程序参与编译,在 class TEST_DEF {该程序段中,首先宏定义 ……TEST_DEF,当下次程序再次 }; 经过该处时, TEST_DEF已 #endif 定义过,于是不再编译。 保证该程序段在整个工程中只被唯一的编译一次
二、类属类的实例化 • 类模板不是类,不能用于创建对象,只有经过实例化后才得到类 • 实例化的一般形式: 类属类名字<实际类属参数表> 例: STACK 类模板,不能用于创建对象 STACK<int> obj; 实例化:用实际类属参数int替换形式类属参数ELEMENT_TYPE后,得到一个整形堆栈类,即可用于声明对象。 • 一个类模板可以实例化为多个不同的类
例1中声明的ARRAY类模板的实例化 void main() { int i; int IData[10] = {4, 2, 5, 3, 10, -4, -5, -2, 0, -3}; ARRAY<int>myArray(IData, 10); cout<< "IData[" <<myArray.seek(-5)<<"]=-5"<<"\n"; cout<<"Sum of IData is : "<< myArray.sum() << "\n"; myArray.sort(); for (i=0; i<10; i++) cout<<IData[i]<<" "; } 输出结果: Idata[6] = -5 Sum of IData is : 10 -5 -4 -3 -2 0 2 3 4 5 10
三、类属类的继承关系 • 一个类模板可以作为一个普通类的派生类 • 一个类模板也可作为其它类模板的基类 例: #include <iostream.h> // 声明一个类属类作为基类。 template <class TYPE> class BASE { public: void show(TYPE obj) { cout << obj << "\n"; return; } };
// 声明一个类属类作为BASE的派生类。 template <class TYPE1, class TYPE2> class DERIVED:public BASE<TYPE2> { public: void show2(TYPE1 obj1, TYPE2 obj2) { cout << obj1 << " " << obj2 << "\n"; return; } }; // 演示类属类层次的用法 int main() { DERIVED<char*, double> obj; //基类被实例化为BASE<double> obj.show(3.14); // 正确 obj.show2("Pi is: ", 3.14159); // 正确 // obj.show(“Has any error?”); // 类型错误 //形参类型是double,实参类型是字符串 return 0; }
§9.3 函数模板 一、类属函数 • 一般形式: template <class 类属参数1, class 类属参数2, …> 函数类型 函数名(形参表) { 函数体… } 例: template <class TYPE> TYPE max(TYPE x, TYPE y) { return (x > y) ? x : y; }
函数模板定义时应注意的问题 • 在函数模板定义template中给出的每一个形式类属参数都必须出现在函数形参表中 • 如: template <class T1, class T2> void fun(T1 x, T2 y) { …… } • 错误的用法 template <class TYPE> template <class TYPE> TYPE* func()void func() { ……} { TYPE obj; …… } 对于类模板的成员函数则不做此项要求
函数模板的实例化 • 由编译系统根据函数调用的实际参数类型自动完成函数模板的实例化,不须显式实例化 template <class TYPE> TYPE max(TYPE x, TYPE y) { return (x > y) ? x : y; } void main( ) { MY_CLASS obj1, obj2; max(10, 5); // 用整型实例化 max(obj1, obj2); // 用MY_CLASS实例化 }
调用类属函数时,应保证函数的实参类型与形式类属参数完全匹配调用类属函数时,应保证函数的实参类型与形式类属参数完全匹配 • 因为函数模板在实例化过程中不做任何类型转换 • 例:已知函数模板max的两个形参都是形式类属参数TYPE类型的,则以下两个应用错误 max(10, 10.5); 一个实参是整型,另一个是double型,类型不同 max(10, obj1); 一个实参是整型,另一个是MY_CLASS类型 都无法实例化
二、类属函数的重载例: #include <iostream.h> template <class TYPE> TYPE max(TYPE x, TYPE y) { return (x>=y)?x:y; } template <class TYPE> TYPE max(TYPE x, TYPE y, TYPE z) { TYPE w = (x>=y)?x:y ; return (w>=z)?w:z; }
template <class TYPE> TYPE max(TYPE x[], int n) { TYPE m = x[0]; for (int i=0; i<n; i++) if (m<x[i]) m = x[i]; return m; } void main() { float m, x=56, y=-55.8, z=99.1; int data[10] = {1,3,15,7,9,22,4,26,8,10}; m = max(x, y); // TYPE被实例化为float m = max(x,y,z); int t = max(data, 10); }
在引入类属函数后,确定调用函数的哪个版本所遵循的匹配原则:在引入类属函数后,确定调用函数的哪个版本所遵循的匹配原则: • (1)如果某一普通函数的形参类型正好与函数调用的实际参数类型匹配,则调用该函数。否则, • (2)如果能从同名的类属函数实例化一个函数实例,而该函数的参数类型正好与函数调用的实际参数类型匹配,则调用该实例化的函数。否则, • (3)对函数调用的实际参数作隐式类型转换后与非类属函数再作匹配,找到匹配的函数则调用它。否则, • (4)提示语法错误。
例1: #include <iostream.h> template <class TYPE> void swap(TYPE& x, TYPE& y) // 声明一个函数模板swap { TYPE temp; temp = x; x = y; y = temp; cout << "Calling generic version of swap().\n"; } void swap(int& x, int& y) // 重载swap,定义一特殊版本(不是模板) { int temp; temp = x; x = y; y = temp; cout << "Calling special version of swap().\n"; } int main() { int i = 10, j = 20;float x = 1.44, y = 3.14;char a = 'x', b = 'y'; swap(i, j); // 调用swap()的特殊版本(非类属函数) (1) swap(x, y); // 调用类属函数实例化结果swap(float, float) (2) swap(a, b); // 调用类属函数实例化结果swap(char, char) (2) swap(i, b); // 调用swap()的特殊版本(编译时作隐式类型转换) return 0; (3) }
版本①实例化为func(char,char); 把实参‘c’隐式转换为int型后,调用版本② func(int, int); 版本①实例化为func(char*,char*); 例2:以下程序main()中的函数调用是否合法?如果合法则指出调用的是哪一个版本的函数。 版本①:函数模板 template <class T> void func(T x, T y) { // … } func(int i, int j) { // … } void main() { func('a', 'b'); func(10, 'c'); func("string1", "string2"); func(10, "string"); } 版本②:普通函数