一、函数模板:
1.什么是函数模板?
把类型当做未知量(未知类型,可以是任何类型),可以忽略类型影响
例:求最大值函数(传入的参数可string可以是int)->体会模板的基本写法
template<typename _Ty1>//这里的typename可以写多个
_Ty1 Max(_Ty1 one, _Ty1 two)
{
return one > two ? one : two;
}
void test1()
{
cout << "较大的整数是" << Max(6, 9) << endl;
cout << "较大的string是" << Max("python", "C++") << endl;
}
输出:
较大的整数是9
较大的string是python
注: ①.当然关键字typename在现在是可以等效于class的(写的更加方便)
②.实际上template和函数名这两行是一行,为了美观和可读性,笔者在此写作两行。
2.调用函数模板的两种方式:
①隐式调用:当做正常函数的调用即可。
②显式调用:
函数名<类型名>(参数)
如上述Max函数用的就是隐式调用,下面用的是显式调用。
cout << "较大的整数是" << Max<int>(6, 9) << endl;
cout << "较大的string是" << Max<const char*>("python", "C++") << endl;
3.函数模板的2种形态
①普通函数当做函数模板。(如:上面的Max函数)
②类的成员函数也可以写成函数模板(例(下)类中声明类外实现)
class wbm
{
public:
wbm(int age):age(age){}
template<class _Ty1,class _Ty2>
void print(_Ty1,_Ty2);
protected:
int age;
};
template<class _Ty1, class _Ty2>
void wbm::print(_Ty1 a, _Ty2 b)
{
cout << a << " " << b << endl;
}
void test2()
{
wbm jie(1);
jie.print("杰杰子",18);
}
输出:
杰杰子 18
注:函数模板的缺省,显示调用可以不用传类型,但是参数个数是不可以缺少的。
template<class _Ty1,class _Ty2=int>
void print(_Ty1 a, _Ty2 b)
{
cout << a << " " << b << endl;
}
print<const char*>("杰杰子",18);//这边只需要写一个基本类型,第二个不写默认是int类型。
4、特殊写法:
①缺省写法(见上)
②存在常量写法。
template<class _Ty,size_t size=5>//size_t是unsigned long long类型
void printArray(_Ty array)
{
for (int i = 0; i < size; i++)//在模板里有了size这个变量,下面函数直接用
{
cout << array[i] << "\t";
}
}
void testarr()
{
int arr[5] = { 1,2,5,3,66 };
printArray<int*>(arr);//做了缺省
cout << endl;
printArray<int*, 5>(arr);//等效
cout << endl;
printArray(arr);//隐式调用
}
输出:(注意:size_t是unsigned long long 的别称)
1 2 5 3 66
1 2 5 3 66
1 2 5 3 66
注:1.不能传入变量,只能传入常量,函数模板如果存在变量的情况下
2.size=5做了缺省,可以隐式调用仅传一个参数arr;若不做缺省,必须显式调用,(来初始化size在尖括号中<int *, 11>)
二、类模板
1.如何生成一个类模板?
template<class _Ty>class wbm{};
(注:用没用到_Ty只要template修饰就是一个类模板)
2.调用:
①必显示调用。
②类名不是一个实际类型=>所有用到类名的地方都需要使用如下的形式去用:
类名<未知类型>
实例1:
template<class _Ty>
class jie
{
public:
jie(_Ty a):num(a){}
void print();
protected:
_Ty num;
};
template <class _Ty>
void jie<_Ty>::print()
{
cout << num << endl;
}
template<class _Ty>
class wbm :public jie<_Ty>
{
public:
wbm(int age):jie<_Ty>(age){}
protected:
int age;
};
void test1()
{
jie<int> zi(2);//用到了模板类,都需要写<类型>
zi.print();
wbm<double> bao(20);
bao.print();
}
注意:1.子类继承父类的时候,需要前置template <class _Ty> 此后再写尖括号的时候就无需再写class
2.类模板的类中声明,类外实现的函数也需要前置template <class _Ty> 此后再写尖括号的时候就无需再写class (class 一般只在template的时候加上
3.public 父类<> 用到父类这个模板类均需要把尖括号带着。包括子类和父类在创建对象的时候,均不能忘记<>尖括号。以及在子类的构造函数中调用父类的构造函数的时候,都需要加上尖括号<>
实例2:
//案例2
template <class _Ty1,class _Ty2>
class Date
{
public:
Date(_Ty1 a,_Ty2 b):a(a),b(b){}
void print();
protected:
_Ty1 a;
_Ty2 b;
};
template <class _Ty1, class _Ty2>//类外实现print函数
void Date<_Ty1,_Ty2>::print()
{
cout << a << "\t" << b << endl;
}
void test2()
{
Date<const char*,int> name_age("guozijie", 18);
Date<int, int > score(100, 100);
name_age.print();
cout << "高数" << "\t" << "线代" << endl;
score.print();
}
输出:
guozijie 18
高数 线代
100 100
注意:多文件中,类模板 中的声明和实现一定在一起的,不能分开写
三、自定义类型当做模板的参数
1.基本的自定义类型当做参数:
案例1:模板函数print的参数传入一个对象并打印出其中的数据。
//案例一:模板函数print的参数传入一个对象并打印出其中的数据。
class wbm
{
public:
wbm(string name, int age):name(name),age(age){}
friend ostream& operator<<(ostream& out, wbm& b)
{
out << b.name << "\t" << b.age;
return out;
}
protected:
string name;
int age;
};
template<class _Ty>
void print(_Ty a)
{
cout << a << endl;
}
void testprint()
{
print(wbm("二狗", 19));
print(666);
}
案例2: //案例2:写一个B类,比较B1,B2中成员数据较大的那个
class B
{
public:
B(int age):age(age){}
operator int()
{
return age;
}
protected:
int age;
};
template<class ty1,class ty2>
void testComAge(ty1 a,ty2 c)
{
if (a > c)
{
cout << "前者年龄较大" << endl;
}
else
{
cout << "后者年龄较大" << endl;
}
}
2.当自定义类型也是一个模板
案例:写一个链表,实现各种数据类型的存储。
class bm
{
public:
bm(string name,int age):name(name),age(age){}
friend ostream& operator<<(ostream& out, bm& bb)
{
out << bb.name << "\t" << bb.age << endl;
return out;
}
protected:
string name;
int age;
};
template<class _ty>
class Node
{
public:
Node(_ty data, Node<_ty>* next):data(data),next(next){}
_ty& getdata()
{
return data;
}
Node<_ty>* getnext()
{
return next;
}
protected:
_ty data;
Node<_ty>* next;
};
template<class _ty>
class List
{
public:
List()
{
headNode = nullptr;//创建一个空链表
}
void insertNode(_ty data)
{
headNode = new Node<_ty>(data, headNode);//前插法,only one sentence
}//别忘记Node<> 尖括号写到这容易忘。
void printList()
{
Node<_ty>*pMove = headNode;//定位到开头准备从头开始遍历
while (pMove!=nullptr)
{
cout << pMove->getdata();//pMove也是Node*类型
pMove = pMove->getnext();
}//此处还要注意一下访问权限问题,需要调用公有接口!!!
}
protected:
Node<_ty> *headNode;//注意:都是Node*<>类型,缺一不可
};
void testList()
{
List<double> list;
list.insertNode(283.2);
List<bm> blist;
blist.insertNode(bm("guo", 12));
blist.insertNode(bm("zi", 15));
blist.insertNode(bm("jie", 18));
blist.printList();
}
注意:尖括号不能忘,是Node*类型还是Node类型要分清楚,访问权限要给一个接口。
优化:利用using语法,可以这样写
using Lptype=Node<_ty>;
那么下面的Node* headNode等都可以简化为:
Lptype * headNode;
思考 :
//error C2679: 二进制“<<”: 没有找到接受“_Ty”类型的右操作数的运算符(或没有可接受的转换)
原因: 忘记了加上引用符号。去给data去取别名再调用。!!!!!
四、模板嵌套
关键:明白类型是什么。
案例一:函数模板参数用模板类,分别隐式调用、显式调用
案例二:一个Data类模板调用另一个类的n个对象作为数据成员并打印。
//案例一:函数模板参数用模板类,再写一个print函数分别隐式调用和显式调用
template<class _ty1,class _ty2>
class wbm
{
public:
wbm(_ty1 one,_ty2 two):one(one),two(two){}
friend ostream& operator<<(ostream& out, wbm& bb)
{
out << bb.one << "\t" <<bb.two<<endl;
return out;
}
protected:
_ty1 one;
_ty2 two;
};
template<class _ty,class _ty2>
class Data
{
public:
Data(_ty a,_ty2 b):a(a),b(b) {}
void printdata()
{
cout << a << " " << b;
}
protected:
_ty a;
_ty2 b;
};
void testFunc()
{
Data<wbm<string, int>, wbm<int, int>>
data(wbm<string, int>("杰杰子", 18), wbm<int, int>(100, 100));
data.printdata();
}
template<class _ty>
void print(_ty one)
{
cout << one;
}
int main()
{
testFunc();
//隐式调用print
print(wbm<string, int>("jie", 181));
//显式调用print
print<wbm<string, int>>(wbm<string, int>("jie", 182));
//using语法简化代码
using wbmty1 = wbm<string, int>;
print<wbmty1>(wbmty1("jie", 183));
return 0;
}
五、模板重载(函数)
函数名相同的普通函数,模板函数的重载(显式必调用模板)
参数类型与普通函数一致,则优先调用普通函数(注:字符串类型优先推导为const char* 类型)
当两个模板同时成立是,优先调用类型相似度高的那个
#include <iostream>
using namespace std;
void print(int a, string b)
{
cout << "普通函数" << endl;
}
template <class _Ty1, class _Ty2>
void print(_Ty1 a, _Ty2 b)
{
cout << "两个类型" << endl;
}
template <class _Ty>
void print(_Ty a, _Ty b)
{
cout << "一个类型" << endl;
}
int main()
{
print<int, string>(12, "显示调用百分调用模板");
print(12, string("优先调用适应的普通函数"));
//两个模板同时成立,优先调用类型相似度高的那个
print(12, 12); //
return 0;
}
输出:
两个类型
普通函数
一个类型
六、类模板的特化:
1.局部特化:特殊化,重写产生的类,类名要用: 类名<类型> 方式使用。
(对于相同的2个基本数据类型,有特殊情况\不兼容的时候,需要这样写)
例案:A类和Data类,A两个参数均为Data类,最好调用print()函数,而不是cout<<(需要重载)
//两未知类型
template <class _Ty1,class _Ty2>
class MM
{
public:
MM(_Ty1 one, _Ty2 two) :one(one), two(two) {}
void print()
{
cout << one << " " << two << endl;
}
protected:
_Ty1 one;
_Ty2 two;
};
class Data
{
public:
Data(int a, int b) :a(a), b(b) {}
void print()
{
cout << a << " " << b << endl;
}
protected:
int a;
int b;
};
//局部特化,特殊化
template <class _Ty>
class MM<_Ty,_Ty> //特化产生类,类名要用: 类名<类型> 方式使用
{
public:
MM(_Ty one, _Ty two) :one(one), two(two) {}
void print()
{
//cout << one << " " << two << endl;
one.print();
two.print();
cout << "特殊化" << endl;
}
protected:
_Ty one;
_Ty two;
};
2.完全特化:template<> class wbm(string,string){ };已经没有未知类型了
//完全特化
template <>
class MM<string, string>
{
public:
MM(string one, string two) :one(one), two(two) {}
void print()
{
cout << "完全特化" << endl;
cout << one << " " << two << endl;
}
protected:
string one;
string two;
};
int main()
{
//原生模板
MM<string, int> mm1("小芳", 18);
mm1.print();
//局部特化模板
MM<Data, Data> dMM(Data(1,2),Data(3,4));
dMM.print();
//完全特化的模板
//折叠参数 后续讲元组容器的时候讲
MM<string, string> mm2("小丽", "小美");
mm2.print();
tuple<string, string> tp("张三", "李四");
return 0;
}