HOWTO · C++
C++에서 파일을 문자열로 읽기
이 기사에서는 C++에서 파일을 문자열로 읽는 방법을 보여줍니다.
이 페이지의 내용
파일을 std::string으로 읽으려면 줄 단위로 처리할 때는 std::ifstream과 std::getline을 사용하고, 전체 스트림을 읽으려면 std::istreambuf_iterator<char>로 문자열을 구성하세요. 아래 예제는 각각 자체 임시 입력 파일을 만들므로 각 프로그램을 따로 컴파일하고 실행할 수 있습니다.
std::ifstream과 std::getline으로 C++ 파일을 문자열로 읽기
std::getline은 각 줄의 구분 문자를 제거합니다. 호출이 성공할 때마다 "\n"을 덧붙이면 줄 구분자를 복원할 수 있지만, 원본 파일의 마지막 줄이 줄바꿈 문자로 끝나지 않아도 마지막에 줄바꿈 문자가 추가됩니다. 이 차이가 중요하다면 구분 문자가 있었는지 추적하거나 아래의 전체 스트림 읽기 방법을 사용하세요.
이 완전한 예제는 임시 파일에 두 줄을 쓰고, std::ifstream으로 파일을 연 다음 각 줄을 문자열에 덧붙입니다. 예제 앞의 설명은 일반적인 성공 사례를 다룹니다.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-lines.txt";
{
std::ofstream sample(path);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::string fileContent;
std::string line;
while (std::getline(inputFile, line)) {
fileContent += line + "\n";
}
if (inputFile.bad()) {
std::cout << "Read failed\n";
inputFile.close();
std::filesystem::remove(path);
return 0;
}
std::cout << fileContent;
inputFile.close();
std::filesystem::remove(path);
}
std::getline이 더 이상 줄을 추출할 수 없으면 반복문이 끝납니다. bad()를 확인하면 정상적인 스트림 끝과 낮은 수준의 I/O 오류를 구분할 수 있습니다. 파일은 기본적으로 텍스트 모드로 열립니다. 줄 끝을 변환하는 시스템에서는 결과 문자열의 바이트가 파일에 저장된 바이트와 달라질 수 있습니다. 바이트를 그대로 보존해야 한다면 바이너리 모드를 사용하세요.
istreambuf_iterator로 C++ 파일을 문자열로 읽기
전체 스트림을 읽으려면 std::istreambuf_iterator<char> 두 개를 사용해 문자열을 구성하세요. 첫 번째 반복자는 스트림 버퍼에서 읽고, 기본 생성한 반복자는 끝을 나타냅니다. 이 방법은 데이터를 줄 단위로 나누지 않으므로 스트림에서 전달되는 줄바꿈 문자를 그대로 보존합니다.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-iterator.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path, std::ios::binary);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
const std::string fileContent{
std::istreambuf_iterator<char>(inputFile),
std::istreambuf_iterator<char>()};
if (inputFile.bad()) {
std::cout << "Read failed\n";
} else {
std::cout << fileContent << '\n';
}
inputFile.close();
std::filesystem::remove(path);
}
alpha
beta
파일 전체가 메모리에 들어가고 줄 단위 처리가 필요하지 않을 때 이 방법을 사용하세요. 바이너리 모드로 열면 텍스트 모드에서 발생할 수 있는 줄바꿈 변환을 피할 수 있습니다. std::string에는 0 바이트를 저장할 수 있지만, c_str()을 널 종료 텍스트 문자열로 취급하는 함수는 첫 번째 0 바이트에서 처리를 멈출 수 있습니다.
rdbuf로 C++ 파일을 문자열로 읽기
표준 라이브러리의 또 다른 방법은 입력 스트림의 rdbuf()를 std::ostringstream에 삽입한 뒤 str()을 호출해 내용을 std::string으로 얻는 것입니다. 주변 코드에서 문자열 스트림을 이미 사용하고 있다면 편리합니다. 읽기 오류를 보고해야 한다면 전송 후 입력 스트림의 상태를 확인하세요.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-rdbuf.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path, std::ios::binary);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::ostringstream stream;
stream << inputFile.rdbuf();
if (inputFile.bad()) {
std::cout << "Read failed\n";
} else {
const std::string fileContent = stream.str();
std::cout << fileContent << '\n';
}
inputFile.close();
std::filesystem::remove(path);
}
삽입 연산은 파일 버퍼의 문자를 문자열 스트림으로 복사하고, str()은 누적된 문자열을 반환합니다. 파일 전체를 직접 구성하려면 반복자 방식이 더 간단합니다. 어느 방식을 사용하든 파일이 열렸는지, 읽기 오류가 발생했는지 확인해야 합니다.
fread 함수로 파일을 문자열로 읽기
fread는 C 표준 라이브러리 함수이며 C++에서도 사용할 수 있습니다. 코드에서 이미 FILE* 스트림을 사용하거나 바이트 단위의 C API가 필요한 경우 유용합니다. C++ 스트림 방식보다 본질적으로 더 빠른 것은 아니며, 성능은 작업 부하와 구현에 따라 달라집니다.
이 예제는 탐색 가능한 샘플 파일의 크기를 fseek와 ftell로 구하고, 그 크기만큼 문자열을 할당한 뒤 바이트를 읽습니다. 탐색할 수 없는 스트림이나 메모리에 담기에는 너무 큰 파일은 고정 크기 청크로 읽으세요. 크기를 측정한 뒤 읽기 전에 파일 크기가 바뀔 수 있으므로 반환된 바이트 수와 ferror도 확인해야 합니다.
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-fread.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::FILE* file = std::fopen(path.string().c_str(), "rb");
if (file == nullptr) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
if (std::fseek(file, 0, SEEK_END) != 0) {
std::cout << "Could not seek sample file\n";
std::fclose(file);
std::filesystem::remove(path);
return 0;
}
const long length = std::ftell(file);
if (length < 0 || std::fseek(file, 0, SEEK_SET) != 0) {
std::cout << "Could not determine sample size\n";
std::fclose(file);
std::filesystem::remove(path);
return 0;
}
std::string content(static_cast<std::size_t>(length), '\0');
const std::size_t bytesRead = std::fread(content.data(), 1, content.size(), file);
const bool failed = std::ferror(file) != 0 || bytesRead != content.size();
std::fclose(file);
std::filesystem::remove(path);
if (failed) {
std::cout << "Read failed or file changed while reading\n";
return 0;
}
std::cout << content << '\n';
}
세 번째 인수는 요소 수이고 두 번째 인수는 요소 하나의 크기입니다. 요소 크기로 1을 사용하면 반환값은 바이트 수가 됩니다. fread는 요청한 수보다 적은 바이트를 반환할 수 있으므로 한 번의 호출로 버퍼가 전부 채워진다고 가정하지 마세요. 이 예제는 탐색 가능한 파일을 대상으로 하며 ftell이 지원하는 long 범위를 사용합니다. 더 큰 파일을 다루는 애플리케이션은 플랫폼에 맞는 대용량 파일 API를 사용하거나 청크 단위로 읽어야 합니다. 문자열은 바이트를 저장할 뿐 C 문자열 함수에 필요한 널 종료 문자를 추가하지 않습니다.
read로 파일을 문자열로 읽기
POSIX read 함수는 파일 디스크립터와 호출자가 제공하는 버퍼를 사용합니다. 표준 C++의 일부가 아니므로 적절한 POSIX 헤더를 포함하고, 이 API를 제공하는 플랫폼에서만 사용하세요. 호출이 성공해도 요청한 바이트보다 적게 반환할 수 있으므로 파일 끝에 도달할 때까지 반복해서 읽고, 중단과 오류도 처리해야 합니다.
다음 POSIX 전용 프로그램은 파일 크기를 미리 측정하지 않고 청크 단위로 읽습니다. 이 시스템 호출은 모든 C++ 플랫폼에서 사용할 수 있는 것이 아니므로 이 코드는 이식 가능한 C++ 예제가 아닌 POSIX 예제로 취급하세요.
#include <cerrno>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <fcntl.h>
#include <unistd.h>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-read.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
const int fd = ::open(path.c_str(), O_RDONLY);
if (fd < 0) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::string content;
char buffer[4096];
bool failed = false;
for (;;) {
const ssize_t count = ::read(fd, buffer, sizeof buffer);
if (count > 0) {
content.append(buffer, static_cast<std::size_t>(count));
} else if (count == 0) {
break;
} else if (errno != EINTR) {
failed = true;
break;
}
}
if (::close(fd) != 0) {
failed = true;
}
std::filesystem::remove(path);
if (failed) {
std::cout << "Read or close failed\n";
return 0;
}
std::cout << content << '\n';
}
일반 파일에서 read는 파일 끝에 도달하면 0을 반환합니다. 다른 종류의 디스크립터에서는 동작이 장치나 스트림에 따라 다릅니다. 양수로 반환된 값은 덧붙일 바이트 수이고, 음수 반환은 오류를 나타냅니다. EINTR이면 호출을 다시 시도할 수 있습니다. 디스크립터는 항상 닫고, 한 번의 호출로 요청한 버퍼가 모두 채워진다고 가정하지 말고 부분 읽기를 처리하세요. 예제는 std::string::append를 사용해 0 바이트를 포함한 정확한 바이트 수를 보존합니다.
결론
이식 가능한 C++에서 전체 스트림을 문자열로 만들려면 std::istreambuf_iterator나 rdbuf()를 선택하고, 줄 단위 처리가 유용하면 std::getline을 사용하세요. 줄을 추출하면 구분 문자가 제거되며, 이를 다시 덧붙이면 마지막 줄에 줄바꿈 문자가 없던 경우 결과가 달라질 수 있습니다. 주변 코드에서 C FILE* 인터페이스를 사용한다면 fread를 고려하고, 플랫폼별 파일 디스크립터 API가 적합한 경우에만 POSIX read를 사용하세요. 큰 파일은 전체를 메모리에 보관하는 대신 청크 단위로 처리하는 방법을 고려하고, 파일 열기와 읽기 결과를 모두 확인하세요.