Banking Record System 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++의 입출력 라이브러리는 다음과 같은 클래스들로 구성되어 있다.
입출력 구조23

  • 클래스의 객체를 찍을 수 있는 것은, 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
binarytxt 형식이 아닌 2진형식으로 파일 오픈
ate파일을 오픈하면서 파일 포인터를 끝부분으로 이동
app파일에서 내용이 끝나는 부분에서 커서가 시작(내용을 덧붙이기)
trunctruncate의 준말. 파일을 열면서 이전에 있었던 내용을 전부 삭제하고 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()를 통해 파일을 닫아준다.