Banking Record System Project 해석
in Project on Analyze, Project
#include<iostream>
#include<fstream>
#include<cstdlib>
using std::cout;
using std::cin;
using std::endl;
using std::fstream;
using std::ofstream;
using std::ifstream;
using std::ios;
class account_query
{
private:
char account_number[20];
char firstName[10];
char lastName[10];
float total_Balance;
public:
void read_data();
void show_data();
void write_rec();
void read_rec();
void search_rec();
void edit_rec();
void delete_rec();
};
void account_query::read_data()
{
cout << "\nEnter Account Number: ";
cin >> account_number;
cout << "Enter First Name: ";
cin >> firstName;
cout << "Enter Last Name: ";
cin >> lastName;
cout << "Enter Balance: ";
cin >> total_Balance;
cout << endl;
}
void account_query::show_data()
{
cout << "Account Number: " << account_number << endl;
cout << "First Name: " << firstName << endl;
cout << "Last Name: " << lastName << endl;
cout << "Current Balance: Rs. " << total_Balance << endl;
cout << "-------------------------------" << endl;
}
void account_query::write_rec()
{
ofstream outfile;
outfile.open("record.bank", ios::binary | ofstream::app);
read_data();
outfile.write(reinterpret_cast<char*>(this), sizeof(*this));
outfile.close();
}
void account_query::read_rec()
{
ifstream infile;
infile.open("record.bank", ios::binary);
if (!infile)
{
cout << "Error in Opening! File Not Found!!" << endl;
return;
}
cout << "\n****Data from file****" << endl;
while (!infile.eof())
{
if (infile.read(reinterpret_cast<char*>(this), sizeof(*this)))
{
show_data();
}
}
infile.close();
}
void account_query::search_rec()
{
int n;
ifstream infile;
infile.open("record.bank", ios::binary);
if (!infile)
{
cout << "\nError in opening! File Not Found!!" << endl;
return;
}
infile.seekg(0, ios::end);
int count = infile.tellg() / sizeof(*this);
cout << "\n There are " << count << " record in the file";
cout << "\n Enter Record Number to Search: ";
cin >> n;
infile.seekg((n - 1) * sizeof(*this));
infile.read(reinterpret_cast<char*>(this), sizeof(*this));
show_data();
}
void account_query::edit_rec()
{
int n;
fstream iofile;
iofile.open("record.bank", ios::in | ios::binary);
if (!iofile)
{
cout << "\nError in opening! File Not Found!!" << endl;
return;
}
iofile.seekg(0, ios::end);
int count = iofile.tellg() / sizeof(*this);
cout << "\n There are " << count << " record in the file";
cout << "\n Enter Record Number to edit: ";
cin >> n;
iofile.seekg((n - 1) * sizeof(*this));
iofile.read(reinterpret_cast<char*>(this), sizeof(*this));
cout << "Record " << n << " has following data" << endl;
show_data();
iofile.close();
iofile.open("record.bank", ios::out | ios::in | ios::binary);
iofile.seekp((n - 1) * sizeof(*this));
cout << "\nEnter data to Modify " << endl;
read_data();
iofile.write(reinterpret_cast<char*>(this), sizeof(*this));
}
void account_query::delete_rec()
{
int n;
ifstream infile;
infile.open("record.bank", ios::binary);
if (!infile)
{
cout << "\nError in opening! File Not Found!!" << endl;
return;
}
infile.seekg(0, ios::end);
int count = infile.tellg() / sizeof(*this);
cout << "\n There are " << count << " record in the file";
cout << "\n Enter Record Number to Delete: ";
cin >> n;
fstream tmpfile;
tmpfile.open("tmpfile.bank", ios::out | ios::binary);
infile.seekg(0);
for (int i = 0; i < count; i++)
{
infile.read(reinterpret_cast<char*>(this), sizeof(*this));
if (i == (n - 1))
continue;
tmpfile.write(reinterpret_cast<char*>(this), sizeof(*this));
}
infile.close();
tmpfile.close();
remove("record.bank");
rename("tmpfile.bank", "record.bank");
}
int main()
{
account_query A;
int choice;
cout << "***Acount Information System***" << endl;
while (true)
{
cout << "Select one option below ";
cout << "\n\t1-->Add record to file";
cout << "\n\t2-->Show record from file";
cout << "\n\t3-->Search Record from file";
cout << "\n\t4-->Update Record";
cout << "\n\t5-->Delete Record";
cout << "\n\t6-->Quit";
cout << "\nEnter your choice: ";
cin >> choice;
switch (choice)
{
case 1:
A.write_rec();
break;
case 2:
A.read_rec();
break;
case 3:
A.search_rec();
break;
case 4:
A.edit_rec();
break;
case 5:
A.delete_rec();
break;
case 6:
exit(0);
break;
default:
cout << "\nEnter corret choice";
exit(0);
}
}
system("pause");
return 0;
}
출처 == https://www.codewithc.com/banking-record-system-project-c/
이 프로젝트는 C++에서 파일 입출력을 연습하기 위한 프로젝트이다.
느낀점
1. using 사용 :
using namespace std;를 사용하지 않고 std namespace에서 필요한 것만 using으로 가져다 썼다. using std::을 통해 cin, cout, ios 등 파일 입출력에 필요한 객체와 클래스를 사용했다.
2. 입출력 구조 :
C++의 입출력 라이브러리는 다음과 같은 클래스들로 구성되어 있다.
클래스의 객체를 찍을 수 있는 것은, ios, ifstream, ofstream, fstream이다. istream, ostream, iostream, ios_base는 객체가 생성되지 않는다.(왜 그런지 모르겠다)
- istream은 문자 스트림을 타입 있는 객체(int, double, float등)로 변환한다. » 는 istream 클래스에 정의되어 있는 연산자이다.
cin은 istream 클래스의 객체 중 하나이다.
- ostream은 타입 있는 객체(int, float, double)를 문자(바이트) 스트림으로 변환한다. « 는 ostream 클래스에 정의되어 있는 연산자이다.
cout은 ostream 클래스의 객체 중 하나이다.
#include
하면 istream 클래스와 ostream 클래스의 내용들이 들어온다 #include
하면 ifstream 클래스와 ofstream - 의 내용들이 들어온다. - ifstream 클래스에는 프로그램이 파일에 있는 내용을 읽기 위한 기능, ofstream 클래스에는 프로그램이 파일에 쓰기 위한 기능 fstream 클래스에는 파일에 읽고 쓰기 위한 기능이 있다.
3. 클래스 구조 :
=> 멤버 변수 : 1 ) 계좌번호를 저장할 배열(account_number), 2 ) 이름을 저장할 배열(firstName, lastName), 3 ) 금액(total_Balance)를 두고 있다.
=> 멤버 함수 : 1 ) 콘솔에서 입력을 받는 함수(read_data) 2 ) 콘솔로 출력하는 함수(show_data) 3 ) 파일에 콘솔로부터 입력받은 멤버 변수를 입력하는 함수(write_rec) 4 ) 파일로부터 값을 읽어와서 콘솔에 출력하는 함수(read_rec) 5 ) 파일에서 정보를 찾는 함수(search_rec) 6 ) 파일을 수정하는 함수(edit_rec) 7 ) 파일에서 정보를 삭제하는 함수(delete_rec)
4. account_query 멤버함수 구조
- 4-1) account_query::write_rec()
void account_query::write_rec()
{
ofstream outfile;
outfile.open("record.bank", ios::binary | ofstream::app);
read_data();
outfile.write(reinterpret_cast<char*>(this), sizeof(*this));
outfile.close();
}
1 ) ofstream outfile;를 통해 ofstream의 인스턴스 outfile을 생성한다 2 ) ofstream에는 ios 클래스나 ostream 클래스로부터 상속받지 않은 ofstream만 가지고 있는 7가지 public 멤버 함수가 있다.
- constructor : 생성자
- open()
- is_open()
- close()
- rdbuf()
- operator =()
- swap()
**(std::ofstream의) constructor(생성자)
// default (1)
ofstream();
// initialization (2)
explicit ofstream (const char* filename, ios_base::openmode mode = ios_base::out);
explicit ofstream (const string& filename, ios_base::openmode mode = ios_base::out);
//copy (3)
ofstream (const ofstream&) = delete;
// move (4)
ofstream (ofstream&& x);
-> default로 기본 생성자 :
ofstream의 객체가 어떤 파일과도 연관이 없을 때 호출된다. 위의 ofstream outfile;는 기본생성자를 호출한 형태의 예시이다. 또한 내부적으로 해당 객체(여기선 outfile)의 ostream 기본 생성자(base constructor)는 새로 구성된 filebuf 객체(내부 파일 스트림 버퍼)에 대한 포인터를 전달한다. 이 부분이 이해가 잘 가지 않는다. ofstream 클래스는 ostream 클래스를 상속받고 있다. 따라서 ofstream의 생성자를 호출할 때 부모 클래스인 ostream의 클래스의 기본생성자를 같이 호출해서 값(filebuf 객체에 대한 포인터)을 넘겨주고 있는 것 같다.
-> 파라미터를 가진 생성자 :
파라미터를 가진 생성자는 두 종류이다. 특징적인 점은 컴파일러에 의한 암시적인 변환을 막기 위해 두 생성자 모두 앞에 explicit 키워드가 있다.
- 두 생성자의 차이점은 filename을 어떤 타입으로 받는가이다. 하나는 const char* 타입의 filename을 입력 받고 다른 하나는 const string& 타입의 filename을 입력 받는다.
const std::string& str = "filename";
ofstream out(str);
const char* chr = "file";
ofstream out2(chr);
- 생성자의 두 번째 파라미터는 어떤 모드로 파일을 open 할지 결정하는 파라미터이다. ios_base::openmode mode = ios_base::out 는 얼핏 보면 매우 복잡하다.
ios_base::openmode mode = ios_base::out
위 코드를 (ios_base::openmode), (mode), (ios_base::out) 3가지로 분리해서 봐야한다.
1 ) ios_base::openmode의 뜻 : ios_base 클래스안에는 openmode라는 것이 있다. F12를 통해 openmode의 정의로 이동하면 using openmode = int; 라는 정의가 나온다. 즉 ios_base::openmode는 ios_base :: int 와 같으며 파일을 어떤 형식으로 open 할지 정수로 결정하고 있음을 알 수 있다. 레퍼런스에 따르면 openmode는 bitmask member type의 객체이다. openmode는 멤버 상수(member constants)의 결합으로 구성되어 있다는 설명으로 되어 있다.
2 ) mode : 이건 int a; 같이 그냥 변수명이었다.
3 ) ios_base::out의 뜻 : ios_base 클래스에 mode를 어떻게 할건지 정의가 되어있다. 여기선 그 중 하나인 out을 쓴 것
멤버 상수(out 자리에 올 수 있는 값들) | 내용 |
---|---|
in | 읽기 전용으로 file open |
out | 쓰기 전용으로 file open |
binary | txt 형식이 아닌 2진형식으로 파일 오픈 |
ate | 파일을 오픈하면서 파일 포인터를 끝부분으로 이동 |
app | 파일에서 내용이 끝나는 부분에서 커서가 시작(내용을 덧붙이기) |
trunc | truncate의 준말. 파일을 열면서 이전에 있었던 내용을 전부 삭제하고 open |
** 위 내용들은 전부 | 를 통해 한 번에 여러 옵션으로 줄 수 있다.
예제 코드
std::ios_base::openmode mode = std::ios_base::out;
std::ios_base::openmode mode2 = std::ios_base::binary;
std::ofstream ofs("test", std::ios_base::out | std::ios_base::binary);
std::ofstream ofs2("test", mode | mode2);
본문 코드
ofstream outfile;
outfile.open("record.bank", ios::binary | ofstream::app);
=> 본문은 예제와 다르게 ios_base::binary를 하지 않고 ios::binary를 했는데, ios_base를 ios가 상속 받았기 때문에 상관이 없는 것 같다. ios::binary 말고 ofstream::app을 해봤는데도 문제 없이 작동한다.
-> 복사 생성자 : 자기 자신의 클래스 참조 타입을 인자로 받는 생성자(const로 주로 받는다)
1 ) 복사 생성자가 delete 키워드를 통해 막혀 있다. ofstream의 객체는 서로 복사 생성할 수 없다.
std::ofstream ofs("test.txt", std::ios_base::out | std::ios_base::binary);
std::ofstream copy_ofs(ofs);
여기서 std::ofstream copy_ofs(ofs);의 경우 삭제된 함수입니다라는 오류를 발생시킨다. delete를 통해 복사 생성자가 삭제되었기 때문이다.
-> 오른값 참조 :
ofstream (ofstream&& x); 오른값 참조를 통해 소유권을 아예 이동(move) 시키는 것은 가능하다
**(std::ofstream의) open() 멤버함수
void open (const char* filename, ios_base::openmode mode = ios_base::out);
void open (const string& filename, ios_base::openmode mode = ios_base::out);
- open 멤버함수는 constructor에서 파라미터를 가진 constructor와 완전히 동일하다.
- open에 대상이 된 filename이 없으면 파일을 생성한다.
- open에 성공하면 goodbit을 set한다. 반면에 실패하면 failbit를 set한다.
- Reference에 따르면 mode 설정시 trunc와 app을 동시에 사용해 파일을 여는 것에 실패한다고 한다
- ios 클래스에서 스트림의 상태를 관리한다. 이 때, 스트림의 상태를 관리하는 flag(단순히 1bit 0 혹은 1로만 표시)가 4개 정의되어 있다. 4개의 flag는 goodbit, badbit, failbit, eofbit이다.
ios에 정의 된 flag들 | 내용 | |
---|---|---|
goodbit | 스트림에 입출력 작업이 가능할 때 | 테스트3 |
badbit | 스트림에 복구 불가능한 오류 발생시 | 테스트3 |
failbit | 스트림에 복구 가능한 오류 발생시 | 테스트3 |
eofbit | 입력 작업시에 EOF 도달 | 테스트3 |
접근은 아래와 같이 할 수 있다. -> 어디에 쓰이는지는 모르겠다.
cout << ios::goodbit << endl;
cout << std :: boolalpha <<cin.good() << endl;;
cout << ios::badbit << endl;
cout << std::boolalpha << cin.bad() << endl;
cout << ios::failbit << endl;
cout << std::boolalpha << cin.fail() << endl;
cout << ios::eofbit << endl;
cout << std::boolalpha << cin.eof() << endl;
- open() 함수가 파일을 여는데 실패하면 failbit가 set된다(1로 변한다)
**(std::ofstream의) is_open() 멤버함수
bool is_open() const;
- 파일이 정상적으로 열렸는지 확인하는 함수. 반환형이 bool 타입이므로 정상적으로 열렸다면 true를, 열리지 않았다면 false를 반환한다.
- 파일이 정상적으로 열렸다는 뜻 == 파일이 열리고 && stream 객체와 연결이(associated) 되었다.
- 실패는 다른 모든 경우
- Reference에 따르면 Streams는 멤버함수 open() 혹은 생성자의 호출을 성공시켜 파일과 연결될(associated) 수 있다. 혹은 close() 함수나 소멸자를 통해 파일과 연결이 끊길 수 있다(disassociated).
- stream과 연관되어-연결되어(assoication) 있는 파일은 내부 stream buffer에 보관되어 있다.
- 내부적으로 rdbuf()->is_open()로 함수가 호출된다.
```c++ // ofstream::is_open #include// std::cout #include // std::ofstream
int main () { std::ofstream ofs; ofs.open (“test.txt”); if (ofs.is_open()) { ofs « “lorem ipsum”; std::cout « “Output operation successfully performed\n”; ofs.close(); } else { std::cout « “Error opening file”; } return 0; } // 출처 : cplusplus.com 에서 가져온 코드입니다.
위 예제 코드에서 stream 객체는 ofstream의 객체인 ofs를 뜻한다. ofs.is_open()이 true이기 위해선 ofs와 test.txt 파일이 서로
associated되어 있어야 한다.
<br/>
#### **(std::ofstream의) close() 멤버함수
```c++
void close();
- 열린 파일을 닫을 때 사용한다.
- 파일을 닫지 못하면(close() 함수가 호출되기 이전에 파일을 open()한적이 없어서 닫지 못하는 경우르 포함) failbit가 stream에 set 된다.
- 내부적으로는 rdbuf()->close()로 호출된다.
std::ofstream ofs; ofs.open("test.txt"); ofs.close();
**(std::ofstream의) rdbuf() 멤버함수
filebuf* rdbuf() const;
- 내부적으로 존재하는 filebuf 객체의 포인터(주소)를 반환해준다.
- 주의! : 지금 stream buffer와 associated 된 것(되어 있는 filebuf)와 항상 같을 필요는 없다.
- 인터넷 검색 결과 : 서로 다른 두 스크림을 연결한다.(이해가 잘 안간다)
**(std::ofstream의) operator = 멤버함수
// copy (1)
ofstream& operator= (const ofstream&) = delete;
// move (2)
ofstream& operator= (ofstream&& rhs);
- 복사 생성자처럼 (복사) 대입 연산자도 delete 키워드를 통해 막혀있다.(삭제된 함수)
- 복사 생성자와 동일하게 오른값 참조를 통한 이동(move)는 지원한다.
ofstream ofs("test", std::ios_base::binary); ofstream ofs2; ofs2 = ofs;
위 코드에서 ofs2 = ofs;를 지원하지 않는다는 뜻
**(std::ofstream의) swap() 멤버함수
void swap (ofstream& x);
- swap() 멤버함수를 호출한 객체와 x를 서로 맞바꾸는 기능을 한다.
- 내부적으로 stream buffer에 연결된 형태까지 바꾸는 것 같다.
- 대상은 다른 ofstream 객체이다
- Exchanges all internal data between x and *this. (x와 *this( swap()함수를 호출한 객체 아래 코드에서는 foo가 된다 )의 모든 내부적인 데이터를 교환한다는 뜻.
- Internally, the function calls ostream::swap and then calls the member swap of the associated filebuf object. (내부적으로 ofstream의 swap()함수가 ostream의 swap()함수를 호출한 후, associated filebuf 객체의 멤버 swap을 호출합니다 -> 무슨 뜻일까) ```c++ #include
// swapping ofstream objects #include // std::ofstream
int main() { std::ofstream foo; std::ofstream bar(“test.txt”);
foo.swap(bar);
foo << "lorem ipsum";
foo.close();
return 0; // 출처 : cplusplus.com } ``` - 위 코드에서도 ofstream의 기본 생성자로 foo 객체가 찍히게 된다. ofstream의 또다른 객체 bar가 파라미터를 가진 생성자로 생성된다. bar라는 객체가 test.txt와 내부적으로 associated 되어 있었는데 swap를 통해 이 관계를 foo라는 객체가 test.txt 파일과 associated 하게 만들어 주는 것 같다.
2 - 1 ) ofstream 객체 outfile을 이용해서 record.book을 (binary 모드 && 덧붙이기 모드)로 open 한 후 read_data();를 통해 account_query 클래스의 멤버변수 값들 채운다.
3 )
outfile.write(reinterpret_cast<char*>(this), sizeof(*this));
outfile.close();
-> write() 함수는 ofstream 클래스의 멤버함수가 아니라 ostream 클래스의 멤버함수이다.
**(std::ostream의) write() 멤버함수
ostream& write (const char* s, streamsize n);
- 첫번째 파라미터 s는 최소 길이가 n개의 문자로 구성된 배열의 포인터(주소)
- n은 집어넣을 문자들의 개수.
본문
A.write_rec();
=> 본문에서 A.write_rec();를 호출 -> void account_query::write_rec() 멤버 함수 안에서 outfile.write(reinterpret_cast<char >(this), sizeof(this));이 호출되고 있는 상황.
-> 여기서 this는 A객체의 주소(데이터 타입은 account_query * 이다. )를 *this는 A객체가 들고 있는 모든 데이터를 뜻한다. -> reinterpret_cast<char *>를 통해 account_query * 를 char *로 바꿨다. reinterpret_cast<바꿀 타입="">(대상)은 임의의 포인터 타입끼리 변환을 허용하는 캐스트 연산자이다.바꿀>
-> reinterpret_cast<char >를 거쳐 const char * 조건을 맞추고, sizeof(this) 를 통해 객체의 크기를 넣어줬다.
-> write()함수의 결론 : outfile에 연결된 파일에 A객체의 주소를 타고 들어가서(첫번째 파라미터), A객체의 크기만큼 정보를 입력시켜준다.
4 ) outfile.close()를 통해 파일을 닫아준다.