1、函数模板
模板就是建立通用的模具,提高程序复用性。C++提供两种模板机制:函数模板和类模板
1.1 函数模板语法
作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。
模板是为了提高复用性,将数据类型参数化。
- template template --生命创建模板, --typename 表明后面的符号一种数据类型 --T 通用的数据类型,名称可以替换,通常为大写字母
- 函数声明或定义
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T &a, T &b) {
T t = a;
a = b;
b = t;
}
int main() {
int a = 2;
int b = 3;
float c = 1.3;
float d = 2.1;
// 两种方式使用函数模板
// 自动类型推导;显示指定
mySwap(a, b);
mySwap<float>(c, d);
cout << c << d;
return 1;
}
1.2 函数模板注意事项
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
template<class T>
void func() {
cout << "hello";
}
int main() {
func(); //错误,没有指定函数模板类型
func<int>();
return 1;
}
利用函数模板对数组进行排序
template<class T>
void myswap(T& a, T& b) {
T t;
t = a;
a = b;
b = t;
}
template<typename T> //typename可以替换为class,没有任何区别
void mySort(T arr[], int len) { //数组,长度
int max;
for (int i = 0; i < len - 1; i++) {
max = i;
for (int j = i+1; j < len; j++) {
if (arr[max] < arr[j]) {
max = j;
}
}
if (max != i) {
myswap(arr[i], arr[max]);
}
}
}
template<class T>
void printArr(T arr[],int len) {
for (int i = 0; i < len; i++) {
cout << arr[i]<<" ";
}
}
int main() {
int arr[4] = {3,1,5,2};
char arr_char[] = "eqfa";
mySort(arr_char, sizeof(arr_char)/sizeof(char)); //数组名,数组长度
mySort(arr, sizeof(arr_char) / sizeof(char));
printArr(arr,4);
return 1;
}
1.3 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数会发生隐式类型转换
int myAdd(int a, int b) {
return a + b;
}
//函数模板不会主动隐式类型转换
template<class T>
T myAdd01(T a,T b) {
return a + b;
}
int main() {
cout << myAdd01<int>('a', 'c'); // 不加<int> 不会将字符型转换为整型
cout << myAdd01(2, 4);
return 1;
}
1.4 普通函数与函数模板调用的规则
- 1、如果函数模板和普通函数都可以实现,优先调用普通函数
- 2、可以通过空模板参数列表的方式强制调用函数模板
- 3、函数模板也可以重载
- 4、如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a, int b) {
cout << "调用的普通函数" << endl;
}
template<class T> //函数模板的重载
void myPrint(T a, T b, T c) {
cout << "调用的模板" << endl;
}
int main() {
myPrint(2, 3); //调用普通函数
//myPrint<>(2, 3);
myPrint(5, 3, 1); //通过空模板参数列表调用普通函数
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2, 'x'); // 根据类型推断,优先调用函数模板
return 1;
}
1.5 模板的局限性
模板的通用性并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。
void myPrint(int a, int b) {
cout << "调用的普通函数" << endl;
}
class Person {
public:
int age;
string name;
Person(int age,string name) {
this->age = age;
this->name = name;
}
};
template<class T>
bool myCompare(T &a,T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
//利用具体化Person的版本实现代码,具体化优先调用
template<>bool myCompare(Person& p1, Person& p2)
{
if (p1.name == p2.name && p1.age == p2.age) {
return true;
}
else {
return false;
}
}
int main() {
Person p1(43,"曹操");
Person p2(32,"曹植");
cout<<myCompare(p1,p2);
return 1;
}
1.6 类模板
作用:建立一个通用类,类中的成员 数据类型可以不具体指定,用一个虚拟的类型来代表
- template
- 类
template<class NameType,class AgeType>
class Person {
public:
NameType name;
AgeType age;
Person(NameType name,AgeType age) {
this->age = age;
this->name = name;
}
};
int main() {
Person<string,int> p1("曹操",32); //分别给类型传值和构造函数传值
cout << p1.name<<" "<<p1.age;
return 1;
}
1.7 类模板和函数模板的区别
- 1、类模板没有自动类型推导的使用方式
- 2、类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType=int> //指定默认参数类型
class Person {
public:
NameType name;
AgeType age;
Person(NameType name,AgeType age) {
this->age = age;
this->name = name;
}
void showPerson() {
cout << name <<" "<< age;
}
};
int main() {
Person<string,int> p1("曹操",32); //分别给类型传值和构造函数传值
//Person p("曹冲",13); // 无法进行自动类型推导
Person<string,int> p("曹冲",13); //只能用显示指定类型
Person<string> p("曹冲",13); //默认参数类型
return 1;
}
1.8 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
1.9 类模板对象做函数参数
- 1、指定传入的类型 —直接显示对象的数据类型
- 2、参数模板化 —将对象中的参数变为模板进行传递
- 3、整个类模板化 —将这个对象类型模板化进行传递
template<class T1,class T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name,T2 age) {
this->age = age;
this->name = name;
}
void showPerson(){
cout << name << " " << age;
}
};
void printPerson1(Person<string,int>&p) { //指定传入类型
p.showPerson();
}
template<class T1,class T2>
void printPerson2(Person<T1, T2>&p) { //参数模板化
p.showPerson();
cout << typeid(T1).name()<<endl;
cout << typeid(T2).name();
}
template<class T>
void printPerson3(T &p) {//整个类模板化
p.showPerson();
cout << typeid(T).name() << end;
}
void test01() {
Person<string, int>p("曹冲", 13);
printPerson1(p);
}
void test02() {
Person<string, int>p("曹冲", 13);
printPerson2(p);
}
void test03() {
Person<string, int>p("曹植", 34);
printPerson3(p);
}
1.10 类模板与继承
- 当子类继承的父类是一个类模板时,子类在声明的时候,需要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中T的类型,子类也需创建为类模板
template<class T1,class T2>
class Base {
};
template<class T3,class T2,class T1>
class Son :public Base<T1,T2>{
public:
T3 obj;
Son() {
cout << typeid(T1).name() << endl;
cout << typeid(T3).name() << endl;
cout << typeid(T2).name() << endl;
}
};
int main() {
Son<char, int, string>s;
return 1;
}
1.11 类模板成员函数的类外实现
template<class T1,class T2>
class Person {
public:
T1 name;
T2 age;
Person(T1 name,T2 age);
void showPerson();
/*person(t1 name,t2 age) {
this->age = age;
this->name = name;
}
void showperson() {
cout << age << " " << name;
}*/
};
template<class T1, class T2>
void Person<T1,T2>::showPerson() {
cout << age << " " << name;
}
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age) {
this->age = age;
this->name = name;
}
1.12 类模板分文件编写
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
- 直接包含cpp源文件
- 将.h和.cpp内容写在一起,将后缀名改为.hpp文件
1.13 类模板与友元
全局函数类内实现–直接在类内添加友元声明
全局函数类外实现–需要声明模板类
template<class T1,class T2>
class Person;
template<class T1, class T2>
void printPerson1(const Person<T1, T2>& p) {
cout << p.name << " " << p.age;
}
template<class T1,class T2>
class Person {
//全局函数类内实现
friend void printPerson(const Person<T1, T2>& p) {
cout << p.name << " " << p.age;
}
//全局函数类外实现
friend void printPerson1<>(const Person<T1, T2>& p);
public:
Person(T1 age,T2 name) {
this->age = age;
this->name = name;
}
private:
T1 age;
T2 name;
};
1.14 案例
案例描述:实现一个通用的数组类,需求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据传入数组的容量
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
//Main.cpp
#include <iostream>
#include "MyArray.hpp"
using namespace std;
class Person {
public:
Person() {};
Person(string name, int age) {
this->age = age;
this->name = name;
}
string name;
int age;
};
void printIntArray(MyArray <int>&arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << arr[i] << endl;
}
}
void test01() {
MyArray<int>arr1(5);
for (int i = 0; i < 5; i++) {
arr1.push_back(i);
}
cout << "array的打印输出为:";
printIntArray(arr1);
arr1.pop_back();
arr1.pop_back();
cout << arr1.getSize();
cout << arr1.getCapacity();
cout << arr1[4];
}
void printPersonArray(MyArray<Person>&arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << arr[i].name << " " << arr[i].age << endl;
}
//cout << arr.getCapacity() << endl;
}
void test02() {
Person p1("曹冲",13);
Person p2("曹植",23);
Person p3("曹丕",43);
MyArray<Person>arr(3);
arr.push_back(p1);
arr.push_back(p2);
arr.push_back(p3);
printPersonArray(arr);
}
int main() {
test02();
return 1;
}
//MyArray.hpp
#pragma once
#include<iostream>
using namespace std;
template<class T>
class MyArray {
public:
MyArray(int capacity) {
this->capacity = capacity;
this->size = 0;
this->pAddress = new T[this->capacity];
}
MyArray(const MyArray & arr) {
this->capacity = arr.capacity;
this->size = arr.size;
this->pAddress = new T[arr.capacity]; //深拷贝
for (int i = 0; i < this->size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
MyArray& operator=(const MyArray& arr) {
//先判断原来堆区是否有数据,如果有先释放
if (this->pAddress != NULL) {
delete [] this->pAddress;
this->pAddress = NULL;
this->size = 0;
}
//深拷贝
this->capacity = arr.capacity;
this->size = arr.size;
this->pAddress = new T[arr, capacity];
for (int i = 0; i < this->size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
void push_back(const T& t) {
if (this->capacity == this->size) {
return;
}
else {
this->pAddress[this->size] = t;
this->size++;
}
}
void pop_back() {
if (this->size==0) {
return;
}
else {
this->size--;
}
}
T& operator[](int index) {
return this->pAddress[index];
}
int getCapacity() {
return this->capacity;
}
int getSize() {
return this->size;
}
~MyArray() {
cout << "析构函数";
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress; //指针指向堆区开辟的真实数组
int capacity;
int size;
};