文本编辑《数据结构》上机实验报告

更新时间:2024-05-10 23:05:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

成都信息工程学院计算机系

课程实验报告

实验课程: 数据结构

实验项目: 文本编辑器的实现 指导教师: 李莉丽 学生姓名: 陈德怀 学生学号: 2009053035 班 级: 数媒一班 实验地点: 实验时间: 20 10 年 月 日 点~ 点 实验成绩: 评阅老师:

一【上机实验目的】

要求功能与界面模拟WINDOWS记事本,支持鼠标,因为记事本功能较多,可以根据自己的能力模拟出部分功能,文本编辑这部分功能必须实现,主要利用串的知识。

二【实验环境】

PC机每人1台

三【上机实验内容】

要求功能与界面模拟WINDOWS记事本,支持鼠标,因为记事本功能较多,可以根据自己的能力模拟出部分功能,文本编辑这部分功能必须实现,主要利用串的知识。

四【上机调试程序流程图】(注:可打印)

在此程序中,主要包含了添加、插入、删除、复制、剪切、粘贴、还有文件操作。

五【上机调试中出现的错误信息、错误原因及解决办法】

1、 开始的时候当我输入字符的时候,总是输不进去,经过检查才知道,我忘了把字符输出

到屏幕上。 2、 在删除的时候,当一行删除完的时候,光标并不会上移到上一行。然后我通过判断当这

光标处的坐标减一后为零(表示这行没有字符了)的时候,然后重新读取光标,让光标显示在上一行。

3、 在进行插入操作之后,移动光标会出现问题,就好像插入的字符并没有在链表当中一样。

然后我写了一个测试函数,来判断插入后链表是否满足每列的字符数不得超过80个字符。然后让每列的字符数都在链表当中,满足要求。

六【上机调试后的源程序及还存在的问题】(注:源程序可打印)

/*文本编辑器editor源代码*/ #include #include

#include

#include

#define LEFT 0x4b00 /*←:光标左移*/ #define RIGHT 0x4d00 /*→:光标右移*/ #define DOWN 0x5000 /*↓键:光标下移*/

#define UP 0x4800 /*↑键:光标上移*/

#define ESC 0x011b /*ESC键:取消菜单打开操作*/

#define ENTER 0x1c0d /*回车键:换行*/

#define BACK 3592 /*BackSpace键:删除当前光标位置前一个字符*/ #define CL 29440 /*ctrl+←键:从右至左,选定文本*/ #define CR 29696 /*ctrl+→键:从左到右,选定文本*/

#define Cc 11779 /*ctrl+c键:将选定文本,复制一份到剪贴板中*/ #define Cv 12054 /*ctrl+v键:将剪贴板中的内容复制到当前位置*/ #define Cx 11544 /*ctrl+x键:对选定文本,执行剪切操作*/ #define F1 15104 /*F1键:打开文件菜单*/ #define F2 15360 /*F2键:打开编辑菜单*/ #define F3 15616 /*F3键:打开帮助菜单*/

int value,backup;

/*value保存有值数组元素的最大下标值,backup保存value的副本,NUM保存当前行中的用户输入的字符个数*/

typedef struct record {

char ch; /*保存一字符*/

int col, line; /*x轴和y轴坐标*/

}record;

record r[500]; /*定义一个有500个元素的结构体数组,保存选定的文本字符的属性*/

typedef struct node /*定义保存行中的单个字符的结构*/ {

char ch; /*数据域:保存一字符*/

struct node *next; /*指针域:指向下一个结点的指针*/ }node;/*由此类型节点构成的单链表,命名为:列单链表*/

typedef struct Hnode /*定义保存所有列单链表首节点的指针的结构*/ {

node *next; /*指向列单链表的首节点的地址*/ struct Hnode *nextl; /*指向下一个节点的指针*/

}Hnode;/*由此类型节点构成的单链表,命名为:行单链表*/

void view(Hnode *q) /*按行显示保存在单链表中的文本字符,q为指向行单链表中第一个节点的指针*/

{

node *p; /*p为保存列单链表节点元素地址的指针*/

clrscr(); /*清屏*/

/*双重循环,读取并显示保存在单链表中字符*/

do{

p=q->next;

while(p!=NULL&&p->ch>=32&&p->ch<127&&p->ch!=13&&p->ch!=-1) /*指针p不能为空,且数据域必须为常规字符*/

{

putch(p->ch);/*在文本窗口中输出该字符*/ p=p->next; /*指向下一个节点*/ }

q=q->nextl; /*指向下一个节点*/

if((p->ch==13||p->ch==-1)&&q!=NULL) gotoxy(1,wherey()+1); /*若ch为回车或EOF标记,光标跳至下行的开始处*/

}while(q!=NULL); /*逐行逐列显示文本字符*/ }

void control(int A, Hnode *Hhead) {

void colorview(Hnode *,int,int); /*函数声明*/

int x,y,flag=0;

x=wherex(); y=wherey(); /*得到当前光标的坐标值*/

if((A==CL)&&(x!=1)) /*ctrl+←,当前光标不是在行首,光标移动*/ gotoxy(wherex()-1,wherey());

if((A==CL)&&(x==1)) /*ctrl+←,在行首*/

gotoxy(abs(judge(Hhead,wherey()-1)),wherey()-1); /*judge(Hhead,wherey()-1)上一行的字符个数作为x值,光标移动*/

if((A==CR)&&check(Hhead,wherey(),wherex())>0) /*ctrl+→,当前光标的右边有字符,光标移动*/

{ flag=1; gotoxy(wherex()+1,wherey()); }

if((A==CR)&&check(Hhead,wherey()+1,1)>0&&check(Hhead,y,x)==0) /*ctrl+→,当前光标处没有字符但下一行的第一列有字符,光标移动*/ { flag=1; gotoxy(1,wherey()+1); }

if((A==CR)&&x==80) /*ctrl+→,当前光标在当前行的行尾,光标移动*/ { flag=1; gotoxy(1,wherey()+1); }

if(A==CR&&flag==1) /*ctrl+→,光标已经跳至新处,将当前光标所在位置的字符的坐标和值保存在r数组中*/ {

r[abs(value)].col=wherex();

r[abs(value)].line=wherey();

r[abs(value)].ch=check(Hhead,r[abs(value)].line,r[abs(value)].col);

if(r[abs(value)].ch==-1) r[abs(value)].ch=13; /*若第line行,第col列的字符为回车键,则返回-1*/ value--;

}

if(A==CL&&(x!=1||y!=1)) /*ctrl+←,当前光标并不在窗口左上角,将当前光标所在位置的字符的坐标和值保存在r数组中*/ {

r[abs(value)].col=wherex(); r[abs(value)].line=wherey();

r[abs(value)].ch=check(Hhead,r[abs(value)].line,r[abs(value)].col); value++;

}

colorview(Hhead,wherex(),wherey()); }

/*用不同的前背景色显示选择的字符*/ void colorview(Hnode *Hhead,int x,int y) {

int i;

view(Hhead);/*重新显示所有文本字符*/

for(i=0;i

gotoxy(r[i].col,r[i].line); textcolor(0); textbackground(7); cprintf(\ }

textcolor(7);

textbackground(0); gotoxy(x,y); }

int check(Hnode *Hhead,int m,int n) /*check():在单链表中检查第m行第n列位置的字符,若为常规字符,则返回该字符*/ {

int i;

Hnode *q; node *p;

q=Hhead;

for(i=1;inextl;

p=q->next;/*获取第m个节点的数据域*/

for(i=1;i

p=p->next;

if(p->ch==13) return -1; /*若第m行,第n列的字符为回车键,则返回-1*/

if(p->ch>=32&&p->ch<127) return p->ch; /*若第m行,第n列的字符为常规字符,则返回该字符*/

else return 0; /*若第m行,第又非常规字符,则返回0*/

}

int judge(Hnode *Hhead,int m) /*judge():返回第m行中的常规字符总的个数,不包括回车符*/ {

Hnode *q;

node *p; int i,num=0; q=Hhead;

for(i=1;i

q=q->nextl;

if(q==NULL) return -1; /*返回-1,表示第m行不存在*/ p=q->next;

while(p->next!=NULL) {

p=p->next;

num++; /*统计第m行的字符个数*/ }

/*行尾字符还没有判断,接下来判断行尾字符*/

if(p->ch==13&&num==0) return 0; /*返回0,表示当前行只有一个回车字符*/ if(p->ch>=32&&p->ch<127) return num+1; /*返回num+1,表示当前行的最后一个字符为常规字符*/

if(p->ch==13&&num!=0) return num; /*返回num,表示当前行的最后一个字符为回车符,不计算在内*/

else return num;/*返回num,表示当前行中只有一个字符,且没有回车符*/ }

int del(Hnode *Hhead,int m,int n) /*del():删除第m行,第n列位置的字符*/

{

Hnode *q,*q1; node *p1,*p2,*tail; int i ,j,flag=0; q=Hhead;

if(n==0&&m==1) return; /*第1行,第0列不存在*/

if(n==0&&m>1) /*若为第0列字符,但行必须大于1,执行向上行移处理*/

{

m=m-1;

gotoxy(judge(Hhead,wherey()-1)+1,m);/*移至第m-1行,第76列*/ flag=1; /*移位的标志置1*/ }

for(i=1;inextl; p1=q->next;

for(i=1;inext;

p2=p1->next; /*p2指向列单链表中的第n个元素*/ if(n==1) /*若是删除第m行第1列的字符*/ {

q->next=p1->next; free(p1); } else

{

p1->next=p2->next; /*在单链表中删除第m行第n列的元素*/ free(p2); }

return flag; /*返回0:表示没有换位,返回1:表示有换位*/

}

void insert(Hnode *Hhead,int m,int n, char a) /*第m行,第n列的位置之前一个位置,插入单字符*/ {

int i; Hnode *q;

node *p,*p1,*p2; q=Hhead;

for(i=1;inextl;

p1=q->next;

for(i=1;i

p1=p1->next;

p=(node *)malloc(sizeof(node)); /*创建一个新的列单链表节点*/ p->ch=a; /*给此节点的数据域赋值*/

if(n==1) /*插入之前,若只有一个字符在行中,则插在此节点之前*/ {

p->next=q->next; q->next=p; } else {

p->next=p1->next; /*在第m行,第n列的字符前,插入一字符*/ p1->next=p;

}

test(Hhead,m); /*在插入新元素后,检验并处理单链表中第m行开始的元素,使其满足规则*/

}

/*执行insert()后,检验第n行及后面的数据,使其满足规则*/ int test(Hnode *Hhead,int n)

{

int i=0;

node *p1,*p2,*tail,*temp1,*temp2; Hnode *q,*q1;

q=Hhead;

for(i=1;i

q=q->nextl; } tail=p1=q->next; if(p1==NULL)

{ return ; /*若此行没有任何字符,则返回*/

}

while(tail->next!=NULL) /*定位至列单链表中的最后一个元素*/

{

tail=tail->next; } {

p1=p1->next; }

for(i=1;i<80;i++)

p2=p1->next;

p1->next=NULL; /*在此行的最后一个字符的前一个字符处断行,因为插入在此行插入了一个新的字符*/

if(tail->ch!=13) /*若此行行尾不是回车键*/

{ if(q->nextl==NULL)/*若p1的数据域为回车符且行单链表中只有n个节点*/

q1=(Hnode *)malloc(sizeof(Hnode)); q1->nextl=NULL;

q1->next=p2; /*新行单链表节点保存此行多出的字符*/

{

q->nextl=q1; } else {

q=q->nextl;

tail->next=q->next;/*将多出的字符与下一行的字符相连*/ q->next=p2; test(Hhead,++n); }

} else

{

q1=(Hnode *)malloc(sizeof(Hnode)); q1->nextl=q->nextl; q1->next=p2;

q->nextl=q1; }

}

/*从任意文本文件中读取文件内容,保存至行单链表和列单链表形式的数据结构中*/ void opens(Hnode *Hp) {

FILE* fp;

Hnode *q11,*q22; node *p11,*p22,*hp; char temp;

int count=0,flags=1;

char filename[10]; /*保存文件名*/

clrscr();/*清屏*/

printf(\输入文件名格式*/ scanf(\输入文件名*/

fp=fopen(filename,\以只读方式打开文件,filename必须要存在*/ if(fp==NULL)/*打开文件失败*/

{

textbackground(2); textcolor(13);

cprintf(\ getchar(); exit(0) ; }

q11=Hp;

while(!feof(fp)) {

count=0;flags=1;

q22=(Hnode *)malloc(sizeof(Hnode));/*新建一个行单链表中的节点*/

p11=(node *)malloc(sizeof(node)); /*新建一个列单链表中的节点*/

while((temp=fgetc(fp))!=10&&count<=80&&!feof(fp)) /*循环结束,表示在单链表中一行处理完毕,开始新行*/

{

p22=(node *)malloc(sizeof(node));/*新建一个列单链表中的节点*/ if(flags==1)

{

hp=p22;

flags=0;

} /*hp保存列单链表中的首节点的地址*/

p22->ch=temp;

p22->next=NULL;

p11->next=p22; p11=p22; count++;

}

if(temp==10)

{ /*若为换行符,将其转换为回车符,因为在程序中,是按回车符处理的*/ p22=(node *)malloc(sizeof(node));

p22->ch=13; p22->next=NULL;

p11->next=p22;

p11=p22;

}

if(!feof(fp))/*若没此条件,文件最后一行会处理两次.*/

{

q22->next=hp;

q22->nextl=NULL; /*将存储了字符的新列单链表与行单链表中的新节点建立关联*/

q11->nextl=q22;

q11=q22; }

}

fclose(fp);

Hp=Hp->nextl;/*因为Hp的所在节点的数据域为空,所以Hp=Hp->nextl*/ return ; }

/*将head所指的行单链表中所指的各个列单链表中的数据域的值写入文件,文件路径和文件名由用户指定*/

void save(Hnode *head) {

FILE* fp; Hnode *q;

node *p;

char filename[10]; /*保存文件名*/ q=head;

clrscr();/*清屏*/

printf(\输入文件名格式*/ scanf(\输入文件名*/ fp=fopen(filename,\

if(fp==NULL) /*打开文件失败*/ {

}

printf(\getchar(); return ;

do{

p=q->next; /*指向node类型的数据*/

while(p!=NULL) {

if((int)p->ch==13)/*把回车转化为换行符*/

{

fputc('\\n',fp);

p=p->next;

} else {

fputc(p->ch, fp);

} } q=q->nextl; }while(q!=NULL);

fclose(fp); /*关闭此文件*/

return ;

p=p->next;

}

void main() { char a;

int i,A,x,y,flag=0,b,t; Hnode *Hhead,*q;

node *p1,*p2;

Hhead=q=(Hnode *)malloc(sizeof(Hnode)); /*为行单链表中首节点分配内存空间*/ q->nextl=NULL;

q->next=p1=p2=(node *)malloc(sizeof(node)); p1->ch=13; p1->next=NULL; while(1) {

while(bioskey(1)==0) continue; /*等待用户按键*/ a=A=bioskey(0); /*返回输入的字符的键值*/ if(a>=32&&a<127) /*若输入为常规字符*/ {

if(check(Hhead,wherey(),wherex())<=0)/*当前位置没有字符且输入是常规字符,

则执行添加字符操作*/ { p2->ch=a; putch(a);

if(wherex()-1==80) {

q->nextl=(Hnode *)malloc(sizeof(Hnode)); q=q->nextl; q->nextl=NULL; q->next=NULL;

p1=p2=q->next=(node *)malloc(sizeof(node)); p1->ch=13; p1->next=NULL;

} else {

p2->next=(node *)malloc(sizeof(node));

p2=p2->next; p2->ch=13; p2->next=NULL;

gotoxy(1,wherey()+1);

}

}

else /*当前位置有字符且输入是常规字符,则执行插入字符操作*/

x=wherex(); y=wherey();

insert(Hhead,wherey(),wherex(),a); view(Hhead); gotoxy(x+1,y);

{ }

}

列有字符*/

if(a==13) {

gotoxy(1,wherey()+1);

q->nextl=(Hnode *)malloc(sizeof(Hnode)); q=q->nextl;

q->nextl=NULL;

q->next=NULL;

p1=p2=q->next=(node *)malloc(sizeof(node)); p1->ch=13;

p1->next=NULL;

} x=wherex(); y=wherey();

/*文本窗口中左移,当前光标不在窗口的第1列*/ if((A==LEFT)&&(x!=1))

gotoxy(wherex()-1,wherey());

/*文本窗口中左移,当前光标在窗口的第1列*/ if((A==LEFT)&&(x==1))

gotoxy(abs(judge(Hhead,wherey()-1)),wherey()-1);

/*文本窗口中右移,若当前光标的右边一位有字符*/

if((A==RIGHT)&&check(Hhead,wherey(),wherex())>0)

gotoxy(wherex()+1,wherey());

/*文本窗口中右移至下行的第1列,若当前光标位置没有字符且下行的第1if((A==RIGHT)&&check(Hhead,wherey()+1,1)!=0&&check(Hhead,y,x)<=0) gotoxy(1,wherey()+1);

/*右移*/

if((A==RIGHT)&&x==80)

gotoxy(1,wherey()+1);

/*上移*/

if((A==UP)&&check(Hhead,wherey()-1,wherex())!=0)

gotoxy(wherex(),wherey()-1);

/*下移*/

if((A==DOWN)&&check(Hhead,wherey()+1,wherex())!=0) gotoxy(wherex(),wherey()+1); /*处理BackSpace键*/

if(A==BACK) /*处理BackSpace键*/ {

flag=del(Hhead,wherey(),wherex()-1); x=wherex()-1; y=wherey(); view(Hhead); if(flag==0) {

gotoxy(x,y);

}

if(flag==1) {

gotoxy(x+1,y); flag=0; } } /*处理Ctrl+x按键*/ /*处理Ctrl+x按键*/ if(A==Cx&&value!=0) {

if(value>0) { x=wherex();

y=wherey(); }

Else

{

x=r[0].col;

y=r[0].line; }

for(i=0;i

del(Hhead,r[i].line,r[i].col);

backup=value; /*保存r数组的有值元素的最大下标值*/ value=0; /*此value为全局变量*/ view(Hhead); gotoxy(x,y); }

/*处理Ctrl+c按键*/ if(A==Cc&&value!=0) {

x=wherex();

y=wherey();

backup=value; value=0; /*此value为全局变量*/ view(Hhead); gotoxy(x,y); }

/*处理Ctrl+v按键*/ if(A==Cv&&backup!=0) {

x=wherex();

y=wherey();

if(backup<0) /*Ctrl+右移键选定的文本,贴切此当前位置*/ for(i=0;i

insert(Hhead,y,x+i,r[i].ch);/*逐个插入*/ if(backup>0) /*Ctrl+左移键选定的文本,贴切此当前位置*/ for(i=0;i

insert(Hhead,y,x+i,r[backup-1-i].ch); view(Hhead); gotoxy(x,y);

}

/*处理Ctrl+左移键或右移键*/

if(A==CL||A==CR) control(A,Hhead); if(A==F1)

{

clrscr();/*清屏*/

main(); } /*新建文件*/

if(A==F3)

{ /*打开文件*/

Hhead=(Hnode *)malloc(sizeof(Hnode)); opens(Hhead); getchar();

clrscr();

view(Hhead);

}

/*保存文件*/

if(A==F2) {

save(Hhead); clrscr();

cprintf(\getch();

view(Hhead); }

} }

七【上机实验中的其他它问题及心得】

此编辑器主要采用的是用链表的作为他的存储结构,此编辑器用了三个结构体来分别存储行(主要作用是存储列单链表的首地址)、每列的字符还有存储复制、剪切的字符。为什么要选择这种存储结构呢?因为在用键盘控制光标的情况下,我们必须知道光标的位置来控制字符的位置,所以此种结构能够很好建立光标和链表之间的配合。然后用光标来控制链表。最后实现文本的编辑。在选择存储方式的时候我也想过用串的块链表,但是在建立此种存储方式的时候必须先定义确定的空间,所以在很大程度上,空间会有冗余。

我想过用串和栈的知识,但是在用串和栈的知识的时候很难用光标去定位字符的位置,而且只能整体操作:比如说:在插入的是后必须是整体插入,不能直接定位到某一个特定的字符之间插入。还有在删除的时候也是,不能定位到某一个特定的位置删除某一个字符或者几个字符。用此种方法最方便的操作就是进行行操作(就是整行整行的插入和删除),这不符合我的初衷。

还有Turbo C2.默认定义的文本窗口为整个屏幕,共有80列(或40列),25行的文本单元,每个单元包括一个字符和一个属性,字符即ASCII码字符,属性规定该字符的颜色和强度。同时,他还规定整个屏幕的左上角坐标为(1,1),右下角坐标为(80,25)。并规定沿水平方向为X轴,方向朝右;眼垂直方向为Y轴,方向朝下。

功能心得: 一、 添加

当前光标处没有字符并且输入的是常规字符。

创建两个单链表,一个用来存储每列的字符,另一个用来存储每列的首地址(方便用光标定位字符的位置)称为行。

在添加的过程中如果当列的字符个数没到80个时,那么列单链表不断的创建(不断地在后面添加字符),当此行字符的个数到达了80的时候,那么重新创建一行,然后在不断在此行的后面添加字符。直到到达下次满足80的字符的个数。当在添加字符的过程中如果输入的是一个回车键,那么重新创建一个行链表节点,然后光标移动到下一行的开始的位置,此时再在列单链表的后面添加字符。

二、 插入

当前光标处有字符的时候并且输入的是常规字符,那么执行插入操作。

当光标的位置在第m行第n列的时候,那么在第m行第n列之前插入字符。如果在此行中只有一个元素则把把字符插在此字符之前,然后把此节点的地址赋给此行的行链表中。如果不只一个元素的话,那么直接把字符插在第m行第n列之前。然后再检查此列是否满足只有80个字符,如果满足则后面的字符不用移动。若果不满足,那么就要通过test()函数来处理。

三、 检查

此函数的作用主要是当执行插入操作后进行检查每行是否满足规则(每列的元素个数必须小于等于80个,当多余这个数的时候就要进行移动处理)。 先用指针指向此列的最后一个字符(tail),然后再分别用两个指针指向第80个字符(p1)和第81(p2)个字符。如果现在指针处在最后一行,那么新建一个行节点,然后直接把多出来的字符移动到下一行。如果光标不在最后一行,那么直接把此行的下一行的字符接到tail指针后,然后再把p2赋给下一行的行节点中。如果tail的数据域中试一个回车键,那就要再建一个行节点来存放多出来的字符。

四、 删除

如果光标所处的行中没有字符,那么返回不操作。如果光标处在行中的第一列,那么直接free掉第一个字符。如果光标处在列的其他位置,那么直接free掉此处的这个字符。

五、 复制、剪切、粘贴

复制和剪切这两个模块都有一个共同点就是要先把所选中的文本字符存入一个结构体中,这个结构体主要用来存储当前光标的x、y坐标和所选中的字符。这两个操作唯一的区别就是剪切需要把所选文本的字符删除掉,而复制不需要。

粘贴:这个操作时建立在复制、剪切的基础上的。其实粘贴的本质就是把所选的文本字符依次插入到编辑行中。

最后的阐述:因为第一次用光标来定位字符的位置,所以光标和链表的关系控制的不是很好。所以在程序当中还会有很多问题,但是我相信,随着知识的增长,我会很好的运用此种操作,最后达到很好的运行效果。

本文来源:https://www.bwwdw.com/article/u6pg.html

Top