本文是为数据结构与算法课设《文本编辑》课题的一个分享,采用了结构体数组(本质还是顺序表)。采用了kmp算法进行查找。
目录
1.设计内容与要求
设计内容:
设计要求: (1)符合课题要求,实现相应功能; (2)要求界面友好美观,操作方便易行; (3)注意程序的实用性、安全性; |
2.功能分析与程序调试
1).功能分析
存储:对于这样一个最多行数和单行最多字符数都已知的情况,就不用去动态分配内存,直接 使用二维数组来存储字符信息。
统计:可以在进行输入数据时就对于文章信息做好统计,在对文本信息进行过修改后,先将原 先的数据清空,再重新进行一次统计。
查找:查找时进行逐行查找,先构建好这一行的next数组,然后使用kmp算法进行查找并统计 出现次数。
删除和替换:这里是通过BF算法和字符串函数来实现功能的,其实,和查找同理,也是可以通 过kmp算法来实现功能的。
2).程序调试
1. 程序开始运行,打印菜单进行选择。
2.信息的两种输入方式,键盘键入和文件导入,这里是使用了文件导入 的信息。
这是文件里存入的信息,是通过键入输入到系统中,然后再导出到文件里。
导入后 就开始进行了三步操作,查询,修改和删除。
3.代码实现
1.结构体定义
typedef struct contents {
char tents[M + 1] = { 1 };
int next[M + 1];
}contents;
typedef struct documennt {
contents con[N] = {1};
int num_line = 0;
}Document;
typedef struct num {
int letter_a = 0;
int letter_A = 0;
int number = 0;
int black = 0;
int punctuation = 0;
}Num;
Document Doc;
Num nums;
定义了三个结构体,Document是用于存储文本信息,内含二维数组和列数。在存储列时还存储了一个next数组方便kmp算法的调用。
2.信息输入
void Importbykoyboard() {
int i;
char c ;
while (1) {
i = 0;
while (i < M && (c = getchar()) != '\n') {
Doc.con[Doc.num_line].tents[i++] = c; // 将字符存储在数组中,并增加索引
count(c);
if (c == '#') { break; }
}
if (i == M) { Doc.con[Doc.num_line].tents[M] = '#'; } Doc.num_line++;
if (c == '\n') { break; }
}
printf("键盘输入已完成\n");
}
void Impotrbyfile() {
getchar();
char filename[50];
printf("请输入导出文件的名称(不含扩展名): ");
// 使用 fgets 代替 scanf_s 以避免缓冲区溢出,并允许空格
if (fgets(filename, sizeof(filename), stdin) != NULL) {
// 移除 fgets 读取的换行符(如果有)
size_t len = strlen(filename);
if (len > 0 && filename[len - 1] == '\n') {
filename[len - 1] = '\0';
}
}
strcat(filename, ".txt");
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("无法打开文件 %s !\n", filename);
return;
}
char line;
int wei = 0;
while ((line = fgetc(file)) != NULL && Doc.num_line < N) {
if (line == '\n') { wei = 0; Doc.num_line ++; }
else if (line == '\0' || line == EOF) { break; }
else { Doc.con[Doc.num_line].tents[wei] = line; wei++; }
}
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break;} }
}
fclose(file);
printf("已成功读取文件\n");
}
3.导出到文件
void P_file() {
getchar();
char filename[50];
printf("请输入导出文件的名称(不含扩展名): ");
// 使用 fgets 代替 scanf_s 以避免缓冲区溢出,并允许空格
if (fgets(filename, sizeof(filename), stdin) != NULL) {
// 移除 fgets 读取的换行符(如果有)
size_t len = strlen(filename);
if (len > 0 && filename[len - 1] == '\n') {
filename[len - 1] = '\0';
}
// 确保有足够的空间添加扩展名
if (strlen(filename) + 4 < sizeof(filename)) { // 4 是 ".txt" 的长度加上空字符
strcat(filename, ".txt"); // 自动添加扩展名
FILE* file = fopen(filename, "w");
if (file == NULL) {
printf("无法打开文件 %s !\a\n", filename);
return;
}
for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { fprintf(file, "%c", Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { fprintf(file, "\n"); } }if (i + 1 != N) { if (Doc.con[i].tents[M] = '#') { fprintf(file, "#"); fprintf(file, "\n"); } } }
fprintf(file, "\n\n");
fprintf(file, "数据统计:\n");
fprintf(file, "英文字母:%d个\n", nums.letter_A + nums.letter_a);
fprintf(file, "数字:%d个\n", nums.number);
fprintf(file, "空格:%d个\n", nums.black);
fprintf(file, "标点符号:%d个\n", nums.punctuation);
fclose(file);
printf("已成功写入文件\n\n");
}
else {
printf("文件名过长,无法添加扩展名。\n");
}
}
else {
printf("读取文件名时发生错误。\n");
}
}
4. 系统信息显示
void P_out() {
printf("*********************\n\n");
printf(" 信息显示: \n\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
printf("%c", Doc.con[i].tents[j]);
if (Doc.con[i].tents[j] == '#') { printf("\n"); }
}
if (i + 1 != N) { if (Doc.con[i].tents[M] == '#') { printf("#"); printf("\n"); } }
}
printf("\n\n");
printf(" 数据统计:\n");
printf(" 英文字母:%d个\n",nums.letter_A+nums.letter_a);
printf(" 数字:%d个\n", nums.number);
printf(" 空格:%d个\n", nums.black);
printf(" 标点符号:%d个\n\n", nums.punctuation);
printf(" 文章总字数:%d个\n\n", nums.letter_A + nums.letter_a+nums.punctuation+ nums.number+ nums.black);
printf("\n 显示完毕 \n");
printf("*********************\n\n");
}
5.查找字符串
void donext(int line)
{
int i = 0, j = 0; Doc.con[line].next[0] = -1;
while (i < strlen(Doc.con[line].tents))
{
if (j == -1 || Doc.con[line].tents[i] == Doc.con[line].tents[j])
{
i++; j++;
Doc.con[line].next[i] = j;
}
else
j = Doc.con[line].next[j];
}
}
int func_sum(char pattern[], int line) { //子串 列数
int textLen = strlen(Doc.con[line].tents);
int patternLen = strlen(pattern);
donext(line);
int i = 0, j = 0;
int count = 0;
while (i < textLen ) {
if (j == -1 || Doc.con[line].tents[i] == pattern[j]) {
i++;
j++;
}
else {
j = Doc.con[line].next[j];
}
if (j == patternLen) {
count++;
j = 0;
}
}
return count;
}
void Search_num() {
char search[M];
printf("请输入要查询的子串\n");
scanf("%s",search);
int sum = 0;
for (int i = 0; i < Doc.num_line; i++) { //对于每一行都进行一般查找
sum = sum + func_sum(search,i);
}
printf("子串%s出现了%d次\n",search,sum);
}
6.修改字符串
void func_string(int line,char S1[],char S2[]) {
char* original = Doc.con[line].tents;
int textLen = strlen(original);
int patternLen = strlen(S1);
int replacementLen = strlen(S2);
// 计算新字符串的最大可能长度 //设置为最大是为了防止数组越界 实际计算可以先计算要替换的子串出现的次数 再乘以与新子串的长度差
int newLen = textLen + (textLen / patternLen) * (replacementLen - patternLen) + 1;
char* result = (char*)malloc(newLen);
if (!result) {
return; // 内存分配失败
}
char* writePtr = result;
const char* readPtr = original;
while (*readPtr) {
if (strncmp(readPtr, S1, patternLen) == 0) {
// 找到了匹配项,复制替换字符串
memcpy(writePtr, S2, replacementLen);
writePtr += replacementLen;
readPtr += patternLen;
}
else {
// 没有找到匹配项,复制当前字符
*writePtr++ = *readPtr++;
}
}
*writePtr = '\0'; // 确保字符串以null终止
// 将结果复制回原始文本
strcpy(Doc.con[line].tents, result);
free(result);
}
void Search_string() {
getchar();
char string1[M];
char string2[M];
printf("请输入要修改的字符串\n");
fgets(string1,M,stdin);
string1[strcspn(string1, "\n")] = 0;
printf("请输入要替换的字符串\n");
fgets(string2, M, stdin);
string2[strcspn(string2, "\n")] = 0;
for (int i = 0; i < Doc.num_line; i++) {
func_string(i, string1, string2);
}
nums.letter_a = 0;
nums.letter_A = 0;
nums.number = 0;
nums.black = 0;
nums.punctuation = 0;
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break; } }
}
printf("替换完成\n");
}
7.删除字符串
void func_delete(int line,char S1[]) {
char* original = Doc.con[line].tents;
int textLen = strlen(original);
int patternLen = strlen(S1);
//int replacementLen = strlen(S2);
// 计算新字符串的最大可能长度 //设置为最大是为了防止数组越界 实际计算可以先计算要替换的子串出现的次数 再乘以与新子串的长度差
int newLen = textLen;
char* result = (char*)malloc(newLen); //存储数据的新数组
if (!result) {
return; // 内存分配失败
}
char* writePtr = result;
const char* readPtr = original;
while (*readPtr) {
if (strncmp(readPtr, S1, patternLen) == 0) {
readPtr += patternLen;
}
else {
// 没有找到匹配项,复制当前字符
*writePtr++ = *readPtr++;
}
}
*writePtr = '\0'; // 确保字符串以null终止
// 将结果复制回原始文本
for (int i = 0; i < textLen-1; i++) { Doc.con[line].tents[i] = 0; }
strcpy(Doc.con[line].tents, result);
free(result);
}
void Search_delete() {
getchar();
char string3[M];
printf("请输入要删除的字符串\n");
fgets(string3, M, stdin);
string3[strcspn(string3, "\n")] = 0;
for (int i = 0; i < Doc.num_line; i++) {
func_delete(i, string3);
}
nums.letter_a = 0;
nums.letter_A = 0;
nums.number = 0;
nums.black = 0;
nums.punctuation = 0;
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break; } }
}
printf("已完成删除\n\n");
}
8.主函数和菜单函数
void Menu() {
system("cls");
printf("\n********************************************\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");
printf("********************************************\n");
}
void Menuport() {
int input;
while (1) {
printf("请选择您的操作:");
scanf("%d", &input);
switch (input) {
case 1: {Menu(); char a = getchar(); printf("开始通过键盘输入信息\n"); printf("如果您想换行 请输入 # \n"); printf("最后回车结束输入\n"); Importbykoyboard(); break; }
case 2:Menu(); Impotrbyfile(); break;
case 3:Menu(); P_file(); break;
case 4:Menu(); Menu(); P_out(); break;
case 5:Menu(); Search_num();break;
case 6:Menu(); Search_string(); P_out(); break;
case 7:Menu(); Search_delete(); P_out(); break;
case 0:printf("您已退出文本编辑 感谢您的使用\n"); exit(-339);
}
}
}
int main() {
Menu();
Menuport();
return 0;
}
*9.完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 20 //文章的最大行数
#define M 80 //文章一行最多的字符数
typedef struct contents {
char tents[M + 1] = { 1 };
int next[M + 1];
}contents;
typedef struct documennt {
contents con[N] = {1};
int num_line = 0;
}Document;
typedef struct num {
int letter_a = 0;
int letter_A = 0;
int number = 0;
int black = 0;
int punctuation = 0;
}Num;
Document Doc;
Num nums;
void Importbykoyboard();
void Impotrbyfile();
void count(char c);
void P_out();
void P_file();
void donext(int line)
{
int i = 0, j = 0; Doc.con[line].next[0] = -1;
while (i < strlen(Doc.con[line].tents))
{
if (j == -1 || Doc.con[line].tents[i] == Doc.con[line].tents[j])
{
i++; j++;
Doc.con[line].next[i] = j;
}
else
j = Doc.con[line].next[j];
}
}
// KMP算法
int func_sum(char pattern[], int line) { //子串 列数
int textLen = strlen(Doc.con[line].tents);
int patternLen = strlen(pattern);
donext(line);
int i = 0, j = 0;
int count = 0;
while (i < textLen ) {
if (j == -1 || Doc.con[line].tents[i] == pattern[j]) {
i++;
j++;
}
else {
j = Doc.con[line].next[j];
}
if (j == patternLen) {
count++;
j = 0;
}
}
return count;
}
void Search_num() {
char search[M];
printf("请输入要查询的子串\n");
scanf("%s",search);
int sum = 0;
for (int i = 0; i < Doc.num_line; i++) { //对于每一行都进行一般查找
sum = sum + func_sum(search,i);
}
printf("子串%s出现了%d次\n",search,sum);
}
void func_string(int line,char S1[],char S2[]) {
char* original = Doc.con[line].tents;
int textLen = strlen(original);
int patternLen = strlen(S1);
int replacementLen = strlen(S2);
// 计算新字符串的最大可能长度 //设置为最大是为了防止数组越界 实际计算可以先计算要替换的子串出现的次数 再乘以与新子串的长度差
int newLen = textLen + (textLen / patternLen) * (replacementLen - patternLen) + 1;
char* result = (char*)malloc(newLen);
if (!result) {
return; // 内存分配失败
}
char* writePtr = result;
const char* readPtr = original;
while (*readPtr) {
if (strncmp(readPtr, S1, patternLen) == 0) {
// 找到了匹配项,复制替换字符串
memcpy(writePtr, S2, replacementLen);
writePtr += replacementLen;
readPtr += patternLen;
}
else {
// 没有找到匹配项,复制当前字符
*writePtr++ = *readPtr++;
}
}
*writePtr = '\0'; // 确保字符串以null终止
// 将结果复制回原始文本
strcpy(Doc.con[line].tents, result);
free(result);
}
void Search_string() {
getchar();
char string1[M];
char string2[M];
printf("请输入要修改的字符串\n");
fgets(string1,M,stdin);
string1[strcspn(string1, "\n")] = 0;
printf("请输入要替换的字符串\n");
fgets(string2, M, stdin);
string2[strcspn(string2, "\n")] = 0;
for (int i = 0; i < Doc.num_line; i++) {
func_string(i, string1, string2);
}
nums.letter_a = 0;
nums.letter_A = 0;
nums.number = 0;
nums.black = 0;
nums.punctuation = 0;
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break; } }
}
printf("替换完成\n");
}
void func_delete(int line,char S1[]) {
char* original = Doc.con[line].tents;
int textLen = strlen(original);
int patternLen = strlen(S1);
//int replacementLen = strlen(S2);
// 计算新字符串的最大可能长度 //设置为最大是为了防止数组越界 实际计算可以先计算要替换的子串出现的次数 再乘以与新子串的长度差
int newLen = textLen;
char* result = (char*)malloc(newLen); //存储数据的新数组
if (!result) {
return; // 内存分配失败
}
char* writePtr = result;
const char* readPtr = original;
while (*readPtr) {
if (strncmp(readPtr, S1, patternLen) == 0) {
readPtr += patternLen;
}
else {
// 没有找到匹配项,复制当前字符
*writePtr++ = *readPtr++;
}
}
*writePtr = '\0'; // 确保字符串以null终止
// 将结果复制回原始文本
for (int i = 0; i < textLen-1; i++) { Doc.con[line].tents[i] = 0; }
strcpy(Doc.con[line].tents, result);
free(result);
}
void Search_delete() {
getchar();
char string3[M];
printf("请输入要删除的字符串\n");
fgets(string3, M, stdin);
string3[strcspn(string3, "\n")] = 0;
for (int i = 0; i < Doc.num_line; i++) {
func_delete(i, string3);
}
nums.letter_a = 0;
nums.letter_A = 0;
nums.number = 0;
nums.black = 0;
nums.punctuation = 0;
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break; } }
}
printf("已完成删除\n\n");
}
void Importbykoyboard() {
int i;
char c ;
while (1) {
i = 0;
while (i < M && (c = getchar()) != '\n') {
Doc.con[Doc.num_line].tents[i++] = c; // 将字符存储在数组中,并增加索引
count(c);
if (c == '#') { break; }
}
if (i == M) { Doc.con[Doc.num_line].tents[M] = '#'; } Doc.num_line++;
if (c == '\n') { break; }
}
printf("键盘输入已完成\n");
}
void Impotrbyfile() {
getchar();
char filename[50];
printf("请输入导出文件的名称(不含扩展名): ");
// 使用 fgets 代替 scanf_s 以避免缓冲区溢出,并允许空格
if (fgets(filename, sizeof(filename), stdin) != NULL) {
// 移除 fgets 读取的换行符(如果有)
size_t len = strlen(filename);
if (len > 0 && filename[len - 1] == '\n') {
filename[len - 1] = '\0';
}
}
strcat(filename, ".txt");
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("无法打开文件 %s !\n", filename);
return;
}
char line;
int wei = 0;
while ((line = fgetc(file)) != NULL && Doc.num_line < N) {
if (line == '\n') { wei = 0; Doc.num_line ++; }
else if (line == '\0' || line == EOF) { break; }
else { Doc.con[Doc.num_line].tents[wei] = line; wei++; }
}
for (int i = 0; i < Doc.num_line; i++) {
for (int j = 0; j < M; j++) { count(Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { break;} }
}
fclose(file);
printf("已成功读取文件\n");
}
void count(char c) { //统计功能
if (c >='a' && c <= 'z') { nums.letter_a++; }
else if (c >= 'A' && c <= 'Z') { nums.letter_A++; }
else if (c >= '0' && c <= '9') { nums.number++; }
else if (c == ' ') { nums.black++;}
else { nums.punctuation++; }
}
void P_file() {
getchar();
char filename[50];
printf("请输入导出文件的名称(不含扩展名): ");
// 使用 fgets 代替 scanf_s 以避免缓冲区溢出,并允许空格
if (fgets(filename, sizeof(filename), stdin) != NULL) {
// 移除 fgets 读取的换行符(如果有)
size_t len = strlen(filename);
if (len > 0 && filename[len - 1] == '\n') {
filename[len - 1] = '\0';
}
// 确保有足够的空间添加扩展名
if (strlen(filename) + 4 < sizeof(filename)) { // 4 是 ".txt" 的长度加上空字符
strcat(filename, ".txt"); // 自动添加扩展名
FILE* file = fopen(filename, "w");
if (file == NULL) {
printf("无法打开文件 %s !\a\n", filename);
return;
}
for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { fprintf(file, "%c", Doc.con[i].tents[j]); if (Doc.con[i].tents[j] == '#') { fprintf(file, "\n"); } }if (i + 1 != N) { if (Doc.con[i].tents[M] = '#') { fprintf(file, "#"); fprintf(file, "\n"); } } }
fprintf(file, "\n\n");
fprintf(file, "数据统计:\n");
fprintf(file, "英文字母:%d个\n", nums.letter_A + nums.letter_a);
fprintf(file, "数字:%d个\n", nums.number);
fprintf(file, "空格:%d个\n", nums.black);
fprintf(file, "标点符号:%d个\n", nums.punctuation);
fclose(file);
printf("已成功写入文件\n\n");
}
else {
printf("文件名过长,无法添加扩展名。\n");
}
}
else {
printf("读取文件名时发生错误。\n");
}
}
void P_out() {
printf("*********************\n\n");
printf(" 信息显示: \n\n");
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
printf("%c", Doc.con[i].tents[j]);
if (Doc.con[i].tents[j] == '#') { printf("\n"); }
}
if (i + 1 != N) { if (Doc.con[i].tents[M] == '#') { printf("#"); printf("\n"); } }
}
printf("\n\n");
printf(" 数据统计:\n");
printf(" 英文字母:%d个\n",nums.letter_A+nums.letter_a);
printf(" 数字:%d个\n", nums.number);
printf(" 空格:%d个\n", nums.black);
printf(" 标点符号:%d个\n\n", nums.punctuation);
printf(" 文章总字数:%d个\n\n", nums.letter_A + nums.letter_a+nums.punctuation+ nums.number+ nums.black);
printf("\n 显示完毕 \n");
printf("*********************\n\n");
}
void Menu() {
system("cls");
printf("\n********************************************\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");
printf("********************************************\n");
}
void Menuport() {
int input;
while (1) {
printf("请选择您的操作:");
scanf("%d", &input);
switch (input) {
case 1: {Menu(); char a = getchar(); printf("开始通过键盘输入信息\n"); printf("如果您想换行 请输入 # \n"); printf("最后回车结束输入\n"); Importbykoyboard(); break; }
case 2:Menu(); Impotrbyfile(); break;
case 3:Menu(); P_file(); break;
case 4:Menu(); Menu(); P_out(); break;
case 5:Menu(); Search_num();break;
case 6:Menu(); Search_string(); P_out(); break;
case 7:Menu(); Search_delete(); P_out(); break;
case 0:printf("您已退出文本编辑 感谢您的使用\n"); exit(-339);
}
}
}
int main() {
Menu();
Menuport();
return 0;
}
4.问题总结和反思
关于这个课题,其实耗时挺长的,主要的问题是对于串操作的不熟悉,在查找,删除,和修改中,只有查找使用了kmp,但是删除和修改时还是暴力算法加字符串函数的匹配。还有一些遗留的问题,就是修改和删除时没有添加文本中不存在字符串的情况判断。