IO Stream
카테고리: Cpp
태그: Programming
모두의 코드 씹어먹는 c++ 자료를 보고 정리한 내용입니다.
ios_base
: 스트림의 입출력 형식 관련 데이터 처리ios
: 스트림 버퍼 관리 & 입출력 작업의 상태 관리iostream
: 실제로 입력/출력을 수행하는 클래스 (operator », operator » 정의 )fstream
: 파일 입출력 수행하는 클래스
스트림 상태 관리
int main() {
int t;
while (true) {
std::cin >> t;
std::cout << "입력 :: " << t << std::endl;
if (t == 0) break;
}
}
- 위 코드에서
s
하나를 입력하면 무한 루프에 빠짐. Why??
ios 클래스에서 [스트림의 상태를 관리] → 상태를 관리하는 4개의 플래그(비트 하나) 정의
goodbit
: 스트림에 입출력 작업이 가능할 때badbit
: 스트림에 복구 불가능한 오류 발생 시failbit
: 스트림에 복구 가능한 오류 발생 시eofbit
: 입력 작업시에 EOF 도달 시
타입에 맞지 않는 값을 넣어서 오류가 발생하는 경우 failbit
플래그 ON
→ 입력을 받지 않고 리턴해버림
→ 버퍼에 문자열이 그대로 남아있음
→ 읽고 또 읽고, 또 읽고, ..
while (std::cin >> t)
{
// ...
}
s
를 입력하면operator >>
는cin
객체의failbit
킴std::cin >> t
이후cin
객체가 리턴 (operator >>
는 호출한 자기 자신을 리턴)- 리턴된
cin
객체가while
문의 조건식으로 들어가기 때문에 컴파일러가 적절한 타입 변환을 찾게 됨 ios
(cin) 객체 내부에는operator void*() const
함수가 존재 (void포인터로의 형변환 연산자)- 즉,
ios
객체 →void*
→bool
(false) →while
문 탈출 - 참고)
NULL 포인터
는 bool 상 false
[문제는 해결했지만, 입력을 계속 진행할 수는 없음…]
int main() {
int t;
while (true) {
std::cin >> t;
std::cout << "입력 :: " << t << std::endl;
if (std::cin.fail()) {
std::cout << "제대로 입력해주세요" << std::endl;
std::cin.clear(); // 플래그들을 초기화 하고
std::cin.ignore(100, '\n'); // 개행문자가 나올 때 까지 무시한다
}
if (t == 1) break;
}
}
std::cin.fail()
:failbit
orbadbit
가 true면 true 반환std::cin.clear()
: 플래그 초기화std::cin.ignore(100, '\n')
: 두 번째 인자가 나올 때 까지 최대 첫 번째 인자만큼 버퍼 비우기
Format Flag & Manipulator
입력한 수를 16진수로 처리하고 싶음!
[Format Flag]
std::cin.setf(std::ios_base::hex, std::ios_base::basefield);
- 몇 진법으로 수를 처리할지 보관하는
basefield
의 값을 초기화하고, 16진법hex
플래그를 적용 std::ios_base::hex
: hex는 단순한 상수 값- 사실 문자열로 입력받아서 처리해도 되지만, 이것저것 처리할 것들이 많기 때문에
IO
라이브러리에서 처리하는 것이 깔끔
[Manipulator]
std::cin >> std::hex >> t;
std::hex
: 스트림을 조작하여 입력 혹은 출력 방식을 바꿔주는 함수!
std::ios_base& hex(std::ios_base& str);
istream& operator>>(ios_base& (*pf)(ios_base&));
str.setf(std::ios_base::hex, std::ios_base::basefield)
hex
는ios_base
객체의 레퍼런스를 받고, 다시 그 객체를 리턴하는 함수operator >>
에 함수포인터(ios_base
레퍼런스를 파라미터로 받고, 리턴하는 함수) 오버로딩 되어있음std::hex
가 하는 일은setf
와 동일
이 외에도 여러 Manipulator(조작자) 존재
boolalpha
: ture
나 false
를 1과 0으로 처리하는 대신 문자열 그대로 입력, 출력
left
/ right
: 왼쪽 / 오른쪽 정렬
endl
: 한 줄 개행문자 출력 & 버퍼 Flush
Stream Buffer
모든 입출력 객체들은 대응되는 스트림 객체를 가지고 있다. 따라서 c++의 입축력 라이브러리에는 이에 대응되는 스트림 버퍼 클래스도 있는데, 바로 streambuf
클래스 이다. streambuf
클래스는 스트림의 상태를 나타내기 위해 아래와 같은 세 개의 포인터를 가지고 있다.
- 버퍼의 시작 부분을 가리키는 시작 포인터
- 읽을 문자를 가리키고 있는 포인터 (스트림 위치 지정자)
- 버퍼의 끝 부분을 가리키는 포인터
또한
steambuf
클래스는 입력 버퍼와 출력 버퍼를 구분해 [get area] 와 [put area] 로 구분하고, 이에 따라 각각을 가리키는 포인터도g
와p
를 붙여서 표현한다.
스트림 버퍼 다루기 예제)
int main() {
std::string s;
std::cin >> s;
// 위치 지정자를 한 칸 옮기고, 그 다음 문자를 훔쳐본다 (이 때는 움직이지 않음)
char peek = std::cin.rdbuf()->snextc();
if ( std::cin.fail() ) std::cout << "Failed";
std::cout << "두 번째 단어 맨 앞글자 : " << peek << std::endl;
std::cin >> s;
std::cout << "다시 읽으면 : " << s << std::endl;
}
컴파일 결과)
hello world 두 번째 단어 맨 앞글자 : w 다시 읽으면 : world
char peek = std::cin.rdbuf()->snextc();
rdbuf()
: [cin] 객체가 입력을 수행하고 있던steambuf
객체 포인터 리턴.- 이때 [cin] 객체가
istream
이므로, [get area]만 존재
- 이때 [cin] 객체가
snextc()
: 스트림 위치 지정자를 한 칸 전진시킨 후, 그자리에 해당하는 문자를 엿봄- 원래 해당 문자를 읽으면 위치 지정자를 한 칸 전진시킴 (엿보는 것은 전진 X)
Stream Buffer를 도입한 중요한 이유 중 한 가지는, 1바이트짜리 문자 뿐만이 아니라. 다중 바이트 문자들 (UTF-8과 같이) 에 대한 처리도 가능하게 하기 위함이다.
파일 입출력
fstream
: iostream
을 상속받아 파일 처리 기능을 추가한 라이브러리
int main() {
// 파일 읽기 준비
std::ifstream in("test.txt");
std::string s;
if (in.is_open()) {
in >> s;
std::cout << "입력 받은 문자열 :: " << s << std::endl;
} else {
std::cout << "파일을 찾을 수 없습니다!" << std::endl;
}
return 0;
}
cin
이나 cout
의 경우 이미 표준 입출력에 연동이 되어 있지만, 파일 입출력의 경우 어느 파일에 입출력을 해야할 지 지정을 해야한다. 이는 ifstream
객체 생성자에 연동하고자 하는 파일의 경로를 전달하면 된다.
한 가지 흥미로운 점은, C 언어와 달리 close
를 해줄 필요가 없다. 왜 그렇냐면, 이미 ifstream
객체 소멸자에 구현이 되어있기 때문이다. 다만, 같은 객체가 새로운 파일에서 입력을 받고자 한다면 기존 파일 스트림과의 연결을 종료하고 새로운 파일을 연결해주어야 한다 (close, open) .
리틀 에디안
int main() {
// 파일 읽기 준비
std::ifstream in("test.txt", std::ios::binary);
std::string s;
int x;
if (in.is_open()) {
in.read((char* )(&x), 4);
std::cout << std::hex << x << std::endl;
} else {
std::cout << "파일을 찾을 수 없습니다!" << std::endl;
}
return 0;
}
Input file | Compile Result |
---|---|
파일 입출력에서 주의해야 할 것은 있다. 우리가 쓰는 CPU
의 경우 리틀 에디안이라 해서, 높은 주소값에 높은 자리수가 온다고 생각하면 된다. 따라서 각각의 바이트 EF / BB / BF / EC
가 거꾸로 EC / BF / BB / EF
이렇게 int
변수에 기록된다.
std::ifstream in("test.txt", std::ios::binary);
ifstream
객체를 생성할 때 생성자에 옵션으로 문자열 데이터를 받는게 아니라 그냥 이진 그대로의 값을 받음- Default는 문자열.
int x;
in.read((char* )(&x), 4);
read
함수는 말 그대로 두 번째 인자(4) 바이트 만큼의 내용을 읽으라는 내용- 첫 번째 인자에는 해당하는 버퍼를 전달해주어야 함
- 위 명령어의 경우
int
변수를 4바이트 짜리char
배열이라 생각하라는 의미
파일 전체 읽기
int main() {
// 파일 읽기 준비
std::ifstream in("test.txt");
std::string s;
if (in.is_open()) {
// 위치 지정자를 파일 끝으로 옮긴다.
in.seekg(0, std::ios::end);
// 그리고 그 위치를 읽는다. (파일의 크기)
int size = in.tellg();
// 그 크기의 문자열을 할당한다.
s.resize(size);
// 위치 지정자를 다시 파일 맨 앞으로 옮긴다.
in.seekg(0, std::ios::beg);
// 파일 전체 내용을 읽어서 문자열에 저장한다.
in.read(&s[0], size);
std::cout << s << std::endl;
} else {
std::cout << "파일을 찾을 수 없습니다!" << std::endl;
}
return 0;
}
seekg()
: 파일 위치 지정자 움직임 (파일 내 두 번째 인자로부터 첫 번째 인자만큼 떨어진 위치)tellg()
: 파일의 시작 지점으로 부터 위치 지정자 까지의 거리 반환
파일 한 줄씩 읽기
int main() {
// 파일 읽기 준비
std::ifstream in("test.txt");
char buf[100];
if (!in.is_open()) {
std::cout << "파일을 찾을 수 없습니다!" << std::endl;
return 0;
}
while (in) {
in.getline(buf, 100);
std::cout << buf << std::endl;
}
return 0;
}
getline()
- 파일에서 개행문자(‘ /n’) 가 나올 때까지 (최대 지정한 크기 - 1) 만큼 읽음
- -1을 하는 이유는 bufer 맨 마지막 문자로 널 종료 문자를 넣어줘야 하기 때문
- 개행문자 말고도 다른 문자 지정 가능
while (in)
ifstream
에는 자기 자신을bool
로 캐스팅하는 캐스팅 연산자operator bool()
가 오버로딩 되어 있음- 다음 입력 작업이 성공적이여야
true
반환
하지만 getline()
함수는 개행문자(혹은 지정한 문자)가 나오기 전에 지정한 버퍼의 크기가 다 차면 failbit
키게 됨. 따라서 버퍼의 크기가 작다면 정상적으로 데이터를 받을 수 없음
→ 매우 귀찮기 때문에 std::string
에서 getline()
함수 제공
std::string s;
while (in) {
getline(in, s);
std::cout << s << std::endl;
}
파일 쓰기
int main() {
// 파일 쓰기준비
std::ofstream out("test.txt", std::ios::app);
std::string s;
if (out.is_open()) {
out << "덧붙이기";
}
return 0;
}
ios::app
: 파일의 기존 내용을 지우지 않고, 그 뒤에 붙여 씀ios::ate
: 파일을 열 때 위치 지정자가 파일 끝을 가리키고 있음ios::trunc
: 파일 스트림을 열면 기존 내용들을 모두 지워버림. [디폴트 설정]
app vs ate app은 원본 파일의 내용을 건드릴 수 없음 반면 ate의 경우 위치 지정자를 이전으로 옮겨 수정이 가능
std::ofstream 연산자 오버로딩
class Human {
std::string name;
int age;
public:
Human(const std::string& name, int age) : name(name), age(age) {}
std::string get_info() {
return "Name :: " + name + " / Age :: " + std::to_string(age);
}
friend std::ofstream& operator<<(std::ofstream& o, Human& h);
};
std::ofstream& operator<<(std::ofstream& o, Human& h) {
o << h.get_info();
return o;
}
int main() {
// 파일 쓰기 준비
std::ofstream out("test.txt");
Human h("이재범", 60);
out << h << std::endl;
return 0;
}
// test.txt
Name :: 이재범 / Age :: 60
문자열 스트림
int main() {
std::istringstream ss("123");
int x;
ss >> x;
std::cout << "입력 받은 데이터 :: " << x << std::endl;
return 0;
}
std::istringstream
: 마치 문자열을 하나의 스트림이라 생각하게 해주는 가상화 장치
위 코드는 마치 ss란 파일에 문자열 “123”을 기록해놓고 거기서 입력을 받는 것과 같음
std::ostringstream
함수 역시 마찬가지
댓글 남기기