C++

C++ / cin을 활용하여 키 입력받기

발아현미 2025. 3. 24. 16:36

C++의 기본중의 기본이 되는 cin, cout을 활용하여 사각형의 면적을 구하는 코드를 짜 보겠다.

 

//키 입력 받기
#include <iostream>

int main() {
	int width;
	int height;
	int area;

	std::cout << "너비를 입력하시오: ";
	std::cin >> width;

	std::cout << "높이를 입력하시오: ";
	std::cin >> height;

	area = width * height;
	std::cout << "면적: " << area << std::endl;

	return 0;
}

 

 - int타입 말고 다른 타입도 받아올 수 있을까?

물론 cin 입력스트림은 int type뿐 아니라 double, float, char 등 여러 type의 변수값도 받아올 수 있다.

 

//여러 타입의 키 입력 받기
#include <iostream>

int main() {
	int a;
	double b;
	char c;

	std::cout << "int 타입 변수 입력:";
	std::cin >> a;

	std::cout << "double 타입 변수 입력: ";
	std::cin >> b;

	std::cout << "char 타입 변수 입력: ";
	std::cin >> c;

	std::cout << "int 타입 변수 : " << a << std::endl;
	std::cout << "double 타입 변수 : " << b << std::endl;
	std::cout << "char 타입 변수 : " << c << std::endl;

	return 0;
}

 

- cin이 어떻게 작동하는지 자세하게 알고 싶어.

cin기능을 이해하기 전에 Stream buffer라는 것을 알아야 한다.

 '스트림 버퍼' 라는 기능은 사용자가 키보드로 특정 값을 입력했을 때, '버퍼'라는 임시 공간에 받은 정보를 저장했다가 필요 시 저장한 값을 보내주는 역할을 한다.

 컴퓨터가 키보드, 마우스 등과 같은 입출력장치로 막대한 정보의 양을 받을 때, 정보들을 CPU에 전송하는 데에 많은 시간이 소요된다. 입출력장치와 CPU의 처리 속도 간극을 줄이기 위해, 받은 데이터를 일정량 모아서 한꺼번에 CPU에 전송하는 기능이 '스트림 버퍼' 가 된다.

 

더 자세한 설명을 위해 예제 코드를 주겠다.

//스트림 버퍼의 이해

#include <iostream>

int main() {
    std::cout << "Hello";
    std::cout << " World";
    std::cout << std::endl;

    return 0;
}

 

위 코드를 문득 본다면,

  1. "Hello" 문자열이 출력되고 
  2. " World" 문자열이 출력되고
  3. std::endl로 인해 문장이 끝난다

 

라고 오인할 수 있다. 하지만 실상은 아래와 같다.

  1. "Hello" 문자열이 스트림 버퍼에 저장된다. (출력X)
  2. " World" 문자열이 스트림 버퍼에 저장된다. (출력X)
  3. std::endl이 '\n'을 스트림 버퍼에 넣고, std::cout에게 현재 스트림 버퍼에 있는 데이터를 출력하라고 지시
  4. Hello World\n 출력

시각화한다면 아래와 같겠다!

- std::endl 로 출력할 때도 있고, '\n'으로 출력할 때도 있는데... 뭐가 다른건가?

//\n과 std::endl의 차이

#include <iostream>

int main() {
    std::cout << "Hello" << std::endl;
    
    std::cout << "Hello\n";
    
    return 0;
}

std::endl으로 스트림버퍼를 출력시키는 방법은 아래 순서를 따른다.

  1. "Hello" 행이 스트림버퍼에 저장된다.
  2. << 연산자가 endl() 함수를 호출한다. 이때 endl() 함수는 <iostream>헤더 파일에 들어있는 함수이다.
  3. endl() 함수는 스트림 버퍼에 '\n'을 넣고, 지금까지 스트림 버퍼에 쌓여있는 데이터를 출력하도록
    std::cout 에게 지시한다.

\n으로 스트림버퍼를 출력시키는 방법은 아래 순서를 따른다.

  1. "Hello\n"행이 스트림버퍼에 저장된다. 
  2. std::cout 은\n을 감지하여 지금까지 스트림 버퍼에 쌓인 데이터를 출력한다.

- \n은 그저 스트림 버퍼에 '\n'을 입력할 뿐, 출력명령을 내리진 않네?

이 궁금중의 원인은 std::cout에게 있다.

C++에는 std::cout이외에도 출력 버퍼가 여러 개 존재하고, 각자만의 특징이 존재한다.

완전 버퍼링(Fully Buffered) std::ofstream 버퍼에 특정 크기의 데이터가 쌓일 때 출력
줄 단위 버퍼링(Line Buffered) std::cout 개행('\n') 감지시에 출력
버퍼 없음(Unbuffered) std::cerr 버퍼 없이 즉시 출력

 

std::cout을 사용했기 때문에 '\n'을 감지하여, 자동적으로 출력이 진행된 것이다.

- cin은 여러 값을 받아올 수 있다!

아래 코드를 살펴보자.

#include <iostream>

int main() {
    int num;
    std::string text;

    std::cout << "숫자와 문자 입력: ";
    std::cin >> num >> text;

    std::cout << "숫자: " << num << ", 문자: " << text << std::endl;
}

코드를 실행하면 신기한 사실을 볼 수 있다.

"5 Oh\n"문자열을 입력했는데, 마치 std::cin이 숫자와 문자를 구별한 것 처럼 int type의 변수 num에 5가 저장, string type의 변수 text가 "Oh"로 초기화되었다!

 

코드의 행동양상을 확인해보자.

  1. 스트림 버퍼에 "5 Oh\n" 이 저장됨
  2. std::cin >> num에서, 스트림버퍼에 저장된 값 중 정수 부분을 읽어서 num에 저장
    (스트림버퍼에는 " Oh\n"이 남아있음)
  3. std::cin >> text에서, "Oh"만 저장

- std::cin은 공백과 개행을 무시하여 필요한 정보만 빼먹는가?

 반은 맞고 반은 틀린 말이다. 바로 위에서 다룬 코드를 본다면, text를 읽기 바로 직전에는 스트림버퍼에 아래와 같은 데이터가 저장되어 있었을 것이다. 

std::cin은 공백(스페이스, 탭, \n)을 마치 칸막이처럼 생각한다. 그렇기 때문에 위 버퍼에 저장된, '칸막이' 사이 담겨있는 "Oh" 문자열을 text 변수에 저장했을 것이다.

 

 - cin은 가끔씩 머리가 아프다

공백을 무시하는 cin의 "칸막이" 기능은 정말 편해 보이지만, 가끔씩 두통을 유발하곤 한다.

#include <iostream>

int main() {
    int num;
    std::string text;

    std::cout << "숫자와 문자 입력: ";
    std::cin >> num >> text;

    std::cout << "숫자: " << num << ", 문자: " << text << std::endl;
}

아까와 동일한 코드에서, text에 "Hello World"를 입력하고 싶다고 치자.

의도와 다르게, text변수에는 "Hello World"가 아닌 "Hello" 만 저장된 모양이다.

스트링 버퍼를 들여다 본다면 아래와 같을 것이다.

num변수에는 무사히 31이 저장되었겠다. 하지만 그 이후가 문제인 모양이다. 스페이스라는 '칸막이'에 둘러싸인 "Hello", 

칸막이와 \n에 둘러싸인 "World"가 있는데, cin 요 친구가 "Hello"만을 text 변수에 저장을 했고, 결과적으로 "World\n" 만 스트림 버퍼에 남게 되었다.

주의!
cin은 변수의 값을 무사히 받아온 후 개행(스페이스), 탭을 버퍼에서 제거하지만,
\n은 제거하지 않는다.

 - 단어 말고 문장을 받고싶다.

getline()함수를 사용하면 된다!

 getline함수는 cin과 비슷하게 문자열을 받아오지만, cin은 char type의 변수 전용이라 한다면 getline은 string type 의 변수라고 할 수 있다.

 아래 간단한 예제면 이해가 될 것이다.

#include <iostream>
#include <string>

int main() {
    std::string text;

    std::cout << "문장 입력: ";
    std::getline(std::cin, text);

    std::cout << "문장: " << text << std::endl;
}

getline함수 안에 std::cin, (저장할 변수명)을 입력하면 끝이다!

띄어쓰기, 탭이 있어도 입력을 중단하지 않고, 계속 문자열을 받아오는 모습을 볼 수 있다!

 하지만 getline함수도 치명적인 단점이 있는데, 스트림 버퍼에 \n이 남아 있는 상태에서 입력을 받기 시작하면 맥을 못 추린다.

 

- getline과 버퍼의 부조화

버퍼에 불필요한 값이 이미 입력된 상태에서 getline을 사용하게 된다면, 원하지 않은 결과를 얻어올 수 있다.

#include <iostream>
#include <string>

int main() {
    std::string text;
    int num;

    std::cout << "숫자 입력: ";
    std::cin >> num;

    std::cout << "문장 입력: ";
    std::getline(std::cin, text);

    std::cout << "숫자: " << num << ", 문장: " << text << std::endl;
}

 

숫자입력까지는 순조롭게 이어졌지만, 문장을 입력할 새도 없이 프로그램이 종료되었다.  어떻게 된 일인지 스트림 버퍼를 자세히 보자.

  1. 프로그램이 실행되었다. 이때는 스트림 버퍼에 아무 내용도 들어있지 않았을 것이다.
  2. num변수에 정보를 저장하기 위해 사용자는 "23\n"을 입력했다. 
    이때 cin은 23을 num변수에 저장하고, \n을 버퍼에 삭제하지 않은 채 남겨두었다.
  3. text변수에 정보를 저장하기 위해 실행된 getline()함수는, 실행과 동시에 스트림 버퍼에 있던 \n을 사용자가 입력한 값으로 착각하고 빈 문자열 ""을 text에 저장해버렸다.
    (getline함수는 \n을 감지하면, 그 전까지의 값을 저장한 후 버퍼에서 만났던 \n을 삭제한다.)
  4. getline함수가 \n을 삭제했기 때문에 스트림 버퍼에는 아무것도 남지 않게 된다.

 - \n문제를 해결하려면 어떻게 해야 할까?

ignore()함수를 사용하면 된다. ignore함수는 기본적으로 버퍼에 저장되어 있는 값들을 n개 삭제할 수 있다.

#include <iostream>
#include <string>

int main() {
    std::string text;
    int num;

    std::cout << "숫자 입력: ";
    std::cin >> num;
    std::cin.ignore();

    std::cout << "문장 입력: ";
    std::getline(std::cin, text);

    std::cout << "숫자: " << num << ", 문장: " << text << std::endl;
}

ignore()함수를 사용한 이후 버퍼는 아래와 같은 양상을 보일 것이다!

 

이렇게 해서 cout, cin, getline(), ignore까지 간단히 알아보았다.

 

 

헷갈릴만 한 cin, getline()의 차이를 도식화하면 아래와 같다.

  입력 시작 입력 도중 입력 종료
cin 개행, 탭, \n이 존재할 시 제거하고 입력받기 기다림 개행, 탭이 있을 경우 거기까지 읽는다
(개행, 탭은 삭제
 \n은 삭제하지 않는다)
\n은 제거하지 못하고 버퍼에 남겨놓는다
getline() 개행, 탭, \n을 모조리 입력받는다 개행, 탭이 있어도 모조리 저장 \n을 제거한다

 

'C++' 카테고리의 다른 글

C++ / 문자열 입력하기  (1) 2025.03.27