本文最后更新于:2021年2月5日 晚上
                
              
            
            
              概览:C++函数模板、类模板,类模板与继承、类模板成员函数实现。
模板就是建立通用的模具,来提高代码的复用性。
模板不能够直接使用,它只是框架。此外模板并非万能。
函数模板基本语法
语法:template<typename T>或者template<class T>.然后接一个函数。
typename或者class均可,看个人的使用习惯。 
T这个也可以随意替换,代表通用模板类型。 
- 函数模板可以由多个类型,使用都逗号分割即可。
template<class T,class P,class M>。 
- 函数模板这句话的声明只对其后紧挨着的第一个函数有效。
 
函数模板的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
   | void swap(int& a, int& b) { 	int temp = a; 	a = b; 	b = temp; }
  template<typename T> void myswap(T& a, T& b) { 	T temp = a; 	a = b; 	b = temp; }
  void test1() {
  	int a = 10; 	int b = 20;
  	swap(a, b); 	cout << "a = " << a << " , b = " << b << endl;
  	int c = 10; 	int d = 20;
  	myswap(c,d);	 	cout << "c = " << c << " , d = " << d << endl;
  	char e = 'A'; 	char f = 'B';
  	myswap<char>(e,f);	 	cout << "e = " << e << " , f = " << f << endl;
  }
 
  | 
 
显然,使用函数模板的优点是:将参数类型化,从而提高代码的复用性。
使用函数模板的两种方式:
- 自动类型推导,
myswap(c,d);不指定类型,让编译器自行推导。 
- 显式指定类型,
myswap<char>(e,f);,显式的在模板参数列表中指定类型。 
- 推荐使用显式指定类型的方式。
 
函数模板使用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | template<typename T> void myswap(T& a, T& b) { 	T temp = a; 	a = b; 	b = temp; }
  template<typename T> void func() { 	cout << "func" << endl; }
 
  template<typename T> void func(T a) { 	cout << a << " func" << endl; }
  void test2() {
  	int a = 10; 	char b = 'A';
  	 	
  	 	func<int>();		
  	func<int>(a);		
  }
 
  | 
 
- 对于模板参数类型T,必须推导出一致的数据类型才能够使用。
 
- 模板必须确定T的数据类型,才可以使用。
 
- 函数模板可以重载。
 
普通函数与函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | int add(int a, int b) { 	return a + b; }
  template<typename T> T t_add(T a, T b) { 	return a + b; }
  void test3() { 	int a = 1; 	int b = 2; 	char c = 'A';
  	cout<<add(a, b)<<endl;	 	cout<<add(a, c)<<endl;	
  	
  	cout << t_add<int>(a, c)<<endl; }
 
  | 
 
区别
- 普通函数调用时可以发生自动类型转换,即隐式类型转换。
 
- 函数模板调用时,若是自动类型推导,就不会发生隐式类型转换。
 
- 函数模板使用显式类型调用时,可以发生类型转换。
 
普通函数与模板函数调用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | void myfunc(int a) { 	cout << "普通函数" << endl; }
  template<typename T> void myfunc(T a) { 	cout << "模板函数" << endl; }
  void test4() {
  	int a = 10;
  	
  	myfunc(a);		
  	
  	myfunc<>(a);	
  	char b = 'B';
  	
  	myfunc(b);		
  }
 
  | 
 
- 当普通函数和模板函数均可调用调用时,优先调用普通函数。
 
- 可以通过空模板参数列表来强制调用函数模板。
 
- 当函数模板可能产生更好的匹配时,优先调用函数模板。
 
一般的建议是:提供了模板函数就不要再提供普通函数了,容易出现二义性。
函数模板的局限性
模板函数并非万能。例如
1 2 3 4 5
   | template<class T> void f(T a, T b) {      if(a > b) { ... } }
 
  | 
 
当T为自定的类时,通常会无法正常运行。
自定义数据类型的解决
方式一:类重载比较运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | class Person { public: 	int id; 	bool operator>(const Person& p) { 		if (this->id > p.id) 			return true; 		else return false; 	} };
  template<typename T> void cmp(T &a, T& b) { 	if (a > b) 		cout << ">" << endl; 	else cout << "!>" << endl; }
  void test5() { 	Person p1 = { 10 }; 	Person p2 = { 12 };
  	cmp(p1, p2); }
 
  | 
 
缺点就是可能要对每一个比较运算符都需要进行重载。
方式二:具体化模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | class Person { public: 	int id; };
  template<typename T> void cmp(T &a, T& b) { 	if (a > b) 		cout << ">" << endl; 	else cout << "!>" << endl; }
 
  template<> void cmp(Person &a, Person &b) { 	if (a.id > b.id) 		cout << ">" << endl; 	else cout << "!>" << endl; }
  void test5() { 	Person p1 = { 10 }; 	Person p2 = { 12 };
  	cmp(p1, p2); }
 
  | 
 
具体化模板写法:template<> 函数返回值类型 函数名(具体类型 参数)
类模板
语法:template<typename T>或者template<class T>.然后接一个类。
具体规则和函数模板类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   | template<class T1,class T2> class Person { public: 	Person(T1 name,T2 age):m_name(name),m_age(age) 	{}
  	void showInfo() { 		cout << "name:" << m_name << ", age:" << m_age << endl;
  		cout << "name type:" << typeid(m_name).name() << endl; 		cout << "age type:" << typeid(m_age).name() << endl; 	}
  	T1 m_name; 	T2 m_age; };
  void test1() { 	Person<string, int> p1 = Person<string, int>("Cc",18);           	p1.showInfo(); }
 
  | 
 
输出结果:
1 2 3
   | name:Cc, age:18 name type:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > age type:int
 
  | 
 
类模板与函数模板
区别
- 类模板必须使用显式指定类型,没有自动类型的推导方式。
 
- 函数模板如上面所示,可以有隐式类型推导。
 
而可以通过在模板参数列表中设置默认参数来减少指定的类型.
1 2 3 4 5 6
   | template<class T1,class T2 = int> class Person {……};
 
  Person<string> p1 = Person<string>("Cc",18);
 
 
  | 
 
函数模板可在类模板中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | template<class T> class Animal { public: 	Animal(T id) :m_id(id) {}
  	template<typename T2>  	void func(T2 t2) { 		cout << t2 << " " << m_id << endl; 	}
  private: 	T m_id; };
  void test2() {
  	Animal<int> animal(249);
  	animal.func<string>("Id为");
  }
 
  | 
 
类模板中成员函数的创建时机
- 普通类中的成员函数在一开始的时候就可以创建。
 
- 而类模板中的成员函数在调用时才会生成,所以有些代码编辑器检查不出问题,只有编译生成时才会检查出问题。
 
不同类型—类模板的不兼容
1 2 3 4
   | Person<string, int> *p1; Person<string, double> p2("Bb", 12);
  p1 = &p2;	
 
  | 
 
类模板对象做函数参数
当类模板实例化出来的对象作为函数参数时,传参的方式有:
- 传入指定的类型,即直接显式的设置为对象的数据类型。使用较为广泛。
 
- 将参数模板化,将对象中的参数变为模板进行传递。
 
- 将整个类模板化,直接将这个对象类型模板化传递。
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | template<class T1,class T2> class Person { public: 	Person(T1 name,T2 age):m_name(name),m_age(age) 	{}
  	void showInfo() { 		cout << "name:" << m_name << ", age:" << m_age << endl;
  		cout << "name type:" << typeid(m_name).name() << endl; 		cout << "age type:" << typeid(m_age).name() << endl; 	}
  	T1 m_name; 	T2 m_age; };
 
  void myfunc1(Person<string,int> &p) { 	p.showInfo(); }
 
  template<typename T1,typename T2> void myfunc2(Person<T1,T2> &p) { 	p.showInfo(); }
 
  template<typename T> void myfunc3(T &t) { 	t.showInfo(); }
  void test3() { 	Person<string, int> p("Cc", 18);
  	myfunc1(p); 	myfunc2<string,int>(p); 	myfunc3<Person<string, int>>(p);
  }
 
  | 
 
类模板与继承
如果基类是类模板,派生类继承时要指明基类T的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
   |  template<class T> class Base1 { 	T m; };
 
 
  class Son1 :public Base1<int> {};	
 
  class Base2 { 	int m; };
  template<class T> class Son2:public Base2{ 	T v; };
 
  template<class T> class Base3 { 	T m; };
  template<class T> class Son3 :public Base3<int> { 	T n; };
 
  template<class T> class Base4 { 	T m; };
  template<class BT,class ST> class Son4 :public Base4<BT> { 	ST n; };
  void test4() {
  	Son1 son1;	
  	Son2<int> son2;	
  	Son3<char> son3;	
  	Son4<int,char> son4; }
 
  | 
 
类模板成员函数类外实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | template<class NameType,class IdType> class Person { public: 	Person(NameType name, IdType id);
  	void showInfo();
  private: 	NameType m_name; 	IdType m_id; };
 
 
  template<class NameType, class IdType> Person<NameType, IdType>::Person(NameType name, IdType id) { 	m_name = name; 	m_id = id; }
  template<class NameType, class IdType> void Person<NameType, IdType>::showInfo() { 	cout << "name: " << m_name << " , id: " << m_id << endl; }
 
  | 
 
- 类外实现的时候,要把模板的声明加上,而且作用域限定符前的类要加上类名以及模板参数列表。
 
产生问题:类模板分文件编写
上面的代码,进行分文件编写时:
类的定义,Person.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   |  #pragma once
  template<class NameType,class IdType> class Person { public: 	Person(NameType name, IdType id);
  	void showInfo();
  private: 	NameType m_name; 	IdType m_id; };
 
  | 
 
类的实现,Person.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | #include "Person.h"
  #include<iostream> #include <string> using namespace std;
  template<class NameType, class IdType> Person<NameType, IdType>::Person(NameType name, IdType id) { 	m_name = name; 	m_id = id; }
  template<class NameType, class IdType> void Person<NameType, IdType>::showInfo() { 	cout << "name: " << m_name << " , id: " << m_id << endl; }
 
  | 
 
main函数调用:
1 2 3 4 5 6 7 8 9 10 11 12
   | #include <iostream> using namespace std;
  #include "Person.h"
  int main() {
  	Person<string, int> p("VS",2017); 	p.showInfo();
  	return 0; }
 
  | 
 
执行时,编译器会报错误,链接时错误:无法解析的外部符号……。
产生错误原因:类模板中的成员函数的创建时机是在调用阶段,分文件编写时,链接不到这些成员函数。
解决方式:
- 在
main函数那里包含.cpp文件。 
- 将这个模板类的声明与实现写在同一个文件里,并将文件后缀改为
.hpp。.hpp是约定成俗的习惯,也是主流的解决方式。 
一般来说含有模板类的文件都不应该进行分文件编程,它们的声明与实现都应该放在同一个文件之中,并使用.hpp这种文件格式进行存储。
STL中有许多都是采用的这种方式。
类模板与友元
友元函数,全局类内实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | template<class T> class Person {
  	 	friend void showInfo(Person<T> &p) 	{ 		cout << p.m_name << endl; 	}
  public: 	Person(T name) :m_name(name) {}
  private: 	T m_name; };
 
  void test1() { 	Person<string> p("Bob");
  	showInfo(p); }
 
  | 
 
如上,在类内的全局函数加上friend即可。
友元函数,全局类外实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   |  template<class T> class Man;
 
  template<class T> void showManInfo(Man<T> &m) { 	cout << m.m_name << endl; }
  template<class T> class Man {
  	 template<class T>	 	 friend void showManInfo(Man<T> &m);
  public: 	Man(T name) :m_name(name) {}
  private: 	T m_name; };
 
  void test2() { 	Man<string> m("Alice");
  	showManInfo(m); }
 
  | 
 
类外实现比较复杂,为了能让编译器识别必须先放类的声明,再放函数,再放类的实现。
关于类外实现时 类内友元声明的写法:
- 第一种如上所示,
template<class T>友元声明前必须加这个,否则执行时会报错,但是据说这种方式在Linux上不适用。 
- 第二种方式:
friend void showManInfo <T>(Man<T> &m);,这种在全平台均适用。 
这两种方式仅类内友元声明时方式不同,类外的实现写法一模一样,没有任何差别。
建议使用全局函数做类内的实现,用法简单,而且编译器可以直接识别。
类模板与静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | template<class T> class Cat { public: 	Cat(T name) :m_name(name) {}
  	static int sta_a;
  	static T sta_b;
  	static void changeSta_a() { 		sta_a++; 		cout << "sta_a=" << sta_a << endl; 	}
  	T m_name; };
 
 
  template<class T> int Cat<T>::sta_a = 0;
  void test3() { 	Cat<int> c1(1); 	Cat<string> c2("123");
  	c1.changeSta_a();	 	c1.changeSta_a();	
 
  	c2.changeSta_a();	
  }
 
  | 
 
- 静态数据成员初始化的时候,记得加上
template<class T>以及作用域Cat<T>。 
从上述代码可以看到,从类模板实例化的每一个模板类都有自己的类模板数据成员,该模板的所有对象共享一个static的数据成员。