본문 바로가기
C++/Unmanaged C++ 강좌 노트

[C++]파일 입출력(I/O)

by 계양구놈팽이 2023. 3. 1.

Udemy에서 C++ 언 매니지드 프로그래밍을 수강하면서 배운 내용을 정리한 것입니다.

파일 입출력 <fstream>

  • ifstream
    • 파일입력
  • ofstream
    • 파일 출력
  • fstream
    • 파일 입력 및 출력
  • 파일 스트림에 << , >> 조장자 (manipulaotr)등 도 쓸 수 있음
//읽기 전용으로 파일을 오픈
ifstream fin;
fin.open("helloWorld.txt");

//Tm기 전용으로 파일을 오픈 (파일이 없으면 만듦)
ofstream fout;
fout.open("helloWorld.txt");

//읽기와 쓰기 범용으로 파일을 오픈
fstream fs;
fs.open("helloWorld.txt");

 

  • open()
    • 각 스트림마다 open() 메서드가 있음
fin.open("HelloWorl.txt",ios_base::in| ios_base::binary);
  • 모드 플래그
    • 네임 스페이스 ios_base

  • close(0 
    • fin이 스코프를 벗어나면 자동으로 소멸하면서 닫아주기는 하지만, 명시적으로 닫았음을 남기고 싶을 때 쓰면 된다.

파일에서 읽기 (read)

파일에서 문자 하나씩 읽기

ifstream fin;
fin.open("HelloWorld.txt");

char character;
while(1)
{
	fin.get(character);
    if(fin.fail())
    {
    	break;
    }
    cout << character;
}

character로 못 읽을 문자는 없기에 만약에 실패했다면 eof를 만났음을 뜻한다.

fin.close를 굳이 코드에 적지 않은 이유 또한 위에서 언급 한대로 스코프를 벗어나면서 자동으로 close 될 것이기 때문이다.

 

파일에서 한 줄씩 읽기

ifstream fin;
fin.open("HelloWorld.txt");

string line;
while(!fin.eof())
{
	getline(fin, line)
    cout << line <<endl;
}
  • 만약 HelloWorld.txt가 빈 파일이었다면, 의도치 않게 공백의 한 줄이 출력이 될 것이다. 나라면 경험적으로 여기서는 line 읽기가 실패했다면 출력을 안 하도록 했을 것이다. fin.fail()을 통해 failbit 확인.

문자열 하나와 숫자 하나 출력

ifstream fin;
fin.open("HelloWorld.txt");

string name;
float balance
while(!fin.eof())
{
	fin >> name >> balance;
	cout << name << "$" << balance <<endl;
}

하지만 실무에서 반드시 예상한 인풋파일만 주어질 리가 없다. 

잘못된 입력이 있는 경우 어떻게 고칠까?

예시) 숫자만을 읽을 것을 예상한 프로그램이 있다. 하지만, 읽어 들인 파일에는 문자가 섞여 있었다.

 

ifstream fin;
fin.open("HelloWorld.txt"); //100 C++ 300EOF이러한 내용이 담겨있다. 

float number;
while(!fin.eof())
{
	fin >> number;
    
    if(fin.fail())
    {
    	fin.clear(); //상태를 원상복귀 쉬켜준다,
        fin.ignore(LLONG_MAX,' '); // ' ' 공백까지 무시하자
    }
    else
    {
    	cout << number <<endl;
    }    
	
}

주의 만약 탭(\t) 공백이 섞여 있다면 위의 코드로는 해결이 안 된다.

숫자만 읽기

ifstream fin;
fin.open("HelloWorld.txt"); //100 C++ 300EOF이러한 내용이 담겨있다. 

int number;
string trash

while(true)
{
	fin >> number;

	if(!fin.fail())
    {
    	cout << number << endl;
    	continue;
    }    
    if(fin.eof())
    {
    	break;
    }
    
    fin.clear();
    fin >> trash;
    
}
  • trash에 fin의 쓰레기 값을 넣어주기 전에 clear()를 해서 failbit을 초기화해주는 것은 필수다.

EOF 처리는 까다롭다.

  • 입출력 연산이 스트림 상태 비트를 변경한다는 사실을 기억할 것
  • EOF를 잘못 처리하면 무한 반복을 초래
  • clear()를 쓸 때는 두 번 생각하자.

Best Practie

  • 입력 처리 문제는 업계에서 매우 흔한 문제
  • 처음부터 완벽하게 입력을 처리하는 코드를 작성하는 건 거의 불가능
    • 반복적인 테스트가 중요
  1. 유효한 입력뒤에 EOF
  2. 유효한 입력과 뉴라인(\n) 뒤에 EOF
  3. 유효하지 않은 입력 뒤에 EOF
  4. 유효하지 않은 입력과 뉴라인(\n)뒤에 EOF
  5. 공백:탭도 포함할 건가?
  6. 키보드 입력과 리다이렉션을 둘 다 확인할 것

 

파일에 쓰기 (write)

ofstream fout;
fout.open("HelloWorld.txt");

string line;
getline(cin, line);
if(!cin.fail())
{
	fout << line <<endl;
}

fout.close();

 

  • put()
    • 문자를 써 넣음
    • fout.put(character)
  • <<
    • fout << line <<endl;

바이너리 파일 읽기

ifstream fin("studentRecords.dat", ios_base::in | ios_base::binary);

if(fin.is_open())
{
	Record record;
    fin.read((char*)&record,sizeof(Record));
}

fin.close();

Record의 크기만큼 공간을 할당 받고, record의 주소에 읽어온 데이터를 저장한다.

 

바이너리 파일 쓰기

ofstream fout("studentRecords.dat", ios_base::out | ios_base::binary);

if(fout.is_open())
{
	char buffer[20] = "Hello World!";
    fout.write(buffer,20);
}

fout.close();

파일 안에서의 탐색

fstream fs("studentRecords.dat", ios_base::in | ios_base::out | ios_base::binary);

if(fs.is_open())
{
	fs.seekp(20, ios_base::beg);
    if(!fs.fail()_
    {
		//21번째 위치에서 부터 덮어쓰기
	}
}

fs.close();

탐색 유형

  • 절대적
    • 예)특정한 위치로 감
    • 보통 tellp()/tellg()를 사용해서 기억해 놨던 위치로 들어갈 때 사용
  • 상대적
    • 예)파일의 끝에서부터 5바이트 앞의 위치로 이동