概要
这个实验有几个点需要重新学一下,知识点还是模糊的:
- git仓库的使用和相应命令(基本操作还是看了就忘)
- Linux命令行指令(总是记总是忘,有必要日常操作换成终端)
- 数据结构的单链表的实现
底层数据结构
csapp-clab目录下文件queue.h定义的结构如下:
/* Linked list element */
typedef struct ELE {
int value;
struct ELE *next;
} list_ele_t;
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
} queue_t;
如Figure 1所示,代码要实现一个队列。队列的顶层表示是类型为queue_t的结构,在最初代码中,这个结构只包含一个字段“head”,你需要自行添加其他字段。队列被表示为单链表,其中每个元素由结构list_ele_t表示,该结构包含字段“value”和“next”,分别存储队列值和一个指向下一个元素的指针。尽管可以在不更改list_ele_t的情况下实现本实验要求的所有函数,但欢迎你对list_ele_t进行更改,例如将单链表转换为双链表。
翻译成人话:
看代码可以知道现在队列结构只有一个头节点,题目让我们自己添加别的节点,并且现在的元素节点结构中包含value
和*next
剩下的自己更改把他改成双链表(如果可以的话)
如图所示
在我们给你的C代码中,队列是一个类型为queue_t *的指针。
我们区分两种特殊情况:一个NULL队列是指针设置为NULL的队列。 而一个空(empty)队列是一个指向一个有效的queue_t结构的队列,该queue_t结构的head字段被设置为NULL。除了能正确处理包含一个或多个元素的队列,你的代码还要能正确处理这两种情况。
(保证程序的健壮性)
编程任务
你的任务是修改queue.h和queue.c中的代码以实现以下功能:
- q_new:创建一个新的空队列
- q_free: 释放队列使用的所有存储空间
- q_insert_head:在队列的头部插入新元素(LIFO)
- q_insert_tail:在队列的尾部插入新元素(FIFO)
- q_remove_head:从队列的头部删除一个元素
- q_size: 计算队列中元素的个数
- q_reverse:对列表重新排列,使得队列中元素的顺序都反转过来
就是完成增删改查和其他返回队列基本性质的操作
更多细节可以在这两个文件的注释中找到,包括如何处理无效操作(例如,从空队列或NULL队列中移除元素),以及函数应该具有哪些副作用和返回值。
以下是关于如何实现这些功能的一些重要注意事项:
- 当q_new和q_insert_head需要动态分配内存时,使用
malloc
或calloc
来实现(如果不熟悉这两个函数,可以在命令行中输入man malloc或man calloc来查阅手册;或者搜索引擎之); - 其中q_insert_tail 和 q_size需要满足时间复杂度为O(1),即所需的时间与队列大小无关。你可以通过在queue_t数据结构中添加其他字段来实现此功能;
- q_reverse的实现不需要分配额外的内存。你的代码应该修改现有链表中的指针。 在实现反向操作时,直接或间接调用malloc、calloc或free都视为错误;
- 测试代码会在超过1,000,000个元素的队列上对你的程序进行测试,所以不能使用递归函数来遍历这样的长列表,因为这需要太多的栈空间。(后续课程中,我们将深入学习这背后的原理,cool吧?)
queue.h
/*
* Code for basic C skills diagnostic.
* Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017
*/
/*
* This program implements a queue supporting both FIFO and LIFO
* operations.
*
* It uses a singly-linked list to represent the set of queue elements
*/
#include <stdbool.h>
/************** Data structure declarations ****************/
/* Linked list element (You shouldn't need to change this) */
typedef struct ELE {
int value;
struct ELE *next;
} list_ele_t;
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
unsigned size;
} queue_t;
/************** Operations on queue ************************/
/*
Create empty queue.
Return NULL if could not allocate space.
*/
queue_t *q_new();
/*
Free all storage used by queue.
No effect if q is NULL
*/
void q_free(queue_t *q);
/*
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
*/
bool q_insert_head(queue_t *q, int v);
/*
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
*/
bool q_insert_tail(queue_t *q, int v);
/*
Attempt to remove element from head of queue.
Return true if successful.
Return false if queue is NULL or empty.
If vp non-NULL and element removed, store removed value at *vp.
Any unused storage should be freed
*/
bool q_remove_head(queue_t *q, int *vp);
/*
Return number of elements in queue.
Return 0 if q is NULL or empty
*/
int q_size(queue_t *q);
/*
Reverse elements in queue
No effect if q is NULL or empty
*/
void q_reverse(queue_t *q);
头文件的作用:.h中一般放的是同名.c文件中定义的变量、数组、函数的声明,需要让.c外部使用的声明。
queue.c
/*
* Code for basic C skills diagnostic.
* Developed for courses 15-213/18-213/15-513 by R. E. Bryant, 2017
*/
/*
* This program implements a queue supporting both FIFO and LIFO
* operations.
*
* It uses a singly-linked list to represent the set of queue elements
*/
#include <stdlib.h>
#include <stdio.h>
#include "harness.h"
#include "queue.h"
/*
Create empty queue.
Return NULL if could not allocate space.
*/
queue_t *q_new()
{
queue_t *q = NULL;
if((q = malloc(sizeof(queue_t))))
{
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
else
{
return NULL;
}
}
/* Free all storage used by queue */
void q_free(queue_t *q)
{
/* How about freeing the list elements? */
/* Free queue structure */
if (q)
{
list_ele_t *i = q->head;
while(i)
{
list_ele_t *tmp = i;
i = i->next;
free(tmp);
}
free(q);
}
}
/*
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
*/
bool q_insert_head(queue_t *q, int v)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (q)
{
if ((newh = malloc(sizeof(list_ele_t))))
{
newh->value = v;
newh->next = q->head;
q->head = newh;
if (!q->size)
{
q->tail = newh;
}
++q->size;
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
/*
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
*/
bool q_insert_tail(queue_t *q, int v)
{
/* You need to write the complete code for this function */
/* Remember: It should operate in O(1) time */
list_ele_t *newh;
if (q)
{
if ((newh = malloc(sizeof(list_ele_t))))
{
newh->value = v;
newh->next = NULL;
if (q->tail)
{
q->tail->next = newh;
q->tail = newh;
++q->size;
}
else
{
q->head = q->tail = newh;
++q->size;
}
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
/*
Attempt to remove element from head of queue.
Return true if successful.
Return false if queue is NULL or empty.
If vp non-NULL and element removed, store removed value at *vp.
Any unused storage should be freed
*/
bool q_remove_head(queue_t *q, int *vp)
{
/* You need to fix up this code. */
if (!q || !q->size)
{
return false;
}
else
{
if (vp)
{
*vp = q->head->value;
}
list_ele_t *tmp = q->head;
q->head = q->head->next;
free(tmp);
--q->size;
return true;
}
}
int q_size(queue_t *q)
{
/* You need to write the code for this function */
/* Remember: It should operate in O(1) time */
if (!q || !q->size)
{
return 0;
}
else
{
return q->size;
}
}
/*
Reverse elements in queue
*/
void q_reverse(queue_t *q)
{
if (q && q->size)
{
int cache[q->size];
list_ele_t *tmp = q->head;
for (int i = q->size - 1; (i >= 0) && (tmp != NULL); --i)
{
cache[i] = tmp -> value;
tmp = tmp->next;
}
tmp = q->head;
for (int i = 0; (i < q->size) && (tmp != NULL); ++i)
{
tmp->value = cache[i];
tmp = tmp->next;
}
}
}
Build
拿到初始的代码时,敲make命令并不会开始build你的代码:
linux> make
你需要修改用于构造qtest的Makefile文件,你要在build和链接过程中添加queue.o。有关Makefile规则的说明可以参考 Using make and writing Makefiles。
其中的“An example of building an executable from multiple .o files”部分对本作业很有帮助。正确的修改Makefile能使得在输入make命令时,如果queue.o发生了变化,就重新生成qtest。
无论是在Linux还是在Unix环境中,make
都是一个非常重要的编译命令。
不管是自己进行项目开发还是安装应用软件,我们都经常要用到make
或make install
。利用make
工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make
和makefile
工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系。而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员来说简直就是一场灾难。而make
工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。因此,有效的利用make
和makefile
工具可以大大提高项目开发的效率。同时掌握make
和makefile
之后,您也不会再面对着Linux
下的应用软件手足无措了。
言归正传
按照题目说的如果直接make的话并不会编译我的代码,反而出现了以下状况:
大概就是因为没有修改qtest的makefile文件,里面的函数引用就未定义。
打开文件夹中的makefile
文件,出现了更改提示:
我们只需要添加queue.o
到编译过程和链接过程即可。
开始是不能编辑的,按下i
键改换成insert模式即可,改成如下图可视即可:
上下都需要加上需要编译的queue.o
再次键入make
,成功
测试
如果你的Makefile书写正确,编译器将生成一个可执行程序qtest,提供一个命令界面,你可以使用该界面创建,修改和检查队列。 关于可用命令的文档可以通过启动该程序并运行help命令找到:
linux> ./qtest
cmd>help
成功生成一个可执行程序qtest
:
以下文件(traces/trace-eg.cmd)演示了一个示例命令序列:
在批处理模式下运行qtest能看到这些命令的效果:
linux> ./qtest -f traces/trace-eg.cmd
在初始代码中很多操作都没有正确实现。
traces目录包含11个trace文件,其格式为trace-k-cat.txt,其中k是trace号,cat指明它测试的属性的类别。每个trace由一系列命令组成,与上面显示的相似,来测试程序的正确性、健壮性和性能的不同方面。你可以使用这些trace文件直接与qtest进行交互,测试和调试你的程序。
按照教程输入即可
评测
评测阶段,我们会用15个trace文件对你的程序进行评估,除了已经发给你们的11个外,每种类型新增加一个trace(共4个)。每一个trace有一个分数值(6或7分不等),总计为100分(提供给你的11个trace总分为67分)。每执行正确一个结果获得相应的分数。程序driver.py在trace上运行qtest并计算分数。你可以在本地使用以下命令直接调用它:
linux> ./driver.py
或者
linux> make test
73分……人麻了……等我再改改
本地测试通过后,务必一定进行在线评测,否则没有成绩!!!
将下面三个文件都拷贝到桌面上:
unix> cp Makefile queue.c queue.h ~/Desktop
然后点击“提交评测”,自动评测即可。
满分哈哈哈,做完作业了。
代码存档
设置下邮箱和姓名
交完作业啦🎉