CS/C++

C++ 공부 섹션17 String : 홍정모의 따배씨쁠쁠

샤아이인 2022. 1. 17.

내돈내고 내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

1) 섹션17

이번시간에는 string에 대하여 집중적으로 배웠다.

17-1 std::string과 std::wstring

문자열 사용을 편리하게 하기위한 string class가 준비되어있다. 기존의 C-style의 방식은 번거롭다.

#include <iostream>
#include <cstddef>
#include <string>
#include <locale>

using namespace std;


int main() {
	// c-style string example
	//{
	//	char* strHello = new char[7];
	//	strcpy_s(strHello, sizeof(char) * 7, "hello");
	//	std::cout << strHello << std::endl;
	//}

	// basic_string string;
	{
		std::string string;
		std::wstring wstring;

		wchar_t wc;
	}
} 
 

이번시간은 라이브러리 확인하는 법을 배웠다.

string위에서 오른쪽 클릭하여 go to definition을 누르니 해당 라이브러리로 이동할 수 있었다. 다음과 같다.

전부 보면 basic_string 이 공통되고 있다. 차이는 char, wchar_t, char16_t, char32_t 가 다르다.

 

중요한점은 basic_string이 template으로 생성된 제네릭 class 라는 점 이다. basic_string 이라는 한 class의 서로다른 instantiation인 것 이다.

class가 있는데 하나는 char, 다른하나는 wchar_t가 들어간 것 이다.

 

추가적으로 wchar_t는 typedef unsigned short wchar_t 이다 별거 없다.

이걸 쓰는 이유는 char 은 데이터 사이즈가 작기 때문에 표현할 수 있는 글자의 수가 한계가 있다.

따라서 wchar_t를 이용하여 데이터 사이즈를 많이 사용하는 문자를 표현할때 사용한다. 특히 국제언어 사용시 사용된다.

조금더 설명해주신 부분이 있는데 기록하진 않겠다.

 

17-2 std::string의 여러가지 생성자들과 형변환

문자열을 초기화 할때 편리하게 사용할수 있는 생성자에 대하여 배웠다.

또한 string을 기본 데이터 형으로 변환하거나 혹은 그반대의 경우에 대하여도 배웠다.

#include <iostream>
#include <string>
#include <vector>

int main() {
	std::string my_string; // default constructor 호출

	std::cout << my_string << std::endl;

	return 0;
} 
 

실행시 아무것도 출력되지 않는다.

 

이번에는 다음과 같이 c스타일로 초기화 해주면 결과가 나온다.

또한 복사 생성자를 이용하여 초기화가 가능하다.

또한 추가적인 인자를 전달해 줄 수 있다.

인덱스 3번 위치부터 5글자를 출력한 것 이다.

 

다음과 같이 중복출력또한 가능하다.

이번에는 STL의 반복자를 이용하여 초기화 하는 법을 배웠다.

find를 사용한 다음과 같은 방식도 사용할 수 있다.

이번에는 좀 유용한 내용에 대하여 배웠다.

string 에 1004를 인자로 넘겨주면 정수형 이기때문에 변환하지 못하고 있다.

따라서 string 으로 변환해준 후 사용해야 한다. 정수를 문자열로 바꿔줘야 사용가능하다.

문자열로서 1004가 출력되고있다. 이 방식은 파일을 출력할때 파일 뒤에 숫자를 바꿔가면서 여러장 출력해야 할 때, 출력 파일명을 결정할 때 아주 유용하다.

또한 다음과 같이 stoi함수를 사용하여 문자열을 다시 정수로 변환이 가능하다.

두번째는 정수 1004 가 출력되고 있다. 첫줄의 1004는 문자열이고, 두번째는 정수형이다.

 

다음 방식은 조금 색다르다. 우선 ToString함수에 인자로 넘어온 x를 osstream으로 넘겨준다.

그다음 이것을 다시 string으로 전환하여 반환한다.

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

template <typename T>
std::string ToString(T x)
{
	std::ostringstream osstream;
	osstream << x; // 들어온 것을 대입
	return osstream.str(); // string 으로 바꿔 return
}

int main() {
	std::string my_str(ToString(3.141592));

	std::cout << my_str << std::endl;

	return 0;
} 
 

다음은 stream을 초기화 할때 string을 넣어 초기화 하는 방식이다.

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

template <typename T>
std::string ToString(T x)
{
	std::ostringstream osstream;
	osstream << x; // 들어온 것을 대입
	return osstream.str(); // string 으로 바꿔 return
}

template <typename T>
bool FromString(const std::string& str, T &x)
{
	std::stringstream isstream(str); // str을 스트림에 넘김 
	return (isstream >> x) ? true : false; // stream을 흘려 보내줌
}

int main() {
	std::string my_str(ToString(3.141592));

	double d;
	if (FromString(my_str, d))
		std::cout << d << std::endl;
	else
		std::cout << "Cannot convert string to double" << std::endl;

	std::cout << my_str << std::endl;

	return 0;
} 
 

위와 같이도 템플릿을 사용할 수 있다. &x를 통하여 3항연산자를 통한 x에 stream을 흘려보내주는 것 이 신기하다.

 

17-3 std::string의 길이와 용량

string 또한 이전에 배운 vector 처럼 길이와 용량이 별도로 관리된다.

#include <iostream>
#include <string>

using namespace std;

int main() {
	string my_str("012345678");

	cout << my_str.size() << endl;

	return 0;
} 
 

std::string 같은경우 c언어 방식의 문자열 처리와는 다르다. C언어 같은겨우 문자열 맨 뒤에 null이 추가됨으로써 문자열의 끝임을 알린다. 하지만 string은 class이다. 그러니까 내부적으로 포인터와 더불어 길이정보를 직접 갖고있다. 

따라서 C언어 에서처럼 문자열의 끝을 찾을필요가 없다.

 

다음으로는 capacity(용량)을 확인해 보자.

#include <iostream>
#include <string>

using namespace std;

int main() {
	string my_str("012345678");

	cout << std::boolalpha;
	cout << my_str.length() << endl;
	cout << my_str.size() << endl;
	cout << my_str.capacity() << endl;

	return 0;
}
 

결과는 다음과 같다.

string도 벡터와 마찬가지로 새로 데이터가 추가되는 상황을 고려해서 가급적이면 동적할당을 안할려고 든다.

new, delete를 반복하기 보다는 처음부터 여분의 공간까지 확보한 후 사용하는 것 이 효율적이다.

그렇다면 이 여분의 공간은 내가 정할 수 없을까? 당연히 가능하다.

reserve에 들어가는 100만큼은 최소한 잡아달라는 의미가 된다.

 

17-4 문자 접근하기와 배열로의 변환

string을 사용할 때 문자에 접근하는 방법 2가지를 먼저 살펴보자.

한가지 오류 처리를 안해주는 좀더 빠른버전이고, 2번째의 at()을 사용하는 버전은 오류처리를 해주는 대신 좀 느리다는 단점이 있다.

살펴보자!

위의 결과는 당연하다. 이렇게만 보면 vector를 사용하는것과 비슷하다는 생각이 든다.

 

다음으로는 예외처리를 사용해 보자. 다음 코드는 string의 범위를 넘어서는 index에 값을 대입하고있는 코드이다.

int main() 
{
	string my_str("abcdefg");

	try
	{
		my_str[100] = 'X';
	}
	catch (std::exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}
 

예외 처리가 발생할 것 이라 생각했지만, 발생하지 않았다. 왜 이런 결과가 나오는 것 일까? 예외처리의 단점중 하나는 느려진다는 것 이다. 따라서 느려질까봐 예외처리를 해주지 않는 것 이다. 따라서 예외처리가 있는 버전을 사용해줘야 한다. 바로 at() 을 사용하는 것 이다!!

위의 코드와 같은 경우 예외처리를 해주고 있다. 원하는 결과가 출력된다.

 

이 내용을 통해 배울점은 string에서 [] 연산자 오버로딩이 되있는 경우에는 예외처리를 안해준다. 

따라서 사용자가 index에 해당하는 데이터가 있음을 보장해줘야 하고, 추가적으로 개선을 할 수 있다면 assert를 사용할 수 있을것 이다.

아니면 그냥 at()을 사용하면 throw()를 해주기 때문에 문제가 있어도 catch하여 예외처리 할 수 있다.

또한 vector에서도 똑같이 사용할 수 있다.

 

다음은 string을 C 스타일의 문자열로 받는 방법을 알아보자.

string 자체에는 null문자가 끝에 추가되지 않는다. 하지만 c_str로 갖어오면서 추가가된다.

이는 사용자가 C스타일의 문자열 인것처럼 사용하도록 배려해주는 것 이다. 이러한 c_str과 비슷한 data라는 함수가 또 있다.

이둘은 거의 동일하다고 cpprefernce에 나와있다.

 

마지막으로 string을 C스타일의 array로 바꿔주는 방법이 하나 더 있다. copy를 하는 것 이다.

복사해주는 방식은 우리가 집접 문자열 끝에 null을 추가해줘야 한다. 결과는 깔끔하게 나온다.

 

17-5 string 대입, 교환, 덧붙이기, 삽입

대입의 경우와 덧붙이기를 살펴보면 다음과 같다.

여기서 주의하고 봐야할 점이 assign()의 반환값이 string&이라는 점 이다. 참조형이다!!

즉 연속된 chaining기법이 가능하다. 따라서 append함수들을 추가해주고 있다.

 

다음은 교환에 대하여 알아보자

결과가 정상적으로 나오고 있다.

 

마지막으로 삽입을 보자

str에 index 2번위치에 "bbb"를 삽입해 주고 있다.

댓글