파일 입출력

Ryan Ham·2024년 5월 22일
0

C++

목록 보기
12/26

C style 파일 입출력

C 스타일의 파일 입출력은 모두 fopen 함수에서 어떠한 모드로 FILE 포인터를 만들 것인지에서 시작한다. 예를 들어 fopen parameter로 'r'을 넘기면 읽기 모드, 'w'를 넘기면 쓰기 모드로 파일 포인터가 생성된다.

파일 읽기(C 스타일)

#include <cstdio>
#include <cstdlib>

int main() {
    FILE* file = fopen("example.txt", "r"); // Open the file in read mode

    if (file != nullptr) {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), file) != nullptr) {
            printf("%s", buffer); // Print each line to the console
        }
        fclose(file); // Close the file
    } else {
        perror("Unable to open file for reading");
    }

    return 0;
}

파일 쓰기(C 스타일)

#include <cstdio>

int main() {
    FILE *file = fopen("example.txt", "w"); // Open the file in write mode

    if (file != nullptr) {
        fprintf(file, "This is a line.\n"); // Write to the file
        fprintf(file, "This is another line.\n");
        fclose(file); // Close the file
    } else {
        perror("Unable to open file for writing");
    }

    return 0;
}

CPP style 파일 입출력

cpp 스타일의 파일 입출력은 procedual한 c스타일과는 반대로 file stream들이 모두 클래스 형태로 구현이 되어 있어 object oriented 한 성격을 가지고 있다.

그 전에 stream이라는 개념은 무엇일까? stream은 '흐름'이라는 뜻을 가지고 있는데 컴퓨터 과학에서는 데이터의 흐름을 stream이라고 한다. 사실 우리는 이미 cpp에서 stream을 쓰고 있었는데 바로 cin과 cout이다. cin은 istream, cout은 ostream에 속하는데, 우리가 밑에서 다뤄볼 ifstream과 ofstream도 각각 이에 속한다.

cin은 사용자의 input을 받고 cout은 console 창에 메시지를 출력한다는 점에서 stream의 방향성이 직관적이었지만, ifstream과 ofstream에서는 살짝 방향성이 헷갈렸다. 하지만 "읽는다"와 "쓴다"의 관점에서 바라보면 cin은 사용자의 입력을 "읽는" 것이고 ifstream 또한 파일을 "읽는다". 반대로 cout은 console에 값을 "쓰고", ofstream 또한 파일에 값을 "쓴다".

ifstream (CPP 스타일로 파일 읽는법)

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream inputFile("example.txt");
    if (!inputFile.is_open()) // 파일 스트림이 열려있으면 에러를 반환
    {
        //cerr는 console error를 출력
        std::cerr << "Error opening file" << std::endl;
        return 1;
    }

    std::string line;
    //getline은 string을 인자로 받는다. 
    while (std::getline(inputFile, line)) {
        std::cout << line << std::endl;
    }

    inputFile.close();
    return 0;
}

파일에서 값을 읽을 때 사용하는 file stream. getline을 통해 줄 단위로 파일을 읽고 cout으로 출력하는 형태를 많이 볼 수 있다.

ofstream (CPP 스타일로 파일 쓰는법)

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream outputFile("example.txt");
    if (!outputFile.is_open()) {
        std::cerr << "Error opening file" << std::endl;
        return 1;
    }

    std::string text = "Hello, World!";
    // 정말 간단하게 ofstream 객체에다가 바로 << 를 통해 string 값을 쓸 수 있다. 
    outputFile << text << std::endl;

    outputFile.close();
    return 0;
}

파일에 값을 작성할 때 사용하는 file stream. ofstream로 파일에 값을 작성할 때의 << 방향은 cout에서의 방향과 마찬가지로 << 이어서 헷갈릴 일은 없다!

fstream (CPP 스타일로 파일 읽기/쓰기 + 다양한 기능)

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::app); 
    // "example.txt" 파일을 읽고, 쓰고, 수정하는 기능을 다 포함한 file stream 생성
    if (!file.is_open()) {
        std::cerr << "Error opening file" << std::endl;
        return 1;
    }

    // Write to the file
    file << "Appending a line of text.\n";

    // Move to the beginning of the file to read
    // C 스타일 파일 입출력에서 인덱스 값을 찾는 fseek와 매우 비슷하다. 
    // 처음부터 읽기 위해서 index를 0으로 설정한다.  
    file.seekg(0);

    // Read from the file
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }

    file.close();
    return 0;
}

1개의 file stream으로 I/O 및 수정, binary file 만들기 등등이 동시에 다 된다. 이는 std::ios namespace에서 설정 값을 찾아서 file stream을 만들때 넣으면 된다.


파일 로그

#include <iostream>
#include <fstream>

// define에서 아직 선언되지도 않은 logMessage 함수를 쓸 수 있을지 의아할 수도 있지만,
// 이는 define이 compile 단계전에 코드 부분으로 쏙 들어간다는 사실을 기억하면 궁금증이 풀린다.
#define LOG(message) \
    logMessage(__FILE__, __LINE__, __FUNCTION__, message)

void logMessage(const char* file, int line, const char* function, const std::string& message) {
    std::ofstream logFile("log.txt", std::ios_base::app);
    logFile << file << ":" << line << " in " << function << " - " << message << std::endl;
}

int main() {
    LOG("Program started.");
    LOG("Another log message.");
    LOG("Program ended.");
    return 0;
}

위의 코드는 define과 ___FILE___ , ___LINE___ , ___FUNCTION___과 같은 메크로 등을 이용해서 파일 로그를 만들었다. 로그는 따로 경로 설정을 하지 않으면 해당 cpp project에 생성이 되게 된다. 다음은 해당 코드를 필자의 로컬 환경에서 돌렸을 때의 결과이다.

C:\Users\jerrypc\source\repos\test_velog\test_velog\main.cpp:13 in main - Program started.
C:\Users\jerrypc\source\repos\test_velog\test_velog\main.cpp:14 in main - Another log message.
C:\Users\jerrypc\source\repos\test_velog\test_velog\main.cpp:15 in main - Program ended.

___FILE___은 코드의 절대 실행 경로, ___LINE___은 코드가 실행된 행의 위치, ___FUNCTION___은 해당 코드가 어떤 함수 안에 있는지를 알려주는 유용한 매크로이다. 이 3개이 매크로를 꼭 기억해서 파일 로그를 만들때 잘 사용해보자!


개인적으로 cpp 스타일의 파일 입출력이 훨씬 더 직관적이고 코드 양도 적었다. Legacy 코드 작업을 해야 하는 경우 C 스타일의 파일 입출력을 집중적으로 다뤄야 할 상황이 오겠지만, 그 전까지는 cpp 스타일의 파일 입출력을 많이 쓸 것 같다.

profile
🏦KAIST EE | 🏦SNU AI(빅데이터 핀테크 전문가 과정) | 📙CryptoHipsters 저자

0개의 댓글