1 模板
1.1 模板的概念
模板就是建立通用的模具,大大提高復用性
1.2 函式模板
- C++另一種編程思想稱為泛型函式,主要利用的技術就是模板
- C++提供兩種模板機制:函式模板和類模板
1.2.1 函式模板語法
模板函式作用:
建立一個通用函式,其函式回傳值型別和形參型別可以不具體指定,用一個虛擬的型別來代表
語法:
template<typename T>
函式宣告或定義
解釋:
template --- 宣告創建模板
typename --- 表面其后面的符號是一種資料型別,可以用class代替
T --- 通用的資料型別,名稱可以替換,通常為大寫字母
示例;
#include<iostream>
using namespace std;
//函式模板
//兩個整數交換函式
void swapInt(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
//交換兩個浮點型的函式
void swapDouble(double& a, double& b)
{
double temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
swapInt(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
//函式模板
template <typename T>//宣告一個模板告訴編譯器后面代碼中緊跟著的T不要報錯,T是一個通用的資料型別
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test02()
{
int a = 10;
int b = 20;
//1、自動型別推導
mySwap(a, b);
//2、顯示指定型別
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
1.2.2 函式模板注意事項
注意事項:
- 自動型別推導,必須推匯出一致的函式型別T,才可以使用
- 模板必須要確定出T的資料型別,才可以使用
示例:
#include<iostream>
using namespace std;
//函式模板注意事項
//template<typename T> // typename可以替換成calss
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);//正確
cout << "a = " << a << endl;
cout << "b = " << b << endl;
//mySwap(a, c);//報錯,無法推匯出一致的資料型別
}
//2、模板必須要確定出T的資料型別,才可以使用
template<class T>
void func()
{
cout << "func 呼叫" << endl;
}
void test02()
{
func<int>();//隨便給一個資料型別就能呼叫func模板函式了
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
1.2.3 函式模板案例
案例描述:
- 利用函式模板封裝成一個排序的函式,可以對不同資料型別陣列進行排序
- 排序規則從大到小,排序演算法為選擇排序
- 分別利用char陣列和int陣列進行測驗
示例:
#include <iostream>
using namespace std;
//實作通用 對陣列進行排序的函式
//規則 從大到小
//演算法 選擇
//測驗 char 陣列,int 陣列
//交換函式模板
template<class T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
//排序演算法
template<class T>
void mySort(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
int max = i;//認定最大值下標
for (int j = i + 1; j < len; j++)
{
//認定的最大值比遍歷出的數值要小,說明j下標的元素才是真正的最大值
if (arr[max] < arr[j])
{
max = j;//更新最大值下標
}
}
if (max != i)
{
//交換max和i元素
mySwap(arr[max], arr[i]);
}
}
}
//列印陣列的模板
template<class T>
void printArray(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << "arr中的第" << i << "元素 = " << arr[i] << endl;
}
}
void test01()
{
//測驗char陣列
char charArr[] = "badcfe";
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr, num);
}
void test02()
{
//測驗int陣列
int intArr[] = { 7,5,1,3,10,2,4,7,9 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
1.2.4 普通函式與函式模板的區別
普通函式與函式模板區別:
- 普通函式呼叫時可以發生自動型別轉換(隱式型別轉換)
- 函式模板呼叫時,如果利用自動型別推導,不會發生隱式型別轉換
- 如果利用顯示指定型別的方式,可以發生隱式型別轉換
示例:
#include <iostream>
using namespace std;
//普通函式與函式模板的區別
//1、普通函式呼叫可以發生隱式型別轉換
//2、函式模板 用自動型別推導,不可以發生隱式型別轉換
//3、函式模板 用顯示指定型別,可以發生隱式型別轉換
//普通函式
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'; //c - 99
cout << myAdd01(a, c) << endl;
//自動型別推導
//cout << myAdd02(a, c) << endl;//報錯,函式模板不可以發生隱式型別轉換
//顯示指定型別
cout << myAdd02<int>(a, c) << endl;//明確型別,正確
}
int main()
{
test01();
system("pause");
return 0;
}
1.2.5 普通函式與函式模板的呼叫規則
呼叫規則如下:
- 如果函式模板和普通函式可以實作,優先呼叫普通函式
- 可以通過空模板引數串列來強調函式模板
- 函式模板也可以發生多載
- 如果函式模板可以產生更好的匹配優先呼叫函式模板
示例:
#include<iostream>
using namespace std;
//普通函式與函式模板呼叫規則
//1、如果函式模板和普通函式都可以呼叫,優先呼叫普通函式
//2、可以通過空模板引數串列 強制呼叫 函式模板
//3、函式模板可以發生函式多載
//4、如果函式模板可以產生更好的匹配,優先呼叫函式模板
void myPrint(int a, int b)//如果只有宣告,沒有函式實作,就會報錯,還是優先呼叫普通函式
{
cout << "呼叫普通函式" << endl;
}
template<class T>
void myPrint(T a, T b)
{
cout << "呼叫模板" << endl;
}
template<class T>
void myPrint(T a, T b,T c)
{
cout << "呼叫多載的模板" << endl;
}
void test01()//優先呼叫普通函式
{
int a = 10;
int b = 10;
myPrint(a, b);//呼叫普通函式
//通過空模板的引數串列,強制呼叫函式模板
myPrint<>(a, b);//呼叫函式模板
myPrint(a, b, 100);//呼叫多載函式模板
//如果函式模板產生更好的匹配,優先呼叫函式模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2);//呼叫函式模板
}
int main()
{
test01();
system("pause");
return 0;
}
總結:既然提供了函式模板,最好就不要提供普通函式了,容易產生二義性,實際開發中這樣子意義不大,
1.2.6 模板的局限性
局限性:
- 模板的通用性并不是萬能的
例如
template<class T>
void f(T a, T b)
{
a = b;
}
在上述代碼中提供了賦值操作,如果傳入a和b是一個陣列,就無法實作了,
再例如
template<class T>
void f(T a, T b)
{
if(a > b){...}
}
在上述代碼中,如果T的資料型別傳入的是像Person的自定義資料型別,也是無法正常運行的,
因此為了解決這類問題,提供模板的多載,可以為這些特定的型別提供具體化的模板
示例:
#include<iostream>
#include<string>
using namespace std;
//模板的局限性
//模板并不是萬能的,有些特定的資料型別,需要用具體化的方式做實作
//對比兩個資料型別是否相等函式
class Person
{
public:
Person(string name, int age)
{
this->m_Age = age;
this->m_Name = name;
}
string m_Name;
int m_Age;
};
template<class T>
bool myCompare(T& a, T& b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
//利用具體化的Person的版本實作代碼,具體化優先呼叫
template<> bool myCompare(Person& p1, Person& p2)
{
if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
{
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);
bool ret = myCompare(p1, p2);
if (ret)
{
cout << "p1 == p2" << endl;
}
else
{
cout << "p1 != p2" << endl;
}
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
總結:
- 利用具體化的模板,可以解決自定義型別的通用化
- 學習模板并不是為了寫模板,而是在STL能夠運用系統提供的模板
1.3 類模板
1.3.1 類模板語法
類模板作用:
- 建立一個通用類,類中成員資料型別可以不具體指定,用一個虛擬的型別來代表
語法:
template<typname T>
類
解釋:
template --- 宣告創建模板
typename --- 表明其后面的符號是一種資料型別,可以用class代替
T --- 通用的資料型別,名稱可以替換,通常為大寫字母
示例:
#include<iostream>
#include<string>
using namespace std;
//類模板
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
};
void test01()
{
Person<string, int>p1("孫悟空", 999);
p1.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
1.3.2 類模板與函式模板區別
類模板與函式模板區別主要有兩點:
- 類模板沒有自動型別推導的使用方式
- 類模板在模板引數串列中可以有模板引數
示例:
#include<iostream>
#include<string>
using namespace std;
//類模板與函式模板區別
template<class NameType, class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
NameType m_Name;
AgeType m_Age;
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
};
//1、類模板沒有自動型別推導使用方式
void test01()
{
//Person p("孫悟空",999)//錯誤,無法用自動型別推導
Person<string, int>p("孫悟空", 999);
p.showPerson();
}
//2、類模板在模板引數串列中可以有默認引數
void test02()
{
Person<string>p("豬八戒", 1000);
p.showPerson();
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
1.3.3 類模板中成員函式創建時機
類模板中成員函式和普通類中成員函式創建時機是有去別的:
- 普通類中的成員函式一開始就可以創建
- 類模板中的成員函式在呼叫時才創建
示例
#include<iostream>
using namespace std;
//類模板中成員函式創建時機
//類模板中成員函式在呼叫時才去創建
class Pesrson1
{
public:
void showPerson1()
{
cout << "Person1 show!" << endl;
}
};
class Pesrson2
{
public:
void showPerson2()
{
cout << "Person2 show!" << endl;
}
};
template<class T>
class MyClass
{
public:
T obj;
//類模板中的成員函式//是在 被呼叫時候才會創建的,需要確認是什么成員函式型別之后才能被呼叫,不然呼叫不出來,
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
void test01()
{
MyClass<Pesrson1> m;
m.func1();
//m.func2();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:類模板中的成員函式不是一開始就創建的,在呼叫時才去創建的,
1.3.4 類模板物件做函式引數
學習目標:
- 類模板實體化出的物件,向函式傳參的方式
一共有三種傳入方式:
- 指定傳入的型別 --- 直接顯示物件的資料型別
- 引數模板化 --- 將物件中的引數變為模板進行傳遞
- 整個類模板化 --- 將整個物件型別 模板化進行傳遞
示例:
#include<iostream>
#include<string>
using namespace std;
//類模板物件做函式引數
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定傳入型別 //最常用
void printPerson1(Person<string,int>&p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("孫悟空", 100);
printPerson1(p);
}
//2、將引數模板化
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;
}
void test02()
{
Person<string, int>p("豬八戒", 1000);
printPerson2(p);
}
//3、整個類模板化
template<class T>
void printPerson3(T& p)
{
p.showPerson();
}
void test03()
{
Person<string, int>p("唐僧", 30);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
總結:
- 通過類模板創建的物件,可以有三種方式向函式中進行傳參
- 比較廣泛使用的方式是指定傳入型別
1.3.5 類模板與繼承
當類模板碰到繼承時,需要注意以下幾點:
- 當子類繼承的父類是一個類模板時,子類在宣告的時候,要指定出父類中T的型別
- 如果不指定,編譯器無法給子類分配記憶體
- 如果想靈活指定出父類中T的型別,子類也需變為類模板
示例:
#include<iostream>
using namespace std;
//類模板與繼承
template<class T>
class Base
{
T m;
};
//class Son :public Base//錯誤,必須知道父類中的T型別,才能繼承子類
class Son:public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想要靈活的指定父類中T的型別,子類也需要邊類模板
template<class T1, class T2>
class Son2 :public Base<T2>
{
T1 obj;
};
void test02()
{
Son2<int, char>S2;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
1.3.6 類模板成員函式類外實作
學習目標:能夠掌握類模板中的成員函式類外實作
示例:
#include<iostream>
#include<string>
using namespace std;
//類模板成員函式類外實作
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
// Person(T1 name, T2 age)
// {
// this->m_Name = name;
// this->m_Age = age;
// }
void showPerson();
//{
// cout << "姓名: " << m_Name << " 年齡:" << m_Age << endl;
//}
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 << "姓名: " << m_Name << " 年齡:" << m_Age << endl;
}
void test01()
{
Person<string, int>p("Tom", 20);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:類模板中成員函式類外實作時,需要加上模板引數串列
1.3.7 類模板分檔案撰寫
學習目標:
- 掌握類模板成員函式分檔案撰寫產生的問題以及解決方式
問題:
- 類模板中成員函式創建時機是在呼叫階段,導致份檔案撰寫時鏈接不到
解決:
- 解決方式1:直接包含.cpp源檔案
- 解決方式2:將宣告和實作卸載同一個檔案中,并更改后綴為.hpp,hpp是約定的名稱,并不是強制
創建.hpp
示例:
#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl;
}
cpp
#include<iostream>
#include<string >
using namespace std;
#include"person.hpp"
//template<class T1, class T2>
//class Person
//{
//public:
// Person(T1 name, T2 age);
// void showPerson();
// T1 m_Name;
// T2 m_Age;
//};
//template<class T1, class T2>
//Person<T1, T2>::Person(T1 name, T2 age)
//{
// this->m_Age = age;
// this->m_Name = name;
//}
//
//template<class T1, class T2>
//void Person<T1, T2>::showPerson()
//{
// cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl;
//}
//
//2、解決方法 將.h和.cpp內容寫在一起,將后綴名改為.hpp檔案
void test01()
{
Person<string, int>p("Jerry", 18);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
1.3.8 類模板與友元
學習目標:
- 掌握類模板配合友元函式的類內和類外實作
全域函式類內實作-直接在類內宣告友元即可
全域函式類外實作-需要提前讓編譯器知道全域函式的存在
示例:
#include<iostream>
#include<string>
using namespace std;
//提前讓編譯器知道類模板
template<class T1, class T2>
class Person;
//類外實作
template<class T1, class T2>
void printPerson2(Person<T1, T2>p)
{
cout << "類外實作:" << "姓名:" << p.m_Name << " 年齡:" << p.m_Age << endl;
}
//通過全域函式 列印Person資訊
template<class T1, class T2>
class Person
{
// 全域函式 類內實作
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;
};
void test01()
{
Person<string, int>p("Tom", 20);
printPerson(p);
}
void test02()
{
Person<string, int>p ("Jerry", 20);
printPerson2(p);
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
總結:建議全域函式做類內實作,用法簡單,而且編譯器可以直接識別
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/507110.html
標籤:C++
上一篇:C++ 指標與二維陣列名
