标题@TOC
用的是VS2020
过年疫情宅家闲着无聊看到书上的一个图书馆里系统挺有意思的就把他拖出来重构了一下,优化了一下写入逻辑的部分。参考书籍是《C++开发实战》
首先这个程序需要实现的功能包括
- 一个主界面用于交互
- 能写入新的图书(任意数据,文中的存储方式为二进制,方便读写)
- 能特定的输出所有图书
- 能删除图书
在这个程序里我把代码分成了两部分,第一部分是BOOK类,主要负责读写操作,第二部分是主程序,主要负责交互和调用BOOK类
首先是BOOK类的头定义
#pragma once
#include<iostream>
#define NUM 256
class CBook {
public:
void inputBook(char* cname, char* clsbn, char* cprice, char* cauthor);
void writeBook(); //将数据写入磁盘
void deleteBook(int count); //删除文本数据
void displayBook(int count); //访问数据
int maxpos(); //返回数据总量
public:
char m_name[NUM/4];//名字
char m_lsbn[NUM/4];//ISBN编号
char m_price[NUM/4];//价格
char m_author[NUM/4];//作责
};
在这里定义的NUM 256是指文件按照256字节为一个存储单位,每次写入一本书都在储存文本中占用256字节,下面public定义的变量平均把NUM分成四份分别储存四种不同的内容。
下面是BOOK类的定义
#include<iostream>
#include<fstream>
#include<vector>
#include<iterator>
#include"Book.h"
#include <iomanip>
using namespace std;
//将数据写入到BOOK变量中暂存
void CBook::inputBook(char* cname, char* clsbn, char* cprice, char* cauthor) {
strcpy_s(m_name, cname);
strcpy_s(m_lsbn, clsbn);
strcpy_s(m_price, cprice);
strcpy_s(m_author, cauthor);
}
//将变量中暂存的文件按二进制写入储存
void CBook::writeBook() {
ofstream ofile;
ofile.open("Book.dat", ios::binary | ios::app);
try {
ofile.write(m_name, NUM / 4);
ofile.write(m_lsbn, NUM / 4);
ofile.write(m_price, NUM / 4);
ofile.write(m_author, NUM / 4);
}
catch (...) {
throw"写入数据失败";
ofile.close();
}
ofile.close();
}
//删除数据
void CBook::deleteBook(int count) {
vector<char> ver;//设置容器
fstream file;
char temp[NUM];
int por = 0;
int i = 0;
file.open("Book.dat", ios::binary | ios::in);
file.seekg(0, ios::beg);
while (file.peek() != EOF) { //这里如果用file.eof()就会循环多一次,
//por = i * NUM; //每次循环指针都偏移一个计量单位
//file.seekg(por, ios::beg); //将指针偏移到下个计量单位的位置
for (int k = 0; k < 4; k++) {
file.read(temp, NUM / 4); //从数据库中取出1/4偏移量的数据
ver.push_back(*temp); //将数据写进向量
}
i++;
}
file.close();
ver.erase(ver.begin() + (count-1)*4 , ver.begin()+count*4); //删除容器中第count个值
vector<char>::iterator itor = ver.begin(); //设置迭代器
file.open("Book.dat", ios::binary | ios::out);
for (; itor != ver.end(); itor++) {
*temp = *itor;
file.write(temp,NUM/4); //将迭代器的内容重新写入数据库中
}
file.close();
}
//打印数据
void CBook::displayBook(int count) {
char temp[NUM];
int por = 0;
fstream file;
file.open("Book.dat", ios::binary | ios::in);
try {
por = NUM * (count-1);//获取总长度
file.seekg(por, ios::beg); //将指针指向要访问的数据区间
for (int i = 0; i < 4; i++) {
file.read(temp, NUM/4);
cout << setw(12) << temp;//每次读取到终止符就会停止,所以循环四次
}
file.close();
}
catch (...) {
throw"displayBook is error";
file.close();
}
}
//返回数据总长度
int CBook::maxpos() {
int maxpos = 0;
fstream file;
file.open("Book.dat", ios::binary | ios::in);
try {
file.seekg(0, ios::end);
maxpos = file.tellg();//获取数据总长
file.close();
}
catch (...) {
throw"访问失败";
file.close();
}
return maxpos;
}
关于代码40行为什么要用 file.peek() != EOF 而不是常见的file.eof()是应为他在循环的时候总会多循环一次,参考文章是https://www.cnblogs.com/mengfanrong/p/4065318.html
大概意思就是,
fstream流的eof()推断有点不合常理
按常理逻辑来说,假设到了文件末尾的话,eof()应该返回真,可是,C++输入输出流怎样知道是否到末尾了呢?
原来依据的是:假设fin>>不能再读入数据了,才发现到了文件结尾,这时才给流设定文件结尾的标志,此后调用eof()时,才返回真。
如果
find>>x; //此时文件刚好读完最后一个数据(将其保存在x中)
可是,这时fin.eof()仍为假,由于 fin流的标志eofbit是False,fin流此时觉得文件还没有到末尾,仅仅有当流再次读写时 fin>>x ,发现已无可读写数据,此时流才知道到达了结尾,这时才将标志eofbit改动为True,此时流才知道文件到了末尾。
也就是说,eof在读取完最后一个数据后,仍是False,当再次试图读一个数据时,因为发现没数据可读了,才知道到末尾了,此时才改动标志,eof变为True。
然后就是关于代码44行为什么要循环四次,因为file.read()函数读取的时候遇到“\0”就会直接结束,而我们上面写入文件时候方式就是用字符串写入,所以我们每一个数据块后面都被默认添加了\0,如果我们直接读取就只能取出部分的数据而不是全部,所以这个坑本人技术有限也只能用个循环定死他。
删除数据这块的大概思路就是从文本里面取出所有数据然后把他写入容器vector中,然后特定的删除容器内我们想要的数据,再把数据存回文件。但这样做一旦数据量很大就会造成效率低下(容器过长)
最后是主程序的代码
#include<iostream>
#include<fstream>
#include<vector>
#include<iterator>
#include"Book.h"
#include <iomanip>
#include<Windows.h>
#include<string>
using namespace std;
#define CMD_COLS 100 //初始化窗口的长宽
#define CMD_LINES 30
#define PAGE 20 //每隔PAGE个数据就换页
void SetScreenGrid();//设置屏幕显示的行数和列数
void ClearScreen();//清除屏幕信息
void SetSysCaption(const char* pText);//设置窗体标题栏
void gotoxy(double&& x, double&& y);//光标定位
void menu();//开始菜单
void mainloop();//主循环
void joinBook();//添加新书
void readBook();//查看所有书籍
void deleteBook();//删除特定书籍
int input();//获取用户输入的值
void SetScreenGrid() {
char sysSetBuf[80];
sprintf_s(sysSetBuf, "mode con cols=%d lines=%d", CMD_COLS, CMD_LINES);
system(sysSetBuf);
}
void ClearScreen() {
system("cls");
}
void SetSysCaption(const char* pText) {
char sysSetBuf[80];
sprintf_s(sysSetBuf, "title %s", pText);
system(sysSetBuf);
}
void gotoxy(double&& x, double&& y)
{
COORD coord = { x,y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void menu() {
ClearScreen();
gotoxy(15, 6);
cout << "1·添加新书";
gotoxy(15, 8);
cout << "2·浏览全部数据";
gotoxy(15, 10);
cout << "3·删除书籍";
}
void mainloop() {
while (1){
menu();
gotoxy(18, 15);
cout << "请输入";
switch (input()) {
case 1:
ClearScreen();
joinBook();
break;
case 2:
ClearScreen();
readBook();
break;
case 3:
ClearScreen();
deleteBook();
break;
}
}
}
void joinBook() {
char exmp[PAGE];
int judge=1;
memset(exmp, 0, sizeof(exmp));
while (judge) {
char m_name[NUM / 4];
char m_lsbn[NUM / 4];
char m_price[NUM / 4];
char m_author[NUM / 4];
cout << "请输入书本名字" << endl;
cin >> m_name;
cout << "输入ISBN编码" << endl;
cin >> m_lsbn;
cout << "输入价格" << endl;
cin >> m_price;
cout << "输入作者" << endl;
cin >> m_author;
CBook book;
book.inputBook(m_name, m_lsbn, m_price, m_author);
book.writeBook();
cout << endl << endl << endl;
cout << "输入“return”结束操作,输入任意值继续" << endl;
cin >> exmp;
if (strcmp(exmp,"return")==0) {
judge = 0;
memset(exmp, 0, sizeof(exmp));
}
}
}
void readBook() {
int now = 1; //记录当前页数
char record=NULL ; //记录换页字符
int total = 0;
CBook exmp;
total= exmp.maxpos()/NUM; //总数据大小
while (record != 'k') {
record = NULL;
if (now <= total/PAGE+1 && now > 0) {
ClearScreen();
gotoxy(3, 1);
cout << setiosflags(ios::left);//左对齐
cout << "共有记录" << setw(4) << total;
cout << "共有页数" << setw(4) << total / PAGE + 1;
cout << "当前页数" << setw(4) << now;
cout << "输入“m”上一页 输入“n”下一页" << " 输入“k”跳回主程序" << endl;
cout <<setw(10)<< "编码" << setw(10) << "书名" << setw(10) << "ISBN编码" << setw(10) << "价格" << setw(10) << "作者" << endl;
//每次换页累加
int i = 1;
if (now > 1) {
i = i + (now - 1) * PAGE;
}
for (; i <= PAGE+(now-1)*PAGE; i++) {
if (i > total) { break; }//越界保护
//判断当前页数该输出的数据
cout << setw(10) << i;
exmp.displayBook(i);
cout << endl;
}
cin >> record;
if (record == 'n') { now++; } //页数++
else if (record == 'k') { mainloop(); } //跳回主程序
else if (record == 'm') { now--; } //页数--
else {
cout << "输入错误" << endl;
cin >> record;
}
}
else {
cout << "超出限制" << endl;
now = 1;//越界重置
cin >> record;
}
}
}
void deleteBook() {
cout << "请输入要删除的书籍的编码" << endl;
CBook book;
int exmp = -1;
exmp = input();
if (exmp == 0) {
cout << "输入数据异常,请重新输入" << endl;
deleteBook();
}
else {
book.deleteBook(exmp);
cout << "删除成功" << endl;
cout << "输入任意键返回" << endl;
input();
}
}
int input() {
char buf[64];
gets_s(buf);
return atoi(buf);
}
int main(void) {
SetScreenGrid();//窗口初始化
SetSysCaption("图书管理系统测试");
mainloop();//主界面循环
}
在主函数中的input()函数,如果输入的字符是数字之外的数据就会返回0,所以有些判断也利用了这个特性
基本上这个程序也用到了我现在学C++中的大部分知识了,除了虚继承哪方面的模块,关于程序输入越界的判断写的很乱,也有点懒得改了,大概能动就行了。就当是自己写的一个总结了。代码很菜,大家看看玩笑就好,别较真。
存档用