std::fstream은 "파일"에 대한 입력 및 출력을 지원하는 클래스로 파일 스트림 클래스라고도 불립니다.
(std::ifstream와 std::ofstream의 기능을 모두 포함하고 있어 파일을 읽고 쓸 수 있습니다)
std::fstream은 <fstream> 헤더 파일에서 정의되며, std::fstream 클래스 내부에 구현된 함수를 사용하여 파일을 열고 닫습니다.
아래는 std::fstream 클래스의 주요 멤버 함수입니다.
open() : 파일을 엽니다.
close() : 파일을 닫습니다.
read() : 파일에서 데이터를 읽습니다.
write() : 파일에 데이터를 씁니다.
seekg() : 파일 내에서 읽기 위치를 이동합니다.
seekp() : 파일 내에서 쓰기 위치를 이동합니다.
tellg() : 읽기 위치를 반환합니다.
tellp() : 쓰기 위치를 반환합니다.
eof() : 파일 끝에 도달했는지 확인합니다.
먼저, 파일을 읽고 쓰는 간단한 코드를 한번 보도록 하겠습니다.
예제: 파일 읽고 쓰기
#include <iostream>
#include <fstream>
int main() {
std::fstream fs;
fs.open("example.txt", std::ios::in | std::ios::out | std::ios::binary);
if (!fs) {
std::cout << "파일 열기 실패" << std::endl;
return 1;
}
// 파일 읽기
char buffer[256];
fs.read(buffer, 256);
std::cout << buffer << std::endl;
// 파일 쓰기
fs.write("Hello World!", 12);
fs.close();
return 0;
}
먼저 fs라는 이름의 fstream 인스턴스를 활용하여 fstream 내부함수인 open 함수를 호출합니다,
파일이름은 example.txt 이고, 파일 처리 방식을 입출력이 모두 가능한 바이너리 형식으로 지정했습니다.
(여러 개의 열기 모드를 지정할 때는 비트 OR 연산자(|)를 사용)
아래는 open 함수의 주요 열기 모드입니다.
ios_base::in : 파일을 읽기 위해 엽니다.
ios_base::out : 파일을 쓰기 위해 엽니다.
ios_base::app : 파일 끝에 추가하기 위해 엽니다.
ios_base::binary : 이진 파일로 엽니다.
파일 읽기에 실패한 경우 읽기 실패에 대한 로깅을 하고 프로그램을 종료하고,
그렇지 않은 경우라면 read 함수로 파일 내용을 읽은 다음 buffer 라는 이름의 배열에 저정한 후 콘솔 출력합니다.
그리고 Hello World! 를 파일 끝에 추가로 쓰고 파일을 닫습니다.
예제 : 로그 파일 생성
로그 파일을 생성할 때는 이전 로그 내용을 보존하면서 새로운 로그 내용을 추가해야 하므로 std::fstream::out | std::fstream::app 모드로 파일을 열어, 새로운 로그 내용이 파일 끝에 추가되게끔 함으로써 이전 로그 내용을 계속 유지할 수 있습니다.
#include <iostream>
#include <fstream>
int main() {
std::fstream log_file;
log_file.open("log.txt", std::fstream::out | std::fstream::app);
if (!log_file.is_open()) {
std::cerr << "Failed to open log file." << std::endl;
return -1;
}
log_file << "New log message." << std::endl;
log_file.close();
return 0;
}
위 예제에서는 log.txt 파일을 쓰기 모드로 열면서 파일 끝에 추가 쓰기 모드를 지정하여 로그 파일에 새로운 로그 메시지를 추가합니다. 만약 파일을 열지 못했을 경우, is_open() 함수를 사용하여 파일이 열렸는지 확인합니다.
fstream과 버퍼
std::fstream은 기능적 특성상 내부적으로 파일 입출력 작업을 위한 버퍼를 가지고 있습니다.
버퍼는 일시적으로 데이터를 저장하고, 저장된 데이터를 파일에 쓰거나 파일에서 읽어들일 때 사용됩니다.
std::fstream 클래스는 내부적으로 두 개의 버퍼를 가지고 있습니다.
하나는 입력 버퍼(input buffer)이고, 다른 하나는 출력 버퍼(output buffer)입니다.
입력 버퍼는 파일에서 읽어들인 데이터를 저장하는데 사용되며, 출력 버퍼는 파일에 쓰기 위한 데이터를 저장하는데 사용됩니다
std::fstream 클래스에서는 버퍼링을 사용하여 입출력 작업의 성능을 향상시킬 수 있습니다.
버퍼링을 사용하면 데이터를 파일에 쓰거나 파일에서 읽어들이는 작업을 수행할 때, 매번 파일에 직접 접근하지 않고 버퍼에 데이터를 저장하거나 버퍼에서 데이터를 읽어들입니다. 이렇게 함으로써 파일 입출력 작업이 더욱 효율적으로 수행될 수 있습니다.
std::fstream 클래스에서는 기본적으로 출력 버퍼링이 사용되며, 입력 버퍼링은 사용되지 않습니다.
즉, 출력 작업은 버퍼에 쌓인 데이터가 일정 크기 이상이 되거나, 파일을 닫을 때까지 버퍼에 저장되어 있게 됩니다.
반면에 입력 작업은 즉시 처리되며, 파일에서 데이터를 읽어들이면 입력 버퍼에 저장하지 않고 즉시 읽어들입니다.
버퍼링을 제어하기 위해서는 std::fstream 클래스의 멤버 함수인 flush()와 rdbuf()를 사용할 수 있습니다.
flush() 함수는 출력 버퍼에 저장된 데이터를 파일에 쓰는 작업을 수행하며, rdbuf() 함수는 입력 및 출력 버퍼에 대한 스트림 버퍼 포인터를 반환합니다. 이를 통해 버퍼를 직접 제어할 수 있습니다.
fstream 내부 버퍼와 운영체제의 버퍼
std::fstream 내부 버퍼와 운영체제(OS)의 버퍼는 서로 독립적으로 작동합니다.
std::fstream 내부 버퍼는 C++ 라이브러리에서 제공되는 버퍼링 기능으로, 입출력 연산의 성능을 향상시키기 위한 것인 반면,
운영체제의 버퍼는 실제 파일 시스템에 접근하여 입출력 연산을 수행하는 데 사용됩니다.
std::fstream 클래스의 객체를 생성하면, 내부적으로 파일과 연결된 운영체제의 파일 디스크립터를 생성하고, 파일 시스템에서 파일을 열
어야 합니다. 이 때, 운영체제는 해당 파일에 대한 버퍼를 생성하여 파일 시스템에서 데이터를 읽어들이거나 쓰기 작업을 수행합니다
std::fstream 클래스에서는 입출력 작업을 수행할 때, 먼저 C++ 내부 버퍼에 데이터를 저장하고, 일정량 이상의 데이터가 쌓이면 운영체제의 버퍼에 데이터를 쓰거나, 운영체제의 버퍼에서 데이터를 읽어들입니다. 이 때, C++ 내부 버퍼와 운영체제의 버퍼는 서로 독립적으로 작동하기 때문에, C++ 내부 버퍼에 데이터를 쓴 후에도, 바로 운영체제의 버퍼에 데이터가 쓰이는 것이 아니라, 내부 버퍼에 데이터가 쌓이게 됩니다. 이후에, 일정량 이상의 데이터가 쌓이면, C++ 라이브러리에서 버퍼의 내용을 운영체제의 버퍼로 옮겨주고, 운영체제의 입출력 함수를 호출하여 실제 파일 시스템에서 데이터를 읽거나 쓰게 됩니다
즉, std::fstream 내부 버퍼와 운영체제의 버퍼는 서로 다른 기능을 수행하면서도, 데이터를 주고받는 과정에서 서로 협력하여 작동합니다. 이러한 구조는 입출력 연산의 성능을 최적화하기 위한 것입니다.
fstream을 통해 파일로부터 데이터를 읽어들일 때 운영체제의 역할
std::fstream 클래스를 사용하여 파일로부터 데이터를 읽어들일 때, 운영체제(OS)는 다음과 같은 역할을 수행합니다
1. 파일 시스템에서 파일을 열기: std::fstream 객체가 생성되면, 운영체제는 파일 시스템에서 해당 파일을 열어야 합니다.
파일이 존재하지 않거나, 권한이 없는 경우 등에는 파일 열기에 실패할 수 있습니다
2. 파일 읽기: 파일이 열리면, std::fstream 클래스의 멤버 함수를 이용하여 데이터를 읽어들일 수 있습니다.
데이터를 읽어들일 때, 운영체제는 해당 파일의 내용을 버퍼에 읽어들입니다.
버퍼는 운영체제의 내부 버퍼와 std::fstream 클래스의 내부 버퍼 두 가지가 있습니다.
3. 버퍼의 데이터를 프로그램으로 복사: 운영체제가 파일의 데이터를 버퍼에 읽어들이면, C++ 라이브러리는 이 데이터를 std::fstream 클래스의 내부 버퍼로 복사합니다. std::fstream 클래스는 내부적으로 버퍼를 사용하여 파일의 데이터를 저장합니다.
이렇게 복사된 데이터는 프로그램에서 처리될 수 있는 형태가 되며, 읽어들인 데이터는 프로그램에서 이용됩니다
4. 파일 닫기: 데이터를 모두 읽어들인 후에는, 파일을 닫아야 합니다. 파일을 닫으면, 해당 파일에 대한 운영체제의 자원이 반환됩니다
위와 같은 과정을 통해, 운영체제는 파일 시스템에서 파일을 읽어들이고, std::fstream 클래스는 내부적으로 데이터를 버퍼에 저장합니다. 이러한 구조는 파일 입출력 작업을 효율적으로 수행하기 위한 것입니다.
seekg
std::fstream 클래스는 파일 입출력 작업을 수행하기 위한 다양한 멤버 함수를 제공합니다. seekg 함수는 그 중 하나로, 파일 내에서 위치를 이동하는데 사용됩니다.
seekg 함수는 파일 포인터의 위치를 이동시킵니다. 파일 포인터는 파일에서 데이터를 읽어들이거나 쓰기 위해 사용되는 위치 정보를 담고 있는 변수입니다. seekg 함수를 이용하면, 파일 포인터를 파일 내의 원하는 위치로 이동시킬 수 있습니다.
seekg 함수는 다음과 같은 구조를 가집니다
std::fstream& seekg (streampos pos);
std::fstream& seekg (streamoff off, ios_base::seekdir way);
- 첫 번째 형식의 seekg 함수는, 파일 포인터를 지정된 위치로 이동시킵니다. pos 매개변수는 파일 내에서 이동시킬 위치를 나타내며, streampos 타입으로 지정됩니다.
- 두 번째 형식의 seekg 함수는, 상대적인 위치와 이동 방향을 이용하여 파일 포인터를 이동시킵니다. off 매개변수는 현재 위치에서 이동할 바이트 수를 나타내며, way 매개변수는 이동 방향을 나타내는 ios_base::seekdir 열거형 값 중 하나를 지정합니다.
예를 들어, seekg(10)은 파일 포인터를 파일의 10번째 위치로 이동시킵니다. seekg(-5, std::ios_base::cur)은 현재 파일 포인터 위치에서 5바이트 앞으로 이동시킵니다.
seekg 함수는 std::ios::cur (현재 위치), std::ios::beg (파일 시작 위치), std::ios::end (파일 끝 위치)와 같은 ios::seekdir 값으로 이동 방향을 지정할 수 있습니다. 이러한 방법을 이용하면 파일 포인터를 특정 위치가 아닌, 파일의 시작이나 끝 부분으로 이동시킬 수 있습니다.
아래는 seekg 함수의 사용 예제입니다. 예제에서는 파일에서 10바이트를 읽어들이기 위해, 파일 포인터를 파일의 10번째 위치로 이동시킨 후 데이터를 읽어들이는 방법을 보여줍니다
#include <fstream>
#include <iostream>
int main() {
std::ifstream infile("data.txt", std::ios::in | std::ios::binary);
if (!infile) {
std::cerr << "Failed to open file." << std::endl;
return 1;
}
// 파일 포인터를 10번째 위치로 이동시킴
infile.seekg(10, std::ios::beg);
char data[11] = {0};
infile.read(data, 10);
std::cout << "Data read from file: " << data << std::endl;
infile.close();
return 0;
}
위 예제에서, seekg 함수를 이용하여 파일 포인터를 10번째 위치로 이동시킨 후 read 함수를 이용하여 10바이트를 읽어들입니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.
seekg(0, std::ios::end)는 파일 포인터를 파일의 끝 부분으로 이동시키는 역할을 합니다.
두 번째 인자인 std::ios::end는 이동 방향을 나타내며, 파일의 끝에서부터 이동하라는 의미입니다. 첫 번째 인자인 0은 파일 끝으로부터 0바이트 떨어진 위치로 이동하라는 의미이므로, 실제로는 파일의 끝 부분으로 파일 포인터가 이동됩니다.
이렇게 파일 포인터를 파일 끝 부분으로 이동시키면, 파일의 크기를 계산하거나 파일에 새로운 데이터를 추가할 때 유용합니다. 예를 들어, 파일의 크기를 계산하기 위해서는 파일 포인터를 끝 부분으로 이동시켜 파일의 위치를 알아내고, 그 값을 파일의 크기로 사용할 수 있습니다. 또한, 파일 끝에 새로운 데이터를 추가하기 위해서도 파일 포인터를 끝 부분으로 이동시켜야 합니다.
src.seekg(beg, std::ios::beg)는 파일 포인터를 파일의 시작 부분으로부터 beg 바이트 떨어진 위치로 이동시키는 역할을 합니다.
첫 번째 인자인 beg는 파일 포인터를 이동할 바이트 수를 나타내며, 두 번째 인자인 std::ios::beg는 이동 방향을 나타냅니다. std::ios::beg는 파일의 시작부터 이동하라는 의미입니다.
예를 들어, src.seekg(10, std::ios::beg)은 파일의 시작 부분으로부터 10바이트 떨어진 위치로 파일 포인터를 이동시킵니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.
std::fstream src(frag, std::fstream::in);
src.seekg(0, std::ios::end);
src.seekg(beg, std::ios::beg);
위 코드는 파일을 열어서 파일 포인터를 파일의 끝으로 이동시킨 후, 다시 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 파일 포인터를 이동시키는 역할을 합니다.
첫 번째 줄에서 std::fstream::in 모드로 파일을 열어서 src 객체를 생성합니다. 이때, 파일을 읽기 모드로 열기 때문에 파일 내용을 수정할 수 없고, 읽기만 가능합니다.
두 번째 줄에서 src.seekg(0, std::ios::end)를 호출하여 파일 포인터를 파일의 끝으로 이동시킵니다. 이렇게 파일 포인터를 파일의 끝으로 이동시키면, 파일의 크기를 계산하거나 파일에 새로운 데이터를 추가할 때 유용합니다.
세 번째 줄에서 src.seekg(beg, std::ios::beg)를 호출하여 파일 포인터를 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 이동시킵니다. 이렇게 파일 포인터를 원하는 위치로 이동시켜서 파일 내의 원하는 위치에서 데이터를 읽어들일 수 있습니다.
즉, 이 코드는 파일에서 데이터를 읽어들이기 위해서 파일 포인터를 파일의 시작 부분으로부터 beg바이트 떨어진 위치로 이동시킨 후, 파일의 끝 부분에서부터 파일 포인터를 이동시켜 파일의 크기를 계산하는 방법을 사용합니다
'C++ > syntax' 카테고리의 다른 글
변환 생성자(conversion constructor)의 활용 (0) | 2023.04.10 |
---|---|
C++ 컴파일러로 C 소스코드를 함께 컴파일하고자 할때 (extern C) (0) | 2023.04.10 |
버퍼(buffer)와 표준입력 스트림(stream) 그리고 getch 와 getchar (0) | 2023.03.30 |
const char* 와 string class (0) | 2023.03.30 |
[C++] shared_ptr 사용시 주의할 점 (0) | 2023.03.27 |