c언어에서 쓰는 cstring은 문자열이 아니라 문자(char)의 배열인데 끝에 '\0'(널)로 막아줘야 하는 등 불편하고 문제가 많아서 C++에서는 string 클래스를 사용해서 문자열을 처리한다.
string은 int, double과 같은 기초타입(basic type)이 아니라 클래스이다.
따라서 string 클래스를 사용하고 싶으면 #include <string> 문장을 써서 <string> 헤더파일을 포함해야 한다.
string 클래스는 문자의 배열을 멤버변수로 가지며, 그것을 처리하는 함수들을 멤버함수로 가지고 있다.
아래의 두 문장은 결과적으로는 같지만 첫 번째 문장이 더 빠르다.
string s("Welcome to c++");
string s = "Welcome to c++";
첫 번째 문장은 바로 문자열 객체를 생성하는 것이지만
두 번째 문장이 동작하는 과정은 다음과 같다.
string은 클래스이기 때문에 생성자가 있다. 두 번째 문장의 경우 string s; 이렇게 선언한 것과 같으므로 string 클래스의 인자 없는 생성자가 호출된다. 따라서 일단 빈 문자열을 가진 객체가 생성된다.
그러고 나서 "Welcome to c++"라는 리터럴을 리터럴 저장소에서 복사해서 가져오는 것이다.
따라서 두 문장은 결과적으로는 같지만 내부적으로 동작되는 것은 다르다.
하지만 요즘은 내부적으로 optimizing(최적화) 시키기 때문에 시간 차이가 거의 없다고 한다.
문자열 추가
(참고로 UML 클래스 다이어그램에서 +는 public을, -는 private을 뜻한다.)
+append(s :string): string | 문자열 s를 string 객체에 추가 |
+append(s: string, index: int, n: int): string | 문자열 s를 index 위치에서 n개의 문자를 string에 추가 |
+append(s: string, n: int): string | 문자열 s의 처음부터 n개의 문자를 string에 추가 |
+append(n: int, ch: char): string | n개의 문자 ch를 string에 추가 |
①append(s :string)
string s1("Welcome");
s1.append(" to C++");
cout << s1 << endl; // Welcome to C++ 출력
②append(s: string, index: int, n: int)
string s2("Welcome");
s2.append(" to C and C++", 0, 5);
cout << s2 << endl; // Welcome to C 출력
여기서 헷갈리지 말아야 할 것은
s2.append(문자열, 0, 5");는 0번째 인덱스에서 시작해서 5개를 추가한다. ("0번째 인덱스부터 5번째 인덱스까지"가 아님)
③append(s: string, n: int)
string s3("Welcome");
s3.append(" to C and C++", 5);
cout << s3 << endl;
s3.append(문자열, 5);는 처음부터 5개를 s3에 추가하는 것이다. 즉 s3.append(문자열, 0, 5);와 같다.
④append(n: int, ch: char)
string s4("Welcome");
s4.append(4, 'G');
cout << s4 << endl;
이 함수에서 두 번째 인자는 char 타입이어야 한다. 'G'는 char 타입의 문자이지만 "G"는 string 클래스의 문자열이다.
작은따옴표는 char(문자), 큰 따옴표는 string(문자열)이라는 것 잊지 말자.
문자열 대입
+assign(s[]: char): string | 문자 배열 또는 문자열 s를 string 객체에 대입 |
+assign(s: string, index: int, n: int): string | 문자열 s의 index 위치에서 n개의 문자를 string 객체에 대입 |
+assign(s: string, n: int): string | 문자열 s의 처음부터 n개의 문자를 string 객체에 대입 |
+assign(n: int, ch: char): string | n개의 문자 ch를 string에 대입 |
①assign(s[]: char)
string s1("Welcome");
s1.assign("Dallas");
cout << s1 << endl; // Dallas 출력
②assign(s: string, index: int, n: int)
string s2("Welcome");
s2.assign("Dallas, Texas", 0, 5);
cout << s2 << endl; // Dalla 출력
③assign(s: string, n: int)
string s3("Welcome");
s3.assign("Dallas, Texas", 5);
cout << s3 << endl; // Dalla 출력
④assign(n: int, ch: char)
string s4("Welcome");
s4.assign(4, 'G');
cout << s4 << endl; // GGGG 출력
at, clear, erase, empty 함수
+at(index: int): char | 문자열로부터 index 위치의 문자를 반환 |
+clear(): void | 문자열의 모든 문자 제거 |
+erase(index: int, n: int): string | 문자열의 index 위치에서 시작해서 n개의 문자 제거 |
+empty(): bool | 문자열이 비어있으면 true 반환, 아니면 false 반환 |
string s1("Welcome");
cout << s1.at(3) << endl; // c 출력
cout << s1.erase(2, 3) << endl ; // Weme 출력
s1.clear(); // s1은 빈 문자열
cout << s1.empty() << endl ; // s1은 비어있으므로 1(true)을 출력한다.
length, size, capacity 함수
+length(): int | 문자열에서 문자의 개수를 반환 |
+size(): int | length()와 같음 |
+capacity(): int | 문자열에 할당된 저장 공간의 크기를 반환 |
+c_str(): char | 문자열에 대한 c문자열을 반환 |
+data(): char | c_str()과 같음 |
string s1("Welcome");
cout << s1.length() << endl; // 길이는 7
cout << s1.size() <<endl; // 크기는 7
cout << s1.capacity() << endl; // 용량은 15
s1.erase(1, 2); //s1은 Wcome가 됨
cout << s1.length() << endl; // 길이는 5
cout << s1.size() << endl; // 크기는 5
cout << s1.capacity() << endl; // 길이는 줄었지만 용량은 여전히 15
length()나 size()는 우리가 직접 글자 수를 세어보면 알 수 있지만 capacity()의 경우 직접 출력해보지 않으면 알 수 없다.
이 경우 운영체제가 15byte를 잡고 시작한 것이다. 규칙은 없고 OS가 여유있게 잡아놓고 나중에 필요하면 용량을 증가시킨다. 운영체제가 몇 바이트를 잡았는지는 문자열객체.capacity()로 직접 찍어봐야 알 수 있다.
문자열 비교 함수
+compare(s: string): int | 문자열 객체와 s를 비교해서 정수(-1, 0, 1)를 반환한다. |
두 개의 문자열 내용을 비교해야 할 때에는 compare() 함수를 사용한다.
두 개의 문자열을 비교하는데 왜 인자가 한 개일까?
문자열 객체 자기 자신과 매개변수로 들어온 문자열을 비교한다.
string s1("Welcome");
string s2("Welcomg");
cout << s1.compare(s2) << endl; // 앞이 작으므로 -1 반환
cout << s2.compare(s1) << endl; // 앞이 크므로 1 반환
cout << s1.compare("Welcome") << endl; // 같으므로 0 반환
문자열을 비교하는 방법은 앞에서부터 한 글자씩 비교하는 것이다.
s1.compare(s2);의 경우를 예로 살펴보자.
일단 s1의 0번 인덱스에 해당하는 문자인 'W'와 s2의 0번 인덱스에 해당하는 문자 'W'를 비교한다. 같다.
그러면 s1의 1번 인덱스의 'e'와 s2의 1번 인덱스 'e'를 비교한다. 같다.
그러면 또 다음 인덱스인 2번 인덱스를 비교한다.
그렇게 하다가 6번 인덱스를 비교할 차례이다.
s1의 6번 인덱스인 'e'와 s2의 6번 인덱스인 'g'를 비교한다. 'e'의 아스키코드 값은 101, 'g'의 아스키코드 값은 103이다.
101 - 103은 음수이다. 이렇게 객체 자신(이 경우 s1)이 인자로 들어온 문자열(이 경우 s2)보다 작을 경우 -1을 반환한다.
s2.compare(s1); 처럼 객체 자신이 인자로 들어온 문자열보다 클 경우 1을 반환한다.
s1.compare("Welcome"); 처럼 문자열 객체 자신과 인자로 들어온 문자열이 일치하면 0을 반환한다.
만약 헷갈리고 외우기가 어렵다면 앞의 것에서 뒤의 것을 뺀다고 생각하면 된다.
앞의 것이 더 크면 1, 뒤의 것이 더 크면 -1, 같으면 0.
부분 문자열 구하기
+substr(index: int, n: int): string | index 위치부터 n개의 문자열을 반환 |
+substr(index: int): string | index 위치부터 끝까지의 문자열을 반환 |
string s1("Welcome");
cout << s1.substr(0, 1) << endl; // W 출력(0위치부터 1개)
cout << s1.substr(5) << endl; // me 출력(5위치부터 끝까지)
cout << s1.substr(3, 3) << endl; // com 출력(3위치부터 3개)
여기서 헷갈리지 말아야 할 것은 s1.substr(5);은 처음부터 5까지도 아니고, 처음부터 5개도 아니고 인덱스 5 위치부터 끝까지이다.
아까 위에서 append() 함수의 경우 s1.append(" to C and C++", 5);는 처음부터 5개이기 때문에 이것과 헷갈릴 수 있다.
append() 함수는 처음부터 5개, substr() 함수는 5부터 끝까지라는 것 꼭 기억하자.
또 하나 기억해야 할 것은 substr() 함수는 원본을 바꾸지 않는다는 것이다. 단지 부분문자열을 반환만 할 뿐이다.
s1.substr(0, 1)을 출력하면 W를 출력하겠지만 s1이 W로 바뀌는 것은 아니다. s1은 "Welcome" 그대로이다.
문자열 검색
+find(c: char): int | 문자열에서 문자 c가 발견되는 최초 인덱스를 반환 |
+find(c: char, index: int): int | index부터 찾아서 문자 c가 발견되는 최초 인덱스를 반환 |
+find(s: string): int | 문자열에서 문자열 s가 발견되는 최초 인덱스를 반환 |
+find(s: string, index: int): int | index부터 찾아서 문자열 s가 발견되는 최초 인덱스를 반환 |
//문자 검색
cout << s1.find('o') << endl; // 4 출력(처음위치부터 찾음)
cout << s1.find('o', 6) << endl; // 9 출력(인덱스 6 부터 찾음)
//문자열 검색
string s1("Welcome to HTML");
cout << s1.find("co") << endl; // 3 출력(처음위치부터 찾음)
if (s1.find("co", 6) == string::npos)//찾지 못하면(인덱스 6부터 찾음) string::npos 반환
cout << "co는 없습니다" << endl;
find() 함수는 최초 발견된 위치를 반환하기 때문에 문자열에서 'o'가 있는 모든 인덱스를 찾고 싶으면 이렇게 사용하면 된다.
string s1("Welcome to c++");
int idx = 0;
int count = 1;
while (s1.find('o', idx) != string::npos) {
idx = s1.find('o', idx);
cout << count << "번째로 발견된 o의 위치는 " << idx++ << "입니다." <<endl;
count++;
}
이렇게 find() 함수의 반환값 + 1을 find 함수의 두 번째 인자로 주면 계속 검색할 수 있다. (여기에서는 후위연산자로 idx를 1 증가시켜줬다)
만약에 찾지 못하면 string::npos를 반환하는데 npos는 const형 static 변수이다. (npos는 not position의 줄인 말이다.)
static 멤버를 사용할 때에는 클래스이름::을 앞에 붙여줘야 한다.
문자열 삽입과 교체
+insert(index: int, s: string) | 문자열의 index 위치에 문자열 s 삽입 |
+insert(index: int, n: int, char: c) | 문자열의 index 위치에 문자 c를 n개 삽입 |
+replace(index: int, n: int, s: string) | 문자열의 index 위치부터 n개를 문자열 s로 교체 |
string s1("Welcome to HTML");
s1.insert(11, "C++ and "); // s1의 인덱스 11 위치에 문자열 삽입
cout << s1 << endl; // Welcome to C++ and HTML 출력
string s2("AA");
s2.insert(1, 4, 'B'); //s2의 인덱스 1 위치에 문자 4개 삽입
cout << s2 << endl; //ABBBBA 출력
string s3("Welcome to HTML");
s3.replace(11, 4, "C++"); // s3의 인덱스 11부터 4개를 문자열로 교체
cout << s3 << endl; // Welcome to C++ 출력
문자열 연산자
[] | 배열 첨자 연산자. 첨자 안의 인덱스에 해당하는 문자에 접근 |
= | 한 문자열의 내용을 다른 문자열로 복사 |
+ | 두 개의 문자열을 새로운 하나의 문자열로 연결 |
+= | 하나의 문자열 내용을 다른 문자열에 추가 |
<< | 문자열을 스트림에 삽입 |
>> | 스트림으로부터 공백이나 NULL 문자에 의해 구분되는 문자열 추출 |
==, !=, <, <=, >, >= | 문자열 비교를 위한 관계연산자 |
string s1="ABC";
string s2 = s1;
cout << s1[0]; // A 출력
string s3 = s1 + "DEFGH"; //s3는 ABCDEFGH
s1 += "ABC"; //s1은 ABCABC
cout << (s1<=s3) << endl ; // 1(true) 출력
s1[0]은 s1.at(0)과 똑같고 s1 += "ABC"는 s1.append("ABC")와 똑같다.
+=라는 연산자를 그런 역할을 하도록 내부적으로 그렇게 정의해놓은 것이다.
그런데 +=보다는 append() 함수를 사용하는 것이 문자열을 더 정교하게 컨트롤할 수 있다.
물론 단순히 문자열을 하나의 문자열 뒤에 통째로 추가하고 싶을 때에는 +=와 append()나 똑같기 때문에 아무거나 사용해도 된다.
또 주의해야 할 것은 <= 연산자는 참일 때 1(true)를 리턴하지만 compare() 함수는 두 문자열이 같을 때 1을 리턴한다.
'C++' 카테고리의 다른 글
[C++] <set> 라이브러리, set 사용법 (0) | 2020.01.30 |
---|---|
[C++] <tuple> 라이브러리 - tuple, pair (0) | 2020.01.30 |
[C++] 포인터 사용 시 주의 사항 (0) | 2020.01.30 |
[C++] 포인터 (1) | 2020.01.27 |
[C++] 백준 시간초과 문제 해결 입출력 속도 향상 (0) | 2020.01.24 |