본문 바로가기

별걸다하는 IT/프로그래밍언어

C언어 문자열 istream::getline()과 C++ string의 getline()! 한 줄 읽는 함수가 두 개?

[C언어, C++언어, JAVA언어 포스팅 링크, 라이브러리 함수 모음 링크]

[C/C++]

포스팅에 들어가기 전 cstring vs string.h vs string 스트링클래스 차이(C-strings vs std::string) 이 포스팅을 먼저 읽고 보길 권장드려요!

 

getline()함수가 두 개?

getline() 함수가 두 개 있어서 헷갈려 본사람 계신가요? getline()함수는 각각 다른 라이브러리에 속해있는데요, 왜 동일한 이름의 함수가 두 개 존재할까요?

 

string 포스팅에서 언급했듯이 우리는 문자열 관련된 라이브러리가 cstring 그리고 string 두 종류가 있어요. (cstring하고 string.h는 동일한거라고 묶을게요).

뒤에 '/0'으로 끝나는 char* 형식을 따르는 C언어 방식의 문자열 라이브러리(cstring)와 std::string을 따르는 라이브러리(string) 이렇게 두 개요! 문자열을 처리하는 방법이 두 가지가 되면서 그 방법에 따라 getline()함수도 두 종류가 존재하게 된겁니다.ㅎㅎㅎ 즉 각 getline()함수는 문자열을 처리하는 방식이 달라요.

 

istream 라이브러리에 속해있는 getline()함수는 뒤에 '/0'이 붙는 char* 형식 즉 클래직한 C언어 문자열을 따르는 입력 방법입니다. 반면에 string 라이브러리에 속해있는 getline()함수는 std::string 방식으로 동작해요 ㅎㅎ 그래서 인자를 보면 istream:getline()함수의 경우 char* 타입을 인자로 받는데 string의 getline()함수는 string을 인자로 받을 수 있습니다.

각가의 문법을 좀 더 자세히 알아볼게요.

 

1. std::istream::getline - cin.getline()

1
2
istream& getline(char* s, streamsize n);
istream& getline(char* s, streamsize n, char delim);
cs

 

인자:

s - C 형식 문자열을 저장할 배열을 가리키는 포인터

n - 저장할 문자의 최대 개수 (끝의 종료 널 문자를 포함한 값). 만약 입력 스트림의 최대 크기에 도달하여 입력이 중단되면 failbit 플래그가 설정됩니다.

delim - 제한자로 이 문자에 도달시 추출이 중단됩니다. 이 때 이 문자는 s에 기록되지는 않지만 스트림에서 사라지게 됩니다. 

 

이 getline()함수는 istream에 속한 메서드입니다.

즉 istream을 상속받는 클래스에서 getline()함수를 사용할 수 있어요.

콘솔에서 문자열을 입력받으려면 cin.getline()을, 파일으로부터 문자열을 가져오려면 파일입력스트림인 ifstream의 인스턴스에서 getline()을 호출하면 됩니다.

 

# 기본 사용법

#include <iostream>
#include <fstream> //파일입출력
using namespace std;
 
int main()
{
    char greeting[100];
    cout << "say sth: ";
    cin.getline(greeting, 10);
    cout << "greeting console: " << greeting<<"\n";
 
    ifstream ifs;
    ifs.open("fileinput.txt");
    ifs.getline(greeting, 100);
    cout << "greeting file: " << greeting;
    ifs.close();
    return 0;
}

 

콘솔에는 hello Console!을 입력해주고 

파일명은 fileinput.txt로 저장을 한뒤 내용은 이렇게 작성했어요.

결과를 볼까요?

 

 

cin.getline(greeting, 10)은 10자를 입력받아 greeting이라는 변수에 넣어라! 라는 뜻입니다.

그런데 greeting에 저장되어 있는 것은 10자인 'hello Cons'이 아닌 'hello Con'이네요. getline()함수는 c스타일 문자열 형식을 따른다고 했죠?! 마지막에 널('\0)을 저장해줘야 하기 때문에 실질적으로 저장되는 자리수는 n-1인 9자리입니다. 또 n의 자리수를 크게 하면 파일에 쓰여진 글은 두 줄이지만 한 줄(hello File!)까지만 출력됩니다.

당연히 문자열에 C-배열 타입 말고 string을 인자로 넣으면 에러뜹니다. string을 인자로 넣고 싶으면 string 라이브러리의 getline()을 사용하기!

istream& getline(char* s, streamsize n);

즉 이 함수는 n-1개의 문자 개수만큼 읽어와서 s에다가 저장시킵니다. 3번째 인자인 char delim을 별도로 지정해주지 않으면 엔터('\n')로 인식하기 때문에 n이 클 경우 s에는 한 줄이 저장돼요. 3번째 인자를 지정해주면 그 문자를 만나면 읽어오지 않기 때문에 그 제한자(delim) 문자 직전까지 읽어다가 s에 저장해줍니다. 즉 ifs.getline(greeting, 10, 'i');하면 i직전인 hello F까지 저장됩니다.

 

2. std::getline (string)

1
2
istream& getline (istream& is, string& str);
istream& getline (istream& is, string& str, char delim);
cs

 

인자:

is - 입력스트림 오브젝트 ex) cin

str - 입력받은 문자열을 저장할 string 객체

delim - 제한자로 이 문자에 도달시 추출이 중단됩니다. 이 때 이 문자는 s에 기록되지는 않지만 스트림에서 사라지게 됩니다. 

# 기본 사용법

#include <iostream>
#include <string>
#include <fstream>
using namespace std;

int main()
{
    string greeting;
    getline(cin, greeting);
    cout << "console 입력 값:"<<greeting <<"\n";

    ifstream ifs;
    ifs.open("fileinput.txt");
    getline(ifs, greeting);
    cout << "file 입력 값:" << greeting << "\n";
    return 0;
}

결과는 위와 똑같아요 ㅎㅎ  대신 이 함수는 입력스트림이 첫 번째 인자로 오고 두 번째 인자로 string 객체를 받습니다. string으로 문자열을 해석하는 함수이기 때문에 C스타일의 문자열 (char *)을 인자로 넣어주면 안돼요! 

 

3. 주의해야 할 점 (getline 함수 둘 다 모두 해당)

#include <iostream>
using namespace std;

int main()
{
    char a[100], b[100], c[100];
    cin >> a;
    cin.getline(b, 100);
    cin.getline(c, 100);
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "c:" << c << endl;
    return 0;
}​

이렇게 코드를 작성한 후 입력을 아래처럼 주었을 경우 결과가 어떻게 될까요?

입력: hello 엔터 Ella 엔터 Yang

a에 hello, b에 Ella c에 Yang이 들어갔을까요?

 

 

출력은 이처럼 됩니다. 조심해야 해요. 이럴 경우, cin 이후에 버퍼를 비워주는 함수(ex cin.ignore())를 실행해서 버퍼를 비워주고 난 뒤 getline으로 입력을 받아야 해요.

이런 문제는 cin함수와 getline함수의 차이에서 오는데요

어떤 원리로 입력을 받아들이는지 하나씩 살펴보면서 설명할게요

 

-- cin의 경우

#include <iostream>
using namespace std;

int main()
{
    char a[100], b[100], c[100];
    cin >> a;
    cin >> b;
    cin >> c;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "c:" << c << endl;
    return 0;
}​

입력을 'hello              Ella         Yang'으로 사이사이에 공백을 충분히 주고 넣어줬어요.

 

이 결과에서 알 수 있는 것은 cin은 공백을 무시한다는거예요. cin은 입력값의 기준을 띄어쓰기, 엔터, 탭 등으로 나눕니다. 그래서 문자를 읽다가 띄어쓰기를 만나면 그 전까지를 하나의 입력으로 받아들여요. 반면 띄어쓰기, 엔터, 탭 등 이런 문자가 먼저 나오면 그냥 무시합니다. 대신에 버퍼에는 그대로 남아있어요.

cin이 버퍼에 있는 글자 하나하나를 읽어들입니다. hello까지 읽고 공백이 나오니까 공백 전 hello까지를 a에 저장시켜요.

그러면 다음 버퍼에 남아있는 문자들은 띄어쓰기들이겠죠!! 이때 cin은 멀쩡한 문자가 나올 때까지 공백들을 다 무시합니다. 그리고 Ella가 나오면 읽고 저장합니다. 이런식으로 동작해요. 

입력함수가 계속 cin이라면 문제되지 않아요. cin은 내부적으로 공백 같은 문자들을 무시해주는 formatting된 함수이기 때문에 버퍼에 공백이 남아도 무시하고 지나가기 때문이죠.

 

-- getline() 의 경우

getline이 하나의 입력으로 받아들이는 기준은 따로 delim을 지정해주지 않는한 엔터('\n')입니다. 

그런데 getline()함수는 unformatting 함수입니다. 

getline은 엔터('\n')를 무시하지 않고 잡습니다! 이 차이에서 문제가 발생해요.

getline만 사용할 경우에는 문자열을 치고 엔터를 쳤을 때, 엔터 직전까지를 하나의 입력으로 받아들이고 엔터를 버려요! 버퍼에 남기지 않아요. 그래서 다음 getline을 사용할 때에도 버퍼에 남아있는 것이 없기 때문에 문제가 되지 않습니다.

하지만 앞에서 봤듯이 cin은 엔터를 버리지 않고 버퍼에 남긴다고 했죠? 그냥 cin은 엔터를 무시하는 기능이 있기 때문에 문제되지 않았던 거예요. getline은 무시하는 기능이 없고 잡아서 읽기 때문에 반드시 두 함수에서 나오는 차이점을 숙지하고 사용하셔야 합니다 ㅎㅎ 

오늘은 여기까지입니다. 공감, 댓글, 또는 광고보답은 포스팅을 작성하는데 힘이 됩니다.