본문 바로가기
c, c++/c++로 시작하는 객체지향 프로그래밍

13.1~ 13.5)파일 입력과 출력

by 피스타0204 2024. 5. 24.

13.1) 들어가기

변수나 배열, 객체에 저장된 데이터는 휘발성 데이터로 프로그램이 종료되면 모두 사라집니다. 프로그램 안에서 생성된 데이터를 영구히 저장하리 위해서는 하드디스크같이 비휘발성 데이터를 저장하는 저장매체에 파일로 저장해야 합니다.

c++에서는 ifstream, ofstream, fstream 클래스에 있는 함수를 사용하여 파일로부터 데이터를 읽거나 쓸 수 있습니다.

모두 fstream을 include하여 사용할 수 있고 ifstream은 읽기, ofstream은 쓰기, fstream은 읽고 쓰기를 할 수 있는 클래스입니다.

 

c++에서는 데이터의 흐름을 스트림(stream)이라고 이야기하는 데 예를 들어 데이터가 프로그램쪽으로 흐르면 입력 스트림(input stream), 데이터가 프로그램으로부터 나오는 출력 스트림(output stream)이라고 합니다. 예를 들어 앞서 배웠던 cin(console input), cout(console output)은 각각 iostream을 이용해 console에 문자를 입력하는 입력스트림과 출력하는 출력스트림 객체였습니다. 

 


13.2 ) 텍스트 입출력

13.2.0) 파일경로

모든 파일은 파일 시스템 내 디렉터리 안에 존재하고 파일경로에는 절대 경로와 상대경로가 존재합니다. 텍스트 파일의 데이터는 텍스트 편집기로 익을 수 있습니다. 

 

13.2.1) 파일로 데이터 쓰기

ofstream클래스는 텍스트 파일로 primitive 자료형의 값, 문자열, 객체를 작성하는데 사용됩니다.

 

ofstream output;으로 ofstream클래스로부터 output객체가 생성되고 output객체로 scores.txt 파일을 엽니다. 만약 파일이 존재하지 않으면 생성되고, 파일이 이미 존재한다면 경고없이 기존 파일 내용이 지워집니다.

공백을 쓰고 싶다면 반드시 명시해 주어야 합니다.

#include <iostream>
#include <fstream>
using namespace std;

int main() {
	ofstream output;

	//파일 생성
	output.open("scores.txt");

	//두줄 쓰기
	output << "John" << " " << "T" << " " << "Smith"
		<< " " << 90 <<endl;
	output << "Eric" << " " << "K" << " " << "Jones"
		<< " " << 85 << endl;
	
	output.close();
	cout << "Done" << endl;

	

	return 0;
}

 

open시에 경로를 정해줄 수도 있는데 windows에서는 디렉터리 구분자가 역슬래시 2개라는 점을 주의해야 합니다. 이는 이스케이프 구분자로 output.open("c:\\example\\scores.txt"); 로 사용합니다.

 

13.2.2 파일로부터 데이터 읽기

 

ifstream클래스는 텍스트 파일로부터 데이터를 읽기 위해 사용됩니다. 

ifstream클래스로 scores.txt 파일과 관련된 객체 input을 생성할 수 있습니다. 스트림 연산자(>>)를 사용하여 input객체로부터 데이터를 읽을 수 있습니다.input >> firstName >> mi >> lastName >> score;처럼 사용합니다.

이 방식으로 데이터를 올바르게 읽기 위해서는 데이터가 어떻게 저장되어있는지를 정확히 정의해야 합니다. 예를 들어 소수(ex.20.3)가 파일에 포함되어 있다면 double값으로 수를 받아야 합니다. 

#include <iostream>
#include <fstream>
using namespace std;

int main() {
	
	ifstream input("scores.txt");

	string firstName;
	char mi;
	string lastName;
	int score;
	input >> firstName >> mi >> lastName >> score;
	cout << firstName << " " << mi << " " << lastName << " " << score << endl;
	input.close();
	cout << "Done" << endl;
	

	return 0;
}

 

13.2.3) 파일 존재 여부 퀘스트

파일을 읽을 때 파일이 존재하지 않는다면 프로그램 실행후 잘못된 결과가 나올 수 있습니다. 파일이 존재하고 있는지 여부를 프로그램에서 검사하고, 파일을 열어야 합니다. open 함수를 호출한 바로 다음에 input 객체에 fail함수 호출하면 검사할 수 있습니다.

#include <iostream>
#include <fstream>
using namespace std;

int main() {

	ifstream input("scs.txt");
	input.open("scs.txt");
	if (input.fail()) {
		cout << "File does not exist" << endl;
		cout << "Exit program" << endl;
	}



	return 0;
}

 

13.2.4) 파일 끝 테스트

파일의 끝을 검출하기 위해 입력 객체에 대해  eof()함수를 호출할 수 있습니다. 하지만 마지막 숫자 후에 여분의 공백(blank)문자가 있다면 이 프로그램은 동작하지 않을 것입니다. 

//오류 코드
#include <iostream>
#include <fstream>
using namespace std;

int main() {

	ifstream input("score.txt");

	double sum = 0;
	double number;
	while (!input.eof()) { 
		input >> number;
		cout << number << " ";
		sum += number;
	}

	cout << "result: " << sum << endl;

	return 0;
}

위 코드는 무한 반복됩니다.

//해결법1
#include <iostream>
#include <fstream>
using namespace std;

int main() {

	ifstream input("scores.txt");
	if (input.fail()) {
		cout << "File does not exist" << endl;
		cout << "Exit program" << endl;
	}
	double sum = 0;
	double number;
	while (!input.eof()) {
		input >> number;
		if (input.eof())break;
		cout << number << " ";
		sum += number;
	}

	cout << "\nresult: " << sum << endl;

	return 0;
}

해결법 첫번째로 숫자를 읽은 후에 바로 eof()함수를 확인할 수 있습니다. 만일 eof()함수가 true(파일의 끝)이면 break하는 것입니다.

//해결법2-연산자 함수 이용
#include <iostream>
#include <fstream>
using namespace std;

int main() {

	ifstream input("scores.txt");
	if (input.fail()) {
		cout << "File does not exist" << endl;
		cout << "Exit program" << endl;
	}

	double sum = 0;
	double number;
	while (input >> number) {
		cout << number << " ";
		sum += number;
	}
	input.close();

	cout << "\nresult: " << sum << endl;

	return 0;
}

input>>number를 통해 연산자 함수를 호출하는 해결법도 있습니다. 이 함수는 숫자가 읽혀지면 객체를 반환하고 아니면 NULL을 반환합니다.

 

 

13.2.5) 사용자의 파일 이름 입력

아래는 사용자가 파일 이름을 입력하고 파일의 존재 여부를 확인하는 예입니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
	string filename;
	cout << "Enter a file name: ";
	cin >> filename;
	ifstream input(filename.c_str());
	if (input.fail()) {
		cout << filename << "does not exist" << endl;
	}
	else {
		cout <<" "<< filename << " exists" << endl;
	}
	return 0;
}

 

13.3) 출력 형식 지정

iomanip같은 스트림 조정자는 콘솔 출력뿐만 아니라 파일 출력형식을 지정하는데에도 사용될 수 있습니다.

#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

int main() {
	ofstream output;

	output.open("formattedscores.txt");

	output << setw(6) << "John" << setw(2) << "T" << setw(6) << "Smith" 
		<< " " << setw(4) << 90 << endl;
	output << setw(6) << "Eric" << setw(2) << "K" << setw(6) << "Johnes"
		<< " " << setw(4) << 85 << endl;

	output.close();
	cout << "Done" << endl;

	return 0;
}

//출력
  John T Smith   90
  Eric KJohnes   85

 

13.4) getline,get, put 함수

fstream의 ofstream은 공백 문자열을 저장할 수 있습니다. 하지만 fstream의 ifstream 객체는 공백을 기준으로 문자열, 객체 등을 구별하여 저장하기 때문에 공백이 포함된 문자열을 읽을 수 없습니다. 그래서 파일에서 공백이 포함된 문자열을 읽을 때에는 getline함수를 이용합니다.

getline(ifstream객체이름, 문자열을 저장할 변수이름, 구분자); 형식으로 사용합니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
	ofstream output("state.txt");
	output << "New"<<" " << "York#New"<<" " << "Mexico#Texas#Indiana" << endl;
	output.close();

	ifstream input("state.txt");
	if (input.fail()) {
		cout << "File does not exist" << endl;
		cout << "Exit program" << endl;
	}
	string state;
	while (!input.eof()) {
		getline(input, state, '#'); //#나 \n(줄바꿈)나 eof가 나오면 input을 저장 state에 저장
		cout << state << endl;  // 문자열을 저장한 변수 출력 
	}
	input.close();
	cout << "Done" << endl;

	return 0;
}
//파일에 저장된 내용
New York#New Mexico#Texas#Indiana

 

입력 객체로 문자열이 아닌 문자 하나씩을 읽어오고 싶다면 get함수, 출력 객체로 문자를 쓰기 위해서는 put함수를 사용합니다.

input은 파일에서 프로그램으로 입력해 들어오는 것이므로 char ch;같은 변수를 만들어 입력해야 합니다.

 

get은 두 가지 방법으로 쓸 수 있는데 첫번째로 ch =input.get()해서 매개변수를 받지 않고 char형 변수(ch)에 저장해서 사용하는 것이고 두번째로 input.get(ch);로 해서 매개변수로 char형 변수 ch를 저장하여 ch에 문자를 하나씩 받아옵니다. 두번째 방법으로는 return값으로 ifstream*을 받아옵니다.(input의 참조값)

 

char get()

ifstream* get(char& ch)

 

void put(char ch)을 하면 ch라는 문자를 출력 객체로 써넣습니다.

 

다음은 다른 파일의 내용을 복사하여 새로운 파일을 만드는 코드입니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
	cout << "Enter a source file name: ";
	string inputFileName;
	cin >> inputFileName;

	cout << "Enter a target file name: ";
	string outputFileName;
	cin >> outputFileName;

	ifstream input(inputFileName.c_str());
	ofstream output(outputFileName.c_str());

	if (input.fail()) {
		cout << inputFileName << " does not exists" << endl;
		cout << "Exit program" << endl;
		return 0;
	}
	char ch = input.get(); //state에서 문자 하나 읽어와서 ch에 입력
	while (!input.eof()) {
		output.put(ch); // newState.txt에 ch 입력
		ch = input.get();
	}
	input.close();
	output.close();
	cout << "\ncopy done" << endl;

	return 0;
}

 

두번째 방법으로 이 코드를 작성하면 copy된 내요에 1byte의 가비지 값이 들어갑니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
	cout << "Enter a source file name: ";
	string inputFileName;
	cin >> inputFileName;

	cout << "Enter a target file name: ";
	string outputFileName;
	cin >> outputFileName;

	ifstream input(inputFileName.c_str());
	ofstream output(outputFileName.c_str());

	if (input.fail()) {
		cout << inputFileName << " does not exists" << endl;
		cout << "Exit program" << endl;
		return 0;
	}
	
	while (!input.eof()) {
		output.put(input.get());
	}

	input.close();
	output.close();
	cout << "\ncopy done" << endl;

	return 0;
}

 

while (!input.eof()) {output.put(input.get());}로 바꿔 작성했을 때 문제는 input.get()으로 마지막 값을 읽었을 때는 eof가 false이기 때문에 한번더 input.get으로 값을 읽어옵니다.

 

13.5) fstream과 파일 열기 모드

include <fstream>을 하면 ifstream과 ofstream을 사용할 수 있지만 그 외에도 fstream을 사용할 수 있습니다. fstream은 입력과 출력을 동시에 사용할 수 있게 해주는 클래스로 ios::in과 같은 파일 열기 모드(file open mode)를 지정해주어 사용해야 합니다.

ios::in    파일 입력

ios::out   파일 출력

ios::app   모든 출력을 파일의 끝에 추가, 포인터가 파일 끝 외의 장소로 이동할 수 없어 파일 끝에만 쓸 수 있음.

ios::ate    출력을 위한 파일 열기, 만약 파일이 이미 존재하면 파일의 끝으로 이동, 데이터는 파일의 어느 곳이나 쓸 수 있음

ios::trunc   파일이 이미 존재하면 파일의 내용을 버림(ios::out의 기본동작임)

ios::binary  이진 입력과 출력을 위한 파일 열기

 

ifstream과 ofstream에서도 파일 열기 모드를 정해줄 수 있지만, fstream에서 사용하는 것을 권장합니다.

 

위의 파일 열기 모드는 비트 or 연산자(|)와 같이 사용하면서 여러 기능을 같이 사용하는 객체를 만들 수 있습니다. 

ios::in | ios::out 을 사용해서 in, out을 동시에 사용할 수도 있고, ios::out | ios::app를 사용하여 새로운 파일을 만들지 않고 추가해서 작성할 수 있습니다. 단, ios::in 이나 ios::out 중 하나는 반드시 설정해주어야 합니다.

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
	fstream inout;
	inout.open("city.txt", ios::out); //같은 이름의 파일이 있으면 내용을 삭제하고 새로 작성
	inout << "Dallas" << " " << "Houston" << " " << "Atlanta" << " ";
	inout.close();
	
	inout.open("city.txt", ios::out | ios::app); // 추가로 작성
	inout << "Savannah" << " " << "Austin" << " " << "Chicago";
	inout.close();

	string city;
	inout.open("city.txt", ios::in); 
	while (!inout.eof()) {
		inout >> city;
		cout << city << " ";
	}
	cout << endl;
	inout.close();
	return 0;
}

 

13.6) 스트림 상태 검사 

 

 

 

 

 

checkpoint)

13.1 출력을 위한 선언과 파일 열기는 어떻게 하는가? 입력을 위한 선언과 파일 열기는 어 떻게 하는가?

//파일 쓰기 output//
#include <fstream>

ofstream output;
//파일 생성(같은 이름 파일 다시 쓰기)
output.open("scores.txt");

//파일 작성 int, float, string, object등 맞춰서 작성할 수 있다.
output << "John" << " " << 90 <<endl;
output << "Eric" << " " << 85 << endl;
output.close();

//파일 읽기 input//
#include <fstream>

//파일 생성(이미 존재하는 파일 열기)
ifstream input("scores.txt");

string firstName;
char mi;
string lastName;
int score;
input >> firstName >> mi >> lastName >> score;
input.close();

 

파일 쓰기output은 원본 파일을 덮어써버리지만 파일 읽기 input은 원본 파일에 내용을 추가한다.

cin >> 변수   console ->변수

input >> 변수; input(파일 내용받아옴)-> 변수

cout << 변수;  console<- 변수

output << 내용; output (파일로 만듬) <- 내용


13.2 파일을 처리한 후에, 파일을 항상 닫아야 하는 이유는 무엇인가?

output stream은 닫아주지 않으면 데이터가 파일에 적절하지 못하게 저장될 수 있다.

input stream은 꼭 닫아야 하는 것은 아니지만 파일에 의해 점유된 리소스를 풀어주는 것이 좋다.


13.3 파일의 존재 여부를 어떻게 검사하는가?

ifstream input("scs.txt");
input.open("scs.txt");
if (input.fail()) {
cout << "File does not exist" << endl;
cout << "Exit program" << endl;
}

파일이 존재하지 않으면 입력에 문제가 생길 수 있으므로 파일의 존재여부를 검사해야 한다. 따라서 input 객체에 input.fail()처럼 파일이 없으면 true값을 반환하는 fail함수를 사용하여 만약 파일이 없으면 알려주도록 검사할 수 있다.

 
13.4 파일의 끝에 도달했는지를 어떻게 검사하는가?

입력객체와 eof함수를 사용하여 파일의 끝에 도달했는지를 확인할 수 있습니다. 파일의 끝에 도달하면 암시적으로 eof를 만난다고 생각합니다. eof를 만나면 input.eof()는  true를 반환합니다.

하지만 맨 끝에 공백이 입력되어 있으면 eof는 false를 반환하고 그러면서 변수에 값을 저장합니다. 하지만 다음 데이터가 공백(없으므로) 이전의 데이터를 변수에 또 저장합니다.


13.5 입출력 스트림 객체를 생성하기 위해서 open 함수에 문자열 또는 C-문자열로 파일 이름을 전달해야 하는가?

파일 이름은 프로그램 안에서 정해진 채로 기록된 문자열 리터럴이기 때문에 문자열 혹은 c-문자열로 전달해야 합니다. c_str() 메소드를 이용해 문자열로 바꾸어 저장할 수 있습니다.

 

13.6)  텍스트 출력의 형식 지정을 위해 스트림 조정자를 사용할 수 있는가?

 

13.7) getline과 get함수의 차이점은 무엇인가?

getline은 공백을 포함한 '문자열'을 출력할 수 있습니다.

get함수는 공백을 포함하여 '문자 하나'를 출력합니다.

 

13.8) 문자를 쓰는데 어떤 함수를 사용할 수 있느가?

ouput과 스트림 연산자를 사용할 수도 있고, put함수를 사용할 수 있다.

 

13.9) 파일에 데이터를 추가하기 위해서는 어떻게 파일을 열어야 하는가?

fstream inout;

inout.open("파일이름",ios::out | ios::app);

inout << "추가할 내용" ;

 

13.10) ios::trunc의 파일 열기 모드는 무엇인가?

출력모드 ios::out

 

 

'c, c++ > c++로 시작하는 객체지향 프로그래밍' 카테고리의 다른 글

14.1~ 연산자 오버로딩  (1) 2024.06.18
13.7~)이진 입출력  (0) 2024.05.24
15.5~15.9) 상속과 "다형성"  (0) 2024.05.09
15.1~15.4, 15.8) 상속  (0) 2024.05.02
12.1~12.5  (2) 2024.04.25