요약
프로그램이 계산한 모든 것은 프로그램이 끝나면 사라진다. 프로그램보다 오래 남는 어딘가에 적어 두지 않는 한 그렇다. 이 수업은 그 장소, 곧 텍스트 파일과, 근본적으로 새로운 것을 하나도 배우지 않고 그것을 쓸 수 있게 해 주는 생각을 소개한다. 파일은 스트림처럼, 키보드나 화면과 같은 연산으로 읽고 쓴다. std::ifstream 타입과 std::ofstream 타입, 파일을 쓰기 전에 열렸는지 확인하는 일, 그리고 파일을 어디서 찾을지 정하는 작업 폴더를 공부한다. 올바른 읽기 반복문인 while (std::getline(in, line)) 반복문을, 데이터를 하나 더 처리하는 eof() 반복문과 비교하고, 그 여분의 데이터가 생기는 정확한 이유를 본다. std::println 함수를 이용한 쓰기, 추가 모드, 블록을 벗어날 때의 닫기도 다룬다. 마지막으로 줄 형식을 다룬다. 필드를 쉼표로 나누고, std::from_chars 함수로 검증하고, 형식이 잘못된 줄을 어떻게 할지 명세에서 정하는 일이다.
학습 목표
- 읽기용이나 쓰기용으로 파일을 열고 쓰기 전에 열렸는지 확인하며, 존재하지 않는 파일과 다른 폴더에서 찾고 있는 파일을 구별한다.
while (std::getline(in, line))반복문으로 파일을 한 줄씩 읽고, 스트림의 상태를 따라가며while (!in.eof())반복문이 왜 데이터를 하나 더 처리하는지 설명한다.std::println(out, …)호출로 결과를 파일에 쓰되, 내용을 바꿀지 끝에 추가할지 고른다.- 쉼표로 구분한 값의 파일을 레코드의 벡터로 처리하고, 각 줄을 검증하며, 잘못된 줄을 그 번호와 함께 보고한다.
파일은 스트림이다
영속성
프로그램의 변수는 메모리에 살고, 프로그램의 메모리는 프로그램이 끝나면 해제된다. 그 뒤의 실행은 아무것도 기억하지 못한 채 시작한다. 어떤 데이터가 그것을 만든 프로그램보다 오래 남으려면 영구적인 매체에 남아야 하며, 범용 매체가 바로 파일이다. 이름이 있고 디스크에 저장된 바이트의 나열로서, 운영 체제가 실행과 실행 사이에 보존한다. 텍스트 파일은 바이트가 문자인 파일로, 문자는 텍스트는 ASCII가 아니다 수업에서 본 것처럼 UTF-8로 표현되며, 문자 '\n' 기호로 구분되는 줄로 짜여 있다. 가장 소박하면서 가장 오래가는 형식이다. 어떤 편집기로도 열 수 있고, 어떤 언어로도 읽을 수 있으며, 사람이 한눈에 살펴볼 수도 있다.
그런데 C++는 파일을 키보드와 다른 것으로 취급하지 않는다. 입력, 출력, 오류 수업 이래로 std::cin 객체는 입력 스트림이다. 차례로 소비되는 문자의 공급원이며, 마지막 연산이 실패했는지를 기록하는 상태를 지닌다. 읽기용으로 연 파일은 또 하나의 입력 스트림으로, std::ifstream 타입이다(input file stream에서 온 이름). 쓰기용으로 연 파일은 출력 스트림으로, std::ofstream 타입이다. 둘 다 헤더 <fstream> 파일에 선언되어 있다. 따라서 입력을 제대로 읽기 수업에서 std::cin 객체에 대해 배운 모든 것, 곧 연산자 >> 기호, std::getline 함수, 오류 상태가 파일에도 그대로 통한다.
열고 확인하기
파일은 스트림을 만들 때 열리며, 파일 이름은 중괄호 안에 쓴다. std::ifstream in{"grades.txt"}; 같은 식이다. 열기는 실패할 수 있고(파일이 없거나, 읽을 권한이 없거나, 이름을 잘못 썼거나), 실패해도 프로그램은 멈추지 않으며 아무 메시지도 나오지 않는다. 스트림이 오류 상태에 놓일 뿐이다. 오류 상태의 스트림을 조건으로 쓰면 false 값이 된다. 그러므로 확인은 if (!in) 조건문이며, 첫 읽기 전에 해야 한다. 아래 프로그램은 존재하지 않는 파일을 열려고 한다.
#include <fstream>#include <iostream>#include <print>int main(){ std::ifstream in{"grades.txt"}; if (!in) { std::println(std::cerr, "grades.txt 파일을 열 수 없습니다"); return 1; } int first{}; in >> first; std::println("첫 점수: {}", first);}grades.txt 파일을 열 수 없습니다프로그램이 코드 1(으)로 종료되었습니다.
세 가지 세부 사항에 주의할 만하다. 첫째는 std::cerr 객체(<iostream> 헤더에 있다), 곧 오류 스트림이다. 표준 출력처럼 화면에 쓰지만 별개의 통로이므로, 출력을 파일로 리디렉션할 때 오류 메시지가 결과와 섞이지 않는다. std::println 함수는 써야 할 스트림을 첫 인수로 받는다. 둘째는 return 1; 문장이다. 0이 아닌 종료 코드는 프로그램의 해부 수업에서 본 것처럼, 프로그램을 실행한 사람에게 무언가 잘못되었음을 알린다. 셋째는 확인이 없었다면 일어났을 일이다. 오류 상태의 스트림에 대한 in >> first 연산은 아무것도 하지 않고, 프로그램은 태연하게 “첫 점수: 0”이라고 썼을 것이다. 참처럼 보이는 거짓 결과는 오류 메시지보다 나쁘다. 여기서 이 수업의 규칙이 나온다. 모든 파일은 열 때 확인한다.
상대 경로와 작업 폴더
이름 "grades.txt" 문자열은 파일이 어느 폴더에 있는지 말하지 않는다. 이런 이름은 상대 경로이며, 작업 폴더를 기준으로 해석된다. 작업 폴더란 프로그램을 실행하는 위치의 폴더로, 실행 파일의 폴더나 소스 코드의 폴더와 반드시 같지는 않다. 아래 프로그램은 폴더 project 안에 data.txt 파일과 나란히 저장되어 있으며, 그 파일을 열어 첫 줄을 쓴다. 이 프로그램을 두 번 실행했는데, 처음에는 project 폴더에서, 이어서 그 위 폴더에서 실행했다.
$ cd project$ g++ -std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined main.cpp -o main$ ./main첫 줄: 파일에서 안녕하세요$ cd ..$ ./project/maindata.txt 파일을 열 수 없습니다실행 파일은 같고, 파일도 제자리에 있다. 바뀐 것은 작업 폴더뿐이며, 그와 함께 data.txt 파일을 찾는 위치가 바뀌었다. 이것이 “파일을 찾지 못한다”의 가장 흔한 원인이다. 버튼으로 프로그램을 실행하는 편집기는 작업 폴더를 스스로 고른다(Visual Studio Code에서는 보통 편집기에 열린 폴더이지만, 작업 설정에 따라 다르다). 그러므로 편집기가 무엇을 하는지 알기 전까지는 터미널에서, 데이터가 있는 폴더에서 실행하는 편이 낫다. 반대의 유혹, 곧 코드에 "/home/ana/curso/data.txt" 경로나 "C:\\Users\\Ana\\data.txt" 경로 같은 절대 경로를 쓰는 일은 한 컴퓨터에서 문제를 풀고 나머지 모든 컴퓨터에서 문제를 만든다. 다른 사람이 실행하거나 폴더를 옮기는 순간 프로그램은 동작하지 않는다. 권장되는 것은 알려진 작업 폴더를 전제로 한 상대 경로이고, 더 나은 것은 파일 이름을 사용자가 제공하는 데이터로 삼는 것이다.
콘텐츠를 불러오는 중…