前言
BAMS Version 1.0系列更新日志
【2020.8.6】 BAMS V1.0/1.1
【2020.8.11】 BAMS V1.2
【2020.8.13】 BAMS V1.3
【2020.9.10】 BAMS V1.4
在编写这个系统的过程中,参考了以下资料和站上一些大佬的文档:
CSDN-C语言实现数据库操作
CSDN-C语言文件操作
CSDN-从B树、B+树、B*树谈到R 树
CSDN-C语言使用文件系统模拟数据库
一、BAMS基本功能实现介绍
BAMS是基于txt文件建构的模拟数据库,其中实现了基本的数据库/数据表创建、删除、查找功能,详细可见如下:
- 系统登录
- 数据库创建
- 数据库删除
- 数据库查找
- 代码数据文件创建
- 代码数据文件删除
- 代码数据文件查找/复制
这个是最开始的设计图,其实也是期末作业当时做的design。BAMS系统其实就是在这个基础上改进而来:
- 删除了代码文件修改功能 这里包括了原先的文件内容单独修改、增加、删除。因为在初始修改的时候,考虑到用户(一般是我自己)使用的时候一般已经有IDE处理好代码,这个系统无需再去提供编辑功能,而且这些功能容易对代码的缩进等重要信息产生错误编辑,故不考虑在系统中对文件进行编辑。
- 修改了代码数据读取/输出的方式 在原先的系统中,数据是按照表-行的方式进行输入和读取的。但当其应用到代码读取时,用户不可能一行一行地将代码重新输入系统界面。因此,BAMS系统新增了剪贴板的读取功能,该功能能够读取用户复制后存储在剪贴板的代码块,并一次性将其输入文件。该方法还应用于代码从文件的读取和显示。
- 优化了交互界面 虽然还是采用了命令行界面作为系统的交互界面,但是在信息提示区、显示区和输入区之间做了分界,优化了用户的体验。
这些功能的实现其实依赖于非常基本的C语言函数。详细使用方法已经写在了代码之中,在此不再过多赘述
在数据库-数据文件查询中,嵌入了B树的构建与查找功能,以希望提高文件的查找效率,包括了B树的构建,删除;结点的创建,插入,删除;结点递归查找等功能。
考虑到用户可能需要调用已经存在的代码数据,自V1.3开始,增加了一键复制代码数据到剪贴板的功能。
在BAMS V1.0完成时,这个系统还是存在着很多的问题,包括但不限于:
- B树算法依然存在一些问题。
- 对内存的占用可能在未知的情况下突然增大
- 用户交互界面8太行
- 需要小心保存所在文件夹文件,防止被修改
- 使用了自动读取剪贴板,可能造成读取错误
- 部分代码比较冗杂,需要考虑复用
- 操作比较复杂(只适合我自己的习惯)
经过了两个月大约300余次的测试,解决了B树算法上存在的问题,对用户界面也做了一些完善。对于文件保存的问题,可以独立设置文件夹的权限进行保护;操作复杂性其实也是可以接受的。但是剪贴板读取复制出现了一点问题,但是还没有找到原因。在之后的测试中希望能找出来。
特别需要注意的是,在使用BAMS系统时,尽量按照程序提示进行操作,以防出现编码错误。不要私自篡改注册表文件和库文件,以防程序读取出现问题。 ·代码文件与程序界面显示的数据可能存在出入,代码数据文件信息以程序界面的为准。
二、BAMS具体技术细节
1.主模块 MAIN.c
代码如下:
#include "Database.h"
#include "Btree.h"
int main() //----主函数----//
{
int temp = 0;
while (temp < MAXTIME) //无论账号还是密码,顶多只有3次机会,超出则程序自动关闭
{
if (DT_Login())
{
while (DT_Database_Func());
break;
}
temp++;
}
if (temp == MAXTIME)
{
system("cls");
printf("-----------------------------------\n");
printf("已达到输入次数上限,程序即将自动关闭\n");
printf("-----------------------------------\n");
Sleep(2000);
return 0;
}
system("cls");
printf("-----------------------------------\n");
printf("感谢您的使用\n");
printf("-----------------------------------\n");
return 0;
}
2.模拟数据库模块 DATABASE.h
代码如下:
2.0 前置声明
/**
* Code by C
* @version 1.40 2020-09-10
* @author MPX
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<windows.h>
#include<conio.h>
#include<ctype.h>
#include<errno.h>
#include "Btree.h"
#include "GetPlate.h"
#define MAXLEN 10 // 最大输入长度
#define MAXTIME 3 // 最大尝试次数
FILE* fp;
FILE* fp2;
FILE* fp3;
FILE* fp4;
FILE* fp5; //注册表文件
FILE* fp6; //注册表修改临时文件
//登录
int DT_Login();
//代码数据库界面
int DT_Database_Func();
//代码数据表界面
int DT_Table_Func(char* db_name);
//创建数据库
int DT_Database_Create();
//删除数据库
int DT_Database_Delete();
//调用数据库
int DT_Database_Open();
//创建代码数据文件
int DT_Table_Create(char* db_url, BTree* T, BTree_node* node, char* setting);
//删除代码数据文件
int DT_Table_Delete(char* db_url, BTree* T, BTree_node* node, char* seeting);
//查询代码数据文件
int DT_Data_Select(char* db_url, BTree* T, BTree_node* node);
//编码转换
int DT_AutoCode(char RID[]);
2.1 系统登录 Login
用户输入相应的用户名和密码,如果成功匹配则进入数据库界面;否则提示用户出错并提供重试机会(最大重试机会为3次)。
int DT_Login()
{
system("cls");
int i = 0, flag = 0;
char ch = 0;
char password[MAXLEN] = { 0 }, username[MAXLEN] = { 0 };
char true_password[] = "111", true_username[] = "111";
printf("-----------------------------------\n");
printf("< 代码数据管理系统 Version 1.40 >\n");
printf("< 最后更新日期: 2020.9.10 >\n");
printf("-----------------------------------\n");
printf("请您先登录\n");
printf("-----------------------------------\n");
printf("请输入账号:");
scanf_s("%s", username, MAXLEN); //scanf_s必须严格限定读入字符大小,否则就会报错
if (!strcmp(username, true_username)) //CASE_1:用户名输入正确
{
printf("请输入密码:");
scanf_s("%s", password, MAXLEN);
if (!strcmp(password, true_password)) //CASE_1_1:密码输入正确
{
return 1;
}
else //CASE_1_2:密码输入错误
{
printf("-----------------------------------\n");
printf("密码输入错误!\n");
printf("-----------------------------------\n");
printf("您有以下两个选择:\n");
printf("1.再次尝试\n2.退出程序\n");
printf("-----------------------------------\n");//35标准格式
printf("请输入您的选择");
scanf_s("%d", &flag);
if (flag == 1) return 0;
else exit(0);
}
}
else //CASE_2:用户名输入错误
{
printf("-----------------------------------\n");
printf("用户不存在!\n");
printf("-----------------------------------\n");
printf("您有以下两个选择:\n");
printf("1.再次输入账号\n2.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 0;
else exit(0);
}
}
2.2 数据库菜单 Database_Menu
int DT_Database_Func()
{
system("cls");
int flag = 0, flag_1 = 0, func = 0;
printf("-----------------------------------\n");
printf("功能界面\n");
printf("-----------------------------------\n");
printf("1.创建代码数据库\n2.删除代码数据库\n3.调用代码数据库\n4.退出程序\n");
printf("-----------------------------------\n");
while (flag_1 == 0)
{
printf("请输入需要使用的功能序号:");
scanf_s("%d", &func);
if (func <= 4 && func >= 1)
{
flag_1 = 1;
break;
}
else
{
printf("-----------------------------------\n");
printf("无效输入!\n");
}
}
switch (func)
{
case 1: while (DT_Database_Create()); break; //CASE_1:创建
case 2: while (DT_Database_Delete()); break; //CASE_2:删除
case 3: while (DT_Database_Open()); break; //CASE_3:调用
case 4: exit(0); break; //CASE_4:退出程序
}
/*
system("cls"); //在使用完上述功能后,判断用户是否还要继续操作
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.继续使用\n2.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1; //CASE_1:继续使用
if (flag == 2) return 0; //CASE_2:退出程序
*/
return 1;
}
2.3 数据表菜单 Table_Menu
int DT_Table_Func(char* db_url)
{
system("cls");
int flag = 0, func = 0, count = 0, flag_1 = 0;
char db_name[13] = { 0 }, SID[10], haven[16], file_haven[45]; char* p;
char setting[45];
errno_t err, err1;
BTree T;
BTree_CreateTree(&T, 3);
err = fopen_s(&fp, db_url, "r"); //不需要重复写这句话,因为err在条件时已经被赋值,如果写下这句话,会显示文件被占用从而使err的值发生改变【已修复】
fseek(fp, 0L, SEEK_SET);
while (fgets(haven, 16, fp) != NULL) //读取数据库文件中的文件名,在内存中生成B树
{
strcpy_s(SID, strlen(haven) + 1, haven); //分离尾缀
int key = atoi(SID);
BTree_Insert(&T, key);
}
fclose(fp);
printf("-----------------------------------\n");
strcpy_s(db_name, strlen(strtok_s(db_url, ".txt", &p)) + 1, strtok_s(db_url, ".txt", &p));
printf("%s\n", db_name);
//独立注册表设置
strcpy_s(setting, strlen(db_name) + 1, db_name);
strcat_s(setting, strlen(setting) + strlen("程序注册表.txt") + 1, "程序注册表.txt");
if ((err = fopen_s(&fp5, setting, "r")) == 0) {
printf("-----------------------------------\n");
printf("----< 代码数据文件注册表 >----\n");
printf("-----------------------------------\n");
while (fgets(file_haven, 45, fp5) != NULL) {
printf("%s", file_haven);
count++;
}
fclose(fp5);
}
printf("-----------------------------------\n");
printf("该库内总代码数据文件数:%d\n", count);
printf("-----------------------------------\n");
printf("1.创建代码数据文件\n2.删除代码数据文件\n3.查找代码数据文件\n4.添加代码【暂不支持】\n5.删除代码【暂不支持】\n6.修改代码【暂不支持】\n7.退出程序\n");
while (flag_1 == 0)
{
printf("请输入需要使用的功能序号:");
scanf_s("%d", &func);
if (func <= 3 && func >= 1)
{
flag_1 = 1;
break;
}
else
{
printf("-----------------------------------\n");
printf("无效输入!\n");
}
}
switch (func)
{
case 1: while (DT_Table_Create(db_url, &T, T.root, setting)); break; //CASE_1:创建代码数据文件
case 2: while (DT_Table_Delete(db_url, &T, T.root, setting)); break; //CASE_2:删除代码数据文件
case 3: while (DT_Data_Select(db_url, &T, T.root)); break; //CASE_3:查询代码数据文件
case 4: break; //CASE_4:添加数据【暂不支持】
case 5: break; //CASE_5:删除数据【暂不支持】
case 6: //CASE_6:修改数据【暂不支持】
case 7: exit(0); break; //CASE_7:退出程序
}
system("cls");
strcat_s(db_name, strlen(db_name) + strlen(".txt") + 1, ".txt");
err1 = fopen_s(&fp4, "db_temp.txt", "w"); //更新数据库文件内容
BTree_Traverse(&T, T.root, 0);
remove(db_name);
fclose(fp4);
rename("db_temp.txt", db_name);
printf("-----------------------------------\n");
printf("1.继续使用\n2.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 0; //CASE_1:继续使用
if (flag == 2) exit(0); //CASE_3:退出程序
return 0;
}
2.4 数据库创建 Database_Create
int DT_Database_Create() //----数据库创建----// 用户根据提示进行数据库创建操作
{
system("cls");
int flag = 0;
char db_name[10] = { 0 }, db_url[18], setting[30];
errno_t err, err1;
printf("-----------------------------------\n");
printf("新建代码库\n");
printf("-----------------------------------\n");
printf("请输入代码库的名字(10个字以内):");
scanf_s("%s", db_name, 10);
strcpy_s(db_url, strlen(db_name) + 1, db_name);
strcat_s(db_url, strlen(db_url) + strlen("代码库") + 1, "代码库");
strcpy_s(setting, strlen(db_url) + 1, db_url);
strcat_s(setting, strlen(setting) + strlen("程序注册表.txt") + 1, "程序注册表.txt");
err1 = fopen_s(&fp5, setting, "w");
fclose(fp5);
strcat_s(db_url, strlen(db_url) + strlen(".txt") + 1, ".txt");
if ((err = fopen_s(&fp, db_url, "r")) != 0)
{
err = fopen_s(&fp, db_url, "w");
fclose(fp);
printf("-----------------------------------\n");
printf("创建成功!\n");
Sleep(1000);
return 0;
}
else
{
system("cls");
printf("-----------------------------------\n");
printf("Error:该代码库已经被创建,或存在命名错误\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.重新创建\n2.取消创建\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入你的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("-----------------------------------\n");
printf("没有该选项\n结束创建");
Sleep(1000);
return 0;
}
}
}
2.5 数据库删除 Database_Delete
int DT_Database_Delete() //----数据库删除(二级界面)----// 用户根据提示进行数据库删除操作
{
system("cls");
int flag = 0;
char db_name[10] = { 0 }, db_url[18], haven[18], setting[30];
//size_t errmsglen = 100;//strerrorlen_s(errno) + 1;
char errmsg[100];
errno_t err;
printf("-----------------------------------\n");
printf("删除代码库\n");
printf("-----------------------------------\n");
printf("请输入需要删除的代码库名:");
scanf_s("%s", db_name, 10);
strcpy_s(db_url, strlen(db_name) + 1, db_name);
strcat_s(db_url, strlen(db_url) + strlen("代码库") + 1, "代码库");
strcpy_s(setting, strlen(db_url) + 1, db_url);
strcat_s(setting, strlen(setting) + strlen("程序注册表.txt") + 1, "程序注册表.txt");
remove(setting);
strcat_s(db_url, strlen(db_url) + strlen(".txt") + 1, ".txt");
err = fopen_s(&fp, db_url, "r"); //对相关的库进行遍历删除
while (fscanf_s(fp, "%s", haven, 18) != EOF) remove(haven); //使用了fscanf函数用于防止程序读入\n造成错误
fclose(fp);
if (!(remove(db_url)))
{
printf("-----------------------------------\n");
printf("删除成功\n");
printf("-----------------------------------\n");
Sleep(1000);
return 0;
}
else
{
system("cls");
printf("-----------------------------------\n");
printf("在本次运行打开过的库删除会失败,建议退出程序再运行删除。\n"); //程序漏洞提示
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.再次删除\n2.取消删除\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入你的选择:\n");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("-----------------------------------\n");
printf("没有该选项\n结束删除");
Sleep(1000);
return 0;
}
}
}
2.6 数据库调用 Database_Open
int DT_Database_Open() //----数据库打开----// 数据库三级界面入口
{
system("cls");
int flag = 0;
char db_name[10] = { 0 }, db_url[18];
errno_t err;
printf("-----------------------------------\n");
printf("打开代码库\n");
printf("-----------------------------------\n");
printf("请输入需要打开的代码库名:");
scanf_s("%s", db_name, 10);
strcpy_s(db_url, strlen(db_name) + 1, db_name);
strcat_s(db_url, strlen(db_url) + strlen("代码库") + 1, "代码库");
strcat_s(db_url, strlen(db_url) + strlen(".txt") + 1, ".txt");
if ((err = fopen_s(&fp, db_url, "r")) == 0) //判断数据库是否存在,若存在则进入数据库操作三级界面,否则返回不存在提示并令用户选择下一步操作
{
fclose(fp);
printf("-----------------------------------\n");
printf("成功打开代码库\n");
Sleep(1000);
while (DT_Table_Func(db_url));//?
return 0;
}
else
{
system("cls");
printf("-----------------------------------\n");
printf("该库不存在\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.打开另一个代码库\n2.取消打开\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1; //继续使用
if (flag == 2) return 0; //取消打开,跳出循环
if (flag == 3) exit(0); //退出程序
else
{
printf("-----------------------------------\n");
printf("没有该选项\n结束打开");
Sleep(1000);
return 0;
}
}
}
2.7 代码数据文件创建 Table_Create
int DT_Table_Create(char* db_url, BTree* T, BTree_node* node, char* setting) //----数据表创建模块(四级界面)----// 用户根据提示创建数据表
{
system("cls");
int flag = 0, n = 0, i = 0, line;
char research_id[10] = { 0 };
char Rid[14], file_name[30], file_url[45], code[200];
char* p;
errno_t err;
printf("-----------------------------------\n");
printf("新建代码数据文件\n");
printf("-----------------------------------\n");
printf("标准格式:202001-01\n");
printf("请按照格式输入代码数据文件的编号:");
rewind(stdin);
scanf_s("%s", research_id, 10);
strcpy_s(Rid, strlen("TEST") + 1, "TEST");
strcat_s(Rid, strlen(Rid) + strlen(research_id) + 1, research_id);
int key = DT_AutoCode(research_id);
printf("请输入代码数据文件的标题(10字以内):");
scanf_s("%s", file_name, 30);
strtok_s(db_url, "代码库", &p);
strcpy_s(file_url, strlen(db_url) + 1, db_url);
strcat_s(file_url, strlen(file_url) + strlen("-") + 1, "-");
strcat_s(file_url, strlen(file_url) + strlen(Rid) + 1, Rid);
strcat_s(file_url, strlen(file_url) + strlen(file_name) + 1, file_name);
strcat_s(file_url, strlen(file_url) + strlen(".txt") + 1, ".txt");
err = fopen_s(&fp5, setting, "a+");
fprintf(fp5, "%s\n", file_url);
fclose(fp5);
if (BTree_Search(&T, node, key, 0) == -1)
{
BTree_Insert(T, key);
err = fopen_s(&fp2, file_url, "w"); //以只写方式打开,若文件不存在则建立文件
printf("-----------------------------------\n");
printf("请输入数据(可复制):\n");
printf("----------------------------------\n");
GetPlateString(fp2);
fclose(fp2);
fflush(stdin);
printf("创建成功\n");
printf("-----------------------------------\n");
printf("按下任意键继续......");
_getch();
return 0;
}
else
{
system("cls");
printf("-----------------------------------\n");
printf("该数据表名称已经存在,或命名错误\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.重新创建数据表\n2.取消创建数据表\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1; //CASE_1:重新创建
if (flag == 2) return 0; //CASE_2:取消创建
if (flag == 3) exit(0); //CASE_3:退出程序
else
{
printf("-----------------------------------\n");
printf("选项不存在!\n取消创建数据表");
Sleep(2000);
return 0;
}
}
}
2.8 代码数据文件删除 Table_Delete
int DT_Table_Delete(char* db_url, BTree* T, BTree_node* node, char* setting) //----数据表删除模块----//
{
system("cls");
int flag;
errno_t err, err1;
char research_id[10] = { 0 };
char Rid[14], file_name[30], file_url[45], haven[45], code[200];
char* p;
printf("-----------------------------------\n");
printf("删除代码数据文件\n");
printf("-----------------------------------\n");
printf("标准格式:202001-01\n");
printf("请按照格式输入代码数据文件的编号:");
rewind(stdin);
scanf_s("%s", research_id, 10);
strcpy_s(Rid, strlen("TEST") + 1, "TEST");
strcat_s(Rid, strlen(Rid) + strlen(research_id) + 1, research_id);
int key = DT_AutoCode(research_id);
printf("请输入代码数据文件的标题(10字以内):");
scanf_s("%s", file_name, 30);
strtok_s(db_url, "代码库", &p);
strcpy_s(file_url, strlen(db_url) + 1, db_url);
strcat_s(file_url, strlen(file_url) + strlen("-") + 1, "-");
strcat_s(file_url, strlen(file_url) + strlen(Rid) + 1, Rid);
strcat_s(file_url, strlen(file_url) + strlen(file_name) + 1, file_name);
strcat_s(file_url, strlen(file_url) + strlen(".txt") + 1, ".txt");
BTree_Print(&T, T->root, 0);
if (BTree_Search(&T, node, key, 0) != -1)
{
printf("-----------------------------------\n");
printf("该代码数据文件存在!\n");
printf("B树查询编号 SID :%d\n", key);
printf("代码数据文件名 RID :%s\n", file_url);
printf("-----------------------------------\n");
err = fopen_s(&fp5, setting, "r");
err1 = fopen_s(&fp6, "_temp.txt", "w");
while (fscanf_s(fp5, "%s\n", haven, 45) != EOF)
{
if (strcmp(haven, file_url) == 0)
{
continue;
}
else fprintf(fp6, "%s\n", haven);
}
fclose(fp5);
remove(setting);
fclose(fp6); //先关闭文件,后修改名字
rename("_temp.txt", setting);
if (!(remove(file_url))) //若删除成功则返回0,此处还没有进行测试
{
BTree_DeleteTree(T, key);
printf("删除成功!\n");
printf("-----------------------------------\n");
printf("按下任意键继续......");
_getch();
return 0;
}
else //删除失败
{
system("cls");
printf("-----------------------------------\n");
printf("删除代码数据文件出错!\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.重新删除\n2.结束删除\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("-----------------------------------\n");
printf("没有该选项\n结束删除");
//Sleep(1000);
return 0;
}
}
}
//CASE_2:该数据表不在数据库内
system("cls");
printf("-----------------------------------\n");
printf("该代码数据文件不存在于库!\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.删除其他\n2.取消删除\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("-----------------------------------\n");
printf("没有该选项\n取消删除");
Sleep(2000);
return 0;
}
}
2.9 代码数据文件调用 Table_Select
int DT_Data_Select(char* db_url, BTree* T, BTree_node* node)
{
system("cls");
int flag = 0, flag1;
int a;
char* p;
char research_id[10] = { 0 };
char Rid[14], file_name[30], file_url[45], code[1000];
errno_t err;
printf("-----------------------------------\n");
printf("查找代码数据\n");
printf("-----------------------------------\n");
printf("标准格式:202001-01\n");
printf("请按照格式输入代码数据文件的编号:");
scanf_s("%s", research_id, 10);
strcpy_s(Rid, strlen("TEST") + 1, "TEST");
strcat_s(Rid, strlen(Rid) + strlen(research_id) + 1, research_id);
int key = DT_AutoCode(research_id);
printf("请输入代码数据文件的标题(10字以内):");
scanf_s("%s", file_name, 30);
strtok_s(db_url, "代码库", &p);
strcpy_s(file_url, strlen(db_url) + 1, db_url);
strcat_s(file_url, strlen(file_url) + strlen("-") + 1, "-");
strcat_s(file_url, strlen(file_url) + strlen(Rid) + 1, Rid);
strcat_s(file_url, strlen(file_url) + strlen(file_name) + 1, file_name);
strcat_s(file_url, strlen(file_url) + strlen(".txt") + 1, ".txt");
if (BTree_Search(&T, node, key, 0) != -1)
{
printf("-----------------------------------\n");
printf("代码数据文件存在!\n");
printf("B树查询编号 SID :%d\n", key);
printf("代码数据文件名 RID :%s\n", file_url);
printf("-----------------------------------\n");
if ((err = fopen_s(&fp2, file_url, "r") == 0))
{
while (fgets(code, 1000, fp2) != NULL)
{
printf("%s", code);
}
printf("查询代码成功!\n");
//fclose(fp2);
printf("-----------------------------------\n");
printf("是否需要复制代码数据?\n1.需要复制\n2.不需要\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag1);
if (flag1 == 1) StringAutoCopy(fp2);
fclose(fp2);
printf("按下任意键继续......");
_getch();
return 0;
}
else
{
system("cls");
printf("-----------------------------------\n");
printf("查询代码数据文件出错!\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.重新查询\n2.结束查询\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("没有该选项\n结束查询");
return 0;
}
}
}
system("cls");
printf("-----------------------------------\n");
printf("该代码数据文件不存在于该数据库!\n");
printf("-----------------------------------\n");
printf("您有以下选择:\n");
printf("1.查询其他\n2.取消查询\n3.退出程序\n");
printf("-----------------------------------\n");
printf("请输入您的选择:");
scanf_s("%d", &flag);
if (flag == 1) return 1;
if (flag == 2) return 0;
if (flag == 3) exit(0);
else
{
printf("-----------------------------------\n");
printf("没有该选项\n结束查询");
Sleep(1000);
return 0;
}
}
2.10 自动转码 AutoCode
int DT_AutoCode(char RID[]) //----自动编码----// 用户输入查询编号,自动将查询编号 RID 转换为搜索编号 SID
{
int i,j;
for(i = 0, j = 0; *(RID + i) != '\0'; i++)
{
if (*(RID + i) == '-')
continue;
else
{
*(RID + j) = *(RID + i);
j++;
}
}
*(RID + j) = '\0';
int SID = atoi(RID);
return SID;
}
3.B树查找模块
3.0 前置声明
/**
* Code by C
* @version 1.40 2020-09-10
* @author MPX
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#define MaxM 10 //B树最大阶数
typedef struct BTree_node { //B树和B树结点类型
int* keys; //结点关键字指针
struct BTree_node** childrens; //孩子指针
int num; //关键字个数
int leaf; //叶结点
}BTree_node;
typedef struct BTree {
BTree_node* root;
int t;
}BTree;
//查找(k为要查找的内容)
int BTree_Search(BTree* T, BTree_node* node, int k, int layer);
//创建B树结点
BTree_node* BTree_CreateNode(int t, int leaf);
//创建B树
void BTree_CreateTree(BTree* T, int t);
//摧毁B树结点
void BTree_Destory(BTree_node* node);
//分裂结点x的第i个孩子
void BTree_SplitChild(BTree* T, BTree_node* x, int i);
//插入前对关键字的排序
void BTree_Insert_Nonfull(BTree* T, BTree_node* x, int k);
//在B树中插入关键字
void BTree_Insert(BTree* T, int key);
//遍历B树并将其结点输入文件
void BTree_Traverse(BTree* T, BTree_node* node, int layer);
//打印显示整棵树
void BTree_Print(BTree* T, BTree_node* node, int layer);
//B树删除关键字
void BTree_DeleteKey(BTree* T, BTree_node* node, int key);
//删除B树
int BTree_DeleteTree(BTree* T, int key);
3.1 递归查找 Node_Search
int BTree_Search(BTree* T, BTree_node* node, int k, int layer) // k为要查找的内容,layer默认为0,查找成功返回关键字结点(int),失败返回-1
{
BTree_node* p = node;
int i;
if (p)
{
for (i = 0;i < node->num;i++)
{
if (p->keys[i] == k)
return p->keys[i];
}
layer++;
for (i = 0;i <= p->num;i++)
{
if (p->childrens[i])
{
if ((BTree_Search(T, p->childrens[i], k, layer)) != -1)
{
return BTree_Search(T, p->childrens[i], k, layer);
break;
}
else
continue;
}
}
}
return -1;
}
3.2 创建B树结点 Node_Create
BTree_node* BTree_CreateNode(int t, int leaf) //创建B树结点
{
BTree_node* node = (BTree_node*)calloc(1, sizeof(BTree_node)); //分配内存空间,返回指向指针,并设置分配的内存为0
if (node == NULL) assert(0); //不存在结点,程序停止运行
node->leaf = leaf;
node->keys = (int*)calloc(1, (2 * t - 1) * sizeof(int)); //给键结点分配内存用尽
node->childrens = (BTree_node**)calloc(1, (2 * t) * sizeof(BTree_node)); //给孩子分配内存空间
node->num = 0;
return node;
}
3.3 创建B树 Tree_Create
void BTree_CreateTree(BTree* T, int t) //创建B树
{
T->t = t;
BTree_node* x = BTree_CreateNode(t, 1);
T->root = x;
}
3.4 删除B树结点 Node_Delete
void BTree_Destory(BTree_node* node) //摧毁B树
{
assert(node); //若存在node,则向下执行;若不存在,终止程序
free(node->childrens);
free(node->keys);
free(node);
}
3.5 分裂结点的孩子 Node_SpiltChild
void BTree_SplitChild(BTree* T, BTree_node* x, int i) //分裂结点x的第i个孩子
{
int t = T->t;
BTree_node* y = x->childrens[i];
BTree_node* z = BTree_CreateNode(t, y->leaf); //创建一个B树结点
z->num = t - 1;
int j = 0;
for (j = 0;j < t - 1;j++)
{
z->keys[j] = y->keys[j + t];
}
if (y->leaf == 0) //如果叶结点为0,将孩子结点下移一层
{
for (j = 0;j < t;j++)
{
z->childrens[j] = y->childrens[j + t];
}
}
y->num = t - 1;
for (j = x->num;j >= i + 1;j--)
{
x->childrens[j + 1] = x->childrens[j];
}
x->childrens[i + 1] = z;
for (j = x->num - 1;j >= i;j--)
{
x->keys[j + 1] = x->keys[j];
}
x->keys[i] = y->keys[t - 1];
x->num += 1;
}
3.6 关键字排序 Key_Sort
void BTree_Insert_Nonfull(BTree* T, BTree_node* x, int k) //插入前对关键字的排序
{
int i = x->num - 1; //i为关键字个数-1,数组中即为关键字对应下标
if (x->leaf == 1)
{
while (i >= 0 && x->keys[i] > k) //比较前后关键字大小,排序
{
x->keys[i + 1] = x->keys[i];
i--;
}
x->keys[i + 1] = k;
x->num += 1;
}
else
{
while (i >= 0 && x->keys[i] > k)
i--;
if (x->childrens[i + 1]->num == (2 * (T->t)) - 1)
{
BTree_SplitChild(T, x, i + 1); //分裂结点
if (k > x->keys[i + 1])
i++;
}
BTree_Insert_Nonfull(T, x->childrens[i + 1], k); //递归,分裂后重新排序关键字
}
}
3.7 关键字插入 Key_Insert
void BTree_Insert(BTree* T, int key) //在B树中插入关键字
{
BTree_node* r = T->root;
if (r->num == 2 * T->t - 1) // 若该结点中关键码个数等于阶数-1,则将引起结点的分裂。以中间关键码为界将结点一分为二,产生一个新结点,并把中间关键码插入到父结点(k-1层)中
{
BTree_node* node = BTree_CreateNode(T->t, 0);
T->root = node;
node->childrens[0] = r;
BTree_SplitChild(T, node, 0); //插入叶子结点后不满足B树性质,需要对叶子结点进行分裂
int i = 0;
if (node->keys[0] < key)
i++;
BTree_Insert_Nonfull(T, node->childrens[i], key);
}
else
{
BTree_Insert_Nonfull(T, r, key); //关键码个数小于阶数-1,直接插入
}
}
3.8 遍历B树输出 Tree_Traverse
FILE* fp;
void BTree_Traverse(BTree* T, BTree_node* node, int layer) //遍历B树写入库文件
{
BTree_node* p = node;
int i;
if (p) {
for (i = 0;i < node->num;i++)
fprintf(fp, "%d\n", node->keys[i]); //写入文件
layer++;
for (i = 0;i <= p->num;i++)
if (p->childrens[i])
BTree_Traverse(T, p->childrens[i], layer);
}
else printf("空\n");
}
3.9 打印B树(测试用)Tree_Print
void BTree_Print(BTree* T, BTree_node* node, int layer)
{
BTree_node* p = node;
int i;
if (p) {
printf("\n层数 = %d 关键字个数 = %d 是否叶结点 = %d\n", layer, p->num, p->leaf);
for (i = 0;i < node->num;i++)
printf("%d ", p->keys[i]);
printf("\n");
layer++;
for (i = 0;i <= p->num;i++)
if (p->childrens[i])
BTree_Print(T, p->childrens[i], layer);
}
else printf("该树为空树\n");
}
3.10 关键字合并 Key_Merge
void BTree_Merge(BTree* T, BTree_node* node, int idx) //把内结点node的idx个孩子和idx+1个孩子合并
{
BTree_node* left = node->childrens[idx]; //第idx个孩子
BTree_node* right = node->childrens[idx + 1]; //第idx+1个孩子
int i = 0;
left->keys[T->t - 1] = node->keys[idx]; //数据合并
for (i = 0;i < T->t - 1;i++)
{
left->keys[T->t + i] = right->keys[i];
}
if (!left->leaf)
{
for (i = 0;i < T->t;i++) {
left->childrens[T->t + i] = right->childrens[i];
}
}
left->num += T->t;
BTree_Destory(right); //抹去右部分
for (i = idx + 1;i < node->num;i++)
{
node->keys[i - 1] = node->keys[i];
node->childrens[i] = node->childrens[i + 1];
}
node->childrens[i + 1] = NULL;
node->num -= 1;
if (node->num == 0)
{
T->root = left;
BTree_Destory(node);
}
}
3.12 关键字删除 Key_Delete
void BTree_DeleteKey(BTree* T, BTree_node* node, int key) //B树删除关键字
{
if (node == NULL) return;
int idx = 0, i;
while (idx<node->num && key>node->keys[idx])
{
idx++;
}
if (idx < node->num && key == node->keys[idx]) {
if (node->leaf) { //关键字 key 在结点 node 中,node是叶结点, 直接在 node 中删除 key
for (i = idx;i < node->num - 1;i++)
{
node->keys[i] = node->keys[i + 1];
}
node->keys[node->num - 1] = 0;
node->num--;
if (node->num == 0)
{
free(node);
T->root = NULL;
}
return;
}
else if (node->childrens[idx]->num >= T->t)
//结点node前于 key 的子结点 left 至少含有 t 个关键字, 找出 key 在以 left 为根的子树中的前驱 key-1, 递归地删除key-1,
//并在node中用key-1 代替key
{
BTree_node* left = node->childrens[idx];
node->keys[idx] = left->keys[left->num - 1];
BTree_DeleteKey(T, left, left->keys[left->num - 1]);
}
else if (node->childrens[idx + 1]->num >= T->t)
/*对称的情况, 如果left中少于 t-1 个结点, 检查结点 node 中后于 key 的子结点 right. 如果 right 有 t 个关键字,
则找出 key 在以 right 为根的子树中的后继 key+1. 递归地删除 key+1, 并在 node 中 用 key+1 代替 key*/
{
BTree_node* right = node->childrens[idx + 1];
node->keys[idx] = right->keys[0];
BTree_DeleteKey(T, right, right->keys[0]);
}
else
/*left和right都只是有t-1 个结点, 则把 key 和 right 合并进入结点 left, 此时的 node 失去了 key 和指向 right 的指针,
且 left 包含t-1个关键字. 释放right, 递归地从left中删除 key*/
{
BTree_Merge(T, node, idx);
BTree_DeleteKey(T, node->childrens[idx], key);
}
}
else
/*关键字 key 不在内部结点 node 中, 则必包含于node->childrens中. 如果 node->childrens 只有 t-1 个关键字,
那么执行这两步保证下降到一个至少包含 t 个关键字的结点*/
{
BTree_node* child = node->childrens[idx];
if (child == NULL)
{
printf("无法删除键%d\n", key);
return;
}
/*如果node->children只有 t-1 个关键字, 但它的兄弟至少有 t 个关键字, 则把node中的某个关
键字下降到node->children中, 讲 node->children 的相邻左兄弟或右兄弟的一个关键字升至 node, 讲该兄弟
中相应的孩子指针移到 node->children中, 使node->children增加一个关键字 */
/*如果 node->children 以及 node->children 的所有兄弟结点都只有 t-1 个关键字, 则把 node->children 与一个兄弟合
并, 即把 node 的一个关键字移到新合并的结点, 使之成为该结点的中间关键字*/
if (child->num == T->t - 1)
{
BTree_node* left = NULL;
BTree_node* right = NULL;
if (idx - 1 >= 0)
left = node->childrens[idx - 1];
if (idx + 1 <= node->num)
right = node->childrens[idx + 1];
if ((left && left->num >= T->t) || (right && right->num >= T->t))
{
int richR = 0;
if (right)
richR = 1;
if (left && right) {
if (right->num > left->num)
richR = 1;
else richR = 0;
}
if (right && right->num >= T->t && richR)
{
child->keys[child->num] = node->keys[idx];
child->childrens[child->num + 1] = right->childrens[0];
child->num++;
node->keys[idx] = right->keys[0];
for (i = 0;i < right->num - 1;i++)
{
right->keys[i] = right->keys[i + 1];
right->childrens[i] = right->childrens[i + 1];
}
right->keys[right->num - 1] = 0;
right->childrens[right->num - 1] = right->childrens[right->num];
right->childrens[right->num] = NULL;
right->num--;
}
else
/*如果 node->children 以及 node->children 的所有兄弟结点都只有 t-1 个关键字, 则把 node->children 与一个兄弟合
并, 即把 node 的一个关键字移到新合并的结点, 使之成为该结点的中间关键字*/
{
for (i = child->num;i > 0;i--)
{
child->keys[i] = child->keys[i - 1];
child->childrens[i + 1] = child->childrens[i];
}
child->childrens[1] = child->childrens[0];
child->childrens[0] = left->childrens[left->num];
child->keys[0] = node->keys[idx - 1];
child->num++;
left->keys[left->num - 1] = 0;
left->childrens[left->num] = NULL;
left->num--;
}
}
else if ((!left || (left->num == T->t - 1)) && (!right || (right->num == T->t - 1)))
{
if (left && left->num == T->t - 1)
{
BTree_Merge(T, node, idx - 1);
child = left;
}
else if (right && right->num == T->t - 1)
{
BTree_Merge(T, node, idx);
}
}
}
BTree_DeleteKey(T, child, key);
}
}
3.12 摧毁B树 Tree_Destroy
int BTree_DeleteTree(BTree* T, int key) //删除B树
{
if (!T->root)
return -1;
BTree_DeleteKey(T, T->root, key);
return 0;
}
4.剪贴板字符处理模块 GETPALTE.h
4.0 前置声明
/**
* Code by C
* @version 1.40 2020-09-10
* @author MPX
*/
#include<stdio.h>
#include<ctype.h>
#include<windows.h> //需要使用到系统提供的剪切板clipboard
#define N 2000
//自动读取剪贴板数据
int GetPlateString(FILE* fp);
//自动读取复制代码到剪贴板
int StringAutoCopy(FILE* fp);
//读取文件多行内容
char* GetFileString(int m, int n, char* mystring, FILE* fp);
4.1 自动读取剪贴板
int GetPlateString(FILE* fp)
{
char* pbuf = NULL;
int i, count = 0;//count 表示单词的数量
HANDLE hclip;//剪切板句柄
//打开剪切板,获取里面的数据
if (OpenClipboard(NULL) == 0)
{
printf("fail to open the Plate\n");
return -1;
}
//剪切板当中的数据是不是文本类型的
if (!IsClipboardFormatAvailable(CF_TEXT))
{
printf("剪切板当中的数据类型不匹配!\n");
CloseClipboard();
return -1;
}
//获取剪切板里面的数据
hclip = GetClipboardData(CF_TEXT);
pbuf = (char*)GlobalLock(hclip); //加锁,返回一个VOID类型的指针
GlobalUnlock(hclip); //解锁
fprintf(fp, "%s", pbuf);
printf("%s", pbuf);
CloseClipboard();//关闭剪切板,不然其他程序无法正常0使用剪切板
return 0;
}
4.2 自动复制代码
int StringAutoCopy(FILE* fp)
{
if (!OpenClipboard(NULL) || !EmptyClipboard())
{
printf("打开或清空剪切板出错!\n");
return;
}
HGLOBAL hMen;
char strText[10000];
GetFileString(0, 2000, strText, fp);
// 分配全局内存
hMen = GlobalAlloc(GMEM_MOVEABLE, ((strlen(strText) + 1) * sizeof(char)));
if (!hMen)
{
printf("分配全局内存出错!\n");
// 关闭剪切板
CloseClipboard();
return;
}
// 把数据拷贝考全局内存中
// 锁住内存区
LPSTR lpStr = (LPSTR)GlobalLock(hMen);
// 内存复制
memcpy(lpStr, strText, ((strlen(strText)) * sizeof(char)));
// 字符结束符
lpStr[strlen(strText)] = (TCHAR)0;
// 释放锁
GlobalUnlock(hMen);
// 把内存中的数据放到剪切板上
SetClipboardData(CF_TEXT, hMen);
CloseClipboard();
printf("复制成功!");
return 0;
}
4.3 一次性读取文件多行内容
char* GetFileString(int m, int n, char* mystring, FILE* fp) {
int i;
char str[N];
*mystring = '\0';
fseek(fp, 0L, SEEK_SET);
for (i = 1; i < m; i++) {
if (fgets(str, N, fp) == NULL) {
//printf("文件长度不足");
fclose(fp);
return NULL;
}
}
for (i = 1; i < n; i++) {
if (fgets(str, N, fp) == NULL) {
//printf("文件长度不足");
fclose(fp);
return NULL;
}
strcat_s(mystring, strlen(mystring) + strlen(str) + 1, str);
}
fclose(fp);
return mystring;
}