티스토리 뷰
C++의 String class에 대해서 분석해본 내용입니다.
C++ reference를 참고하였습니다.
1. String의 특성
우선 C++ String class는 STL에 속하지는 않습니다. 그냥 라이브러리에 있는 표준 클래스입니다.
String은 연속적인 문자를 담을 수 있고 원소 각각은 char로 되어있습니다.
그리고 Vector와 같이 Random Iterator를 지원한다는 점이 특징입니다.
2.String의 구성
String은 알고리즘에서 막~ 다뤄본 경험이 조금 없어서 중요해 보이는 것이나, 필요에 따라서 하나하나 모두 살펴보겠습니다.
2.1) 생성자
생성자에 관한 설명은 C++ reference에 아주 잘 나와있습니다.
(코드 예제도 이번에는 C++ reference에서 가져왔습니다.)
순서대로 확인해보겠습니다.
//default (1) string();
//copy (2) string (const string& str);
//substring (3) string (const string& str, size_t pos, size_t len = npos);
//from c-string (4) string (const char* s);
//from buffer (5) string (const char* s, size_t n);
//fill (6) string (size_t n, char c);
//range (7) template <class InputIterator>
// string (InputIterator first, InputIterator last);
//initializer list (8) string (initializer_list<char> il);
//move (9) string (string&& str) noexcept;
원형은 위와 같습니다.
(1) ~ (7) 생성자들을 코드를 통해서 사용해보겠습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s4("Initial string"); //(4) from c-string
string s1; //(1) default
string s2(s4); // (2) copy
string s3(s4, 8, 3); //(3)substring (8, 9, 10)
string s5("Another character sequence", 12); //(5) from buffer (0~11)
string s6a(10, 'x'); //(6) fill
string s6b(10, 42); //(6) fill (42 is '*')
string s7(s4.begin(), s4.begin() + 7); //(7) range (iterator)
cout << "s1: " << s1 << "\ns2: " << s2 << "\ns3: " << s3;
cout << "\ns4: " << s4 << "\ns5: " << s5 << "\ns6a: " << s6a;
cout << "\ns6b: " << s6b << "\ns7: " << s7 << '\n';
return 0;
}
string s4는 문자열을 매개변수로 받습니다.
string s1는 default생성자로 아무 값을 가지지 않습니다.
string s2는 다른 string class를 받습니다. 복사 생성자 역할을 합니다.
string s3은 substring 하는 생성자입니다.
프로토 타입은 string (const string& str, size_t pos, size_t len = npos); 이고,
첫 번째 매개변수는 substring 할 string class, 두 번째 매개변수는 substring을 시작할 부분, 마지막 매개변수는 시작한 부분에서 몇 개의 문자를 substring 할 것인지 나타냅니다. 마지막 매개변수에 값을 넣지 않는 경우는 string의 끝까지 substring 합니다.
string s5는 첫 번째 매개변수의 문자열에서 두 번째 매개변수의 개수만큼 시작 지점부터 복사해서 옵니다.
string s6a, s6b의 경우에는 첫 번째 매개변수의 개수만큼 두 번째 매개변수의 문자로 채웁니다.
string s7은 substring과 비슷한데 iterator로 substring 하는 생성자입니다.
위의 7가지 생성자들은 외워두면 string 사용하는데 문제는 없을 거라 생각합니다.
참고로, string을 생성한 다음 cin으로 입력을 받을 수 있습니다.
2.2) 반복자
string은 random iterator를 지원합니다. 따라서 다음 코드처럼 []를 사용해서도 접근할 수 있습니다.
vector도 []를 사용했던 것처럼 string도 똑같이 사용할 수 있습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("String Iterator Test");
string::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter)
cout << *iter;
cout << "\n\n";
for (int i = 0; i < s.length(); ++i)
cout << s[i];
cout << "\n\n";
string::reverse_iterator riter;
for (riter = s.rbegin(); riter != s.rend(); ++riter)
cout << *riter;
cout << "\n\n";
return 0;
}
순방향 iterator를 사용한 것과 0부터 length까지 출력한 결과는 같습니다.
reverse iterator는 사용할 일이 딱히 없지만 그래도 사용법을 보여드리기 위해 코드에 첨부했습니다.
2.3) Capacity & Size
String의 Capacity와 Size의 조절은 Vector와 아주 유사합니다.
따라서 .resize(), .capacity()와 같은 메서드를 지원하고 있습니다.
capacity와 관련하여 할당의 중요성(?)이나 성능은 vector글에 적어놓았으니 참고 바랍니다.
자주 쓰는 부분을 살펴보겠습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("String Size Test");
cout << "string s : " << s << '\n';
cout << "s.size() :" << s.size() << '\n';
cout << "s.length() :" << s.length() << '\n';
cout << "s.capacity() :" << s.capacity() << '\n';
cout << "s.empty() : " << s.empty() << "\n\n";
cout << "----------s.resize(5)----------" << '\n';
s.resize(5);
cout << "string s : " << s << "\n\n";
cout << "----------s.clear()----------" << '\n';
s.clear();
cout << "string s : " << s << '\n';
cout << "s.size() :" << s.size() << '\n';
cout << "s.capacity() :" << s.capacity() << '\n';
cout << "s.empty() : " << s.empty() << "\n\n";
return 0;
}
string s는 공백 포함 16글자입니다.
size()와 length()는 string의 길이, 글자 수 를 출력합니다.
capacity는 메모리에서 몇 칸을 할당했는지 나타내기 때문에 size, length와 다른 값을 가지는 것을 볼 수 있습니다. empty()는 문자열이 비어있지 않기 때문에 0 -> false를 나타냅니다.
문자열이 존재하는 상황에서 resize를 하면 개당 크기만큼만 남겨놓고 모두 잘라버립니다.
그리고 clear() 메서드를 사용하면 문자열이 모두 삭제되고 empty가 1 즉, True가 됩니다.
capacity는 남겨져 있는 것을 볼 수 있습니다. capacity도 0으로 하고 싶다면 shirnk_to_fit이나, reserve함수를 사용해서 낮춰줍시다.
2.4) 매서드
2.4.1) 연산자
매서드를 들어가기에 앞서 String의 연산중 특별한 부분을 보면,
'+'과 '='입니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1, str2, str3;
str1 = "Test string: ";
str2 = 'x';
str3 = str1 + str2;
cout << str3 << '\n';
return 0;
}
모두 default로 생성하고 =를 사용해서 할당할 수 있습니다.
str1처럼 C string형태로 할당하고, str2처럼 'x' 문자 1개를 할당할 수 있습니다.
그리고 나서, str2를 str1의 오른쪽에 붙인 다음에 str3에 할당할 수 있습니다.
응용해서 +=를 사용해서 추가적으로 오른쪽에 붙이는 효과를 나타낼 수 있습니다.
2.4.2) 원소 접근
vector와 마찬가지로 []로 접근할 수 있고, at(), front(), back() 함수를 사용해서 접근할 수 있습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("String Access Test");
cout << "string s : " << s << '\n';
cout << "s.at(2) : " << s.at(2) << '\n';
cout << "s[2] : " << s[2] << '\n';
cout << "s.front() : " << s.front() << '\n';
cout << "s.back() : " << s.back() << '\n';
return 0;
}
at과 []는 동일한 것을 볼 수 있고 front는 문자열의 가장 왼쪽(앞), back는 문자열의 가장 오른쪽(뒤)을 출력합니다.
모두 상수 시간으로 접근합니다.
2.4.3) Modifiers
앞서 연산자 파트에서 적었던 +=과 관련해서 똑같은 기능을 하는 append, 마지막에 문자 1개를 붙이는 push_back() 문자를 할당하는 assign (생성자의 할당방식과 비슷함), 문자를 삽입하는 insert, 일정 범위 삭제하는 erase, 해당 문자를 재배치하는 replace, 문자 1개를 삭제하는 pop_back 등등 많은 것이 있습니다.
한번 사용하면서 확인해보도록 하겠습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("String Modifiers Test");
cout << "string s : " << s << '\n';
s += " += Test";
cout << "s += \" += Test\" : " << s << '\n';
s.append(" Append");
cout << "s.append(\"Append\") : " << s << '\n';
s.push_back('1');
cout << "s.push_back(\'1\') : " << s << '\n';
s.pop_back();
cout << "s.pop_back() : " << s << '\n';
return 0;
}
기본 string에 글자가 많아서 보기 힘드실 수 있겠지만...
+=, append() 모두 정상적으로 문자의 마지막 부분에 덧붙여지는 걸 볼 수 있습니다.
push_back, pop_back() 모두 정상적으로 마지막 부분의 문자를 추가하고 삭제하는 것을 볼 수 있습니다.
다음으로 insert, erase, replace가 있는데 이 부분은 중요하기 때문에 따로 설명하도록 하겠습니다.
erase는 그나마 간단합니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("Hello, C++ World!");
cout << "string s : " << s << '\n';
s.erase(0, 7);
cout << "s.erase(0, 7) : " << s << '\n'; //erase 0~6
s.erase(3);
cout << "s.erase(3) : " << s << '\n'; //erase 3~end
s.erase(--s.end());
cout << "s.erase(--s.end()) : " << s << '\n'; //erase +
return 0;
}
erase의 매개변수는 대부분(시작, 종료) 범위의 매개변수를 가지고 있습니다.
iterator를 매개변수로 넘기지 않고 size_t형태로 넘겨주는 메서드를 사용하는 경우에는 시작 부분은 0으로 종료 부분은 npos가 dafault 값입니다.
그래서 첫 번째 사용과 같이 범위를 나타내어 줄 수도 있고, 두 번째 사용처럼 3번째부터 끝까지 쭉 삭제할 수 있습니다.
또, iterator를 매개변수로 넘겨주어 특정 1 문자를 지워줄 수 있습니다. 예시에는 넣지 않았지만 iterator도 범위를 넣어줄 수 있습니다.
여기서 중요한 부분은 만약 s.end()와 같이 iterator를 반환하는 메서드가 아닌, iterator 변수를 가지고 있고 그걸 사용해서 지울 때, erase를 사용하고 나서는 그 iterator변수가 가리키는 곳이 없어진다는 것입니다. 따라서 iterator를 erase함수가 반환하는 iterator로 다시 대입해주어야 합니다.
insert는 약간 생성자와 비슷합니다.
거의 첫 번째 매개변수로는 삽입할 위치가 들어가게 됩니다. 두 번째 매개변수로는 삽입할 string이 들어갑니다.
이제 필요에 따라 substring 해서 넣을지, 뭐 C 스타일의 string을 넣을지, string이 아니고 char을 넣을지 구분하게 됩니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("Hello,");
string s1(" C++");
string s2("Hello, C++ World");
cout << "string s : " << s << '\n';
s.insert(6, s1);
cout << "string s.insert(6, s1) : " << s << '\n';
s.insert(10, s2, 10);
cout << "string s.insert(10, s2, 10) : " << s << '\n';
s.insert(s.end(), '!');
cout << "string s.insert(s.end(),'!') : " << s << '\n';
s.insert(0, "String ");
cout << "string s.insert(0,\"String\") : " << s << '\n';
return 0;
}
insert 중에서 가장 많이 사용될 것 같은 부분을 코드로 실행해보았습니다.
생성자와 상당히 닮아있습니다.
첫 번째 insert와 같은 경우 6의 위치에 s1을 갖다 붙이는 형태입니다.
두 번째 insert는 10번째 위치에 s2의 10번째부터 npos까지 복사하는 것입니다. npos는 default 매개변수라서 끝까지 하고 싶지 않은 경우는 4번째 매개변수에 다른 값을 넣으면 됩니다.
1글자를 넣고 싶을 때는 세 번째 매개변수처럼 iterator로 접근합니다.
그리고 네 번째 insert는 char* 문자열을 삽입합니다.
replace 같은 경우는 한 번에 erase 하고 insert 하는 느낌의 함수입니다.
첫 번째 매개변수에서 두 번째 매개변수의 위치까지, 세 번째 매개변수의 string을 넣을 건데 필요에 따라 네 번째, 다섯 번째 매개변수로 substring 하는 형태를 많이 갖습니다.
이 부분은 C++ reference를 보고 직접 보시는 게 가장 좋을 것 같아 링크를 남겨놓겠습니다.
http://www.cplusplus.com/reference/string/string/replace/
2.4.4) 그 외 연산
2.4.4.1) find
여기서는 find, substr, c_str 등등 알아두면 유용한 매서드에 대해서 알아보도록 하겠습니다.
find는 문자 혹은 문자열을 찾는 멤버 매서드입니다.
시간 복잡도는 특정한 시간을 나타내지는 않지만 문자 길이 n, 찾으려는 문자열의 길이 m이라고 할 때 대략 O(mn)라고 할 수 있습니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("Find Test.");
cout << "string s : " << s << '\n';
cout << "s.find(\'T\') : " << s.find('T') << '\n';
cout << "s.find(\"Test\") : " << s.find("Test") << '\n';
cout << "s.find(\"Find\") : " << s.find("Find") << '\n';
cout << "s.find(\"ABCD\") : " << s.find("ABCD") << '\n';
cout << "string::npos : " << string::npos;
// s.find("String", start, end)
return 0;
}
다음과 같이 찾으려는 문자 or 문자열을 매개변수로 넘깁니다. 그러면 그 문자가 시작하는 위치의 index를 출력해줍니다.
만약 찾으려는 문자가 없다면 string::npos라는 값을 반환합니다.
2.4.4.2) substr
substr은 어떤 string의 일부분을 가져오는 것입니다.
그래서 멤버 함수의 substr를 사용하면 해당 string의 특정 부분을 string의 형태로 반환해줍니다.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("Hello, C++ World!");
cout << "string s : " << s << '\n';
cout << "s.substr(7) : " << s.substr(7)<<'\n';
cout << "s.substr(0,5) : " << s.substr(0, 5) << '\n';
cout << "s.substr(7,3) : " << s.substr(7, 3) << '\n';
return 0;
}
substr의 매개변수를 보면, (시작, 개수 = npos)로 되어있습니다.
따라서 첫 번째의 s.substr(7)처럼 사용하면, 7번째 index의 문자부터 string의 끝까지 substr 하게 됩니다.
두 번째는 0번째 index부터 5개 즉, 0~4번을 가지고 오게 되고, 이와 마찬가지로 (7,3) 역시 7번째인 C부터 3개 C++를 출력하게 됩니다.
2.4.4.3) c_str
이 멤버 함수는 string 객체가 가지고 있는 문자열을 C style의 문자열로 변경해줍니다. ( char *의 형태 ex) "~~~" )
크게 알고리즘 문제 풀 때는 사용할 일이 없을 것 같지만 알아두면 좋을 것 같아서 적었습니다.
마치며...
string은 C에서 char*로 다루는것보다 훨씬 프로그래밍을 쉽게 해줍니다.
메모리도 동적으로 관리 해주고, 여러 매서드를 통해서 문자열을 쉽게 변경, 접근할 수 있습니다.
알아두면 정말 도움이 될 것 같습니다.
댓글 지적 환영입니다. :)
'개발 > C++' 카테고리의 다른 글
[C++ / 2인용] 테트리스 개발 ( Class 부분 ) (0) | 2020.08.27 |
---|---|
[C++ / STL] Set 분석 (2) | 2020.08.19 |
[C++ / STL] Queue, Priority Queue 분석 (2) | 2020.08.01 |
[C++ / STL] Stack 분석 (0) | 2020.07.31 |
[C++ / STL] vector 분석 (0) | 2020.07.31 |