目录
2、函数模板 当使用自动类型推导时 不可以发生隐式类型转换
模板并不是万能的 有些特定的数据类型 需要具体化方式做特殊实现 例如我们在对两个类进行对比时
结论:类模板中的成员函数并不是一开始就创建的 而是在调用的时候才去创建
1、当子类继承的父类是一个模板时 子类在声明的时候 要指定出父类中T的了类型
首先:让我们思考为什么要使用函数模板哪?
例如一个简单的交换函数,在大的程序中一般会比较常用,如果你不使用函数模版,便要声明多个不同类型的交换函数。而使用一个模版函数就可以解决问题了,既免了功夫,也减少了代码的字数。
函数模板的基本语法:
template<typename T>
其中typname可以替换为class
该声明的意义在于声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型。
例如我们通过函数模板实现数据的交换:
void MySwap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
void test()
{
int a = 10;
int b = 20;
//1、自动类型推导
MySwap(a, b);
cout << "a = " << a << endl;//20
cout << "b = " << b << endl;//10
//2、显示指定类型
MySwap<int>(a, b);
cout << "a = " << a << endl;//10
cout << "b = " << b << endl;//20
}
其中对于自动类型推导有以下注意事项:
a、自动类型推导 必须推导出一致的数据类型T才可以使用
b、模板必须要确定出T的数据类型 才可以使用
函数模板案例—数组排序
实现对通用数组的排序
规则 从大到小
算法 选择排序
测试 char int
#include<iostream>
using namespace std;
//利用函数模板实现对数据的交换
template<class T>
void MySwap(T&a, T&b)
{
T tmp = a;
a = b;
b = tmp;
}
template<class T>
void MySort(T Arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
int max = i;//假设最大值的下标为i
for (int j = i + 1; j < sz; j++)
{
if (Arr[max] < Arr[j])//结果发现 遍历后下表j的数值比i的数值大
{
max = j;//更新最大值的下标
}
}
if (max != i)
{
//交换max下标和i下标的元素
MySwap(Arr[max], Arr[i]);
}
}
}
//char数组测试
void test1()
{
char arr[] = "bcdage";
int sz = sizeof(arr) / sizeof(arr[0]);
MySort(arr, sz);
for (int i = 0; i < sz; i++)
{
cout << " " << arr[i];
}
}
//int数组测试
void test2()
{
int arr[] = { 1, 2, 5, 6, 3, 7 };
int sz = sizeof(arr) / sizeof(arr[0]);
MySort(arr, sz);
for (int i = 0; i < sz; i++)
{
cout << " " << arr[i];
}
}
int main()
{
test1();
test2();
return 0;
}
运行结果如下:
普通函数与函数模板的区别:
1、普通函数的调用可以发生隐式类型转换
//普通函数
int Add01(int a, int b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
char ch = 'a';
cout << "a+b=" << Add01(a, b) << endl;
cout << "a+ch=" << Add01(a, ch) << endl;
}
2、函数模板 当使用自动类型推导时 不可以发生隐式类型转换
//函数模板
template<class T>
T MyAdd(T a, T b)//这里不可以用引用的方式进行值传递
{
return a + b;
}
//1、自动类型推导
void test02()
{
int a = 10;
int b = 20;
char ch = 'a';
cout << "a+b=" << MyAdd(a, b) << endl;
cout << "a+ch=" << MyAdd(a, ch) << endl;//显示出现错误
}
3、函数模板 当使用显示指定类型时 可以发生隐式类型转换
void test03()
{
int a = 10;
int b = 20;
char c = 'c';
cout << "a+b=" << MyAdd<int>(a, b) << endl;
cout << "a+ch=" << MyAdd<int>(a, c) << endl;
}
普通函数与函数模板的调用规则:
1、如果函数模板和普通函数都可以实现 优先使用普通函数
void Print1(int a, int b)
{
cout << "普通函数的调用" << endl;
}
template<class T>
void Print1(T a, T b)
{
cout << "函数模板的调用"
}
void test01()
{
int a = 10;
int b = 20;
Print1(a, b);
}
2、可以通过空模板参数列表来强制调用函数模板
void Print2(int a, int b)
{
cout << "普通函数的调用" << endl;
}
template<class T>
void Print2(T a, T b)
{
cout << "函数模板的调用" << endl;
}
void test02()
{
int a = 10;
int b = 20;
Print2<>(a, b);
}
3、函数模板也可以发生重载
void Print3(int a, int b)
{
cout << "普通函数的调用" << endl;
}
template<class T>
void Print3(T a, T b,T c)
{
cout << "重载函数模板的调用" << endl;
}
void test03()
{
int a = 10;
int b = 20;
Print3(a, b,10);
}
4、如果函数模板可以更好的匹配 优先调用函数模板
void Print4(int a, int b)
{
cout << "普通函数的调用" << endl;
}
template<class T>
void Print4(T a, T b)
{
cout << "函数模板的调用" << endl;
}
void test04()
{
char a = 'a';
char b = 'b';
Print4(a, b);
模板的局限性
模板并不是万能的 有些特定的数据类型 需要具体化方式做特殊实现 例如我们在对两个类进行对比时
对比两个数据是否相等
1、对比两个整数
template <class T>
bool MyCompare(T&a, T&b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
void test01()
{
int a = 10;
int b = 20;
bool ret = MyCompare(a, b);
if (ret)
{
cout << "a == b" << endl;
}
else
{
cout << "a != b" << endl;
}
}
2、对比两个Person类时
class Person
{
public:
Person(string name, int age)
{
m_name = name;
m_age = age;
}
string m_name;
int m_age;
};
template <class T>
bool MyCompare(T&a, T&b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
出现了以上问题,这是因为在对特定的数据类型 需要具体化方式做特殊实现
class Person
{
public:
Person(string name, int age)
{
m_name = name;
m_age = age;
}
string m_name;
int m_age;
};
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.m_age == P2.m_age && P1.m_name == P2.m_name)
{
return true;
}
else
{
return false;
}
}
//对比两个Person类
void test02()
{
Person P1("Tom", 23);
Person P2("Tom", 24);
bool ret = MyCompare(P1, P2);
if (ret)
{
cout << "P1 == P2" << endl;
}
else
{
cout << "P1 != P2" << endl;
}
}
类模板
类模板的使用规则
template < class NameType, class AgeType >
class Person
{
public:
Person(NameType name, AgeType age)
{
m_name = name;
m_age = age;
}
void Show()
{
cout << "name:" << this->m_name << " age:" << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test()
{
Person<string, int> P1("Tom", 19);
P1.Show();
}
int main()
{
test();
return 0;
}
类模板与函数模板的区别
1、类模板没有自动类型推导使用方式
2、类模板在模板参数列表中可以有默认参数
类模板中成员函数的调用时机
结论:类模板中的成员函数并不是一开始就创建的 而是在调用的时候才去创建
类模板对象做函数参数
1、指定传入类型
template < class NameType, class AgeType >
class Person
{
public:
Person(NameType name, AgeType age)
{
m_name = name;
m_age = age;
}
void Show()
{
cout << "name:" << this->m_name << " age:" << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
//1、指定传入类型
void PriPerson1(Person<string, int>&P)
{
P.Show();
}
void test01()
{
Person<string, int>P("张三", 23);
PriPerson1(P);
}
2、参数模板化
template < class NameType, class AgeType >
class Person
{
public:
Person(NameType name, AgeType age)
{
m_name = name;
m_age = age;
}
void Show()
{
cout << "name:" << this->m_name << " age:" << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
template < class NameType, class AgeType >
void PriPerson2(Person<NameType, AgeType>&P)
{
P.Show();
cout << "NameType的类型为:" << typeid(NameType).name() << endl;
cout << "AgeType的类型为:" << typeid(AgeType).name() << endl;
}
void test02()
{
Person<string, int>P("李四", 24);
PriPerson2(P);
}
3、整个类模板化
template < class NameType, class AgeType >
class Person
{
public:
Person(NameType name, AgeType age)
{
m_name = name;
m_age = age;
}
void Show()
{
cout << "name:" << this->m_name << " age:" << this->m_age << endl;
}
NameType m_name;
AgeType m_age;
};
//3、整个类模板化
template <class T>
void PriPerson3( T &P)
{
P.Show();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>P("王五", 25);
PriPerson3(P);
}
类模板与继承
当类模板碰到继承时 需要注意一下三点:
1、当子类继承的父类是一个模板时 子类在声明的时候 要指定出父类中T的了类型
2、如果不指定 编译器无法给予子类分配内存
3、如果想灵活指出父类中T的类型 子类也需变成类模板
template<class T>
class Base
{
public:
T t;
};
template <class T1,class T2>
class Son :public Base<T1>
{
public:
Son()
{
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
T2 t;
};
void test02()
{
Son<int, char>s;
}
int main()
{
test02();
return 0;
}
类模板成员函数类外实现
#include<iostream>
#include<string>
using namespace std;
template <class T1, class T2>
class Person
{
public:
Person(string name, int age);
void show();
T1 m_name;
T2 m_age;
};
//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
this->m_age = age;
this->m_name = name;
}
//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
cout << "姓名:" << this->m_name << endl;
cout << "年龄:" << this->m_age << endl;
}
void test()
{
Person<string,int> P1("张三", 23);
P1.show();
}
int main()
{
test();
return 0;
}
类模板分文件编写
Person.h
#pragma once
#include<iostream>
#include<string>
using namespace std;
template <class T1, class T2>
class Person
{
public:
Person(string name, int age);
void show();
T1 m_name;
T2 m_age;
};
Person.cpp
#include "Person.h"
//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
this->m_age = age;
this->m_name = name;
}
//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
cout << "姓名:" << this->m_name << endl;
cout << "年龄:" << this->m_age << endl;
}
test.c
#include "Person.cpp"
void test()
{
Person<string,int> P1("张三", 23);
P1.show();
}
int main()
{
test();
return 0;
}
这时我们通过运行发现有两处错误:
解决该问题的第一种方法:
1、在test.c中引用Person.cpp
2、将函数的实现与声明统一放在Person.hpp中
Person.hpp
#pragma once
#include<iostream>
#include<string>
using namespace std;
template <class T1, class T2>
class Person
{
public:
Person(string name, int age);
void show();
T1 m_name;
T2 m_age;
};
//构造函数的类外实现
template <class T1, class T2>
Person<T1, T2>::Person(string name, int age)
{
this->m_age = age;
this->m_name = name;
}
//成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::show()
{
cout << "姓名:" << this->m_name << endl;
cout << "年龄:" << this->m_age << endl;
在test.c中添加:
类模板与友元
全局函数 类内实现
通过添加关键字:friend
template <class T1, class T2>
class Person
{
//1、全局函数 类内实现
friend void show(Person<T1, T2>P)
{
cout << " 姓名: " << P.m_name << " 年龄:" << P.m_age << endl;
}
public:
Person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
private:
T1 m_name;
T2 m_age;
};
void test()
{
Person<string,int> P1("张三", 23);
show(P1);
}
int main()
{
test();
return 0;
}
全局函数 类外实现
1、加空模板参数列表
2、如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在
//提前让编译器知道Person类存在
template <class T1, class T2>
class Person;
//类外实现
template <class T1, class T2>
void show(Person<T1, T2>P)
{
cout << " 姓名: " << P.m_name << " 年龄:" << P.m_age << endl;
}
template <class T1, class T2>
class Person
{
friend void show<>(Person<T1, T2>P);
public:
Person(T1 name, T2 age)
{
this->m_age = age;
this->m_name = name;
}
private:
T1 m_name;
T2 m_age;
};
void test()
{
Person<string,int> P1("张三", 23);
show(P1);
}
int main()
{
test();
return 0;
}
类模板案列--数组类封装
1、可以对内置数据类型以及自定义数据类型的数据进行存储
2、将数组中的数据存储到堆区
3、构造函数中可以传入数组的容量
//有参构造 参数 容量
MyArray(int capacity)
{
/*cout << "有参构造函数的调用" << endl;*/
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
4、提供对应的拷贝构造函数以及operator=防止浅拷贝问题
//拷贝构造
MyArray(const MyArray& arr)
{
/*cout << "拷贝构造函数的调用" << endl;*/
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = this->pAddress[i];
}
}
//operator = 防止浅拷贝问题
MyArray& operator=(const MyArray& arr)
{
/*cout << "operator = 函数的调用" << endl;*/
//先判断原来堆区是否有数据 如果有显示放
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = this->pAddress[i];
}
return *this;
}
5、提供尾插法和尾删法对数组中的数据进行增加和删除
//尾插法
void Push_Back(const T& val)
{
//判断容量是否满了
if (this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size++;//更新数组的大小
}
//尾删法
void Pop_Back()
{
if (this->m_Size == 0)
{
return;
}
this->m_Size--;//让用户访问不到最后一个元素 逻辑删除
}
6、可以通过下标的方式访问数组中的元素
//通过下标方式访问数组中的元素
T& operator[](int index)
{
return this->pAddress[index];
}
7、可以获取数组中当前元素个数和数组的容量
//返回数组的容量
int GetCapacity()
{
return this->m_Capacity;
}
//返回数组的大小
int GetSize()
{
return this->m_Size;
}
析构函数:
~MyArray()
{
/*cout << "析构函数的调用" << endl;*/
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
}
完整MyArray.h
#pragma once
#include<iostream>
#include<string>
using namespace std;
template<class T>
class MyArray
{
public:
//有参构造 参数 容量
MyArray(int capacity)
{
/*cout << "有参构造函数的调用" << endl;*/
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
/*cout << "拷贝构造函数的调用" << endl;*/
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = this->pAddress[i];
}
}
//operator = 防止浅拷贝问题
MyArray& operator=(const MyArray& arr)
{
/*cout << "operator = 函数的调用" << endl;*/
//先判断原来堆区是否有数据 如果有显示放
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
//深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = this->pAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T& val)
{
//判断容量是否满了
if (this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size++;//更新数组的大小
}
//尾删法
void Pop_Back()
{
if (this->m_Size == 0)
{
return;
}
this->m_Size--;//让用户访问不到最后一个元素 逻辑删除
}
//通过下标方式访问数组中的元素
T& operator[](int index)
{
return this->pAddress[index];
}
//返回数组的容量
int GetCapacity()
{
return this->m_Capacity;
}
//返回数组的大小
int GetSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
/*cout << "析构函数的调用" << endl;*/
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress;//指针指向堆区开辟的真实数组
int m_Capacity;//数组容量
int m_Size;//数组大小
};
test.c
#include <iostream>
#include"MyArray.h"
using namespace std;
void test()
{
MyArray<int> arr1(6);
MyArray<int> arr2(arr1);
MyArray<int> arr3(100);
arr3 = arr1;
}
int main()
{
test();
return 0;
}
测试运行结果: