C++模板
写C/C++代码的时候总是会存在重复造轮子的情况,C++就比较好一点,函数有重载的特性,可以减少一些代码量,而C语言就真的要自己一个个写出来。C++还有一个强大的工具,就是模板(template)。
模板可以减少大量的代码量。C++模板有分有函数模板和类模板。
函数模板
没有模板之前,写同一个实现的功能,需要写重载函数,有了函数模板之后,就可以把类型当作未知量去使用,从而减少重载带来的代码量。
函数模板的写法:
template <typename 未知类型的名字> 函数返回值 函数名(形参列表)
{
···
函数体
···
}
注意的是template <typename 未知类型的名字>和函数返回值 函数名是写在同一行的,但是一般为了更清楚的看函数就把这两个分成两行来写。
在template的尖括号中的typename可以使用class代替,表示的是一个意思,即以下两行代码表示是同一个意思
template<typename T>
template<class T>
简单使用一下函数模板:
#include<iostream>
using namespace std;
int Max(int a, int b)
{
return a > b ? a : b;
}
string Max(string str1, string str2)
{
return str1 > str2 ? str1 : str2;
}
double Max(double d1, double d2)
{
return d1 > d2 ? d1 : d2;
}
int main()
{
cout << Max(2, 5) << endl;
cout << Max(3.14159, 5.57) << endl;
cout << Max("ILoveyou", "IMissyou") << endl;
return 0;
}
5
5.57
IMissyou
代码比较多,而且这三个Max的重载函数实现的功能都是一样的,只是换了参数而已。
#include<iostream>
using namespace std;
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
cout << Max(2, 5) << endl;
cout << Max(3.14159, 5.57) << endl;
cout << Max("ILoveyou", "IMissyou") << endl;
return 0;
}
5
5.57
ILoveyou
细心的同学就可以发现前面的比较数字是没什么问题,但是比较字符串的时候就出了点分歧。函数重载的方式说IMissyou比较大,而函数模板的方式说ILoveyou比较大。这个是因为下面函数模板比较字符串的时候把ILoveyou和IMissyou理解常const char*类型了。我们的字符串比较的认知中,无论是按单个字符的ASCII比还是整体的ASCII比都是IMissyou的比较大。const char*是不能用 ‘>’ 等运算符直接比较的,所以在上面得出结果是ILoveyou比较大是因为ILoveyou和IMissyou比较的是它们自己的地址,不是字符串本身。如果把
cout<<Max("ILoveyou","IMissyou")<<endl;
改成
cout<<Max(string("ILoveyou"),string("IMissyou"))<<endl;
这样就是正常的比较字符串本身了,string的字符串比较和const char*的字符串比较是一样的都是按字符的ASCII比,只是说string可以直接用 '>'等运算符直接比,而const char*不能。
类与类之间使用函数模板的比较
重点就是类里面的运算符重载的问题,因为类在没有写运算符重载的时候是不能之间使用 '>'等运算符之间比较的。没有写运算符重载类和类之间的比较也不知道比什么。所以当函数模板存在类类型作为参数并且要比较它们的时候,就必须写类的运算符重载。
#include<iostream>
using namespace std;
class MM
{
public:
MM(string name,int faceScore):name(name),faceScore(faceScore){}
void print()
{
cout << name << '\t' << faceScore << endl;
}
protected:
string name;
int faceScore;
};
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
MM mm1("Alice", 100);
MM mm2("Coco", 80);
MM result = Max(mm1, mm2);
result.print();
return 0;
}
运行肯定是不会通过的,并且会报下面的错误: C2676 二进制“>”:“T”不定义该运算符或到预定义运算符可接收的类型的转换
原因就是没有写运算符重载,类和类之间没有比较的依据。
写了运算符重载后:
#include<iostream>
using namespace std;
class MM
{
friend bool operator>(const MM& mm1, const MM& mm2);
public:
MM(string name,int faceScore):name(name),faceScore(faceScore){}
void print()
{
cout << name << '\t' << faceScore << endl;
}
protected:
string name;
int faceScore;
};
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
MM mm1("Alice", 100);
MM mm2("Coco", 80);
MM result = Max(mm1, mm2);
result.print();
return 0;
}
bool operator>(const MM& mm1, const MM& mm2)
{
return mm1.faceScore > mm2.faceScore ? mm1.faceScore : mm2.faceScore;
}
程序可以正常运行。
输出结果:
Alice 100
显式调用与隐式调用
函数模板的显示调用很简单,就是在调用的时候加一对尖括号,尖括号里面写显式调用的类型;隐式调用则什么都不用写,程序运行调用函数的时候会自己推断类型,然后生成对应类型的函数。
直接看代码:
#include<iostream>
using namespace std;
class MM
{
friend bool operator>(const MM& mm1, const MM& mm2);
public:
MM(string name,int faceScore):name(name),faceScore(faceScore){}
void print()
{
cout << name << '\t' << faceScore << endl;
}
protected:
string name;
int faceScore;
};
template <typename T>
T Max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
MM mm1("Alice", 100);
MM mm2("Coco", 80);
MM result = Max(mm1, mm2);
result.print();
MM result2=Max<MM>(mm1,mm2);
result2.print();
return 0;
}
bool operator>(const MM& mm1, const MM& mm2)
{
return mm1.faceScore > mm2.faceScore ? mm1.faceScore : mm2.faceScore;
}
同时存在函数模板和普通函数时,隐式调用则优先调用已有的普通函数,然后才考虑调用模板函数。如果使用了显示调用的话就只会调用函数模板。
#include<iostream>
using namespace std;
template<typename T>
T Max(T a, T b)
{
cout << "函数模板" << endl;
return a > b ? a : b;
}
int Max(int a, int b)
{
cout << "普通函数" << endl;
return a > b ? a : b;
}
int main()
{
cout << Max(55, 46) << endl;
cout << Max<int>(3, 78) << endl;
return 0;
}
普通函数
55
函数模板
78
我们把普通函数注释掉的时候再运行程序会得到下面结果:
函数模板
55
函数模板
78
我们再把函数模板注释掉,然后留下普通函数,再运行程序会得到下面结果:
然后就会报下面错误: E0299 无法确定需要哪个 函数模板 “std::endl” 实例
综上所述,隐式调用的时候,优先调用普通函数,如果没有对应类型的普通函数才会调用函数模板。而显式调用则是必须存在函数模板才可以正确调用。
存在多个未知类型的函数模板调用情况
函数模板是可以存在多个未知类型的
template <class T1,class T2>
.....
调用情况:
- 如果实参是同一个类型的时候优先调用同类型的未知类型
- 如果实参不是同一个类型的时候,就对应调用合适的函数模板
直接看代码:
#include<iostream>
using namespace std;
template <class T1>
void print(T1 a, T1 b)
{
cout << "T1+T1" << endl;
cout << a << '\t' << b << endl;
}
template <class T1,class T2>
void print(T1 a, T2 b)
{
cout << "T1+T2" << endl;
cout << a << '\t' << b << endl;
}
int main()
{
print(5, 6);
print("Hello", 99);
print(5.9, 5);
print('a', "Yes");
print('h', 't');
return 0;
}
T1+T1
5 6
T1+T2
Hello 99
T1+T2
5.9 5
T1+T2
a Yes
T1+T1
h t
函数模板的注意点:
- 函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码。即用到模板的时候才会生成对应的函数。
- 被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能向普通函数一样只将声明放在头文件中。即模板的实现和头文件写在一起;.hpp文件而不是.h文件。
- 函数指针也只能指向模板的实例,而不能指向模板本身。即模板只是模型,本身不存在。在使用模板的时候才会生成对应的函数。
类模板
使用类模板使用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数、返回值或局部变量能取不同类型(包括系统预定义的和用户自定义的)。这个是《清华大学出版社C++语言程序设计》的原话。简单的理解类模板就是,类中存在未知类型的数据那就是类模板。
类模板的基本概念
类模板:
- 类中用到未知类型
- 必须显式调用
- 所有用到类名的地方都要用:类名<未知类型> 的方式使用
- 类模板只有实例化的时候才生成对应的类,本身不是类,而是一个模板
#include<iostream>
using namespace std;
template <class T>
class MM
{
public:
void print();
void printData()
{
cout << "类中实现没有用到类名" << endl;
}
};
template <class T>
void MM<T>::print()
{
cout << "类外实现" << endl;
}
int main()
{
MM<int> mm;
mm.print();
mm.printData();
return 0;
}
从代码中可以看到的是,除了一开始写MM类的时候,MM不用加尖括号,而后面的每出现一次就使用尖括号的方式写代码,然后创建对象的时候,也是使用了尖括号(显式的使用)。
再看下面的例子,可以看到结构体类型作为实参传入的话要特殊写一下,然后在构建对象的时候结构体类型也是按结构体初始化的方式做。
#include<iostream>
using namespace std;
template <class T1,class T2>
class MM
{
public:
MM(T1 data1,T2 data2):data1(data1),data2(data2){}
void print();
void printData();
protected:
T1 data1;
T2 data2;
};
struct Score
{
int faceScore;
int math;
};
int main()
{
MM<string, int> mm("Alice", 18);
mm.print();
MM<string, Score> mm2("Coco", { 99,78 });
mm2.printData();
return 0;
}
template<class T1, class T2>
void MM<T1, T2>::print()
{
cout << data1 << '\t' << data2 << endl;
}
template<class T1, class T2>
void MM<T1, T2>::printData()
{
cout << data1 << '\t' << data2.math << '\t' << data2.faceScore << endl;
}
类模板的特化
原来声明的模板类中有多个未知类型,然后特化的作用就是,把这几个类型给它特殊化了,特殊成少一点的未知类型。
举个简单的例子,原来一个模板类中有三个未知数据类型,然后特殊化后可以,特殊化成这三个未知的数据类型变成同一个类型的未知数据类型。
直接看代码:
#include<iostream>
using namespace std;
template <class T1,class T2,class T3>
class MM
{
public:
MM(T1 Data1,T2 Data2,T3 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
{
cout << "类模板" << endl;
}
void printData();
protected:
T1 Data1;
T2 Data2;
T3 Data3;
};
template<class T1, class T2, class T3>
void MM<T1, T2, T3>::printData()
{
cout << Data1 << '\t' << Data2 << '\t' << Data3 << endl;
}
template <class T>
class MM<T, T, T>
{
public:
MM(T Data1,T Data2,T Data3):Data1(Data1),Data2(Data2),Data3(Data3)
{
cout << "类模板的特化,3->1" << endl;
}
protected:
T Data1;
T Data2;
T Data3;
};
int main()
{
MM<string, int, int> mm("Alice", 18, 1001);
MM<int, int, int> mm2(5, 2, 0);
return 0;
}
类模板
类模板的特化,3->1
就是很简单,本来是有三个互不相同的数据类型,经过特化后变成三个相同 的类型,如果显式传参的是int那三个未知数据类型都变成int如果是显式传参是string那三个未知数据类型都变成string
当然也是可以是两个未知数据类型相同,然后第三个未知数据类型与前面两个不同,也可以是后面两个相同,前面那个不同,当然也可以有4个未知数据类型,然后再在这4个未知数据类型里面进行特化操作。
#include<iostream>
using namespace std;
template <class T1,class T2,class T3>
class MM
{
public:
MM(T1 Data1,T2 Data2,T3 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
{
cout << "类模板" << endl;
}
void printData();
protected:
T1 Data1;
T2 Data2;
T3 Data3;
};
template<class T1, class T2, class T3>
void MM<T1, T2, T3>::printData()
{
cout << Data1 << '\t' << Data2 << '\t' << Data3 << endl;
}
template <class T>
class MM<T, T, T>
{
public:
MM(T Data1,T Data2,T Data3):Data1(Data1),Data2(Data2),Data3(Data3)
{
cout << "类模板的特化,3->1" << endl;
}
protected:
T Data1;
T Data2;
T Data3;
};
template <class T1,class T2>
class MM<T1, T2, T2>
{
public:
MM(T1 Data1,T2 Data2,T2 Data3):Data1(Data1),Data2(Data2),Data3(Data3)
{
cout << "类模板的特化,3->2+1" << endl;
}
protected:
T1 Data1;
T2 Data2;
T2 Data3;
};
int main()
{
MM<string, int, int> mm("Alice", 18, 1001);
MM<int, int, int> mm2(5, 2, 0);
MM<string, string, string> mm3("于文文", "张靓颖", "宋茜");
MM<int, double, const char*> mm4(8, 32.2, "Hello");
return 0;
}
类模板的特化,3->2+1
类模板的特化,3->1
类模板的特化,3->1
类模板
注意点:类模板不是真正的类,所以不可以使用多文件写法写类模板,只能把声明和实现写在同一个文件中。创建的时候写.hpp中。
#pragma once
template<class T>
class Test
{
public:
Test(T Data):Data(Data){}
void Fun();
void Fun2();
protected:
T Data;
};
#include"test.h"
#include<iostream>
using namespace std;
template<class T>
inline void Test<T>::Fun()
{
cout << "Fun" << endl;
}
template<class T>
inline void Test<T>::Fun2()
{
cout << "Fun2" << endl;
}
在主函数的源文件中进行测试,然后就会报下面的错误:
原因就是把类模板的头文件和源文件分开写了。只要把头文件和源文件都写在一起就可以了。
#pragma once
#include<iostream>
using namespace std;
template<class T>
class Test
{
public:
Test(T Data):Data(Data){}
void Fun();
void Fun2();
protected:
T Data;
};
template<class T>
inline void Test<T>::Fun()
{
cout << "Fun" << endl;
}
template<class T>
inline void Test<T>::Fun2()
{
cout << "Fun2" << endl;
}
使用以上的代码(头文件和源文件写在一起就不会错误)。
模板类的嵌套
嵌套,意思就是,一个东西里面又有另外一个东西。而这里的模板类的嵌套就是在一个模板类中又嵌套了另一个模板类。
直接看代码观察效果:
#include<iostream>
using namespace std;
template <class T>
class MM
{
friend ostream& operator<<(ostream& out, MM<T> mm)
{
out << mm.Data;
return out;
}
public:
MM(T Data) :Data(Data) {}
void print()
{
cout << Data << endl;
}
protected:
T Data;
};
template <class T1, class T2>
class GG
{
public:
GG(MM<T1> Data1, T2 Data2) :Data1(Data1), Data2(Data2) {}
void print()
{
cout << Data1 << '\t' << Data2 << endl;
}
protected:
MM<T1> Data1;
T2 Data2;
};
int main()
{
GG<MM<string>, int> gg(MM <string>("Cukor"), 20);
gg.print();
return 0;
}
Cukor 20
上面的代码中的一些问题:
friend ostream& operator<<(ostream& out, MM<T> mm);
template <class T>
ostream& operator<<(ostream& out, MM<T> mm)
{
out << mm.Data;
return out;
}
会报下面的错误:
GG<MM<string>, int> gg("Cukor", 20);
会出现下面的错误:
|