类模板和虚函数,都是实现多态的重要方法。
类模板,锁定算法、步骤,偏重类型相同。
虚函数,偏重步骤不相同。
9.3类模板
类模板用于实现类所需数据的类型参数化。
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
类模板声明的一般方法如下:
template<类模板参数>class 类名{//类体};
用类模板定义对象的一般格式如下:
类名<模板实例化参数类别>对象名(构造函数实参列表);
//类模板实际上对于使用不同的数据类型,但是操作方法一样的类的一种抽象
//类模板实现通用
1 #include <iostream> 2 3 //类模板实际上对于使用不同的数据类型,但是操作方法一样的类的一种抽象 4 //类模板实现通用 5 template <class T> 6 class com 7 { 8 public: 9 T a; 10 T b; 11 T add() 12 { 13 std::cout << typeid(T).name() << std::endl; 14 return a + b; 15 } 16 }; 17 18 void main() 19 { 20 com<int>comx; 21 22 comx.a = 19; 23 comx.b = 29; 24 25 std::cout << comx.add() << std::endl; 26 27 system("pause"); 28 }
//定义两种数据类型的类模板
//STL数据结构,算法,适用任何类型
1 #include <iostream> 2 #include <string> 3 4 //定义两种数据类型的类模板 5 //STL数据结构,算法,适用任何类型 6 template<class T1, class T2> 7 class myclass 8 { 9 public: 10 T1 t11; 11 T2 t22; 12 myclass(T1 t111, T2 t222) :t11(t111), t22(t222) 13 { 14 15 } 16 void print() 17 { 18 std::cout << t11 << " " << t22 << std::endl; 19 } 20 }; 21 22 void main() 23 { 24 myclass<int, double>my1(10, 20.8); 25 my1.print(); 26 27 myclass<double, std::string>my2(20.8, "123456abc"); 28 my2.print(); 29 30 system("pause"); 31 }
//类模板可以有一个默认的值,C++11
1 #include "myArray.h" 2 3 template<class T = int>//类模板可以有一个默认的值,C++11 4 myArray<T>::myArray() 5 { 6 std::cout << "构造" << typeid(T).name() << std::endl; 7 } 8 9 template<class T = int>//类模板可以有一个默认的值,C++11 10 myArray<T>::~myArray() 11 { 12 std::cout << "销毁" << typeid(T).name() << std::endl; 13 }
9.3.2类模板作函数参数
函数的形式参数类型可以是类模板或类模板的引用。对应的实际参数是该类模板实例化的模板类对象。
当一个函数拥有类模板参数时,这个函数必定是函数模板。
9.3.3在类层次中的类模板
类模板派生普通类,在定义派生类时要对基类的抽象类参数实例化。
从普通类派生模板类,意味着派生类添加了抽象类数据成员。
一个类模板在类层次结构中既可以是基类也可以是派生类:
1类模板可以从模板类派生。
2类模板可以从非模板类派生。
3模板类可以从类模板派生。
4非模板类可以从类模板派生。
//类模板可以直接继承类模板,但是类型必须传递
//普通类继承类模板,需要明确类型实例化类模板
//类模板继承普通类,常规操作方式
//类模板当作普通类使用,需要模板参数实例化
类模板->类模板
1 #include <iostream> 2 #include <string> 3 4 //模板类的继承 5 6 template <class T> 7 class myclass//基类 8 { 9 public: 10 T x; 11 myclass(T t) :x(t)//构造函数 12 { 13 14 } 15 void print() 16 { 17 std::cout << x << std::endl; 18 } 19 }; 20 21 template <class T> 22 class newclass :public myclass<T>//派生类 23 { 24 public: 25 T y; 26 newclass(T t1, T t2) :myclass(t1), y(t2)//构造函数 27 { 28 29 } 30 void print() 31 { 32 std::cout << x << " " << y << std::endl; 33 } 34 }; 35 36 void main() 37 { 38 newclass<double>my1(10.9, 2.3); 39 my1.print(); 40 41 newclass<std::string>my2("abc", "xyz"); 42 my2.print(); 43 44 system("pause"); 45 }
普通类->类模板
1 #include <iostream> 2 #include <string> 3 4 class xyz//基类 5 { 6 public: 7 int x; 8 int y; 9 int z; 10 xyz(int a, int b, int c) :x(a), y(b), z(c)//构造函数 11 { 12 13 } 14 void print() 15 { 16 std::cout << x << " " << y << " " << z << std::endl; 17 } 18 }; 19 20 template<class T> 21 class newxyz :public xyz//派生类 22 { 23 public: 24 T a; 25 newxyz(T t1, int a1, int b1, int c1) :xyz(a1, b1, c1), a(t1)//构造函数 26 { 27 28 } 29 void print() 30 { 31 std::cout << "Ta=" << a << std::endl; 32 std::cout << x << " " << y << " " << z << std::endl; 33 } 34 }; 35 36 void main() 37 { 38 std::string str1 = "china"; 39 newxyz<std::string> new1(str1, 10, 90, 89); 40 new1.print(); 41 42 system("pause"); 43 }
普通类->类模板->普通类
1 #include <iostream> 2 #include <string> 3 4 //类模板可以直接继承类模板,但是类型必须传递 5 //普通类继承类模板,需要明确类型实例化类模板 6 //类模板继承普通类,常规操作方式 7 //类模板当作普通类使用,需要模板参数实例化 8 9 class xyz//父类 10 { 11 public: 12 int x; 13 int y; 14 int z; 15 xyz(int a, int b, int c) :x(a), y(b), z(c)//构造函数 16 { 17 18 } 19 void print() 20 { 21 std::cout << x << " " << y << " " << z << std::endl; 22 } 23 }; 24 25 template<class T> 26 class newxyz :public xyz//子类 27 { 28 public: 29 T a; 30 newxyz(T t1, int a1, int b1, int c1) :xyz(a1, b1, c1), a(t1)//构造函数 31 { 32 33 } 34 void print() 35 { 36 std::cout << "Ta=" << a << std::endl; 37 std::cout << x << " " << y << " " << z << std::endl; 38 } 39 }; 40 41 class classrun :public newxyz<int>//孙类 42 { 43 public: 44 int d = 1000; 45 classrun(int a2, int b2, int c2, int d2) :newxyz<int>(a2, b2, c2, d2)//构造函数 46 { 47 48 } 49 void print() 50 { 51 std::cout << d << " " << x << " " << y << " " << z << " " << a << std::endl; 52 } 53 }; 54 55 void main() 56 { 57 classrun run1(1, 2, 3, 4); 58 run1.print(); 59 60 system("pause"); 61 } 62 63 void main3() 64 { 65 std::string str1 = "china"; 66 newxyz<std::string> new1(str1, 10, 90, 89); 67 new1.print(); 68 69 system("pause"); 70 }
面试:什么是模板抽象类?
模板抽象类,不能说明抽象类的对象,但可以说明指向抽象类对象的指针(或引用)
1 #include <iostream> 2 3 template <class T>//模板抽象类,不能说明抽象类的对象,但可以说明指向抽象类对象的指针(或引用) 4 class myclass 5 { 6 public: 7 T x; 8 myclass(T t) :x(t)//构造函数 9 { 10 11 } 12 virtual void print() = 0;//纯虚函数 13 }; 14 15 template <class T> 16 class newclass :public myclass<T> 17 { 18 public: 19 T y; 20 newclass(T t1, T t2) :myclass(t1), y(t2)//构造函数 21 { 22 23 } 24 virtual void print()//虚函数 25 { 26 std::cout << x << " " << y << std::endl; 27 } 28 }; 29 30 void main() 31 { 32 myclass<int> *p = new newclass<int>(10, 9);//模板抽象类,不能说明抽象类的对象,但可以说明指向抽象类对象的指针(或引用) 33 p->print();//10 9 34 35 system("pause"); 36 }
9.3.4类模板与友元
在类模板中可以声明各种友元关系
一个函数或函数模板可以是类或类模板的友元。
一个类或类模板可以是类或类模板的友元类。
声明这种模板之间的友元关系符号比较烦琐。
1友元函数在类模板的内部定义
2友元函数在类模板的外部定义
1友元函数在类模板的内部定义
operator友元运算符重载+,实现两个类的对象的x和y分别相加
1 #include <iostream> 2 3 template <class T> 4 class myclass 5 { 6 private: 7 T x; 8 T y; 9 public: 10 myclass(T t1, T t2) :x(t1), y(t2)//构造函数 11 { 12 13 } 14 friend void print(myclass<T> &my)//友元函数在类模板的内部定义 15 { 16 std::cout << my.x << " " << my.y << std::endl; 17 } 18 friend myclass * operator+(const myclass<T> &my1, const myclass<T> &my2)//重载+ 19 { 20 myclass *p = new myclass(my1.x + my2.x, my1.y + my2.y);//创建在堆上,为了返回 21 return p; 22 } 23 }; 24 25 void main() 26 { 27 myclass<int>my1(19, 29); 28 myclass<int>my2(11, 1); 29 30 myclass<int> *pclass = my1 + my2;//重载+ 31 32 print(*pclass);//30 30 33 34 system("pause"); 35 }
2友元函数在类模板的外部定义
//如果友元函数在类模板的外部定义,第1声明要加类型T,第2必须上方声明类和函数
1 #include <iostream> 2 template<class T> class myclass;//声明类 3 template<class T> void print(myclass<T> & my);//声明函数 4 5 //如果友元函数在类模板的外部定义,第1声明要加类型T,第2必须上方声明类和函数 6 7 template<class T> 8 class myclass 9 { 10 private: 11 T x; 12 public: 13 myclass(T t) :x(t) 14 { 15 16 } 17 friend void print<T>(myclass<T> & my);//声明友元函数 18 }; 19 20 template<class T> 21 void print(myclass<T> & my)//定义友元函数 22 { 23 std::cout << my.x << std::endl; 24 } 25 26 void main() 27 { 28 myclass<int>my1(10); 29 myclass<double>my2(10.9); 30 31 print(my1);//10 32 print(my2);//10.9 33 34 system("pause"); 35 }
类模板和友元类
1 #include <iostream> 2 template<class T> class runclass;//声明友元类 3 4 //友元类必须声明类的存在 5 //需要声明友元类,必须要与类型相关 6 7 template<class T> 8 class myclass 9 { 10 private: 11 T x; 12 public: 13 myclass(T t) :x(t) 14 { 15 16 } 17 friend class runclass<T>;//声明友元类 18 }; 19 20 template<class T> 21 class runclass//定义友元类 22 { 23 public: 24 void print(const myclass<T> & my)//打印不修改,const引用 25 { 26 std::cout << my.x << std::endl; 27 } 28 }; 29 30 void main() 31 { 32 myclass<double>my1(10.9); 33 runclass<double>run1; 34 35 run1.print(my1); 36 37 system("pause"); 38 }
面试,类模板当作类模板的参数
1 #include <iostream> 2 #include <string> 3 4 //面试,类模板当作类模板的参数 5 6 template<class T> 7 class ren//一个通用类型的类模板 8 { 9 public: 10 T name; 11 ren(T t) :name(t)//构造函数 12 { 13 14 } 15 }; 16 17 template<template<class T>class T1> 18 class people//类模板当作类模板的参数 19 { 20 public: 21 T1<std::string>t1x = "123";//T1必须实例化,必须结合 22 T1<std::string>num = "ABC";//等价于ren类型 23 people(T1<std::string>t1)//构造函数 24 { 25 std::cout << typeid(t1).name() << std::endl;//打印类型 26 std::cout << typeid(T1).name() << std::endl;//打印类型 27 } 28 }; 29 30 void main() 31 { 32 ren<std::string>ren1("hello");//基本数据类型 33 people<ren>people1(ren1); 34 35 std::cout << people1.t1x.name << std::endl; 36 std::cout << people1.num.name << std::endl; 37 38 system("pause"); 39 }
9.3.5类模板与static成员
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员。
和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化。
每个模板类有自己的类模板的static数据成员副本。
//类模板的static静态数据成员,访问方式:1对象,2类名<数据类型>
//不同数据类型的静态数据成员,地址不一样
//相同数据类型的静态数据成员,地址一样
1 #include <iostream> 2 #include <string> 3 4 //类模板的static静态数据成员,访问方式:1对象,2类名<数据类型> 5 //不同数据类型的静态数据成员,地址不一样 6 //相同数据类型的静态数据成员,地址一样 7 8 template<class T> 9 class myclass 10 { 11 public: 12 static int num;//声明静态数据成员 13 T a; 14 myclass(T t) :a(t)//构造函数 15 { 16 num++;//计数器 17 } 18 }; 19 20 template<class T> 21 int myclass<T>::num = 0;//初始化静态数据成员 22 23 void main() 24 { 25 myclass<int>my1(10); 26 myclass<double>my2(10.9); 27 myclass<std::string>my3("1234"); 28 myclass<int>my4(10); 29 30 //访问方式:1对象 31 std::cout << my1.num << std::endl;//2 32 std::cout << my2.num << std::endl;//1 33 std::cout << my3.num << std::endl;//1 34 std::cout << my4.num << std::endl;//2 35 36 //发现<int>的地址一样 37 std::cout << &my1.num << std::endl;//一样 38 std::cout << &my2.num << std::endl; 39 std::cout << &my3.num << std::endl; 40 std::cout << &my4.num << std::endl;//一样 41 42 //访问方式:2类名<数据类型> 43 std::cout << myclass<int>::num << std::endl; 44 45 system("pause"); 46 }
类模板以及类模板嵌套
一个普通类的对象作为一个类模板的成员,可以紧跟在类说明之后进行定义
一个类模板的对象作为一个类模板的成员,不可以紧跟在类说明之后进行定义
1 #include <iostream> 2 3 template<class T> 4 class myclass 5 { 6 public: 7 class newclass 8 { 9 public: 10 int num; 11 }new1;//一个普通类的对象作为一个类模板的成员,可以紧跟在类说明之后进行定义 12 13 template<class V> 14 class runclass 15 { 16 public: 17 V v1; 18 };//一个类模板的对象作为一个类模板的成员,不可以紧跟在类说明之后进行定义 19 runclass<T>t1;//先说明类,再单独进行变量定义 20 runclass<double>t2;//先说明类,再单独进行变量定义 21 }; 22 23 void main() 24 { 25 myclass<int> my1; 26 my1.new1.num = 10; 27 28 my1.t1.v1 = 12; 29 my1.t2.v1 = 12.9; 30 31 std::cout << my1.t1.v1 << std::endl;//12 32 std::cout << my1.t2.v1 << std::endl;//12.9 33 34 system("pause"); 35 }
小结
模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
同一个类属参数可以用于多个模板。
类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数模板称为模板函数;实例化的类模板称为模板类。
函数模板可以用多种方式重载。
类模板可以在类层次中使用。