目录
一.函数模板
什么是模板?简单来说,就是一段任何类型都通用的代码,比如我们定义一个比较大小的函数,那这个函数可以适合整型数据,也可以适合浮点型,甚至可以适合string类型,这种把类型当作未知量,可以忽略类型影响的类或函数,我们称之为模版。先来了解函数模版。
1.定义和声明
如何声明和定义一个函数模版?下面以一个比较大小的函数为例:
template <typename _Ty>
_Ty Max(_Ty a, _Ty b)
{
return a > b ? a : b;
}
也可以同时定义多个未知类型:
template <typename _Ty1, typename _Ty2>
void print(_Ty1 a, _Ty2 b)
{
cout << a << " " << b;
}
如果觉得typename太陌生的话,也可以将它换成class,这也是可以的。
template <class C>
void printC(C c)
{
cout << c;
}
2.调用函数模版
调用函数模版有隐式调用和显式调用两种,先来看隐式调用:
template <typename _Ty>
_Ty Max(_Ty a, _Ty b)
{
return a > b ? a : b;
}
int main()
{
cout << Max(2, 6) << " " << Max(2.1, 2.2);
}
可以看到,隐式调用跟正常的函数传参没什么区别,它会自己识别你的类型,再来看看显式调用:
template <typename _Ty>
_Ty Max(_Ty a, _Ty b)
{
return a > b ? a : b;
}
int main()
{
cout << Max<int>(2, 6) << " " << Max<double>(2.1, 2.2);
}
可以看到,显示调用的格式是函数名<类型名>(参数)。同样,多个未知类型的调用也是如此:
template <typename _Ty1, typename _Ty2>
void print(_Ty1 a, _Ty2 b)
{
cout << a<< " " << b;
}
int main()
{
print(1, 1.2);
print<int, double>(1, 2.1);
}
3.函数模版在类中的应用
class A
{
public:
template <class _Ty1>
void print(_Ty1 a)
{
cout << a;
}
};
int main()
{
A a;
a.print<int>(2);
a.print<string>("小蓝");
}
其实函数模版在类中跟普通函数差别不大,差别比较大的是类中声明类外实现的方式,函数模版不能省略template语句。
class A
{
public:
template <class _Ty1>
void print(_Ty1 a);
};
template <class _Ty1>
void A::print(_Ty1 a)
{
cout << a;
}
普通函数有缺省的写法,同样的,函数模版也有缺省写法。
template <class _Ty1,class _Ty2 = int>
void print(_Ty1 a, _Ty2 b)
{
cout << a << " " << b;
}
int main()
{
print<string>("小蓝", 2);
}
函数模版存在传常量的写法:
template <class _Ty1,size_t size>
void printArr(_Ty1 arr)
{
for (int i = 0;i < size;i ++)
{
cout << arr[i] << " ";
}
}
int main()
{
int arr[3] = { 1,2,3 };
printArr<int*, 3>(arr);
}
其中的size_t是无符号整型的别称,这种写法只能显示调用,因为需要传入一个有关大小的常量,但是我们用缺省写法可以做到隐式调用:
template <class _Ty1,size_t size = 3>
void printArr(_Ty1 arr)
{
for (int i = 0;i < size;i ++)
{
cout << arr[i] << " ";
}
}
int main()
{
int arr[3] = { 1,2,3 };
printArr(arr);
}
4.函数模板重载
- 模板和普通函数 ,调用函数函数类型一致情况 优先调用普通函数
- 两个模板同时成立,优先调用类型相似度搞的那个
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.声明语法
template <class _Ty>
class A
{
};
2.类模板的声明,实现和调用
-
多文件中,类模板 中的声明和实现一定在一起的,不能分开写
- 必须采用显式调用
- 类模板不是一个实际类型,所以所有用到类名的地方都需要使用: 类名<未知类型> 方式使用
template <class _Ty>
class A
{
public:
A() {}
A(string name) :name(name) {}
void print(_Ty a); //类中声明在类外实现
protected:
string name;
};
template <class _Ty>
void A<_Ty>::print(_Ty a) //所有用到类名的地方:都需要用类名<类型>的方式使用
{
cout << a << endl;
}
int main()
{
A<int>a;
a.print(1);
A<string>b;
b.print("小蓝");
}
3.模板类继承
template <class _Ty>
class A
{
public:
A() {}
A(string name) :name(name) {}
void print(_Ty a); //类中声明在类外实现
protected:
string name;
};
template <class _Ty>
void A<_Ty>::print(_Ty a) //所有用到类名的地方:都需要用类名<类型>的方式使用
{
cout << a << endl;
}
template <class _Ty>
class B :public A<_Ty>
{
public:
B(string name):A<_Ty>(name){}
};
int main()
{
B<int> b("小蓝");
b.print(1);
}
总之,用到模板类的地方都要template修饰一下,继承也是一样。
4.自定义类型当模板参数
注意:模板传入自定义类型,关键在于重载运算符!
template <class _Ty>
void print(_Ty a)
{
cout << a;
}
class A
{
public:
A(string name,int age):name(name),age(age){}
friend ostream& operator<<(ostream& out, A& a)
{
out << a.name << " " << a.age;
return out;
}
protected:
string name;
int age;
};
int main()
{
print(A("小蓝", 18));
}
再来一个练练手:
template <class _Ty>
_Ty Max(_Ty a, _Ty b)
{
return a > b ? a : b;
}
class A
{
public:
A(string name,int age):name(name),age(age){}
friend ostream& operator<<(ostream& out, A& a)
{
out << a.name << " " << a.age;
return out;
}
friend bool operator>(A& a,A& b)
{
return a.age > b.age;
}
protected:
string name;
int age;
};
int main()
{
A a1("小蓝", 18);
A a2("小红", 19);
A a = Max(a1, a2);
cout << a;
}
之前设计链表的时候总是感觉底层数据太多难以封装,现在可以试着写一个链表来管理模板类型的数据。
class A
{
public:
A(string name,int age):name(name),age(age){}
friend ostream& operator<<(ostream& out,const A& a)
{
out << a.name << " " << a.age;
return out;
}
protected:
string name;
int age;
};
template <class _Ty>
class Node
{
public:
Node(){}
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> //因为用到了Node模板
class List
{
public:
List()
{
headNode = nullptr;
}
void insertData(_Ty data)
{
headNode = new Node<_Ty>(data, headNode);
}
void printList()
{
Node<_Ty>* pMove = headNode;
while (pMove)
{
cout << pMove->getData() << endl;
pMove = pMove->getNext();
}
}
~List()
{
Node<_Ty>* p = headNode, * q;
while (p)
{
q = p;
p = p->getNext();
delete q;
}
}
protected:
Node<_Ty>* headNode;
};
int main()
{
List<int> list;
list.insertData(1);
list.insertData(2);
list.printList();
List<A> list2;
list2.insertData(A("小蓝", 18));
list2.insertData(A("小宏", 19));
list2.printList();
}
需要注意的是:提供的接口是返回值,是一个const类型的对象(常属性) 需要把A的重载函数改为const A& a,返回值是值,不可以成为左值 ---> 返回值充当函数参数,需要加const修饰 ,加const是为了传入const 对象的时候这个重载函数也能用。
三.模板嵌套
1.函数模板嵌套
template <class _Ty>
void print(_Ty a)
{
cout << a;
}
template <class _Ty1,class _Ty2>
class A
{
public:
A(_Ty1 name,_Ty2 age):age(age),name(name){}
friend ostream& operator<<(ostream& out, const A& a)
{
out << a.name << " " << a.age << endl;
return out;
}
protected:
_Ty1 name;
_Ty2 age;
};
int main()
{
print<A<string, int>>(A<string, int>("小蓝",18));
print(A<string,int>("小红", 19));
//起别名简化代码
using AType = A<string,int>;
print<AType>(AType("小绿", 20));
}
无论外层是隐式调用还是显式调用,里层必须显式调用。
2.类模板嵌套
template <class _Ty1,class _Ty2>
class A
{
public:
A(_Ty1 name,_Ty2 age):age(age),name(name){}
friend ostream& operator<<(ostream& out, const A& a)
{
out << a.name << " " << a.age ;
return out;
}
protected:
_Ty1 name;
_Ty2 age;
};
template <class _Ty1,class _Ty2>
class Data
{
public:
Data(_Ty1 one,_Ty2 two):one(one),two(two){}
void print()
{
cout << one << " " << two << endl;
}
protected:
_Ty1 one;
_Ty2 two;
};
int main()
{
A<string, int> a("小蓝", 19);
A<double, double> b(11.1, 12.1);
Data<A<string, int>, A<double, double>> d(a, b);
d.print();
//上面四行等效于
Data<A<string, int>, A<double, double>> d2(A<string, int>("小蓝", 19), A<double, double>(11.1, 12.1));
d2.print();
}
其实嵌套就是每一层剥洋葱,没有什么特别的。
四.类模板特化
为啥要特化?因为有些数据是特殊形式的,特殊形式的数据在调用原模板时候可能不兼容,可以把它特殊化到一个单独的类中去实现,针对这种独立的数据类型去做处理
1.局部特化
template <class _Ty1, class _Ty2>
class A
{
public:
A(_Ty1 one, _Ty2 two) :one(one), two(two) {}
void print()
{
cout << one << " " << two << endl;
}
protected:
_Ty1 one;
_Ty2 two;
};
class Data
{
public:
Data(int one,int two):one(one),two(two){}
void print()
{
cout << one << " " << two << endl;
}
protected:
int one;
int two;
};
//局部特化,将两种未知类型特化为一种
template <class _Ty>
class A<_Ty,_Ty>
{
public:
A(_Ty one, _Ty two) :one(one), two(two) {}
void print()
{
one.print();
two.print();//可以传两个类的对象类型进来 调用各自类对象的方法去打印数据
cout << "局部特化" << endl;
}
protected:
_Ty one;
_Ty two;
};
int main()
{
//调用原模板
A<string, int> a("小蓝", 18);
a.print();
//调用局部特化模板
A<Data, Data> d(Data(1, 2), Data(3, 4));
d.print();
}
2.完全特化
局部特化升级版,传入已知类型。
template <class _Ty1, class _Ty2>
class A
{
public:
A(_Ty1 one, _Ty2 two) :one(one), two(two) {}
void print()
{
cout << one << " " << two << endl;
}
protected:
_Ty1 one;
_Ty2 two;
};
class Data
{
public:
Data(int one,int two):one(one),two(two){}
void print()
{
cout << one << " " << two << endl;
}
protected:
int one;
int two;
};
//完全特化,传入指定类型
template <>
class A<Data, Data>
{
public:
A(Data one, Data two) :one(one), two(two) {}
void print()
{
one.print();
two.print();//可以传两个类的对象类型进来 调用各自类对象的方法去打印数据
cout << "完全特化" << endl;
}
protected:
Data one;
Data two;
};
int main()
{
//调用完全特化模板
A<Data, Data> d(Data(1, 2), Data(3, 4));
d.print();
}