9장 문자열

2023. 11. 25. 21:37프로그래밍 공부/OOP

9.1 문자열의 배열형식

C-string 값 및 C-string 변수

C-string(C 문자열): '\0'으로 끝나는 문자 배열

'\0': null 문자(null character)이라고 불리며 문자 배열에 저장된 C 문자열의 끝을 표시하는 데 사용된다.

문자 배열이 이런 식으로 사용될 때, 그 배열을 종종 C 문자열 변수라고 부른다.

null 문자 '\0'은 두 개의 기호를 사용하여 쓰이지만,

문자 배열의 한 가지 유형 문자 변수 또는 색인화된 한 가지 변수에 맞는 하나의 문자이다.

 

C-string 변수 선언
C-string 변수는 문자의 배열과 동일한 것이지만 다르게 사용된다.

C-string 변수는 일반적인 방법으로 문자의 배열이라고 선언된다.
SYNTAX
char Array_Name[Maximum_C-string_Size + 1];
EXAMPLE
char myCstring[11];
+ 1은 배열에 저장된 C 문자열을 종료하는 null 문자 '\0'을 허용한다.
예를 들어, 앞의 예제의 C-string 변수 myCstring은 10자 이하의 길이의 C-string을 보유할 수 있다.

 

C-string 변수 초기화
C-string 변수는 다음 예제와 같이 선언될 때 초기화할 수 있다:
char yourString[11] = "Do Be Do";
이러한 방식으로 초기화하면 지정된 C-string의 끝에 있는 배열에 null 문자 '\0'이 자동으로 배치된다.
대괄호 안의 숫자인 []를 생략하면 C-string 변수에 C-string의 길이보다 한 글자 더 긴 크기가 부여된다.

예를 들어, 다음은 myString을 9개의 인덱싱된 변수(C-string "Do Be Do" 문자의 경우 8개, null 문자 '\0'의 경우 1개)로 선언한다:
char myString[] = "Do Be Do";

 

C-string 변수에 대한 인덱싱된 변수

C-string 변수는 배열이므로 다른 배열과 마찬가지로 사용할 수 있는 색인화된 변수가 있다.

예를 들어 프로그램에 다음과 같은 C 문자열 변수 선언이 포함되어 있다고 가정한다:
char ourString[5] = "Hi";
표시된 것과 같이 문자열이 선언된 상태에서 프로그램의 인덱스 변수는 다음과 같다:
ourString[0] , ourString[1] , ourString[2] , ourString[3] , and ourString[4] .
예를 들어, 다음은 문자열의 C-string 값을 모든 'X' 문자로 구성된 동일한 길이의 C-string으로 변경한다:
int index = 0;
while (ourString[index] != "\0")
{
    ourString[index] = "X";
    index++;
}

 

'\0'을 파괴하지 말기

이러한 인덱싱된 변수를 조작할 때는 null 문자 '\0'을 다른 값으로 대체하지 않도록 매우 주의해야 한다.

배열이 '\0' 값을 잃으면 더 이상 C-string 변수처럼 작동하지 않는다.

 

예시1)

다음은 배열에 더 이상 C-string이 포함되지 않도록 happyString을 변경한다:
char happyString[7] = "DoBeDo";
happyString[6] = "Z";

이전 코드가 실행된 후에도 배열 happyString은 여전히 C-string "DoBeDo"의 6개 문자를 포함하지만

happyString은 더 이상 C-string의 끝을 표시하는 null 문자 '\0'을 포함하지 않는다.

많은 문자열 조작 기능은 C-string 값의 끝을 표시하기 위해 '\0'의 존재에 결정적으로 의존한다.


예시2)

C-string 변수 ourString에서 문자를 변경하는 아래의 while loop을 생각해 보라.

char ourString[5] = "Hi";

int index = 0;
while (ourString[index] != "\0")
{
    ourString[index] = "X";
    index++;
}

반면 loop은 '\0'이 발생할 때까지 문자를 변경한다.

만약 loop이 '\0'이 발생하지 않는다면,

많은 양의 메모리를 원하지 않는 값으로 변경할 수 있고,

이것은 프로그램이 이상한 일을 하게 만들 수 있다.

 

<cstring> 라이브러리
C-string을 선언하고 초기화하기 위해 include 지시문이나 using 명령문을 사용할 필요는 없다.

그러나 C-string을 처리할 때 필연적으로 라이브러리 <cstring>에 있는 미리 정의된 문자열 함수 중 일부를 사용하게 된다.

따라서 C-strings를 사용할 때 일반적으로 코드를 포함하는 파일의 처음 부분 근처에서 다음과 같은 include 명령어를 제공한다:
#include <cstring>
<cstring>의 정의는 std 네임스페이스가 아닌 글로벌 네임스페이스에 배치되므로 사용문이 필요하지 않다.

 

함정: C-string과 함께 = 및 == 사용하기

C-string 값과 C-string 변수는 다른 데이터 유형의 값과 변수와 같지 않으며, 

일반적인 연산 중 많은 것이 C-string에 대해 작동하지 않다.

=를 사용하여 할당문에 C-string 변수를 사용할 수 없다.

==를 사용하여 C-string의 동등성 테스트를 수행하면 기대한 결과를 얻을 수 없다.

이러한 문제가 발생하는 이유는 C-string과 C-string 변수가 배열이기 때문이다.

 

C-string 값에 할당하기
C-string 변수에 값을 할당하는 것은 다른 종류의 변수에 대한 것만큼 간단하지 않다. 다음은 불법이다:
char aString[10];
aString = "Hello";

변수가 선언될 때 등호를 사용하여 C-string 변수에 값을 할당할 수는 있지만

프로그램의 다른 곳에서는 할 수 없다.

엄밀히 말하면 선언에서 등호를 사용하는 것은 다음과 같다
char happyString[7] = "DoBeDo";
는 할당이 아닌 초기화이다.

C-string 변수에 값을 할당하려면 다른 작업을 수행해야 한다.


C-string 변수에 값을 할당하는 방법에는 여러 가지가 있다.

가장 쉬운 방법은 다음과 같이 미리 정의된 함수 strcpy를 사용하는 것이다:
strcpy(aString, "Hello");
그러면 문자열 값이 "Hello"로 설정된다.

안타깝게도 이 버전의 함수 strcpy는 복사가 첫 번째 인수인 문자열 변수의 크기를 초과하지 않는지 확인하지 않는다.

전부는 아니지만 많은 버전의 C++에도 strncpy라는 strcpy가 있으며 복사할 최대 문자 수를 제공하는 세 번째 인수를 사용한다.

이 세 번째 매개 변수가 첫 번째 인수 위치에서 배열 변수 크기보다 1개 작은 값으로 설정되면 안전한 버전의 strncpy를 얻을 수 있다.

예를 들어, C++ 버전에서 이 세 번째 인수를 허용한다면 안전한 버전의 strncpy를 얻을 수 있다,
char anotherString[10];
strncpy(anotherString, aStringVariable, 9);
이 strncpy 버전을 사용하면 StringVariable의 문자열 길이에 상관없이 C-stringVariable에서 최대 9자('\0'의 여유 공간)가 복사된다.

 

C-string의 동등성 테스트하기
연산자 ==를 표현식으로 사용하여 두 개의 C 문자열이 같은지 여부를 검정할 수도 없다.

(사실 상황은 그보다 훨씬 더 좋지 않다.

==를 C 문자열과 함께 사용할 수 있지만 C 문자열이 같은지 검정하지는 않는다.

따라서 ==를 사용하여 두 C 문자열의 동일성을 검정하면

잘못된 결과가 나올 수 있지만 오류 메시지는 없다!)
두 개의 C-끈이 동일한지 여부를 검정하기 위해 미리 정의된 함수 strcmp를 사용할 수 있다.

 

예시)
if (strcmp(cString1, cString2))
    cout << "The strings are NOT the same.";
else
    cout << "The strings are the same.";
strcmp 함수는 여러분이 추측할 수 있는 것과 다르게 작동한다.

문자열이 일치하지 않으면 비교는 참이다.

함수 strcmp: C-string 인수의 문자를 한 번에 하나의 문자를 비교한다.

어떤 시점에서 cString1의 문자의 숫자 인코딩이 cString2의 해당 문자의 숫자 인코딩보다 작으면

테스트가 그 시점에서 중지되고 음수가 반환된다.

cString1의 문자가 cString2의 문자보다 크면 양수가 반환된다.

(strcmp의 일부 구현은 문자 인코딩의 차이를 반환하지만, 그것에 의존해서는 안 된다.)

C-string이 같으면 0이 반환된다.

사전 순서(lexicographic order): 문자를 비교할 때 사용되는 순서 관계

중요한 점은 두 문자열이 모두 대문자이거나 모두 소문자인 경우 사전 순서는 알파벳 순서일 뿐이라는 것이다.
strcmp는 C-string이 사전적으로 더 작거나, 더 크거나, 같은지에 따라 음의 값, 양의 값, 또는 0을 반환한다.

만약 C-string의 동등성을 검정하기 위해 if 또는 루프 문장에서 strcmp를 bool 식으로 사용한다면,

문자열이 다르면 0이 아닌 값이 true로 변환되고 0은 false로 변환된다.

C-string 동등성을 검정할 때 이 논리를 반드시 기억해야 한다.


표준을 준수하는 C++ 컴파일러는 비교할 수 있는 최대 문자 수를 제공하는 세 번째 인수가 있는 더 안전한 strcmp 버전을 가지고 있다. strcpy와 strcmp 함수는 헤더 파일 <cstring>이 있는 라이브러리에 있으므로

이들을 사용하려면 파일의 맨 위에 다음을 삽입해야 한다:

#include <cstring>

strcpy 및 strcmp의 정의는 std 네임스페이스가 아닌 전역 네임스페이스에 배치되므로 명령어를 사용할 필요가 없다.


<cstring>에서의 다른 함수

디스플레이 9.1에는 헤더 파일 <cstring>이 있는 라이브러리에서 가장 일반적으로 사용되는 몇 가지 함수가 포함되어 있다.

이들 함수를 사용하려면 파일의 맨 위에 다음을 삽입하자:
#include <cstring>
<cstring>은 모든 정의를 std 네임스페이스가 아닌 글로벌 네임스페이스에 배치하므로 using 문이 필요하지 않다.
strcpy와 strcmp에 대해서는 이미 논의했다.

strlen은 이해하기 쉽고 사용하기 쉽다.

예시) strlen("dobedo")은 dobedo에 6개의 문자가 있으므로 6을 반환한다.

 

함수 strcat: 두 개의 C-string을 연결하는 데 사용된다.

즉, 두 개의 더 짧은 C-string을 끝에서 끝으로 연결하여 더 긴 문자열을 만든다.

첫 번째 인수: C-string 변수여야 한다.

두 번째 인수: 인용 문자열과 같이 C-string 값으로 평가되는 모든 것일 수 있다.

결과는 첫 번째 인수인 C-string 변수에 배치된다.

 

예시)
char stringVar[20] = "The rain";
strcat(stringVar, "in Spain");
이 코드는 stringVar의 값을 "The rain in Spain"으로 바꾼다.

이 예가 보여주듯이, C-string을 연결할 때 빈칸을 설명하는 것을 주의할 필요가 있다.

만약 당신이 디스플레이 9.1의 표를 본다면, C++의 많은 버전들에서 이용할 수 있는 더 안전한 함수 strcat의 인수 3개 버전이 있다는 것을 알 수 있을 것이다.

 

C-String 인수 및 매개변수
C-string 변수는 배열이므로 함수에 대한 C-string 매개변수는 단순히 배열 매개변수이다.
다른 배열 매개변수와 마찬가지로 함수가 C-string 매개변수의 값을 변경할 때마다 

C-string 변수의 선언된 크기를 고려하여 추가 int형 매개변수를 포함하는 것이 가장 안전하다.
반면 함수가 C-string 인수의 값만 사용하고 값을 변경하지 않는 경우 

C-string 변수의 선언된 크기 또는 채워지는 C-string 변수 배열의 양 중 하나를 제공하기 위해 

다른 매개 변수를 포함할 필요가 없다.
Null 문자인 '\0'을 사용하여 C-string 변수에 저장된 C-string 값의 끝을 탐지할 수 있다.

 

디스플레이 9.1 <cstring>에서의 미리 정의된 cstring 함수

FUNCTION DESCRIPTION CAUTIONS
strcpy(Target_String_Var, Src_String) C-string 값 Src_String을 C-string 변수 Target_String_Var에 복사합니다. Target_String_Var가 Src_String 값을 보유할 수 있을 정도로 큰지 확인하지 않습니다.
strncpy(Target_String_Var,
Src_String, Limit)
최대 제한 문자가 복사된다는 점을 제외하고는 2개 인수 strcpy와 동일합니다. Limit을 신중하게 선택하면 strcpy의 2개 인수 버전보다 안전합니다. C++의 일부 버전에서는 구현되지 않습니다.
strcat(Target_String_Var,
Src_String)
C-string 변수 Target_String_Var의 C-string 끝에 C-string 값 Src_String을 연결합니다. Target_String_Var가 연결 결과를 유지할 수 있을 정도로 크기가 큰지 확인하지 않습니다.
strncat(Target_String_Var,
Src_String, Limit)
최대 제한 문자가 추가되는 것을 제외하고는 2개 인수 strcat과 동일합니다. Limit을 신중하게 선택하면 strcat의 2개 인수 버전보다 안전합니다. C++의 일부 버전에서는 구현되지 않습니다.
strlen (Src_String) Src_String의 길이와 같은 정수를 반환합니다. (null 문자인 '\0'은 길이로 계산되지 않습니다.)  
strcmp(String_1, String_2) String_1과 String_2가 같으면 0을 반환합니다. String_1이 String_2보다 작으면 값 < 0을 반환합니다. String_1이 String_2보다 크면 값 > 0을 반환합니다(즉, String_1과 String_2가 다른 경우 0이 아닌 값을 반환합니다). 순서는 사전 형식입니다. String_1이 String_2와 같으면 이 함수는 0을 반환하고 이는 false로 변환됩니다. 이는 문자열이 같을 때 반환되는 것과 반대입니다.
strncmp(String_1, String_2, Limit) 최대 제한 문자를 비교하는 것을 제외하고는 2개 인수 strcat과 동일합니다. Limit을 신중하게 선택하면 strcmp의 2개 인수 버전보다 안전합니다. 일부 C++ 버전에서는 구현되지 않습니다. 


예: 커맨드 라인 인수

지금까지 메인 함수에 대한 어떤 매개 변수도 지정하지 않았다.

그러나 main에 대한 입력 매개 변수를 지정하는 것은 가능하다.

입력 매개 변수는 명령줄에서 프로그램을 호출할 때 주어진 인수와 일치하다.

예시) UNIX 시스템에서 명령 ls /home은 /home 디렉터리 내용을 나열하기 위해 /home의 명령줄 인수와 함께 ls 프로그램을 호출한다.

 

C++ 프로그램에 입력되는 명령줄 인수에 액세스하려면 main에 대해 다음 헤더를 사용하라:
int main( int argc, char *argv[])
argc 파라미터는 프로그램에 부여되는 인수의 수를 지정하는 정수이다.

프로그램의 이름이 카운트되므로 argc는 적어도 1이 될 것이다.
argv 매개 변수는 C-strings의 배열이다.

argv[0]은 프로그램 이름을 보유한다.

argv[1]은 첫 번째 매개 변수의 이름을 보유하고,

argv[2]는 두 번째 매개 변수의 이름을 보유하며,

따라서 argv[argc-1]까지이다.

 

예시) 

프로그램의 이름이 getPalindromes이고 명령행에서 getPalindromes string1 string2로 호출된 경우

main, argc = 3, argv[0] = "getPalindromes", argv[1] = "string1" 및 argv[2] = "string2"로 호출된다.

프로그램이 argv의 내용을 사용하고자 한다면 

argc가 적절하게 설정되어 있는지 확인하여 

실제로 입력된 인수가 있는지 확인해야 한다.

그렇지 않으면 argv에 액세스할 때 잘못된 결과가 나올 수 있다.

 

C-String 입출력

C-string은 삽입 연산자 <<. 사실, 우리는 인용 문자열로 이미 그렇게 하고 있다.

C-string 변수를 같은 방식으로 사용할 수 있다.

예시)
cout << news << "Wow.\n";
여기서 news는 C-string 변수이다.
입력 연산자 >>를 사용하여 C 문자열 변수를 채울 수도 있지만, 한 가지 유의해야 할 사항이 있다.

다른 모든 유형의 데이터는 C 문자열을 이렇게 읽을 때 공백(빈칸, 탭, 줄 바꿈)을 모두 생략한다.

더욱이 입력을 읽을 때마다 다음 공백이나 줄 바꿈에서 멈춘다.

 

예시)
char a[80], b[80];
cout << "Enter some input:\n";
cin >> a >> b;
cout << a << b << "END OF OUTPUT\n";
샘플 대화문)
Enter some input:
Do be do to you!
DobeEND OF OUTPUT

 

C-string 변수 a와 b는 각각 입력의 한 단어만 수신한다.

a는 Do 뒤에 오는 입력 문자가 공백이므로 C-string 값 "Do"를 수신하고,

b는 뒤에 오는 입력 문자가 공백이므로 "be"를 수신한다.
프로그램이 전체 입력 줄을 읽기를 원한다면 추출 연산자 >>를 사용하여 한 번에 한 단어씩 줄을 읽을 수 있다.

 

입력 줄 전체를 읽고 결과적으로 C-string을 C-string 변수로 만드는 더 쉬운 방법이 있다.

미리 정의된 멤버 함수 getline을 사용하면 모든 입력 스트림의 멤버 함수이다.

함수 getline에는 두 가지 인수가 있다.

첫 번째 인수는 입력을 받는 C-string 변수이고

두 번째 인수는 일반적으로 C-string 변수의 선언된 크기인 정수이다.

두 번째 인수는 C-string 변수에서 getline을 채우는 데 허용되는 배열 요소의 최대 수를 지정한다

 

예시)
char a[80];
cout << "Enter some input:\n";
cin.getline(a, 80);
cout << a << "END OF OUTPUT\n";
샘플 대화문)
Enter some input:
Do be do to you!
Do be do to you!END OF OUTPUT

 

함수 cin.getline을 사용하면 전체 행이 읽힌다.

결과적으로 C 문자열이

두 번째 인수에 의해 지정된 최대 문자 수보다 짧을 수 있지만

행이 끝나면 읽기가 종료된다.
getline이 실행되면

C-string 배열에 두 번째 인수로 주어진 문자 수가 채워진 후에는

행의 끝에 도달하지 못하더라도 읽기가 중지된다.

 

예시)
char shortString[5];
cout << "Enter some input:\n";
cin.getline(shortString, 5);
cout << shortString << "END OF OUTPUT\n";
샘플 대화문)
Enter some input:
dobedowap
dobeEND OF OUTPUT
두 번째 인수가 5임에도 불구하고 C-string 변수 shortString에 5개가 아닌 4개의 문자가 읽혀진다.

이는 null 문자 '\0'이 하나의 배열 위치를 채우기 때문이다.

C-string 변수에 저장된 모든 C-string은 null 문자로 종료되며,

이는 항상 하나의 배열 위치를 소비한다.


cout 및 cin에 대해 설명한 C-string 입력 및 출력 기법은

파일과 함께 입력 및 출력을 위해 동일한 방식으로 작동한다.

입력 스트림 cin은 파일에 연결된 입력 스트림으로 대체될 수 있다.

출력 스트림 cout은 파일에 연결된 출력 스트림으로 대체될 수 있다.

(파일 I/O는 12장에서 설명한다.)

 

getline
멤버 함수 getline은 입력 줄을 읽고 해당 줄에 있는 문자의 문자열을 C-string 변수에 배치하는 데 사용할 수 있다.
SYNTAX
cin.getline(String_Var, Max_Characters + 1);
한 줄의 입력이 Input_Stream 스트림에서 읽혀지고 결과적으로 C-string이 String_Var에 배치된다.

행의 길이가 Max_Characters보다 길면 행의 첫 번째 Max_Characters만 읽혀진다.

(모든 C-string은 C-string의 끝에 null 문자 '\0'이 추가되므로

String_Var에 저장된 문자열은 읽혀진 문자 수보다 한 줄 더 길기 때문에 +1이 필요하다.)

EXAMPLE
charoneLine[80];
cin.getline(한 줄, 80);
12장에서 볼 수 있듯이 cin 대신 텍스트 파일에 연결된 입력 스트림을 사용할 수 있다.

 

9.2 문자 조작 도구

문자 입출력

모든 데이터는 문자 데이터로 입력되고 출력된다.

프로그램이 숫자 10을 출력할 때 실제로 출력되는 것은 문자 '1'과 '0' 두 개이다.

마찬가지로, 사용자가 숫자 10을 입력하려고 할 때, 그 뒤에 문자 '1'과 문자 '0'을 입력한다.

컴퓨터가 이 '10'을 두 개의 문자로 해석할 것인지 숫자 10으로 해석할 것인지는

프로그램이 어떻게 쓰이느냐에 따라 달라진다.

그러나 프로그램이 쓰이더라도 컴퓨터 하드웨어는 항상 숫자 10이 아니라 문자 '1'과 '0'을 읽는다.

문자와 숫자 사이의 이러한 변환은

보통 그런 세부 사항에 대해 생각할 필요가 없도록 자동으로 수행되지만,

때때로 이 모든 자동 도움말이 방해가 된다.

따라서 C++는 문자 데이터의 입출력을 위한 몇 가지 낮은 수준의 편의를 제공한다.

이러한 낮은 수준의 편의에는 자동 변환이 포함되지 않는다.

이렇게 하면 자동 편의를 우회하여 원하는 방식으로 입출력을 할 수 있다.

심지어 로마 숫자 표기법으로 값을 읽고 쓸 수 있는 입출력 기능을 쓸 수도 있다.

 

멤버 함수 get과 put

함수 get을 사용하면 프로그램이 한 문자의 입력으로 읽을 수 있고, char라는 변수에 저장할 수 있다.

입력 파일 스트림이든 스트림 cin이든 모든 입력 스트림은 멤버 함수로 사용된다.

우리는 여기서 get을 객체 cin의 멤버 함수로 설명할 것이다.

(12장에서 파일 I/O에 대해 설명할 때,

우리는 입력 파일 스트림에 대해 cin에 대해 설명하는 것과

정확히 동일한 동작을 한다는 것을 알게 될 것이다.)


지금까지는 추출 연산자 >>와 함께 cin을 사용하여 입력 문자를 읽어 보았다.

추출 연산자 >>를 사용하면 공백을 건너뛰는 것과 같은 작업이 자동으로 수행된다.

그러나 때로는 공백을 건너뛰고 싶지 않을 때도 있다.

 

cin.get

멤버 함수 cin.get은 문자가 공백인지 아닌지에 관계없이 다음 입력 문자를 읽는다.
멤버 함수 get은 char형 변수여야 하는 인수 하나를 사용한다.

해당 인수는 입력 스트림에서 읽은 입력 문자를 받는다.
예시)
char nextSymbol;
cin.get(nextSymbol);

 

공백과 '\n' 읽기
프로그램이 이러한 방식으로 모든 문자를 읽을 수 있다는 점에 유의해야 한다.

다음 입력 문자가 비어 있으면 이 코드는 비어 있는 문자를 읽는다.

다음 문자가 새 줄 문자 '\n'이면(즉, 프로그램이 입력 줄의 끝에 도달한 경우),

이전에 cin.get으로 호출한 것은 다음 기호의 값을 '\n'으로 설정한다.

예시)
char c1, c2, c3;
cin.get(c1);
cin.get(c2);
cin.get(c3);
샘플 입력)
AB
CD
c1의 값은 'A', c2의 값은 'B', c3의 값은 '\n'으로 설정된다.

변수 c3은 'C'와 동일하게 설정되지 않는다.

 

입력 라인 끝 탐지

멤버 함수를 사용하여 할 수 있는 한 가지 일은 프로그램이 줄의 끝을 감지하게 하는 것이다.

다음 루프는 입력 줄을 읽고 새 줄 문자 '\n'을 지나면 중지된다.

다음 입력은 다음 줄의 처음부터 읽힌다.

첫 번째 예에서, 우리는 단순히 입력을 반복했지만,

동일한 기법을 사용하면 입력으로 원하는 모든 것을 할 수 있다.
cout << "Enter a line of input and I will echo it:\n";
char symbol;
do
{
    cin.get(symbol);
    cout << symbol;
} while (symbol != '\n');
cout << "That's all for this demonstration.\n";
이 루프는 빈칸을 포함하여 입력 줄을 읽고 정확하게 되풀이한다.

대화 예시)
Enter a line of input and I will echo it:
Do Be Do 1 2 34
Do Be Do 1 2 34
That's all for this demonstration.
새로운 행 문자 '\n'은 읽기와 출력이 동시에 됨을 주목하라.

'\n'이 출력되므로 "That's"로 시작하는 문자열은 새로운 행에 있다.

'\n'과 "\n"
'\n'과 "\n"은 때때로 같은 것처럼 보인다.

cout 문장에서 이들은 동일한 효과를 내지만, 모든 상황에서 서로 교환하여 사용할 수는 없다.

'\n'은 char형의 값이며 char형 변수에 저장될 수 있다.

반면, "\n"은 정확히 하나의 문자로 구성되는 문자열이다.

따라서, "\n"은 char형이 아니므로 char형 변수에 저장될 수 없다.

멤버 함수 put

입력이 아닌 출력을 위해 사용된다는 점을 제외하고는

멤버 함수 get과 유사하다.

함수 put: 프로그램에서 하나의 문자를 출력할 수 있다.

멤버 함수 cout.put은 하나의 인수를 사용하는데,

이 인수는 상수나 변수와 같이 타입 char의 표현이어야 한다.

인수의 값은 함수가 호출되면 화면에 출력된다.

예시)

문자 'a'를 화면에 출력한다:
cout.put ("a");
함수 cout.put은 삽입 연산자 <<로 할 수 없는 작업을 허용하지 않지만 완전성을 위해 포함한다.

(12장에서 파일 I/O에 대해 논의할 때,

우리는 put이 텍스트 파일에 연결된 출력 스트림과 함께 사용될 수 있으며

cout에서만 사용되는 것으로 제한되지 않는다는 것을 알게 될 것이다.)

 

멤버 함수 get
함수 get: 입력 문자 한 개를 읽는 데 사용할 수 있다.

추출 연산자인 >>와 달리 get은 해당 문자가 무엇이든 간에 다음 입력 문자를 읽는다.

특히 get은 빈칸이나 새로운 줄 문자인 '\n'을 읽는다.

함수 get은 한 인수를 사용하는데, 이 인수는 char형 변수여야 한다.
get이 호출되면 다음 입력 문자가 읽혀지고

인수 변수의 값이 이 입력 문자와 동일하게 설정된다.

char nextSymbol;
cin.get(nextSymbol);
12장에서 볼 수 있듯이

파일에서 읽기 위해 get을 사용하려면

스트림 cin 대신 입력 파일 스트림을 사용한다.

 

프로그램이 cin.get 또는 cout.put을 사용하는 경우,

cin 및 cout의 다른 용도와 마찬가지로 프로그램에 다음 중 하나(또는 이와 유사한 것)가 포함되어야 한다:

#include <iostream>
using namespace std;
or
#include <iostream>
using std::cin;
using std::cout;


예: 줄바꿈 함수를 사용한 입력 확인

디스플레이 9.2의 getInt 함수는

사용자에게 입력이 올바른지를 묻고

사용자가 입력이 틀렸다고 말하면

새로운 값을 요구한다.


newLine() 함수에 대한 호출에 주목하라.

newLine 함수는 현재 행의 나머지 부분에 있는 모든 문자를 읽지만

문자에 대해서는 아무 일도 하지 않는다.

이것은 행의 나머지 부분을 버리게 되는 것이다.

 

따라서 사용자가 No를 입력하면

프로그램은 첫 번째 문자인 N을 읽고

newLine 함수를 호출하여 입력 행의 나머지 부분을 버린다.

 

이것은 만약 사용자가 예제 대화에서 보여준 것처럼

다음 입력 행에 75를 입력하면

프로그램은 숫자 75를 읽고

단어 No에 있는 문자 o를 읽으려고 시도하지 않음을 의미한다.

 

프로그램이 newLine 함수에 대한 호출을 포함하지 않았다면

다음 행의 숫자 75 대신 No를 포함하는 행의 o가 될 것이다.

 

디스플레이 9.2 입력 확인하기

//Program to demonstrate the functions newLine and getInput
#include <iostream>
using namespace std;

void newLine( );
//Discards all the input remaining on the current input line.
//Also discards the '\n' at the end of the line.

void getInt( int& number);
//Sets the variable number to a
//value that the user approves of.

int main( )
{
	int n;
    
	getInt(n);
	cout << "Final value read in = " << n << endl
	     << "End of demonstration.\n";
         
	return 0;
}

//Uses iostream:
void newLine( )
{
	char symbol;
	do
	{
		cin.get(symbol);
	} while (symbol != '\n');
}
//Uses iostream:
void getInt( int& number)
{
	char ans;
	do
	{
		cout << "Enter input number: ";
		cin >> number;
		cout << "You entered " << number
		     << " Is that correct? (yes/no): ";
		cin >> ans;
		newLine( );
	} while ((ans == 'N') || (ans == 'n'));
}

 

샘플 대화 상자

Enter input number: 57
You entered 57 Is that correct? (yes/no): No No No!
Enter input number: 75 You entered 75
Is that correct? (yes/no): yes
Final value read in = 75
End of demonstration.

 

함정: 입력에서 예기치 않은 '\n'

멤버 함수 get을 사용할 때는

빈칸이나 새 줄 문자 '\n'과 같이

기호로 생각하지 않는 문자까지도

입력의 모든 문자를 설명해야 한다.

get을 사용할 때 흔히 발생하는 문제는

모든 입력 줄이 끝나는 '\n'을 처리하는 것을 잊어버리는 것이다.

 

입력 스트림에 읽지 않는(그리고 일반적으로 버려지는) 새 줄 문자가 있으면,

다음 프로그램에서 멤버 함수 get을 사용하여 "진짜" 기호를 읽을 것으로 예상할 때

대신 '\n' 문자를 읽을 것이다.

 

남은 '\n'의 입력 스트림을 지우려면,

디스플레이 9.2에서 정의한 newLine 함수를 사용할 수 있다

(또는 함수 ignore를 사용할 수 있으며, 이 함수는 다음 하위 섹션에서 설명한다).

 

예시)
다양한 형태의 cin을 혼합하는 것은 합법적이다. 예를 들어, 다음은 합법적이다:
cout << "Enter a number:\n";
int number;
cin >> number;
cout << "Now enter a letter:\n";
char symbol;
cin.get(symbol);


그러나 이는 다음 대화에서 알 수 있듯이 문제를 발생시킬 수 있다:
Enter a number:
21
Now enter a letter:
A
이 대화상자에서 숫자의 값은 당신이 기대하는 대로 21이 될 것이다.

그러나 만약 당신이 변수 기호의 값을 'A'로 예상한다면, 당신은 실망할 것이다.

기호에 주어진 값은 '\n'이다.

숫자 21을 읽은 후 입력 스트림의 다음 문자는 새로운 줄 문자 '\n'이므로, 그것은 다음에 읽힌다.

줄 바꿈과 공백을 건너뛰지 말자.

 

(사실, 프로그램의 나머지 부분에 무엇이 있는지에 따라,

당신은 A를 입력할 기회조차 얻지 못할 수 있다.

변수 기호가 '\n'으로 채워지면,

프로그램은 프로그램의 다음 문장으로 진행된다.

만약 다음 문장이 화면에 출력을 보내면,

당신이 A를 입력할 기회를 얻기 전에 화면은 출력으로 채워질 것이다.)

 

대안1)

이전 코드를 다음과 같이 다시 쓰면 이전 대화에서 변수 번호를 21로 채우고 변수 기호를 'A'로 채운다:
cout << "Enter a number:\n";
int number;
cin >> number;
cout << "Now enter a letter:\n";
char symbol;
cin >> symbol;

 

대안2)
또는 디스플레이 9.2에 정의된 newLine 기능을 다음과 같이 사용할 수 있다:
cout << "Enter a number:\n";
int number;
cin >> number;
newLine( );
cout << "Now enter a letter:\n";
char symbol;
cin.get(symbol);
이 두 번째 다시 쓰기에서 알 수 있듯이

두 가지 형태의 cin을 혼합하고 프로그램을 올바르게 작동시킬 수 있지만

약간의 주의가 필요하다.

 

대안3)
세 번째 대안으로 함수 ignore를 사용할 수 있다.

이 함수는 다음 하위 섹션에서 논의한다.

 

멤버 함수 putback, peek, 그리고 ignore

프로그램이 입력 스트림의 다음 문자를 알아야 할 때가 있다.

하지만 다음 문자를 읽은 후에는 해당 문자를 처리하고 싶지 않아서 "뒤로 돌려놓기"를 원할 수도 있다.

예를 들어, 프로그램이 처음 접하는 빈칸을 포함하지 않고 최대로 읽게 하려면

프로그램이 첫 번째 빈칸을 읽어야 하는데,

이 빈칸은 입력 스트림에 더 이상 존재하지 않는다.

프로그램의 다른 부분에서는 이 빈칸을 읽고 처리해야 할 수도 있다.

 

putback

함수 cin.putback: char형의 인수 하나를 가져가서 다시 입력 스트림에 그 인수의 값을 배치한다.

따라서 그 인수가 다음에 읽어야 할 문자가 되도록 한다.

인수는 입력 문자의 값을 평가하는 모든 표현식이 될 수 있다.

멤버 함수 putback으로 입력 스트림에 다시 입력되는 문자가

마지막으로 읽혀질 필요는 없으며 원하는 문자가 될 수도 있다.

 

peek

cin.peek( ): cin이 읽을 다음 문자를 반환하지만 그 문자를 다 사용하지는 않는다.

다시 말해서, peek 함수는 당신의 프로그램이 읽을 다음 문자가 무엇인지 알려주기 위해 앞을 내다본다.

 

ignore
ignore 멤버 함수: 새 줄 문자 '\n'과 같이 지정된 문자까지의 입력을 건너뛸 수 있다.

예시)

다음은 새 줄 문자 '\n'까지의 입력 문자를 건너뛸 것이다:
cin. ignore(1000, '\n');
1000은 무시할 최대 문자 수이다. 

이 경우 '\n' 구분자가 1000자 이후에 발견되지 않으면 더 이상의 문자가 무시되지 않는다.
물론 1000 대신 다른 int 인수를 사용할 수 있고

'\n' 대신 다른 문자 인수를 사용할 수 있다.


12장에서 보게 되겠지만,

멤버 함수인 putback, peek, ignore는

cin을 텍스트 파일 입력을 위한 파일 입력 스트림 객체로 대체하여 사용할 수 있다.

 

문자 조작 함수

텍스트 처리에서 종종 소문자를 대문자로 변환하거나 그 반대의 경우도 있다.

미리 정의된 함수 toupper: 소문자를 대문자로 변환하는 데 사용될 수 있다.

예) toupper('a')는 'A'를 반환한다.

함수 toupper에 대한 인수가 소문자가 아닌 다른 것이라면,

toupper는 단순히 인수를 변경하지 않고 반환한다.
따라서 toupper('A')는 'A'를 반환하고 toupper('?')는 '?'를 반환한다.

tolower 함수: 대문자를 소문자 버전으로 변환한다는 점을 제외하고는 비슷하다.

toupper 및 tolower 함수는 헤더 파일 <cctype>과 함께 라이브러리에 있으므로

이러한 기능 또는 이 라이브러리의 다른 기능을 사용하는 모든 프로그램은 다음을 포함해야 한다:
#include <cctype>

<cctype>은 이러한 정의를 전역 네임스페이스에 모두 포함시키므로 명령어를 사용할 필요가 없다.

디스플레이 9.3에는 라이브러리 <cctype>에서 가장 일반적으로 사용되는 함수에 대한 설명이 포함되어 있다.

 

공백 문자(whitespace)

공백 문자: 빈 문자인 탭 문자와 새 줄 문자인 '\n'을 포함하여 화면에 빈 문자로 표시되는 모든 문자
함수 isspace는 인수가 공백 문자인 경우 true를 반환한다.

함수 isspace는 인수가 공백 문자가 아닌 경우 false를 반환한다.

따라서 isspace('')는 true를 반환하고

isspace('a')는 false를 반환한다.
예시)

다음은 마침표로 끝나는 문장을 읽고

모든 공백 문자가 '-' 기호로 대체된 문자열을 되풀이한다:

char next;
do
{
    cin.get(next);
    if (isspace(next))
        cout << '-';
    else
        cout << next;
} while (next != '.');

예를 들어, 이전 코드에 다음과 같은 입력이 주어지면,
Ahh do be do.
다음과 같은 출력을 생성한다:
Ahh do be do.

 

디스플레이 9.3 ctype에서의 함수

FUNCTION DESCRIPTION EXAMPLE
toupper
(Char_Exp)
Char_Exp의 대문자 버전(int형 값)을 반환합니다. char c = toupper('a');
cout << c;
Outputs: A
tolower
(Char_Exp)
Char_Exp의 소문자 버전(int형 값)을 반환합니다. char c = tolower ('A');
cout << c;
Outputs: a
isupper
(Char_Exp)
Char_Exp가 대문자일 경우 true를 반환하고 그렇지 않으면 false를 반환합니다. if (isupper(c))
    cout << "Is uppercase.";
else
    cout << "Is not uppercase.";
islower
(Char_Exp)
Char_Exp가 소문자일 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다. char c = 'a';
if (islower(c))
    cout << c << " is lowercase.";
Outputs: a is lowercase.
isalpha
(Char_Exp)
Char_Exp가 알파벳 문자일 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다. char c = '$';
if (isalpha(c))
    cout << "Is a letter.";
else
    cout << "Is not a letter.";
Outputs: Is not a letter.
isdigit
(Char_Exp)
Char_Exp가 '0'에서 '9'까지의 숫자 중 하나일 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다. if (isdigit('3'))
    cout << "It's a digit.";
else
    cout << "It's not a digit.";
Outputs: It's a digit.
isalnum
(Char_Exp)
Char_Exp가 문자 또는 숫자일 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다. if (isalnum('3') && isalnum('a'))
    cout << "Both alphanumeric.";
else
    cout << "One or more are not.";
Outputs: Both alphanumeric.
isspace
(Char_Exp)
Char_Exp가 공백 또는 줄 바꿈 문자와 같은 공백 문자일 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다. //Skips over one "word" and sets c
//equal to the first whitespace
//character after the "word":
do
{
    cin.get(c);
}
while (! isspace(c));
ispunct
(Char_Exp)
Char_Exp가 공백, 숫자 또는 문자 이외의 인쇄 문자일 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다. if (ispunct('?'))
    cout << "Is punctuation.";
else
    cout << "Not punctuation.";
isprint
(Char_Exp)
Char_Exp가 인쇄 문자일 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다.  
isgraph
(Char_Exp)
Char_Exp가 공백 이외의 인쇄 문자일 경우 true를 반환합니다. 그렇지 않으면 false를 반환합니다.  
isctrl
(Char_Exp)
Char_Exp가 컨트롤 문자인 경우 true를 반환하고 그렇지 않으면 false를 반환합니다.  


함정: toupper과 tolower은 int값을 반환한다

많은 면에서 C++는 문자를 int형의 숫자와 마찬가지로 정수라고 생각한다.

각 문자에는 숫자가 할당된다.

문자가 char형 변수에 저장될 때 컴퓨터 메모리에 저장되는 것은 이 숫자이다.

C++에서는 예를 들어 char형의 값을 숫자로 사용할 수 있다.

(숫자가 너무 크지 않은 경우) char형 변수에 int의 숫자를 저장할 수도 있다.

따라서 char형은 문자의 종류로 사용되거나 작은 정수의 종류로 사용될 수 있다.

보통 이 세부 사항에 신경 쓸 필요가 없으며

단순히 char형의 값을 숫자로 사용하는 것에 대한 걱정 없이 문자라고 생각할 수 있다.

 

그러나 <ctype>의 몇 가지 함수를 사용할 때는 이 세부 사항이 중요할 수 있다.

함수를 터치하여 char형의 값이 아닌 int형의 실제 반환 값을 낮추는 함수,

즉 문자 자체가 아니라 반환하는 것으로 생각하는 문자에 해당하는 숫자를 반환한다.

따라서 다음은 문자 'A'를 출력하지 않고 'A'에 할당된 숫자를 출력한다:
cout << toupper ('A');


컴퓨터가 toupper에 의해 반환된 값을

int형의 값이 아닌 char형의 값으로 처리하도록 하려면

char형의 값을 원한다는 것을 표시해야 한다.

 

한 가지 방법은 반환된 값을 char형 변수에 넣는 것이다.

다음은 일반적으로 우리가 원하는 문자인 'A'를 출력한다.
char c = toupper('a');
cout << c;


컴퓨터가 topper에서 반환한 값을 처리하거나

char형의 값으로 낮추도록 하는 또 다른 방법은

다음과 같이 type cast를 사용하는 것이다:
cout << static_cast<char>(toupper('a'));

 

9.3 표준 string 클래스

표준 string 클래스 소개

클래스 string은 이름도 <string>인 라이브러리에 정의되며 정의는 std 네임스페이스에 배치된다.

따라서 클래스 string을 사용하려면

코드에 다음과 같은 내용(또는 어느 정도 동등한 내용)이 포함되어야 한다:
#include <string>
using namespace std;
클래스 string을 사용하면 string 값과 string 식을 단순한 유형의 값과 매우 유사하게 취급할 수 있다.

 

+는 연결을 한다

= 연산자를 사용하여 string 변수에 값을 할당하고 + 부호를 사용하여 두 string을 연결할 수 있다.

예시)
s1, s2, s3는 string 유형의 개체이며 s1과 s2는 모두 string 값을 갖는다. 

그러면 s3는 s1의 string 값과 s2의 string 값의 연결과 동일하게 설정할 수 있다:
s3 = s1 + s2;
새로운 string 값에 비해 s3가 너무 작다는 위험은 없다.

s1과 s2의 길이의 합이 s3의 용량을 초과하면 s3에 대해 더 많은 공간이 자동으로 할당된다.


이 장 앞에서 언급했듯이 따옴표 string은 실제로 C-string이므로 문자 그대로의 string이 아니다.

그러나 C++는 따옴표 string을 따옴표 string 값에 자동으로 유형 캐스팅을 제공한다.

따라서 따옴표 string을 문자 그대로의 string 값인 것처럼 사용할 수 있으며,

우리는 따옴표 string을 따옴표 string 값인 것처럼 사용할 것이다.

 

생성자
클래스 string에는 빈 string로 string 개체를 초기화하는 기본 생성자가 있다.

클래스 문자열에는 표준 C-string인 인수 하나를 따는 두 번째 생성자도 있으므로 따옴표로 된 문자열이 될 수 있다.

두 번째 생성자는 문자열 개체를 C-string 인수와 같은 string을 나타내는 값으로 초기화한다.

예시)
string phrase;
string noun("ants");
첫 번째 줄: string 변수 구문을 선언하고 빈 string로 초기화한다.
두 번째 줄: 명사가 string 유형임을 선언하고 C-string "ants"에 해당하는 string 값으로 초기화한다. 

느슨하게 말할 때 대부분의 프로그래머들은 "명사가 "ants"로 초기화된다"고 말할 것이지만,

실제로 여기에는 유형 변환이 있다.

인용된 문자열 "ants"는 string 유형 값이 아니라 C-string이다.

변수 명사는 "ants"와 같은 순서로 "ants"와 같은 문자를 갖는 string 값을 받지만

string 값이 '\0'으로 끝나는 것은 아니다.


string 변수를 선언하고 기본 생성자를 호출하는 대체 표기법이 있다.

다음 두 줄은 정확히 일치한다:
string noun("ants");
string noun = "ants";
클래스 string에 대한 이러한 기본 세부 정보는 디스플레이 9.4에 나와 있다.

여기에 표시된 것처럼 연산자 <<를 사용하여 string 값을 출력할 수 있다.

 

디스플레이 9.4 string 클래스를 이용한 프로그램

//Demonstrates the standard class string.
#include <iostream>
#include <string>
using namespace std;

int main( )
{
	string phrase; //Initialized to the empty string
	string adjective("fried"), noun("ants"); //Two equivalent ways of 
	string wish = "Bon appetite!";           //initializing a string variable
    
	phrase = "I love" + adjective + " " + noun + "!";
	cout << phrase << endl
	     << wish << endl;
         
	return 0;
}

 

샘플 대화 상자

I love fried ants!
Bon appetite!

 

C-string 상수를 string 유형으로 변환

디스플레이 9.4의 다음 행을 고려한다:
phrase = "I love "+ adjective + " " + noun + "!";

C++는 이런 단순하고 자연스러운 방식으로 string을 연결할 수 있도록 많은 작업을 해야 한다.

string 상수 "I love"는 string 유형의 개체가 아니다.

"I love"와 같은 string 상수는 C-string로 저장된다.

C++가 "I love"를 +에 대한 인수로 간주할 때,

"I love"와 같은 값에 적용되는 +의 정의(또는 오버로딩)를 찾는다.

 

이 위치 지정의 반대뿐만 아니라

왼쪽에 C-string이 있고 오른쪽에 문자열이 있는 + 연산자의 오버로딩도 있다.

심지어 +의 양쪽에 C-string이 있고 반환되는 값으로 문자열 객체를 생성하는 버전도 있다.

물론 두 피연산자 모두에 대한 string 유형에 대한 오버로딩도 있다.


클래스 string
클래스 string은 문자의 문자열인 값을 나타내는 데 사용할 수 있다.

클래스 string은 9.1절에서 설명한 C-string보다 더 다양한 string 표현을 제공한다.
클래스 string은 이름이 <string>인 라이브러리에서 정의되며 정의는 std 네임스페이스에 있다.

따라서 클래스 문자열을 사용하는 프로그램은 다음 중 하나를 포함해야 한다:
#include <string>
using namespace std;
or
#include <string>
using std::string;

 

클래스 string에는 문자열 개체를 빈 string로 초기화하는 기본 생성자와

C-string을 인수로 가져가서 인수로 주어진 string을 나타내는 값으로 string 개체를 초기화하는 생성자가 있다.

예를 들어,
string s1, s2("Hello");

 

string 클래스가 있는 입출력

다른 종류의 데이터와 마찬가지로 삽입 연산자 >>와 cout을 사용하여 문자열 객체를 출력할 수 있다.

이는 디스플레이 9.4에 나와 있다. 클래스 문자열을 사용한 입력이 조금 더 미묘하다.
추출 연산자 >>와 cin은 문자열 객체에 대해서는 다른 데이터와 동일하게 작동하지만, 

추출 연산자는 초기 공백을 무시하고 더 많은 공백을 만나면 읽기를 중지한다.

이것은 문자열에 대해서도 다른 데이터에 대해서도 마찬가지이다.

예시)
string s1, s2;
cin >> s1;
cin >> s2;
May the hair on your toes grow long and curly!
그러면 s1은 선행(또는 후행) 공백이 삭제된 상태에서 "May" 값을 받게 된다. 변수 s2는 string "the"를 받게 된다.

추출 연산자인 >>와 cin을 사용하면 단어로만 읽을 수 있고, 공백이 있는 행이나 다른 string로는 읽을 수 없다.
때로는 이것이 정확히 당신이 원하는 것이지만, 때로는 당신이 원하는 것이 전혀 아니다.
프로그램에서 입력 줄 전체를 문자열 유형 변수로 읽고 싶다면 getline 함수를 사용할 수 있다.

 

getline을 문자열 객체와 함께 사용하는 구문은 9.1절에서 C-string에 대해 설명한 것과 약간 다르다.
cin.getline을 사용하지 않고 cin을 getline의 첫 번째 인수로 사용한다.
(따라서 이 버전의 getline은 멤버 함수가 아니다.)
string line;
cout << "Enter a line of input:\n";
getline(cin, line);
cout << line << "END OF OUTPUT\n";

전체 대화문)
Enter some input:
Do be do to you!
Do be do to you!END OF OUTPUT
선 위에 선두 또는 후행 빈칸이 있다면, 그들 역시 getline이 읽는 string 값의 일부가 될 것이다.

이 버전의 getline은 라이브러리 <string>에 있다.
(12장에서 살펴보겠지만,

cin 대신 텍스트 파일에 연결된 스트림 객체를 사용하여

getline을 사용하여 파일에서 입력을 수행할 수 있다.)

cin과 >>을 빈 문자로 읽는 것은 사용할 수 없다.

 

한 번에 하나의 문자를 읽고 싶다면 9.2절에서 설명한 cin.get을 사용할 수 있다.
cin.get 함수는 문자열 유형이 아닌 char 유형의 값을 읽지만 문자열 입력을 처리할 때 유용할 수 있다.

 

디스플레이 9.5에는 문자열 입력에 사용되는 getline과 cin.get을 모두 보여주는 프로그램이 포함되어 있다.

함수 newline의 중요성은 “Pitfall: Mixing cin >> variable ; and getline .”이라는 제목에 설명되어 있다.

 

문자열 개체가 있는 I/O
삽입 연산자 << 와 cout을 사용하여 문자열 객체를 출력할 수 있다.

추출 연산자 >와 cin으로 문자열을 입력할 수 있다.

입력을 위해 >>를 사용할 때 코드는 공백으로 구분된 문자열로 읽힌다.

문자열 객체에 텍스트의 전체 행을 입력하기 위해 getline 함수를 사용할 수 있다.

EXAMPLES
string greeting("Hello"), response, nextLine;
cout << greeting;
cin >> response;
getline(cin, nextLine);

 

디스플레이 9.5 string 클래스를 이용한 프로그램

//Demonstrates getline and cin.get.
#include <iostream>
#include <string>
using namespace std;

void newLine( );
int main( )
{
	string firstName, lastName, recordName;
	string motto = "Your records are our records.";    
	cout << "Enter your first and last name:\n";
	cin >> firstName >> lastName;
	newLine( );
    
	recordName = lastName + ", " + firstName;    
	cout << "Your name in our records is: ";
	cout << recordName << endl;
	cout << "Our motto is\n"
	     << motto << endl;
	cout << "Please suggest a better (one line) motto:\n";
	getline(cin, motto);
	cout << "Our new motto will be:\n";
	cout << motto << endl;
	return 0;
}
//Uses iostream:
void newLine( )
{
	char nextChar;
	do
	{
		cin.get(nextChar);
	} while (nextChar != '\n');
}

 

샘플 대화 상자

Enter your first and last names:
   B'Elanna Torres
Your name in our records is:
Torres, B'Elanna
Our motto is
Your records are our records.
Please suggest a better (one-line) motto:
Our records go where no records dared to go before.
Our new motto will be:
Our records go where no records dared to go before.

 

팁: getline의 추가 버전

지금까지 getline을 사용하는 방법에 대해 다음과 같이 설명했다:
string line;
cout << "Enter a line of input:\n";
getline(cin, line);
이 버전은 줄 끝 표시인 '\n'을 만나면 읽기를 중지한다.

 

중지 신호로 사용할 다른 문자를 지정할 수 있는 버전이 있다.
예시)

첫 번째 물음표가 나타나면 다음이 중지된다:
string line;
cout << "Enter some input:\n";
getline(cin, line, '?');

 

getline을 void 함수인 것처럼 사용하는 것이 타당하지만, 

실제로는 첫 번째 인수인 이전 코드의 cin에 대한 참조를 반환한다.

따라서 다음은 텍스트 행에서 s1로, 공백이 아닌 문자의 문자열을 s2로 읽는다:
string s1, s2;
getline(cin, s1) >> s2;
호출 getline(cin, s1)은 cin에 대한 참조를 반환하므로 

getline 호출 후 다음에 일어날 일은 다음과 같다
cin >> s2;

 

함정: cin >> variable;과 getline을 혼합하기

cin >> variable ;을 사용한 입력과 getline을 사용한 입력을 혼합할 때 주의하자.

 

예시)
int n;
string line;
cin >> n;
getline(cin, line);

 

이 코드가 다음 입력을 읽을 때 

n 값은 42로 설정되고 

행 값은 "Hello hitchhiker"를 나타내는 문자열 값으로 설정될 것으로 예상할 수 있다:
42
Hello hitchhiker.

 

하지만, n이 실제로 42의 값으로 설정되어 있는 반면,

줄은 빈 줄과 동일하게 설정되어 있다.

어떻게 된 것일까?
cin >> n을 사용하면 입력의 선두 공백을 건너뛰지만 

나머지 줄은 다음 입력을 위해 '\n'만 남긴다.

다음과 같은 문장은
cin >> n;
항상 다음 getline이 읽을 수 있도록 줄에 무엇인가를 남긴다. (단, '\n'인 경우에도).

이 경우 getline은 '\n'을 보고 읽기를 중지하므로 getline은 빈 문자열을 읽는다.

 

프로그램이 이상하게도 입력 데이터를 무시하는 것처럼 보인다면,

이 두 종류의 입력을 혼합했는지 확인하라.

디스플레이 9.5의 newLine 함수나 라이브러리 iostream의 함수 ignore를 사용해야 할 수 있다.

예를 들어,
cin. ignore(1000, '\n');
이러한 인수를 사용하면 ignore 멤버 함수에 대한 호출은

'\n'까지의 나머지 행 전체를 읽고 폐기한다


>>와 getline 둘 다에서 cin을 사용하는 프로그램들에서는 다른 당황스러운 문제들이 나타날 수 있다.

더욱이, 이런 문제들은 C++ 컴파일러들 사이에서 다른 컴파일러들 사이로 이동하면서 오고 갈 수 있다.

다른 모든 문제들이 실패하거나, 휴대성을 확신하고 싶을 때, cin.get을 사용하여 문자별 입력에 의존할 수 있다.
이러한 문제는 이 장에서 논의하는 getline의 모든 버전에서 발생할 수 있다.

 

클래스 문자열의 객체에 대한 getline
문자열 개체에 대한 getline 함수에는 다음 두 가지 버전이 있다:
istream& getline(istream& ins, string& strVar, char delimiter);
and
istream& getline(istream& ins, string& strVar);
함수의 첫 번째 버전에서는

첫 번째 인수로 주어진 istream 개체(이 장에서는 항상 cin)에서 문자를 읽으며,

구분 기호 문자의 인스턴스가 발생할 때까지 문자열 변수 strVar에 문자를 삽입한다.

구분 기호 문자는 입력에서 제거되어 폐기된다.

함수의 두 번째 버전에서는

구분 기호의 기본값으로 '\n'을 사용하지만 그렇지 않으면 동일하게 작동한다.
이러한 getline 함수는 첫 번째 인수(이 장에서 항상 cin)를 반환하지만, 일반적으로 void 함수인 것처럼 사용된다.

 

string 클래스를 사용한 string 처리

클래스 문자열을 사용하면 9.1절 이상에서 설명한 C-string으로 수행할 수 있는 것과 동일한 작업을 수행할 수 있다.

배열 요소에 접근하는 것과 동일한 방식으로 문자열 객체의 문자에 접근할 수 있으므로 

문자열 객체는 문자 배열의 장점과 자동으로 용량이 증가하는 등 배열에 없는 여러 가지 장점이 있다.
lastName이 문자열 객체의 이름인 경우 

lastName[i]에서는 lastName으로 표시되는 문자열의 i번째 문자에 접근할 수 있다.

이러한 배열 대괄호 사용은 디스플레이 9.6에 나와 있다.

 

length
디스플레이 9.6은 멤버 함수 length도 보여준다.

length 멤버 함수: 인수를 사용하지 않고 문자열 객체가 나타내는 스트링의 길이를 반환한다.

따라서 문자열 객체는 배열처럼 사용될 수 있을 뿐만 아니라

length 멤버 함수를 사용하면 몇 개의 위치가 차지하는지

자동으로 추적하는 부분적으로 채워진 배열처럼 동작한다.

 

at 함수

at이라는 이름의 멤버 함수는 기본적으로 대괄호와 동일하게 동작한다:

at 함수 표기법을 사용하므로 a[i] 대신 a.at (i)를 사용한다.

at 멤버 함수: 내가 잘못된 인덱스로 평가하는지 확인한다.

a.at (i)의 i 값이 잘못된 인덱스이면 무엇이 잘못되었는지 알려주는 런타임 오류 메시지를 받아야 한다.

 

예시)

다음 코드 조각에서 접근 시도가 범위를 벗어났지만

존재하지 않는 인덱스 변수에 접근할 것이지만 오류 메시지를 생성하지는 않을 것이다:str("Mary");
cout << str[6] << endl;

 

예시)

다음 코드 조각에서

프로그램이 비정상적으로 종료되어 적어도 무언가가 잘못되었다는 것을 알 수 있다:

str("Mary");
cout << str.at (6) << endl;


그러나 a.at (i)에 잘못된 인덱스 i가 있을 때

일부 시스템은 매우 불량한 오류 메시지를 제공하므로 주의하자.
str[i]와 같이 색인화된 변수에 문자 값을 할당하여 문자열의 단일 문자를 변경할 수 있다.

멤버 함수 at는 참조를 반환하므로 멤버 함수 at에서도 이 작업을 수행할 수 있다.

 

예시)

문자열 객체 str의 세 번째 문자를 'X'로 변경하려면 다음 코드 조각 중 하나를 사용할 수 있다:
str.at (2)='X';
아니면
str[2]='X';
일반적인 문자 배열과 마찬가지로 유형 문자열의 개체에 대한 문자 위치가 0으로 시작하여 

문자열의 세 번째 문자가 색인 위치 2에 있도록 색인화된다.


디스플레이 9.7은 클래스 문자열의 멤버 함수의 일부 목록을 제공한다.
많은 면에서 클래스 문자열의 개체는 9.1절에서 소개한 C 문자열보다 더 잘 작동한다.

특히 문자열 클래스의 개체에 대한 == 연산자는

문자열이 동일하다는 직관적인 개념에 해당하는 결과를 반환한다.

즉, 두 문자열에 동일한 문자가 같은 순서로 포함되어 있으면 true로 반환하고 그렇지 않으면 false로 반환한다.

마찬가지로 비교 연산자 <, >, <= 및 >=는 사전 순서를 사용하여 문자열 객체를 비교한다.

(사전 순서(Lexicographic ordering): 부록 3의 ASCII 문자 집합에 주어진 기호 순서를 사용한 알파벳 순서

문자열이 모든 문자로 구성되고 모두 대문자 또는 소문자인 경우

이 경우 사전 순서는 일상적인 알파벳 순서와 동일하다.)

 

디스플레이 9.6 string 객체는 배열 같이 행동한다

//Demonstrates using a string object as if it were an array.
#include <iostream>
#include <string>
using namespace std;

int main( )
{
	string firstName, lastName;
    
	cout << "Enter your first and last name:\n";
	cin >> firstName >> lastName;
    
	cout << "Your last name is spelled:\n";
	int i;
	for (i = 0; i < lastName.length( ); i++)
	{
		cout << lastName[i] << " ";
		lastName[i] = '-';
	}
	cout << endl;
	for (i = 0; i < lastName.length( ); i++)
		cout << lastName[i] << " "; //Places a "-" under each letter.
	cout << endl;
    
	cout << "Good day " << firstName << endl;
	return 0;
}

 

샘플 대화 상자

Enter your first and last names:
John Crichton
Your last name is spelled:
C r i c h t o n
- - - - - - - -
Good day John

 

디스플레이 9.7 표준 클래스 string의 멤버 함수

Example Remarks
생성자  
string str; 기본 생성자; 빈 문자열 개체 str을 만듭니다.
string str("string"); 데이터 "string"으로 문자열 개체를 만듭니다.
string str(aString); 문자열의 복사본인 문자열 개체 문자열을 만듭니다. aString은 클래스 문자열의 개체입니다.

 

요소 접근  
str[i] str 인덱스 i의 문자에 대한 읽기/쓰기 참조를 반환합니다.
str.at(i) str 인덱스 i의 문자에 대한 읽기/쓰기 참조를 반환합니다.
str.substr(position, length) position에서 시작하고 length 문자가 있는 호출 개체의 부분 문자열을 반환합니다.

 

할당/수정자  
str1 = str2; str2의 데이터에 공간을 할당하여 초기화하고 str1에 할당된 메모리를 해제하고 str1의 크기를 str2의 크기로 설정합니다.
str1 += str2; str2의 문자 데이터는 str1의 끝에 연결되며, 크기는 적절하게 설정됩니다.
str.empty( ) str이 빈 문자열이면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
str1 + str2 str1의 데이터 끝에 str2의 데이터가 연결된 문자열을 반환합니다. 크기는 적절하게 설정됩니다.
str.insert(pos, str2) str2를 위치 pos에서 시작하는 str에 삽입합니다.
str.remove(pos, length) 위치 pos에서 시작하여 크기 길이의 부분 문자열을 제거합니다.

 

비교  
str1 == str2 
str1 != str2
같음 또는 같음을 비교합니다. 부울 값을 반환합니다.
str1 < str2
str1 > str2
네 개의 비교. 모두 사전적 비교입니다.
str1 <= str2
str1 >= str2
 
str.find(str1) str1의 첫 번째 발생에 대한 인덱스를 반환합니다.
str.find(str1, pos) str에서 문자열 str1의 첫 번째 발생에 대한 인덱스를 반환합니다. 검색은 위치 pos에서 시작됩니다.
str.find_first_of(str1, pos) str1에서 모든 문자의 첫 번째 인스턴스 str의 인덱스를 반환하고 위치 pos에서 검색을 시작합니다.
str.find_first_not_of(str1, pos) str1에 포함되지 않은 문자의 첫 번째 인스턴스 인덱스를 반환하고 위치 pos에서 검색을 시작합니다.

 

= 및 == strings과 C-string의 차이
표준 C++형 문자열과 함께 사용하면

=, ==, !=, <, >, <=, <=, 그리고 <results> 연산자는

문자열이 비교되는 방식에 대한 우리의 직관적인 개념과 일치하는 결과를 가져온다.

9.1절에서 설명한 것처럼 C 문자열과 마찬가지로 잘못된 행동을 하지 않는다.

 

예: 회문 테스트하기

회문(palindrome): 앞에서 뒤로 읽을 때와 같은 문자열

디스플레이 9.8의 프로그램은 입력된 문자열이 회문인지 확인하기 위해 테스트한다.

회문 테스트는 모든 공백과 구두점을 무시하고 어떤 것이 회문인지 결정할 때 대문자와 소문자 버전이 동일하다고 간주한다.

 

예시)
Able was I 'ere I saw Elba.
I Love Me, Vol. I.
Madam, I'm Adam.
A man, a plan, a canal, Panama.
Rats live on no evil star.
radar
deed
mom
racecar


removePunct 함수는 문자열 멤버 함수 substr과 find를 사용한다는 점에서 흥미롭다.

멤버 함수 substr: 원하는 부분 문자열의 위치와 길이가 주어지면 호출하는 객체의 부분 문자열을 추출한다.

removePunct의 처음 세 줄은 함수에 사용할 변수를 선언한다.

for문은 매개변수 s의 문자를 한 번에 하나씩 통과하여 punct 문자열에서 찾으려 한다.
이를 위해 각 문자 위치에서 길이가 1인 s의 부분 문자열을 추출한다.

이 부분 문자열은 find 멤버 함수를 사용하여 punct의 위치를 결정한다.

이 한 문자 문자열이 punct 문자열에 없으면 반환되는 noPunct 문자열에 연결된다.

 

디스플레이 9.8 회문 테스트 프로그램

//Test for palindrome property.
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
void swap( char& v1, char& v2);
//Interchanges the values of v1 and v2.
string reverse( const string& s);
//Returns a copy of s but with characters in reverse order.

string removePunct( const string& s, const string& punct);
//Returns a copy of s with any occurrences of characters
//in the string punct removed .

string makeLower ( const string& s);
//Returns a copy of s that has all uppercase
//characters changed to lowercase, with other characters unchanged.

bool isPal( const string& s);
//Returns true if s is a palindrome; false otherwise.

int main( )
{
	string str;
	cout << "Enter a candidate for palindrome test\n"
	     << "followed by pressing Return.\n";
	getline(cin, str);
    
	if (isPal(str))
		cout << "\"" << str + "\" is a palindrome.";
        
	else
		cout << "\"" << str + "\" is not a palindrome.";
	cout << endl;
    
	return 0;
}

void swap( char& v1, char& v2)
{
	char temp = v1;
	v1 = v2;
	v2 = temp;
}

string reverse( const string& s)
{
	int start = 0;
	int end = s.length( );
	string temp(s);
    
	while (start < end)
	{
		end--;
		swap(temp[start], temp[end]);
		start++;
	}
    
	return temp;
}

//Uses <cctype> and <string>
string makeLower( const string& s)
{
	string temp(s);
	for ( int i = 0; i < s.length( ); i++)
		temp[i] = tolower(s[i]);

	return temp;
}

string removePunct( const string& s, const string& punct)
{
	string noPunct; //initialized to empty string
	int sLength = s.length( );
	int punctLength = punct.length( );
	for ( int i = 0; i < sLength; i++)
	{
		string aChar = s.substr(i,1); //A one-character string
		int location = punct.find(aChar, 0);
		//Find location of successive characters
		//of src in punct.
        
		if (location < 0 || location >= punctLength)
			noPunct = noPunct + aChar; //aChar is not in punct, so keep it
	}

	return noPunct;
}

//uses functions makeLower, removePunct
bool isPal( const string& s)
{
	string punct(",;:.?!'\" "); //includes a blank
	string str(s);
	str = makeLower(str);
	string lowerStr = removePunct(str, punct);

	return (lowerStr == reverse(lowerStr));
}

 

샘플 대화 상자

Enter a candidate for palindrome test
followed by pressing Return.
Madam, I'm Adam.
"Madam, I'm Adam." is a palindrome.
Enter a candidate for palindrome test
followed by pressing Return.
Radar
"Radar" is a palindrome.
Enter a candidate for palindrome test
followed by pressing Return.
Am I a palindrome?
"Am I a palindrome?" is not a palindrome.

 

string 객체와 C-String 간 변환

C++가 C-string을 유형 문자열 변수에 저장할 수 있도록

자동 형변환을 수행한다는 것을 이미 확인했다.

예를 들어, 다음은 정상적으로 작동한다:
char aCString[] = "This is my C-string.";
string stringVariable;
stringVariable = aCString;
그러나 다음은 컴파일러 오류 메시지를 생성한다:
aCString = stringVariable; //ILIGNAL
다음은 또한 불법이다:
strcpy(aCString, stringVariable); //ILIGAL
strcpy는 문자열 객체를 두 번째 인수로 사용할 수 없으며 

문자열 객체를 C-string으로 자동 변환할 수 없다.

이것은 우리가 벗어날 수 없는 문제이다.

 

c_str()

문자열 객체에 해당하는 C- 문자열을 얻으려면 명시적인 변환을 수행해야 한다.

문자열 멤버 함수 c_str( )을 사용하면 이 작업을 수행할 수 있다.

우리가 시도한 복사의 올바른 버전은 다음과 같다:
strcpy(aCString, stringVariable.c_str( )); //Legal;

 

복사를 하려면 strcpy 함수를 사용해야 한다.

멤버 함수 c_str( )는 문자열 호출 개체에 해당하는 C- 문자열을 반환한다.

이 장에서 앞에서 언급했듯이 할당 연산자는 C- 문자열을 사용하지 않는다.

따라서 다음이 작동할 수 있다고 생각한 경우를 대비하여 이것 역시 불법임을 지적해야 한다.

aCString = stringVariable.c_str( ); //ILLEGAL