函数模板和类模板

模板可以实现参数多态性,它对程序处理的对象类型进行参数化,使得单个程序能够处理多种不同类型的对象。

函数模板

函数模板的目的是为了避免重复编写仅在数据类型上有所不同的函数,从而大大提高代码的可重用性,增强软件开发效率。

语法形式是在函数定义前添加语句 template<模板参数列表>

模板参数列表的内容:

  • 类型参数:class(或 typename)标识符
  • 常量参数:类型说明符标识符
  • 模板参数:template<参数列表> class 标识符

示例:

1
2
3
4
5
//通用绝对值函数
template<typename T>
T abs(T x){
return x>0 ? x : -x;
}

函数模板与函数在本质上是不同的

  1. 函数模板本身不会生成任何对象代码;只有函数模板的实例才会生成对象代码。
  2. 被多个源文件引用的函数模板需要与函数体一起放在头文件中,而不仅仅是像普通函数那样的声明。
  3. 函数指针只能指向函数模板的实例,不能指向函数模板

类模板

使用类模板可以定义类的模式,使得某些数据成员、某些成员函数的参数、返回值或类中的局部变量可以采用任意类型。

类模板的声明方式与函数模板相同,

1
2
3
4
5
6
7
8
9
//在类模板外定义成员函数
template<模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)
{
...
}

//使用模板类定义对象
模板名<模版参数类型>对象名1,...,对象名n;

线性组

线性组顾名思义,其元素的位置对应于它们的相对关系。它们可以分为直接访问、顺序访问和索引访问。直接访问意味着可以直接跳转到需要访问的位置,而不需要遵循顺序,而顺序访问只能根据元素排列顺序从头开始访问。

直接访问组 - 数组类

需要设计一个可变长度的数组,点击查看源代码,并列出一些需要注意的语法要点。

语法规则规定“=”、“[ ]”、“( )”、“->”只能作为成员函数进行重载,而派生类中的“=”运算符函数将始终隐藏基类中的“=”运算符。

如果我们想在程序中像普通数组一样使用 Array 类对象,我们需要重载指针转换运算符。

指针转换运算符的作用

为了说明重载指针转换运算符的必要性,先看以下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using namespace std;
void read (int *p, int n)
{
for (int i=0;i<n;i++)
cin>>p[i];
}

int main()
{
int a[10];
read(a,10);
return 0;
}

这里,read 函数的第一个参数是一个 int 指针,而数组名 a 是一个 int 地址常量,因此类型完全匹配。如果我们想在程序中像普通数组一样使用 Array 类对象,可以将 main 函数修改如下:

1
2
3
4
5
6
int main ()
{
Arrays<int> a(10);
read(a,10);
return 0;
}

会发生什么呢?这次调用 read 时,会发现实际参数类型与形式参数类型不同。此时,编译系统会尝试进行自动类型转换:将对象名转换为 int * 类型。由于 a 是一个自定义类型对象,编译系统提供的自动转换函数无法实现这种转换,所以我们需要编写自己的重载指针类型转换函数。 在 C++ 中,如果你想隐式或显式地将自定义类型 T 的对象转换为类型 S,可以将 operator S 定义为 T 的成员函数。这样,在隐式将 T 类型对象转换为 S 类型,或使用 static_cast 显式转换为 S 类型时,将调用该成员函数。转换运算符的重载函数不需要指定返回值类型,因为在这种情况下重载函数的返回类型与运算符名称一致,因此 C++ 标准规定此类函数不能指定返回值类型(也不应写为 void)。 当对象本身是常量时,为了避免通过指针修改数组内容,对象只能转换为常量指针。

在这个 Array 类中重载指针转换运算符的方式如下:

1
2
3
4
5
template<class T>
Array<T>::operator T *() //不用写返回值的类型
{
return list;//数组的首地址
}

顺序访问组 - 链表类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// LinkedList.h
#ifndef LINKEDLIST_H
#define LINKEDLIST_H
#include "Node.h"
template <class T>
class LinkedList
{
private:
Node<T> *front, *rear; //头尾指针
Node<T> *prevPtr, *currPtr; //记录当前遍历位置的指针,通过插入和删除操作更新
int size; //链表中的元素数量
int position; //当前元素在链表中的位置索引。由 reset 函数使用
//生成带有数据域 item 和指针域 ptrNext 的新节点
Node<T> *newNode(const T &item, Node<T> *ptrNext = NULL);
void freeNode(Node<T> *p); //释放节点
//将链表 L 复制到当前链表(假设当前链表为空),由拷贝构造函数和运算符 = 调用
void copy(const LinkedList<T> &L);

public:
LinkedList(); //构造函数
LinkedList(const LinkedList<T> &L); //拷贝构造函数
~LinkedList(); //析构函数
LinkedList<T> &operator=(const LinkedList<T> &L); //重载赋值运算符
int getSize() const; //返回链表中的元素数量
bool isEmpty() const; //链表是否为空
void reset(int pos = 0); //初始化游标位置
void next(); //游标移动到下一个节点
bool endOfList() const; //游标是否到达链表末尾
int currentPosition() const; //返回当前游标位置
void insertFront(const T &item); //在头部插入节点
void insertRear(const T &item); //在尾部添加节点
void insertAt(const T &item); //在当前节点之前插入节点
void insertAfter(const T &item); //在当前节点之后插入节点
T deleteFront(); //删除头节点
void deleteCurrent(); //删除当前节点
T &data(); //返回对当前节点成员数据的引用
const T &data() const; //返回对当前节点成员数据的常量引用
//清空链表:释放所有节点的内存空间。由析构函数和运算符=调用
void clear();
}
#endif // LINKEDLIST_H
//链表类模板函数实现代码可以从网上下载

链表的基本操作:

  • 生成链表
  • 插入节点
  • 查找节点
  • 删除节点
  • 遍历链表
  • 清空链表

栈类

栈类模板实现点击查看源代码

栈元素实际上可以使用数组链表来表示。

栈的基本操作:

  • 初始化
  • 压栈
  • 弹栈
  • 清空栈
  • 访问栈顶元素
  • 检查栈状态(满、空)

队列类

设计为使用数组的循环队列,这在添加和删除时相当麻烦。实际上,我认为使用链表就不需要循环实现。

点击查看源代码

组数组的组织

简单的排序和搜索相当基础,因此这里插入一些图片和链接以便更好地理解。

I wrote a poem about binary search - labuladong

综合示例 - 个人银行账户管理程序的改进

点击这里查看源代码