C++泛型编程:用模板技术实现【参数类型化】
函数模板的基本使用方法
template<class T> 或 template<typename T> 告诉编译器紧跟的代码里出现T不要报错
例子:交换两个数字【如果按照常规的写法,那一种数据类型就要写一个函数,太麻烦】
void mySwapInt(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
void mySwapDouble(double &a, double &b)
{
double tmp = a;
a = b;
b = tmp;
}
void test01()
{
int a = 10;
int b = 20;
mySwapInt(a, b);
double c = 3.14;
double d = 1.1;
mySwapDouble(c, d);
}
解决方法:使用类型参数化,也就是泛型编程——模板技术。
template<class T>
void mySwap(T &a, T&b)
{
T tmp = a;
a = b;
b = tmp;
}
void test01()
{
int a = 10;
int b = 20;
char c1 = 'c';
mySwap(a, b); //1、自动类型转换,必须有参数类型才可以推导
mySwap<int>(a, b); //2、显示指定类型
//mySwap(a, c1); 推导不出来T, 所以不能运行
double c = 3.14;
double d = 1.1;
mySwap(c, d);
mySwap<double>(a, b);
}
写一个数组排序的例子
//使用函数模板实现对char和int数组进行排序,利用选择排序进行从大到小的排序
template<class T>
void mySwap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
template<class T>
void mySort(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
int max = i;
for (int j = i + 1; j < len; j++)
{
if (arr[max] < arr[j])
{
max = j;
}
}
if (max != i)
{
mySwap(arr[max], arr[i]);
}
}
}
template<class T>
void printArray(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void test01()
{
char charArr[] = "helloworld";
int len = strlen(charArr); // ==sizeof(charArr)/size(char)
mySort(charArr, len);
printArray(charArr, len);
int intArr[] = { 1, 4, 0, 100 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}
函数模板与普通函数的区别
C++编译器优先考虑普通函数
//1、编译器优先考虑普通函数
//函数模板:不可以进行隐式类型转换
template<class T>
T myPlus(T a, T b)
{
return a + b;
}
//普通函数: 可以进行隐式类型转换
int myPlus2(int a, int b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
myPlus(a, b);
myPlus2(a, b);
char c = 'c';
//myPlus(a, c); //类型推导不出来
myPlus2(a, c); //可以,输出的结果是109 = 10 + 99
}
可以通过空模板实参列表的语法限定编译器只能通过模板匹配
如果想强制调用模板,那么可以使用空参数列表myPrint<>(m,n);
//2、可以通过空模板实参列表的语法限定编译器只能通过模板匹配
//函数模板
template<class T>
void myPrint(T a, T b)
{
cout << "模板函数调用的myPrint" << endl;
}
template<class T>
void myPrint(T a, T b, T c)
{
cout << "abc模板函数调用的myPrint" << endl;
}
//普通函数
void myPrint(int a, int b)
{
cout << "普通函数调用的myPrint" << endl;
}
void test01()
{
int m = 10;
int n = 20;
myPrint(m, n); //如果出现重载,优先使用普通函数调用,如果普通函数没有实现,就会出现错误
//如果想强制调用模板,那么可以使用空参数列表
myPrint<>(m, n);
}
函数模板可以像普通函数那样可以被重载
//3、函数模板可以发生重载
template<class T>
void myPrint(T a, T b)
{
cout << "模板函数调用的myPrint" << endl;
}
template<class T>
void myPrint(T a, T b, T c) //函数模板可以发生重载
{
cout << "abc模板函数调用的myPrint" << endl;
}
void test01()
{
int m = 10;
int n = 20;
int k = 40;
myPrint(m, n, k);
}
如果函数模板可以产生一个更好的匹配,那么选择模板
//4、如果函数模板可以产生更好的匹配,那么优先调用函数模板
//函数模板
template<class T>
void myPrint(T a, T b)
{
cout << "模板函数调用的myPrint" << endl;
}
//普通函数
void myPrint(int a, int b)
{
cout << "普通函数调用的myPrint" << endl;
}
void test01()
{
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2); //此时调用函数模板
}
模板机制
编译器并不是把函数模板处理成能够处理任何类型的函数:模板并不是万能的,不能通用所有的数据类型
函数模板通过具体类型产生不同的函数:通过模板生成的函数,叫模板函数
//函数模板
template<class T>
void myPrint(T a, T b, T c)
{
cout << "abc模板函数调用的myPrint" << endl;
}
//模板函数
void myPrint(int a, int b, int c)
{
cout << "abc模板函数调用的myPrint" << endl;
}
编译器对函数模板进行两次编译,第一次对模板进行编译,第二次对替换T类型后的代码进行编译
模板的局限性及解决
模板的局限性:模板不能解决所有的类型
如果出现了不能解决的类型,可以通过第三代具体化来解决问题:template<>返回值 函数名<具体类型>(参数)
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
template<class T>
bool myCompare(T &a, T &b)
{
if (a == b)
{
return true;
}
return false;
}
//第三代具体化
template<>bool myCompare<Person>(Person &a, Person &b)
{
if (a.m_Name == b.m_Name && a.m_Age == b.m_Age)
{
return true;
}
return false;
}
void test01()
{
int a = 10;
int b = 20;
int ret = myCompare(a, b);
cout << "ret = " << ret << endl;
Person p1("Tom", 10);
Person p2("Jerry", 20);
int ret = myCompare(p1, p2); //如果只用普通的模板会发生错误,需要进行第三代具体化
}
类模板的基本使用
语法:template<class NameType, class AgeType> 紧跟着类
与函数模板的区别,可以有默认类型参数 template<class NameType, class AgeType = int>
函数模板可以进行自动类型推到,而类模板不行
//template<class NameType, class AgeType = int> // 类模板可以有默认的参数
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "姓名:" << this->m_Name << endl;
cout << "年龄:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
//自动类型推到, 类模板,不支持
//Person p1("张三", 100);
//只能用显示指定类型
Person<string, int> p("张三", 100);
p.showPerson();
}
类模板 的成员函数 一开始不会创建出来,而是在运行时才创建
class Person1
{
public:
void showPerson1()
{
cout << "Person1的调用" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2的调用" << endl;
}
};
template<class T>
class MyClass
{
public:
T obj;
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
//类模板 的成员函数 一开始不会创建出来,而是在运行时才创建
void test01()
{
MyClass<Person1> m;
m.func1();
//m.func2(); //Person2不是Person1的成员
}
类模板做函数的参数
指定传入类型
//1、指定传入类型
void doWork(Person<string, int> &p)
{
p.showPerson();
}
参数模板化
template<class T1, class T2>
void doWork2(Person<T1, T2> &p)
{
//如何查看类型
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
p.showPerson();
}
整体模板化:对Person这个对象进行了整体模板化
template<class T>
void doWork3(T &p)
{
cout << typeid(T).name() << endl;
p.showPerson();
}
类模板遇到继承的问题
基类如果是模板类,必须让子类告诉编译器,基类中的T到底是什类型class Child :public Base<int>。如果不告诉,那么无法分配内存,编译不过
template<class T>
class Base
{
public:
T m_A;
};
//child继承于base必须告诉base中的T的类型,否则T无法分配内存
class Child :public Base<int>
{
};
template<class T1, class T2>
class Child2 :public Base<T2>
{
public:
Child2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
T1 m_B;
};
void test01()
{
//由用户指定类型
Child2<int, double> child; //m_A是double类型,m_B是int类型
}
类模板的类外实现成员函数
template<class T1,class T2>
class Person
{
public:
//类内实现
/*Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}*/
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
//类外实现成员函数
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << this->m_Name << "的年龄是" << this->m_Age << endl;
}
类模板的分文件编写问题及解决
由于 类模板的成员函数只有在运行阶段才去创建,导致包含.h头文件,不会创建函数的实现,无法解析外部命令。
建议类模板不要做分文件编写,一般写到一个文件.h中,并把文件的后缀名改成.hpp
友元函数碰到类模板
友元函数类内实现
template<class T1,class T2>
class Person
{
//友元函数类内实现 默认全局函数
friend void printPerson(Person<T1, T2> &p)
{
cout << p.m_Name << "的年龄是" << p.m_Age << endl;
}
public:
//类内实现
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int> p("张三", 10);
printPerson(p);
}
友元函数类外实现
//让编译器提前看到Person类声明
template<class T1, class T2> class Person;
//让编译器提前看到printPerson声明
template<class T1, class T2> void printPerson(Person<T1, T2> &p);
template<class T1, class T2>
class Person
{
//友元函数类内实现 默认全局函数
//friend void printPerson(Person<T1, T2> &p); 普通函数的声明
friend void printPerson<>(Person<T1, T2> &p); //利用空参数列表 告诉编译器 模板函数的声明
public:
//类内实现
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
//友元函数类外实现
template<class T1, class T2>
void printPerson(Person<T1, T2> &p)
{
cout << p.m_Name << "的年龄是" << p.m_Age << endl;
}
void test01()
{
Person<string, int> p("张三", 10);
printPerson(p);
}
类模板的封装——数组类的构造
#pragma once
#include <iostream>
using namespace std;
template<class T>
class MyArray
{
public:
explicit MyArray(int capacity) //防止隐式类型转换 防止MyArray arr=10的写法
{
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
MyArray(const MyArray & array)
{
this->m_Capacity = array.m_Capacity;
this->m_Size = array.m_Size;
this->pAddress = new T[array.m_Capacity];
for (int i = 0; i < m_Size; i++)
{
this->pAddress[i] = array[i];
}
}
~MyArray()
{
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
}
MyArray& operator=(MyArray & array)
{
//先判断原始数据,有就清空
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
this->m_Capacity = array.m_Capacity;
this->m_Size = array.m_Size;
this->pAddress = new T[array.m_Capacity];
for (int i = 0; i < m_Size; i++)
{
this->pAddress[i] = array[i];
}
}
T& operator[](int index)
{
return this->pAddress[index];
}
void pushBack(T val) //值传递
{
this->pAddress[this->m_Size] = val;
this->m_Size++;
}
int getSize()
{
return this->m_Size;
}
int getCapacity()
{
return this->getCapacity;
}
private:
T *pAddress; //指向堆区指针
int m_Capacity;
int m_Size;
};
#include <iostream>
#include <string>
#include "MyArray.hpp"
using namespace std;
void printIntArray(MyArray<int>& array)
{
cout << "输出数组" << endl;
for (int i = 0; i < array.getSize(); i++)
{
cout << array[i] << " ";
}
cout << endl;
}
class Person
{
public:
Person()
{
}
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void printPersonArray(MyArray<Person>& array)
{
for (int i = 0; i < array.getSize(); i++)
{
cout << "姓名:" << array[i].m_Name << "年龄:" << array[i].m_Age << endl;
}
}
void test01()
{
MyArray<int> arr(10);
for (int i = 0; i < 10; i++)
{
arr.pushBack(i+100);
}
printIntArray(arr);
Person p1("Tom", 10);
Person p2("Herry", 11);
Person p3("Jack", 12);
Person p4("Hebe", 13);
Person p5("Tim", 14);
MyArray<Person> arr2(5);
arr2.pushBack(p1);
arr2.pushBack(p2);
arr2.pushBack(p3);
arr2.pushBack(p4);
arr2.pushBack(p5);
printPersonArray(arr2);
}