一,泛型编程
泛型即泛泛,泛即泛指,是一种基于模板机制的一种编程方式。
传统的编程思想中,把软件设计建立在三维空间:数据类型,容器,算法的基础上,针对不同的数据类型,不同的容器对同一的算法设计不同的代码,但这样会生成大量源码,而且代码重用性较低。
而泛型思想把算法与容器、数据类型相分离,使同一算法适用于不同的容器和数据类型,成为通用性算法,这样可以最大限度地节省源代码,实现代码的重用。
二,函数模板
定义:建立一个通用的函数,函数的返回值类型和形参类型不具体指定,而是用一个虚拟的1类型来代表,这个类型叫做泛型。这样可以提高复用性,将类型参数化。
1.定义语法
通过函数前添加一行模板声明,来声明一个函数模板,有两种声明格式:
template<typename T>
template<class T>,此处的T即泛型
2.注意事项
1)由于是自动类型推导,必须推导出一致的数据类型才能使用,比如传进去一个int ,一个char,就不一致,以交换两个数的函数为例:swap函数里面传入的参数类型必须一致,
template<typename T>
void mySwap(T& a, T& b) {
//这里加 & 用引用传递,就是为了高效又正确地实
// 现两个变量的交换,让函数能真正影响到外部的实参 。
T temp = a;
a = b;
b = temp;
}
void test01() {
int n1 = 10;
char n2 = 'A';
mySwap(n1,n2);//会报错
2)函数的模板必须确定T的类型,如果不能自动推导,就需要手动指定,仍以mySwap(函数)为例:
void test01(){
int n3 = 5;
int n4 = 8;
mySwap<int>(n3, n4);//相当于指定T为int类型。
cout << "交换后n3=" << n3 << " n4=" << n4 << endl;
}
3.与普通函数的区别
1)普通函数调用时可以发生隐式的自动类型转换
2)函数模板在调用时,如果是自动类型推导,不会发生隐式转换,显式指定类型也可以发生隐式类型的转换,如下,显式指定泛型为Int类型后,会发生隐式转换,n4转换为int。
void test01(){
int n3 = 5;
double n4 = 8;
mySwap<int>(n3, n4);//相当于指定T为int类型
cout << "交换后n3=" << n3 << " n4=" << n4 << endl;
}
关于隐式类型的转换,遵循一定的规则:
- 在算术运算中,低类型可以转换为高类型。
- 赋值表达式中,赋值运算符右边的类型会转为左边的类型。
- 函数调用时,实参会转换为形参的类型。
- 函数返回值时,返回表达式类型会转换为返回值类型。
- 自定义类型的转换是不支持隐式转换的,因为不安全。需要手动转换,包括了静态转换
4.调用规则
1)如果函数模板和普通函数都可以实现, 实现了重载,优先调用普通函数,逻辑是:具体的实现 优先于 通用的实现。
2)可以通过添加空的模板参数列表 <>来强制调用函数模板。
3)函数模也可以直接发生重载。
4)如果函数模板比普通函数可以产生更好的匹配效果,则会优先调用函数模板
案例:
void myPrint(int a, int b) { cout << "a=" << a << "b=" << b << "调用普通函数" << endl; }
template<typename T>
void myPrint(T a,T b) { cout << "a=" << a << "b=" << b << "调用函数模板" << endl; }
template<typename T>
void myPrint(T a, T b,T c) { cout << "a=" << a << "b=" << b << "c=" <<c<< "调用函数模板,三个参数" << endl; }
void test06()
{
//1)如果函数模板和普通函数都可以实现,实现了重载,优先调用普通函数。逻辑是:具体的实现高于通用的实现。
int a = 10, b = 20;
myPrint(a, b);
//2)可以通过添加空的模板参数列表<>来强制调用函数模板。
myPrint<>(a, b);
//3)函数模板之间也可以发生重载
//重载就是函数名相同,但参数类型和数量不同,这里是数量不同;
int c = 30;
myPrint(a, b, c);
//4)如果函数模板比普通函数可以产生更好的匹配效果,则会优先调用函数模板
//因为普通函数是int类,但函数模板是任意类型,更加适配。
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2);
}
5. 特定模板
template <typename T>
bool myCompare(T a, T b) {
cout << "调用函数模板myCompare(T a,T b)" << endl;
if (a == b) {
//==比较运算符,不支持自定义类型
return true;
}
else
{
return false;
}
}
//自定义一个学生类
class Student {
public:
string m_Name;
int m_Age;
//构造函数
Student(string name,int age): m_Name(name),m_Age(age){}
};
//添加一个特定模板来解决整个不兼容问题
template<> bool myCompare(Student s1, Student s2)
{
cout << "调用特殊模板myCompare(Student s1, Student s2)" << endl;
//比较对象类型就是比较,每个对象的属性的值是否相等
if (s1.m_Name == s2.m_Name and s1.m_Age == s2.m_Age) {
return true;
}
else {
return false;
}
}
void test07() {
Student s1("Tom", 12);
Student s2("Tom", 12);
cout << myCompare(s1, s2);
}
三,类模板
1.类模板的概念
(1) 类模板是比函数模板更加通用的一种方式;类模板就是建立一个更加通用的类,类中的成 员等用到的数据类型都是不仅具体指定,用泛型来表示
(2)与函数模板的区别:
在使用时,类模板没有自动类型推到的方式,只能是显式指定泛型的类型;(3)语法:
类前面加一行模板声明:
template<typename 泛型名称>或者template<class 泛型名称>
举例:
template<class NameType ,class AgeType=int>
class Person {
NameType m_Name;// 没有指定具体的类,泛型来表示
AgeType m_Age;
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
void show() { cout << "name: " << m_Name << ", age: " << m_Age << endl; }
void setvalue(NameType name, AgeType age) {
m_Name = name;
m_Age = age;
}
};
void test01() {
Person<string, int> p1("szl", 34);
Person<string, string> p2("saz", "二十岁");
p1.show();
p2.show();
p1.setvalue("tyz", 23);
p1.show();
}
2. 与函数模板的结合
根据结合的范围分为以下几种情况
1)指定类模板对象的泛型类型(便于理解,但是灵活度不高,传参的时候必须按照指定类型去传)
2)不指定类模板对象的泛型类型,而是通过函数模板的模板参数类别来指定(提高了灵活性,可以在使用这个函数模板的时候再进行指定)
3)将整个类当成一个泛型,使用函数模板的模板参数列表来指定(灵活性最高,可以传入任意类型的对象)
3. 类模板遇到继承

如果父类是类模板,会有几种变化:
1)父类是类模板,子类可以是普通类,也可以是类模板
2)父类和子类都是类模板,子类和父类的泛型可以不同,各自指定自己的泛型类型。但是子类在继承父类时需要指定父类的泛型类型。
子类的泛型类型可以在使用的时候再去指定。
3)父类和子类都是类模板,让子类和父类共同使用同一个泛型类型。
总之,可以根据实际需要,在继承一个类模板的时候,直接确定他的泛型类型,或者继续用一个泛型代替,在子类实例化的时候再确定。
练习:
使用模板实现一个模板数组类:TemplateArray,数组中可以存储任意类型的数据,类的属性有:
T* data;//数组的地址,数据存储在堆内存
int capacity;//数组的容量,即数组中最多可以容纳的元素个数
int size=0;//数组中实际元素的个数,初始值为零并提供以下几个类方法:
从尾部添加数据元素: void addBack(T value);
从尾部删除元素:void delBack();
打印数组: void printArray();
返回数组中元素的个数:int getSize();
返回数组的容量:int getCapacity();
返回指定下标位置的元素:T getValueByIndex(int index);
参考代码:
template<class T>
class TemplateArray
{
private:
T* data = NULL;
int capacity;
int size = 0;
public:
TemplateArray(int cap)
{
data = new T[cap];//在对空间申请了对应大小的内存
capacity = cap;
}
~TemplateArray()
{
if (data!=NULL)
{
delete[]data;
data = NULL;
}
}
void addBack(T value)
{
//判断数组是否已满,已满就不能添加了
if (capacity==size)
{
cout << "数组已满,无法添加元素" << endl;
}
else
{
data[size] = value;
size++;
}
}
void delBack()
{
//先判断有没有元素,有元素才能删除
if (size>0)
{
//删除元素其实就是让可访问的有效元素范围进行变化,从尾部删除就是将最后一个元素排除,那么修改size的值即可
size--;
}
}
void printArray()
{
for (size_t i = 0; i < size; i++)
{
cout << data[i] << ",";
}
cout << endl;
}
int getSize()
{
return size;
}
int getCapacity()
{
return capacity;
}
T getValueByIndex(int index)
{
return data[index];
}
};
void test04()
{
//整型数组
TemplateArray<int> tarr(6);
tarr.addBack(1);//添加元素
tarr.addBack(2);
tarr.addBack(3);
tarr.addBack(4);
tarr.addBack(5);
tarr.addBack(6);
tarr.addBack(7);//已满,无法添加元素
tarr.printArray();//打印元素
tarr.delBack();//从尾部删除元素
tarr.printArray();
cout << "容量:" << tarr.getCapacity() << ",元素个数:" << tarr.getSize() << ",下标为2的元素" << tarr.getValueByIndex(2) << endl;
//字符数组
TemplateArray<char> tarr_char(3);
tarr_char.addBack('a');
tarr_char.addBack('b');
tarr_char.addBack('c');
tarr_char.addBack('d');//已满,无法添加
tarr_char.printArray();
tarr_char.delBack();
tarr_char.printArray();
cout << "容量:" << tarr_char.getCapacity() << ",元素个数:" << tarr_char.getSize() << ",下标为0的元素" << tarr_char.getValueByIndex(0) << endl;
}