提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、模板的概念
- 二、函数模板
-
- [2.1 函数模板的使用](#2.1 函数模板的使用)
- [2.2 函数模板注意事项](#2.2 函数模板注意事项)
- [2.3 普通函数与函数模板的区别](#2.3 普通函数与函数模板的区别)
- [2.4 普通函数与函数模板的调用规则](#2.4 普通函数与函数模板的调用规则)
- [2.5 模板的局限性](#2.5 模板的局限性)
- 三、类模板
-
- [3.1 类模板的使用](#3.1 类模板的使用)
- [3.2 类模板与函数模板的区别](#3.2 类模板与函数模板的区别)
- [3.3 类模板中成员函数的的创建时机](#3.3 类模板中成员函数的的创建时机)
- [3.4 类模板对象做函数参数](#3.4 类模板对象做函数参数)
- [3.5 类模板与继承](#3.5 类模板与继承)
- [3.6 类模板成员函数类外实现](#3.6 类模板成员函数类外实现)
- [3.7 类模板分文件编写](#3.7 类模板分文件编写)
- [3.8 类模板与友元](#3.8 类模板与友元)
前言
C++中的另一种编程思想称为泛型编程,主要利用模板和标准模板库(STL)。泛型编程通过编写与数据类型无关的代码,提高了代码的复用性和灵活性。STL提供了一套丰富的容器、算法和迭代器,使开发者可以高效地处理各种数据结构和算法。本章节将系统介绍泛型编程的概念、模板的使用方法以及STL的基本组件。
一、模板的概念
模板 :模板就是建立通用的模具,大大提高复用性
模板的特点 :
a.
模板不可以直接使用,它只是一个框架
b.
模板的通用并不是万能的
二、函数模板
2.1 函数模板的使用
语法 :
template< typename T>
函数声明或定义
解释 :template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
示例 :
传统的实现:对于不同的数据类型的交换函数,要分开每个都要写一遍实现:
cpp
//交换整型函数
void swapInt(int &a, int &b){
int temp = a;
a = b;
b = emp;
}
//交换浮点型函数
void swapDouble(double &a, double &b){
double temp = a;
a = b;
b = temp;
}
使用函数模板:对于操作一样的函数,写一次实现即可
cpp
template<typename T>
void mySwap(T &a,T &b) { //传入引用:交换实参
T temp = a;
a = b;
b = temp;
}
//使用
int main()
{
int a = 10;
int b = 20;
mySwap(a,b); //利用模板实现交换//1、自动类型推导
// mySwap<int>(a,b); //2、显示指定类型
cout<< "a=" << a << "b=" << b <<endl;
system("pause");
return 0;
}
总结 :
函数模板定义后,有两种使用方式:
a
. 自动类型推导
cpp
mySwap(a,b);
b
. 显示指定类型
cpp
mySwap<int>(a,b);
mySwap<double >(a,b);
2.2 函数模板注意事项
(1) 注意事项一 :使用自动类型推导调用的方法
自动类型推导,必须推导出一致的数据类型T, 才可以使用
示例:
cpp
int main()
{
int a = 10;
int b = 20;
char c = 'c';
mySwap(a,b); //正确,可以推导出一致的T
//mySwap(a,c);//错误,a和c数据类型不同,推导不出一致的T类型
cout<< "a=" << a << "b=" << b <<endl;
system("pause");
return 0;
}
(2) 注意事项二 :
使用模板时必须要确定出T的数据类型,才可以使用
示例:
cpp
template<classT>
void func(){
cout<< "func调用" <<endl;
}
int main()
{
//func();//错误,模板不能独立使用,必须确定出T的类型
func<int>();//利用显示指定类型的方式,给T一个类型,才可以使用该模板}
system("pause");
return 0;
}
2.3 普通函数与函数模板的区别
区别 :
a.
普通函数调用时可以发生自动类型转换(隐式类型转换)
b.
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
c.
如果利用显示指定类型的方式,可以发生隐式类型转换
示例:
cpp
void myPrint(int a,int b){
cout<< "调用的普通函数a + b:" << a + b <<endl;
}
template<typename T>
void myPrint(T a, T b){
cout<< "调用的模板a + b:" << a + b <endl;
}
void test01(){
int a = 10;
int b = 20;
char c = 'c';
//myPrint(a,c); //报错,使用自动类型推导时,不会发生隐式类型转换
myPrint<int>(a,c);//正确,如果用显示指定类型,可以发生隐式类型转换
}
int main()
{
test01();
system("pause");
return 0;
}
总结 :
建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T
2.4 普通函数与函数模板的调用规则
调用规则如下 :
1.
如果函数模板和普通函数都可以实现,优先调用普通函数
2
. 可以通过空模板参数列表来强制调用函数模板
3.
函数模板也可以发生重载
4.
如果函数模板可以产生更好的匹配,优先调用函数模板
意思就是,例如:当传入的参数为char类型,普通函数形参定义的是int,那么"编译器会觉得隐式转换麻烦",优先去使用模板函数的实现
示例:
cpp
void myPrint(int a,int b){
cout<< "调用的普通函数" <<endl;
}
template<typename T>
void myPrint(T a, T b){
cout<< "调用的模板" <<endl;
}
template<typename T>
void myPrint(Ta, Tb, Tc){ //重载的函数模板
cout<< "调用重载的模板" <<endl;
}
void test01(){
int a = 10;
int b = 20;
myPrint(a,b);//优先调用普通函数的实现
myPrint<>(a, b);//可以通过空模板参数列表来强制调用函数模板
int c = 30;
myPrint(a, b, c);//调用重载的函数模板
char c2 = 'b';
myPrint(c1, c2);//如果函数模板可以产生更好的匹配,优先调用函数模板charc1='a';
}
int main()
{
test01();
system("pause");
return 0;
}
2.5 模板的局限性
局限性 :模板的通用性并不是万能的
例如:
cpp
template<classT>
void func(T a,T b){
a=b;
}
在上述代码中提供的赋值操作,如果传入的a和b是一个数组,就无法实现了
cpp
template<classT>
void func(T a,T b){
if(a>b){...}
};
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
解决 :
C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板(说白了就是不同的类型,不同的处理,感觉和写普通函数实现差不多了)
三、类模板
3.1 类模板的使用
语法
:template< typename T>
类
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
示例:
cpp
//类模板
template<class T1, class T2>
class Person{
public:
Person(NameTypename,AgeTypeage){
this->m_Name = name;
this->m_Age = age;
}
void showPerson(){
cout<< "name:" << this->m_Name << "age:" << this->m_Age <<endl;
}
public:
T1 m_Name;
T2 m_Age;
};
void test01(){
//类模板没有自动类型推导的使用方式,要指定NameType为string类型,AgeType为int类型
Person<string,int>P1("孙悟空",999);
P1.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
注意
:类模板没有自动类型推导的使用方式,要指定类型
3.2 类模板与函数模板的区别
区别 :
a.
类模板没有自动类型推导的使用方式,只能用显示指定类型方式
b.
类模板在模板参数列表中可以有默认参数
示例:
cpp
//有默认参数的类模板
template<class T1,class T2=int>
class Person{
public:
Person(T1 name,T2 age){
this->mName = name;
this->mAge = age;
}
void showPerson(){
cout<< "name:" << this->mName << "age:" << this->mAge <<endl;
}
public:
T1 mName;
T2 mAge;
};
//使用
int main()
{
Person<string>p("猪八戒",999);//类模板中的模板参数列表可以指定默认参数
//Person<string,int>p("孙悟空",1000);//必须使用显示指定类型的方式,使用类模板
p.showPerson();
system("pause");
return 0;
}
3.3 类模板中成员函数的的创建时机
类模板中成员函数和普通类中成员函数创建时机的区别:
a.
普通类中的成员函数一开始就可以创建
b.
类模板中的成员函数在调用时才创建
示例:
总结:类模板中的成员函数并不是一开始就创建的,在调用时才去创建
3.4 类模板对象做函数参数
使用 :类模板实例化出的对象,向函数传参的方式
一共有三种传入方式 :
a.
指定传入的类型 --- 直接显示对象的数据类型
b.
参数模板化 --- 将对象中的参数变为模板进行传递
c.
整个类模板化 --- 将这个对象类型模板化进行传递
示例:
总结 :
1.
通过类模板创建的对象,可以有三种方式向函数中进行传参
2.
使用比较广泛是第一种:指定传入的类型
3.5 类模板与继承
当类模板碰到继承时,需要注意一下几点 :
a.
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
b.
如果不指定,编译器无法给子类分配内存
c.
如果想灵活指定出父类中T的类型,子类也需变为类模板
示例:
总结:如果父类是类模板,子类需要指定出父类中T的数据类型
3.6 类模板成员函数类外实现
学习目标 :能够掌握类模板中的成员函数类外实现
示例:
总结:类模板中成员函数类外实现时,需要加上模板参数列表
3.7 类模板分文件编写
问题
:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决
:
解决方式1
:直接包含.cpp源文件
解决方式2
:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
示例:
总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp
3.8 类模板与友元
类模板配合友元函数的类内和类外实现 :
a.
全局函数类内实现 - 直接在类内声明友元即可
b.
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
示例:
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别