目录
本阶段主要针对泛型编程和STL技术作详细讲解。
1 模板
1.1 模板的概念
C++提供一种泛型编程,主要利用模板的技术,模板的目的是提高代码复用性,将类型参数化。
C++提供两种模板机制:
- 函数模板
- 类模板
模板的特点:
- 模板不可用直接使用,它只是一个框架;
- 模板的通用表示万能的,也是由使用范围的。
1.2 函数模板
函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,而是用一个虚拟的类型来代表。
语法:
template<typename/class T>
函数
template:声明创建模板
typename/class:表明其后面的符号是一种数据类型
T:通用的数据类型,该名称可以替换,通常为大写字母
1.2.1 函数模板的创建与使用
语法:
//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型
template<typename T>
//模板使用的两种方式:1.自动类型推导;2.显示指定类型
//1.自动类型推导
mySwap(a,b);//2.显示指定类型
mySwap<int>(a,b);
码如下:
#include <iostream>
using 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;
}
//函数模板
template<typename T>//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型
void mySwap(T &a, T &b)
{
T temp=a;
a=b;
b=temp;
}
void test01()
{
int a=4;
int b=10;
//swapInt(a,b);
//利用模板交换,两种方式:1.自动类型推导;2.显示指定类型
//1.自动类型推导
///mySwap(a,b);
//2.显示指定类型
mySwap<int>(a,b);
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
double c=4.66;
double d=3.22;
swapDouble(c,d);
//mySwap(a,c);//错误示例1:自动类型推导,必须推导出一致的数据类型T才能使用
cout<<"c="<<c<<endl;
cout<<"d="<<d<<endl;
}
template<class T>
void func()
{
cout<<"this is func()"<<endl;
}
void test02()
{
//func();//错误示例2:模板必须确定T的数据类型才能使用
}
int main()
{
test01();
test02();
return 0;
}
注意事项:
- 自动类型推导,必须推导出一致的数据类型T才能使用
- 模板必须确定T的数据类型才能使用
错误示例1:
错误示例2:
1.2.2 函数模板案例
案例描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则为从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
代码如下:
#include <iostream>
using namespace std;
//函数模板案例:对不同数据类型的数组进行从大到小的选择排序
template<class T>
void mySort(T buff[],int len)
{
for(int i=0;i<len;i++)
{
int max=i;
for(int j=i+1;j<len;j++)
{
if(buff[j]>buff[max])
{
max=j;
}
}
if(max!=i)
{
T temp=buff[i];
buff[i]=buff[max];
buff[max]=temp;
}
}
}
template<class T>
void printArry(T buff,int len)
{
for(int i=0;i<len;i++)
{
cout<<buff[i]<<endl;
}
}
//测试char数组
void test01()
{
char arr[]="abcdef";
int len=sizeof(arr)/sizeof(char);
mySort(arr,len);
printArry(arr,len);
}
//测试int数组
void test02()
{
int arr[]={2,4,6,8,3,1};
int len=sizeof(arr)/sizeof(int);
mySort(arr,len);
printArry(arr,len);
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
写错的地方:
1.选择排序比较两个数的大小是用第j个和最大的数比较if(buff[j]>buff[max]),而不是和第i个比较if(buff[j]>buff[i])。
2.计算数组长度时忘记除以数据类型的长度sizeof(arr)/sizeof(int)。
考究的地方:
向函数传递数组时似乎可以只写数组名void printArry(T buff,int len),而不一定要写中括号void mySort(T buff[ ],int len)。
1.2.3 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 函数模板调用时,如果利用显示指定类型,可以发生隐式类型转换
建议:使用显式指定类型调用函数模板
代码如下:
#include <iostream>
using namespace std;
//普通函数调用时可以发生自动类型转换(隐式类型转换)
//函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
//如果利用显示指定类型的方式,可以发生隐式类型转换
//普通函数
int myadd(int a,int b)
{
return a+b;
}
template<class T>
T myadd2(T a,T b)
{
return a+b;
}
void test01()
{
int a=3;
int b=4;
char c='c';
cout<<myadd(a,b)<<endl;
//普通函数调用时可以发生自动类型转换(隐式类型转换)
cout<<myadd(a,c)<<endl;//将字符C隐式地转换为了整形
cout<<myadd2(a,b)<<endl;
//函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
//cout<<myadd2(a,c)<<endl;
//如果利用显示指定类型的方式,可以发生隐式类型转换
cout<<myadd2<int>(a,c)<<endl;
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:调用函数模板时,使用自动类型推导不会发生隐式类型转换
1.2.4 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
代码如下:
#include <iostream>
using namespace std;
//如果函数模板和普通函数都可以实现,优先调用普通函数
//可以通过空模板参数列表来强制调用函数模板
//函数模板可以发生重载
//如果函数模板可以产生更好的匹配,优先调用函数模板
//普通函数
void myprint(int a,int b)
{
cout<<"普通函数"<<endl;
}
template<class T>
void myprint(T a,T b)//函数重载
{
cout<<"函数模板"<<endl;
}
template<class T>
void myprint(T a,T b,T c)//函数模板重载
{
cout<<"函数模板重载"<<endl;
}
void test01()
{
int a=3;
int b=4;
//如果函数模板和普通函数都可以实现,优先调用普通函数
myprint(a,b);
//可以通过空模板参数列表来强制调用函数模板
myprint<>(a,b);
//函数模板可以发生重载
myprint(a,b,18);
//如果函数模板可以产生 更好的匹配 ,优先调用函数模板
char c1='a';
char c2='b';
myprint(c1,c2);
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:普通函数没有实现,只有定义,调用时仍然不会调用函数模板且报错。
1.2.5 模板的局限性
模板的通用性表示万能的。
template<class T>
void f(T a, T b)
{
a=b;}
例1:在上述代码中提供的赋值操作,如果传入的a和b是一个数组(数组之间无法赋值),就无法实现。
template<class T>
void f(T a, T b)
{
if(a>b){...}
}
例2:在上述代码中,如果T的数据类型传入的是像person这样自定义的数据类型(自定义的数据类型不知道如何比较),也无法正常运行。
解法1:运算符重载
解法2:对自定义的数据类型提供一个具体的版本的函数模板
因此,C++提供模板的重载,为特定的类型提供具体化的模板,来解决上述问题。
代码如下:
#include <iostream>
using namespace std;
#include <string>
//自定义数据类型
class Person
{
public:
Person(string name,int age)
{
this->m_Age=age;
this->m_Name=name;
}
string m_Name;
int m_Age;
};
//对比两个数是否相等的函数
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;
}
}
//为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 ret=mycompare(p1,p2);
if(ret)
{
cout<<"p1=p2"<<endl;
}
else
{
cout<<"p1!=p2"<<endl;
}
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
1.3 类模板
1.3.1 类模板基本语法
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体指定,而是用一个虚拟的类型代表。
语法:
template<typename T>
类
代码如下:
#include <iostream>
using namespace std;
//类模板
template <class NameType,class AgeType>
class Person
{
public:
Person(NameType name,AgeType age)
{
this->m_Name=name;
this->m_Age=age;
}
void showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
Person<string,int> p1("ddd",22);
p1.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
1.3.2 类模板成员函数创建时机
- 普通类:成员函数一开始就可以创建
- 类模板:成员函数在调用时创建
代码如下:
#include <iostream>
using namespace std;
class Person1
{
public:
void showPerson1()
{
cout<<"Person1"<<endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout<<"Person2"<<endl;
}
};
template<class T>
class MyClass
{
public:
T obj;
//类模板的成员函数
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
void test01()
{
MyClass<Person1> m;
m.func1();
//m.func2();//出错
}
int main()
{
test01();
return 0;
}
输出如下:
错误示例:
1.3.3 类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式:
- 指定传入类型:直接显示对象的数据类型(最常用
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将对象模型,模板化 进行传递
代码如下:
#include <iostream>
using namespace std;
//类模板做函数参数
//类模板
template <class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
void showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
T1 m_Name;
T2 m_Age;
};
//指定传入类型:直接显示对象的数据类型
void printPerson1(Person<string,int> &p)
{
p.showPerson();
}
void test01()
{
Person<string,int> p("ddd",23);
printPerson1(p);
}
//参数模板化:将对象中的参数变为模板进行传递
template <class T1,class T2>
void printPerson2(Person<T1,T2> &p)
{
p.showPerson();
cout<<"T1 type:"<<typeid(T1).name()<<endl;
cout<<"T2 type:"<<typeid(T2).name()<<endl;
}
void test02()
{
Person<string,int> p("qqq",24);
printPerson2(p);
}
// 整个类模板化:将对象模型,模板化 进行传递
template <class T>
void printPerson3(T &p)
{
p.showPerson();
cout<<"T type:"<<typeid(T).name()<<endl;
}
void test03()
{
Person<string,int> p("fff",25);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
return 0;
}
输出如下:T出数据类型名打印和学习视频中有些出入,应该识别出class Person,string和int。
1.3.4 类模板与继承
当类模板遇到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型。
- 如果不指定类型,编译器无法为子类分配内存。
- 如果想灵活指出父类中的T的类型,子类也需要变为类模板。
代码如下:
#include <iostream>
using namespace std;
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son:public Base//错误示例:没有指定T的数据类型
class Son:public Base<int>
{
};
void test01()
{
Son s1;
}
template<class T1,class T2>
class Son2:public Base<T2>
{
public:
Son2()
{
cout<<"T1的数据类型为:"<<typeid(T1).name()<<endl;
cout<<"T2的数据类型为:"<<typeid(T2).name()<<endl;
}
T1 obj;
};
void test02()
{
Son2<int,char> s2;
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
错误示例:子类继承类模板父类时没有指定数据类型,所以也无法计算Son类占的内存空间大小
1.3.5 类模板成员函数类外实现
类模板中成员函数类外实现时,需要加上模板参数列表。
代码如下:
#include<iostream>
using namespace std;
//类模板成员函数类外实现
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age);
// {
// this->m_Name=name;
// this->m_Age=age;
// }
void showPerson();
// {
// cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
// }
T1 m_Name;
T2 m_Age;
};
//类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
}
void test()
{
Person<string,int> p("ddd",22);
p.showPerson();
}
int main()
{
test();
return 0;
}
输出如下:
1.3.6 类模板分文件编写
类模板中成员函数在调用阶段创建,导致分文件编写时链接不到。
解决方案1:直接包含.cpp源文件
解决方案2:将声明和实现写到同一个文件中,并更改后缀为.hpp,hpp是约定的名称,并不是强制。
解决方案1代码如下:
1-3-6-person.h
#pragma once
#include <iostream>
using namespace std;
template<class T1,class T2>
class Person
{
public:
Person(T1 name ,T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
1-3-6-person.cpp
#include "1-3-6-person.h"
template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
1-3-6.cpp
#include <iostream>
using namespace std;
#include "1-3-6-person.cpp"//解决方案1:成员函数调用时才创建,.h中没有成员函数实现的代码
//类模板分文件编写
void test01()
{
Person<string,int> p("fff",23);
p.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
解决方案2代码如下:
1-3-6-person.hpp
#pragma once
#include <iostream>
using namespace std;
template<class T1,class T2>
class Person
{
public:
Person(T1 name ,T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}
1-3-6.cpp
#include <iostream>
using namespace std;
#include "1-3-6-person.hpp"//解决方案2:将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件
//类模板分文件编写
void test01()
{
Person<string,int> p("fff",23);
p.showPerson();
}
int main()
{
test01();
return 0;
}
输出如下:
1.3.7 类模板与友元
掌握类模板配合友元函数的类内和类外实现。
- 全局函数类内实现:直接在类内声明友元即可
- 全局函数类外实现:需要提前让编译器知道全局函数的存在
代码如下:
#include <iostream>
using namespace std;
//通过全局函数打印Person信息
//全局函数 类外实现:提前让编译器知道Person类的存在
template<class T1,class T2>
class Person;
//全局函数 类外实现
template<class T1,class T2>
void showPerson2(Person<T1,T2> p)
{
cout<<"类外 name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
}
template<class T1,class T2>
class Person
{
//全局函数 类内实现
friend void showPerson(Person<T1,T2> p)
{
cout<<"name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
}
//全局函数 类外实现
//加空模板参数列表<>(声明是普通函数的声明,实现是函数模板的实现,两套系统不一样,因此声明的时候 增加参数列表变成函数模板的函数声明)
friend void showPerson2<>(Person<T1,T2> p);
public:
Person(T1 name ,T2 age)
{
this->m_Name=name;
this->m_Age=age;
}
private:
T1 m_Name;
T2 m_Age;
};
//全局函数 类内实现
void test01()
{
Person<string,int> p("ddd",23);
showPerson(p);
}
void test02()
{
Person<string,int> p("fff",25);
showPerson2(p);
}
int main()
{
test01();
test02();
return 0;
}
输出如下:
1.3.8 类模板案例
案例描述:实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储·将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
代码如下:
1-3-8-MyArray.hpp
#pragma once
#include <iostream>
using namespace std;
template<class T>
class MyArray
{
public:
//有参构造
MyArray(int capacity)
{
this->m_Capacity=capacity;
this->m_Size=0;
this->pAddress=new T[this->m_Capacity];
cout<<"有参构造"<<endl;
}
//拷贝构造
MyArray(const MyArray& arr)
{
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]=arr.pAddress[i];
}
cout<<"拷贝构造"<<endl;
}
//=运算符重载 防止浅拷贝的问题(堆区重复释放)
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.m_Capacity];
//将arr中的数据拷贝
for(int i=0;i<this->m_Size;i++)
{
this->pAddress[i]=arr.pAddress[i];
}
cout<<"operator="<<endl;
return * this;
}
//尾插法
void PushBack(const T& val)
{
//判断容量
if(this->m_Capacity==this->m_Size)
{
return;
}
this->pAddress[this->m_Size]=val;
this->m_Size++;
}
//尾删法
void PopBack()
{
if(this->m_Size==0)
{
return;
}
this->m_Size--;
}
//通过下标访问:重载[]
T& operator[](int index)
{
return this->pAddress[index];
}
int getCap()
{
return this->m_Capacity;
}
int getSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
if (this->pAddress!=NULL)
{
delete [] this->pAddress;
this->pAddress=NULL;
}
cout<<"析构函数"<<endl;
}
private:
T * pAddress;
int m_Capacity;
int m_Size;
};
1-3-8.cpp
#include <iostream>
using namespace std;
#include "1-3-8-MyArray.hpp"
void printArry(MyArray<int>& arr)
{
for(int i=0;i<arr.getSize();i++)
{
cout<<arr[i]<<endl;
}
}
void test()
{
//测试构造和析构
MyArray<int> ma1(12);
//测试拷贝构造
MyArray<int> ma2(ma1);
MyArray<int> ma3(100);
//测试operate=
ma3=ma1;
}
void test01()
{
MyArray<int> arr1(4);
for(int i=0;i<4;i++)
{
//插入数据
arr1.PushBack(i);
}
cout<<"数组arr1为:"<<endl;
printArry(arr1);
cout<<"数组arr1的容量为:"<<arr1.getCap()<<endl;
cout<<"数组arr1的大小为:"<<arr1.getSize()<<endl;
MyArray<int> arr2(arr1);
cout<<