🎉welcome🎉
✒️博主介绍:一名大一的智能制造专业学生,在学习C/C++的路上会越走越远,后面不定期更新有关C/C++语法,数据结构,算法,Linux,ue5使用,制作游戏的心得,和大家一起共同成长。
✈️C++专栏:C++爬塔日记
😘博客制作不易,👍点赞+⭐收藏+➕关注
在上一层中,我遇到了面向对象的最后一个特性——多态,使用多态可以让代码组织结构更加情绪,使可读性变强,利于程序的前期和后期的扩展和维护,掌握多态之后,面向对象结束了, 后面会是什么呢…
- 🚄上章地址:第七层:多态
上到了第八层,映入眼帘的是一个巨大的石碑,“你来了,在C++中,除了面向过程编程和面向对象编程以外,还有一种编程思想,叫做泛型编程,泛型编程主要通过模板实现,这层就需要你掌握模板的力量…"。
- 模板就是建立一个通用的模具,大大提高复用性,比如PPT模板、空白的学生证,这些东西都有一些特点,不能直接使用,需要使用者去添加一些东西,而且不是通用的,比如这个PPT模板是做年度总结的,那就不能强套到一些和年度总结不着边的东西上,因此可以总结出模板的特点。
- 模板只是一个框架
- 这个框架不是万能的
在C++中模板分为两类:
- 函数模板
- 类模板
- 建立一个通用函数,其函数的返回类型和形参可以不具体制定,用一个虚拟的类型来代表
函数模板的语法为:
- templatte< typename T>
函数的声明或者定义
解释:
- template:声明创建函数模板
- typename:表明其后面的符号是一种通用的数据类型,可以用class替代。
- T:通用的数据类型,可以替换成别的字母
可以尝试写一个函数模板:
#include
using namespace std;template
void print(T a)
{cout << a << endl;
}
int main()
{return 0;
}
可以正常跑起来,那函数模板怎么使用呢?
- 自动类型推导:直接传入类型,让编译器自己识别是什么类型
- 显示制定类型:告诉编译器是什么类型,语法如下:
函数名< 是什么数据类型 >(参数)
验证一下两种使用方法:
- 自动类型推导
#include
using namespace std;template
void print(T a)
{cout << a << endl;
}
void test1()
{print(1);
}
int main()
{test1();return 0;
}
- 显示指定类型
#include
using namespace std;template
void print(T a)
{cout << a << endl;
}
void test1()
{print(1);
}
int main()
{test1();return 0;
}
- 自动类型推导必须推导出一致的数据类型才可以使用
- 模板必须确定出T的数据类型,才可以使用
第一点解释:
#include
using namespace std;template
void print(T a,T b)
{cout << a << endl;
}
void test1()
{int a = 0;char b = 0;print(a,b);
}
int main()
{test1();return 0;
}
可以看到,一个int的类型和一个char类型的数据,两个类型就不一致,这个时候,T不知道自己应该是什么,所以报错,说明对于自动类型推导就需要让两个类型一致,那显示指定类型呢?
#include
using namespace std;template
void print(T a,T b)
{cout << a << b << endl;
}
void test1()
{int a = 0;char b = 0;print(a,b);
}
int main()
{test1();return 0;
}
显示指定类型就可以使用。
第二点解释:
当函数模板没有参数的时候,可以直接调用吗?
#include
using namespace std;template
void print()
{cout << "hello world" << endl;
}
void test1()
{print();
}
int main()
{test1();return 0;
}
是不可以直接调用的,因为使用函数模板时,必须人编译器知道通用数据类型是什么,就算没有参数,也需要,这个时候可采用显示指定类型,不管是什么类型,都能跑去来:
#include
using namespace std;template
void print()
{cout << "hello world" << endl;
}
void test1()
{print();print();print();
}
int main()
{test1();return 0;
}
- 普通函数调用可以发生自动类型转换(隐式类型转换)
- 函数模板在调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型,可以发生隐式类型转换
普通函数:
#include
using namespace std;int Swap(int a, int b)
{return a + b;
}void test1()
{int a = 20;char b = '1';int c=Swap(a, b);cout << c << endl;
}
int main()
{test1();return 0;
}
c是69,这是因为发生了隐式类型转换,char转换成了int,同时这是字符1的ASCII码值为49,49加20,就是69。
函数模板:
自动类型推导在上面的注意事项说到,必须推导出统一的数据类型才能跑起来,所以这个时候传两个类型不同的一定是会报错的,就不进行验证了,这个时候试着用显示指定类型试试:
#include
using namespace std;template
int Swap(T a, T b)
{return a + b;
}void test1()
{int a = 20;char b = '1';int c=Swap(a, b);cout << c << endl;
}
int main()
{test1();return 0;
}
这个时候,编译器会将参数内的所有数视为int类型的,就发生了隐式类型转换。
- 如果函数模板和普通函数都可以实现时,优先调用普通函数
- 可以通过空模板参数来强调函数模板,让使用函数模板
- 函数模板可以发生重载
- 如果函数模板可以产生更好的匹配,就使用函数模板
#include
using namespace std;template
int Add(T a, T b)
{cout << "函数模板调用" << endl;return a + b;
}int Add(int a, int b)
{cout << "普通函数调用" << endl;return a + b;
}void test1()
{int a = 20;int b = 20;int c=Add(a, b);cout << c << endl;
}
int main()
{test1();return 0;
}
可以发现两个函数实现内容相同,但是优先调用的普通函数。
- 空模板的语法:函数名<>(传参)
#include
using namespace std;template
int Add(T a, T b)
{cout << "函数模板调用" << endl;return a + b;
}int Add(int a, int b)
{cout << "普通函数调用" << endl;return a + b;
}void test1()
{int a = 20;int b= 20;int c=Add<>(a, b);cout << c << endl;
}
int main()
{test1();return 0;
}
这个时候就调用了函数模板了,就是空模板起的作用,可以强制调用模板。
#include
using namespace std;template
int Add(T a, T b)
{return a + b;
}
template
int Add(T a, T b, T c)
{return a + b + c;
}void test1()
{int a = 20;int b= 20;int c = 20;int d = Add<>(a, b, c);cout << d << endl;
}
int main()
{test1();return 0;
}
- 当普通函数要发生类型转换,但模板可以直接调用的时候,就会调用函数模板
#include
using namespace std;template
void print(T a, T b)
{cout << "调用函数模板" << endl;
}void print(int a, int b)
{cout << "调用普通函数" << endl;
}void test1()
{char a = 0;char b = 0;print(a, b);
}
int main()
{test1();return 0;
}
这个时候如果时调用普通函数,就会发生隐式类型调用,这个时候用函数模板就不需要,就调用函数模板。
- 模板的通用性不是万能的,比如要进行交换操作的时候,如果传参的是数组或者对象的时候,可以调用吗?
#include
#include
using namespace std;template
void Swap(T &a, T &b)
{T tmp = a;a = b;b = tmp;
}
class A
{
public:A(string a, int b){_name = a;_age = b;}string _name;int _age;
};void test1()
{A c1("zhangsan", 19);A c2("lisi",20);Swap(c1, c2);cout << c1._name << "的年龄" << c1._age << endl;cout << c2._name << "的年龄" << c2._age << endl;
}
int main()
{test1();return 0;
}
发现并不能实现交换,那对于这些特殊的类型,怎么才可以进行呢?操作符重载吗?试一试:
#include
#include
using namespace std;class A
{
public:A(string a, int b){_name = a;_age = b;}void operator=(A& a){_name =a._name;_age = a._age;}string _name;int _age;
};
template
void Swap(T &a, T &b)
{T tmp = a;a = b;b = tmp;
}void test1()
{A c1("zhangsan", 19);A c2("lisi", 20);Swap(c1, c2);cout << c1._name << "的年龄" << c1._age << endl;cout << c2._name << "的年龄" << c2._age << endl;
}
int main()
{test1();return 0;
}
操作符重载是可以的,那还有没有方法呢?这个时候可以具体化一个类的版本实现,这个时候具体化是优先调用的,那具体化的语法是什么呢?语法:
- template< > 返回类型 函数模板名 (参数的数据类型为类名)
#include
#include
using namespace std;template
void Swap(T& a, T& b)
{T tmp = a;a = b;b = tmp;
}class A
{
public:A(string a, int b){_name = a;_age = b;}string _name;int _age;
};
template< > void Swap(A& c1, A& c2)
{string tmp1 = c1._name;c1._name = c2._name;c2._name = tmp1;int tmp2 = c1._age;c1._age = c2._age;c2._age = tmp2;
}
void test1()
{A c1("zhangsan", 19);A c2("lisi", 20);Swap(c1, c2);cout << c1._name << "的年龄" << c1._age << endl;cout << c2._name << "的年龄" << c2._age << endl;
}
int main()
{test1();return 0;
}
也是可以实现。
- 建立一个通用类,类内成员的数据类型不可具体定义,用一个通用的类型来代表
那类模板的语法是什么?语法如下:
- template< typename T>
类
因为类内会有多个数据类型,可以定义多个通用的数据类型,那尝试写一个类模板出来:
#include
using namespace std;template
class A
{
public:T1 _a;T2 _b;
};
int main()
{return 0;
}
是可以正常跑起来的,那怎么使用这个类模板实例化一个对象出来呢?
实例化对象的语法:
- 类名< 数据类型(为模板参数列表) > 对象名
那选择通过一个类模板实例化出一个对象:
#include
using namespace std;template
class A
{
public:T1 _a;T2 _b;
};
void test1()
{A a1;a1._a = 10;a1._b = 'a';cout << a1._a << endl;cout << a1._b << endl;
}
int main()
{test1();return 0;
}
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以具有默认参数
#include
using namespace std;template
class A
{
public:T1 _a;T2 _b;
};
void test1()
{A< > a1;a1._a = 10;a1._b = 'a';cout << a1._a << endl;cout << a1._b << endl;
}
int main()
{test1();return 0;
}
报错了,跑不起来,是不可以用的。
这个是什么意思呢?这里用代码证明:
#include
using namespace std;template
class A
{
public:T1 _a;T2 _b;
};
void test1()
{A< > a1;a1._a = 10;a1._b = 'a';cout << a1._a << endl;cout << a1._b << endl;
}
int main()
{test1();return 0;
}
这个代码和上面的代码的区别在哪?
看到,在模板的参数列表中,给通用数据类型进行了赋值,让它具有了默认数据类型,这个时候就可以在实例化对象的时候,不用写数据类型,用默认的数据类型,也可以在改数据类型:
#include
#include
using namespace std;template
class A
{
public:T1 _a;T2 _b;
};
void test1()
{A a1;a1._a = 10.0;a1._b = "abcd";cout << a1._a << endl;cout << a1._b << endl;
}
int main()
{test1();return 0;
}
- 普通类的成员函数会在一开始就创建,类模板中的成员函数在应用时才会创建,对于类模板中调用其他类的时候的成员函数,在确认数据类型前,可以进行编译,没有问题,但是否调用成功时在确定数据类型后
代码验证:
#include
using namespace std;class A1
{
public:void print1(){cout << "A1在被调用" << endl;}
};
class A2
{
public:void print2(){cout << "A2在被调用" << endl;}
};
template
class A
{
public:void print1(){T a;a.print1();}void print2(){T a.print2();}
};
void test1()
{A a;a.print1();a.print2();
}
int main()
{test1();return 0;
}
代码在没有运行的时候,没有报错,在运行之后,会发现内部编译错误,是因为通用类型被定义为A1,A1中没有print2函数,现在注释掉,在来看看:
#include
using namespace std;class A1
{
public:void print1(){cout << "A1在被调用" << endl;}
};
class A2
{
public:void print2(){cout << "A2在被调用" << endl;}
};
template
class A
{
public:void print1(){T a;a.print1();}//void print2()//{// T a.print2();//}
};
void test1()
{A a;a.print1();//a.print2();
}
int main()
{test1();return 0;
}
可以调用,这就是因为在确定通用类型之前没有生成内部函数,在确定好之后,会创建成员函数。
- 类模板实例化出来的对象,可以做函数的参数吗?可以而且方法还不少:
- 指定传入类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变成模板进行传递
- 整个类模板化:将整个对象模型模板化进行传递
指定传入类型怎么去做?下面用代码来演示:
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
void print(per p)
{cout << p._name << "的年龄" << p._age << "岁" << endl;
}void test1()
{per p("张三",18);print(p);
}
int main()
{test1();return 0;
}
直接将对象名前面的类名和模板的数据类型传过去就可以。
参数模板化,顾名思义,就是将参数变成和模板一样,模板有什么特点?通用的数据类型,就其实就是将模板的参数列表变成通用的数据类型即可:
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
template
void print(per p)
{cout << p._name << "的年龄" << p._age << "岁" << endl;
}void test1()
{per p("张三",18);print(p);
}
int main()
{test1();return 0;
}
将对象模型进行模板化,就是将对象变成一个通用类型:
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
template
void print(T p)
{cout << p._name << "的年龄" << p._age << "岁" << endl;
}void test1()
{per p("张三",18);print(p);
}
int main()
{test1();return 0;
}
既然都是类,那类模板可以作为父类吗?被继承吗?是可以的,但是有需要注意的地方:
- 当子类继承的父类是一个类模板的时候,子类在声明的时候,要指出父类中通用类型的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活的指出父类中通用类型的类型,子类也需要变成类模板
- 因为在子类中会继承父类中的所有非静态成员变量,这个时候如果不知道父类中成员是什么类型,则编译器不知道应该分配多少内存给子类当中,所以要指定出来,那怎么指定呢?写法:
- class 子类 : 继承方式 父类< 指定出类型 >
代码验证:
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
class per1 :public per
{
public:};
int main()
{return 0;
}
这个时候没有写,报错。
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
class per1 :public per
{
public:};
int main()
{return 0;
}
写上之后不报错。
那怎么样才能灵活的指出呢?这个时候将子类变成模板即可:
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;}T1 _name;T2 _age;
};
template
class per1 :public per
{
public:};
int main()
{return 0;
}
这个时候,这个T和T1就指向的是父类中的两个通用模板:
- 普通类的成员函数可以在类外实现,那类模板的是否可以?是可以的,有一定的语法格式,不仅仅要加作用域:
- template < class T >
返回类型 类名< 参数 >:: 函数名 (T ____)
构造函数也可以进行类外实现,用代码验证一下:
#include
#include
using namespace std;template
class per
{
public:per(T1 a, T2 b);T1 _name;T2 _age;
};
template
per::per(T1 a, T2 b)
{_name = a;_age = b;cout << _name << "的年龄为" << _age << endl;
}
void test1()
{perp("张三", 18);
}
int main()
{test1();return 0;
}
- 当代码量过大时,程序员通常会选择分文件编写,将声明实现和调用分开,那模板分开可以正常使用吗?
验证:
- test.cpp
#include"per.h"void test1()
{perp("张三", 18);
}
int main()
{test1();return 0;
}
- per.cpp
#include"per.h"
template
class per
{
public:per(T1 a, T2 b){_name = a;_age = b;cout << _name << "的年龄为" << _age << endl;}T1 _name;T2 _age;
};
- per.h
#pragma once#include
#include
using namespace std;
template
class per;
是报错的,那这是为什么?上面说到,对于类模板中的成员函数在编译器阶段才去创建的,我们包含的头文件,那这个时候编译器就不会看到实现部分,只看到了声明,那这个时候可以将引的头文件该成实现的模块:
#include"per.cpp"void test1()
{perp("张三", 18);
}
int main()
{test1();return 0;
}
是可以正常使用的,那这个时候还可以将声明和实现放在一起,任何将后缀改为hpp,其他也可以,只是hpp是约定俗成的,一看上去就知道是模板的实现和定义。
- 那类模板可以有友元函数吗?是可以拥有的,分为类内实现和类外实现
类内实现直接在类捏将函数功能实现出来即可:
template
class per
{friend void print(per &p){cout << _name << "的年龄为" << _age << endl;}
public:per(T1 a, T2 b){_name = a;_age = b;}
private:string _name;int _age;
};
template
class per
{friend void print< >(per& p);
public:per(T1 a, T2 b){_name = a;_age = b;}
private:string _name;int _age;
};
类外实现时要注意,需要提前让编译器知道它存在,所以声明在类模板中。
- 掌握模板不是为了写模板,而是为了使用第九层中的stl。
看完上面的内容,我便踏入了前往第九层的楼梯,上来之后,抬头便是许久未见的天空…
😘预知后事如何,关注新专栏,和我一起征服C++这座巨塔
🚀专栏:C++爬塔日记
🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉