网站备案手机号,珠海制作企业宣传片,龙岩刚刚发生的事,如何设置网站兼容性文章目录 函数模板1.函数模板基本语法2.函数模板使用的注意事项3.函数模板案例——数组排序4.普通函数和函数模板的区别5.普通函数和函数模板的调用规则6.模板的局限性 类模板1.类模板2.类模板和函数模板的区别3.类模板中成员函数创建时机4.类模板对象做函数参数5.类模板与继承… 文章目录 函数模板1.函数模板基本语法2.函数模板使用的注意事项3.函数模板案例——数组排序4.普通函数和函数模板的区别5.普通函数和函数模板的调用规则6.模板的局限性 类模板1.类模板2.类模板和函数模板的区别3.类模板中成员函数创建时机4.类模板对象做函数参数5.类模板与继承6.类模板成员函数的类外实现7.类模板分文件编写8.类模板与友元 类模板案例 函数模板
C是面向对象的编程其另一种编程思想是泛型编程主要利用的技术就是模板。C提供两种模板机制函数模板和类模板。 模板是建立通用的模具能够大大提高复用性。 模板的特点模板的通用性很强但其不是万能的模板不可以直接使用其只是一个框架。 比如照片模板需要把自己的照片P上去PPT模板中需要把自己的内容写进去。
1.函数模板基本语法
函数模板的作用建立一个通用函数函数返回值类型和形参类型可以不具体制定用一个虚拟的类型来代表。 函数模板基本语法template typename T 后面是函数的声明或者实现 template 声明创建模板 typename 后面的符号是一种数据类型typename可以用class代替 T 表示通用的数据类型其名称可以替换一般是大写字母。 函数模板在什么时候使用呢下面两个函数分别实现交换两个整型数和交换两个浮点型数。
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;
}通过观察发现这两个函数有很多地方的逻辑是类似的框架大体一致这个时候就可以使用函数模板。 上面不同类型的数据交换函数就可以使用下面的函数模板。
template typename T //声明一个模板T是一个通用的数据类型提示编译器不要报错
void Swap(T a,T b)
{T temp a;a b;b temp;
}在调用的时候有两种方式一种是自动类型推导另一种是显式指定类型。
int a10;
int b20;
double c0.1;
double d0.2;Swap(a,b); //自动类型推导
Swap(c,d);
Swapint(a,b); //显式指定类型
Swapdouble(c,d);2.函数模板使用的注意事项
函数模板使用的注意事项 1.自动类型推导中必须推导出一致的数据类型T才可以使用 2.模板必须要确定出T的数据类型才可以使用。 上面的交换例子中传入的两个参数类型必须一致否则无法推导出一致的数据类型传入不同类型的参数代码会报错。 模板必须要确定出T的数据类型才可以使用。
template typename T
void fun()
{coutfun()的调用endl;
}int main()
{fun(); //直接调用时无法确定T的数据类型尽管T没有在函数中使用代码会报错funint(); //随便给T显式的指定数据类型这样调用就是正确的...
}3.函数模板案例——数组排序
利用函数模板对不同类型的数组进行排序。 该案例的代码如下。
#include iostream
#include string
using namespace std;template typename T //声明一个模板T是一个通用的数据类型提示编译器不要报错
void Swap(T a,T b)
{T temp a;a b;b temp;
}template class T
void arraySort(T arr,int len)
{for(int i0;ilen;i) //选择排序{int mini;for(int ji1;jlen;j){if(arr[min]arr[j]){minj;}}if(min!i){Swap(arr[min],arr[i]);}}
}template class T
void printArr(T arr,int len)
{for(int i0;ilen;i){coutarr[i];}coutendl;
}int main()
{int a[10]{1,5,9,3,6,2,4,8,7,0};char b[]bcade;int len_asizeof(a)/sizeof(a[0]);int len_bstrlen(b)/sizeof(b[0]);cout排序前整型数组a: ;printArr(a,len_a);arraySort(a,len_a);cout排序后整型数组a: ;printArr(a,len_a);cout排序前字符型数组b: ;printArr(b,len_b);arraySort(b,len_b);cout排序后字符型数组b: ;printArr(b,len_b);system(pause);return 0;
}代码中分别对字符型数组和整型数组进行了测试运行结果如下图所示。
4.普通函数和函数模板的区别
普通函数调用时可以发生自动类型转换即隐式类型转换。 函数模板调用时如果利用自动类型推导不会发生隐式类型转换如果利用显示指定类型的方式可以发生隐式类型转换。
#include iostream
#include string
using namespace std;int add(int a,int b)
{return ab;
}template typename T
T t_add(T a,T b)
{return ab;
}int main()
{int a10;int b20;char ca;cout1.abadd(a,b)endl;cout1.acadd(a,c)endl;cout2.abt_add(a,b)endl;//cout2.abt_add(a,c)endl; //报错cout2.abt_addint(a,c)endl; //需要显式的指定system(pause);return 0;
}程序运行结果如下图所示。 因此在使用函数模板时一般使用显式的方式调用函数模板因为在调用的时候我们就可以知道具体的函数类型T。
5.普通函数和函数模板的调用规则
普通函数和函数模板的调用规则如下 1.如果函数模板和普通函数都可以实现优先调用普通函数 2.可以通过空模板参数列表来强制调用函数模板 3.函数模板也可以发生重载 4.如果函数模板可以产生更好的匹配优先调用函数模板。
#include iostream
using namespace std;void fun(int a,int b)
{cout1.调用普通函数endl;
}template typename T
void fun(T a,T b)
{cout2.调用函数模板endl;
}template typename T
void fun(T a,T b,T c) //函数模板的重载
{cout3.调用重载的函数模板endl;
}int main()
{int a10;int b20;fun(a,b); //优先调用普通函数fun(a,b); //如果非要调用函数模板通过空模板参数列表来实现fun(a,b,30); //调用重载的函数模板char c a;char d b;fun(c,d); //调用普通函数需要发生隐式类型转换函数模板有更好的匹配性system(pause);return 0;
}上面程序的运行结果如下图所示。
6.模板的局限性
模板并不是万能的有些特定的数据类型需要用具体化的方式做特殊实现。 利用具体化的模板可以解决自定义类型的通用化。 学习模板并不是为了写模板而是在STL(Standard Template Library, 标准模板库)能够运用系统提供的模板。 比如下面的代码如果要比较特定的数据类型就需要用具体化的方式在代码中做特殊实现。
#include iostream
#include string
using namespace std;class Person
{
public:Person(string name,int age){this-name name;this-age age;}string name;int age;
};template class T
void Compare(T a,T b)
{if(ab){coutabendl;}else{couta!bendl;}
}template void Compare(Person p1,Person p2) //模板的重载具体化的数据类型会优先调用
{if(p1.namep2.name p1.agep2.age){coutp1p2endl;}else{coutp1!p2endl;}
}int main()
{Person p1(Tom,10);Person p2(Tom,10);Compare(p1,p2);system(pause);return 0;
}类模板
1.类模板
类模板和函数模板写法相似稍有不同的是类中含有的数据类型较多这就需要在声明类模板的时候定义多个通用类型然后在调用的时候传入具体的数据类型。 一个简单的类模板例子如下。
#include iostream
#include string
using namespace std;template class NameT,class AgeT //类模板有多个类型的情况
class Person
{
public:Person(NameT name,AgeT age){this-name name;this-age age;}void showinfo(){coutname:nameendl;coutage:ageendl;}NameT name;AgeT age;
};int main()
{Personstring,int p(Tom,10);p.showinfo();system(pause);return 0;
}程序运行结果如下图所示。
2.类模板和函数模板的区别
类模板和函数模板的区别类模板中没有自动类型推导的使用方式只能用显示指定类型的方式类模板在模板参数列表中可以有默认参数。
Person p(Tom,10); //报错无法进行自动类型推导
Personstring,int p(Tom,10);如果在声明类模板的时候给了默认参数在调用的时候就可以省略掉该位置的数据类型。
template class NameT,class AgeTint
Personstring p(Tom,10);3.类模板中成员函数创建时机
普通类中的成员函数一开始就可以创建类模板中的成员函数在调用时才创建。 因为类模板中的成员函数类型是不确定的只有在调用的时候才可以确定因此类模板中的成员函数在调用时才创建。 类模板中成员函数创建时机的例子如下。
#include iostream
#include string
using namespace std;class Person1
{
public:void showPerson1(){cout调用showPerson1()成员函数endl;}
};class Person2
{
public:void showPerson2(){cout调用showPerson2()成员函数endl;}
};template class T
class Myclass
{
public:T obj;void fun1(){obj.showPerson1(); //不会报错因为T的类型还不确定}void fun2(){obj.showPerson2();}
};int main()
{MyclassPerson1 p1;p1.fun1(); //成员函数在调用时才创建MyclassPerson2 p2;p2.fun2(); //成员函数在调用时才创建system(pause);return 0;
}程序运行结果如下图所示。
4.类模板对象做函数参数
通过类模板创建的对象有三种方式向函数传参直接指定传入类型参数模板化整个类模板化。直接指定传入类型是比较常用的方式。 如果要看模板中某个通用数据类型T具体是什么类型可以使用以下语句实现。
typeid(T).name()类模板对象做函数参数的例子如下。
#include iostream
#include string
using namespace std;template class NameT,class AgeT
class Person
{
public:Person(NameT name,AgeT age){this-name name;this-age age;}void showinfo(){coutname:nameendl;coutage:ageendl;}NameT name;AgeT age;
};//1.指定传入类型
void fun1(Personstring,int p)
{p.showinfo();
}//2.参数模板化
template class T1,class T2
void fun2(PersonT1,T2 p)
{p.showinfo();coutT1的类型为typeid(T1).name()endl;coutT2的类型为typeid(T2).name()endl;
}//3.整个类模板化
template class T
void fun3(T p)
{p.showinfo();coutT的类型为typeid(T).name()endl;
}int main()
{Personstring,int p1(Tom,10);fun1(p1);Personstring,int p2(Brown,11);fun2(p2);Personstring,int p3(Jack,12);fun3(p3);system(pause);return 0;
}程序运行结果如下图所示。
5.类模板与继承
当类模板遇到继承的时候 需要注意的是当子类继承的父类是一个类模板时子类在声明的时候需要指定父类中的T类型。 如果不指定父类中的T类型编译器无法给子类分配内存如果想要灵活指定出父类中的T类型子类也需要写成类模板。
template class T
class Parent
{
public:T a;
};//class Son : public Parent //不指明父类中的T类型会报错
class Son : public Parent int //需指明父类中的T类型才能完成继承
{};如果要灵活指定父类中的T类型子类也需要变成类模板。
#include iostream
#include string
using namespace std;template class T
class Parent
{
public:T a;
};template class T1,class T2 //要灵活指定父类中的T类型子类需要变成类模板
class Son : public Parent T1
{
public:Son(){coutT1的类型为typeid(T1).name()endl;coutT2的类型为typeid(T2).name()endl;}T2 b;
};void fun()
{Sonint,char s; //父类传入的是int子类传入的是char
}int main()
{fun();system(pause);return 0;
}上面程序的运行结果如下图所示。
6.类模板成员函数的类外实现
前面已经提到过类模板成员函数的类内实现一个简单的例子如下。
#include iostream
#include string
using namespace std;template class T1,class T2
class Person
{
public:Person(T1 name,T2 age){this-name name;this-age age;}void showinfo(){coutname:nameendl;coutage:ageendl;}T1 name;T2 age;
};void fun()
{Personstring,int p(Tom,10);p.showinfo();
}int main()
{fun();system(pause);return 0;
}同样是上面的例子类模板成员函数的类外实现如下。
#include iostream
#include string
using namespace std;template class T1,class T2
class Person
{
public:Person(T1 name,T2 age); //构造函数声明void showinfo(); //成员函数声明T1 name;T2 age;
};template class T1,class T2 //先定义模板
PersonT1,T2::Person(T1 name,T2 age) //类模板构造函数的类外实现
{this-name name;this-age age;
}template class T1,class T2 //类模板成员函数的类外实现
void PersonT1,T2::showinfo()
{coutname:nameendl;coutage:ageendl;
}void fun()
{Personstring,int p(Tom,10);p.showinfo();
}int main()
{fun();system(pause);return 0;
}7.类模板分文件编写
类模板中成员函数的创建时机在调用阶段这会导致代码分文件编写时链接不到。 解决的方法直接包含.cpp源文件将声明和实现写在同一个文件中并将后缀名改为.hpp。 比如上面的例子将其拆分成三个文件person.h中的内容如下。
#pragma once
#include iostream
using namespace std;template class T1,class T2
class Person
{
public:Person(T1 name,T2 age);void showinfo();T1 name;T2 age;
};person.cpp中的内容如下。
#include person.htemplate class T1,class T2
PersonT1,T2::Person(T1 name,T2 age)
{this-name name;this-age age;
}template class T1,class T2
void PersonT1,T2::showinfo()
{coutname:nameendl;coutage:ageendl;
}demo.cpp中的内容如下。
#include iostream
#include string
using namespace std;
#include person.h
//#include person.cpp //将头文件替换为源文件后程序就能正确运行了void fun()
{Personstring,int p(Tom,10);p.showinfo();
}int main()
{fun();system(pause);return 0;
}这样写看似没有问题但是在运行程序的时候发生了错误错误内容如下。 解决错误的第一种方法就是保持原有的文件数量不变但是将引用的头文件替换为源文件这样在编译源文件的时候也会用到头文件。 第二种方法是将头文件和源文件的内容合在一个文件中函数的声明和实现在同一个文件中实现然后引用这个头文件即可为了区分其是类模板后缀名可改为.hpp但也可以不改动。
8.类模板与友元
全局函数类模板内实现时直接在类模板内声明友元即可。全局函数类模板外实现时需要提前让编译器知道全局函数的存在。 全局函数类内实现的例子如下。
#include iostream
#include string
using namespace std;template class T1,class T2
class Person
{friend void showinfo(PersonT1,T2 p) //通过友元 类内实现{coutname:p.nameendl;coutage:p.ageendl;}
public:Person(T1 name,T2 age){this-name name;this-age age;}
private:T1 name;T2 age;
};void fun()
{Personstring,int p(Tom,10);showinfo(p);
}int main()
{fun();system(pause);return 0;
}全局函数类外实现的例子如下。
#include iostream
#include string
using namespace std;//提前声明类模板
template class T1,class T2
class Person;//全局函数提到类前面
template class T1,class T2
void showinfo(PersonT1,T2 p) //通过友元 类外实现
{coutname:p.nameendl;coutage:p.ageendl;
}template class T1,class T2
class Person
{friend void showinfo(PersonT1,T2 p); //声明的时候要加上空模板参数列表
public:Person(T1 name,T2 age){this-name name;this-age age;}
private:T1 name;T2 age;
};void fun()
{Personstring,int p(Tom,10);showinfo(p);
}int main()
{fun();system(pause);return 0;
}类模板案例
实现一个通用的数组类要求如下 可以对内置数据类型以及自定义数据类型的数据进行存储 将数组中的数据存储到堆区 构造函数中可以传入数组的容量 提供对应的拷贝构造函数以及operator防止浅拷贝问题 提供尾插法和尾删法对数组中的数据进行增加和删除 可以通过下标的方式访问数组中的元素 可以获取数组中当前元素个数和数组的容量。 MyArray.hpp文件中的代码如下其中包括构造函数、析构函数和一些函数的重载。
#pragma once
#include string
using namespace std;template class T
class MyArray
{
public:MyArray(int capacity) //带参构造函数{cout构造函数调用endl;this-capacity capacity;this-size 0;this-p new T[this-capacity]; //根据传入的数组容量大小开辟堆区内存}MyArray(const MyArray a) //拷贝构造函数{cout拷贝构造函数调用endl;this-capacity a.capacity;this-size a.size;//this-p a.p; //浅拷贝this-p new T[a.capacity]; //深拷贝for(int i0;ithis-size;i) //将原来数组中的数据进行拷贝{this-p[i] a.p[i];}}MyArray operator(const MyArray a){coutoperator函数调用endl;if(this-p!NULL) //先判断原来堆区中是否有数据如果有就先释放{delete [] this-p;this-p NULL;this-capacity 0;this-size 0;}//进行深拷贝this-capacity a.capacity;this-size a.size;this-p new T[a.capacity];for(int i0;ithis-size;i){this-p[i] a.p[i];}return *this;}void Insert(const T a) //尾插法{if(this-size this-capacity){cout数组容量已满endl;return;}this-p[this-size] a; //在数组尾插入数据this-size; //更新当前数组大小}void Delete() //尾删法{if(this-size 0){cout数组已空endl;return;}this-size--; //让用户访问不到即可}T operator[](int index) //通过下标的方式访问数组元素{return this-p[index];}int getCap() //返回数组容量{return this-capacity;}int getSize() //返回数组大小{return this-size;}~MyArray() //析构函数{cout析构函数调用endl;if(this-p!NULL){delete [] this-p;this-p NULL;}}
private:T *p; //指向堆区开辟的数组int capacity; //数组容量int size; //数组大小
};主函数文件中的代码如下。
#include iostream
#include string
using namespace std;
#include MyArray.hppvoid fun1() //各函数的调用测试
{MyArrayint a(5);for(int i0;i5;i){a.Insert(i);}cout数组的输出如下:endl;for(int i0;i5;i){couta[i]endl;;}a.Delete();cout数组的容量:a.getCap()endl;cout数组的大小:a.getSize()endl;MyArrayint b(a); //拷贝构造函数调用MyArrayint c(5); c a; //operator 的调用
}class Person
{
public:Person(){} //自定义数据类型需提供默认构造函数的空实现Person(string name,int age){this-name name;this-age age;}string name;int age;
};void printPerson(MyArrayPerson a)
{for(int i0;ia.getSize();i){cout姓名:a[i].nameendl;cout年龄:a[i].ageendl;}
}void fun2() //自定义数据类型测试
{MyArrayPerson a(5);Person p1(Tom,10);Person p2(Jack,12);Person p3(Danny,11);a.Insert(p1);a.Insert(p2);a.Insert(p3);printPerson(a);cout数组的容量:a.getCap()endl;cout数组的大小:a.getSize()endl;
}int main()
{fun1();fun2();system(pause);return 0;
}程序运行后的结果如下图所示。 本文参考视频 黑马程序员匠心之作|C教程从0到1入门编程,学习编程不再难