在做线性表工具之前,先明确何为线性表。
线性表性质:
1.多个数据,有且仅有一个数据没有前件;
2.多个数据,有且仅有一个数据没有后件;
3.其他数据,有且仅有一个前件和一个后件;
4.在任意删除或增加一个数据后,剩余数据仍然满足以上三个条件。
线性表的表示:
*1.可以用线性存储结构表示: 数组
2.可以用非线性存储结构表示:链表 *
在这里我们使用数组实现。
但是类似于int arr[10],是一个线性表吗?
答案是 是,但并不具有工具性。 所谓工具性是指,能处理各种类型的数据,不能只处理int类型的。
工具性也指,应该提供一整套数据结构(数据类型)和操作(函数),使得在使用这个工具时,不再额外编写关于工具的整理、规范等等有关工具管理的代码。
线性表工具实现的分析:
1.这个线性表应该有一段数据存储空间
2.这个空间应该存在一系列有关这个空间的管理的数据
比如:空间容量(capacity),有效元素个数(count)。根据capacity和count,可以判断线性表的空和满。
所以我们需要建立一个表头,来控制整个线性表。
以下为linear.h头文件中的内容
#ifndef _TYZ_LINEAR_H_
#define _TYZ_LINEAR_H_
typedef struct LINEAR {
USER_TYPE *data;
int capacity;
int count;
}LINEAR;
#endif
现在我们需要解决USER_TYPE类型的问题,如何实现让用户能存储任何类型的数据呢?
在此我给出第一种方法,下一个方法会在我的堆栈工具中实现。
我们只需要再定义一个开放给用户的头文件,即userType.h,在里面用户可以根据自己的需要而改变USER_TYPE的类型。
以下为userType.h的代码。
#ifndef _USER_TYPE_H_
#define _USER_TYPE_H_
typedef int USER_TYPE; //用户在此可以更改需要的数据类型
#endif
在此我们先将USER_TYPE定义为int类型。
解决了这个问题,我们可以开始着手做线性表工具了。
我们之前定义了一个表头,表头里还有个指向数据的指针,因此我们需要先申请空间给表头及数据,所以第一个函数我们需要写初始化线性表的函数。
#include
#include
#include "userType.h"
#include "tyz.h"
#include "linear.h"
boolean initLinear(LINEAR **head, int capacity) {
if (NULL != *head) {
return FALSE;
}
*head = (LINEAR *) calloc(sizeof(LINEAR),1);
(*head)->capacity = capacity;
(*head)->count = 0;
(*head)->data = (USER_TYPE *) calloc(sizeof(USER_TYPE),capacity);
return TRUE;
}
//boolean本质的类型是unsigned cha,其定义在我编写的tyz.h的头文件中
以下为tyz.h中的代码
#ifndef _TYZ_H_
#define _TYZ_H_
#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1
typedef unsigned char boolean;
int skipBlank(const char *str);
boolean isRealStart(int ch);
#endif
如果 *head == NULL,说明该线性表没有被初始化过,这个判断是为了防止用户对同一个线性表进行重复初始化,造成内存泄漏。我们编写工具的时候,一定要把用户当成敌人,尽可能考虑到所有他有可能摧毁我们程序的操作。
编写完申请空间,接下来一定要考虑释放空间,内存泄漏是编程的大忌。
以下为销毁线性表的函数,即释放所申请的空间。
void destoryLinear(LINEAR **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->data);
free(*head);
*head = NULL;
}
销毁函数不需要知道它有没有成功,所以返回值类型用void.
现在我们从最简单的开始,先编写判断线性表空与满的函数。我们之前分析的时候说过,要判断空和满,需要根据capacity和count来判断,所以在判断之前,我们需要得到线性表的capacity和count。
int getCapacity(LINEAR *head) {
return head->capacity;
}
int getCount(LINEAR *head) {
return head->count;
}
得到了capacity和count,我们可以进行空和满的判断了。
boolean isLinearFull(LINEAR *head) {
return head->count >= head->capacity;
}
boolean isLinearEmpty(LINEAR *head) {
return head->count <= 0;
}
接下来我们需要完善插入、修改、追加、删除等基本操作,我们先完成修改操作。
boolean setElementAt(LINEAR *head, int index, USER_TYPE data) {
if (NULL == head || index < 0 || index >= head->count) {
return FALSE;
}
head->data[index] = data;
return TRUE;
}
修改的函数便完成了。在完成其他操作之前,我们可以先做一个将元素取出的函数。
boolean getElement(LINEAR *head, int index, USER_TYPE *data) {
if (NULL == head || index < 0 || index >= head->count) {
return FALSE;
}
*data = head->data[index];
return TRUE;
}
接下来需要完成插入的函数。
boolean insertElementAt(LINEAR *head, int index, USER_TYPE data) {
int i;
if (NULL == head || isLinearFull(head)
|| index < 0 || index >= head->count) {
return FALSE;
}
for (i = head->count; i > index; i--) {
head->data[i] = head->data[i - 1];
}
head->data[index] = data;
++head->count;
return TRUE;
}
只有线性表没有满的时候,才能插入元素,需要注意这个判断。插入元素后,一定要记得将count++,否则访问会出错。
完成了插入的函数,末尾追加的函数就很简单了。
boolean appendElement(LINEAR *head, USER_TYPE data) {
return insertElementAt(head, head->count, data);
}
最后进行删除函数的编写。
boolean removeElement(LINEAR *head, int index, USER_TYPE *data) {
if (NULL == head || isLinearEmpty(head)
|| index < 0 || index >= head->count) {
return FALSE;
}
*data = head->data[index];
for (; index < head->count; index++) {
head->data[index] = head->data[index + 1];
}
head->count--;
return TRUE;
}
*data可以将要删除的元素保存,让用户知道删除了哪个元素。
接下来还需要一个清空线性表的功能,即用户想重新录入数据,之前的数据不需要了,此时需要一个清空的功能。
void clearLinear(LINEAR *head) {
head->count = 0;
}
我们只需要将count变成0,就已经完成了。之前的数据会变成垃圾数据被覆盖,不会再访问到。这同时也是其他电子设备删除信息的原理,你的信息并没有被移除,只是你无法访问到,它们变成了垃圾数据而已。
最后,我们需要一个将元素下标取出的函数。这个函数将会引出一个重要的知识点,即指向函数的指针。
为什么需要指向函数的指针呢?
在开始我们用USER_TYPE定义了数据类型,并且这个数据类型是由用户决定的,我们并不知道它是什么类型的数据,所以我们用的比较元素的方法可能会造成语法错误。在找出我们想要的元素的下标时,我们需要将两个数据进行相等比较,但是数组和数组,结构体和结构体单纯的比较都可能会造成错误,所以我们是无法进行元素的相等比较的。
由此我们可以定义一个指向函数的指针,这个指针指向用户定义的相等比较的函数,也就是这个指针是指向未来的。
以下为提取元素下标的函数
int getIndexOfElement(LINEAR *head, USER_TYPE data,
boolean (*eq)(USER_TYPE,USER_TYPE)) {
int index;
if (NULL == head || isLinearEmpty(head)) {
return NOT_FOUND;
}
for (index = 0; index < count; index++) {
if (eq(head->data[index],data)) {
return index;
}
}
return NOT_FOUND;
}
其中,boolean (*eq)(USER_TYPE,USER_TYPE) 定义了一个指向函数的指针,即eq。函数的名称本质上其实是一个指令的首地址,在我用汇编代码分析函数调用的博客中会详细讲解。
eq指向了一个返回值为boolean, 参数类型为(USER_TYPE,USER_TYPE)的函数,该函数将由使用该工具的用户进行编写,根据用户需要的数据类型,编写适用的函数。用户定义的函数返回值类型必须是boolean,参数必须是(USER_TYPE,USER_TYPE)。
关于eq,可以这样理解: boolean((USER_TYPE,USER_TYPE)) *eq
我们进行函数声明的时候,格式是 返回值类型 函数名称(形参),比如
void clearLinear(LINEAR *head) ,定义了一个函数名称为clearLinear,返回值类型为void,参数为(LINEAR *head)的函数,根据这个推导,可以更好地理解指向函数的指针。
线性表的完整代码如下:
1.linear.h
#ifndef _TYZ_LINEAR_H_
#define _TYZ_LINEAR_H_
#include "tyz.h"
#include "userType.h"
typedef struct LINEAR {
USER_TYPE *data;
int capacity;
int count;
}LINEAR;
boolean initLinear(LINEAR **head, int capacity);
void destoryLinear(LINEAR **head);
int getCapacity(LINEAR *head);
int getCount(LINEAR *head);
boolean isLinearFull(LINEAR *head);
boolean isLinearEmpty(LINEAR *head);
boolean setElementAt(LINEAR *head, int index, USER_TYPE data);
boolean getElement(LINEAR *head, int index, USER_TYPE *data);
boolean insertElementAt(LINEAR *head, int index, USER_TYPE data);
boolean appendElement(LINEAR *head, USER_TYPE data);
boolean removeElement(LINEAR *head, int index, USER_TYPE *data);
void clearLinear(LINEAR *head);
int getIndexOfElement(LINEAR *head, USER_TYPE data,
boolean (*eq)(USER_TYPE,USER_TYPE));
#endif
2.userType.h
#ifndef _USER_TYPE_H_
#define _USER_TYPE_H_
typedef int USER_TYPE; //用户在此可以更改需要的数据类型
#endif
3.tyz.h
#ifndef _TYZ_H_
#define _TYZ_H_
#define TRUE 1
#define FALSE 0
#define NOT_FOUND -1
typedef unsigned char boolean;
int skipBlank(const char *str);
boolean isRealStart(int ch);
#endif
4.linear.c
#include
#include
#include "userType.h"
#include "tyz.h"
#include "linear.h"
boolean initLinear(LINEAR **head, int capacity) {
if (NULL != *head) {
return FALSE;
}
*head = (LINEAR *) calloc(sizeof(LINEAR),1);
(*head)->capacity = capacity;
(*head)->count = 0;
(*head)->data = (USER_TYPE *) calloc(sizeof(USER_TYPE),capacity);
return TRUE;
}
void destoryLinear(LINEAR **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->data);
free(*head);
*head = NULL;
}
int getCapacity(LINEAR *head) {
return head->capacity;
}
int getCount(LINEAR *head) {
return head->count;
}
boolean isLinearFull(LINEAR *head) {
return head->count >= head->capacity;
}
boolean isLinearEmpty(LINEAR *head) {
return head->count <= 0;
}
boolean setElementAt(LINEAR *head, int index, USER_TYPE data) {
if (NULL == head || index < 0 || index >= head->count) {
return FALSE;
}
head->data[index] = data;
return TRUE;
}
boolean getElement(LINEAR *head, int index, USER_TYPE *data) {
if (NULL == head || index < 0 || index >= head->count) {
return FALSE;
}
*data = head->data[index];
return TRUE;
}
boolean insertElementAt(LINEAR *head, int index, USER_TYPE data) {
int i;
if (NULL == head || isLinearFull(head)
|| index < 0 || index >= head->count) {
return FALSE;
}
for (i = head->count; i > index; i--) {
head->data[i] = head->data[i - 1];
}
head->data[index] = data;
++head->count;
return TRUE;
}
boolean appendElement(LINEAR *head, USER_TYPE data) {
return insertElementAt(head, head->count, data);
}
boolean removeElement(LINEAR *head, int index, USER_TYPE *data) {
if (NULL == head || isLinearEmpty(head)
|| index < 0 || index >= head->count) {
return FALSE;
}
*data = head->data[index];
for (; index < head->count; index++) {
head->data[index] = head->data[index + 1];
}
head->count--;
return TRUE;
}
void clearLinear(LINEAR *head) {
head->count = 0;
}
int getIndexOfElement(LINEAR *head, USER_TYPE data,
boolean (*eq)(USER_TYPE,USER_TYPE)) {
int index;
if (NULL == head || isLinearEmpty(head)) {
return NOT_FOUND;
}
for (index = 0; index < count; index++) {
if (eq(head->data[index],data)) {
return index;
}
}
return NOT_FOUND;
}
当用户使用时,我们只需要提供给用户linear.h,userType.h,tyz.h三个头文件以及编译后的linear.o文件,用户就可以使用了,不用担心我们的工具代码会被用户修改。
经PowerShell编译验证,无错误。