119.模板会写吗?写一个比较大小的模板函数
119.模板会写吗?写一个比较大小的模板函数
本程序适合char、int、float类型数据进行比较,差距小于0.01视为相等
#include <iostream>
#include <cmath>
using namespace std;
//本程序适合char、int、float类型数据进行比较,差距小于0.01视为相等
template<class T1, class T2>
int compare(T1 a, T2 b)
{
if (typeid(a) == typeid(float))
{
// 包含float的比较大小
if (fabs(a - b) < 0.01)
{
return 0;
}
else if (a - b > 0.01)
{
return 1;
}
else
{
return -1;
}
}
else
{
// 不包含float的比较大小
if (a > b)
{
return 1;
}
else if (a == b)
{
return 0;
}
else
{
return -1;
}
}
}
int main()
{
char a1 = 'a', b1 = 'b';
int a2 = 65, b2 = 3;
float a3 = 65.009, b3 = 65.01;
int com;
com = compare(a3, b3);
if (com == 1)
{
cout << "前者 > 后者" << endl;
}
else if (com == 0)
{
cout << "前者 == 后者" << endl;
}
else
{
cout << "前者 < 后者" << endl;
}
com = compare(a3, b3);
if (com == 1)
{
cout << "前者 > 后者" << endl;
}
else if (com == 0)
{
cout << "前者 == 后者" << endl;
}
else
{
cout << "前者 < 后者" << endl;
}
return 0;
}
C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
▷ C++提供两种模板机制:函数模板和类模板
▷ 类属 - 类型参数化,又称参数模板
总结:
■模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
■模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
1.函数模板
1.1什么是函数模版
函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板
1.2怎么编写函数模版
//T代表泛型的数据类型,不是只能写T,
template<class T>//让编译器看到这句话后面紧跟着的函数里有T不要报错
void mySwap(T &a,T &b)
{
T tmp = a;
a = b;
b = tmp;
}
//可以这样定义函数模版
template<typename T>
void func2(T a,T b)
{
}
1.3怎么使用函数模版
//T代表泛型的数据类型,不是只能写T,
template<class T>//让编译器看到这句话后面紧跟着的函数里有T不要报错
void mySwap(T &a,T &b)
{
T tmp = a;
a = b;
b = tmp;
}
template<class T>
void mySwap2()
{
}
//使用函数模版
void test02()
{
int a = 10;
int b = 20;
//1.编译器会根据实参来自动推导数据类型
mySwap(a,b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
char c = 'c';
//mySwap(a, c);err,数据类型要一致
//2.显示的指定类型
mySwap<int>(a, b);//<>用参数列表告诉编译器我只传int类
//mySwap<double>(a, b);//注意:指定类型,传入时不能不一致
mySwap<>(a,b);
//mySwap2<>();//err 调用时,必须让编译器知道泛型T具体是什么类型
}
1.4编译器会对函数模版和类模版进行二次编译
//T代表泛型的数据类型,不是只能写T,
template<class T>//让编译器看到这句话后面紧跟着的函数里有T不要报错
void mySwap(T &a,T &b)//第一次编译
{
T tmp = a;
a= b;
b = tmp;
}
//使用函数模版
void test02()
{
int a = 10;
int b = 20;
//1.编译器会根据实参来自动推导数据类型
mySwap(a,b);//编译器在函数模版被调用时,进行第二次编译
/*
void mySwap(int &a,int &b)
{
int tmp = a;
a= b;
b = tmp;
}
*/
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
1.5隐式转换
template<class T>
T func(T a, T b)
{
return a + b;
}
void test03()
{
int a = 10;
double b = 20.2;
//如果使用参数列表指定数据类型,那么实参中可以隐式转换
//如果转换成功,就调用,转换不成功就报错
cout << func<int>(10,20.2) << endl;
}
1.6函数模板和普通函数的区别
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//普通函数
int myPlus(int a, int b)
{
return a + b;
}
template<class T>
int myPlus2(T a, T b)
{
return a + b;
}
void test()
{
int a = 10;
int b = 20;
char c = 'a';
//普通函数可以进行隐式转换
myPlus(a, c);
//函数模版不能直接的进行隐式转换
//myPlus2(a, c);
myPlus2<int>(a, c);//如果要进行隐性转换,必须加上参数列表
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
1.7普通函数和函数模版的调用规则
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//普通函数
void myPlus(int a, int b)
{
cout << "普通函数" << endl;
}
template<class T>
void myPlus(T a, T b)
{
cout << "函数模版" << endl;
}
template<class T>
void myPlus(T a, T b, T c)
{
cout << "函数模版 T c" << endl;
}
//1.函数模版和普通函数可以重载
void test()
{
int a = 10;
int b = 20;
//2.如果普通函数和函数模版都可以实现的功能,普通函数优先调用
myPlus(a, b);
//3.可以使用<>空参数列表强制调用函数模版
myPlus<>(a, b);
//4.函数模版之间也可以进行重载
//5.如果函数模版可以产生更好的匹配,那么优先使用函数模版
char c1 = 'a';
char c2 = 'b';
myPlus(c1, c2);
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
1.8函数模版的局限性和解决方法
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
template<class T>
void func(T a, T b)
{
if (a > b)
{
cout << "a>b" << endl;
}
else
{
cout << "a<=b" << endl;
}
}
void test()
{
int arr[20];
int arr2[10];
func(arr, arr2);
}
class Maker
{
public:
Maker(string name,int age)
{
this->age = age;
this->name = name;
}
public:
string name;
int age;
};
template<class T>
void myfunc(T &a, T &b)
{
if (a > b)
{
cout << "a>b" << endl;
}
else
{
cout << "a<=b" << endl;
}
}
//不建议具体化函数模版,因为没有通用性
//具体化函数模版,注意上面的函数模版要有,才能具体化
template<>void myfunc<Maker>(Maker &a, Maker &b)
{
cout << "函数模版的具体化" << endl;
if (a.age > b.age)
{
cout << "a>b" << endl;
}
else
{
cout << "a<=b" << endl;
}
}
void test02()
{
Maker m1("aaa", 10);
Maker m2("bbb", 20);
myfunc(m1, m2);
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
2.类模板
2.1类模板基本概念
函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板
●类模板用于实现类所需数据的类型参数化
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
void test01()
{
//Person P1("德玛西亚",18); // 类模板不能进行类型自动推导
Person<string, int>P1("德玛西亚", 18);
P1.showPerson();
}
2.2类模板做函数参数
//类模板
template<class NameType, class AgeType>
class Person{
public:
Person(NameType name, AgeType age){
this->mName = name;
this->mAge = age;
}
void PrintPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
//类模板做函数参数
void DoBussiness(Person<string,int>& p){
p.mAge += 20;
p.mName += "_vip";
p.PrintPerson();
}
int main(){
Person<string, int> p("John", 30);
DoBussiness(p);
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
//普通类继承类模版
template<class T>
class Father
{
public:
Father()
{
m = 20;
}
public:
T m;
};
//普通类 继承 类模版
class Son :public Father<int>//要告诉编译器父类的泛型数据类型具体是什么类型
{
public:
};
//类模版 继承 类模版
template<class T1,class T2>
class Son2 :public Father<T2>//要告诉编译器父类的泛型数据类型具体是什么类型
{
};
void test()
{
Son2<int,int> s;
cout << s.m << endl;
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
2.3类模板派生普通类
#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
//类模板
template<class T>
class MyClass {
public:
MyClass(T property) {
this->mProperty = property;
}
public:
T mProperty;
};
//子类实例化的时候需要具体化的父类,子类需要知道父类的具体类型是什么样的
//这样c++编译器才能知道给子类分配多少内存
//普通派生类
class SubClass : public MyClass<int> {
public:
SubClass(int b) : MyClass<int>(20) {
this->mB = b;
}
public:
int mB;
};
int main()
{
SubClass son(1);
cout << "son.mProperty:" << son.mProperty << "," << "son.mB:" << son.mB << endl;
system("pause");
return EXIT_SUCCESS;
}
输出:
son.mProperty:20,son.mB:1
请按任意键继续. . .
2.4模板派生类模板
//父类类模板
template<class T>
class Base
{
T m;
};
template<class T >
class Child2 : public Base<double> //继承类模板的时候,必须要确定基类的大小
{
public:
T mParam;
};
void test02()
{
Child2<int> d2;
}
2.5类模板类内实现
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->mName = name;
this->mAge = age;
}
void showPerson()
{
cout << "name: " << this->mName << " age: " << this->mAge << endl;
}
public:
NameType mName;
AgeType mAge;
};
void test01()
{
//Person P1("德玛西亚",18); // 类模板不能进行类型自动推导
Person<string, int>P1("德玛西亚", 18);
P1.showPerson();
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
template<class NameType, class AgeType>
class Maker
{
public:
Maker(NameType name, AgeType age);
/*{
this->name = name;
this->age = age;
}*/
void printMaker();
/*{
cout << "Name:" << this->name << " Age:" << this->age << endl;
}*/
public:
NameType name;
AgeType age;
};
//类模版的成员函数类外实现
//要写成函数模版
template<class NameType, class AgeType>
Maker<NameType, AgeType>::Maker(NameType name, AgeType age)
{
cout << "构造函数" << endl;
this->name = name;
this->age = age;
}
template<class NameType, class AgeType>
void Maker<NameType, AgeType>::printMaker()
{
cout << "Name:" << this->name << " Age:" << this->age << endl;
}
int main()
{
Maker<string, int> m("haha", 20);
m.printMaker();
system("pause");
return EXIT_SUCCESS;
}
2.6类模板类外实现
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Person{
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 mName;
T2 mAge;
};
//类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
void test()
{
Person<string, int> p("Obama", 20);
p.showPerson();
}
int main(){
test();
system("pause");
return EXIT_SUCCESS;
}
2.7类模板头文件和源文件分离问题
Person.hpp
#pragma once
template<class T1,class T2>
class Person{
public:
Person(T1 name,T2 age);
void ShowPerson();
public:
T1 mName;
T2 mAge;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
template<class T1, class T2>
void Person<T1, T2>::ShowPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
#include"Person.hpp"
//模板二次编译
//编译器编译源码 逐个编译单元编译的
int main(){
Person<string, int> p("Obama", 20);
p.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
结论: 案例代码在qt编译器顺利通过编译并执行,但是在Linux和vs编辑器下如果只包含头文件,那么会报错链接错误,需要包含cpp文件,但是如果类模板中有友元类,那么编译失败!
解决方案: 类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是个约定的规则,并不是标准,必须这么写).
原因:
●类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
●C++编译规则为独立编译
2.8模板类碰到友元函数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include <string>
template<class T1, class T2> class Person;
//告诉编译器这个函数模板是存在
template<class T1, class T2> void PrintPerson2(Person<T1, T2>& p);
//友元函数在类内实现
template<class T1, class T2>
class Person{
//1. 友元函数在类内实现
friend void PrintPerson(Person<T1, T2>& p){
cout << "Name:" << p.mName << " Age:" << p.mAge << endl;
}
//2.友元函数类外实现
//告诉编译器这个函数模板是存在
friend void PrintPerson2<>(Person<T1, T2>& p);
//3. 类模板碰到友元函数模板
template<class U1, class U2>
friend void PrintPerson(Person<U1, U2>& p);
public:
Person(T1 name, T2 age){
this->mName = name;
this->mAge = age;
}
void showPerson(){
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
private:
T1 mName;
T2 mAge;
};
void test01()
{
Person <string, int>p("Jerry", 20);
PrintPerson(p);
}
// 类模板碰到友元函数
//友元函数类外实现 加上<>空参数列表,告诉编译去匹配函数模板
template<class T1 , class T2>
void PrintPerson2(Person<T1, T2>& p)
{
cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl;
}
void test02()
{
Person <string, int>p("Jerry", 20);
PrintPerson2(p); //不写可以编译通过,写了之后,会找PrintPerson2的普通函数调用,因为写了普通函数PrintPerson2的声明
}
int main(){
//test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<string>
template<class NameType, class AgeType>
class Maker
{
friend void printMaker(Maker<NameType, AgeType> &p)
{
cout << "类内实现" << p.name << " " << p.age << endl;
}
public:
Maker(NameType name, AgeType age)
{
this->name = name;
this->age = age;
}
private:
NameType name;
AgeType age;
};
void test01()
{
Maker<string, int> m("悟空", 18);
printMaker(m);
}
template<class NameType, class AgeType>
class Maker2;
//告诉编译器下面有printMaker2的实现
template<class NameType, class AgeType>
void printMaker2(Maker2<NameType, AgeType> &p);
template<class NameType, class AgeType>
class Maker2
{
//1.在函数名和()之间加上<>。(使得可以去找函数模板)
friend void printMaker2<>(Maker2<NameType, AgeType> &p);
//2.编译器不知道printMaker2下面有没有实现,需要知道函数的结构
public:
Maker2(NameType name, AgeType age)
{
this->name = name;
this->age = age;
}
private:
NameType name;
AgeType age;
};
//友元在类外实现要写成函数模版
template<class NameType, class AgeType>
void printMaker2(Maker2<NameType, AgeType> &p)
{
cout << "类外实现的友元函数 " << p.name << " " << p.age << endl;
}
void test02()
{
Maker2<string, int> m("贝吉塔", 18);
printMaker2(m);
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
2.9类模板的应用
设计一个数组模板类(MyArray),完成对不同类型元素的管理
类模版实现数组.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include"MyArray.hpp"
#include<string>
class Maker
{
public:
Maker(){}
Maker(string name, int age)
{
this->name = name;
this->age = age;
}
public:
string name;
int age;
};
void printMaker(MyArray<Maker> &arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << "姓名:" << arr[i].name << " 年龄:" << arr[i].age << endl;
}
}
void test()
{
MyArray<Maker> myarr(4);
Maker m1("悟空", 18);
Maker m2("贝吉塔", 30);
Maker m3("短笛", 200);
Maker m4("小林", 19);
myarr.Push_Back(m1);
myarr.Push_Back(m2);
myarr.Push_Back(m3);
myarr.Push_Back(m4);
printMaker(myarr);
MyArray<int> myint(10);
for (int i = 0; i < 10; i++)
{
myint.Push_Back(i + 1);
}
for (int i = 0; i < 10; i++)
{
cout << myint[i] <<" ";
}
cout << endl;
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
MyArray.hpp
#pragma once
template<class T>
class MyArray
{
public:
MyArray(int capacity)
{
this->mCapacity = capacity;
this->mSize = 0;
//T如果是Maker,这里要调用什么构造函数,要调用无参构造
p = new T[this->mCapacity];
}
//拷贝构造
MyArray(const MyArray &arr)
{
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
p = new T[arr.mCapacity];
for (int i = 0; i < this->mSize; i++)
{
p[i] = arr.p[i];
}
}
//赋值函数
MyArray &operator=(const MyArray &arr)
{
if (this->p != NULL)
{
delete[] this->p;
this->p = NULL;
}
p = new T[arr.mCapacity];
this->mSize = arr.mSize;
this->mCapacity = arr.mCapacity;
for (int i = 0; i < this->mSize; i++)
{
p[i] = arr.p[i];
}
return *this;
}
//重载[]
T &operator[](int index)
{
return this->p[index];
}
//尾插
void Push_Back(const T &val)
{
if (this->mSize == this->mCapacity)
{
return;
}
this->p[this->mSize] = val;
this->mSize++;
}
//尾删
void Pop_Back()
{
if (this->mSize == 0)
{
return;
}
this->mSize--;
}
~MyArray()
{
if (this->p != NULL)
{
delete[] p;
p = NULL;
}
}
int getSize()
{
return this->mSize;
}
private:
T *p;
int mCapacity;
int mSize;
};
参考资料来源:
黑马程序员