Banking Record System Project 해석3
in Project on Analyze, Project
- 4-4) void account_query::edit_rec() 본문
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); // 1번 부분
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); // 2번 부분
iofile.seekp((n-1)*sizeof(*this));
cout<<"\nEnter data to Modify "<<endl;
read_data();
iofile.write(reinterpret_cast<char*>(this), sizeof(*this));
}
- 이번에는 파일을 fstream 클래스를 이용해서 열었다. ifstream과 ofstream과 다르게 fstream으로 열면 읽고/쓰기를 다 할 수 있다.
iofile.open("record.bank", ios::in|ios::binary);
위 코드를 통해 .open() 함수에서 openmode를 지정할 때 | 를 이용해서 여러개를 지정할 수 있음을 알 수 있다. 파일을 읽어들이는 mode(in)과 파일을 바이너리 모드로 연다고 되어 있다.
1번 부분
iofile.seekg(0, ios::end); // 1번
int count = iofile.tellg()/sizeof(*this); // 2번
cout<<"\n There are "<<count<<" record in the file";
cout<<"\n Enter Record Number to edit: ";
cin>>n; // 3번
iofile.seekg((n-1)*sizeof(*this)); // 4번
iofile.read(reinterpret_cast<char*>(this), sizeof(*this)); // 5번
cout<<"Record "<<n<<" has following data"<<endl;
show_data(); // 6번
iofile.close(); // 7번
- 1번 : .seekg 함수를 이용해서 커서를 파일의 맨 끝으로 이동(파라미터 2개)
- 2번 : .tellg()함수를 이용해서 현재 커서의 위치를 구한다. 그 위치를 edit_rec() 함수를 호출한 객체(*this)의 크기로 나눠주면 이 파일에 몇개의 내용이 들어가 있는지 알 수 있다.
- 3번 : 이 함수도 수정할 내용을 찾을 때 account_number로 찾는 것이 아니고 파일의 몇번째에 입력 되었는지를 기준으로 검색한다. 파일에 3개의 정보가 입력되어 있다면, n = 2를 입력하면 2번째로 입력한 정보를 수정하는 방식이다.
- 4번 : .seekg 함수를 이용해서 커서를 앞에서부터 (n-1)sizeof(this) 만큼의 위치에 이동. 파일에 12byte,,12byte,,12byte 이렇게 정보가 있다하자(객체의 크기가 12byte이고 3번 파일에 입력을 했다 가정했을시). n = 2를 입력하면 1 * sizeof(*this) = 12byte가 된다. 커서는 두번째 12byte 맨 앞을 가리키고 있을 것이다.
- 5번 : .read 함수를 이용해서 sizeof(*this) (= 12byte) 만큼 파일에서 긁어와서 첫번째 파라미터로 넣어준 주소에 데이터를 입력해준다.
- 6번 : show_data()는 this -> show_data()이므로 위의 .read() 함수를 이용해서 파일에서 긁어온 데이터가 저장된 A 객체의 정보를 show_data()를 통해 콘솔로 출력한다
- 7번 : 파일을 닫아 준다
2번 부분
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));
.seekp() 함수 => std::ostream::seekp
(1)
ostream& seekp (streampos pos);
(2)
ostream& seekp (streamoff off, ios_base::seekdir way);
(1)
istream& seekg (streampos pos);
(2)
istream& seekg (streamoff off, ios_base::seekdir way);
출처 : cplusplus.com
위 코드가 seekp 선언이고, 아래가 seekg 선언이다. 반환형을 제외하면 전혀 차이가 없다. 둘 다 파라미터를 1개만 쓰면 파일에서 맨 앞을 기준으로 커서를 이동시켜주고, 파라미터를 2개 쓰면 way에 오는 값을 기준으로 off의 값이 양수면 뒤로, off의 값이 음수면 앞으로 커서를 이동시켜준다. way에 올 수 있는 값도 동일하다. 차이점은 파일을 읽을 때 커서를 이동시키는 함수로 seekg를 쓰고, 파일에 쓸 때 커서를 이동시키는 함수로 seekp를 쓸 수 있다는 것 같다.
본문에서 iofile이 파일을 읽기/쓰기/바이너리 모드로 연다.
위에서 n = 2일 경우 iofile.seekp(12byte)에 의해 앞에서부터 12byte인 곳에 커서가 위치한다(2번째 자료가 시작하는 부분)
그 후 read.data() 함수를 호출하여 객체 A에 변경하고 싶은 정보를 입력한다.
iofile.write(reinterpret_cast<char*>(this), sizeof(*this));
마지막으로 .write() 함수를 이용해서 첫번째 파라미터로 입력한 주소에 접근해서(여기선 객체 A의 시작주소), 두 번째 파라미터에 입력한 크기만큼(여기선 객체 A의 크기만큼) 파일에 정보를 입력한다. 이 때 앞서 실행된 iofile.seekp((n-1)sizeof(this)); 이 코드의 역할로 커서가 2번째(내가 수정하고자 했던 정보) 정보 앞에 위치하므로 2번째 위치한 정보가 내가 새로 입력한 정보로 수정된다.
커서 이동 -> 수정할 정보 입력 받기(객체로 입력 받기) -> 이동한 커서부터 내용을 덮어쓰기
- 4-5) void account_query::delete_rec() 본문
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: ";
// .seekg() 랑 .tellg() 함수가 약간 형제처럼 사용되는 함수들.
// .seekg(얼만큼 이동할지, 기준점) .seekg(0,ios::end);는 ios::end를 기준으로 0만큼 이동 즉 파일의 가장 끝으로 이동 - 매개변수 2개
// .seekg(얼만큼 이동할지) --> 시작 지점으로부터 일정 위치로 워프 - 매개변수 1개
// .tellg() -> 현재 위치를 정수값으로 리턴 : 시작 위치일 경우 0, 파일 끝으로 이동했으면 파일의 글자수가 리턴
// file의 글자 수를 객체의 크기로 나눠서 객체 안에 몇개가 들어가 있는지 계산하는 코드
cin>>n; // 이거 n이 순서다. account_number를 가지고 지우는게 아니라 파일에 저장된 순서를 가지고 지우게 되어있다.
fstream tmpfile;
tmpfile.open("tmpfile.bank", ios::out|ios::binary);
// 중간에 거르는거 넣어보기
if (tmpfile.fail())
{
cout << "\nError in opening! File Not Found!!" << endl;
return;
}
infile.seekg(0); // infile의 커서를 파일의 맨 앞에 위치시킴
for(int i=0; i<count; i++)
{
infile.read(reinterpret_cast<char*>(this),sizeof(*this)); // 왜 읽는건지?
if(i==(n-1))
continue; // 파일에 데이터가 (count = )5개가 적혀있고 내가 (n=)2번째걸 지운다하면 i == 1가 되면(2번째가 되면) 밑에 tmpfile.write을 실행 안하고 for문 처음으로 가버린다.
// 임시파일에 내가 지우려고 했던 내용이 적히지 않는 것
tmpfile.write(reinterpret_cast<char*>(this), sizeof(*this));
}
infile.close();
tmpfile.close();
remove("record.bank"); // cstdio에 remove 함수가 존재한다. -> 글자 그대로 파일을 없앤다. 반환형은 int : 파일이 없어지면 int 0, 못 없애면 0이 아닌 값이 반환된다.
rename("tmpfile.bank", "record.bank"); // rename은 왼쪽 글자를 -> 오른쪽 글자의 파일명으로 파일 명을 바꿔준다.
}
커서 이동하고, 파일 개수 구하는 과정은 동일
임시파일을 연다 -> 커서를 처음으로 이동시킨다 -> 원래 파일에서 정보를 읽는다 -> 단 내가 지우려고 했던 데이터는 continue를 통해 건너 뛰고 읽는다. -> 읽은 정보를 임시파일에 쓴다. :: 파일에서 정보를 삭제하는 방법이 내가 원하는 내용을 원래 파일에서 삭제하는 방식이 아니다. 대신 원래 파일에서 삭제하고 싶은 부분만 빼고 읽어서 임시파일에 다시 적는 방식. A ,, B ,, C, ->, A 읽고(원래파일) A 쓰고(임시파일) B 건너뛰고 C 읽고 C 쓰고 이 방식.
그 후에 remove와 rename을 이용해서 정보가 적힌 파일은 결국 1개만 존재하게 된다.
1. remove() 함수 -> cstdio에 있는 함수
int remove ( const char * filename );
- 기능은 단순하다. filename과 동일한 이름을 찾아서 제거한다. 제거에 성공하면 int 0을 반환하고 파일 제거에 실패하면 0이 아닌 값을 반환한다.
2. rename() 함수 -> cstdio에 있는 함수
int rename ( const char * oldname, const char * newname );
왼쪽 파라미터에 적힌 이름을(oldname) 오른쪽 파라미터에 적힌 이름(newname)으로 바꾼다. 파일명이 바뀌었으면 0을 반환한다. 파일명을 바꾸는데 실패하면 0이 아닌 값을 반환한다.
파일명을 바꾸는 연산(operation)은 파일에 직접적으로 작동한다고 되어 있다. streams는 연산에 포함되지 않는다는 설명이다.