wordpress网站地图百度插件,建站快车品牌,wordpress 当前用户id,wordpress邮件模板模板
1. 模板的概念
模板就是建立通用的模具#xff0c;大大提高复用性
模板的特点#xff1a;
1. 模板不可以直接使用#xff0c;它只是一个框架
2. 模板的通用并不是万能的
2. 函数模板
C另一种编程思想称为泛型编程#xff0c;主要利用的技术就是模板
C提供两种…模板
1. 模板的概念
模板就是建立通用的模具大大提高复用性
模板的特点
1. 模板不可以直接使用它只是一个框架
2. 模板的通用并不是万能的
2. 函数模板
C另一种编程思想称为泛型编程主要利用的技术就是模板
C提供两种模板机制函数模板和类模板
2.1 函数模板语法
函数模板作用建立一个通用函数其函数返回值类型和形参类型可以不具体制定用一个虚拟的类型来代替
语法
templatetypename T
函数声明或定义
解释
template —— 声明创建模板
typename —— 表明其后面的符号是一种数据类型可以用class代替
T —— 通用的数据类型名称可以替换通常为大写字母
#include iostreamusing namespace std;// 交换整型数据函数
void SwapInt(int a, int b)
{int temp a;a b;b temp;
}// 交换浮点型数据函数
void SwapDouble(double a, double b)
{double temp a;a b;b temp;
}// 利用模版提供通用的交换函数
templatetypename T
void mySwap(T a, T b)
{T temp a;a b;b temp;
}void test()
{int a1 10;int b1 20;double c1 1.1;double d1 2.2;int a2 10;int b2 20;double c2 1.1;double d2 2.2;cout 交换前a1 a1 endl;cout 交换前b1 b1 endl;SwapInt(a1, b1);cout 交换后a1 a1 endl;cout 交换后b1 b1 endl;cout 交换前c1 c1 endl;cout 交换前d1 d1 endl;SwapDouble(c1, d1);cout 交换后c1 c1 endl;cout 交换后d1 d1 endl;cout 交换前a2 a2 endl;cout 交换前b2 b2 endl;// 1. 自动类型推导mySwap(a2, b2);cout 交换后a2 a2 endl;cout 交换后b2 b2 endl;cout 交换前c2 c2 endl;cout 交换前d2 d2 endl;// 2. 显示指定类型mySwapdouble(c2, d2);cout 交换后c2 c2 endl;cout 交换后d2 d2 endl;
}int main(int argc, char* argv[])
{test();return 0;
}
总结
1. 函数模板利用关键字 template
2. 使用函数模板有两种方式自动类型推导、显示指定类型
3. 模板的目的是为了提高复用性将类型参数化
2.2 函数模板注意事项
注意事项
1. 自动类型推导必须推导出一致的数据类型T才可以使用
2. 模板必须要确定出T的数据类型才可以使用
2.3 函数模板案例
案例描述
1. 利用函数模板封装一个排序的函数可以对不同数据类型数据进行排序
2. 排序规则从小到大排序算法为选择排序
3. 分别利用char数据和int数组进行测试
#include iostreamusing namespace std;// 交换函数模板
templatetypename T
void swapElement(T a, T b)
{T temp a;a b;b temp;
}// 排序函数模板
templatetypename T
void arrSort(T arr[], int len)
{for (int i 0; i len - 1; i){int min i;for (int j i 1; j len; j){if (arr[min] arr[j])min j;}if (min ! i){swapElement(arr[min], arr[i]);}}
}// 输出函数模板
templatetypename T
void arrPrint(T arr[], int len)
{for (int i 0; i len; i){if (arr[i] ! \0){cout arr[i] ;}}cout endl;
}// 测试char数组
void test01()
{char charArr[] cdefgab;int charLen sizeof(charArr) / sizeof(char);arrSort(charArr, charLen);arrPrint(charArr, charLen);
}// 测试int数组
void test02()
{int intArr[] { 6,9,3,0,8,4,1,7,5,2 };int charLen sizeof(intArr) / sizeof(int);arrSort(intArr, charLen);arrPrint(intArr, charLen);
}int main(int argc, char* argv[])
{test01();test02();return 0;
}
2.4 普通函数和函数模板的区别
普通函数与函数模板区别
1. 普通函数调用时可以发生自动类型转换(隐式类型转换)
2. 函数模板调用时如果利用自动类型推导不会发生隐式类型转换
3. 如果利用显示指定类型的方式可以发生隐式类型转换
#include iostreamusing namespace std;// 普通函数
int myAdd01(int a, int b)
{return a b;
}// 函数模板
templatetypename T
T myAdd02(T a, T b)
{return a b;
}void test()
{int a 10;char b b;// 普通函数调用,输出108,将b转换为int类型,其值为b的ascll码值cout a b myAdd01(a, b) endl; // 自动类型推导(报错)// cout a b myAdd02(a, b) endl;// 显示指定类型:输出108cout a b myAdd02int(a, b) endl;
}
int main(int argc, char* argv[])
{test();return 0;
}
总结建议使用显示指定类型的方式调用函数模板因为可以自己确定通用类型T
2.5 普通函数与函数模板的调用规则
调用规则如下
1. 如果函数模板和普通函数都可以调用优先使用普通函数
2. 可以通过空模板参数列表来强制调用函数模板
3. 函数模板也可以发生重载
4. 如果函数模板可以产生更好的匹配优先调用函数模板
#include iostreamusing namespace std;// 普通函数
void myPrint(int a, int b)
{cout 调用的是普通函数 endl;
}// 函数模板
templatetypename T
void myPrint(T a, T b)
{cout 调用的是模板1 endl;
}templatetypename T
void myPrint(T a, T b, T c)
{cout 调用的是模板2 endl;
}void test()
{int a 10;int b 20;// 1. 优先调用普通函数myPrint(a, b);// 2. 通过空模板参数列表强制调用函数模板myPrint(a, b);// 3. 函数模板也可以发生重载myPrint(a, b, 100);// 4. 如果函数模板可以产生更好的匹配优先调用函数模板char c1 a;char c2 b;// 模板1不用发生隐式类型转换,产生更好的匹配myPrint(c1, c2);
}int main(int argc, char* argv[])
{test();return 0;
}
总结既然提供了函数模板最好就不要提供普通函数否则容易出现二义性
2.6 模板的局限性
局限性模版的通用性并不是万能的
例如
templatetypename T
void f(T a, T b)
{a b;
}
在上述代码中提供的赋值操作如果传入的a和b是一个数组就无法实现了
例如
templatetypename T
void func(T a, T b)
{if(ab){...}
}
在上述代码中如果T的数据类型传入的是想Person这样的自定义数据类型也无法正常运行
因此C为了解决这种问题提供模板的重载可以为这些特定的类型提供具体化的模板
#include iostream
#include stringusing namespace std;class Person
{
public:Person(string name, int age){this-m_Name name;this-m_Age age;}string m_Name;int m_Age;
};// 对比两个数据是否相等函数
templatetypename T
bool myCompare(T a, T b)
{if (a b){return true;}else{return false;}
}void test01()
{int a 10;int b 20;bool res myCompare(a, b);if (res true){cout a b endl;}else{cout a ! b endl;}
}// 利用具体化Person的版本实现代码具体化优先调用
template bool myCompare(Person p1, Person p2)
{if (p1.m_Name p2.m_Name p1.m_Age p2.m_Age){return true;}else{return false;}
}void test02()
{Person p1(tom, 10);Person p2(tom, 10);bool res myCompare(p1, p2);if (res true){cout a b endl;}else{cout a ! b endl;}
}int main(int argc, char* argv[])
{test01();test02();return 0;
}
总结
1. 利用具体化的模板可以解决自定义类型的通用化
2. 学习模板并不是为了写模板而是在STL中能够运用系统提供的模板
3. 类模板
3.1 类模板语法
类模板作用建立一个通用类类中的成员数据类型可以是不具体制定的用一个虚拟的类型来代表
语法
templateclass T
类
解释
template —— 声明创建模板
class —— 表面其后面的符号是一种数据类型可以用typename代替
T —— 通用的数据类型名称可以替换通常为大写字母
#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{
public:NameType m_Name;AgeType m_Age;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;}
};void test()
{Personstring, int p(tom, 10);p.showPerson();
}int main(int argc, char* argv[])
{test();return 0;
}
3.2 类模板和函数模板的区别
类模板与函数模板的区别主要有两点
1. 类模板在模板参数列表中可以有默认参数
2. 类模板没有自动类型推导的使用方式
#include iostream
#include stringusing namespace std;// 1. 类模板在模板参数列表中可以有默认参数
templateclass NameType, class AgeType int
class Person
{
public:NameType m_Name;AgeType m_Age;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;}
};void test()
{// 2. 类模板没有自动类型推导使用方式//Person p(tom, 10);Personstringp(tom, 10);p.showPerson();
}int main(int argc, char* argv[])
{test();return 0;
}
3.3 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是由区别的
普通类中的成员函数一开始就创建
类模板中的成员函数在调用时才创建
3.4 类模板对象做函数参数
类模板实例化出的对象向函数传参的方式
一共有三种传入方式
1. 指定传入的类型 — 直接显示对象的数据类型
2. 参数模板化 — 将对象中的参数变为模板进行传递
3. 整个类模板化 — 将这个对象类型模板化进行传递
#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{
public:NameType m_Name;AgeType m_Age;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;}
};// 1. 指定传入类型
void printPerson01(Personstring, int p)
{p.showPerson();
}void test01()
{Personstring, intp(孙悟空,100);printPerson01(p);
}// 2. 参数模板化
templatetypename NameType, typename AgeType
void printPerson02(PersonNameType, AgeType p)
{p.showPerson();// 查看推到出来的类型cout NameType的类型: typeid(NameType).name() endl;cout AgeType的类型: typeid(AgeType).name() endl;
}void test02()
{Personstring, intp(猪八戒, 90);printPerson02(p);
}// 3. 整个类模板化
templatetypename T
void printPerson03(T p)
{p.showPerson();// 查看推到出来的类型cout T的类型: typeid(T).name() endl;
}void test03()
{Personstring, intp(唐三藏, 45);printPerson03(p);
}int main(int argc, char* agrv[])
{test01();test02();test03();return 0;
}
总结
1. 通过类模板创建的对象可以有三种方式向函数中进行传参
2. 使用比较广泛的是第一种指定传入的类型
3.5 类模板与继承
当类模板碰到继承时需要注意以下几点
1. 当子类继承的父类是一个类模板时子类在声明的时候需要制定出父类中T的类型
2. 如果不指定编译器无法给子类分配内存
3. 如果想灵活指定出父类中T的类型子类也需要变为类模板
#include iostream
#include stringusing namespace std;templateclass T1
class Base
{
public:T1 m_BaseNum;
};//class Son :public Base 错误必须要知道父类中T1的类型才可以继承给子类
class Son1 :public Baseint{};// 如果想灵活指定父类中T1的类型子类也需要变类模板
templateclass T1, class T2
class Son2 :public BaseT1
{
public:T2 m_Son2Num;Son2(){cout Base中T1的数据类型为: typeid(T1).name() endl;cout Son2中T2的数据类型为: typeid(T2).name() endl;}
};
int main(int argc, char* argv[])
{Son2string, ints;return 0;
}
总结如果父类是类模板子类需要指定出父类中T的数据类型
3.6 类模板成员函数的类外实现
#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{
public:NameType m_Name;AgeType m_Age;// 类内声明Person(NameType name, AgeType age);void showPerson();
};// 构造函数的类外实现
templateclass NameType, class AgeType
PersonNameType, AgeType::Person(NameType name, AgeType age)
{this-m_Name name;this-m_Age age;
}// 成员函数的类外实现
templateclass NameType, class AgeType
void PersonNameType, AgeType::showPerson()
{cout 姓名: this-m_Name endl;cout 年龄: this-m_Age endl;
}void test()
{Personstring, intp(tom, 18);p.showPerson();
}int main(int argc, char* argv[])
{test();return 0;
}
总结类模板中成员函数类外实现时需要加上模板参数列表
3.7 类模板分文件编写
问题类模板中成员函数创建时机是在调用阶段导致分文件编写时链接不到
解决1. 直接包含.cpp源文件 2. 将声明(.h)和实现(.cpp)写到同一文件中并更改后缀名为.hpphpp是约定的名称并不是强制
1. 在头文件中创建XXX.h在源文件中创建XXX.cpp
Person.h
#pragma once#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{
public:NameType m_Name;AgeType m_Age;// 类内声明Person(NameType name, AgeType age);void showPerson();
};Person.cpp
#include Person.h// 构造函数的类外实现
templateclass NameType, class AgeType
PersonNameType, AgeType::Person(NameType name, AgeType age)
{this-m_Name name;this-m_Age age;
}// 成员函数的类外实现
templateclass NameType, class AgeType
void PersonNameType, AgeType::showPerson()
{cout 姓名: this-m_Name endl;cout 年龄: this-m_Age endl;
}
main.cpp
#include iostream
#include string
#include Person.cppusing namespace std;void test()
{Personstring, intp(tom, 18);p.showPerson();
}int main(int argc, char* argv[])
{test();return 0;
}
2. 在头文件中创建XXX.hpp
Person.hpp
#pragma once#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{
public:NameType m_Name;AgeType m_Age;// 类内声明Person(NameType name, AgeType age);void showPerson();
};// 构造函数的类外实现
templateclass NameType, class AgeType
PersonNameType, AgeType::Person(NameType name, AgeType age)
{this-m_Name name;this-m_Age age;
}// 成员函数的类外实现
templateclass NameType, class AgeType
void PersonNameType, AgeType::showPerson()
{cout 姓名: this-m_Name endl;cout 年龄: this-m_Age endl;
}
main.cpp
#include iostream
#include string
#include Person.hppusing namespace std;void test()
{Personstring, intp(tom, 18);p.showPerson();
}int main(int argc, char* argv[])
{test();return 0;
}
总结主流的解决方式是第二种将类模板成员函数写到一起并将后缀名改为.hpp
3.8 类模板与友元
全局函数类内实现 — 直接在类内声明友元即可
全局函数类外实现 — 需要提前让编译器知道全局函数的存在
全局函数类内实现
#include iostream
#include stringusing namespace std;templateclass NameType, class AgeType
class Person
{friend void showPerson(PersonNameType, AgeType p){cout 姓名: p.m_Name endl;cout 年龄: p.m_Age endl;}
public:Person(NameType name, AgeType age){this-m_Name name;this-m_Age age;}
private:NameType m_Name;AgeType m_Age;
};void test()
{Personstring, intp(tom, 18);showPerson(p);
}int main(int argc, char* argv[])
{test();return 0;
}
全局函数类外实现
#include iostream
#include stringusing namespace std;// 提前让编译器知道Person类的存在,因为把全局函数的类外实现放在在前面
// 全局函数类外实现用到了Person
templateclass NameType, class AgeType
class Person;// 全局函数类外实现
templateclass NameType, class AgeType
void showPerson(PersonNameType, AgeType p)
{cout 姓名: p.m_Name endl;cout 年龄: p.m_Age endl;
}templateclass NameType, class AgeType
class Person
{// 全局函数类内声明// 加空模板参数列表因为类外实现变成了函数模板// 如果全局函数是类外实现需要让编译器提前知道这个函数的存在friend void showPerson(PersonNameType, AgeType p);public:Person(NameType name, AgeType age){this-m_Name name;this-m_Age age;}
private:NameType m_Name;AgeType m_Age;
};void test()
{Personstring, intp(tom, 18);showPerson(p);
}int main(int argc, char* argv[])
{test();return 0;
}
总结建议全局函数做类内实现用法简单而且编译器可以直接识别
3.9 类模板案例
案例分析实现一个通用的数组类需求如下
1. 可以对内置数据类型以及自定义数据类型的数据进行存储
2. 将数组中的数据存储到堆区
3. 构造函数中可以传入数组的容量
4. 提供对应的拷贝构造函数以及operator防止浅拷贝问题
5. 提供尾插法和尾删法对数组中的数据进行增加和删除
6. 可以通过下标的方式访问数组中的元素
7. 可以获取数组中当前元素的个数和数组的容量
MyArray.hpp
#pragma once#include iostreamusing namespace std;templateclass T
class MyArray
{
public:// 有参构造MyArray(int capacity){this-m_Capacity capacity;this-m_Size 0;this-pAddress new T[this-m_Capacity];}// 拷贝构造函数MyArray(const MyArray arr){this-m_Capacity arr.m_Capacity;this-m_Size arr.m_Size;// 深拷贝this-pAddress new T[arr-pAddress];// 将arr中的数据都拷贝过来for(int i0;ithis-m_Size;i){this-pAddress[i] arr.pAddress[i];}}// operator 防止浅拷贝问题MyArray operator(const MyArray arr){// 先判断原来堆区是否有数据如果有先释放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-pAddress];// 将arr中的数据都拷贝过来for (int i 0; i this-m_Size; i){this-pAddress[i] arr.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(){if (this-pAddress ! NULL){delete[] this-pAddress;this-pAddress NULL;}}
private:T* pAddress; // 指针指向堆区开辟的真实数据int m_Capacity; // 数组容量int m_Size; // 数组大小
};main.cpp
#include iostream
#include string
#include MyArray.hppusing namespace std;class Person
{
public:Person(){}Person(string name, int age){this-m_Name name;this-m_Age age;}string m_Name;int m_Age;
};void printIntArray(MyArray int arr)
{for (int i 0; i arr.getSize(); i){cout arr[i] ;}cout endl;
}void printPersonArray(MyArrayPerson arr)
{for (int i 0; i arr.getSize(); i){cout 姓名: arr[i].m_Name \t年龄: arr[i].m_Age endl;}
}// 测试内置数据类型
void test01()
{MyArray intarr1(20);for (int i 0; i 10; i){// 利用尾插法向数组内插入数据arr1.Push_Back(i);}cout arr1的内容为: endl;printIntArray(arr1);cout arr1的大小为: arr1.getSize() endl;cout arr1的容量为: arr1.getCapacity() endl;// 利用尾删法删除数组尾部的数据arr1.Pop_Back();cout 正在删除...... endl;cout arr1的内容为: endl;printIntArray(arr1);cout arr1的大小为: arr1.getSize() endl;cout arr1的容量为: arr1.getCapacity() endl;
}// 测试自定义数据类型
void test02()
{MyArrayPerson arr(20);Person p1(韩信, 20);Person p2(赵云, 18);Person p3(刘邦, 30);Person p4(孙悟空, 99);Person p5(妲己, 15);Person p6(安琪拉, 13);// 将数据插入到数组中arr.Push_Back(p1);arr.Push_Back(p2);arr.Push_Back(p3);arr.Push_Back(p4);arr.Push_Back(p5);arr.Push_Back(p6);// 打印数组cout arr的内容为: endl;printPersonArray(arr);// 输出大小cout arr的大小为: arr.getSize() endl;// 输出容量cout arr的容量为: arr.getCapacity() endl;// 利用尾删法删除数组尾部的数据arr.Pop_Back();cout 正在删除...... endl;cout arr的内容为: endl;printPersonArray(arr);cout arr的大小为: arr.getSize() endl;cout arr的容量为: arr.getCapacity() endl;}int main(int argc, char* argv[])
{test01();test02();return 0;
}