C++/File IO

C++ File IO

Soul-Learner 2016. 12. 29. 13:00

C++ 프로그래밍, 파일 입출력 ( File Input & Output )


C++ 에서 파일과 관련하여 3개의 스트림 클래스를 지원하고 있다. 이들은 모두 istream, ostream 으로부터 파생된 클래스이며 키보드와 모니터에 입력 기능을 가진 cin, cout 은 각각 istream, ostream 클래스의 객체이다. 아래의 파일을 다루는 입출력 스트림들은 istream, ostream 클래스로부터 파생되었기 때문에 사용하는 방법이 cin, cout 객체와 유사한 점이 많아서 그리 어렵지 않게 익힐 수 있다

  • ofstream  : 파일에 쓰는 기능의 스트림 클래스
  • ifstream  : 파일로부터 읽어오는 기능의 스트림 클래스
  • fstream  : 파일에 대한 읽고 쓰는 기능을 모두 갖춘 스트림 클래스
  • wifstream  : 유니코드 문자로 쓰여진 텍스트 파일 읽는 스트림
  • wofstream  : 유니코드 문자를 텍스트 파일에 쓰는 스트림


파일출력스트림(ofstream)을 이용한 기본적인 파일쓰기

#include <iostream>
#include <locale>
#include <fstream>

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	ofstream fout;						// 파일출력 스트림 객체 생성
	fout.open("C:\\test\\sample.txt");	// 파일 생성

	fout << "File writing" << endl;		// 파일에 쓰기
	fout << "This is file data" << endl;
	fout << "------------------" << endl;

	fout.close();						// 리소스 해제

	wcout << L"파일에 쓰기 완료" << endl;
	return 0;
}


파일 경로에 한글이 포함된 경우와 파일 내용에 한글을 쓰는 예

#include <iostream>
#include <locale>
#include <fstream> // ofstream 클래스
#include <cstdlib> // wcstombs()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wofstream fout;					// 와이드 문자 파일출력 스트림 객체 생성
	wstring wfname(L"C:\\test\\샘플.txt");	// 파일 경로에 한글이 포함됨

	// 소스파일의 내용은 UTF-8 유니코드(Wide Character Sets)로 인코딩
	// Windows 파일 시스템은 MS949(Multibyte Character Sets)가 사용됨
	// 그러므로 소스파일에서 파일시스템에 접근하기 위해서는 Unicode ->MBCS 변환이 필요함
	// wcstombs(char* mbcs, wchar_t* wcs, size) 함수가 필요함

	char* pfname = new char[wfname.size()*2];	// utf-8은 한글자당 2바이트
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);
	fout.open(pfname);						// 파일 생성

	fout << "파일에 쓰기 테스트" << endl;			// 파일에 쓰기
	fout << "이 문장은 파일의 내용입니다" << endl;
	fout << "------------------" << endl;

	fout.close();							// 리소스 해제

	wcout << L"파일에 쓰기 완료" << endl;

	return 0;
}


파일 열기 모드

  • ios::in              input
  • ios::out            output
  • ios::ate            at the end. 파일 내용의 위치를 가리키는 포인터를 파일의 맨 끝으로 이동한다. 디폴트로 파일의 시작부분
  • ios::binary        binary 
  • ios::app           파일 내용 끝에 이어쓰기
  • ios::trunc         파일의 기존 내용을 버리고 새로 시작

open() 함수의 두번째 아규먼트로 파일 열기 모드를 설정한 예

ofstream은 열기 디폴트 모드가 ios::out 으로, ifstream은 ios::in 모드로,  fstream은 ios::in | ios::out 으로 설정되어 있음. 여러가지 열기 모드가 동시에 필요하다면 OR 연산자( | )로 연결하여 여러개의 모드를 사용할 수 있다

#include <iostream>
#include <locale>
#include <fstream> // ofstream 클래스
#include <cstdlib> // wcstombs()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wofstream fout;     // 와이드 문자열을 파일에 출력하는 스트림 생성
	wstring wfname(L"C:\\test\\샘플.txt");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	fout.open(pfname, ios::out); //ofstream 은 디폴트로 ios::out 모드임

	fout << "파일에 쓰기 테스트" << endl;
	fout << "이 문장은 파일의 내용입니다" << endl;
	fout << "------------------" << endl;

	fout.close();

	wcout << L"파일에 쓰기 완료" << endl;
	return 0;
}

파일열기에 대한 성공여부를 검사하는 예

	// 파일열기 성공여부를 검사하는 예
	if(!fout.is_open()) {
		wcerr << L"파일열기 실패" << endl;
		exit(1);
	}


텍스트 파일을 쓰기 / 읽어서 화면에 출력하기 

#include <iostream>
#include <locale>
#include <fstream> // ofstream 클래스
#include <cstdlib> // wcstombs()
#include <string>  // getline()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wofstream fout;		// 와이드 문자를 파일에 출력하는 스트림 생성
	wstring wfname(L"C:\\test\\샘플.txt");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	fout.open(pfname);

	if(!fout.is_open()) {
		wcerr << L"파일열기 실패" << endl;
		exit(1);
	}
	fout << L"파일에 쓰기 테스트" << "\n";
	fout << L"이 문장은 파일의 내용입니다" << "\n";
	fout << "------------------" << "\n";

	fout.close();
	wcout << L"파일에 와이드 문자열 쓰기 성공" << endl;

	wifstream fin;
	fin.open(pfname); // 텍스트 모드로 파일에서 읽기위해 와이드입력 스트림 생성

	if(!fin.good()) {
		wcerr << L"파일 열기 실패" << endl;
		exit(1);
	}

	wstring line;
	while(!fin.eof()) {
		getline(fin, line);
		wcout << line << endl;
	}
	fin.close();

	wcout << L"파일에서 와이드 문자열 읽기 완료" << endl;
	return 0;
}


바이너리/텍스트 모드로 파일에 출력하는 예제

	fout.open(pfname, ios::out|ios::binary);// 바이너리 모드로 파일에 출력할 때
	//fout.open(pfname); 			// 텍스트 모드로 파일에 출력할 때

	if(!fout.is_open()) {
		wcerr << L"파일열기 실패" << endl;
		exit(1);
	}
	fout << "파일에 쓰기 테스트" << "\n";
	fout << "이 문장은 파일의 내용입니다" << "\n";
	fout << "------------------" << "\n";

	fout.close();


위의 코드를 이용하여 바이너리 모드로 파일에 텍스트를 저장한 후 메모장으로 열어본 경우

바이너리 모드를 사용하여 개행문자로 "\n" 을 입력하면 아래 화면처럼 개행문자가 제대로 기능하지 못하는 것을 확인할 수 있다. 윈도우에서는 개행문자가 "\r\n" 인데 위의 코드에서는 "\n" 만 사용했기 때문에 텍스트 파일에 "\n" 만 저장되었고 메모장으로 열어보면 윈도우의 개행문자인 "\n\n" 이 없기 때문에 개행이 되지않고 깨진 문자로 보여지고 한 행에 모두 출력된다


위의 코드를 이용하여 텍스트 모드로 파일에 텍스트를 저장한 후 메모장으로 열어본 경우
텍스트 모드로 텍스트 파일에 저장할 때 개행문자로 위와 동일한 "\n" 을 사용했지만 메모장으로 열었을 때 개행문자가 제대로 기능하고 있는 것을 확인할 수 있다. 결국 텍스트 모드로 파일에 저장할 때 "\n"을 사용하여 개행문자를 입력하면 내부적으로 "\r\n" 으로 변환되어 저장된다는 것을 알 수 있다


바이너리 모드로 텍스트를 파일에 저장할 때 개행문자가 제대로 입력되도록 하려면 윈도우에서는 다음과 같이 캐리지 리턴, 라인 피드(\r\n)를 모두 저장해야만 메모장에서도 개행문자가 제대로 기능한다. 다음과 같은 코드를 이용하면 바이너리 모드를 사용해도 제대로 개행문자를 입력할 수 있다
fout << "파일에 쓰기 테스트" << "\r\n";
fout << "이 문장은 파일의 내용입니다" << "\r\n";
fout << "------------------" << "\r\n";

위와 같은 테스트를 통해서 알 수 있는 것은 텍스트 모드로 텍스트를 파일에 저장하면 "\n" 만 사용하더라도 내부에서 "\r\n" 으로 변환하여 저장해 준다는 것이다. 그러나 바이너리 모드로 "\n" 을 파일에 저장하면 그대로 "\n" 만 저장된다는 것이다


텍스트 모드 / 바이너리 모드로 텍스트의 차이점
  • 텍스트 모드로 파일에 출력할 때는 "\n" -> "\r\n" 으로 변환해준다. 즉, 운영체제에 따른 개행문자를 내부적으로 모든 행마다 추가해준다.
  • 텍스트 모드로 파일에서 읽어 올 때는 한 행을 읽어 올 때 각 행의 끝에 있는 개행문자("\n", 혹은 "\r\n")를 제거한 문자열을 가져온다
  • 바이너리 모드로 파일에 출력할 때는 주어진 데이터를 변환하지 않고 그대로 파일에 저장한다
  • 바이너리 모드로 파일에서 읽어 올 때는 한 행을 읽어서 변환을 거치지 않고 그대로 가져온다



스트림의 상태를 검사하는 함수
bad()  : 읽거나 쓰기 실패시 true
fail()  : bad()와 같으나 숫자를 읽을 때 문자가 포함되어 있는 경우에 true
eof()  : 파일의 끝에 도달한 경우 true
good()  : 위의 3개의 함수가 모두 true 가 아닌 경우 true
위의 상태를 다시 리셋하려면 clear() 함수를 호출하면 된다


스트림의 내부 포인터 : 파일을 읽거나 쓰는 위치를 나타낸다
모든 스트림에는 최소한 1개의 내부 포인터가 있어서 현재 스트림 내에서 작업이 진행되는 위치가 어디인지를 나타낸다.
get pointer : 입력 스트림 내의 현재 작업 위치를 나타내는 포인터. 즉, 파일로부터 읽어오는 작업(get)이 진행되는 위치
put pointer : 출력 스트림 내의 현재 작업 위치를 나타내는 포인터. 즉, 파일에 쓰는 작업(put)이 진행되는 위치


  • tellg() : get 포인터의 위치를 pos_type 형으로 리턴한다. pos_type는 정수인데 일반 정수의 범위를 넘을 수 있는 정수이다
  • tellp() : put 포인터의 위치를 리턴한다
  • seekg() : get 포인터의 위치를 변경한다
  • seekp() : put 포인터의 위치를 변경한다

seekg( off_type, direction

off_type 은 일반 정수의 범위를 넘을 수 있는 정수형이다

direction : ios::beg, ios::cur, ios::end


파일 내의 포인터 이동 예

fin.seekg ( 0, ios::end ) 

읽기 작업을 하는 파일 내의 맨 끝으로부터 0 만큼 떨어진 곳에 포인터를 위치시킨다


텍스트 파일에서 특정 내용 검색하기 예

  • wstring :: find ( L"김인철", 0 )   와이드 문자열 안의 처음(0)부터 "김인철" 을 찾아서 그 인덱스를 리턴한다
  • wstring :: substr (  3, 3 )   와이드 문자열 안에서 인덱스 3의 위치부터 3개의 와이드 문자를 읽어온다

다음과 같이 구성된 사용자 정보가 텍스트 파일에 저장되어 있다고 할 대 이름만 읽어와서 화면에 출력하는 기능을 구현해보자

11|김인철|kim@daum.net

12|박기정|park@daum.net

13|최신실|choi@daum.net

14|박지성|jisung@daum.net

15|홍길동|hong@daum.net


#include <iostream>
#include <locale>
#include <fstream> // wofstream 클래스
#include <cstdlib> // wcstombs()
#include <string>  // getline()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wstring wfname(L"C:\\test\\users.txt");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	// 파일에서 와이드 문자열 읽어오기
	wifstream fin;
	fin.open(pfname); // 텍스트 모드로 파일에서 읽기위해 와이드입력 스트림 생성

	if(!fin.good()) {
		wcerr << L"파일 열기 실패" << endl;
		exit(1);
	}

	wstring line;
	while(!fin.eof()) {
		// 텍스트 파일에서 한행을 읽어온다
		getline(fin, line);
		// 이름 앞에 있는 첫번째 구분자의 위치(인덱스)를 확인한다
		std::string::size_type idx1 = line.find(L"|");
		if(idx1 != -1)  {// 발견되지 않으면 -1
			// 이름 뒤에 있는 구분자의 위치(인덱스)를 확인한다
			std::string::size_type idx2 = line.find(L"|",idx1+1);
			// 이름 앞 뒤에 있는 구분자의 인덱스를 이용하여 이름만 읽어온다
			wstring wname = line.substr(idx1+1, idx2-idx1-1);
			// 파일에서 읽어온 이름을 출력한다
			wcout << wname << endl;
		}
	}
	fin.close();

	wcout << L"파일에서 와이드 문자열 읽기 완료" << endl;
	return 0;
}


텍스트 파일 복사 (원본파일에서 한행씩 읽어서 사본파일에 저장한다)

#include <iostream>
#include <locale>
#include <fstream> // wofstream 클래스
#include <cstdlib> // wcstombs()
#include <string>  // getline()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wstring wfname(L"C:\\test\\users.txt");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	// 파일에서 와이드 문자열 읽어오기
	wifstream fin;
	fin.open(pfname);

	if(!fin.good()) {
		wcerr << L"파일 열기 실패" << endl;
		exit(1);
	}

	// 복사 목적 파일 및 스트림 생성
	wstring wfcopy(L"C:\\test\\복사본.txt");
	char* pfcopy = new char[wfcopy.size()*2];
	wcstombs(pfcopy, wfcopy.c_str(), wfcopy.size()*2);
wofstream fout(pfcopy); if(!fout.good()) { wcerr << "목적파일 스트림 오류" << endl; exit(1); } wstring line; while(!fin.eof()) { // 원본 텍스트 파일에서 한행을 읽어온다 getline(fin, line); // 사본 파일에 복사 저장한다 fout << line << endl; } fin.close(); fout.close(); wcout << L"텍스트 파일 복사하기 완료" << endl; return 0; }


텍스트 파일의 전체 내용을 한번에 목적파일에 복사하는 예 

원본파일에서 읽어온 그대로 변환 없이 목적 파일에 복사해야 하므로 파일의 모드를 바이너리(ios::binary)로 설정해야 한다

#include <iostream>
#include <locale>
#include <fstream> // wofstream 클래스
#include <cstdlib> // wcstombs()
#include <string>  // getline()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wstring wfname(L"C:\\test\\users.txt");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	// 원본파일 스트림
	wifstream fin;
	fin.open(pfname, ios::binary|ios::ate); // 포인터를 끝에 위치시킨다

	if(!fin.good()) {
		wcerr << L"파일 열기 실패" << endl;
		exit(1);
	}

	// 파일의 내용 끝에서 원본파일의 전체 크기를 확인한다
	int len = fin.tellg();
	wchar_t* buf = new wchar_t[len];

	// 포인터를 다시 파일 내용의 처음으로 위치시킨다
	fin.seekg(0, ios::beg);

	// 파일 전체 내용을 버퍼에 복사한다
	fin.read(buf,len);

	// 복사 목적 파일 및 스트림 생성
	wstring wfcopy(L"C:\\test\\복사본.txt");
	char* pfcopy = new char[wfcopy.size()*2];
	wcstombs(pfcopy, wfcopy.c_str(), wfcopy.size()*2);
	wofstream fout(pfcopy,ios::binary);

	if(!fout.good()) {
		wcerr << "목적파일 스트림 오류" << endl;
		exit(1);
	}

	// 버퍼에 저장된 원본 파일의 내용을 목적 파일에 저장한다
	fout << buf;

	fin.close();
	fout.close();

	wcout << L"텍스트 파일 복사하기 완료" << endl;
	return 0;
}


이미지 파일 복사

#include <iostream>
#include <locale>
#include <fstream> // wofstream 클래스
#include <cstdlib> // wcstombs()
#include <string>  // getline()

using namespace std;

int main() {
	setlocale(LC_ALL, "");
	wcout << L"C++ 파일 입출력 스트림" << endl;

	wstring wfname(L"C:\\test\\sample.png");

	char* pfname = new char[wfname.size()*2];
	wcstombs(pfname, wfname.c_str(), wfname.size()*2);

	// 원본파일 스트림
	ifstream fin;
	fin.open(pfname, ios::binary|ios::ate); // 포인터를 끝에 위치시킨다

	if(!fin.good()) {
		wcerr << L"파일 열기 실패" << endl;
		exit(1);
	}

	// 파일의 내용 끝에서 원본파일의 전체 크기를 확인한다
	int len = fin.tellg();
	cout << "source file size = " << len << endl;
	//wchar_t* buf = new wchar_t[len];
	char* buf = new char[len];

	// 포인터를 다시 파일 내용의 처음으로 위치시킨다
	fin.seekg(0);

	// 파일 전체 내용을 버퍼에 복사한다
	fin.read(buf,len);

	// 복사 목적 파일 및 스트림 생성
	wstring wfcopy(L"C:\\test\\복사본.png");
	char* pfcopy = new char[wfcopy.size()*2];
	wcstombs(pfcopy, wfcopy.c_str(), wfcopy.size()*2);
	ofstream fout(pfcopy,ios::binary);

	if(!fout.good()) {
		wcerr << "목적파일 스트림 오류" << endl;
		exit(1);
	}

	// 버퍼에 저장된 원본 파일의 내용을 목적 파일에 저장한다
	//fout << buf;  // 이 문장 대신 아래처럼 해야 한다
	fout.write(buf, len);

	fout.close();
	fin.close();

	wcout << L"이미지 파일 복사하기 완료" << endl;
	return 0;
}