我们前面介绍的Stack,Queue和Vector类是抽象数据类型的一般类别的例子,称为线性结构(linear structures),其中元素以线性顺序排列。这个系列讨论了这些类型的几种可能的表示方式,并考虑了表示形式如何影响效率。
因为线性结构中的元素以类似数组的顺序排列,使用数组来表示它们似乎是一个明显的选择。实际上,我们之前中提到的CharStack类是使用数组作为底层表示来实现的。然而,数组不是唯一的选择。栈,队列和vector也可以使用类似于我们上面所说中用于实现buffer的链接来实现。通过研究这些结构的链表实现,你将不仅增加对链表的理解,还有如何将它们应用于实际的编程环境中。
这里我们还有另一个目的。从之前我们可以看出,我们介绍的类与我们口中所说的CharStack类不同 - 因为它不限于单个数据类型。实际的Stack类允许客户端通过提供类型参数来指定值的类型,如Stack 或Stack 。然而,到目前为止,你只有机会使用参数化类型作为客户端,接下来你将学习如何实现它们。
Templates(模板)
在计算机科学中,能够对多种数据类型使用相同的代码称为多态(polymorphism)。 编程语言以多种方式实现多态。 C ++使用一种称为模板(Templates)的方法,其中程序员定义了可以用于许多不同类型的通用代码模式。 我们之前介绍的集合类(stack,vecto,queue)依赖于C ++模板工具,这意味着你需要了解模板的工作原理,才能了解集合类的基本实现。
在详细描述模板之前,我们最好回顾并重新审视重载(overload)的概念,这在函数中有介绍。重载允许你定义具有相同名称的多个函数,只不过这些函数可以通过其参数来区分即可。 给定一个特定的函数调用,编译器可以查看参数的数量和类型,并选择与该函数名相匹配的函数的版本。
这部分内容 详细参看:泛型编程——模板
用模板定义 栈(stack)
我们前面中的CharStack类定义了所有堆栈所需的相关操作,但是由于它只能存储char类型的元素,因此是有限的。为了获得库类的灵活性,有必要重新实现Stack作为一个模板类,它是一个使用C ++模板工具的类,以便可以使用任何数据类型。
将非模板类更改为模板类涉及一些简单的句法更改。 例如,如果要使用通用Stack模板替换我们所说的的CharStack类,则首先用Stack替换名称CharStack,然后在类定义之前添加以下行:
template <typename ValueType>
template关键字表示遵循此行的整个语法单元(在本例中为类定义),可用于ValueType参数的许多不同值的模板模式的一部分。在下面的类定义中,你可以随时使用占位符名称ValueType来指代要存储的元素的类型。 因此,当你将CharStack类定义转换为更一般的模板表单时,可以使用ValueType替换原型中的每个字符,用以pop,push和peek。 更新版本的stack.h界面如下图所示。 如你所看到的其他接口一样,该类的私有部分存储在一个单独的stackpriv.h文件中。
图下图中的stackpriv.h文件包含复制构造函数和赋值运算符的必要定义,以便可以将数据复制到堆栈中。 该版本的stackpriv.h文件将一个堆栈复制到另一个堆栈是非法的。 如我们之前所述,你还可以编写代码以制作堆栈的深层副本,以便复制堆栈不与原始堆栈共享数据。
头文件
/*
*这部分文件实现我们之前所使用的stack类
*它主要的原理为 后进先出(LIFO)
*/
#ifndef _Stack_h
#define _Stack_h
/*
*类型: Stack<ValueType>
*此类建立一个称为堆栈的线性结构,其中仅从一端添加和删除值。
*这个规定产生了一个(LIFO)的行为,它是堆栈的定义特征。
*基本堆栈操作是push(添加元素到顶部)和pop(把元素从顶部删除)。
*/
template <typename ValueType>
class Stack{
public:
/*
*构造函数:Stack
*用法:Stack <ValueType> stack
*-----------------------------
*初始化一个空栈
*/
Stack();
//析构函数
~Stack();
/*
*方法:size()
*用法:int n = stack.size();
*--------------------------
*返回栈中元素的个数
*/
int size();
/*
*方法:isEmpty()
*用法:stack.isEmpty();
*--------------------------
*判断栈中元素是否为空
*/
bool isEmpty();
/*
*方法:clear()
*用法:stack.clear();
*--------------------------
*清空栈中的所有元素
*/
void clear();
/*
*方法:push()
*用法:stack.push();
*--------------------------
*向栈顶推入一个元素
*/
void push(ValueType);
/*
*方法:pop()
*用法:stack.pop();
*--------------------------
*移除栈顶的一个元素,并返回其值,如果栈空 则返回一个错误
*/
ValueType pop();
/*
*方法:peek()
*用法:stack.peek();
*--------------------------
*返回栈顶的值,但是不移除,peek 偷看的意思,如果栈空 则返回一个错误
*/
ValueType peek();
#include "stackpriv.h" //私有成员部分
};
#include "stackimpl.cpp" //将实现文件包含进来
#endif
私有部分
/*
*这部分文件是基于数组实现的文件
*包含了stack的私有部分
*/
private:
const int INITIAL_CAPACITY = 10; //初始化刚刚开始的容量
/*实例化变量*/
ValueType* array; //指向动态数组的指针
int count;
int capacity;
/*私有方法声明*/
void expandCapacity();
/*禁止深层复制*/
Stack(const Stack & value) { }
const Stack & operator=(const Stack & rhs) { return *this; }
实现部分
/*
*这个文件用数组实现我们的栈,简称顺序栈
*/
#ifdef _Stack_h
#include "error.h"
//构造函数
template <typename ValueType>
Stack<ValueType>::Stack() {
capacity = INITIAL_CAPACITY;
array = new ValueType[capacity];
count = 0;
}
//析构函数
template <typename ValueType>
Stack<ValueType>::~Stack() {
delete []array;
}
//计算栈中的元素
template <typename ValueType>
int Stack<ValueType>::size() {
return count;
}
//计算栈是否为空
template <typename ValueType>
bool Stack<ValueType>::isEmpty() {
return count == 0;
}
//清空栈元素
template <typename ValueType>
void Stack<ValueType>::clear() {
count = 0;
}
//将一个元素压入栈中
template <typename ValueType>
void Stack<ValueType>::push(ValueType ch){
if(count == capacity) expandCapacity();
array[count] = ch;
count ++;
//可以简写称为 array[count++] = ch;
}
//将元素弹出栈中
template <typename ValueType>
ValueType Stack<ValueType>::pop() {
if(isEmpty()) error("pop: Attempting to pop an empty stack");
return array[--count];
}
//查看栈顶的元素
template <typename ValueType>
ValueType Stack<ValueType>::peek() {
if(isEmpty()) error("pop: Attempting to peek at an empty stack");
return array[count - 1];
}
//拓展容量
template <typename ValueType>
void Stack<ValueType>::expandCapacity() {
ValueType *oldArray = array;
capacity *= 2;
array = new ValueType[capacity];
for (int i = 0; i < count; i++) {
array[i] = oldArray[i];
}
delete[] oldArray;
}
#endif
对于实现文件,我们有几点要注意:
1. 我们之前的CharStack的实现,主要是这样的形式
void CharStack::push(char ch) {
if (count == capacity) expandCapacity();
elements[count++] = ch;
}
而在这里我们是这样的:
template <typename ValueType>
void Stack<ValueType>::push(ValueType value) {
if (count == capacity) expandCapacity();
elements[count++] = value;
}
其实我们就是多了一行template 。然后把char类型变为了ValueType类型。
2. 在使用模板类的时候,我们有下面的模板:
stackimpl.cpp文件,实现了类的方法,但在逻辑上是类定义的外部。 因此,实现的#include行出现在类定义本身的闭包括号之后。
3. 如果仔细查看stackimpl.cpp中的实现代码,你将看到它包含了与接口文件中看到的样式板相似的样板。 实现文件的全部内容都包含在以下预处理器行中:
这些行确保只有当该文件包含在定义_stack_h符号的stack.h接口中时才执行编译。 如果你尝试自行编译stackimpl.cpp文件,编译器将简单地忽略内容。一些编程环境会自动编译所有的.cpp文件,因此,在不需要时,确保不会编译此代码是很重要的。
3.关于array[count++]的一些讨论。我们可以写个小程序理解:
#include <iostream>
using namespace std;
void dispaly(int array[]);
int main(){
int array[10];
for (int i = 0; i < 10; i++)
{
array[i] = i;
}
cout << "array is" << endl;
dispaly(array);
cout << endl;
int n = 4;
cout << "第五个数为" << endl;
cout << array[4] << endl;
array[n++] = 0; //将第五位赋值为0
cout << "将第五位赋值为0结果为" << endl;
dispaly(array);
cout << endl;
cout<< "此刻的n的值为:" << endl;
cout << n << endl; //但是此刻n的值为5
return 0;
}
void dispaly(int array[]){
for (int k = 0; k < 10; k++)
{
cout << array[k] << " ";
}
}
测试结果: