레이블이 STL인 게시물을 표시합니다. 모든 게시물 표시
레이블이 STL인 게시물을 표시합니다. 모든 게시물 표시

2022년 1월 2일 일요일

STL, vector

1. Vector 정의

길이가 변하는 동적 배열로서 객체를 삽입하거나 제거할 때 자동으로 크기가 변한다. 배열 대용으로 사용가능하다.


2. Vector 사용법

#include <vector>

vector <변수 형태> 변수이름

at : 원소에 아이템 접근 읽기 쓰기 가능, 영역 벗어나는지 검사 가능

operator[] : 원소에 아이템 접근 읽기 쓰기 가능


3. 메소드 종류

size() : 저장된 item 갯수 획득

reserve() : 스토리지를 미리 예약한다, 메모리 할당이 한번에 많이 할때 성능을 위해서 사용을 고려가 필요하다.

capacity() : 현재 할당할 수 있는 메모리, 이 양보다 증가되면 신규로 메모리 할당이 일어난다.

clear() : 전체 아이템 삭제

insert() : 특정 위치에 아이템 추가

push_back() : 아이템을 뒤쪽에 추가

erase(iterator) : 아이템 삭제

pop_back() : 마지막 아이템 삭제


전체 목록은 아래 참고

https://en.cppreference.com/w/cpp/container/vector


4. 예제

4.1 아이템 추가 / 접근

단순한 예제입니다. push_back()으로 마지막에 아이템을 추가하고 at() 이나 [] 연산자로 접근이 가능합니다.

#include <iostream>
#include <vector>

using namespace std;

int test1()
{
    vector <int> v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.at(0) = 40;
    v[1] = 50;

    int nsize = v.size();
    cout << "size:" << nsize << endl;
    cout << "v = { ";
    for (int i=0 ; i < nsize ; i++) {
        cout << v[i] << ", ";
    }
    cout << "};" << endl;
    return 0;
}
int main()
{
    test1();
    return 0;
}

출력

size:3
v = { 40, 50, 30, };


4.2 at / operator[] 차이 및 주의점

at / []의 가장큰 차이점은 at은 boundary 검사를 한다는것입니다. 당연히 속도가 떨어질 가능성이 있겠죠. []은 검사를 하지 않지만 메모리를 잘못 액세스시 어떤일이 벌어질지 그때 그때마다 동작이 다르게 됩니다.

아래 예제를 보면 v[3] = 50; 를 사용하지만 v.size()그대로 3을 유지하고 있습니다. 즉 [] 연산자에 의해서 크기는 전혀 증가가 되지 않습니다. at은 범위를 벗어나면 exception을 일으킵니다.

#include <iostream>
#include <vector>

using namespace std;

int test2()
{
    vector <int> v;

    v.push_back(10); // index 0
    v.push_back(20); // index 1
    v.push_back(30); // index 2
    cout << "vsize:" << v.size() << endl;
    v[3] = 50;
    cout << "vsize:" << v.size() << " v[3]:" << v[3] << endl;
    v.at(3) = 40;
    cout << "vsize:" << v.size() << " v[3]:" << v[3] << endl;

    int nsize = v.size();
    cout << "size:" << nsize << endl;
    cout << "v = { ";
    for (int i=0 ; i < nsize ; i++) {
        cout << v[i] << ", ";
    }
    cout << "};" << endl;
    return 0;
}
int main()
{
    test2();
    return 0;
}

결과

vsize:3
vsize:3 v[3]:50
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 3) >= this->size() (which is 3)


2022년 1월 1일 토요일

STL, map

1. map 정의

한 데이터 아이템(키)에서 다른 것(값)으로 매핑을 할때 사용하는 자료 구조 입니다.

map 은 알아두면 굉장히 유용한 자료 구조입니다. 성능을 개선할때 map사용을 고려해보는게 좋습니다. n개의 원소가 있을 때 O(log n)의 시간 복잡도로 삽입, 삭제, 검색이 가능합니다.

MAP에 대한 기본적인것은 여기에서 다루지는 않고, MAP을 STL에서 사용하는 예제들로만 다룰 예정입니다. 

기본적인 상식은 MAP에서는 key가 중복되지 않습니다. key가 중복된다면 중복MAP을 이용해야합니다.


2. 사용 형태

map<key type, value type> map변수이름

예) map<intint> map_variable;

값을 넣을때 

  map변수이름[key]=값

값을 읽을때

  map변수이름[key]


3. 메소드 종류

begin() :  첫 번째 원소의 iterator (반복자)를 반환한다. (map의 원소를 반복자를 이용해서 접근할때 사용한다.)

end() : 마지막 원소 다음의 반복자를 반환. (map의 원소를 반복자를 이용해서 접근할때 사용한다.)

clear() : 저장하고 있는 모든 원소를 삭제한다.

insert() : 원소를 추가한다. 이것 보다는 [] 형태를 사용하는것을 추천합니다.

find() : key와 관련된 원소의 반복자를 반환한다.  (단 찾지 못한 경우 end() 반복자를 반환한다)

size() : 원소의 개수를 반환한다.

erase() : 해당 원소를 삭제한다.


4. 예제

4.1 가장 기본 key, value가 모두 int,float

#include <map>
#include <iostream>

using namespace std;


int test1()
{
	// defination key type, value type
	map<int, float> mTest;
	
	// Add item
	mTest.insert(make_pair(1,1.1));
	mTest.insert(make_pair(1,2.1));
	mTest.insert(make_pair(50,3.1));
	mTest[50]=4.11; // 갱신 가능
	mTest.insert(make_pair(50,5.1)); // 갱신이 일어나지 않음
	
	cout << "mTest[50]:" << mTest[50] << endl;
	cout << "mTest.find(50)->first:" << mTest.find(50)->first << endl;
	cout << "mTest.find(50)->second:" << mTest.find(50)->second << endl;
	
	return 0;
}

int main()
{
	test1();
	return 0;
}

find의 리턴값은 iterator입니다. 키의 접근은 ->first , 값의 접근은 ->second를 사용합니다.

mTest[50]:4.11
mTest.find(50)->first:50
mTest.find(50)->second:4.11


4.2 begin(), end()예제

iterator(반복자)를 이용하여 구현합니다.

방법은 다음과 같은 형태를 사용합니다. map<int, float>::iterator iter;

#include <map>
#include <iostream>

using namespace std;

int test1()
{
	// defination key type, value type
	map<int, float> mTest;
	
	// Add item
	mTest.insert(make_pair(1,1.1));
	mTest.insert(make_pair(1,2.1));
	mTest.insert(make_pair(50,3.1));
	mTest[50]=4.11; // 갱신 가능
	mTest.insert(make_pair(50,5.1)); // 갱신이 일어나지 않음
	
	map<int, float>::iterator iter;
	for(iter = mTest.begin() ; iter!=mTest.end();iter++){
		cout << "iter->first:" << iter->first << endl;
		cout << "iter->second:" << iter->second << endl;
	}
	
	return 0;
}

int main()
{
	test1();
	return 0;
}

기존과 동일한 예제를 사용하였습니다.

iter->first:1
iter->second:1.1
iter->first:50
iter->second:4.11


4.3 key가 문자열인 경우

#include <map>
#include <iostream>

using namespace std;

int test1()
{
	{
		map<const char*, float> mTest;
		
		mTest["abc"]=1.11;
		mTest["ab"]=2.11;
		mTest["bbb"]=3.11;
		
		cout << mTest["ab"] << endl;
	}
	{
		map<char*, float> mTest;
		
		mTest[(char *)"abc"]=1.11;
		mTest[(char *)"ab"]=2.11;
		mTest[(char *)"bbb"]=3.11;
		
		cout << mTest[(char *)"ab"] << endl;
		
		char buf[20];
		buf[0]='a';
		buf[1]='b';
		buf[2]=0;
		cout << buf << endl;
		cout << mTest[buf] << endl;
	}
	
	return 0;
}

int main()
{
	test1();
	return 0;
}

문자열을 다루는것인데 이번에는 좀 더 복잡한 예제입니다. 쉽게 생각하면 map에 const char type 키로 만들고 예제를 만들어 보았습니다. 결과 확인해보면 정상적으로 나옵니다.

두번째 예제는 string 변형을 하기위해서 const를 제거한 char type으로 해봤습니다.

2.11
2.11
ab
0
cout << mTest["ab"] << endl;
cout << mTest[(char *)"ab"] << endl;

위와 같이 표현하고 있는 부분에서는 정상적으로 2.11 이 출력되었습니다만 아래쪽 buf로 만든 곳에서는 0이 출력되었습니다. 분명 map에는 정상적으로 "ab"가 전달 되었을 텐데......

이것이 map 사용시 주의해야할 점입니다.

위와 같이 사용하면 map에 char *의 포인터값을 key를 생성합니다. 그말은 즉 포인터 주소를 이용해서 map을 만들게 되고 해당 포인터의 값에는 영향을 받지 않습니다. 그러면 "ab" 할때 정상적으로 나오는 부분은 컴파일러가 RO(Read only)변수들을 모아서 같은 주소로 매핑을 하기 때문에 같은 주소를 지니기 때문입니다.

이것을 잘생각해보면 구조체의 포인터나 클래스의 포인터를 이용하는 경우도 안된다는 것을 알 수 있습니다.

string의 경우 어떻게 해야할까요?

대표적인것이 아래 STL string을 이용하는 방법입니다. 

추가로 속도를 더 높이기 위해서는 string의 hash값만 저장해서 이용하는 방법도 있습니다.


4.4 key가 string인 경우

구현은 간단합니다. key에 string을 사용해 주는것입니다.

#include <map>
#include <iostream>
#include <string>

using namespace std;

int test1()
{
	{
		map<string, float> mTest;
		
		mTest[string("abc")]=1.11;
		mTest[string("ab")]=2.11;
		mTest[string("bbb")]=3.11;
		
		char buf[20];
		buf[0]='a';
		buf[1]='b';
		buf[2]=0;
		cout << buf << endl;
		cout << mTest[string(buf)] << endl;
	}

	return 0;
}
int main()
{
	test1();
	return 0;
}

값이 정상적으로 잘 나옵니다.

ab
2.11


2021년 12월 27일 월요일

STL, string

1. string 정의

string은 문자열을 좀 더 쉽게 다루기위한 STL 컨테이너 입니다.

 string은 STL 컨테이너는 아니지만 C++언어에 string을 편하게 다루는 방식이 있지 않다보니  많이 사용하게 됩니다.


2. string 사용법

string를 사용하기 위해서는 <string> 헤더를 인클루드 해야합니다. 

사용한 컴파일러 정보입니다.

g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=C:/TDM-GCC-64/bin/../libexec/gcc/x86_64-w64-mingw32/5.1.0/lto-wrapper.exe
Target: x86_64-w64-mingw32
Configured with: ../../../src/gcc-5.1.0/configure --build=x86_64-w64-mingw32 --enable-targets=all --enable-languages=ada,c,c++,fortran,lto,objc,obj-c++ --enable-libgomp --enable-lto --enable-graphite --enable-cxx-flags=-DWINPTHREAD_STATIC --disable-build-with-cxx --disable-build-poststage1-with-cxx --enable-libstdcxx-debug --enable-threads=posix --enable-version-specific-runtime-libs --enable-fully-dynamic-string --enable-libstdcxx-threads --enable-libstdcxx-time --with-gnu-ld --disable-werror --disable-nls --disable-win32-registry --prefix=/mingw64tdm --with-local-prefix=/mingw64tdm --with-pkgversion=tdm64-1 --with-bugurl=http://tdm-gcc.tdragon.net/bugs
Thread model: posix
gcc version 5.1.0 (tdm64-1)

생성하기

string s : 기본 생성자로 s 생성(초기화)

string s(str) : str 문자열로 s 생성

string s(p1, p2) : 포인터 구간 [p1, p2)의 문자로 s를 생성



3. 메소드 종류

+ 연산자, append() : 문자열 뒤에 붙이기

data() : 주소 획득

c_str() : \0 으로 끝나는 주소 획득

size() or length() : \0을 포함하냐 안하냐에 따라 다를 수 있지만 두개가 같다고 보면 됩니다. 

compare() : 스트링을 비교합니다.

s1 > s2 : 1

s1 < s2 : -1

s1 == s2 : 0

사용법 : s1.compare(s2)

(부분 비교도 가능)

copy() : 스트링 복사

s1.copy(buf, s1.length());

buf[s.length()] = 0; //\0을 복사하지 않으므로 0은 마지막에 넣어 줍니다.

find() : 검색

문자열에서 해당 문자/문자열의 위치를 반환. 없을 경우 string::npos, -1 반환

replace(시작위치, 길이, 교체문자열)

substr() : 스트링에서 부분 문자열을 추출

erase() : 문자열 지우기

insert() : 문자열에서 원하는 위치에 원하는 문자열 추가


4. 예제

4.1 대입

s=문자열


예제

#include <string>
#include <iostream> 

using namespace std;

int main()
{
	string str1,str2;
	const char *ptr1="abcdef";
	string str3(ptr1+1,ptr1+3);
	
	str1="abc";
	
	cout << "[" << str1 << "]" << "\n";
	//[abc]
	cout << "[" << str2 << "]" << "\n";
	//[]
	cout << "[" << str3 << "]" << "\n";
	//[bc]

	return 0;
}

결과

[abc]
[]
[bc]


4.2 문자열 붙이기

+ 연산자, append() 메소드

예제

#include <string>
#include <iostream> 

using namespace std;

int main()
{
	string str1,str2;
	const char *ptr1="abcdef";
	string str3(ptr1+1,ptr1+3);//[bc]
	str1="abc";//[abc]

	string str4;
	str4 = str1 + str3;
	str2.append(str1);
	str2.append(str3);
	
	cout << "[" << str2 << "]" << "\n";
	cout << "[" << str4 << "]" << "\n";
	
	return 0;
}

결과

[abcbc]
[abcbc]


4.3 데이터 가져오기/접근하기

string은 \0(null)을 가지고 있을 수 있기 때문에 두가지 메소드가 준비가 되어있습니다.

data() : 포인터 획득

c_str() : \0 으로 끝나는 포인터 획득

이중 c_str()은 printf/scanf등에서 사용가능한 형태의 함수가 됩니다.

예제

#include <string>
#include <iostream> 
#include <stdio.h>
using namespace std;

int main()
{
	string str1,str2;
	const char *ptr1="abcdef";
	string str3(ptr1+1,ptr1+3);//[bc]

	printf("%s\n",str3.data()); // Wrong
	printf("%s\n",str3.c_str()); // Right
	
	return 0;
}

위 예제에서 str3.data() 이 부분은 잘못된 사용이 됩니다. 왜냐하면 printf 는 \0을 만날때 까지 출력하는 함수이기 때문입니다. 그렇치만 결과는 제대로 나올 수 있습니다.

결과

bc
bc

포인터 주소를 알았으니 조작하는건 일도 아닐겁니다. 하지만 string 은 주소가 변할 수 있다는점 명심하시기 바랍니다. 즉 이번에 획득한 c_str()의 주소가 다음번 획득한 주소와 다를 수 있습니다. 이것은 string STL에서 새로운 문자열이 할당이 이뤄질때 remalloc이 일어나기 때문입니다.


4.4 문자열 찾기/바꾸기

find()

문자열에서 해당 문자/문자열의 위치를 반환. 없을 경우 string::npos, -1 반환

replace(시작위치, 길이, 교체문자열)

예제

#include <string>
#include <iostream> 

using namespace std;

int main()
{
    string str("Hello World!!");
    string str2("Wor");
 
    size_t found = str.find(str2);
    if (found != string::npos)
        cout << "first 'Wor' found at: " << found << '\n';

    str.replace(str.find(str2), str2.length(), "XXX");
    cout << str << '\n';

    return 0;
}

결과

first 'Wor' found at: 6
Hello XXXld!!