13.7)이진 입출력
ios::binary 모드로 이진 입력과 출력을 하루 있습니다. 이전 포스트에서 배운 텍스트 파일 입출력은 메모장이나 텍스트 탐색기로 열수 있지만 이진 파일은 그 파일에 따라 특별한 프로그램으로 열어야 사람의 눈으로 읽을 수 있습니다. 실제 이진 파일을 메모장으로 열면 메모장의 논리로는 제대로 값을 읽어오지 못하기 때문에 가비지 값으로 가득찬 파일을 보게 됩니다.
하지만 이진 파일은 텍스트 파일이 저장되기 위해 문자 - >이진수 변환 과정을 가지는 것과 달리 바로 저장되기 때문에 성능면에서 우수합니다.
또, 저장되는 방식도 다릅니다. 예를 들어 10진수 199를 저장할 때 텍스트 파일은 먼저 ,'1', '9', '9'를 각각 문자로 인식하고 각 문자를 아스키코드의 숫자로 변환합니다. (49, 57, 57) 그리고 아스키코드를 이진수로 바꿔 저장합니다.(110001,111001,111001) 하지만 이진 파일에서는 11000111를 저장합니다.( 00000000000000000000000011000111)
즉, 텍스트 입출력은 문자 부호화(encoding)과 복호화(decoding)을 위한 추출 과정이 필요합니다.
텍스트 파일에 출력하고 입력하기 위해서는 << 연산자와 put함수, >>연산자와 getline함수 그리고 get함수를 사용했습니다. 이진 파일에 입력과 출력하기 위해서는 read함수와 write함수를 사용합니다.
#출력이 쓰기, 입력이 읽기
write함수는 기본적으로 문자 배열이름(const char*)과 배열크기(int)를 매개변수로 받습니다. 따라서 int형이나 다른 자료형을 받을 때는 reinterpret_cast<char*>을 사용해 인자로 넣어주어야 합니다. 문자열은 문자열.c_str()과 문자열.size()로 받습니다.
write함수 모양 ▼
streamObject.write(const char* s, in size);
read 함수도 문자열이 아니면 reinterpret_cast<char*>(&변수)과 sizeof(변수)로 받고 cout으로 출력합니다.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
fstream binaryio("city.dat", ios::out, ios::binary);
int value = 199;
binaryio.write(reinterpret_cast<char*>(&value), sizeof(value));
binaryio.close();
cout << "Done" << endl;
const int SIZE = 10;
binaryio.open("city.dat", ios::in, ios::binary);
int val;
binaryio.read(reinterpret_cast<char*>(&val), SIZE);
cout << "Number of chars read: " << binaryio.gcount() << endl;
cout << val << endl;
binaryio.close();
return 0;
}
string은 특이한 방법으로 씁니다 s.c_str로 꼭바꿔서 입력해주어야 합니다.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
fstream binaryio("city.dat", ios::out, ios::binary);
string s = "Atlanta";
binaryio.write(s.c_str(), s.size());
binaryio.close();
cout << "Done" << endl;
return 0;
}
#include <iostream>
#include <fstream>
using namespace std;
int main() {
const int SIZE = 10;
fstream binaryio("city.dat", ios::in, ios::binary);
char s[SIZE];
binaryio.read(s, SIZE);
cout << "Number of chars read: " << binaryio.gcount() << endl;
s[binaryio.gcount()] = '\0';
cout << s << endl;
binaryio.close();
return 0;
}

string을 입력받을 때(읽기)는 s[fstream객체.gcount()] ='\0'으로 줄바꿈을 넣어주지 않으면 아래처럼 어디까지 읽어와야 하는지 몰라 (string으로 제대로 인식 못함) 가비지값을 읽어옵니다.

13.7.3) 배열의 이진 입출력
#include <iostream>
#include <fstream>
using namespace std;
int main() {
const int SIZE = 5;
fstream binaryio;
binaryio.open("array.dat", ios::out | ios::binary);
double array[SIZE] = { 3.4, 1.3, 2.5, 5.66, 6.9 };
binaryio.write(reinterpret_cast<char*>(&array), sizeof(array));
binaryio.close();
binaryio.open("array.dat", ios::in | ios::binary);
double result[SIZE];
binaryio.read(reinterpret_cast<char*>(&result), sizeof(result));
binaryio.close();
for (int i = 0; i < SIZE; i++) {
cout << result[i] << " ";
}
return 0;
}

13.7.4) 객체의 이진 입출력
fstream객체명.write(reinterpret_cast<char*> (&작성할객체명), sizeof(작성할객체명));
#include <iostream>
#include <fstream>
#include "Student.h"
using namespace std;
int main() {
fstream binaryio;
binaryio.open("student.dat", ios::out | ios::binary);
Student student1("John", "T", "Smith", 90);
Student student2("John", "T", "Smith", 90);
Student student3("John", "T", "Smith", 90);
Student student4("John", "T", "Smith", 90);
binaryio.write(reinterpret_cast<char*>(&student1), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student2), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student3), sizeof(Student));
binaryio.write(reinterpret_cast<char*>(&student4), sizeof(Student));
binaryio.close();
binaryio.open("student.dat", ios::in | ios::binary);
Student studentNew;
binaryio.read(reinterpret_cast<char*>(&studentNew), sizeof(Student));
displayStudent(studentNew);
binaryio.read(reinterpret_cast<char*>(&studentNew), sizeof(Student));
displayStudent(studentNew);
binaryio.close();
return 0;
}
13.8) 임의 접근 파일
파일 입출력에서 값을 읽을 때에는 파일 포인터(file pointer)라는 것을 이용합니다. c++에서 파일을 읽을 때는 파일 시작에서 왼쪽에서 오른쪽으로 읽는 것이 규칙이기 때문에(단, ios::app를 사용하명 파일 끝부터 다른 데이터 뒤에 항목을 쓴다.) 중간에 이미 지나친 문자를 읽고 싶다면 다시 처음부터 읽어야 합니다.
하지만 seekp와 seekg 멤버 함수를 이용하면 자유롭게 파일 포인터를 앞이나 뒤로 건너뛰게 할 수 있습니다. 이런 기능을 임의 접근 파일(random access file)이라고 합니다.
seekg는 입력스트림(ios::in)을 위해 사용되고, seekp는 출력스트림(ios::out)을 위해 사용됩니다.
input.seekg(0); 과 output.seekp(0);은 파일포인터를 파일 시작 위치로 이동시킵니다.
또, seekp와 seekg모두 모드를 가지고 있습니다.
ios::beg 파일의 시작 위치로부터 오프셋을 계산
ios::end 파일의 끝으로부터 오프셋을 계산
ios::cur 현재 파일 포인터로부터 오프셋을 계산
예시)
seekg(100L, ios::beg); 파일의 시작 위치로부터 100번째 바이트로 파일 포인터 이동.
seekg(-100L, ios::end); 파일의 끝에서부터 역방향으로 100번째 바이트로 파일 포인터 이동.
seekp(42L, ios::cur); 현재의 파일 포인터로부터 전방향으로 42번째 바이트만큼 파일 포인터 이동.
seekp(-42L, ios::cur); 현재의 파일 포인터로부터 역방향으로 42번째 바이트만큼 파일 포인터 이동.
seekp(100L); 파일의 100번째 바이트로 파일 포인터 이동.
또, binaryio.seekg(2 * sizeof(double));는 시작 위치로부터 2칸 포인터를 이동한 것입니다. fstream 객체 .tellg()로 현재 위치를 알 수 있는데 아래 코드를 보면 16(double*2)만큼 이동한 것을 알 수 있습니다.
#include <iostream>
#include <fstream>
using namespace std;
int main() {
const int SIZE = 5; // 앞의 배열입출력 실습부분
double array[SIZE] = { 3.4, 1.3, 2.5, 5.66, 6.9 };
fstream binaryio;
binaryio.open("array.dat", ios::out | ios::binary);
binaryio.write(reinterpret_cast<char*>(array), sizeof(array));
binaryio.close();
//step1: 3번째 숫자 읽어오기
binaryio.open("array.dat", ios::in | ios::out | ios::binary);
double num;
cout << "현재 위치 주소: " << binaryio.tellg() << endl;
binaryio.seekg(2 * sizeof(double)); //원소 2개 건너뜀
cout << "현재 위치 주소: " << binaryio.tellg() << endl;
binaryio.read(reinterpret_cast<char*>(&num), sizeof(double));
cout << "num = " << num << endl;
//step2: 3번째 숫자를 4.56으로 갱신하기
num = 4.56;
binaryio.seekp(2 * sizeof(double));
binaryio.write(reinterpret_cast<char*>(&num), sizeof(double));
//step3: 갱신된 3번째 숫자 읽어오기
binaryio.seekg(2 * sizeof(double)); //모두 읽어와서 출력하게 해보기
binaryio.read(reinterpret_cast<char*>(&num), sizeof(double));
cout << "num = " << num << endl;
// 모두 읽어와 출력하기
binaryio.seekg(0);
double result[SIZE];
binaryio.read(reinterpret_cast<char*>(result), sizeof(result));
binaryio.close();
for (int i = 0; i < SIZE; i++) // 배열출력
cout << result[i] << " ";
return 0;
}

13.9) 파일 갱신
ios::in | ios::out | ios::binary 모드를 사용하여 파일을 열면 이진 파일을 갱신할 수 있습니다.
binaryio.open("array.dat", ios::in | ios::out | ios::binary);
double num = 4.56;
binaryio.seekp(2 * sizeof(double));
binaryio.write(reinterpret_cast<char*>(&num), sizeof(double));
checkpoint)
13.12 텍스트 파일과 이진 파일이란 각각 무엇인가? 텍스트 편집기를 사용하여 텍스트 파일 또는 이진 파일의 내용을 볼 수 있는가?
텍스트 파일은 볼 수 있으나 이진 파일은 볼 수 없다.
13.13 이진 입출력에 대한 파일을 여는 방법은 무엇인가?
fstream ioput;
ioput.open("파일이름",io::out | io :: in | io::binary);
13.14 write 함수는 바이트의 배열에만 쓰기가 가능하다. 원시 유형 값 또는 객체를 이진 파일에 쓰는 방법은 무엇인가?
ioput.write(reinterpret_cast<char*>(&적을 변수명), sizeof(변수명));</char*>
13.15 ASCII 텍스트 파일에 문자열 "ABC"를 쓰는 경우, 파일에 저장되는 값은 무엇인가?
'A' 'B' 'C'
13.16 ASCII 텍스트 파일에 문자열 "100"을 쓰는 경우, 파일에 저장되는 값은 무엇인가?
'1' '0' '0'
만일 이진 입출력을 사용하여 바이트 유형의 숫자 값 100을 쓰는 경우, 파일에 저장되는 값은 무엇인가?
1100100
13.17) 파일 포인터란 무엇인가?
파일을 순차적으로 읽기 위해 파일의 문자를 하나씩 가리키는 포인터입니다.
13.18) seekp와 seekg의 차이점은 무엇인가?
seekp는 출력스트림에서 사용되고 seekg는 입력스트림에서 사용된다. put과 get을 생각하면 외우기 쉽다.
'c, c++ > c++로 시작하는 객체지향 프로그래밍' 카테고리의 다른 글
16.1~16.4, 16.6~ 예외 처리 (0) | 2024.06.18 |
---|---|
14.1~ 연산자 오버로딩 (1) | 2024.06.18 |
13.1~ 13.5)파일 입력과 출력 (0) | 2024.05.24 |
15.5~15.9) 상속과 "다형성" (0) | 2024.05.09 |
15.1~15.4, 15.8) 상속 (0) | 2024.05.02 |