Getline에 대해 알아보자

문자열 입력

아래의 문자열 입력 예시를 보자

input:
ha
haha
haha hoho

위의 입력을 받아야한다고 가정해보자. 첫번째 두번째 문자는 우리가 흔히 사용하는 cin 입력 스트림을 통해 받을 수 있다. 다만, cin 같은 경우 공백 문자(개행문자)를 기준으로 입력 스트림을 통해 문자열을 읽어드린다. 따라서, 세번째 문자열은 “haha hoho” 가 아닌 “haha” 만 입력으로 받아드리고 입력 스트림 내 “hoho” 를 남겨둘 것이다. “haha hoho” 를 한번에 입력 받으려면 어떻게 해야할까?

이때 사용하는 것이 바로 getline() 이다.

getline 함수

Getline 함수란 입력 스트림에서 문자를 읽어드려, 인자로 전달받은 문자를 지정한 공간(문자열)에 저장한다.

getline을 사용하는 방법에는 std::getline, std::istream::getline 2가지가 존재한다. 첫 번째 함수는 string header file에 들어있는 함수이고, 두번째 함수는 istream이 속한 iostream header file에 존재한다.

std::getline()

string 헤더 파일에 정의된 getline() 같은 경우, 입력 스트림에서 문자를 읽어드려 문자열에 저장하도록 한다.

template< class CharT, class Traits, class Allocator >
std::basic_istream<CharT,Traits>& getline( std::basic_istream<CharT,Traits>& input,
                                           std::basic_string<CharT,Traits,Allocator>& str,
                                           CharT delim );
template< class CharT, class Traits, class Allocator >
std::basic_istream<CharT,Traits>& getline( std::basic_istream<CharT,Traits>&& input,
                                           std::basic_string<CharT,Traits,Allocator>& str,
                                           CharT delim );
template< class CharT, class Traits, class Allocator >
std::basic_istream<CharT,Traits>& getline( std::basic_istream<CharT,Traits>& input,
                                           std::basic_string<CharT,Traits,Allocator>& str );
template< class CharT, class Traits, class Allocator >
std::basic_istream<CharT,Traits>& getline( std::basic_istream<CharT,Traits>&& input,
                                           std::basic_string<CharT,Traits,Allocator>& str );

위와 같이 template으로 구성되어져 있다.

string에서 사용하는 getline()은 기본적으로 delimter를 만날 때 까지 입력스트림을 읽어드려 문자열에 저장한다. 보통 delimter는 \n 문자로 선언되어져있다. 또한 어떤 연유에서건 읽기 오류가 발생한다면 fail bit 를 설정해 오류를 발생시킨다. 만약, File의 끝에 도달한다면 eof bit 를 설정해 읽기를 중지하도록한다.

기본적으로 사용하는 인자는 3가지이다.

std:getline()에서 사용하는 인자를 기억하자. 첫째로 stream을 전달해야한다. 보통 cin을 많이 전달해 표준 입력으로 인자를 받는다. 두번째는 문자열이다. 세번째는 delimter로 사용자가 원하는 값으로 변경해도 된다. 이 값을 기준으로 읽어드린다.

아래의 예시는 cppreference 사이트에 발췌하였다.

#include <string>
#include <iostream>
#include <sstream>
 
int main()
{
    // greet the user
    std::string name;
    std::cout << "What is your name? ";
    std::getline(std::cin, name);
    std::cout << "Hello " << name << ", nice to meet you.\n";
 
    // read file line by line
    std::istringstream input;
    input.str("1\n2\n3\n4\n5\n6\n7\n");
    int sum = 0;
    for (std::string line; std::getline(input, line); ) {
        sum += std::stoi(line);
    }
    std::cout << "\nThe sum is: " << sum << "\n";
}

위의 예시에서 istringstream을 이용해 문자열을 스트림 단위로 읽어드렸다. 이 함수를 사용하기 위해 sstream 헤더 파일을 인클루드 했으며, ‘\n’등과 같이 공백을 무시하고 읽어 드려 저장하기 때문에 이 예시에 사용하기 좋다. ‘\n’을 읽어드려야 기본적으로 delimter로 사용하고 있는 getline의 효과를 볼 수 있기 때문이다.

stoi는 함수명에서 알수 있듯 string to integer이다.

결과는 다음과 같다

What is your name? John Q. Public
Hello John Q. Public, nice to meet you.

The sum is 28

###std::basic_istream::getline

basic_istream& getline( char_type* s, std::streamsize count );
basic_istream& getline( char_type* s, std::streamsize count, char_type delim );

위의 구조가 basic_istream의 getline 구조이다. string과는 다소 차이가 존재한다.

string 문자열이 아닌 basic_istream 내 멤버함수로 존재하는 getline이다. 원리는 동일하다. EoF 나 delimiter를 만날 때 까지 스트림을 읽어 드려 stream에 저장한다. 인자에서 약간의 차이가 존재한다. std::basic_istream::getline 인자

예를들어, istream 혹은 ifstream을 보자. istream은 표준 입력을 사용할 때 주로 쓴다 (e.g. cin) ifstream은 파일 입력을 받을 경우 사용한다. 이 두 객체는 basic_istream을 상속받기 때문에 getline()를 갖고 있다. 즉, 사용하기 위해서는 istream.getline(adress, count, ‘\n’ /*생략가능*/); 사용하거나 ifstream.getline(adress, count, ‘\n’ /*생략가능*/); 과 같이 사용하면 된다. 예시를 통해 파악해보자.

동일하게 cppreference 사이트를 통해 발췌했다.

#include <iostream>
#include <sstream>
#include <vector>
#include <array>
 
int main()
{
    std::istringstream input("abc|def|gh");
    std::vector<std::array<char, 4>> v;
 
    // note: the following loop terminates when std::ios_base::operator bool()
    // on the stream returned from getline() returns false
    for (std::array<char, 4> a; input.getline(&a[0], 4, '|'); ) {
        v.push_back(a);
    }
 
    for (auto& a : v) {
        std::cout << &a[0] << '\n';
    }
}

위에서는 특별한 건 없지만, sstream 또한 basic_istream을 상속받고 있기 때문에 getline이 사용가능하다는 것을 알고 있자. 그리고 첫번째 인자로 주소로 전달받는 모습을 기억하자.

결과는 다음과 같다.

abc
def
gh

Tip

Getline을 사용하다보면 꼭 직면하는 문제가 존재한다. cin 다음 getline을 쓰면 getline()가 자동적으로 종료되는 것처럼 보인다. 이 문제는 위의 설명과 cin을 약간만 이해하면 해결할 수 있다.

우선 cin 같은 경우 원리가 공백문자 (공백 또는 개행문자) 전 까지 스트림에서 문자열을 읽어드린다. 예를들 abc라는 문자를 읽는다고 생각해보자. abc라는 것을 친 후 우리는 다 썼다는 의미로 enter를 누른다. 그럴 경우 cin은 enter (\n)를 개행문자로 보고 abc만 stream에서 읽고 enter는 stream에 놔두고 종료한다.

이후 getline을 실행한다고 가정해보자. getline 같은 경우 delimiter를 설정하지 않는다면 ‘\n’를 기준으로 읽어드린다. getline 함수를 호출 할 경우 stream에는 cin이 남겨두고간 enter (\n) 의 흔적을 보고 읽어드리고 자기 일을 마쳤다고 한다. 즉, 아무것도 안한것 처럼 나타나는 것이다.

이 부분을 해결하기 위해서는 cin 이후 스트림을 비워줘야한다.

버퍼를 비우는 cin.ignore() 함수를 호출하자

getline

마지막으로, 그렇다면 getline도 동일하게 enter를 치면 남아있는것 아닌가? 하는 의문이 들 수 있다. getline은 delimter를 기준으로 입력을 받은 후 ‘\n’는 입력받지 않도록 한다. 하지만 안정성을 위해 getline 함수 호출 후에는 clear()를 쓰자. 이 부분은 버퍼가 꽉찼을 떄를 대비한다는데.. 잘 이해가 가지않지만.. 이해를 원하는 분은 여기 를 참고하자.


####Reference