模板
C++另一种编程思想称为泛型编程,主要利用的技术就是模板
C++提供两种模板机制函数模板和类模板
函数模板语法:
函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
利用函数模板 有两种交换方式
1.自动类型推导 2.显示指定类型
函数模板注意事项
注意事项:
自动类型推导,必须推导出一致的数据类型T,才可以使用
模板函数必须要确定T的数据类型才可以使用
函数模板案例-数组排序
函数模板案例:利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
排序规则从小到大,排序算法为选择排序
分别利用char数组和int数组进行测试
普通函数与函数模板区别
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
总结:建议使用显示指定类型的方式,使用函数模板,因为可以自己确定通用类型T
普通函数与函数模板的调用规则
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以产生更好的匹配 优先调用函数模板
总结:既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性。
模板局限性
模板并不是万能的,有些特定数据类型,需要具体化方式做特殊实现
总结:利用具体化模板,可以解决自定义类型的通用化,学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。
类模板语法
类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
template <class T>
类
总结:类模板和函数模板语法相似,在声明模板template后面加类,此类为类模板。
类模板与函数模板区别
类模板与函数模板区别主要有两点:
1.类模板没有自动类型推导的使用方式
2.类模板在模板参数列表中可以有默认参数
总结:类模板使用只能用显示指定类型方式 类模板中的模板参数列表可以有默认参数
类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
总结:类模板中的成员函数并不是一开始就创建,在调用时才会去创建
类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
1.指定传入的类型--直接显示对象的数据类型
2.参数模板化--将对象中的参数变为模板进行传递
3.整个类模板化--将这个对象类型 模板化进行传递
总结:通过类模板创建的对象,可以有三种方式向函数进行传参,使用比较广泛是第一种:指定传入类型。
类模板与继承
当类模板碰到继承时,需要注意以下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型。
如果不指定,编译器无法分配内存给子类
如果想灵活指定出父类中T的类型,子类也需要变为类模板
总结:如果父类是类模板,子类需要指定出父类中T的数据类型
类模板成员函数类外实现
学习目标:能够掌握类模板中的成员函数类外实现
类模板分文件编写
学习目标:掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改辍名为.hpp,hpp是约定的名称,并不是强制。
对上个案例进行编写
解决方式1代码:
解决方式2代码:
总结:主流方法是第二种,将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件。
类模板与友元
学习目标:掌握类模板配合友元函数的类内和类外实现
全局函数类内实现-直接在类内声明友元即可
全局函数类外实现-需要提前让编译器知道全局函数的存在
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
类模板案例:实现一个通用的数组类
案例描述:实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数中可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删除法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组的容量
类模板案例分析:
MyArray.hpp
#pragma once
#include <iostream>
using namespace std;
template <class T>
class MyArray
{
public:
//有参构造函数
MyArray(int Capacity) {
//cout<<"有参构造函数被创建!" << endl;
m_Capacity = Capacity;
m_size = 0;
pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray &arr) {//const只拷贝,不修改
//cout<<"拷贝构造函数被创建" << endl;
this->m_size = arr.m_size;
this->m_Capacity = arr.m_Capacity;
this->pAddress=new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0;i<this->m_size;i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
//operator= 防止浅拷贝问题 a=b=c
MyArray& operator=(const MyArray& arr) {
//cout << "operator=重载函数被创建" << endl;
if (this->pAddress != NULL) {
delete[]this->pAddress;
this->pAddress = NULL;
this->m_size = 0;
this->m_Capacity = 0;
}
//深拷贝操作
this->m_size = arr.m_size;
this->m_Capacity = arr.m_Capacity;
this->pAddress = new T[arr.m_Capacity];
for (int i = 0; i < this->m_size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//析构函数
~MyArray() {
//cout << "析构函数被创建!" << endl;
if (pAddress != NULL) {
delete[]this->pAddress;
this->m_Capacity = NULL;
}
}
//尾插法:
void push_back(const T &val) {
if (this->m_size==this->m_Capacity) {//判断容量是否等于大小
return;
}
this->pAddress[this->m_size] = val;//在数组尾部插入数据
this->m_size++;//更新数组大小
}
//尾删法:
void pop_dele() {
//让用户访问不到最后一个元素,即为尾删,逻辑删除。
if (this->m_size == 0) {
return;
}
this->m_size--;
}
//返回下标中的元素
T& operator[](int index) {
return this->pAddress[index];
}
//返回数组大小
int getSize() {
return this->m_size;
}
//返回数组容量
int getCapacity() {
return this->m_Capacity;
}
private:
T* pAddress;//指针指向堆区开辟的真实数组
int m_Capacity;//容量
int m_size;
};
类模板案例.cpp
#include <iostream>
using namespace std;
#include "MyArray.hpp"
/*案例描述:实现一个通用的数组类,要求如下:
* 可以对内置数据类型以及自定义数据类型的数据进行存储
* 将数组中的数据存储到堆区
* 构造函数中可以传入数组的容量
* 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
* 提供尾插法和尾删除法对数组中的数据进行增加和删除
* 可以通过下标的方式访问数组中的元素
* 可以获取数组中当前元素个数和数组的容量
*/
void printarr1(MyArray<int> &arr) {
for (int i = 0;i<arr.getSize();i++) {
cout<<arr[i]<<" ";
}
cout<<endl;
}
//内置数据类型定义
void test01() {
MyArray<int> arr1(5);
//利用尾插法进行数据定义:
for (int i = 0;i<5;i++) {
arr1.push_back(i);
}
cout<<"arr1数组中的数据元素有:" << endl;
printarr1(arr1);
cout << "arr1数组中的容量为:" <<arr1.getCapacity() << endl;
cout << "arr1数组中的大小为:" << arr1.getSize() << endl;
MyArray<int> arr2(arr1);
cout << "arr2数组中的数据元素有:" << endl;
printarr1(arr2);
cout << "对arr2数组中的数据元素进行尾删" << endl;
arr2.pop_dele();
cout << "arr2数组中的数据元素有:" << endl;
printarr1(arr2);
cout << "arr2数组中的容量:" << arr2.getCapacity()<<endl;
cout << "arr2数组中的大小:" << arr2.getSize()<<endl;
/*MyArray<int> arr2(arr1);
MyArray<int> arr3(100);
arr3 = arr1;*/
}
//利用自定义数据类型
class person {
public:
person() {};
person(string name,int age) {
m_name = name;
m_age = age;
}
string m_name;
int m_age;
};
void printarr2(MyArray<person>& arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << "name:" << arr[i].m_name << " age: " << arr[i].m_age << endl;
}
cout << endl;
}
void test02() {
MyArray<person> arr(10);
person p1("兰陵王", 18);
person p2("孙悟空", 19);
person p3("司马懿", 20);
person p4("王昭君", 21);
person p5("鲁班", 16);
cout<<"王者五个英雄信息:" << endl;
arr.push_back(p5);
arr.push_back(p4);
arr.push_back(p3);
arr.push_back(p2);
arr.push_back(p1);
printarr2(arr);
cout<<"数组容量为:"<<arr.getCapacity() << endl;
cout << "数组长度为:" << arr.getSize() << endl;
cout<<"进行尾删后为:" << endl;
arr.pop_dele();
printarr2(arr);
cout << "数组容量为:" << arr.getCapacity() << endl;
cout << "数组长度为:" << arr.getSize() << endl;
}
int main() {
//test01();
test02();
return 0;
}