c++實戰(二)圖書管理系統

通過該實戰,瞭解軟件整體設計,類的應用,以及文件存儲數據的方法等。

該程序要完成圖書的錄入、刪除、以及瀏覽全部圖書記錄三個模塊。

1.設計一個圖書類

#include "stdafx.h"
#include "Book.h"	
#include "string"
#include "fstream"
#include "iostream"
#include "iomanip"
using namespace std;
CBook::CBook(char* cName, char* cIsbn, char* cPrice, char* cAuthor) {
	strncpy(m_cName, cName, NUM1);
	strncpy(m_cIsbn, cIsbn, NUM1);
	strncpy(m_cPrice, cPrice, NUM2);
	strncpy(m_cAuthor, cAuthor, NUM2);
}
char* CBook::GetName() {
	return m_cName;
}
void CBook::SetName(char* cName) {
	strncpy(m_cName, cName, NUM1);
}
char* CBook::GetPrice() {
	return m_cPrice;
}
void CBook::SetPrice(char* cPirce) {
	strncpy(m_cPrice, cPirce, NUM2);
}
char* CBook::GetAuthor() {
	return m_cAuthor;
}
void CBook::SetAuthor(char* cAuthor) {
	strncpy(m_cAuthor, cAuthor, NUM2);
}

void CBook::WriteData() {
	ofstream ofile;
	ofile.open("book.dat", ios::binary|ios::app);
	try {
		ofile.write(m_cName, NUM1);
		ofile.write(m_cIsbn, NUM1);
		ofile.write(m_cPrice, NUM2);
		ofile.write(m_cAuthor, NUM2);
	}
	catch (...) {
		throw "file error occurred";
		ofile.close();
	}
	ofile.close();
}

void CBook::GetBookFromFile(int iCount) {
	char cName[NUM1];
	char cIsbn[NUM1];
	char cPrice[NUM2];
	char cAuthor[NUM2];
	ifstream ifile;
	ifile.open("book.dat", ios::binary);
	try {
		ifile.seekg(iCount*(NUM1 + NUM1 + NUM2 + NUM2), ios::beg);
		ifile.read(cName, NUM1);
		if (ifile.tellg() > 0) {
			strncpy(m_cName, cName, NUM1);
		}
		ifile.read(cIsbn, NUM2);
		if (ifile.tellg() > 0)
			strncpy(m_cIsbn, cIsbn, NUM1);
		ifile.read(cPrice, NUM2);
		if (ifile.tellg() > 0)
			strncpy(m_cPrice, cPrice, NUM2);
		ifile.read(cAuthor, NUM2);
		if (ifile.tellg() > 0)
			strncpy(m_cAuthor, cAuthor, NUM2);
	}
	catch (...) {
		throw "file error occurred";
		ifile.close();
	}
	ifile.close();
}

void CBook::DeleteData(int iCount) {
	long respos;
	int iDataCount = 0;
	fstream file;
	fstream tmpfile;
	ofstream ofile;
	char cTempBuf[NUM1 + NUM1 + NUM2 + NUM2];
	file.open("book.dat", ios::binary | ios::app | ios::out);
	tmpfile.open("temp.dat", ios::binary | ios::in | ios::out | ios::trunc);
	//  ios::trunc	打開一個文件,然後清空內容

	file.seekg(0, ios::end);
	respos = file.tellg();
	// 通過將文件指針設置在文件末尾,返回文件末尾到文首的偏移字節數,
	// 於是得到文件的大小,即字節數

	iDataCount = respos / (NUM1 + NUM1 + NUM2 + NUM2);
	// 一本圖書的信息在文件中佔據NUM1 + NUM1 + NUM2 + NUM2個字節
	// 用 文件的總字節數/每本圖書所佔的字節數 來獲得文件中的圖書數目

	if (iCount < 0 || iCount > iDataCount)
	{
		throw "Input number error";
	}
	else
	{
		// file.seekg((iCount-1)*(NUM1 + NUM1 + NUM2 + NUM2), ios::beg);
		// 將文件指針定位來要查找的圖書的首地址處

		file.seekg(0, ios::beg);

		for (int j = 1; j <= iDataCount; j++)
		{
			if (j == iCount)
			{
				file.seekg((iCount)*(NUM1 + NUM1 + NUM2 + NUM2), ios::beg);
			}
			if (!file.eof())
			{
				memset(cTempBuf, 0, NUM1 + NUM1 + NUM2 + NUM2);
				// 用NUM1 + NUM1 + NUM2 + NUM2個字節的0來填充數組cTempBuf

				file.read(cTempBuf, NUM1 + NUM1 + NUM2 + NUM2);
				// 從file所綁定的文件的文件指針處向後讀取NUM1 + NUM1 + NUM2 + NUM2個字節的內容
				// 將內容保存在數組cTempBuf中。

				tmpfile.write(cTempBuf, NUM1 + NUM1 + NUM2 + NUM2);
				// 將數組cTempBuf中的相應字節數的內容寫入到tmpfile所綁定的磁盤文件中
			}
		}
		file.close();

		tmpfile.seekg(0, ios::beg);
		// 將tmpfile所綁定的文件的文件指針定位到文件首端

		ofile.open("book.dat");
		// 打開名爲"book.dat"的文件,並將文件與ofile綁定,它被用來向文件寫入數據
		// ofile.seekp((iCount - 1)*(NUM1 + NUM1 + NUM2 + NUM2), ios::beg);

		ofile.seekp(0, ios::beg);
		// seekp:設置輸出文件流的文件流指針位置

		for (int i = 1; i <= iDataCount; i++)
		{
			if (!ofile.eof())
			{
				memset(cTempBuf, 0, NUM1 + NUM1 + NUM2 + NUM2);
				tmpfile.read(cTempBuf, NUM1 + NUM1 + NUM2 + NUM2);
				ofile.write(cTempBuf, NUM1 + NUM1 + NUM2 + NUM2);
			}
		}
	}
	tmpfile.close();
	ofile.close();
	remove("temp.dat");
}

2.主程序,主要是對界面進行設計

// simATM.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"
#include<iostream>
#include<iomanip>
#include<stdlib.h>
#include<conio.h>
#include<string.h>
#include<fstream>
#include "Book.h"

#define CMD_COLS 80  
#define CMD_LINES 25 

using namespace std;

/*
除主函數外,系統自定義了許多函數,主要函數及功能如下:
void SetScreenGrid:設置屏幕顯示行數和列數。
void ClearScreen:清除屏幕信息。
void SetSysCaption(const char *pText):設置窗體標題欄。
void Show Welcome:顯示歡迎信息。
void ShowRootMenu:顯示開始菜單。
void WaitView(int iCurPage):瀏覽數據時等待用戶操作。
void WaitUser:等待用戶操作。
void Guidelnput:使用嚮導添加圖書信息。
int GetSelect:獲得用戶菜單選擇。
long GetFileLength(ifstream & ifs):獲取文件長度。
void ViewData(int iSelPage):瀏覽所有圖書記錄。
voidDeleteBookFromFile:在文件中產生圖書信息。
void mainloop:主循環。
*/
void SetScreenGrid();
void ClearScreen();
void SetSysCaption();
void SetSysCaption(const char *pText);
void ShowWelcome();
void ShowRootMenu();
void WaitView(int  iCurPage);
void WaitUser();
void GuideInput();
int GetSelect();
long GetFileLength(ifstream & ifs);
void ViewData(int iSelPage);
void DeleteBookFromFile();
void mainloop();



void SetScreenGrid()
/*
在控制檯中輸入mode命令可以設置控制顯示信息的行數、列數和背景顏色等信息。
SetScreenGrid函數主要通過system函數來執行mode命令,
CMD_COLS和CMD_LINES是宏定義中的值。
*/
{
	char sysSetBuf[80];
	sprintf(sysSetBuf, "mode con cols=%d lines=%d", CMD_COLS, CMD_LINES);
	system(sysSetBuf);
}


void SetSysCaption()
/*
SetSysCaption函數主要完成在控制檯的標題欄上顯示Sample信息。
控制檯的標題欄信息可以使用title命令來設置,
函數中使用system函數來執行title命令。
*/
{
	system("title Sample");
}



void ClearScreen()
/*
函數ClearScreen主要通過system函數來執行cls命令,
完成控制檯屏幕信息的清除。
*/
{
	system("cls");
	// system("cls")包含頭文件 #include <stdlib.h>中,
	// 其作用是清楚屏幕先前的顯示內容
}



void SetSysCaption(const char *pText)
/*
SetSysCaption函數共有兩個版本,這是SetSysCaption函數的另一個版本,
主要實現在控制 臺的標題欄上顯示指定字符
*/
{
	char sysSetBuf[80];
	sprintf(sysSetBuf, "title %s", pText);
	system(sysSetBuf);
}


void ShowWelcome()
/*
ShowWelcome函數在屏幕上顯示“圖書管理系統”字樣的歡迎信息,
“圖書管理系統”字樣應儘量顯示在屏幕的中央位置。
*/
{
	for (int i = 0; i<7; i++)
	{
		cout << endl;
	}
	cout << setw(50);
	cout << "*********************" << endl;
	cout << setw(50);
	cout << "* 圖 書 管 理 系 統 *" << endl;
	cout << setw(50);
	cout << "*********************" << endl;
	// std::setw(n)是標準庫中的格式化流操作符,位於頭文件iomanip中(#include <iomanip>)
	// setw(int n)只對其後面緊跟的輸出(<<)產生作用,
	// 是在輸出時分配了n個字符的輸出寬度,默認填充的內容爲空格,
	// 可以setfill()配合使用設置其他字符填充,且默認的是在n個字符寬度中右對齊輸出。
	// 若想讓它左對齊的話,只需要插入std::left,如下:
	// cout << std::left << "*圖書管理系統*" <<  endl;
	// 同理,右對齊只要插入std::right,不過右對齊是默認狀態,不必顯式聲明。
}



void ShowRootMenu()
/*
ShowRootMenu函數主要顯示系統的主菜單,
系統中有3個菜單選項,分別是“添加新書”、 “瀏覽全部”、“刪除圖書”。
3個菜單選項是進入系統3個模塊的入口。
*/
{
	cout << setw(45) << "請選擇功能" << endl;
	cout << endl;
	cout << setw(45) << "1 添加新書" << endl;
	cout << endl;
	cout << setw(45) << "2 瀏覽全部" << endl;
	cout << endl;
	cout << setw(45) << "3 刪除圖書" << endl;
}


int GetSelect()
/*函數GetSelect主要負責獲取用戶菜單的選擇。*/
{
	char buf[256];
	gets_s(buf);
	// gets() 接受一個字符串,可以接收空格並輸出,
	// 注意,不能寫成 buf = gets_s();
	// 它與cin的區別在於cin不接受空格,TAB等鍵的輸入,遇到這些鍵,字符串會終止,
	// 而gets()則接受連續的輸入,包括空格,TAB,可以無限讀取,不會判斷上限,以回車結束讀取,
	// 並將讀取的結果存放在buffer指針所指向的字符數組中,
	// 換行符不作爲讀取串的內容,讀取的換行符被轉換爲‘\0’空字符,並由此來結束字符串。
	return atoi(buf);
	// atoi()函數包含在頭文件:#include <stdlib.h>中,
	// atoi()函數用來將字符串轉換成整數(int),其原型爲int atoi(const char* str);
	// atoi()函數會掃描參數str字符串,跳過前面的空白字符(例如空格,tab縮進等,可以通過isspace()函數來檢測),
	// 直到遇上數字或正負符號纔開始做轉換,而再遇到非數字或字符串結束時('\0')才結束轉換,
	// 並返回轉換後的整型數;如果str不能轉換成int或者str爲空字符串,那麼將返回0。
}


void WaitUser()
/*
WaitUser函數主要負責當程序完成某一模塊的執行後,等待用戶進行處理。
用戶可以選擇返回主菜單,也可以直接退出系統。
*/
{
	int iInputPage = 0;
	cout << "enter返回主菜單,q退出" << endl;
	char buf[256];
	gets_s(buf);
	if (buf[0] == 'q')
		system("exit");
}






void WaitView(int iCurPage)
{
	char buf[256];
	cin >> buf;
	if (buf[0] == 'q')
		system("exit");
	if (buf[0] == 'm')
		mainloop();
	if (buf[0] == 'n')
		ViewData(iCurPage);
}



long GetFileLength(ifstream& ifs)
/*
函數GetFileLength用來獲取文件的長度,函數需要指定一個文件流對象。
該函數的思路是:1.調用ifstream打開一個文件(或者直接傳入文件流對象);
2.調用seekg將get pointer置爲文件末尾,seekg(0, ios_base::end);
3.調用tellg獲取總字節數,實際上獲取的是get pointer相對於文件頭的偏移字節數;
4.重置get pointer,使其指向文件頭或者原位置,以便執行其他操作,指向文件頭爲seekg(0, ios_base::beg)。
*/
{
	long tmppos;
	long respos;
	tmppos = ifs.tellg();
	// tellg()函數不需要帶參數,它返回當前定位指針的位置
	ifs.seekg(0, ios::end);
	// 基地址爲文件結束處,偏移地址爲0,於是指針定位在文件結束處。
	// seekg()是對輸入文件定位,它有兩個參數:第一個參數是偏移量,第二個參數是基地址。
	// 對於第一個參數,可以是正負數值,正的表示向後偏移,負的表示向前偏移。
	// 而第二個參數可以是:
	// ios::beg:表示輸入流的開始位置,文件頭;
	// ios::cur:表示輸入流的當前位置;
	// ios::end:表示輸入流的結束位置,文件尾。
	// ifstream,類似istream, 有一個被稱爲get pointer的指針,指向下一個將被讀取的元素。
	respos = ifs.tellg();
	// 因爲定位指針在文件結束處,所以ifs.tellg()的值也就是文件的大小,
	// 因爲tellg獲取總字節數,實際上獲取的是get pointer相對於文件頭的偏移字節數
	ifs.seekg(tmppos, ios::beg);
	// 基地址爲文件頭,偏移量爲tmppos,於是定位在原來的位置
	return respos;
}


void mainloop()
/*
main函數是程序的入口,主要調用了SetScreenGrid、SetSysCaption和mainloop3個函數,
其中mainloop函數是主函數,負責模塊執行的調度。
*/
{
	ShowWelcome();
	while (1)
	{
		ClearScreen();
		ShowWelcome();
		ShowRootMenu();
		switch (GetSelect())
		{
		case 1:
			ClearScreen();
			GuideInput();
			break;
		case 2:
			ClearScreen();
			ViewData(1);
			break;
		case 3:
			ClearScreen();
			DeleteBookFromFile();
			break;
		default:
			break;           // 如果GetSelect()函數的返回值不是1、2、3當中的值,便重新執行while循環。
		}
	}
}


// 主函數
void main()
{
	SetScreenGrid();
	SetSysCaption("圖書管理系統");
	mainloop();
}

3. 添加圖書

void GuideInput()
/*
添加圖書需要調用函數GuideInput來完成,
需要用戶分別輸入書名、ISBN編號、價格和作者,
然後利用CBook類構建一個CBook對象,
通過CBook對象的成員函數WriteData將圖書信息寫入文件。
*/
{
	char inName[NUM1];
	char inlsdn[NUM1];
	char inPrice[NUM2];
	char inAuthor[NUM2];
	cout << "請輸入書名:" << endl;
	cin >> inName;
	cout << "請輸入ISBN:" << endl;
	cin >> inlsdn;
	cout << "請輸入價格:" << endl;
	cin >> inPrice;
	cout << "請輸入作者:" << endl;
	cin >> inAuthor;
	// 注意:cin不接受空格,TAB等鍵的輸入,遇到這些鍵,字符串會終止

	CBook book(inName, inlsdn, inPrice, inAuthor);     // 構造一個CBook類對象,並利用構造函數對數據成員初始化
	book.WriteData();
	cout << "Write Finish!" << endl;

	// 等待用戶進行下一步處理
	cout << endl;
	cout << "******************************" << endl;
	cout << "請選擇接下來的操作(任意鍵:繼續添加新書,q鍵:退出):" << endl;
	char buf;
	cin >> buf;
	if (buf == 'q')
		system("exit");
	else
		GuideInput();
}

4. 瀏覽圖書

void ViewData(int iSelPage)
/*
顯示圖書信息主要通過函數ViewData來完成。函數ViewData按頁數顯示圖書記錄,
每頁可以顯示20條記錄。在函數ViewData中直接使用文件流類打開存儲圖書信息的文件book.dat,
然後根據頁序號讀取文件內容,因爲每條圖書記錄的長度相同,
這樣就很容易計算出每條記錄在文件中的位置,然後將文件指針移動到每頁第一條圖書記錄處,
順序地從文件中讀取20條記錄,並將信息顯示在屏幕上。
*/
{
	int iPage = 0;              // iPage根據圖書數目來確定需要安排幾頁來顯示
	int iCurPage = 0;           // iCurPage表示當前頁面數
	int iDataCount = 0;         // iDataCount表示文件中記錄的圖書數目

	char inName[NUM1];
	char inlsbn[NUM1];
	char inPrice[NUM2];
	char inAuthor[NUM2];

	bool bIndex = false;
	int iFileLength;
	iCurPage = iSelPage;

	ifstream ifile;
	// 創建一個未綁定的文件輸入流,ifstream是頭文件#include<fstream>中定義的一個類型
	ifile.open("book.dat", ios::binary);
	// 打開名爲"book.dat"的文件,並將文件與ifstream綁定。
	// 此處文件名(第一個參數)由C風格的字符串常量類型的參數指定,在新的C++標準中,
	// 文件名既可以是庫類型string對象,也可以是C風格字符數組。
	// open函數的第二個參數定義了文件的打開模式,讀寫二進制文件時,
	//  打開方式中必須指定ios::binary,否則讀寫會出錯,總共有如下模式:
	//	ios::in 讀
	//	ios::out 寫
	//  ios::app  從文件末尾開始寫
	//  ios::binary 二進制模式
	//  ios::nocreate	打開一個文件時,如果文件不存在,不創建文件。
	//  ios::noreplace	打開一個文件時,如果文件不存在,創建該文件
	//  ios::trunc	打開一個文件,然後清空內容
	//  ios::ate	打開一個文件時,將位置移動到文件尾
	//  注意:默認模式是文本;默認如果文件不存在,那麼創建一個新的文件;
	//  多種模式可以混合,用 | (按位或); 文件的byte索引從0開始,(就像數組一樣)。

	iFileLength = GetFileLength(ifile);         // 獲取目標二進制文件的文本長度,即字節數
	iDataCount = iFileLength / (NUM1 + NUM1 + NUM2 + NUM2);  //根據文件長度,計算文件中總的記錄數
	if (iDataCount >= 1)
		bIndex = true;
	iPage = iDataCount / 20 + 1;

	ClearScreen();                 	//清除屏幕信息
	cout << setw(15) << "共有記錄:" << iDataCount << " ";
	cout << setw(15) << "共有頁數:" << iPage << " ";
	cout << setw(15) << "當前頁數:" << iCurPage << " ";
	cout << setw(25) << "n 顯示下一頁 m 返回" << endl;
	cout << std::left << setw(15) << "lndex";
	cout << std::left << setw(15) << "Name" << std::left << setw(15) << "Isbn";
	cout << std::left << setw(15) << "Price" << std::left << setw(15) << "Author";
	cout << endl;

	// 
	try
	{
		// 根據圖書頁面編號查找該頁面第一本圖書在文件中的位置
		ifile.seekg((iCurPage - 1) * 20 * (NUM1 + NUM1 + NUM2 + NUM2), ios::beg);

		// 根據IO類所定義的一些函數和標誌,可以幫助我們訪問和操縱流的條件狀態
		// 部分常用的IO庫條件狀態爲:
		// ifile.eof():若流ifile的eofbit置位,則返回true;
		// ifile.fail():若流ifile的failbit或badbit置位,則返回true;
		// ifile.bad():若流ifile的badbit置位,則返回true;
		// ifile.good():若流ifile處於有效狀態,則返回true;
		// 操作good在所有錯誤位均未置位的情況下返回true,而bad、fail和eof則在
		// 對應錯誤位被置位時返回true。此外,在badbit被置位時,fail也會返回true。
		// 這意味着,使用good或fail是確定流的總體狀態的正確方法,
		// 常用的確定流對象的狀態的方法是:將!ifile.fail()作爲條件來使用。
		if (!ifile.fail())
		{
			for (int i = 1; i < 21; i++)   // 循環20次,讀取出20條圖書信息
			{

				//將變量清零,也就是說將目標數組的給個字節全別賦0
				// memset函數是內存賦值函數,用來給某一塊內存空間進行賦值的。 
				// 其原型是:void* memset(void *_Dst, int  _Val, size_t _Size)
				// _Dst是目標起始地址,_Val是要賦的值,_Size是要賦值的字節數。
				// 例如,char inName[128], 我們用memset給數組inName初始化爲“0000...0000(128個0)”,
				// 用法如下:memset(inName, 0, sizeof(char) *128);
				// 注意,memset是逐字節拷貝的。
				memset(inName, 0, sizeof(char) * 128);
				memset(inlsbn, 0, sizeof(char) * 128);
				memset(inPrice, 0, sizeof(char) * 50);
				memset(inAuthor, 0, sizeof(char) * 50);

				if (ifile.eof())          // 當讀到二進制文件的末尾時,將bIndex置爲false
				{
					bIndex = false;
				}
				else
					bIndex = true;

				if (bIndex)
					cout << std::right << setw(3) << ((iCurPage - 1) * 20 + i);

				// 我們用read函數和write函數來對二進制文件進行讀寫
				// read函數常用格式爲:文件流對象.read(char *buf,int len);
				// write函數常用格式爲:文件流對象.write(const char *buf, int len);
				// 兩者格式上差不多,第一個參數是一個字符指針,
				// 用於指向讀入讀出數據所放的內存空間的其實地址。
				// 第二個參數是一個整數,表示要讀入讀出的數據的字節數。
				ifile.read(inName, NUM1);	   //讀取圖書名稱
				cout << setw(18) << inName;
				ifile.read(inlsbn, NUM1);	   //讀取圖書 ISBN 編號
				cout << setw(18) << inlsbn;
				ifile.read(inPrice, NUM2);
				cout << setw(10) << inPrice;
				ifile.read(inAuthor, NUM2);
				cout << setw(15) << inAuthor;
				cout << endl;
			}
		}
	}
	catch (...)
	{
		cout << "throw file exception" << endl;
		throw "file error occurred";
		ifile.close();
	}


	if (iCurPage < iPage)
	{
		iCurPage = iCurPage + 1;
		WaitView(iCurPage);      // 等待用戶處理
	}
	else
	{
		WaitView(iCurPage);
	}
	ifile.close();
}

5.刪除圖書

void DeleteBookFromFile()
/*
刪除圖書主要通過調用函數DeleteBookFromFile來完成,
而在函數DeleteBookFromFile中則主要調用CBook類的DeleteData成員函數來完成。
DeleteData成員函數需要設置刪除圖書在文件中的順序編號,
在瀏覽圖書時可以看到此編號。
*/
{
	int iDelCount;
	cout << "Input delete index" << endl;
	cin >> iDelCount;
	CBook tmpbook;
	tmpbook.DeleteData(iDelCount);
	cout << "Delete Finish" << endl;
	WaitUser();
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章