函数模版与类模版

模板可以实现参数化多态性,就是将程序处理对象的类型参数化,使一段程序能够用于处理多种不同类型的对象。

函数模板

函数模板存在的意义简单来说就是为了避免两个只有处理数据类型不同的函数重复编写,使代码的可重用性大大提高,从而提高软件的开发效率。

语法形式在函数定义前加上语句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
// 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 复制到当前表(假设当前表为空),被复制构造函数、operator = 调用
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; //返回对当前结点成员数据的常引用
//清空链表:释放所有结点的内存空间。被析构函数、operator= 调用
void clear();
}
#endif // LINKEDLIST_H
//链表类模板函数实现代码可以从网上下载

链表的基本操作:

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

栈类

栈类模版的编写点击查看源码

栈的元素其实可以用数组表示也可以用链表表示。

栈的基本操作:

  • 初始化
  • 入栈
  • 出栈
  • 清空栈
  • 访问栈顶元素
  • 检测栈的状态(满、空)

队列类

设计的是循环队列,用的是数组,增减较为的麻烦,其实我觉得用链表实现的话不需要用循环。

点击此处查看源码

群体数组的组织

简单的排序和查找都较为的基础,在此插入一些图片和链接以更好的理解。

我把二分搜索写了一首诗—labuladong

综合实例—对个人银行账户管理程序的改进

查看源码点击此处