不知你们搜这个的时候是不是帝国理工的人,反正我是,我当初想上网搜一下别人的代码参考,但是很多都是对不上的,大多数的代码都是英文字符串的。自己也是找了很久办法,算法也是自己用笔画图慢慢推的,期间太辛苦了,所以也不想让你们那么辛苦,纯纯造福学弟学妹们好吧,全代码在下边,自己去参考吧!(千万不要懒得写就全抄,你能搜到的东西,别人也肯定能,查重出来还是挺严重的好吧)
代码很长,我写的算法可能比较绕,但是我已经尽量多一些注释了,就凑合着看吧
bug的话……我已经试了一些了,已经写的我好烦了,目前还没有试出来什么bug,你们看着办吧
不过我估计这个代码只适合文件里只有中文的,混杂着英文跟数字的话就会出一些问题,因为如果要辨识中英文数字的话,很多地方又要分别辨识ascall码的正负性,我觉得好麻烦,就算了,毕竟这只是作业不用于商业用途。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINE 50 //预设定一行25个字(一个字占两个字节)
#define PAGE 5 //预设定一页最多显示5行字
#define MAXSIZE 100 //预设定一个结点最大存放100个元素
typedef struct novel{
char data[MAXSIZE]; //存放数据元素的数组
int number; //结点号,方便分页显示
int para; //段号
int lnum; //行号,规定一行50个字即一个结点为一行,刚好为一个结点
struct novel* Llink;//前指针
struct novel* Rlink;//后指针
}Novel;
//编辑器大标题,永远出现在程序的最顶端
void HeadWord()
{
printf("_____________________________________________________________________________________________________\n\n");
printf("******** 欢迎使用欢乐小说编辑器! ********\n");
printf("_____________________________________________________________________________________________________\n\n");
}
//文件读入
void ReadNovel(Novel *head, Novel *next, Novel *info)
{
int i = 0, para = 1, lnum = 1; //para用于记录当前段号
char c;
FILE* fp; //文件指针
if ((fp = fopen("novel.txt", "r")) == NULL)
{
printf("\n文件无法打开!\n");
exit(0);
}
printf("\n正在装入文件!\n");
while ((c = fgetc(fp)) != EOF)
{
if (i == MAXSIZE || c == '\n') //当结点空间用完或检测到换行,则创建新结点
{
if (c == '\n') //若检测到是换行,则段数加一
{
info->data[i] = '\0'; //表明这个结点的有效内容完结了,后面都是无效内容
para++;
lnum = 1;
c = fgetc(fp);
}
else lnum++; //否则就是结点满了,行号加一,段数不变
if (c != EOF) //若未读完文件则申请结点
{
next = (Novel*)malloc(sizeof(Novel)); //申请结点,连接上新结点并让info指向新节点
info->Rlink = next;
next->Llink = info;
next->Rlink = NULL;
next->para = para;
next->lnum = lnum;
next->number = info->number + 1;
info = next;
i = 0;
}
else //若已读完则令最后为‘\0’表示后面为无效内容(文件最后一个为换行符)
{
info->data[i] = '\0';
break;
}
}
info->data[i] = c;
i++;
}
if(i != MAXSIZE) info->data[i] = '\0'; //以免文件最后一位不是换行符就不加结束符
info->Rlink = NULL; //读取文件完毕
info = head; //指针回归到头结点方便后续其他操作
next = head;
printf("\n读取文件完毕!\n\n");
fclose(fp);
}
//从链表里找出对应长度的字符串
int findstr(char str[], char* p, Novel* info, int i)
{
int str_len = 0, j = 0, k, have; //have用于记录保存取出数组中已经有的数据数量
str_len = strlen(str); //记录输入字符串的长度
if (i <= MAXSIZE - str_len) //若i大于,则说明结点剩余文字不足
{
k = 0; //k为暂存取出字符串的数组的数组下标
for (j = i; j < str_len + i; j++) //从结点中取出来与输入字符串同等长度的字符串
{
if (info->data[j] == '\0') //若检测到结束符则不需要继续检测下去
{
return 1;
}
else
{
p[k] = info->data[j];
k++;
}
}
}
else if (info->Rlink != NULL && info->para == info->Rlink->para) //取出文字需要跨越两个同一段的结点
{
k = 0; //k为暂存取出字符串的数组的数组下标
for (j = i; j < MAXSIZE; j++) //从第一个结点中取出字符,直到取完
{
p[k] = info->data[j];
k++;
}
have = k;
info = info->Rlink; //info指向下个结点
for (j = 0; j < str_len - have; j++) //从第二个结点中取出剩余数量的字符
{
p[k] = info->data[j];
k++;
}
info = info->Llink;
return 1;
}
else return 1; //结点剩余字符串少于输入的字符串,且与下个结点不为同一段,或已经是最后一段
return 0;
}
//搜查小说内容
void SearchNovel(Novel* head, Novel* info, char str[])
{
int choice = -1, i, detm = 0; //str_len用于记录输入字符串的长度,detm=1则说明已循环完一个结点,将i置回初值
char takestr[20] = { '\0' }; //暂存从结点中取出来的字符串,再去与str做比较
int found = 0; //found = 0代表还没找到该字符串
i = 0; //i记录从结点中取出文字的位置
while (info != NULL) //字符串匹对,直到找完文章为止
{
detm = findstr(str, takestr, info, i); //返回值返回当前链表中读到哪个位置了
if (strcmp(takestr, str) == 0) //字符串匹对成功
{
found = 1;
printf("\n查找成功!\n");
printf("你想查找的文字位置在:第%d段,第%d行,第%d个位置\n", info->para, info->lnum, i / 2 + 1);
}
if (detm == 1)
{
i = 0;
info = info->Rlink;
}
else i = i + 2; //找出也继续找后面的,直到找完文章为止(由于一个字占两个字节,所以加2)
}
if (found == 0) printf("\n查找无果,请仔细检查所输入文字是否有误!\n\n");
info = head; //指针回归到头结点方便后续其他操作
}
//删除空结点功能函数
void deletenode(Novel *head, Novel *info)
{
Novel* q = NULL; //工具人指针:方便删除结点,被传入来的info就是要被删掉的结点
if (info->Llink == NULL) //要删除的结点在为链表第一个结点
{
q = info->Rlink;
q->Llink = NULL;
free(info);
head = q;
while (q != NULL)
{
q->number--;
q = q->Rlink;
}
}
else if (info->Rlink == NULL) //要删除的结点在为链表最后一个结点
{
q = info->Llink;
info->Llink->Rlink == NULL;
free(info);
}
else //要删除的结点在中间
{
q = info->Llink;
q->Rlink = info->Rlink; //删除结点
info->Rlink->Llink = q;
q = q->Rlink;
free(info);
while (q != NULL)
{
q->number--;
q = q->Rlink;
}
}
}
//删除所给位置的字符串
void deletestr(Novel* head, Novel* info, int i, char str[])
{
int str_len, j, k;
printf("\n准备删除第%d段,第%d行,第%d个位置的内容...\n", info->para, info->lnum, i);
str_len = strlen(str); //计算要删除的字符串长度
i = 2 * i - 2;
if (info->para != info->Rlink->para) //若删除的位置的字符串在该段的最后一行
{
if (info->data[str_len] == '\0') deletenode(head, info); //若最后一行要删完,则删除结点
else
{
for (j = i; j < MAXSIZE - str_len; j++) //删除字符串
info->data[j] = info->data[j + str_len];
}
}
else //若要删除的字符串不在最后一行
{
if (i + str_len > MAXSIZE) //当要删除字符串位置位于两个同段结点交界处
{
for (j = i; j < MAXSIZE; j++) //将后一结点的内容补去上一结点删去内容处的空隙
{
info->data[j] = info->Rlink->data[str_len + j - MAXSIZE];
}
info = info->Rlink;
while (info->para == info->Rlink->para) //直到遍历完这一段为止
{
for (j = 0; j < MAXSIZE - str_len; j++) //单个结点内后面向前补全内容
{
info->data[j] = info->data[j + str_len];
}
k = 0;
for (; j < MAXSIZE; j++) //直到当前结点删除需要跨越结点
{
info->data[j] = info->Rlink->data[k];
if (info->data[j] == '\0') //下一结点的内容都为无效内容,删除结点
{
deletenode(head, info->Rlink);
break;
}
k++;
}
info = info->Rlink;
}
}
else //当要删除的字符串不在最后一段,且不在两段交界处
{
while (info->para == info->Rlink->para) //直到遍历完这一段为止
{
for (j = i; j + str_len < MAXSIZE; j++) //当前结点删除不需要跨越结点,在第一个结点中删去字符
{
info->data[j] = info->data[str_len + j];
}
k = 0;
for (; j < MAXSIZE; j++) //若当前结点删除需要跨越结点
{
info->data[j] = info->Rlink->data[k];
if (info->data[j] == '\0') //下一结点的内容都为无效内容,删除结点
{
deletenode(head, info->Rlink);
break;
}
k++;
}
i = 0; //将i置零,让下一结点从0开始遍历完
info = info->Rlink;
}
}
if (info->para == info->Llink->para) //该段的最后一行
{
for (j = 0; j + str_len < MAXSIZE; j++)
info->data[j] = info->data[str_len + j];
}
}
}
//删除小说内容
void DeleteNovel(Novel* head, Novel* info)
{
int para, lnum, i; //记录想要删除的文字内容位置,分别为段号、行号和位置
char str[20] = { '\0' }; //保存输入的字符串,最多10个字
char takestr[20] = { '\0' }; //暂存从结点中取出来的字符串,再去与str做比较
printf("\n请输入你想要删除的内容:(最多10个字)\n");
scanf("%s", &str);
printf("\n");
SearchNovel(head, info, str); //先调用搜查函数找出想要删除的文字内容位置,再选择删除
printf("\n");
while (1)
{
printf("\n请输入你想要删除的内容的位置:(分别输入段号和行号和第几个字)\n");
printf("tips:若想要退出删除则都分别输入0\n");
scanf("%d%d%d", ¶, &lnum, &i);
if (para == 0 && lnum == 0 && i == 0) break;
while (info->para != para || info->lnum != lnum) //验证输入段号和行号是否有误
{
if (info->Rlink == NULL)
{
printf("\n输入的位置有误!请重新输入!\n");
break;
}
else info = info->Rlink;
}
if (info->para == para && info->lnum == lnum)
{
findstr(str, takestr, info, i * 2 - 2);
if (strcmp(takestr, str) == 0) //字符串匹对成功,输入的位置信息完全正确,进行删除字符串操作
{
deletestr(head, info, i, str);
printf("\n删除成功!\n\n");
}
else printf("\n输入的位置有误!请重新输入!\n");
}
info = head;
}
printf("\n返回主菜单...\n");
}
//在所给位置添加字符串
void addstr(Novel* info, char str[], int lnum, int i)
{
int str_len, j, k;
int last = MAXSIZE, fins; //last方便跨结点传输数据,fins用于后面记录完成插入后跳出循环
Novel* q = NULL; //工具人指针:增加结点后,可调整增加结点后面的结点的数据
Novel* next = NULL; //用于申请新结点
str_len = strlen(str);
printf("\n准备在第%d段,第%d行,第%d个位置添加内容...\n", info->para, info->lnum, i);
k = 0; //用于后面添加字符串内容
last = MAXSIZE - 1; //初始化数据
fins = 0;
i = 2 * i - 2;
while (info->para == info->Rlink->para) info = info->Rlink; //找到想要添加内容的那一段的最后一个节点
for (j = 0; j < MAXSIZE; j++) //找到该段最后一个结点中的最后一个有效元素位置
if (info->data[j] == '\0') break;
if (j + str_len > MAXSIZE) //需要申请新结点
{
next = (Novel*)malloc(sizeof(Novel)); //申请结点,连接上新结点
info->Rlink->Llink = next;
next->Rlink = info->Rlink;
info->Rlink = next;
next->Llink = info;
next->para = info->para;
next->lnum = info->lnum + 1;
next->number = info->number + 1;
info = next; //info指向新结点,调整新结点后面的结点数据
q = next->Rlink;
while (q != NULL) //后面的结点号都加一
{
q->number++;
q = q->Rlink;
}
//遍历找出要添加内容的位置
while (fins == 0)
{
last = j;
for (j = last + str_len - MAXSIZE; j >= 0; last--) //跨结点传输数据,补全后面结点
{
info->data[j] = info->Llink->data[last];
if (last == i && info->Llink->lnum == lnum) //若遍历到了要插入字符串的位置
{
for (; i < MAXSIZE; i++)
{
info->Llink->data[i] = str[k];
k++;
}
if (k == str_len) //若已经添加完,不需要继续进行循环下去
{
fins = 1;
break;
}
else //否则到第二结点继续添加内容
{
for (i = 0; i < str_len - MAXSIZE + last; i++) info->data[i] = str[k];
fins = 1;
break;
}
}
j--;
}
if (fins == 1) break; //若已经添加完内容,则直接退出,不用参加下面循环
info = info->Llink;
for (j = MAXSIZE - 1; last >= 0; last--) //一个结点内的内容后移str_len位,为前面增加内容腾出空位
{
info->data[j] = info->data[last];
if (last == i && info->lnum == lnum)
{
for (; i < j; i++)
{
info->data[i] = str[k];
k++;
}
fins = 1;
break;
}
j--;
}
j = MAXSIZE - 1; //形成回流循环
}
}
else
{
while (fins == 0)
{
for (j += str_len; j >= str_len; j--) //一个结点内的内容后移str_len位,为前面增加内容腾出空位
{
info->data[j] = info->data[j - str_len];
if (j - str_len == i && info->lnum == lnum) //若遍历到了要插入字符串的位置
{
for (; i < j; i++)
{
info->data[i] = str[k];
k++;
}
fins = 1;
break;
}
}
if (fins == 1) break; //若已经添加完内容,则直接退出,不用参加下面循环
for (; j >= 0; j--) //跨结点传输数据,补全后面结点
{
info->data[j] = info->Llink->data[last];
if (last == i && info->Llink->lnum == lnum) //若遍历到了要插入字符串的位置
{
fins = 1;
for (; i < MAXSIZE; i++)
{
info->Llink->data[i] = str[k];
k++;
}
if (k == str_len) break; //若前面那个结点就已经添加完了就跳出
else
{
for (i = 0; i < j; i++)
{
info->data[i] = str[k];
k++;
}
}
break;
}
last--;
}
info = info->Llink; //形成回流循环
j = last;
last = MAXSIZE - 1;
}
}
}
//添加小说内容
void AddNovel(Novel* head, Novel* info)
{
int para, lnum, i; //记录想要添加的文字内容位置,分别为段号、行号和位置
char str[20] = { '\0' }; //保存输入的字符串,最多10个字
while (1)
{
printf("\n请输入你想要添加内容的位置:(分别输入段号和行号和第几个字)\n");
printf("tips:若想要退出添加则都分别输入0\n");
scanf("%d%d%d", ¶, &lnum, &i);
if (para == 0 && lnum == 0 && i == 0) break;
printf("\n请输入你想要添加的内容:(最多10个字)\n");
scanf("%s", &str);
while (info->para != para || info->lnum != lnum) //验证输入段号和行号是否有误
{
if (info->Rlink == NULL)
{
printf("\n输入的位置有误!请重新输入!\n");
info = head;
break;
}
else info = info->Rlink;
}
if (info->para == para && info->lnum == lnum) //如果输入的段号和行号无误
{
addstr(info, str, lnum, i);
printf("\n添加成功!\n\n");
}
info = head;
}
printf("\n返回主菜单...\n");
}
//修改链表(相当于删除后增加)
void ChangeNovel(Novel* head, Novel* info)
{
char strdelete[20] = { '\0' }; //暂存要删除的字符串
char stradd[20] = { '\0' }; //暂存要添加的字符串
char takestr[20] = { '\0' }; //暂存从链表取出的字符串
int para, lnum, i;
while (1)
{
printf("\n请输入你想要修改的内容所在位置:(分别输入段号和行号和第几个字)\n");
printf("tips:若想要退出添加则都分别输入0\n");
scanf("%d%d%d", ¶, &lnum, &i);
if (para == 0 && lnum == 0 && i == 0) break;
printf("\n请输入你想要修改掉的内容:(最多10个字)\n");
scanf("%s", &strdelete);
printf("\n请输入你想要修改成的内容:(最多10个字)\n");
scanf("%s", &stradd);
while (info->para != para || info->lnum != lnum) //找到输入的位置结点
{
if (info->Rlink == NULL)
{
printf("\n输入的位置有误!请重新输入!\n");
info = head;
break;
}
else info = info->Rlink;
}
findstr(strdelete, takestr, info, i * 2 - 2);
if (strcmp(takestr, strdelete) == 0) //字符串匹对成功,输入的位置信息完全正确,进行修改字符串操作
{
deletestr(head, info, i, strdelete); //删除字符串
addstr(info, stradd, lnum, i); //添加字符串
printf("\n已成功修改!\n");
}
else printf("\n输入的位置信息与要修改的文字内容不匹配!请重新输入!\n");
}
info = head;
printf("\n返回主菜单...\n");
}
//输出小说
void PrintfNovel(Novel* head, Novel* info)
{
int i, j, choice = 0, page = 1, prt = 1; //page记录当前输出第几页,prt = 1则打印小说,否则不打印
int number = 1; //记录info要从第几个结点开始打印
printf("小说输出如下:(输入-1返回主菜单)\n");
while (choice != -1)
{
if (prt == 1)
{
printf("\n+---------------------------------------------------------------------------------------------------+\n\n");
for (i = 0; i < PAGE; i++) //打印出一页
{
if (info != NULL) //若结点不为空,则读出
{
for (j = 0; j < MAXSIZE; j++) //打印出一行
{
if (info->data[j] == '\0') break; //打印中途读到‘\0’直接跳出开始打印下一段
else printf("%c", info->data[j]);
}
printf("\n");
if (info->Rlink != NULL && i < PAGE - 1) info = info->Rlink;
else break; //还未打印到一页的最后一行且下个结点不为空则继续打印,否则跳出打印循环
}
}
printf("\n******** 0:上一页 ------ 第%d页 ------ 1:下一页 ********\n\n", page);
printf("+---------------------------------------------------------------------------------------------------+\n\n");
}
scanf("%d", &choice);
switch (choice)
{
case 0:
if (info->number <= 5) //若当前在第一页,则没有上一页
{
printf("当前页没有上一页!请重新输入:\n");
prt = 0;
}
else
{
number = info->number - 4 - info->number % 5; //计算出要跳转到上一页的第一行的结点号
while (info->number != number) info = info->Llink;
page--;
prt = 1;
}
break;
case 1:
if (info->Rlink == NULL)
{
printf("当前页没有下一页!请重新输入:\n");
prt = 0;
}
else
{
info = info->Rlink;
page++;
prt = 1;
}
break;
case -1:break;
default:
printf("输入错误,请重新输入!");
prt = 0;
break;
}
}
info = head; //指针指回头指针方便后续操作
printf("\n返回主菜单...\n\n");
}
//保存小说
void ConserveNovel(Novel *head, Novel *info)
{
FILE* fp;
int i;
if ((fp = fopen("Novel.txt", "w")) == NULL)
{
printf("\n文件打不开!n");
exit(0);
}
printf("\n正在存入文件!\n");
while (info != NULL)
{
i = 0;
while (1) //保存一段的内容
{
while (i != MAXSIZE && info->data[i] != '\0') //保存一个结点的内容
{
putc(info->data[i], fp);
i++;
}
if (info->Rlink != NULL) //当前未为最后一个结点
{
if (i == MAXSIZE && info->para == info->Rlink->para) //本段未结束
{
i = 0;
info = info->Rlink;
}
if (info->data[i] == '\0' || (i == MAXSIZE && info->para != info->Rlink->para)) //检测到本段结束,则跳出
{
putc('\n', fp);
break;
}
}
else //最后一个结点
{
putc('\n', fp);
break;
}
}
info = info->Rlink;
}
printf("\n保存文件完毕!\n\n");
info = head;
fclose(fp);
}
//释放链表
void Empty(Novel *head)
{
Novel* p = head->Rlink;
while (1)
{
free(head);
if (p != NULL)
{
head = p;
p = head->Rlink;
}
else break;
}
printf("\n已释放所有内存!\n\n");
}
//主函数
int main()
{
Novel* head; //双向链表头指针
int choice = -1; //用于主菜单中的功能选择
char str[20] = { '\0' }; //保存输入的字符串,最多10个字
Novel* next = NULL, * info = NULL; //next用于申请新结点,info指向当前使用的结点
head = (Novel*)malloc(sizeof(Novel)); //申请头结点
info = head;
head->number = 1; //第一个节点
head->para = 1; //第一段
head->lnum = 1; //第一行
head->Llink = NULL;
head->Rlink = NULL;
HeadWord(); //小说编辑器大标题
while (choice != 0)
{
printf("+---------------------------------------------------------------------------------------------------+\n");
printf("+------------------------------------------主菜单---------------------------------------------------+\n");
printf("+---------------------------------------------------------------------------------------------------+\n");
printf("+----------------------------------------1.读入文件-------------------------------------------------+\n");
printf("+----------------------------------------2.搜索文字位置---------------------------------------------+\n");
printf("+----------------------------------------3.删除小说内容---------------------------------------------+\n");
printf("+----------------------------------------4.添加小说内容---------------------------------------------+\n");
printf("+----------------------------------------5.修改小说内容---------------------------------------------+\n");
printf("+----------------------------------------6.输出小说-------------------------------------------------+\n");
printf("+----------------------------------------7.保存小说-------------------------------------------------+\n");
printf("+----------------------------------------0.结束程序-------------------------------------------------+\n");
printf("+---------------------------------------------------------------------------------------------------+\n\n");
scanf("%d", &choice);
switch (choice)
{
case 0:break;
case 1:ReadNovel(head, next, info); break;
case 2:
printf("请输入你想查找的文字:(最多10个字)\n");
scanf("%s", &str); //输入要查的字符串
SearchNovel(head, info, str);
break;
case 3:
DeleteNovel(head, info);
printf("返回成功!\n\n");
break;
case 4:
AddNovel(head, info);
printf("返回成功!\n\n");
break;
case 5:
ChangeNovel(head, info);
printf("返回成功!\n\n");
break;
case 6:
PrintfNovel(head, info);
printf("返回成功!\n\n");
break;
case 7:ConserveNovel(head, info); break;
default:printf("输入错误,请重新输入!\n"); break;
}
}
Empty(head); //结束程序,释放全部结点
printf("退出程序成功,谢谢使用!\n");
return 0;
}
文件内容:我的只是我随便从网上粘贴下来的一段话,只要是全中文的就好。文件只需要跟代码文件在同一个文件夹内即可,不想的话就去复制它的全路径吧
如果读入打印出来是乱码的话,那就把你们的文件编码改为“ANSI”,别问,问就是这些坑我已经都帮你们踩过了
第一步:另存为
第二步:更改编码,最后点保存去原文件位置覆盖原文件就好