문자열
String
- 메모리에 Byte 단위로 일련의 문자들이 저장되어 있는 형태이다.
- 문자열은 함수에 매개변수로써 전달될 때, 복사값이 아닌 주소가 전달된다.
(문자열이 저장된 배열의 끝엔 항상 NULL 문자가 배치되어 있으므로 문자열의 크기까지 함수에 알려줄 필요가 없다.)
- C++에서는 문자열을 두 가지 방식으로 처리한다.
1. C Style : char type array
- char type array에 문자들을 순서대로 저장시키고 마지막에 Null Character ('\0', ASCII code 가 0)을 저장함으로써 이 배열의 문자들이 문자열을 구성하고 있음을 표시한다.
- cout 객체가 사용하는 함수들을 포함해서, C++에서는 문자열을 처리하는 다양한 함수들을 지원하는 데, 이들 함수 모두는 Null Character를 만날 때 까지 문자 단위로 문자열을 처리한다. (Null Character는 문자열임을 의미하는 중요한 역할을 수행한다.)
- cout의 경우, 출력할 때 Null Character를 만나면 출력을 중단한다.
- 따라서, 널 문자를 마지막으로 저장하지 않고 cout으로 출력할 경우, 남은 array공간까지 출력하게 되며, 우연히 Null Character를 만나기 전까지 array의 크기에 따라 출력을 계속한다.(이를 이용해서 배열내의 문자들을 수정하지 않고 출력을 제어할 수 있다.)
다만, 메모리 값이 0으로 설정되는 경우는 아주 흔하기 때문에 대부분 출력이 오래가지는 않다.
- cin으로 문자열을 입력받을 시, 배열의 이름을 매개변수로 사용한다.
const int SIZE = 15;
char name[SIZE];
cout << "Your name : ";
cin >> name;
- C Style로 문자열을 처리하는 경우에서, 문자열 자체를 리턴하는 함수를 구현할 수 없다.
(대체방안으로 문자열의 주소를 리턴할 수 있다.)
* String Constant (문자열 상수, String Literal)
- 큰 따옴표 ("", double quotes)로 둘러싸인 문자열
- 큰 따옴표로 묶인 문자열은 끝내기 널 문자를 암시적으로 갖고 있으므로 문자열 끝에 널 문자를 명시적으로 넣을 필요가 없다.
- 문자열 상수는 그 자체로 문자열이 저장된 메모리 공간의 주소를 나타내므로 배열 이름에 바로 대입이 가능하다.
char bird[10] = "Mr. Cheeps";
// String Constant를 이용한 배열 초기화
char fish[] = "Bubbles";
// 컴파일러가 널 문자까지 들어갈 공간을 자동으로 걔산하여 배열의 크기를 부여한다.
// 더욱 안전하고 권장되는 방법
// 문자열 상수로 초기화되고 남는 메모리 공간은 자동으로 널 문자로 초기화된다.
* 문자열 내의 문자들을 처리하는 전형적인 루프 형태
while (*str) { // NULL 문자의 수치코드는 0이다.
statement(s)
str++; // 다음 원소(다음 문자)를 가리키게 된다.
}
* 문자 상수와 문자열 상수의 차이
- single quote 와 double quotes
- 문자 상수는 문자에 해당하는 수치 코드를 내재하고 있고, 문자열 상수는 해당 문자열이 저장되어 있는 메모리 공간의 주소를 내재하고 있다.
char shirt_size = 'S';
// 올바른 구문
char shirt_size = "S";
// 틀린 구문
// shirt_size는 char type 변수이고 "S"는 S 라는 문자열이 저장되어 있는 메모리의 주소이다.
// 두 변수 및 상수의 data type이 일치하지 않으므로 오류가 발생한다. (데이터형 불일치)
* 문자열 상수의 결합
- 문자열의 길이가 너무 길어 한 행에 표현할 수 없을 경우(소스코드의 가독성을 높이기 위해), 큰 따옴표를 이용해 분리할 수 있다.
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right arm " "to be a great violinist.\n";
cout << "I'd give my right"
" arm to be a great violinist.\n";
// 위 세 개의 구문은 다 같은 출력형태를 보인다.
* String Function
- strlen() : string의 길이를 출력 (널문자는 제외하고 문자들의 개수만 헤아려 출력함, cstring 헤더파일)
- strcpy(a, b) : a에 b를 복사하는 함수이다. C++에서는 문자열을 복사 혹은 대입 시에 = 연산자보다는 strcpy() 혹은 strncpy() 함수를 이용할 것을 권고한다.
strcpy()는 대입될 문자열 배열의 메모리 공간이 부족할 경우, 배열의 크기를 벗어나는 나머지 부분을 배열에 바로 이어서 저장하기 떄문에 프로그램이 사용하고 있는 다른 내용을 덮어씌울 가능성이 있다. 그러므로 프로그래머는 이 점을 유념하여 코드를 작성해야 한다. (이에 대한 대안으로 strncpy()가 개발되었다.)
- strncpy(a, b, n) : 세 번째 매개변수까지 사용하여 복사할 최대 문자 수를 지정받음으로써 메모리를 잘못 덮어 씌울 가능성을 낮춰서 안정성을 높이지만 그만큼 복잡도도 증가시키게 된다. 이 함수는 문자열을 끝까지 복사하기 전에 저장 공간이 바닥이 나는 경우에는 NULL을 추가하지 않고 대입하게 되므로(문자열로 인식을 못하게 되는 문제가 생긴다.) 이 점에 유의해야 한다.
- strchr(a, b) : 문자열 a에서 문자값 b가 처음 발견되는 지점의 메모리 주소를 리턴한다. 문자값 b가 발견되지 않으면 NULL 포인터를 리턴한다.
cin object
- 사용자로부터 데이터를 입력받는 데 사용하는 istream 클래스의 객체
- 화이트스페이스(빈칸, 탭, 캐리지 리턴 등)를 만나면 그 위치에서 문자열이 끝난 것으로 간주한다.
- 즉, 문자열을 입력해서 처음 한 단어만 입력받고 나머지 문자열은 입력 큐(input queue)에 그대로 남겨 놓는다.
member function
1. cin.getline(arrayName, stringSize)
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
- 행 단위로 문자열을 입력받는 멤버함수 (행 단위로 입력 = 개행 문자가 나올 때 까지 읽어들임)
- Enter 키에 의해 전달되는 개행 문자를 입력의 끝으로 간주하여 한 행 전체를 읽는다.
- 행의 끝을 표시하는 개행문자까지 포함하여 읽어들이나(개행문자를 입력 큐에 남겨놓지 않음), 문자열을 배열에 저장할 때, 개행 문자는 널 문자로 대체되어 저장된다.
- 입력한 행을 저장할 배열의 이름과 입력받을 문자 수의 한계치를 매개변수로 받는다. (세 번째 매개변수도 있음)
- 예를 들어, stringSize 매개변수로 20을 입력받았을 경우, 널문자를 저장할 1개의 공간을 제외한 최대 19개의 문자를 읽어 들일 수 있다.
* getline(cin, str);
- 입력값을 찾아올 cin
- 입력값을 저장할 str
+ istream 클래스는 string 클래스가 생겨나기 전부터 존재했던 클래스였고, istream 클래스에는 기본 데이터형(int, double 등)을 처리하는 멤버함수는 있지만 string 객체를 직접적으로 처리하는 멤버함수는 없다.
따라서, cin의 멤버함수가 아닌 독립적인 getline() 함수의 사용은 istream의 멤버함수 getline()이 string 클래스로 grant된 friend 함수로 구현되어 사용이 가능해진 형태이다.
2. cin.get(arrayName, stringSize)
- getline 함수와 똑같은 매개변수 체계를 가지며, 입력의 한 행 전체를 읽어들인다.
- getline 함수와 달리, 행의 끝임을 의미하는 개행 문자를 읽고난 후 버리지 않고 입력 큐에 남겨놓는다.
(흔히 사용하는 cin을 통한 입력 또한, 개행문자를 입력 큐에 남겨놓는다.)
- 따라서, get 함수를 연속적으로 사용하여 문자열을 입력하려는 경우, 특별한 조치를 취하지 않는 한, get 함수는 개행 문자를 넘어갈 수가 없고, 원하는 대로 입력이 진행되지 않을 수 있다.
- get함수의 경우, 매개변수를 사용하지 않고 함수를 호출할 수 있는데( cin.get() ), 이는 처음으로 만나는 문자가 개행 문자건 아니건 상관없이 읽어서 처리한다.
Example. Usage for get() function
// 잘못된 예시 -------------------------------------------------
cin.get(name, ArSize);
cin.get(dessert, ArSize);
// get 함수의 연속적 사용으로 인해 입력에 오류가 생길 수 있다.
// 옳은 예시 ---------------------------------------------------
cin.get(name, ArSize);
cin.get(); // 단어 간 문자(개행문자)를 읽어서 버림
cin.get(dessert, ArSize);
// get 함수를 연달아 사용해도 오류가 일어나지 않는다.
// 옳은 예시 ---------------------------------------------------
cin.get(name, ArSize).get();
// 멤버 함수들을 결합시킴
// cin.get(name, ArSize)는 입력을 처리하고 cin 객체를 그 자리에 return함
cin.get(dessert, ArSize);
* getline() 대신 get() 을 사용하는 이유
- 구버전 C++에는 getline() 가 정의되어 있지 않다.
- getline()은 프로그래머가 사용하기 쉽지만 에러 체킹에 약하고, get()은 비교적으로 에러 체킹에 강하다.
ex) 배열에 한 행을 저장하기 위해 get()을 사용했다고 했을 때, 입력한 문자열 전체가 성공적으로 저장되었는지를 확인하려면, 입력이 진행된 후 입력 큐에 남아있는 문자를 확인했을 때 개행문자이면 그 행 전체를 다 읽은 것이고, 아니라면 읽을 문자가 더 남아있다는 증거이다.
* 빈 행이 입력될 경우의 문제
- get()이 빈 행을 입력받을 경우, failbit가 설정되는데, 이는 계속되는 입력을 막는 역할을 한다.
- 이를 해결하기 위해선 cin.clear(); 명령을 이용해야 한다.
cin.clear();
// failbit 를 제거하는 구문
* 설정한 array의 크기보다 더 큰 입력이 들어올 경우
- get()의 경우, 큰 입력이 들어오면 지정된 개수의 문자들은 성공적으로 저장하고 나머지 문자들은 입력 큐에 그대로 남겨놓는다.
- getline()의 경우, 큰 입력이 들어오면 마찬가지로 지정된 개수의 문자들은 저장하고 나머지 문자들에 대해서는 failbit를 설정하고 더 이상의 입력을 받지 않는다.
2. C++ Style : string object
- ISO/ANSI C++ 표준에서 추가된 클래스
- 문자열도 하나의 data type 으로 간주하고 처리하는 개념
- string 객체를 사용하기 위해선 <string> 헤더파일을 포함시켜야 한다.
- string 클래스는 std namespace 에 포함되어 있으므로 using 지시자 혹은 std::string을 통해 이름공간을 구분 지어주어야 한다.
- 콘솔 상에서는, cin을 통해 입력받고, cout을 통해 출력한다.
- 배열 표기를 이용하여 string 객체에 저장되어 있는 개별적인 문자들에 대해 접근할 수 있다.
(C Style 문자열과는 달리, 문자열의 끝을 인식하기 위해 NULL문자를 사용하지는 않는다.)
- string 객체는 문자를 저장할 공간의 크기를 필요에 맞게 적절하게 조절하는 기능이 있다.
(기본적으로 배열에 문자들을 저장하는 방식은 C와 동일하므로 이 방식이 필수적이다.)
Ex. \(\texttt{string}\) 객체에 배열 표기를 적용한 예시
#include <iostream>
#include <string>
int main(){
using namespace std;
string name = "C style initialize";
// C style 초기화가 가능
cin >> name;
cout << "3rd character : " << name[2] << endl;
// string 객체의 배열 표기
cout << "Whole character : " << name << endl;
// cin, cout 을 통한 입출력이 가능
return 0;
}
- 간결하게 대입이 불가능한 array type과 달리, string 클래스는 string 객체 간 대입을 지원한다.
char nameA[10] = "Lee";
char nameB[10];
nameB = nameA; // Compile Error!
string nameC = "Kim";
string nameD;
nameD = nameC; // Allowed!
- string 클래스는 + 연산자 및 += 연산자를 지원하여 문자열들을 결합하는 구문을 직관적이고 쉽게 작성할 수 있게 한다.
string str3;
str3 = str1 + str2; // str1, str2 는 각각 특정 문자열로 초기화 된 string object라 가정
str1 += str2; // str1 = str1 + str2; 와 동치
str1 += "Hello"; // str1 = str1 + "Hello"; 와 동치
* \(\texttt{string}\) 클래스는 \(\texttt{char*}\) 형을 \(\texttt{string}\)으로 변환하는 것을 정의하고 있어서 \(\texttt{string}\) 객체를 C 스타일 문자열로 초기화하는 것을 허용한다.
- 형식 매개변수가 \(\texttt{const string&}\) 형이면, \(\texttt{string}\) 객체, 문자열 리터럴, \(\texttt{char}\)형 문자열 배열, \(\texttt{char}\)형 문자열 배열 등과 같은 문자열과 관련된 데이터형들이 Argument로 올 수 있다.
C++11 Style Initialization
- C+11 에서는 array type string 과 string object에 대해 모두 리스트 초기화를 지원한다.
char fist_data [] = {"Le Chapon Dodu"};
string third_date = {"The Bread Bowl"};
문자열 조작함수
// C++ Style : <string> 헤더파일 필요
// C Style : <cstring> 헤더파일 필요
char charr1[20];
char charr2[20] = "jaguar";
string str1;
stinrg str2 = "panther";
// 문자열 대입
str1 = str2;
strcpy(charr1, charr2);
// 문자열 접합 (Concatenate)
str += " paste";
strcat(charr1, " juice");
// 문자들의 개수 반환
int len1 = str1.size();
int len2 = strlen(charr1);
// strlen() : 첫 번째 원소에서 NULL 문자에 도달할 때 까지의 Byte 수를 셈
strcpy(), strcat() 과 같은 C Style 문자열 함수들은 목적지 배열의 크기를 정확히 알고 있지 않을 경우, 메모리 문제가 발생할 위험이 항상 잔재한다.
목적지 배열의 크기가 상대적으로 작을 경우, 문자열 연산시에 인접한 메모리를 침범하게 되고 이는 프로그램을 먹통으로 만들거나 잘못된 데이터로 인해 논리 오류를 야기한다.
이에 반해, string 클래스를 이용한 문자열 연산은 배열의 크기를 자동적으로 조정하여 이와 같은 종류의 문제에 대처한다.
이 문제를 개선하기 위해 cstring 헤더파일에서는 strncat(), strncpy() 함수를 지원하는 데, 이 함수들을 배열의 최대 허용 크기를 나타내는 세 번째 매개변수를 사용하여 이러한 문제에 대처하지만 매개변수가 추가된다는 것은 프로그램 작성의 복잡함이 증가함을 의미한다.