1. 背景说明
该文本编辑器利用串的堆实现,其中对串的原始存储方式基本不作修改(有部分修改之处),优化之处在于在串的末尾加上了一个空字符,目的是区分字符串结尾,便于将串保存在文件中,且该优化不对原来的功能造成影响,利用了二者的优点。
关键点在于从文件中读出字符串将其保存在串中和将串保存在文件中的处理方式,其实现原理图如下。
2. 示例代码
1) status.h
/* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H
#define STATUS_H#define CHECK_NULL(pointer) if (!(pointer)) { \printf("FuncName: %-15s Line: %-5d ErrorCode: %-3d\n", __func__, __LINE__, ERR_NULL_PTR); \return NULL; \
}#define CHECK_RET(ret) if (ret != RET_OK) { \printf("FuncName: %-15s Line: %-5d ErrorCode: %-3d\n", __func__, __LINE__, ret); \return ret; \
}#define CHECK_VALUE(value, ERR_CODE) if (value) { \printf("FuncName: %-15s Line: %-5d ErrorCode: %-3d\n", __func__, __LINE__, ERR_CODE); \return ERR_CODE; \
}#define CHECK_FALSE(value, ERR_CODE) if (!(value)) { \printf("FuncName: %-15s Line: %-5d ErrorCode: %-3d\n", __func__, __LINE__, ERR_CODE); \return FALSE; \
} /* 函数结果状态码 */
#define TRUE 1 /* 返回值为真 */
#define FALSE 0 /* 返回值为假 */
#define RET_OK 0 /* 返回值正确 */
#define INFEASIABLE 2 /* 返回值未知 */
#define ERR_MEMORY 3 /* 访问内存错 */
#define ERR_NULL_PTR 4 /* 空指针错误 */
#define ERR_MEMORY_ALLOCATE 5 /* 内存分配错 */
#define ERR_NULL_STACK 6 /* 栈元素为空 */
#define ERR_PARA 7 /* 函数参数错 */
#define ERR_OPEN_FILE 8 /* 打开文件错 */
#define ERR_NULL_QUEUE 9 /* 队列为空错 */
#define ERR_FULL_QUEUE 10 /* 队列为满错 */
#define ERR_NOT_FOUND 11 /* 表项不存在 */
typedef int Status; /* Status 是函数的类型,其值是函数结果状态代码,如 RET_OK 等 */
typedef int Bollean; /* Boolean 是布尔类型,其值是 TRUE 或 FALSE */#endif // !STATUS_H
2) hString.h
/* 串的堆分配存储实现头文件 */#ifndef HSTRING_H
#define HSTRING_H#include "status.h"typedef struct {char *ch;int length;
} HString;/* 初始化(产生空串)字符串 T */
void InitString(HString *T);/* 生成一个其值等于串常量 chars 的串 T */
Status StrAssign(const char *chars, HString *T);/* 初始条件: 串 S 存在操作结果: 由串 S 复制得串 T */
Status StrCopy(const HString *S, HString *T);/* 初始条件: 串 S 存在操作结果: 若 S 为空串,则返回 TRUE,否则返回 FALSE */
Bollean StrEmpty(const HString *S);/* 若 S > T,则返回值 > 0;若 S = T,则返回值 = 0;若 S < T,则返回值 < 0 */
int StrCompare(const HString *S, const HString *T);/* 返回 S 的元素个数,称为串的长度 */
int StrLength(const HString *S);/* 将 S 清为空串 */
Status ClearString(HString *S);/* 用 T 返回由 S1 和 S2 联接而成的新串 */
Status Concat(const HString *S1, const HString *S2, HString *T);/* 用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串其中,1 ≤ pos ≤ StrLength(S) 且 0 ≤ len ≤ StrLength(S) - pos + 1 */
Status SubString(const HString *S, int pos, int len, HString *Sub);/* 算法 4.1,T 为非空串。若主串 S 中第 pos 个字符之后存在与 T 相等的子串则返回第一个这样的子串在 S 中的位置,否则返回 0 */
int Index(const HString *S, const HString *T, int pos);/* 算法 4.4, 在串 S 的第 pos 个字符之前插入串 T, 1 ≤ pos ≤ StrLength(S) + 1 */
Status StrInsert(const HString *T, int pos, HString *S);/* 从串 S 中删除第 pos 个字符起长度为 len 的子串 */
Status StrDelete(int pos, int len, HString *S);/* 初始条件: 串 S, T 和 V 存在, T 是非空串(此函数与串的存储结构无关)操作结果: 用 V 替换主串 S 中出现的所有与 T 相等的不重叠的子串 */
Status Replace(const HString *T, const HString *V, HString *S);/* 堆分配类型的字符串无法销毁(结构体)*/
void DestroyString(void);/* 输出 T 字符串 */
void StrPrint(const HString *T);#endif // !HSTRING_H
3) hString.c
/* 串的堆分配存储实现源文件 */#include "hString.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>/* 初始化(产生空串)字符串 T */
void InitString(HString *T)
{T->length = 0;T->ch = NULL;
}/* 生成一个其值等于串常量 chars 的串 T */
Status StrAssign(const char *chars, HString *T)
{CHECK_VALUE(!chars, ERR_NULL_PTR);if (T->ch) {free(T->ch);}int length = (int)strlen(chars);if (length == 0) {T->ch = NULL;T->length = 0;return RET_OK;}T->ch = (char *)malloc(sizeof(char) * (unsigned long long )(length + 1));CHECK_VALUE(!(T->ch), ERR_MEMORY_ALLOCATE);T->length = length;T->ch[length] = '\0';for (int i = 0; i < length; ++i) {T->ch[i] = chars[i];}return RET_OK;
}/* 初始条件: 串 S 存在操作结果: 由串 S 复制得串 T */
Status StrCopy(const HString *S, HString *T)
{CHECK_VALUE(!S || !T, ERR_NULL_PTR);if (T->ch) {free(T->ch);}T->ch = (char *)malloc(sizeof(char) * (unsigned long long)(S->length + 1));CHECK_VALUE(!(T->ch), ERR_MEMORY_ALLOCATE);T->length = S->length;T->ch[S->length] = '\0';for (int i = 0; i < S->length; ++i) {T->ch[i] = S->ch[i];}return RET_OK;
}/* 初始条件: 串 S 存在操作结果: 若 S 为空串,则返回 TRUE,否则返回 FALSE */
Bollean StrEmpty(const HString *S)
{return ((S->length == 0) && (S->ch == NULL)) ? TRUE : FALSE;
}/* 若 S > T,则返回值 > 0;若 S = T,则返回值 = 0;若 S < T,则返回值 < 0 */
int StrCompare(const HString *S, const HString *T)
{CHECK_VALUE(!S || !T, ERR_NULL_PTR);for (int i = 0; i < S->length && i < T->length; ++i) {if (S->ch[i] != T->ch[i]) {return S->ch[i] - T->ch[i];}}return S->length - T->length;
}/* 返回 S 的元素个数,称为串的长度 */
int StrLength(const HString *S)
{return S->length;
}/* 将 S 清为空串 */
Status ClearString(HString *S)
{if (S->ch) {free(S->ch);S->ch = NULL;}S->length = 0;return RET_OK;
}/* 用 T 返回由 S1 和 S2 联接而成的新串 */
Status Concat(const HString *S1, const HString *S2, HString *T)
{CHECK_VALUE(!S1 || !S2 || !T, ERR_NULL_PTR);if (T->ch) {free(T->ch);}int length = S1->length + S2->length;T->ch = (char *)malloc(sizeof(char) * length);CHECK_VALUE(!(T->ch), ERR_MEMORY_ALLOCATE);for (int i = 0; i < S1->length; ++i) {T->ch[i] = S1->ch[i];}for (int i = 0; i < S2->length; ++i) {T->ch[S1->length + i] = S2->ch[i];}T->length = length;return RET_OK;
}/* 用 Sub 返回串 S 的第 pos 个字符起长度为 len 的子串其中,1 ≤ pos ≤ StrLength(S) 且 0 ≤ len ≤ StrLength(S) - pos + 1 */
Status SubString(const HString *S, int pos, int len, HString *Sub)
{CHECK_VALUE(!S || !Sub, ERR_NULL_PTR);CHECK_VALUE((pos < 1) || (pos > S->length) || (len < 0) || (len > S->length - pos + 1), ERR_PARA);if (Sub->ch) {free(Sub->ch);}if (len == 0) {Sub->ch = NULL;Sub->length = 0;} else {Sub->ch = (char *)malloc(sizeof(char) * (unsigned long long)(len + 1));CHECK_VALUE(!(Sub->ch), ERR_MEMORY_ALLOCATE);for (int i = 0; i < len; ++i) {Sub->ch[i] = S->ch[pos - 1 + i];}Sub->length = len;Sub->ch[Sub->length] = '\0';}return RET_OK;
}/* 算法 4.1,T 为非空串。若主串 S 中第 pos 个字符之后存在与 T 相等的子串则返回第一个这样的子串在 S 中的位置,否则返回 0 */
int Index(const HString *S, const HString *T, int pos)
{int maxRange = StrLength(S) - StrLength(T) + 1;CHECK_VALUE((pos < 1) || (pos > maxRange), 0);HString sub;InitString(&sub);while (pos <= maxRange) {SubString(S, pos, StrLength(T), &sub);if (StrCompare(&sub, T) != 0) {++pos;} else {return pos;}}return 0;
}/* 算法 4.4, 在串 S 的第 pos 个字符之前插入串 T, 1 ≤ pos ≤ StrLength(S) + 1 */
Status StrInsert(const HString *T, int pos, HString *S)
{CHECK_VALUE(!T || !S, ERR_NULL_PTR);CHECK_VALUE((pos < 1) || (pos > S->length + 1), ERR_PARA);if (T->length == 0) {return RET_OK;}S->ch = (char *)realloc(S->ch, sizeof(char) * (unsigned long long)(S->length + T->length + 1));CHECK_VALUE(!(S->ch), ERR_MEMORY_ALLOCATE);for (int i = S->length - 1; i >= pos - 1; --i) {S->ch[i + T->length] = S->ch[i];}S->length += T->length;for (int i = 0; i < T->length; ++i) {S->ch[pos - 1 + i] = T->ch[i];}S->ch[S->length] = '\0';return RET_OK;
}/* 从串 S 中删除第 pos 个字符起长度为 len 的子串 */
Status StrDelete(int pos, int len, HString *S)
{CHECK_VALUE(!S, ERR_NULL_PTR);CHECK_VALUE(pos - 1 + len > S->length, ERR_PARA);for (int i = pos - 1; i < S->length - len; ++i) {S->ch[i] = S->ch[i + len];}S->length -= len;S->ch = (char *)realloc(S->ch, sizeof(char) * (unsigned long long)(S->length + 1));S->ch[S->length] = '\0';return RET_OK;
}/* 初始条件: 串 S, T 和 V 存在, T 是非空串(此函数与串的存储结构无关)操作结果: 用 V 替换主串 S 中出现的所有与 T 相等的不重叠的子串 */
Status Replace(const HString *T, const HString *V, HString *S)
{CHECK_VALUE(!T || !V || !S, ERR_NULL_PTR);int pos = 1;do {pos = Index(S, T, pos);if (pos) {StrDelete(pos, StrLength(T), S);StrInsert(V, pos, S);pos += StrLength(V);}} while (pos);return RET_OK;
}/* 堆分配类型的字符串无法销毁(结构体)*/
void DestroyString(void)
{printf("Do not need to destroy!\n");
}/* 输出 T 字符串 */
void StrPrint(const HString *T)
{for (int i = 0; i < T->length; ++i) {putchar(T->ch[i]);}
}
4) textEditing.h
/* 文本行编辑实现头文件 */#ifndef TEXTEDITING_H
#define TEXTEDITING_H#include "hString.h"#define MAX_LINE_NUM 25
#define MAX_CHAR_NUM 75
#define NAME_LEN 20/* 显示文件内容 */
void ShowText(int lineNum, const HString *T);/* 插入新行 */
Status InsertNewLine(int *lineNum, HString *T);/* 删除内容 */
Status DeleteContent(int *lineNum, HString *T);/* 拷贝文件内容 */
Status CopyContent(int *lineNum, HString *T);/* 修改文本行 */
Status ModifyContent(int lineNum, HString *T);/* 查找字符串 */
Status SearchContent(int lineNum, const HString *T);/* 替换字符串 */
Status ReplaceContent(int lineNum, HString *T);/* 将文本行同步到文件中 */
Status SaveToFile(int nameLength, const char *fileName, int lineNum, const HString *T);/* 显示主菜单 */
void ShowMainMenu(void);/* 显示子菜单 */
void ShowSubMenu(void);/* 初始化 */
Status MainMenu(int nameLength, int maxLine, char *fileName, int *lineNum, HString *T);#endif // !TEXTEDITING_H
5) textEditing.c
/* 文本行编辑实现源文件 */#include "textEditing.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>/* 获取最多 n - 1 个字符保存在 str 指向的内存中 */
static char *SGets(int n, char *str)
{char *retVal = fgets(str, n, stdin);if (retVal) {char *find = strchr(str, '\n');if (find) {*find = '\0';} else {while (getchar() != '\n') {continue;}}}return retVal;
}/* 打开已存在文件或创建新文件 */
static Status OperateFile(int nameLength, const char *fileName)
{CHECK_VALUE(!fileName, ERR_NULL_PTR);CHECK_VALUE(nameLength > NAME_LEN, ERR_PARA);FILE *fp;errno_t err_ret = fopen_s(&fp, fileName, "ab+");CHECK_VALUE(err_ret != RET_OK, ERR_OPEN_FILE);fclose(fp);return RET_OK;
}/* 从文件中读取文本行 */
static Status ReadFromFile(int nameLength, const char *fileName, int *lineNum, HString *T)
{CHECK_VALUE(!fileName || !T, ERR_NULL_PTR);CHECK_VALUE((nameLength > NAME_LEN), ERR_PARA);FILE *fp;errno_t err_ret = fopen_s(&fp, fileName, "rb");CHECK_VALUE(err_ret != RET_OK, ERR_OPEN_FILE);char str[MAX_CHAR_NUM + 1];*lineNum = 0;Status ret = RET_OK;while ((fgets(str, MAX_CHAR_NUM + 1, fp) != NULL) && (*lineNum <= MAX_LINE_NUM)) {str[strlen(str) - 1] = '\0';ret = StrAssign(str, T + *lineNum);if (ret != RET_OK) {fclose(fp);break;}++(*lineNum);}CHECK_VALUE(ret != RET_OK, ret);fclose(fp);return RET_OK;
}/* 显示文件内容 */
void ShowText(int lineNum, const HString *T)
{for (int i = 0; i < lineNum; ++i) {printf("Line %d: ", i + 1);StrPrint(T + i);putchar('\n');}
}/* 在第 pos 行前插 num 行 */
static Status InsertLine(int pos, int num, const char str[][MAX_CHAR_NUM], HString *T, int *lineNum)
{CHECK_VALUE((*lineNum + num > MAX_LINE_NUM) || ((pos < 1) || (pos > (*lineNum + 1))), ERR_PARA);Status ret = RET_OK;for (int i = *lineNum - 1; i >= pos - 1; --i) {ret = StrCopy(T + i, T + i + num);CHECK_VALUE(ret != RET_OK, ret);}for (int i = 0; i < num; ++i) {CHECK_VALUE(strlen(str[i]) > MAX_CHAR_NUM, ERR_PARA);StrAssign(str[i], T + pos - 1 + i);++(*lineNum);}return RET_OK;
}/* 插入新行 */
Status InsertNewLine(int *lineNum, HString *T)
{printf("Please input the position(Line Before) you want to insert and the line num: ");int pos, num;scanf_s("%d%d", &pos, &num);getchar();CHECK_VALUE((*lineNum + num > MAX_LINE_NUM) || ((pos < 1) || (pos > (*lineNum + 1))), ERR_PARA);char str[MAX_LINE_NUM][MAX_CHAR_NUM] = { 0 };for (int i = 0; i < num; ++i) {printf("Please input the %dth string: ", i + 1);SGets(MAX_CHAR_NUM, str[i]);}Status ret = InsertLine(pos, num, str, T, lineNum);CHECK_VALUE(ret != RET_OK, ret);return RET_OK;
}/* 删除文本行 */
static Status DeleteLine(int pos, int num, HString *T, int *lineNum)
{CHECK_VALUE(!T || !lineNum, ERR_NULL_PTR);CHECK_VALUE((pos <= 0) || (pos > *lineNum - num + 1), ERR_PARA);for (int i = pos - 1 + num; i < *lineNum; ++i) {StrCopy(T + i, T + i - num);}*lineNum -= num;return RET_OK;
}/* 删除内容 */
Status DeleteContent(int *lineNum, HString *T)
{printf("Please input the pos(From) and num you want to delete: ");int pos, num;scanf_s("%d%d", &pos, &num);getchar();Status ret = DeleteLine(pos, num, T, lineNum);CHECK_VALUE(ret != RET_OK, ret);return RET_OK;
}/* 把第 pos 行开始的 num 行插在原 insPos 行之前 */
static Status CopyLine(int pos, int num, int insPos, HString *T, int *lineNum)
{CHECK_VALUE(!T || !lineNum, ERR_NULL_PTR);CHECK_VALUE((pos < 1) || (pos > *lineNum - num + 1) || (insPos < 1) || (insPos > *lineNum + 1)|| (*lineNum + num > MAX_LINE_NUM), ERR_PARA);char str[MAX_LINE_NUM][MAX_CHAR_NUM] = { 0 };for (int i = 0; i < num; ++i) {(void)strcpy_s(str[i], MAX_CHAR_NUM, (T + pos - 1 + i)->ch);}Status ret = InsertLine(insPos, num, str, T, lineNum);CHECK_VALUE(ret != RET_OK, ret);return RET_OK;
}/* 拷贝文件内容 */
Status CopyContent(int *lineNum, HString *T)
{CHECK_VALUE(!T || !lineNum, ERR_NULL_PTR);printf("Please input the pos and num and insPos: ");int pos, num, insPos;scanf_s("%d%d%d", &pos, &num, &insPos);getchar();Status ret = CopyLine(pos, num, insPos, T, lineNum);CHECK_VALUE(ret != RET_OK, ret);return RET_OK;
}static ModifyLine(const char *str, int pos, HString *T)
{Status ret = StrAssign(str, T + pos - 1);CHECK_VALUE(ret != RET_OK, ret);return ret;
}/* 修改文本行 */
Status ModifyContent(int lineNum, HString *T)
{CHECK_VALUE(!T, ERR_NULL_PTR);printf("Please input the pos you want to modify: ");int pos;scanf_s("%d", &pos);getchar();CHECK_VALUE((pos < 1) || (pos > lineNum), ERR_PARA);printf("Please input the content you want to fill: ");char str[MAX_CHAR_NUM];SGets(MAX_CHAR_NUM, str);Status ret = ModifyLine(str, pos, T);CHECK_VALUE(ret != RET_OK, ret);return ret;
}static Status SearchString(const char *str, int lineNum, const HString *T)
{HString s;InitString(&s);StrAssign(str, &s);Bollean continueFind = TRUE;for (int i = 0; (i < lineNum) && continueFind; ++i) {int start = 1;while (start) {start = Index(T + i, &s, start);if (start) {printf("Find in Line %d: ", i + 1);StrPrint(T + i);printf(" ,Find in %dth position\n", start);printf("Continue find?(Y/N) ");if (getchar() != 'Y') {getchar();continueFind = FALSE;break;}++start;getchar();}}}if (continueFind) {printf("Not find!\n");}return RET_OK;
}/* 查找字符串 */
Status SearchContent(int lineNum, const HString *T)
{CHECK_VALUE(!T, ERR_NULL_PTR);printf("Please input the string you want to find: ");char str[MAX_CHAR_NUM] = { 0 };SGets(MAX_CHAR_NUM, str);Status ret = SearchString(str, lineNum, T);CHECK_VALUE(ret != RET_OK, ret);return ret;
}static Status ReplaceString(const char *str, const char *strInstead, int lineNum, HString *T)
{HString s, sInstead;InitString(&s);InitString(&sInstead);StrAssign(str, &s);StrAssign(strInstead, &sInstead);int lengthChange = (int)(strlen(strInstead) - strlen(str));Bollean continueFind = TRUE;for (int i = 0; (i < lineNum) && continueFind; ++i) {int start = 1;while (start) {start = Index(T + i, &s, start);if (start) {CHECK_VALUE(StrLength(T + i) + lengthChange > MAX_CHAR_NUM, ERR_PARA);printf("Find in Line %d: ", i + 1);StrPrint(T + i);printf(" ,Find in %dth position\n", start);StrDelete(start, (int)strlen(str), T + i);StrInsert(&sInstead, start, T + i);printf("Continue replace?(Y/N) ");if (getchar() != 'Y') {getchar();continueFind = FALSE;break;}++start;getchar();}}}if (continueFind) {printf("Not find!\n");}return RET_OK;
}/* 替换字符串 */
Status ReplaceContent(int lineNum, HString *T)
{CHECK_VALUE(!T, ERR_NULL_PTR);printf("Please input the string you want to replace: ");char str[MAX_CHAR_NUM] = { 0 };SGets(MAX_CHAR_NUM, str);printf("Please input the string you want to instead: ");char strInstead[MAX_CHAR_NUM] = { 0 };SGets(MAX_CHAR_NUM, strInstead);Status ret = ReplaceString(str, strInstead, lineNum, T);CHECK_VALUE(ret != RET_OK, ret);return ret;
}/* 将文本行同步到文件中 */
Status SaveToFile(int nameLength, const char *fileName, int lineNum, const HString *T)
{CHECK_VALUE(!fileName || !T, ERR_NULL_PTR);CHECK_VALUE((nameLength > NAME_LEN) || (lineNum > MAX_LINE_NUM), ERR_PARA);FILE *fp;errno_t err_ret = fopen_s(&fp, fileName, "wb");CHECK_VALUE(err_ret != RET_OK, ERR_OPEN_FILE);char *str = NULL;for (int i = 0; i < lineNum; ++i) {fputs(T[i].ch, fp);fputc(10, fp);}fclose(fp);return RET_OK;
}/* 显示主菜单 */
void ShowMainMenu(void)
{printf("Main Menu:\n");printf("1. Open file(New or Existed)\n");printf("0. Exit system\n");
}/* 显示子菜单 */
void ShowSubMenu(void)
{printf("Sub Menu:\n");printf("1. Show text\n");printf("2. Insert new line\n");printf("3. Delete line\n");printf("4. Copy existed line\n");printf("5. Modify existed line\n");printf("6. Find string\n");printf("7. Replace string\n");printf("8. Save to file\n");printf("0. Exit to main menu\n");
}/* 初始化 */
Status MainMenu(int nameLength, int maxLine, char *fileName, int *lineNum, HString *T)
{CHECK_VALUE(!fileName || !lineNum || !T, ERR_NULL_PTR);CHECK_VALUE((nameLength > NAME_LEN) || (maxLine > MAX_LINE_NUM), ERR_PARA);printf("Please input the file name: ");SGets(NAME_LEN, fileName);OperateFile(nameLength, fileName);ReadFromFile(nameLength, fileName, lineNum, T);ShowText(*lineNum, T);return RET_OK;
}
6) main.c
/* 入口程序源文件 */#include "textEditing.h"
#include <stdio.h>
#include <string.h>int main(void)
{char fileName[NAME_LEN] = { 0 };HString T[MAX_LINE_NUM] = { 0 };for (int i = 0; i < MAX_LINE_NUM; ++i) {InitString(T + i);}int lineNum = 0;int mainChoose = 1, subChoose = 1;while (mainChoose) {printf("\n");ShowMainMenu();printf("Please input you choose: ");scanf_s("%d", &mainChoose);getchar();switch (mainChoose) {case 1:MainMenu((int)strlen(fileName), MAX_LINE_NUM, fileName, &lineNum, T);subChoose = 1;while (subChoose) {printf("\n");ShowSubMenu();printf("Please input your choose: ");scanf_s("%d", &subChoose);getchar();switch (subChoose) {case 1:ShowText(lineNum, T);break;case 2:InsertNewLine(&lineNum, T);break;case 3:DeleteContent(&lineNum, T);break;case 4:CopyContent(&lineNum, T);break;case 5:ModifyContent(lineNum, T);break;case 6:SearchContent(lineNum, T);break;case 7:ReplaceContent(lineNum, T);break;case 8:SaveToFile((int)strlen(fileName), fileName, lineNum, T);break;case 0:break;}}break;case 0:break;}}for (int i = 0; i < MAX_LINE_NUM; ++i) {ClearString(T + i);}return 0;
}
3. 运行示例