天天看點

C++模版模版

模版

模版是一種架構,允許你用不同類型的資料套進架構中進而實作特定的功能,提高了代碼的可複用性。

在使用方面,将類型參數化,允許你使用未知的資料類型(參數類型模版化、傳回值類型模版化)

C++中另一種程式設計思想:泛型程式設計,其主要技術就是模版,模版包括函數模版和類模闆兩種。

函數模版

基本使用

參數類型/傳回值類型模版化(或者說 類型參數化),文法如下:

template<typename T>
函數聲明或定義
           
template<typename T>
T add(T& a, T& b) {
  T tmp;
  tmp = a+b;
  return tmp;
}

int main() {
  int a=1, b=2;
  // 模版調用規則 
  // 1. 自動類型推導
  int c = add(a,b);
  
  // 2. 顯示指定規則
  int c = add<int>(a,b); // 大概就這樣文法,可能會error
  cout << "c="<< c<< endl; //c=3
}
           

注意事項:

  • 自動類型推導,必須推導出一緻的資料類型T,才可以使用
  • 模闆必須要确定出T的資料類型,才可以使用
//利用模闆提供通用的交換函數
template<class T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}


// 1、自動類型推導,必須推導出一緻的資料類型T,才可以使用
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';

	mySwap(a, b); // 正确,可以推導出一緻的T
	//mySwap(a, c); // 錯誤,推導不出一緻的T類型
}


// 2、模闆必須要确定出T的資料類型,才可以使用
template<class T>
void func()
{
	cout << "func 調用" << endl;
}

void test02()
{
	//func(); //錯誤,模闆不能獨立使用,必須确定出T的類型
	func<int>(); //利用顯示指定類型的方式,給T一個類型,才可以使用該模闆
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}
           

普通函數和模版的差別

  • 普通函數調用時可以發生自動類型轉換(隐式類型轉換)
  • 函數模闆調用時,如果利用自動類型推導,不會發生隐式類型轉換
  • 如果利用顯示指定類型的方式,可以發生隐式類型轉換
//普通函數
int myAdd01(int a, int b)
{
	return a + b;
}

//函數模闆
template<class T>
T myAdd02(T a, T b)  
{
	return a + b;
}

//使用函數模闆時,如果用自動類型推導,不會發生自動類型轉換,即隐式類型轉換
void test01()
{
	int a = 10;
	int b = 20;
	char c = 'c';
	
	cout << myAdd01(a, c) << endl; //正确,将char類型的'c'隐式轉換為int類型  'c' 對應 ASCII碼 99

	//myAdd02(a, c); // 報錯,使用自動類型推導時,不會發生隐式類型轉換

	myAdd02<int>(a, c); //正确,如果用顯示指定類型,可以發生隐式類型轉換
}
           

函數模版和普通模版的調用規則

  1. 如果函數模闆和普通函數都可以實作,優先調用普通函數
  2. 可以通過空模闆參數清單來強制調用函數模闆
  3. 函數模闆也可以發生重載
  4. 如果函數模闆可以産生更好的比對,優先調用函數模闆
void print(int a, int b) {
  cout << "調用普通函數..." << endl;
}

templete<typename T>
void print(T& a, T& b) {
  cout << "調用模版函數..." << endl;
}

templete<typename T>
void print(T& a, T& b, T& c) {
  cout << "調用重載模版函數..." << endl;
}

int main() {
	int a = 1, b = 2;
  // 優先調用普通函數
  print(a,b);
  // 強制調用模版函數
  print<>(a,b);
  // 如果函數模闆可以産生更好的比對,優先調用函數模闆
	char c1 = 'a';
	char c2 = 'b';
	print(c1, c2); //調用函數模闆
  
  
  return 0;
}
           

函數模版的局限性

函數模版不是萬能的,比如上面的

add

函數,當參數類型為數組、自定義資料類型時,函數調用會出錯(運作期間出錯)

C++為了解決這種問題,提供模闆的重載,可以為這些特定的類型提供具體化的模闆

#include<iostream>
using namespace std;

#include <string>

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
	string m_Name;
	int m_Age;
};

//普通函數模闆
template<class T>
bool myCompare(T& a, T& b)
{
	if (a == b)
	{
		return true;
	}
	else
	{
		return false;
	}
}


//具體化,顯示具體化的原型和定意思以template<>開頭,并通過名稱來指出類型
//具體化優先于正常模闆
template<> bool myCompare(Person &p1, Person &p2)
{
	if ( p1.m_Name  == p2.m_Name && p1.m_Age == p2.m_Age)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test01()
{
	int a = 10;
	int b = 20;
	//内置資料類型可以直接使用通用的函數模闆
	bool ret = myCompare(a, b);
	if (ret)
	{
		cout << "a == b " << endl;
	}
	else
	{
		cout << "a != b " << endl;
	}
}

void test02()
{
	Person p1("Tom", 10);
	Person p2("Tom", 10);
	//自定義資料類型,不會調用普通的函數模闆
	//可以建立具體化的Person資料類型的模闆,用于特殊處理這個類型
	bool ret = myCompare(p1, p2);
	if (ret)
	{
		cout << "p1 == p2 " << endl;
	}
	else
	{
		cout << "p1 != p2 " << endl;
	}
}

int main() {

	test01();

	test02();

	system("pause");

	return 0;
}
           

類模闆

類模闆就是 類内成員資料類型參數化

基本使用

#include<iostream>
#include<string>
using namespace std;

template<class T1, class T2=string>
class Person {
public:
  Person(T1 age, T2 name) {
    this->age = age;
    this->name = name;
  }

  void showPerson() {
    cout << this->name << "'s age is" << this->age << endl;
  }

public:
  T1 age;
  T2 name;
};

int main() {
  //Person p("孫悟空", 1000); // 錯誤 類模闆使用時候,不可以用自動類型推導
  Person<int,string> p(19, "zhangsan");//必須使用顯示指定類型的方式,使用類模闆
  p.showPerson();
}
           

類模闆和函數模闆的差別:

  1. 類模闆沒有自動類型推導的使用方式
  2. 類模闆在模闆參數清單中可以有預設參數

類模闆中成員函數和普通類中成員函數建立時機是有差別的:

  • 普通類中的成員函數一開始就可以建立
  • 類模闆中的成員函數在調用時才建立
class Person1
{
public:
	void showPerson1()
	{
		cout << "Person1 show" << endl;
	}
};

class Person2
{
public:
	void showPerson2()
	{
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass
{
public:
	T obj;

	//類模闆中的成員函數,并不是一開始就建立的,而是在模闆調用時再生成

	void fun1() { obj.showPerson1(); }
	void fun2() { obj.showPerson2(); }

};

void test01()
{
	MyClass<Person1> m;
	
	m.fun1();

	//m.fun2();//編譯會出錯,說明函數調用才會去建立成員函數
}

int main() {

	test01();

	system("pause");

	return 0;
}
           

類模闆對象作函數參數

類模闆執行個體化出的對象,向函數傳參的方式有三種:

  1. 指定傳入的類型 — 直接顯示對象的資料類型
  2. 參數模闆化 — 将對象中的參數變為模闆進行傳遞
  3. 整個類模闆化 — 将這個對象類型 模闆化進行傳遞
template<class T1, class T2>
class Person {
public:
  Person(T1 age, T2 name) {
    this->age = age;
    this->name = name;
  }
  void showPerson() {
    cout << this->name << "'s age is" << this->age << endl;
  }
public:
  T1 age;
  T2 name;
}

// 指定傳入類型方式
void printPerson(Person<int,string> &p) {
  p.showPerson();
}
// 參數模版化
template<class T1, class T2>
void printPerson2(Person<T1,T2> &p) {
  p.showPerson();
  cout << "T1的類型為: " << typeid(T1).name() << endl;
	cout << "T2的類型為: " << typeid(T2).name() << endl;
}
// 類模闆化
template<class T>
void printPerson3(T& p) {
  p.showPerson();
  cout << "T的類型為: " << typeid(T).name() << endl;
}
int main() {
  Person<int,string> p(18,"zhangsan");
  printPerson1(p);
  printPerson2(p);
  printPerson3(p);
}
           

類模闆與繼承

當類模闆碰到繼承時,需要注意一下幾點:

  • 當子類繼承的父類是一個類模闆時,子類在聲明的時候,要指定出父類中T的類型
  • 如果不指定,編譯器無法給子類配置設定記憶體
  • 如果想靈活指定出父類中T的類型,子類也需變為類模闆
template<class T>
class Base
{
	T m;
};

//class Son:public Base  //錯誤,c++編譯需要給子類配置設定記憶體,必須知道父類中T的類型才可以向下繼承
class Son :public Base<int> //必須指定一個類型
{
};
void test01()
{
	Son c;
}

//類模闆繼承類模闆 ,可以用T2指定父類中的T類型
template<class T1, class T2>
class Son2 :public Base<T2>
{
public:
	Son2()
	{
		cout << typeid(T1).name() << endl;
		cout << typeid(T2).name() << endl;
	}
};

void test02()
{
	Son2<int, char> child1;
}


int main() {

	test01();

	test02();

	system("pause");

	return 0;
}
           

類模闆成員函數類外實作

template<class T>
class Person {
public:
  Person(T name) {};
public:
  T name;
};

template<class T>
Person<T>::Person(T name) { // 類外實作需要加上模闆參數清單
  this->name = name;
}
           

類模闆分檔案編寫

  • 類模闆中成員函數建立時機是在調用階段,導緻分檔案編寫時連結不到

解決:

  • 解決方式1:直接包含.cpp源檔案
  • 解決方式2:将聲明和實作寫到同一個檔案中,并更改字尾名為.hpp,hpp是約定的名稱,并不是強制

person.hpp中代碼:

#pragma once
#include <iostream>
using namespace std;
#include <string>

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);
	void showPerson();
public:
	T1 m_Name;
	T2 m_Age;
};

//構造函數 類外實作
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->m_Name = name;
	this->m_Age = age;
}

//成員函數 類外實作
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名: " << this->m_Name << " 年齡:" << this->m_Age << endl;
}
           

類模闆分檔案編寫.cpp中代碼

#include<iostream>
using namespace std;

//#include "person.h"
#include "person.cpp" //解決方式1,包含cpp源檔案

//解決方式2,将聲明和實作寫到一起,檔案字尾名改為.hpp
#include "person.hpp"
void test01()
{
	Person<string, int> p("Tom", 10);
	p.showPerson();
}

int main() {

	test01();

	system("pause");

	return 0;
}
           

總結:主流的解決方式是第二種,将類模闆成員函數寫到一起,并将字尾名改為.hpp

類模闆和友元

#include <string>

//2、全局函數配合友元  類外實作 - 先做函數模闆聲明,下方在做函數模闆定義,在做友元
template<class T1, class T2> class Person;

//如果聲明了函數模闆,可以将實作寫到後面,否則需要将實作體寫到類的前面讓編譯器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p); 

template<class T1, class T2>
void printPerson2(Person<T1, T2> & p)
{
	cout << "類外實作 ---- 姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}

template<class T1, class T2>
class Person
{
	//1、全局函數配合友元   類内實作
	friend void printPerson(Person<T1, T2> & p)
	{
		cout << "姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
	}


	//全局函數配合友元  類外實作
	friend void printPerson2<>(Person<T1, T2> & p);

public:

	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}


private:
	T1 m_Name;
	T2 m_Age;

};

//1、全局函數在類内實作
void test01()
{
	Person <string, int >p("Tom", 20);
	printPerson(p);
}


//2、全局函數在類外實作
void test02()
{
	Person <string, int >p("Jerry", 30);
	printPerson2(p);
}

int main() {

	//test01();

	test02();

	system("pause");

	return 0;
}
           

繼續閱讀