티스토리 뷰

 

C++의 시작

 
C언어를 학습하고 C++로 넘어가려는 당신! 'C++'다운 코드를 작성하기 위해서 C++의 코딩 스타일을 먼저 알아봅시다.
 

입출력

C++는 cout(console output)과 cin(console input)이 있습니다. 두 객체는 각각 출력과 입력을 담당하게 됩니다.
 

그림 1. 입출력 라이브러리(https://cplusplus.com/reference/iolibrary/)

헤더
C++ <iostream>
C C++ : <cstdio>, C : <stdio.h>

 


스트림 버퍼

기본적으로 모든 입출력은 '스트림 버퍼'라는 공간에 임시저장되었다가 읽기 혹은 쓰기 동작으로 이어집니다.

 
'스트림 버퍼'란, 예로 하드디스크에서 1MB 크기의 파일을 1byte씩 읽는다 가정할 때, 실제 프로그램은 더 큰 범위(예로 64byte)의 데이터를 가져와 스트림 버퍼에 임시 저장하고 프로세스가 1byte씩 읽습니다.
쓰기 과정도 다르지 않습니다. 쓰려는 문자를 버퍼에 임시저장한 뒤, 어느 정도 데이터가 모이면 출력을 합니다.
 
이러한 과정을 거치는 이유는, 하드디스크에서 읽는 작업 속도가 매우 느리기 때문입니다. (그래픽스의 더블버퍼링(Double Buffering) 기법과 같은 방식입니다.)
읽기 작업 중, 버퍼를 사용하지 않고 읽는 속도에 비해 데이터를 가져오는 속도가 느리다면, 프로세스는 모든 파일의 데이터를 읽기 위하여 읽기 작업 스택을 연달아 생성해야 합니다. (한 번에 모든 데이터를 읽으려 한다면 파일크기에 따라 메모리가 대량 필요할 수 있다.)
이러한 비효율적 작업 생성은 예기치 못한 오류나 작업 속도를 현저히 낮추는 결과를 초래할 수 있습니다.
 
 


헤더

cout과 cin은 원래 객체이며, endl, eof, hex, oct 등 사용되는 명령어들은 모두 함수입니다.
 
위 그림 1을 보면, istream과 ostream 헤더가 보입니다. 각 헤더들은 cin과 cout의 객체를 포함하며, 입출력에 필요한 연산자들과 부수적 함수들을 포함합니다.
 

base_ios

base_ios 클래스는 모든 입출력 클래스의 기반이 되며, 문자 유형과 관계없이 모든 스트림 객체에 공통되는 가장 기본적인 헤더입니다.
 
ios_base와 그 파생 클래스 basic_ios는 입력/출력 스트림을 구별하지 않는 스트림 객체를 정의합니다. 때문에, 하나의 base 클래스로 하위 입/출력 헤더 모두에서 사용이 가능합니다.

  • ios_base는 템플릿 타입에서 독립된 자료형의 정보를 가집니다.
  • base_ios는 템플릿을 통한 타입이 적용됩니다.
📌 ios_base::sync_with_stdio
 ios_base에는 'sync_with_stdio'함수가 존재합니다. 해당 함수는 프로세스의 '준비' 단계에서 모든 표준 스트림과 C 스트림의 동기화 설정을 조절합니다.

 이 말의 뜻을 이해하기 위해서는, 기본적으로 iostream 객체와 cstdio 스트림은 같이 동기화됩니다. 즉, C의 입출력 스트림과 C++ 입출력 스트림이 합쳐져 사용됩니다. 그러므로 기본 설정 C++의 입출력 스트림을 사용할 경우, C의 입출력(printf, scanf, etc)들을 사용하는 것보다 속도가 느려집니다. 반대로 말하면 'sync_with_stdio'를 통해 입출력 속도를 비약적으로 높일 수 있습니다.

단, sync_with_stdio를 통해 C와 C++ 스트림의 동기화를 제거하면, C++ 환경에서 C의 입출력 함수를 사용할 수 없게 됩니다.

* 백준에서 사용하기 좋습니다.

 

istream

istream 클래스는 입력을 담당하는 객체와 연산자들을 가진 클래스입니다.
 
cin으로 받은 값은 스트림 버퍼에 저장되고 '>>'연산자를 통해 void*형으로 반환합니다. 마지막으로 사용되는 될 곳에 형변환이 됩니다.
 

cin(ios 객체) void* 최종 자료형

 
기본적으로 cin으로 모든 기본 자료형을 입력받을 수 있는 이유도 istream에서 이미 모든 타입을 받을 수 있는 연산자들이 정의되어 있기 때문입니다.

istream& operator>>(bool& val);
istream& operator>>(short& val);
istream& operator>>(unsigned short& val);
istream& operator>>(int& val);
istream& operator>>(unsigned int& val);
istream& operator>>(long& val);
istream& operator>>(unsigned long& val);
istream& operator>>(long long& val);
istream& operator>>(unsigned long long& val);
istream& operator>>(float& val);
istream& operator>>(double& val);
istream& operator>>(long double& val);
istream& operator>>(void*& val);
istream& operator>>(istream& in, std::string& s);
📌 '>>'연산자는 모든 공백문자 입력을 무시하는 특징이 있습니다.
우선 아래 코드를 확인하시고 우리는 cin이 공백을 기준으로 문자열을 읽는다는 것을 확인할 수 있습니다. (공백까지 읽기 위해서 getline 함수를 대신 사용하는 경우가 많습니다.)

#include <iostream>
#include <string>

int main() {
  std::string s;
  while (true) {
    std::cin >> s;
    std::cout << "word : " << s << std::endl;
  }
}
Hello World
word : Hello
word : World

 
 

ostream

ostream은 cout 객체를 포함한 클래스입니다.
ostream은 기본적으로 출력을 담당하고 있으며, 각종 조작자(cin도 동일)를 통해 자료형을 변환할 수 있습니다.
 
 


I/O stream flags

ios 클래스가 관리하는 스트림에는 스트림의 상태를 나타내는 플래그가 있습니다.
 

iostate 값 설명 상태 값 확인 함수
(bool 반환)
현재 스트림 오류 상태 플래그 리턴
goodbit 스트림의 입출력 작업이 가능한 경우 cin.good() rdstate()
badbit 스트림에 복구 불가능 오류가 발생한 경우 (스트림 버퍼 재사용 불가) cin.bad()
failbit 스트림에 복구 가능한 오류가 발생한 경우 (스트림 버퍼 재사용 가능) cin.fail()
eofbit 입력 작업 중 EOF 도달한 경우 cin.eof()

 
여기서 badbit와 failbit의 차이를 눈여겨볼 필요가 입니다.
 
우선 badbit은 입/출력 값에 의해 혹은 특정 조건에 의해 스트림 버퍼가 재기능을 상실한 경우입니다.
반대로 failbit은 입/출력 값이 잘못되었지만 스트림 버퍼 자체는 이상이 없는 상태입니다.
 

자료형 입력 실수
#include <iostream>
using namespace std;
int main() {
  int t;
  while (true) {
    std::cin >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (t == 0) break;
  }
}
1
입력 :: 1
2
입력 :: 2
c
입력 :: 0
// System down

 
위와 같은 경우, int형을 받아야 하는 상황에서 char형 입력 값을 통해 0이 반환되는 것을 확인할 수 있다. (0이 반환된 것은 컴파일러마다 잘못된 데이터를 처리하는 방법이 다르기 때문입니다.)
 
이는 잘못된 입력 값을 처리하는 방법 중 하나로, 최악의 결과로는 다음과 같이 무한 루프에 빠질 수 도입니다.

 
이러한 현상은 다음과 같은 과정을 통해 발생합니다.
 

  1. 잘못된 데이터 타입을 입력 받는다.
  2. ios에서 failbit를 활성화 시킨다.
  3. 더이상 입력 값을 받지 않고 리턴한다.
    • 하지만 이미 입력 받은 값(예제 기준 'c\n')은 버퍼에 남아 있다.
  4. 버퍼가 비어있지 않아서 반복하여 동일한 값('c\n')을 읽는다.

 
위 상황을 다른 코드로 확인한다면 다음과 같습니다.

#include <iostream>
#include <string>

int main() {
  int t;
  std::cin >> t;  // 고의적으로 문자를 입력하면 failbit 가 켜진다
  std::cout << "fail 비트가 켜진 상태이므로, 입력받지 않는다" << std::endl;
  std::string s;
  std::cin >> s;
}
s
fail 비트가 켜진 상태이므로, 입력받지 않는다

 
 

flag control

그럼 이제 이러한 상황들을 어떻게 해결할지 알아봅시다.
우선 cin 객체의 fail, clear, ignore 함수를 소개하겠습니다. (추가적인 함수들은 다음 링크 확인)

fail failbit 혹은 badbit가 true인지 확인하고 bool형 반환
clear 현재 플래그 상태를 변경, 인자가 없는 경우 goodbit로 변환
ignore istream 클래스 내부 클래스로 현재 버퍼를 지정된 크기만큼 원하는 문자가 나올때까지 비운다.

 
* clear함수는 현재 플래그 상태를 변경하며, default 인자의 경우 goodbit로 변경합니다.
 
위 함수들을 사용하면, 다음과 같이 현재 버퍼의 상태를 확인할 수 있습니다.
 

  1. cin을 통해 잘못된 값을 입력 받는다.
  2. cin.fail을 통해 failbit 확인
  3. failbit가 true이면
    1. 현재 플래그 초기화
    2. 버퍼를 지운다.
#include <iostream>
#include <string>

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;
  }
}
a
입력 :: 0
제대로 입력해주세요
x
입력 :: 0
제대로 입력해주세요
d
입력 :: 0
제대로 입력해주세요
asdf
입력 :: 0
제대로 입력해주세요
2
입력 :: 2
1
입력 :: 1

 
 


형식 플래그와 조작자(Manipulator)

이번엔 입출력 숫자들을 변형하는 방법을 알아보겠습니다. (어느 코테에도 나왔던 소문이...)
 

조작자 (함수호출)

각 입출력 조작자는 8진수, 10진수, 16진수로 변환해 줍니다. 이는 '>>' & '<<' 연산자와 같이 사용이 됩니다.

dec 스트림 정수 입출력 연산 시 10 진법을 사용.
str.setf(std::ios_base::dec, std::ios_base::basefield)과 동일.
std::ios_base& dec(std::ios_base& str);
hex 스트림 정수 입출력 연산 시 16 진법을 사용.
str.setf(std::ios_base::hex, std::ios_base::basefield)과 동일.
std::ios_base& hex(std::ios_base& str);
oct 스트림 정수 입출력 연산 시 8 진법을 사용.
str.setf(std::ios_base::oct, std::ios_base::basefield)과 동일.
std::ios_base& oct(std::ios_base& str);
// 조작자의 사용
#include <iostream>
#include <string>

int main() {
  int t;
  while (true) {
    std::cin >> std::hex >> t;
    std::cout << "입력 :: " << t << std::endl;
    if (std::cin.fail()) {
      std::cout << "제대로 입력해주세요" << std::endl;
      std::cin.clear();           // 플래그들을 초기화 하고
      std::cin.ignore(100, 'n');  //개행문자가 나올 때까지 무시한다
    }
    if (t == 0) break;
  }
}
ff
입력 :: 255
0xFF
입력 :: 255
123
입력 :: 291
ABCDE 
입력 :: 703710

 
 

조작자 (상수 값 사용, cin.setf 함수)

cin.setf 함수는 선언된 위치 아래 스트림 설정을 바꾸어 줍니다.
set 함수의 첫 인자는 목표 형식 플래그를 설정하고, 두 번째 인자는 변경할 내용을 선택할 수 있습니다.
 

fmtfl
format flag value
mask
field bitmask
left, right or internal adjustfield
dec, oct or hex basefield
scientific or fixed floatfield

 

📌 ios_base::hex
ios_base에도 hex, dec, oct가 정의되어 있습니다. 해당 값들은 ios_base에 정의되어 있는 '상수 값'입니다.
반면, 연산자와 같이 사용되는 hex, dec, oct는 ios에 정의되어 있는 '함수'입니다.
#include <string>
#include <iostream>

int main() {
  int t;
  while (true) {
    std::cin.setf(std::ios_base::hex, std::ios_base::basefield);
    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 == 0) break;
  }
}

 
 
 


참고자료

 

https://cplusplus.com/reference/istream/istream/

 

cplusplus.com

 

씹어먹는 C++ - <7 - 1. C++ 에서의 입출력 (istream, ostream)>

모두의 코드 씹어먹는 C++ - <7 - 1. C++ 에서의 입출력 (istream, ostream)> 작성일 : 2015-05-04 이 글은 92910 번 읽혔습니다. 에 대해서 알아봅니다. 안녕하세요! 여러분. 정말 오래간만에 강좌를 올리는 것

modoocode.com

 

'Computer Language > C & C++' 카테고리의 다른 글

[C++] 3) C++ 스타일 기본 문법 :: 레퍼런스와 r-value 참조  (2) 2023.11.20
[C++] 2) C++ 스타일 기본 문법 :: string  (2) 2023.11.20
[C++] Unique 함수  (0) 2023.11.15
[C/C++] Memset  (0) 2023.11.05
[C/C++] EOF  (0) 2023.11.05
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함