2020/02/27完结
1.模板的基本介绍
模板技术使类型参数化,在编写代码时可以忽略类型,避免编写类似重复的代码。
为了让编译器区分普通函数和模板函数:在模板函数的函数声明前添加template< class T> 或者
template< typename T>。
模板函数可以通过传入函数的参数来自动推导类型,也可以显式地指定类型。
#include <iostream>
using namespace std;
template<class T>
void Swap(T& a, T& b){
T temp = a;
a = b;
b = temp;
}
int main(){
int a = 10;
int b = 20;
// 自动类型推导
cout << "a:" << a << " b:" << b << endl;
Swap(a, b); //编译器根据你传的值 进行类型自动推导
cout << "a:" << a << " b:" << b << endl;
//显示指定类型
double c = 1.23;
double d = 3.45;
cout << "c:" << c << "d:" << d << endl;
Swap<double>(c, d);
cout << "c:" << c << "d:" << d << endl;
return 0;
}
2.普通函数与模板函数的区别
普通函数可对传入的参数进行类型转换,例如:将char类型变量转换为int类型变量。
模板函数对传入的参数进行严格的类型匹配,不会进行类型转换
#include <iostream>
using namespace std;
// 模板函数
template<class T>
T MyAdd(T a, T b){
return a + b;
}
// 普通函数
int MyAdd(int a, int b){
return a + b;
}
int main(){
int a = 10, b = 20;
char c1 = 'a', c2 = 'b';
MyAdd<>(a, b); // 调用模板函数
MyAdd(a, b); // 调用普通函数
MyAdd(a, c1); // 调用普通函数
MyAdd(c1, c2); // 调用普通函数
return 0;
}
模板函数原理如下图:
3.类模板的介绍
在函数模板中,模板函数可以根据传入函数的参数自动推导类型。
在类模板中,类模板无法自动推导类型,必须显式指定类型。
#include <iostream>
using namespace std;
template<class T>
class Person {
public:
Person(T id,T age){
this->mAge = age;
this->mId = id;
}
void Show(){
cout << "ID:" << mId << " Age:" << mAge << endl; 、
}
public:
T mId;
T mAge;
};
int main(){
//函数模板在调用的时候,可以自动类型推导
//类模板必须显式指定类型
Person<int> p(10,20);
p.Show();
return 0;
}
4.函数模板案例之对int类型和char类型数组排序
这里是对1、2、3部分的知识总结,不再赘述,代码如下:
#include <iostream>
using namespace std;
//对char类型和Int类型数组进行排序
//打印函数
template<class T>
void PrintArray(T* arr,int len){
for (int i = 0; i < len;i++){
cout << arr[i] << " ";
}
cout << endl;
}
//排序
template<class T>
void MySort(T* arr,int len){
for (int i = 0; i < len;i ++){
for (int j = i + 1; j < len;j++){
//从大到小排序
if (arr[i] < arr[j]){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
int main(){
//数组
int arr[] = {2,6,1,8,9,2};
//数组长度
int len = sizeof(arr) / sizeof(int);
cout << "排序之前:" << endl;
PrintArray(arr,len);
//排序
MySort(arr,len);
//排序之后
cout << "排序之后:" << endl;
PrintArray(arr, len);
cout << "--------------------" << endl;
char chArr[] = {'a','c','b','p','t'};
len = sizeof(chArr) / sizeof(char);
PrintArray(chArr, len);
MySort(chArr, len);
PrintArray(chArr, len);
reurn 0;
}
5.类模板派生普通类
派生子类时需要对模板父类显式指定类型
template<class T>
class Person{
public:
Person(){
mAge = 0;
}
public:
T mAge;
};
// 显式指明模板父类的类型是因为对象编译时需要分配内存
class SubPerson : public Person<int>{};
6.类模板派生类模板
具体用法借鉴下方代码
template <class T>
class Animal{
public:
void description{
cout << mAge << "岁的动物" << endl;
}
public:
T mAge;
};
template <class T>
class Cat : public Animal<T>{};
7.类的写法_头文件(untitle.h)与源文件(untitle.cpp)的分离
// Person.h
#progrma once //防止头文件被重复包含
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
Person(string name, int age);
void Show();
public:
string mName;
int mAge;
int mID;
};
//Person.cpp
#include "Person.h"
Person::Person(string name, int age){
this->mName = name;
this->mAge = age;
}
void Person::Show(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
//main.cpp
#include <iostream>
using namespace std;
#include "Person.h"
int main(void){
Person p("AAA",20);
p.Show();
return 0;
}
8.类模板实现在类内实现
仿照下列代码编写即可:
#include <iostream>
#include<string>
using namespace std;
//类内实现
template<class T1,class T2>
class Person{
public:
Person(T1 name,T2 age){
this->mName = name;
this->mAge = age;
}
void Show(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
public:
T1 mName;
T2 mAge;
};
int main(void){
Person<string, int> p("AAA", 20);
p.Show();
return 0;
}
9.类模板类外实现
说明一:类内友元函数可以访问类的私有成员。
说明二:类内慎用友元函数!!!
具体代码写法如下:
//类内运算符重载友元函数的写法一
template<class T>
class Person{
public:
//...
/*
如果不添加<T>,那么friend ostream& operator <<(ostream& os, Person<T>& p) 与
template<class T> ostream& operator<<(ostream& os, Person<T>& p)不是同一个函数,
前者是后者的一个具体实现
*/
friend ostream& operator << <T>(ostream& os, Person<T>& p); //注意此处的<T>
private:
//...
};
//重载左移运算操作符
template<class T>
ostream& operator<<(ostream& os, Person<T>& p){
os << "Age:" << p.mAge << " ID:" << p.mID << endl;
return os;
}
//最后可直接 cout << p;
//类内运算符重载友元函数的写法二
template<class T>
class Person{
public:
//...
// 这种写法无法在linux通过!!!
template<class T>
friend ostream& operator<<(ostream& os, Person<T>& p); //注意这里没有<T>
private:
//...
};
// 重载左移运算操作符的类外实现和调用同上,不再赘述
//普通友元函数的写法一:类外函数声明
template<class T> class Person;
template<class T> void PrintPerson(Person<T>& p);
template<class T>
class Person{
public:
//...
friend void PrintPerson<T>(Person<T>& p); //注意此处的 <T>
private:
//...
};
template<class T>
void PrintPerson(Person<T>& p){
cout << "Age:" << p.mAge << " ID:" << p.mID << endl;
}
//最后直接调用PrintPerson(p);
//普通友元函数的写法二:
template<class T>
class Person{
public:
//...
//linux无法通过
template<class T>
friend void PrintPerson(Person<T>& p); //注意此处没有<T>
private:
//...
};
//友元函数的实现同上
//函数的调用同上
//原完整代码如下:
#include <iostream>
using namespace std;
template<class T> class Person;
template<class T> void PrintPerson(Person<T>& p);
template<class T>
class Person{
public:
//普通友元函数
//template<class T>
friend void PrintPerson<T>(Person<T>& p);
//重载左移操作符
template<class T>
friend ostream& operator<<(ostream& os, Person<T>& p);
// 或写成
// friend ostream& operator << <T>(ostream& os, Person<T>& p);
Person(T age, T id);
void Show();
private:
T mAge;
T mID;
};
template<class T>
Person<T>::Person(T age, T id){
this->mID = id;
this->mAge = age;
}
template<class T>
void Person<T>::Show(){
cout << "Age:" << mAge << " ID:" << mID << endl;
}
//重载左移运算操作符
template<class T>
ostream& operator<<(ostream& os, Person<T>& p){
os << "Age:" << p.mAge << " ID:" << p.mID << endl;
return os;
}
template<class T>
void PrintPerson(Person<T>& p){
cout << "Age:" << p.mAge << " ID:" << p.mID << endl;
}
int main(void){
Person<int> p(10,20);
//p.Show();
//cout << p;
PrintPerson(p);
return 0;
}
10.类模板源文件(.cpp)与头文件(.h)分开编写
类模板源文件与头文件的分开编写以下方代码(会报错)示例:
//Person.h
#pragma once
#include<iostream>
using namespace std;
template<class T>
class Person{
public:
Person(T age);
void Show();
public:
T age;
};
//Person.cpp
#include "Person.h"
template<class T>
Person<T>::Person(T age){
this->age = age;
}
template<class T>
void Person<T>::Show(){
cout << "Age:" << age << endl;
}
//main.cpp
#include <iostream>
using namespace std;
#include "Person.h"
// 解决方法一:要想编译通过,此处将#include "Person.h"改为 #include "Person.cpp"即可
// 解决方法二:见下方的建议
int main(void){
/*构造函数定义在当前文件没有找到,编译认为这个函数在其他文件中定义
让链接器在链接的时候 去找这个函数的具体位置*/
Person<int> p(10);
p.Show();
return 0;
}
建议:编写模板类代码时,类模板的声明和实现写在一个文件中,且后缀名改成.hpp。
在上面的例子中,Person.h和Person.cpp写在一个文件之中,并取名Person.hpp。最后在main.cpp中包含Person.hpp即可。
说明:hpp文件其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件。
分析报错原因:代码报错的原因和模板的实现机制、C++编译机制有关。
C++编译机制的解释:假设在一个Cpp项目中,包含了三个文件:a.cpp、b.cpp、c.cpp。编译器对三个文件进行独立编译(比如编译a.cpp文件时,只编译a文件的内容,查看a是否有错,而不链接b、c文件的内容),如果在a文件的编译过程中,发现一个函数调用且该函数在a文件中无法找到,则在该函数位置生成一个符号。
模板实现机制:模板代码会经过两次编译。第一次编译检查模板代码的语法是否出错;第二次编译是在模板代码被调用时生成具体的代码(比如生成具体的int类型函数)。
在上面的例子中,main.cpp只包含了Person.h,而头文件中的类模板声明并不编译。在独立编译main.cpp的过程中,执行到Person p(10); p.Show();,编译器认为该函数在其他文件中实现,于是会在此处生成一个符号,编译通过。在独立编译Person.cpp的过程中,编译器对模板函数进行编译(通俗地说:对模板函数语法进行检查,并未生成具体类型的函数),编译通过。
在链接器对Person.cpp和main.cpp进行链接时,由于main.cpp没有调用Person.cpp,所以模板函数没有经过第二次编译生成具体的函数(比如int类型构造函数)。main.cpp使用的还是类型不明确的模板函数,最后出现未知符号的报错。
为了解决这个问题,只需要让main.cpp调用模板函数,生成具体类型的函数,所以引入Person.cpp(而非
Person.h)就可以解决问题。
11.类模板中的static成员
下方是测试代码:
#include <iostream>
using namespace std;
template<class T>
class Person{
public:
static int a;
};
template<class T> int Person<T>::a = 0; //类外初始化
int main(void){
Person<int> p1, p2, p3;
Person<char> pp1, pp2, pp3;
p1.a = 10;
pp1.a = 100;
cout << p1.a << " " << p2.a << " " << p3.a << endl;
cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;
return 0;
}
对普通类而言,类的静态成员是这个类所共有的。这里仅说明模板类的静态成员是属于整个模板类还是对于不同数据类型的类有各自的静态成员? 这里用图说明:
显然,类模板的静态成员针对不同的具体类而言的。从上面的测试代码可以知道:生成不同的具体类时,可以更改静态成员的值。