February 18, 2020 ( last updated : February 17, 2020 )
stl
https://github.com/gmm117/gmm117.github.io
표준 라이브러리
유틸리티
컨테이너
반복자
알고리즘
STL컨테이너
함수객체
스트링
STL 추가적으로 알아낸 내용
표준 라이브러리
- explicit 키워드
단일 인자를 갖는 생성자가 자동 형 변환이 일어나는 것을 막기 위해 사용되는 키워드이다.
class Stack
{
explicit Stack(int nSize){}
}
Stack s1(40) - O
Stack s2 = 40 - X
-> 아래 4가지 캐스팅을 사용함으로써 타입 변환의 의도를 명확히 할 수 있다는 장점을 가지고 있다.
static_cast<> : 캐스팅이 묵시적으로 일어나는 경우 이를 명시적으로 형변환하겠다라고 분명히 해주는 용도로 쓰인다.
논리적으로 값을 변환한다.
static_cast<>는 변환된 값으로 초기화된 임시 객체를 생성한다고 볼 수 있다.
타입 변환이 정의되어 있는 객체에 대해서만 성공한다.
포인터의 타입을 다른 것으로 변환하는 것은 허용되지 않으며 컴파일 에러로 처리된다
is-a 관계와 상속관계에 대해서 모두 허용함.
캐스팅이 묵시적으로 일어나는 경우 이를 명시적으로 형변환하겠다라고 분명히 해주는 용도로 쓰인다. 묵시적 캐스트(implicit cast)와 일차적으로 동일하나, 논리적으로 변환 가능한 타입만 변환해준다. 클래스의 경우엔 상속 관계를 따져보고 변환하며 상호호환 관계가 아니라면 컴파일 에러를 발생시켜 개발자가 에러를 쉽게 찾을 수 있게 해준다. (실수형의 변수를 정수형으로 캐스팅하거나 반대로 실수형 변수를 정수형으로 캐스팅하는 것은 허용된다. 또한 상호 호환되는 열거형과 정수형과의 변환, double과 float의 변환 등도 허용된다. 그러나 포인터의 타입을 다른 것으로 변환하는 것은 허용되지 않으며 컴파일 에러로 처리된다. )
char c = ‘A’;
char* pc = &c;
int* pi = pc; // int* pi = static_cast<int*>(pc); (X)
dynamic_cast<>
다형성을 띄고 있는 타입을 실제 타입으로 변환 하는 것을 가능하게 한다.
dynamic_cast 연산자는 포인터끼리 또는 레퍼런스끼리 변환하는데 사용한다. 포인터를 레퍼런스로 바꾸거나 레퍼런스를 포인터로 변환하는 것은 상식적으로 필요하지도 않고 가능하지도 않다. 포인터끼리 변환할 때도 반드시 상속 계층에 속한 클래스끼리만 변환할 수 있다. int *를 char *로 변환하거나 Parent *를 int *로 변환하는 것은 안된다.
부모 자식간을 변환할 때 업 캐스팅은 원래부터 허용되는 것이므로 이 캐스트 연산자가 있으나 없으나 당연히 가능하다. 문제는 부모 타입의 포인터를 자식 타입의 포인터로 다운 캐스팅할 때인데 이때는 무조건 변환을 허용하지 않고 안전하다고 판단될 때만 허용한다. 안전한 경우란 변환 대상 포인터가 부모 클래스형 포인터 타입이되 실제로 자식 객체를 가리키고 있을 때 자식 클래스형 포인터로 다운 캐스팅할 때이다. 말이 좀 복잡한데 실제로 가리키고 있는 객체의 타입대로 캐스팅했으므로 이 포인터로 임의의 멤버를 참조해도 항상 안전하다.
반대로 부모 클래스형 포인터가 부모 객체를 가리키고 있는 상황일 때 자식 클래스형으로의 다운 캐스팅은 안전하지 않은 변환이다. 왜냐하면 부모 객체를 다운 캐스팅해서 자식 객체를 가리키는 포인터에 대입한 후 이 포인터로 자식에게만 있는 멤버를 참조할 수도 있기 때문이다. dynamic_cast 연산자는 이럴 경우 캐스팅을 허용하지 않고 NULL을 리턴하여 위험한 변환을 허가하지 않는다.
class Person { ~ }; class Child { ~ }; void main() { Person p = new Child(); - (O) Child1 *ch1 = dynamic_cast<Child1>(p); - (O) : Down Cast는 가능 UpCast는 못하게 막아줌 }
const_cast const_cast의 용도는 간단하다. 오직 const 또는 volatile의 속성을 변경할 때 사용한다. 상수 지시 포인터를 비상수 지시 포인터로 잠시 바꾸고 싶을 때 const_cast 연산자를 쓴다. 반대의 경우도 물론 이 연산자를 사용할 수 있겠지만 비상수 지시 포인터는 상수 지시 포인터로 항상 변환 가능하므로 캐스트 연산자를 쓸 필요가 없다. 그냥 대입만 하면 된다.
reinterpret_cast(연관성이 없는 포인터 타입을 변환시키기 위해서 사용됨) 이 캐스트 연산자는 임의의 포인터 타입끼리 변환을 허용하는 상당히 위험한 캐스트 연산자이다. 심지어 정수형과 포인터간의 변환도 허용한다. 정수형값을 포인터 타입으로 바꾸어 절대 번지를 가리키도록 한다거나 할 때 이 연산자를 사용한다.
char c = ‘A’;
char* pc = &c;
int* pi = reinterpret_cast<int*>(pc); (O)
reinterpret_cast와 () 차이는 reinterpret_cast는 포인터끼리, 포인터와 수치형과의 변환에 대해 수행할 수 있다는 것과 const, volitile 타입의 포인터 타입에 대해서는 캐스팅을 수행할 수 없다는 것이다.
C++표준 라이브러리의 식별자를 사용하는데 3가지 방법이 있다. 1) 명시적으로 namespace의 이름을 적어주는 방법( std::cout « ) 2) using 선언문을 사용하는 방법(using std::std) 3) using 지시자를 사용할 수 있다.(using namespace std;)
C 표준헤더파일에서는 확장자 붙었지만, C++ 표준 헤더파일에서는 확장자가 없어지게 되었다.
#include
유틸리티
pair(map, equal_range)
pair 클래스는 두 개의 값을 한개의 단위로 관리하기 위해 제공된다.
pair의 선언은 class가 아니라 struct로 되어 있기 때문에 모든 멤버, 외부에서도 접근이 가능하다.
make_pair
make_pair라는 템플릿 함수는 타입을 명시적으로 선언하지 않고도 pair값을 생성할 수 있도록 도아주는 함수이다.
예) pair<int, char>(42, ‘*’)
make_pair((42, '*')
make_pair는 암시적인 타입 변환을 제공하기 때문에, 타입을 정확히 일치시키지 않더라도 동작하도록 되어있다.
auto_ptr은 대부분 통상적인 포인터와 같은 인터페이스를 가지고 있다.
예) auto_ptr
auto_ptr은 delete 문장과 catch절은 더 이상 필요하지 않다.
auto_ptr<> 클래스는 통상적인 포인터를 할당 연산자(=)를 통해서 초기화 할 수 없다.
예) auto_ptr
예문)
std::auto_ptr
std::auto_ptr
-> 1. ptr1은 new 연산자에 의해서 생성된 객체의 소유권 가짐
2. 객체의 대한 소유권이 ptr1에서 ptr2로 넘어간다.
std::auto_ptr
std::auto_ptr
prt2 = ptr1;
-> auto_ptr할당 > ptr2가 소유한 객체를 소멸 > 소유권을 ptr1에서 prt2로 이전
std::auto_ptr
ptr = new ClassA; - X
ptr = std::auto_ptr
->1. 클래스 안에서 auto_ptr을 사용함으로써 사용자는 리소스 릭을 피할 수 있다.
2. 왜냐하면 멤버가 소멸됨에 따라서 auto_ptr의 소멸자가 자동으로 소멸을 해주기 때문이다.
예)
class ClassB
{
private:
const std::auto_ptr
const std::auto_ptr
public:
ptr1(new ClassA(val1)), ptr2(new ClassA(val2))
{
}
ptr1(new ClassA(x.ptr1)), ptr2(new ClassA(x.ptr2))
{
}
const classB& operator=(const Class B& x)
{
*ptr1 = *x.ptr1;
*ptr2 = *x.ptr2;
return *this;
}
-> 소멸자 필요 없음. / 복사 생성자와 할당 연산자는 반드시 작성해주어야한다.
auto_ptr은 소유권을 공유할 수 없다.
auto_ptr은 배열 타입을 지원하지 않는다. - delete[] 가 아닌 delete를 호출하므로
auto_ptr은 범용 스마트 포인터가 아니다.
-> 다른 스마트 포인터가 유용하게 사용될 수 있는 상황을 해결하기 위해서 설계된 것이 아니다.
-> auto_ptr은 레퍼런스 카운트라는 개념을 사용하지 않는다.
-> auto_ptr은 표준 컨테이너 클래스의 원소로 사용되지 않는다. 왜냐하면, auto_ptr의 복사와 할당이 이루어진 이후 소스와 싱크가 동등하지 않기 때문이다.
소스 : 데이터를 전송하는 곳
싱크 : 데이터를 받는 곳
1) auto_ptr() : auto_ptr의 값을 0으로 초기화
2) 객체를 소유하지 않은 auto_ptr을 생성
3) get() : auto_ptr이 소유한 객체의 주소를 반환
4) operator*() : auto_ptr이 소유한 객체를 반환한다.
5) release() : auto_ptr이 소유한 객체의 소유권 해제, 객체의 주소를 반환, 객체소유 안할시 NULL 포인터를 반환
6) reset(ptr): auto_ptr이 소유하던 기존의 객체는 삭제된다. *this는 ptr이 참조하는 객체의 소유자가 된다.
만약 prt이 널 포인터가 아니라면, new로 생성된 값을 넘겨줘야 한다.
수치제한(사용자가 타입에 대해서 공통의 인터페이스를 유용하게 제공하기 위해서 템플릿을 사용하게 된다)
수치타입은 일반적으로 플랫폼에 종속적인 제한값을 가진다.
C++ 표준 라이브러리는 이러한 제한값들을 numeric_limits 템플릿 클래스로 제공한다.
타입 안정성(type safety)을 제공
제한값을 구하는 텟플릿을 작성가능
보조함수
최소값, 최대값의 처리 : min, max
두 값의 교환 : swap
비교 연산자의 확장(rel_ops)
-> 서로 타입이 달라서 비교할 수 있다는게 이점이다. 하지만 C++ 표준 라이브러리에 의해 제공도는 것이 아니기 때문에 호환성이
보장되지 않는다.
컨테이너
컨테이너
STL은 2가지 컨테이너를 제공한다.
시퀀스 컨테이너 : 모든 원소들이 자신만의 위치를 가지고 있는 순서가 존재하는 컬렉션(vector, deque, list)
연관 컨테이너 : 모든 원소들이 정렬 기준에 의존하여 자동 정렬이 되는 컬렉션(set, multiset, map, multimap)
연관 컨테이너 장점
자동정렬기능이 있어서 더 좋은 성능을 발휘할 수 있다.
시퀀스 컨테이너
vector : * 자신의 원소들을 동적인 배열을 이용하여 관리한다.
* 랜덤 엑세스를 지원한다 - 인덱스를 가지고 곧바로 사용자가 원하는 데이터를 액세스 할 수 있다.
* 배열의 끝부분에 데이터를 추가하거나 삭제하는 것은 매우 빠르다
* 데이터를 배열의 중간이나 앞부분에 삽입하면, 삽인된 위치 이후의 원소들을 모두 이동을 해야하므로 많은 시간이 소비된다
* vector가 push_front 함수를 제공한다면 성능이 매우 저하되므로 push_front 함수를 제공하지 않는다.
deque * vector와 달리 앞과 뒤에 원소를 삽입하는 동작 모두 빠르게 수행된다.
* 원소를 중간에 삽입하는 동작은 원소들이 움직여야 하기 때문에 시간이 걸린다.
list * list는 랜덤 액세스를 지원하지 않는다.
* list의 장점은 바로 어떠한 위치에서도 빠르게 삽입 및 제거가 이루어진다는 것이다.(단지 링크된 주소만 변경가능)
* list에서 []연산자를 지원할 경우 나쁜 성능을 일을 킬 수 있다는 것을 의미한다. 그러므로 지원하지 않는다.
String * C++ 스트링 클래스를 의미하는 것이다.
연관 컨테이너
연관 컨테이너는 정렬기준에 따라 자동적으로 자신의 원소들을 정렬한다. 정렬기준은 두가지 값 또는 값을 위해 정의된 특별한 키를 비
교하는 함수의 형태로 제공함.
예제) typedef set
typedef set<int, greate<int>-> 내가 정의한 함수를 넣어서 정렬을 바꿀수도 있다.
multiset * 중복이 허락된다는 점을 제외하고는 set과 동일, 동일한 값을 지닌 원소들이 동시에 존재할 수 있다.
* value가 중복될 경우 먼저 삽입된 데이터가 먼저 나오게 된다.
map * key/value를 쌍으로 가지고 있는 컨테이너이다. 각각의 원소들은 정렬 기준의 기본이 되는 키를 가지고 있다.
* 같은 값을 지닌 키는 단 하나만 존재할 수 있다.
* 연관배열을 같은 map - 배열의 인덱스를 key처럼 사용이 가능함.
- 연관배열 : 인덱스로 임의의 타입을 가질 수 있는 배열을 의미한다.
예제) coll["VAT"] = 0.15;
coll[“PI”] = 3.14;
coll["HONG"] = 1030;
coll["NULL"] = 0;
* 원소의 키를 변경하고자 한다면 pos->first = "h"; - x
coll["new_key"] = coll["old_key"];
replace_key(coll, "old_key", "new_key");
multimap * 중복이 허락된다는 점을 제외하고는 map과 동일, 중복된 키가 허용되지만 map은 중복되면 안된다.
-> 모든 연관 컨테이너들은 바이너리 트리의 구현을 기반으로 이루어져 있다.
make_pair -> key/value 값을 쌍으로 만들어야 할 경우 쓰인다.(map에 쓰일경우)
컨테이너 어댑터
stack : LIFO(last-in-first-out)
queue : FIFO(Fist-in-first-out)
priority queue : 우선순위를 지닌 원소들을 관리하는 컨테이너이다.
컨테이너 원소
컨테이너 원소들이 갖추어야 할 사항들 : 컨테이너, 반복자, 알고리즘들을 모두 템플릿이다. 그러므로 모든 타입을 처리할 수 있어야함.
1) 원소들은 복사 생성자에 의해서 복사되어 질 수 있어야 한다. 복사된 객체는 원본과 동일해야만 한다.
원소들은 할당 연산자들에 의해서 할당되어 질 수 있어야만 한다.(기존의 값을 새로운 값으로 덮어쓰기 위해서는 할당 연산자를 사용함)
모든 원소들은 소멸자에 의해서 파괴될 수 있어야만 한다.( 자신이 가지고 있는 내부 복사본을 제거한다.)
2) 원소에 대한 요구사항 충족되야함
시퀀스 컨테이너의 멤버 함수들을 위해서 디폴트 생성자는 반드시 존재해야만 한다.
새로운 원소들이 가지고 있는 값이 없더라도 비어있지 않은 컨테이너를 생서하거나 원소의 개수를 증가시킬 수 있어야 한다.
동일함을 검사하기 위한 == 연산자는 반드시 정의되어야만 한다.
연관 컨테이너에 대해서는 정렬기준이 반드시 제공되어야만 한다. < less<> 함수-객체가 호출된다.( < 연산자를 호출 ) >
값 의미론과 레퍼런스 의미론
모든 컨테이너는 원소의 내부적인 복사본을 생성하며, 이 복사본을 반환한다.( 값 의미론)
값의미론 장점
1) 원소를 복사하는 것은 간단하다.
2) 레퍼런스는 에러가 발생하기 쉽다. 즉, 사용자는 더 이상 존재하지 않은 객체에 대해서 참조하지 않는다는 사실을 보장해 주어야한다.
1) 원소를 복사하는 것은 나쁜 성능을 가져올 수 있다.
2) 같은 객체를 여러 컨테이너에 동시에 관리하는 것이 불가능하다.(복사본이므로)
STL에서의 에러 및 예외
에러검사는 가능하지만 STL 안에서는 필요하지 않다.( 목표가 안정성보다는 최고의 성능을 추구하므로)
에러 검사는 성능의 저하를 초래하며, 만약 속도보다 안정성을 요하면 새로운 클래스르 만들어서 써야한다.
STL 사용시 요구사항
반복자는 반드시 유효해야만 한다. 반복자를 사용하기 전에 반드시 초기화를 해야만한다.
vector, deque 사용할 경우, 원소를 삽입하거나 지울 경우, 또는 메모리를 재할당할 경우, 반복자는 무효화된다.
종료 위치 다음을 가리키는 반복자는 참조하는 원소가 없다.
범위는 반드시 유효해야한다.
1) 범위를 나타내는 두 개의 반복자는 반드시 같은 컨테이너를 참조해야한다.
2) 두 번째 반복자는 첫번째 반복자로부터 도달 할 수 있어야만 한다.
3) 목적지 범위는 덮어쓸 수 있도록 충분한 원소를 가지고 있어야한다. 그렇지 않다면, 삽입반복자를 사용해야만 한다.
(예제)
vector
vector
/* 런타임에러
for(int i=0; i<=9; i++) { coll2.push_back(i); }
/* 런타임에러
/* 런타임에러
첫번째 범위로 들어온 컨테이너가 서로다르다. */ copy(coll1.begin(), coll2.end(), coll1.begin());
트랜잭션의-안정적인 컨테이너를 원한다면 반드시 list를 사용해야한다.
-> 여러개의 원소를 삽입하는 연산에 대해서도 트랜잭션-안정성을 지닌다.
-> 트랜잭션 : 하나의 논리적 작업 단위로 수행되는 일련의 작업
배열 기반의 컨테이너(vector, deque)에 대해서는, 원소가 삽입되었을 경우 완전한 복구 능력을 가지고 있지 않다.
-> 이러한 복구 능력을 제공하기 위해서는 삽입 연산 전에 현재 컨테이너의 모든원소들에 대한 복사본을 가지고 있어야 한다.
-> push, pop 동작의 경우에는 모든 원소들에 대한 복사본이 필요없다. 이유는 예외가 발생하더라도 컨테이너의 아무런 영향을 끼치지
않는다.
반복자
반복자는 컨테이너가 소유한 원소들을 순회할 수 있는 객체, 컨테이너의 특정 위치를 나타낸다.
container::iterator -> 원소들을 읽기/쓰기 모드로 순회하기 위해서 제공된다.
container::const_iterator -> 원소들을 읽기 모드로 순회하기 위해서 제공된다.
반복자어댑터
삽입반복자 : - 알고리즘을 덮어쓰기 모드가 아닌 삽입 모드로 동작하게 만든다.
- 목적지가 충분한 공간을 확보하지 못할 경우 사이즈를 증가시켜준다.
1) 후위 삽입 반복자 : 컨테이너 뒷부분에 push_back()을 호출하여 원소를 삽입한다.
가능한 컨테이너 : vector, deque, list
2) 전위 삽입 반복자 : 컨테이너 앞부분에 push_front()를 호출하여 원소를 삽입한다.
가능한 컨테이너 : deque, list
3) 삽입반복자<inserter(container, pos)> : insert()를 사용하여 pos 위치를 삽입한다.
스트림 반복자 : 스트림에서부터 읽기와 쓰기가 가능한 반복자
1) istream_iterator<string>(cin) : 표준 입력 스트림 cin에서부터 읽어드리는 스트림 반복자를 생성한다.
2) unique_copy() : 알고리즘은 인접한, 중복된 값들을 제거한다.
3) ostream_iterator<string>(cout, "\n") : 컨테이너 안에 모든 string 원소들을 cout를 호출하여 출력하기 위해 출력-스트림-반복
자를 생성한다.
역방향 반복자 :
1) rbegin() -> 컬렉션의 마지막 위치
2) rend() -> 컬렉션의 처음 위치
알고리즘
remove 알고리즘 : 컬렉션의 원소 개수를 변경하지 못한다. size는 예전의 원소의 개수를 반환할 것이다. 원소의 위치는 변경된다.
copy(begin(), end(), ostream_iterator
distance 두 반복자의 거리의 차이를 보여준다.
for_each() 알고리즘은 [coll.begin(), coll.end()] 범위에 이는 모든 원소들에 대해서 사용자가 지정한 함수를 호출하게 한다.
예제) for_each(coll.begin(), coll.end(), print);
void print(int elem)
{
cout « elem « ’ ‘;
}
알고리즘을 위해서 제공되는 특별한 함수는 바로 조건자이다.
조건자는 Boolean 값을 반환하는 함수여서 정렬, 검색 조건을 제공하기 위해서 자주 사용된다.
1) 단항 조건자
- find_if() 알고리즘은 주어진 범위에서 단항 조건자가 true을 반환하는 첫번째 원소를 검색하기 위해서 사용된다.
예) find_if(coll.begin(), coll.end(), isPrime);
bool isPrime(int number) { return true; }
2) 이항 조건자 : 2개의 아큐먼트의 속성을 비교한다. 이것은 원소가 < 연산자를 제공하지 못하는 경우나 다른 정렬
기준을 만들고자 할 경우 필요한 것이다.
함수-객체 장점
1) 함수-객체는 스마트 함수이다.
- 함수객체들은 다른 멤버변수들이나 다른 멤버 함수들을 가질 수 있다는 것이다.
- 우리가 호출하기 전에 이들을 런타임시에 초기화 할 수 있다는 것이다.
- 함수 객체는 인라인 함수 : 사용자는 좋은 성능을 얻을 수 있다.
2) 각각의 함수-객체는 자신만의 타입을 가지고 있다.
3) 함수-객체는 대부분의 경우 기존의 함수보다 빠르다.
- 템플릿은 컴파일 타임시에 더욱 자세한 정보들을 정의하기 때문에, 일반적으로 더욱 좋은 최적화를 제공한다.
미리 정의된 함수-객체
1) c++표준 라이브러리에서는 기본적으로 미리 정의된 여러가지의 함수-객체를 제공한다.
| less<> : < 으로 원소들을 정렬한다. | greater<> : >으로 원소들을 정렬한다. |
negate<> : 컬렉션의 모든 원소를 부정한다.( 20 -> -20 )
multiplies<> : 컬렉션 자신의 값으로 자신을 곱하게 되는 것을 말한다.
bind2nd : 두개의 인자를 하나로 동작하도록 합칠 경우 쓰인다.
예) bind2nd(multiplies
transform(coll1.begin(), coll1.end(), back_inserter(coll2), bind2nd(multiplies
remove 알고리즘은 구간 [first, last] 에서 value와 동일한 원소들을 삭제하고, 그 결과 얻어지는
(value와 일치하지 않는 원소들로 구성되는) 구간의 끝경과 반복자 i를 리턴한다.
함수 remove_if는 구간 [first, last]에서 조건자 pred를 만족시키는 원소들을 삭제하고
그 결과 얻어지는 (pred를 만족시키지 않는 원소들로 구성되는) 구간의 끝경과 반복자 i를 리턴한다.
mem_fun_ref : 원소에 대해서 명시된 멤버 함수를 호출한다.
예) mem_fun_ref(&Person::save)
STL컨테이너
컨테이너의 공통적인 특징
모든 컨테이너는 “레퍼런스 의미론”보다는 “값 의미론”을 제공한다. 즉 내부적으로 복사본을 생성한다.
일반적으로 모든 원소들은 순서를 가지고 있다. 모든 원소들을 여러 번 순회하더라도 같은 순서로 순회한다.
각각의 컨테이너는 자신의 원소를 순회할 수 있도록 자신의 반복자를 가진다.
STL 자체는 예외를 발생시키지 않는다. 호출자는 동작에 대한 정확한 인자를 제공해주는 것을 보장해주어야 한다.
컨터에너의 공통적인 동작
초기화
모든 컨테이너 클래스들을 디폴트 생성자, 소멸자, 복사생성자 등을 제공한다.
주어진 범위의 원소들로 이 컨테이너들을 초기화 할 수 있다.
예) 표준 입력을 통한 초기화
std::deque<int> c((std::istream_iterator<int>(std::cin)),
(std::istream_iterator<int>())); - O
std::deque<int> c(std::istream_iterator<int>(std::cin)),
(std::istream_iterator<int>()); - x
=> 2번째는 deque
사이즈와 관련된 동작 : empty(), size(), max_size()
비교와 관련된 동작 : ==, !=, <, <=, >, >=
비교연산자는 3가지의 규칙에 따라서 정의된다.
1) 두 컨테이너는 같은 타입을 가지고 있어야만 한다.
2) 만약 두 컨테이너의 원소들이 같고, 이들의 순서 또한 같다면, 두 컨테이너는 같다.
3) 다른 컨테이너에 비해 작은 것을 검사하기 위해서 사전방식의 비교가 사용된다.
할당과 swap()
컨테이너를 할당한다면, 이것은 소스 컨테이너의 모든 원소들이 목적지 컨테이너의 복사되고, 목적지 컨테이너의 모든 예전 원소들은
제거된다. 오래걸리는 작업이 됨.
swap() 컨테이너의 사용하면 오로지 데이터를 참조하는 포인터만 교체해서 할당연산과 선형 복잡도 대신 상수 복잡도를 보장한다.
vector - #include
vector의 원소는 할당 가능하고 복사 가능하다면 어떠한 타입도 가능하다.
vector는 원소를 자신의 내부 동적 배열에 복사하다.
vector는 순서가 있는 컬렉션이다.
vector는 랜덤액세스를 지원한다. 그러므로 모든원소를 액세스하는 작업은 상수 시간의 복잡도를 가진다.
vector는 끝에 원소를 추가하거나 제거한다면 정말로 놀라운 성능을 가진다.
vector는 중간이나 앞부분에 삽입한다면 성능은 매우 저하될 것이다.
vector의 능력
capacity() : 실제 메모리 안에 vector가 메모리의 재할당 없이 가질 수 있는 원소의 개수를 반환한다. capacity를 초과한다면
내부메모리 재할당해야한다.
재할당은 원소의 모든 레퍼런스와 포인터, 반복자를 무효화시킨다.
재할당은 시간을 필요로 한다.
-> 사용자가 vector 안의 포인터나 레퍼런스, 반복자를 관리하거나 또는 속도가 목표라면 용량을 먼저 확보해 두어야한다.
용량 할당할 시 reserve() 사용하면 됨( v.reserve(80) ) // 80개의 원소를 위해 메모리를 확보한다.
( vector<T> v(5) )
-> 디폴트 생성자가 제공되는 복잡한 타입에 대해서는 초기화에시간이 걸리기 때문에 초기화를 하는 이유가 재할당이라면 reserve를
사용해야함
c.assign(begin, end) : [begin, end] 범위의 원소를 할당한다.
예) coll.assign(l.begin(), l.end()) //l의 내용을 복사하여 coll을 만든다.
c.at(idx) : 인덱스가 idx인 원소를 반환한다(만약 idx 가 범위를 벗어났다면 범위 에러 예외를 발생시킨다. : out of range)
c[idx] : 인덱스가 idx인 원소를 반환한다(에러 검사를 하지 않는다.)
c.erase(pos) : 반복자 pos위치의 원소를 제거한다. 그리고 다음 원소의 위치를 반환한다.
c.clear() : 모든 원소들을 제거한다.(빈 컨테이너로 만든다.)
deque : - vector와 같이 동적 배령에 원소들을 관리하며 랜덤 액세스를 지원한다.
- vector와 다른점은 deque는 양방향에 대해서 개방형이다.
- coll.assign(3, string("string")) -> 출력 : string string string
deque를 아래와 같은 상황이면 deque를 선호하는 것이 좋다.
1) 컨테이너의 앞,끝 쪽으로 삽입과 삭제가 이루어 지는경우
2) 컨테이너의 원소들을 참조하지 않는 경우
3) 컨테이너가 더 이상 사용되지 않을 경우 메모리가 해제되기를 원한 경우
deque와 vector가 다른 경우
1) deque는 용량에 관한 함수를 제공하지 않는다.(capacity()와 reserve())
2) deque는 앞부분과 끝부분에 직접적으로 원소를 추가,삭제할 수 있는 함수를 제공함(push_front, pop_front)
List
랜덤액세스를 지원하지 않는다. 그래서 at()을 지원하지 않는다.
어떠한 위치에서건 삽입과 삭제가 빠르게 이루어진다. 다른 원소들은 이동될 필요가 없기 때문에 삽입, 삭제 동작은 상수시간
복잡도를 가지게 된다.
삽입과 삭제 동작이 모든 포인터와 레퍼런스, 그리고 반복자를 무효화시키지는 않는다.
list는 거의 대부분의 동작들이 성공하거나 아니면 아무런 영향도 받지 않는 예외 핸들링을 제공한다.
list의 멤버 함수들은 vector와 deque와의 다른점
list는 용량과 재할당과 관련된 함수들을 제공하지 않는다.
list는 원소를 이동시키기 위한 특별한 멤버함수들을 제공한다. 포인터 값만 재지정하므로 속도가 빠르다.
원소의 삽입 및 제거
할당연산자와 sort()를 호출하지 않는다는 점을 보장하고 원소를 비교하는 동작에서 예외를 던지지 않는다면, list는 트랜잭션에 대해
안정성을 보장한다고 말할 수 있습니다.( STL의 컨테이너 중 가장 강력한 예외 안정성을 제공함.)
c.unique : 같은 값을 가지는 연속된 원소들의 중복을 제거한다. 예) 1,2,3,3,4,3 -> 1,2,3,4,3
coll1.splice(find(coll1.begin(), coll1.end(), 3), coll2);
-> coll1서 값을 3으로 가지는 원소의 위치에 coll2 의 모든 원소를 삽입한다.
set과 multiset : 정렬방법 : 균형 2진 트리
반드시 반대칭적이여야만 한다. : x < y : true, y < x : false
반드시 추이적이이어야만 한다. x<y : true, y<z : true, x < z : true
반드시 동등함에 대해서 추이적이여야만 한다. a=b, b=c, a=c
반드시 비반사적이어야만 한다. x < x : 항상 false, op(x, x) L false
자동정렬을 할 경우 특정 값으로 검색을 할 경우 바이너리 트리가 좋은 성능을 보장해 준다는 것이다.
자동정렬 단점 : 원소의 값을 직접적으로 수정이 불가능함.(모든 원소값이 상수이므로)
정렬기준은 두가지로 정의
템플릿의 파라미터로 넣는방법 : set<int, greater
생성자의 파라미터로 넣는방법 : 안할경우 디폴트로 less<> 오름차순 정렬
정렬기준의 3가지 장점
정렬 기준으로 단 하나의 인자만을 전달하면 된다.
컨테이너에 사용되는 원소에 대해서 == 연산자를 제공하지 않아도 된다.
두 원소의 동일성에 대해서 정반대의 정의를 가질 수 있다.
1) lower_bound(elem) : elem의 값보다 크거나 같은 값을 가지는 원소의 위치를 반환한다.
2) upper_bound(elem) : elem의 값보다 큰 값을 가지는 원소의 위치를 반환한다.
3) equal_range(elem) : 정렬된 상태를 깨트리지 않고 elem이 삽입될 수 있는 첫 번째 위치와 마지막 위치를 반환한다.
예제)
set
c.insert(1); c.insert(2); c.insert(4); c.insert(5); c.insert(6); c.insert(8);
cout « *c.lower_bound(7) « endl; -> 7크거나 같은 값을 찾아서 리턴한다. : 8 cout « *c.equal_range(5).first « endl; -> 5가 삽입될 수 있는 첫번째 : 5 cout « *c.equal_range(5).second « endl; -> 5가 삽입될 수 있는 두번재 : 6
- 모든 연관 컨테이너와 마찬가지로 반복자는 양방향 반복자이다. 그러므로 랜덤액세스 반복자를 요구하는 알고리즘을 사용할 수 없다.
- 반복자의 입장에서 볼 때 컨테이너의 모든 원소들이 상수로 취급된다는 것이다. 값을 수정하는 알고리즘의 종류들로 set, multiset에
대해서 사용할 수 없다.
- insert 같은 경우에는 반환값이 pair로 반환이 된다. 그 이유는 성공여부를 확인하기 위해서이다. 중복이 허용되지 않으므로
-> first 멤버는 새롭게 삽입된 원소의 위치, second는 삽입 연산의 성공여부
- set 의 erase는 제거된 원소의 개수를 반환한다. set은 중복이 안되므로 0, 1이 나타나게 될 것이다.
- set<int> coll2(coll1.begin(), coll1.end()); // 다른 set에 오름차순으로 모든 원소들을 할당한다.
- 런타임시에 정렬 기준을 결정
map
key/value 쌍은 반드시 할당 가능해야 하며, 복사 가능해야한다.
key는 반드시 정렬 기준에 따라서 비교될 수 있어야 한다.
정렬기준에 두가지 방법
템플릿의 파라미터로 넣는방법 : map<string, greater
생성자의 인자로 넣는방식(특별한 비교 객체 타입을 가지는 map) - 런타임시 처리된다.
typedef map<string, string, RuntimeStringCmp> StringStringMap; // RuntimeStringCmp->class
set과 map의 차이점
key를 가지고 정렬을 하기 때문에 value로 검색할 경우 성능이 느려질 수 있다.
key값은 삽입 후 상수가 되기 때문에 수정이 불가하다.
1) value_type을 사용 : 암시적인 형 변환을 피하기 위해서 사용자는 value_type를 사용하여 정확한 타입을 전달해야함.
예) std::map<std::string, float> coll;
coll.insert(std::map<std::string,float>::value_type("otto", 22.3));
2) pair<> 사용
예) std::map<std::string, float> coll;
//암시적인 형변환 가능
coll.insert(std::pair<std::string,float>("otto", 22.3));
//암시적인 형변환 불가능
coll.insert(std::pair<const std::string,float>("otto", 22.3));
3) make_pair() 사용
예) std::map<std::string, float> coll;
coll.insert(std::make_pair("otto", 22.3));
if(coll.insert(std::make_pair("otto", 22.3)).second) // 새로운 원소의 삽입 결과를 검사한다.
- 사용자는 key 대신 특정 value를 가진 원소를 제거하기 위해서 find() 멤버함수를 사용할 수 없다.
연관 배열처럼 map을 사용하기
연관 컨테이너들은 일반적으로 원소를 직접적으로 액세스할 수 있는 기능을 제공하지 않는다. 하지만 map은 key값을 직접 액세스가
가능하다. - 키 변경 가능
반복자로는 키 변경안됨( c.first = 3 (x))
예) map<string, float> coll;
coll["otto"] = 7.7; // 이 경우에도 map에 자동적으로 추가가 된다.
map을 대입했을 경우
typedef map<string, float> StringFloatMap;
StringFloatMap stocks;
stocks[“BASF”] = 369.50; stocks[“VW”] = 413.50; stocks[“DAIMLER”] = 819.00; stocks[“BMW”] = 834.00;
stocks[“Volkswagen”] = stocks[“VW”]; //Volkswagen key가 추가되고 그 value값은 VW에 있던 value값이 들어가게 됨.
StringFloatMap::iterator pos;
pos = stocks.begin();
for(pos; pos != stocks.end(); pos++) { cout « pos->first « ’ ‘ « pos->second « endl; }
cout.setf(ios::left, ios::adjustfield) -> 왼쪽정렬
setw(10) : 데이터가 출력될 화면의 폭을 10으로 지정
setfill(‘-‘) : 화면에 뿌려줌.
다른 STL 컨테이너
사용자가 작성한 컨테이너를 STL 컨테이너처럼 만들기 위해서는 3가지 접근방법이 있다.
1) invasive 접근방법 : 기능을 제공
- 사용자는 STL 컨테이너가 필요로 하는 인터페이스를 제공해주기만 하면 된다
2) noninvasive 접근방법 : iterator와 같은 반복자를 사용
- 사용자느 알고리즘과 특별한 컨테이너의 상호 인터페이스로 사용되는 특별한 인터페이스를 작성하거나 제공해야한다.
3) wrapper 접근방법
- 내부 자료를 은닉하고 랩퍼 클래스를 작성한다.
해쉬테이블 - 바이너리 트리보다 검색속도가 5,10배 정도 빠르다.
언제 어느 컨테이너를 사용할 것인가?
vector는 간단한 내부 자료구조를 사용하며 랜덤 액세스를 지원한다. 그러므로 데이터 액세스 작업이 편리하며 유연하다. 처리속도가
빠르다.
앞과 끝부분에 자주 삽입/제거가 된다면 반드시 deque를 사용하고 원소가 제거될 때 컨테이너의 메모리 사용량이 줄어들어야만
한다면, deque를 사용하라
원소의 제거, 삽입, 이동을 컨테이너의 중간 위치에서 자주 한다면, list를 고려해봐야한다. list는원소의 이동을 위해서 상수 시간의
복잡도를 가지는 멤버 함수를 제공한다. 하지만 랜덤 액세스를 제공하지 않은 불편한 점이 있다.
list는 원소들이 컨테이너의 속해 있는 한 원소를 참조하는 반복자를 무효화시키지 않는다.
vector는 용량이 넘어가면 모든 반복자와 포인터, 그리고 레퍼런스를 무효화시킨다.
deque의 경우 사이즈가 변경되면, 모든 반복자와 포인터와 레퍼런스등이 무효화된다.
특정 기준에 따라 자주 원소를 검색해야 한다면 set, multiset을 이용한다. 이유는 바이너리 트리로 정렬되기 때문에 검색속도가 빠름
key/value의 쌍을 처리하기 위해서는 map, multimap을 사용해야한다.
연관배열이 필요하다면 map, 사전이 필요하다면 multimap을 이용해야한다.
=> remove_if(coll2.begin(), coll2.end(), bind2nd(less
삭제가 된다.
1) value_type을 사용 : 암시적인 형 변환을 피하기 위해서 사용자는 value_type를 사용하여 정확한 타입을 전달해야함.
coll.insert(std::map<std::string,float>::value_type("otto", 22.3));
2) pair<> 사용
coll.insert(std::pair<std::string,float>("otto", 22.3))
3) make_pair() 사용
coll.insert(std::make_pair("otto", 22.3));
STL반복자
반복자 헤더 파일
모든 컨테이너는 자신만의 반복자 타입을 정의하고 있기 때문에 특별한 헤더 파일을 요구하지 않는다.
그러나 역방향 반복자와 같은 특별한 반복자에 한해서는
반복자 카테고리
반복자는 컨테이너 안에 저장된 원소들을 차례대로 순환할 수 있는 객체이다.
입력 반보자는 오로지 원소를 전진 방향으로만 “읽기(read)” 액세스를 할 수 있는 반복자이다.
한번의 한개의 원소만을 읽어들일 수 있다
성능상을 고려해볼때 후위 증가 연산자보다는 전위 증가 연산자를 사용해야 한다. 전위증가 연산자는 임시 객체를 생성하지 않으므로
오로지 원소를 전진 방향으로만 “쓰기(write)” 액세스할 수 있는 반복자이다.
출력 반복자는 반복자를 비교할 필요가 없다. (iter == iter2 지원안함)
랜덤액세스를 할 수 있는 양방향 반복자
-,+와 같은 반복자 연산자를 제공, 오프셋을 더하거나 뺄 수도 있으며 <,> 연산자를 사용하여서 두 반복자를 비교할 수도 있다.
[] 사용가능
램덤 액세스를 제공하는 컨테이너(vector, deque), 스트링(string, wstring), 기본의 배열(array, pointer)
std::vector
sort(++coll.begin(), coll.end());
-> vector, string이외에는 구현이 가능함.
이유 : vector 반복자 구현을 일반적인 포인터처럼 구현하였기 때문이다. C++언어에서는 포인터와 같은 모든 기본 데이터 타입에
대해서 임시 변수를 수정하는 것을 허락하지 않는다.(deque, list, map, set 가능(class로 구현되어 있음)
보조 반복자 함수
advance() 함수는 인자로 전달된 값만큼 반복자의 위치를 증가시킨다.
반복자의 위치를 하나 이상의 원소로 증가시킬 수 있다.
advance는 아무것도 반환하지 않는다는 것이다.
램덤 액세스 반복자가 아닌 다른 반복자가 사용될 경우, 성능의 저하를 가져 올 수 있다.
distance() 함수는 두 반복자의 위치 차이를 계산하기 위해서 제공된다.
Dist distance(InputIterator pos1, InputIterator pos2)
입력 반복자 pos1, pos2 사이의 거리를 반환한다.
pos1과 pos2는 같은 컨테이너에 존재하는 원소를 가리키고 있어야 한다.
만약 pos1과 pos2가 랜덤액세스 반복자가 아니라면, pos2는 pos1보다 크거나 같아야만한다.
반환값인 Dist는 반복자 타임에 따른 diifference_type이다.
iter_swap()을 사용하여 반복자의 값 교체
반복자 어댑터
역방향 반복자(Reverse Iterators)
역순 모드로 동작하기 위해서 기존의 증가, 감소 연산자를 재정의한 어댑터이다.
rbegin(), rend()
for_each에 대한 설명
for_each(coll.begin(), coll.end(), print) -> coll.end()에 쓰레기 값이 들어가므로 그 전 데이터까지를 출력해준다.
그렇기 때문에 iterator pos = find(coll.begin(), coll.end(), 7) -> iterator에 pos를 두번째에 넣는 경우 그전데이터를 출력해준다.
base()를 사용하여 역방향 반복자를 일반 반복자로 전환하기
삽입반복자
1) 후위 삽입 반복자 : vector, deque, list, string 제공
- 후위 삽입 반복자는 생성할 때 반드시 사용할 컨테이너와 함께 초기화되어야 한다.
예) vector<int> coll;
back_insert_iterator<vector<int> > iter(coll);
2) 전위삽입 반복자 : deque,list만 제공
- 후위 삽입 반복자는 생성할 때 반드시 사용할 컨테이너와 함께 초기화되어야 한다.
예) list<int> coll;
front_insert_iterator<vector<int> > iter(coll);
3) 일반적인 삽입반복자
- inserter_iterator<set<int> > iter(coll, coll.begin());
inserter(coll, coll.end()) = 44;
inserter(coll, coll.end()) = 55;
copy(coll.begin(), coll.end(), inserter(coll2, coll2.begin());
copy(coll.begin(), coll.end(), inserter(coll2, ++coll2.begin());
스트림 반복자
알고리즘에 출력 스트림 반복자를 사용함으로써 알고리즘의 결과를 스트림에 직접적으로 기록할 수 있다.
« 연산자를 사용하여 기록한다는 점이다.
입력 스트림 반복자를 생성할 때에는 읽어들일 입력 스트림과 함께 초기화되어야 한다.
연산자를 사용하여 읽어 드일 수 있다.
“끝 스트림 반복자” 입력 스트림 반복자의 디폴트 생성자와 함께 생성된다. 사용자는 작업이 있은 후에 항상 “끝 스트림 반복자”를
비교해야만 한다. - 디폴트 생성자와 함께 생성된다.
입력 스트림 반복자의 생성자에서는 스트림을 열고 첫 번째 원소를 읽어 들인다는 사실이다. 이유는 초기화된 이후에 사용자가
*연산자를 호출할 경우 반환할 워노가 없기 때문이다.
반복자 특성
1) 반복자의 타입 사용하기
- typename std::iteraqtor_traits<T>::value_type tmp;
2) 반복자 카테고리 사용하기
- 사용자의 템플릿 함수가 추가적인 인자로 반복자 카테고리를 사용하는 다른 함수를 호출시키도록 한다.
- 각각의 반복자 카테고리에 대해서 또 다른 함수를 구현한다.
템플릿 부분 특수화(STL 은 부분 특수화를 통해 iterator_traits)
배열의 요소 타입을 알아 내기 위해 템플릿의 부분 특수화를 알아야 한다.
iterator_traits 는 반복자 특질이라고 번역되지만 사실, 반복자와 배열의 포인터를 구분하기 위해서 만들어진 것이다
template
template
함수객체
함수-객체의 개념
() 연산자를 정의한 객체
함수구문 안에 모든 함수에 대한 내용을 코딩하는 것이 아니라, 함수-객체 클래스의 () 연산자 안에 코딩해야만 한다.
1) 함수-객체 장점
+ 같은 함수-객체에 대해서 동시에 다른 상태를 가질 수 있다는 것이다.
+ 각각의 함수-객체는 자신만의 타입을 가진다. 사용자는 함수-객체의 타입을 템플릿 인자로 제공할 수 있다.
+ 함수-객체는 함수 포인터보다 빠르다.
정렬 기준으로서의 함수-객체(사용자가 정렬기준 관련 함수-객체를 만들 수 있다.)
내부 상태를 가지는 함수-객체
예제)
class IntSequence { private: int value;
public: IntSequence(int initialValue): value(initialValue) {
} int operator() () { return value++; }
};
int main()
{
list
generate_n(back_inserter(coll), 9, IntSequence(1));
PRINT_ELEMENTS(coll);
generate(++coll.begin(), –coll.end(), IntSequence(42));
PRINT_ELEMENTS(coll); }
=> reference 대신에 value로 전달된 함수-객체는 개체의 상태를 변경시키지 않는 상수성과 임시 객체성이라는 장점이 있다.
만약 reference로 전달되었다면 IntSequence(1) 두번 호출이 안될 것이다.
하지만 함수-객체의 상태를 수정할 경우에 얻는 이득을 얻을 수 없었을 것이다.
1) 함수-객체를 레퍼런스로 전달하는 것이다.
2) for_each() 알고리즘의 반환값을 사용하는 것이다.
1-1. Reference와 value에 차이
1) generate_n<back_insert_iterator<list
2) generate_n(back_inserter(coll), 10, IntSequence(1));
-> reference를 넘겼으므로 seq의 value값이 저장되며 다시 호출시 저장된 숫자 이후부터 출력
value를 넘겼으므로 IntSequence class의 value값이 저장되지 않는다.
2-1. for_each()의 반환값
1) for_each() 알고리즘은 함수-객체를 반환하는 특별한 속성을 가지고 있다.
2) for_each() 알고리즘의 반환값을 검사함으로써 함수-객체의 상태를 얻어올 수 있다.
3) 조건자와 함수-객체
함수어댑터란 함수-객체를 특정 값 또는 특정 함수와 결합시킨 함수-객체이다.
함수어댑터는 그들 스스로 함수-객체가 된다. 따라서 사용자는 함수 어댑터를 함수-객체와 결합하여 사용할 수 있다.
기능조합법 : 합수 어댑터를 사용하여, 다른 함수-객체들을 매우 강력한 형태로 결합시키는 것
1) mem_fun,mem_fun_ref
- mem_fun_ref(op) 객체에 대해서 멤버 함수 op()를 호출한다. vector<Person>& coll
- mem_fun(op) 객체의 포인터에 대해서 멤버 함수 op()를 호출한다. vector<Person*>& coll
- mem_fun, mem_fun_ref는 반드시 상수멤버함수라는 점을 알아야한다.
for_each(coll.begin(), coll.end()
- mem_fun_ref(&Person::print), mem_fun(&Person::print)
4. 함수어댑터를 위한 사용자 정의 함수객체
- 함수-객체의 인자와 결과를 위해서 타입 멤버를 제공해야한다.
5. 조립함수-객체
- 기존의 다른 컴포넌트에서 새로운 컴포넌트를 만들고자 할 경우 매우 유용하게 사용할 수 있다. 단순한 함수-객체에서 아주
복잡한 형태의 함수-객체를 생성하는 것을 가능하게 한다.
알고리즘
모든 STL 알고리즘은 하나 이상의 범위에 대해서 원소를 처리한다.
범위의 시작과 끝을 명시하며, 이외의 범위에 대해서는 대부분의 경우 시작 범위만을 제공한다.
두번째 범위의 끝은 첫번쨰 범위의 원소 개수에 따라 달라지기 때문이다.알고리즘의 호출자는 범위가 유효하다는 사실을 보장해야한다
알고리즘은 사용자가 정의한 동작들을 받아 들일 수 있으며, 알고리즘은 이 함수들을 내부적으로 호출이 가능하다. 그중에서 boolean
값을 반환한다면 이것을 조건자라고 말한다.
조건자들은 함수 호출에 대해서 자신의 내부 상태를 변경해서는 안된다. : 두개의 값이 동일한 단항 조건자를 호출하였다면, 조건자는
항상 같은 결과를 반환할 것이다.
알고리즘의 분류
두가지 종류의 알고리즘이 존재하며, 동일한 개수의 인자를 받아들이는 경우에 사용된다.
_if가 없다면 value가 사용되고, _if가 존재한다면 value 대신 함수 또는 함수-객체가 사용된다.
예) find는 특정한 값을 가지는 원소를 검색하지만, find_if()의 경우에는 전달된 함수 또는 함수-객체의 기준을 만족시키는 원소를
검색한다.
- count(coll.begin(), coll.end(), 4); count_if(coll.begin(), coll.end(), isEven - 함수/함수-객체 호출);
인자로 함수 또는 함수-객체를 사용한다고 해서 _if 접미사를 가지는 것은 아니다. 즉, 인자의 개수가 다르다면 그냥 동일한 이름을
사용한다.
min_elemnet() 주어진 범위에서 가장 작은 원소를 발견해서 그 위치를 반환하지만 인자가 3개이면 비교기준으로 사용된다.
원소를 수정하지 않는 알고리즘
for_each()는 주어진 범위의 각각의 원소에 대해서 특정 동작을 호출한다.
각각의 원소를 개별적으로 처리하고자 할 경우 사용된다.
하지만 for_each()은 원소를 수정하는 동작도 호출할 수 있어서 원소를 수정, 수정하지 않은 알고리즘 2개의 다 속한다.
원소를 수정하는 알고리즘
bind2nd의 설명
//5와 같거나 큰 값을 찾아 그 위치를 반환한다.
find_if(vec.begin(), vec.end(), bind2st(std::greater_equal
bind2nd 인자를 두 개를 받는 함수를 하나로 받는 함수로 바꾸는 것이다.
sort 같은 함수의 경우 인자가 하나인 함수만을 받기 때문에 2개를 하나로 바꿔주는 bind를 쓰게 된 것이다.
for_each()
예) void square(int& elem) -> 수정할 값을 인자로 받기 때문에 call_by_reference 참조자로 받아야한다.
{
elem = elem*elem;
}
for_each(coll.begin(), coll.end(), square)
transform
수정된 값을 반환하는 함수를 사용한다.
원소를 직접적으로 수정하기 보다는 새로운 값을 할당하여 반환함으로 약간은 더 느리다.
예) transfrom(coll.begin(), coll.end(), //소스범위
coll.begin(), 목적지범위
bind2nd(plus<int>(), 10));
제거 알고리즘(remove, unique)
단 하나의 범위에 존재하는 원소들 뿐만 아니라 다른 범위에 복사된 원소들도 제거할 수 있다.
제거 알고리즘은 원소를 논리적으로 제거한다는 것이다. 즉, 실제로 제거하는 것이 아니라 제거되지 않은 다음 원소로 덮어쓰기한다.
알고리즘을 수행한 이후에도 원소 개수는 변경되지 않는다. : 새로운 논리적인 끝 위치를 반환해준다.
변경 알고리즘(reverse, rotate, random)
원소의 값을 교체하거나 새롭게 할당하여 원소의 순서를 변경시키는 알고리즘
정렬 알고리즘
원소의 순서를 변경하기 때문에 변경 알고리즘의 특별한 형태라고 할 수 있다.
정렬 알고리즘은 좀 더 복잡하기 때문에 시간이 더 걸린다.
sort는 퀵 소트에 기반을 두고 있다. 평균적으로 n*log(n) 복잡도의 성능을 가지지만 최악의경우에는 이차의 복잡도를 가진다.
partial_sort() 힙 소트에 기반을 두고 있다. 힙 소트는 퀵 소트보다 느리다. 장점은 언제든지 n*log(n)의 복잡도를 가진다.
statble_sort() 머지 소트에 기반을 두고 있다. 충분한 메모리를 가진 경우 nlog(n)가지지만 아닐경우 nlog(n)*log(n) 복잡도를 가짐
같은 값을 가지는 원소의 상대적인 순서를 유지하는 것이다.
nth_element() 알고리즘은 n번째까지 정렬된 원소가 필요한 경우나, n개의 가장 큰 원소의 집합 또는 n개의 가장 작은 원소의 집합을
구할 수 있다.
예) nth_element(coll.begin(), coll.end()+3, coll.end()); //가장 작은 4개의 원소를 앞으로 이동한다.
partition의 경우, 사용자는 첫번째 파트와 두 번째 파트를 구별하기 위해서 정렬기준을 전달해 줄 수 있다.
예) vector
pos = partition(coll1.begin(), coll1.end(), bind2nd(less<int>(), 7)); //7보다 작은 원소는 모두 앞으로 이동한다.
stable_partition() : 같은 파트 안에서 다른 원소와의 상대적인 위치가 유지된다는 것이다.
list의 경우는 램덤 액세스 반복자를 사용하지 않기 때문에 정렬 알고리즘을 사용할 수 없다. 따라서 list는 원소를 정렬하기 위해서 멤버
함수로 sort()를 제공한다.
정렬된 범위 알고리즘
수치 알고리즘
수치 원소들을 다른 방법으로 결합할 수 있다.
acuumlate : 모든 원소들의 값을 결합한다.
inner_product : 두 시퀀스의 내적을 계산한다.
adjacent_difference : 상대적인 값을 절대적인 값으로 변경한다.
partial_sum : 절대적인 값과 상대적인 값으로 변경한다.
보조 함수
1) op(elem)을 호출한다.
op의 모든 반환값들은 무시된다.
원소의 멤버 함수를 호출하기 위해서는 mem_fun 어댑터를 사용하면 됨( class에 멤버함수 호출시 사용)
함수객체의 장점은 런타임시의 처리할 수 있다.
원소를 수정하지 않는 알고리즘
count, 최소(min_element), 최대(max_element), 검색(find)
find(), find_end()
-> find는 첫번째 범위의 위치를 반환하지만, find_end의 경우에는 맨 마지막 부분 범위의 위치를 반환한다.
find(beg, end, 4) -> 값
find_first_of(beg, end, searchBeg, searchEnd) -> 범위
검색 조건과 매치되는 원소들이 연속적으로 n번 나타나는 경우를 검색(search)
첫번째 부분 범위 검색(search) : 두번째 범위가 첫번째 범위에 부분(subrange)범위가 되는지 검사한다.
adjacent_find(beg, end, (op)) : 연속 중복 원소의 검색
1,3,2,4,5,5,0 -> 연속되는 첫번째 위치를 반환해준다.
equal, mismatch, lexicographical_compare(사전식 비교방법),
원소를 수정하는 알고리즘
연관 컨테이너를 목적지의 범위로 사용할 수 없다. 연관 컨테이너의 원소들은 상수이기 때문이다.
원소의 복사(copy, copy_backward(역방향으로 순회함), 원소의 변경 또는 결합(transform)
[beg1, end1)의 범위의 원소들을 beg2로 시작하는 원소들과 교체한다.
두 번째 범위에서 마지막으로 교체된 원소 이후의 위치를 반환한다.
만약 컨테이너 타입이 동일하다면 swap 멤버 함수를 사용해야 한다.
fill 알고리즘은 [beg,end) 범위의 모든 원소에 newValue를 할당한다.
fill_n 알고리즘은 [beg, beg+num) 범위의 모든 원소에 newValue를 할당한다.
TIP : 컨테이너 선언 후 fill_n 을 사용할 경우 첫번째 인자를 begin()으로 넣지 말고 insert로 넣어야한다.
예) generate_n(back_insert(coll), 5, rand); generate(coll.begin(), coll.end(), rand);
beg,end 범위에서 oldValue오 동일한 값을 가지는 원소들을 모두 newValue로 교체함
제거 알고리즘
원소의 개수는 변경하지 않으며, 제거된 원소들을 제거되지 않은 다음의 원소들로 덮어쓰는 것이다.
1) 시퀀스 안의 원소들을 제거(remove, remove_if)
- beg,end 범위 안에서 value와 동일한 값을 가지는 원소를 제거한다.
- 수정된 시퀀스의 논리적인 마지막 위치를 반환한다.
- 제거된 원소들을 제거되지 않은 다음의 원소로 덮어쓴다.
- 이 알고리즘은 원소를 수정하기 때문에 연관 컨테이너와는 사용할 수 없다.
2) 원소의 제거 결과를 복사하여 전달<remove_copy(beg,end,destBeg,value), remove_copy_if(beg,end,destBeg,op)>
- [beg,end)의 소스 범위에서 value와 동일한 값을 가지는 원소를 제거하고 destBeg에 복사한다.
3) 중복된 원소의 제거<unique(beg,end), unique_if(beg,end,op)>
- [beg,end) 범위에서 연 속 중복인 원소를 제거한다. (연속중복이란, 범위 내에서 자신의 바로 뒤에 위치한 원소를 의미)
- 수정된 시퀀스의 새로운 논리적인 끝 위치를 반환한다.
- 제거되지 않은 원소의 순서는 동일하게 유지된다.
- 알고리즘의 원소를 수정하기 때문에 연관컨테이너를 사용하면 안된다.
4) 중복 원소의 제거 결과를 복사하여 전달<unique_copy(beg, end, destBeg), unique_copy_if(beg, end, destBeg, op)>
- [beg,end)의 모든 원소를 destBeg로 시작하는 목적지 범위에 복사한다. _if 같은 경우에는 op가 true로 반환될때 수행되어 진다.
Tip : unique를 통해서 연속 중복을 제거하지만, 사이즈는 변경되지 않으므로, 제거를 위해서는 erase를 사용해야한다.
변경 알고리즘
변경 알고리즘은 원소의 순서를 변화시키고 값은 변경하지 않는다. 연관 컨테이너의 원소는 고정된 순서를 가지기 때문에 이
알고리즘의 목적지로 사용할 수 없다.
[beg,end) 범위 안에 원소들의 순서를 뒤집고 ,_copy가 붙으면 destBeg로 시작하는 목적지 범위에 복사해 둔다.
reverse_copy 알고리즘은 목적지 범위에 마지막으로 복사된 원소 이후의 위치를 반환한다.
리스트는 이와 동일한 멤버 함수인 reverse()를 제공한다. 이 멤버 함수는 원소의 값을 할당하는 대신 포인터만 재연결하기 때문에
더 좋은 성능을 보장한다.
1) 시퀀스 안의 원소들을 회전<roate(beg, newBeg, end)>
// 왼쪽으로 한 개의 원소를 회전시킨다. // 오른쪽으로 두 개의 원소를 회전시킨다.
- rotate(coll.begin(), //범위의 시작위치 - rotate(coll.begin(), //범위의 시작위치
coll.begin()+1, //새로운 첫번째 원소 coll.end()-2, //새로운 첫번째 원소
coll.end()); // 범위의 끝 coll.end()); // 범위의 끝
2) 회전된 원소의 결과를 복사하여 전달<rotate_copy(beg, newBeg, end, destBeg)>
- [beg, end) 범위 안에 원소들을 회전시킨다.
- Tip : advance는 iterator 반복자의 위치를 변경해준다.(advance(iter, 1) == ++iter)
원소의 순열 계산
next_permutation() 알고리즘은 [beg, end) 범위 안에 있는 원소들의 순서를 다음 순열(next permutation)에 따라 변경한다.
원소가 사전식 순서를 가지고 있다면 false를 반환(오름차순 정렬에선 false 반환)
prev_permutation() 알고리즘은 [beg, end) 범위 안에 있는 원소들의 순서를 지난 번 순열(previous permutation)에 따라 변경한다.
원소가 사전식 순서를 가지고 있다면 false를 반환(내름차순 정렬에선 false 반환)
원소를 뒤섞음<random_shuffle(beg, end), random_shuffle(beg, end, op(함수객체로 넘김))>
첫번째 형태는 [beg,end) 안의 원소들의 순서를 난수 생성기를 이용하여 무작위로 재배치한다.
두번째 형태는 [beg,end) 안의 원소들의 순서를 op를 이용하여 무작위로 재배치한다. op는 0보다 크거나 같고, max보다는 작은
난수를 반환해야한다.
op가 비상수 레퍼런스인 이유는 난수 생성기들을 자신만의 내부 상태를 가지고 있기 때문이다. 예전 C언어어에서는 rand()들을
자신의 내부 상태를 static 변수에 저장하였고 멀티스레드링 상황에서는 동기화에 문제가 발생할 수 있다. 그러므로 완전히 독립된
난수 생성을 위해서 새로운 난수가 생성되는 동안 내부 상태가 변경되므로 op는 비상수 레퍼런스이다.
조건을 만족하는 원소를 앞쪽으로 이동하기<partition(beg, end, op), stable_partition(beg, end, op)>
[beg,end) 범위 안에서 단항 조건자 op(elem)가 true를 반환하는 원소를 그렇지 않은 원소들보다 앞쪽으로 배치한다.
op가 false를 반환하는 첫번째 위치를 반환한다.
stable_partition()과 partition() 알고리즘의 차이는 원소들의 상대적인 순서가 그래도 유지되는가 하는 것이다.
정렬 기준에 따라 원소를 두 부분으로 쪼개는 용도로도 이 알고리즘은 사용할 수 있다.
=> nth_element()와 partition()의 차이점
-> 1) nth_element() : 사용자는 첫번째 파트에 몇 개의 원소를 가질 지 결정 할 수 있다.
예) nth_element(coll.begin(), coll.end()+3, coll.end()); //가장 작은 4개의 원소를 앞으로 이동한다.
2) partition() : 첫번째 파트와 두번째 파트를 구별하기 위해서 정렬 기준을 전달해 줄 수 있다.
예) vector<int>::iterator pos;
pos = partition(coll1.begin(), coll1.end(), bind2nd(less<int>(), 7)); //7보다 작은 원소는 모두 앞으로 이동한다.
정렬 알고리즘
STL은 원소를 정렬하는 여러가지 알고리즘을 제공하고 있으며,STL은 전체에 대해서 정렬하는 알고리즘 뿐만 아니라 부분정렬
알고리즘도 제공하고 있다.
원소를 자동적으로 정렬하기 위해서 연관 컨테이너를 사용할 수 있으나 정렬된 상태를 계속적으로 유지하는 것보다는 모든 원소들을
한번에 정렬하는 것이 빠르다.
sort와 stable_sort의 차이점은 stable_sort()의 경우 동등 원소의 상대적인 위치(less<>를 유지시켜 준다는 사실이다.(2x,3x,11,12)
이 알고리즘들은 랜덤 액세스 반복자를 요구하기 때문에, 리스트에 대해서는 이 알고리즘을 사용할 수 없다.
sort(nlogn)을 보장한다. 그러나 최악의 성능이 발생한 경우를 피하는것이 중요하며 반드시 partial_sort, stable_sort를 사용해야한다
[beg,sortEnd) 범위의 정렬된 순서의 원소를 가지게 된다.
sortEnd까지는 정확하게 정렬하므로 사용자가 원하지 않는 부분은 정렬하지 않으므로써 시간을 절약할 수 있다.
sortEnd가 End와 동일하다면, 전체시퀀스를 정렬한다.
이 알고리즘은 sort()보다는 평균적으로 낮은 성능을 보이지만, sort()의 최악의 경우 보다는 좋은 성능을 보장한다.
partial_sort_copy(beg, end, destBeg, destEnd, op)
1) [beg, end)를 [destBeg, destEnd)로 원소들을 정렬한 후 복사한다.
2) 정렬 후 복사된 원소들의 개수는 소스범위와 목적지 범위의 개수 중 더 작은 쪽의 개수이다.
3) 목적지 범위 안에서 마지막으로 복사된 원소 이후의 위치를 반환한다.
지정된 위치에 시퀀스를 정렬했을 때 놓이게 되는 원소를 가져다 은 것으로서 nth중심으로 왼쪽과 오른쪽으로 분리하여 nth 왼편의
원소들이 모두 nth 오른편에 있는 원소들보다 작거나 같도록 한다.
이항조건자인(op(elem1, elem2))를 사용한다.
예) nth_element(coll.begin(), coll.begin()+3, coll.end()); 가장 작은 3개를 앞으로 정렬
nth_element(coll.begin(), coll.end()-3, coll.end()); 가장 큰 3개를 앞으로 정렬
1) nth_element, partition 차이점
- nth_element() : 첫 번째 파트에 몇 개의 원소를 가질 지 결정할 수 있다.
- partition : 첫번째 파트와 두 번째 파트를 구별하기 위해서 정렬기준을 전달해 줄 수 있다.
힙 알고리즘(바이너리 트리로 정렬해서 부모 노드의 값보다 같거나 작은 상태를 말함)
힙이란, 원소를 특별한 방법으로 정렬하는 곳에 사용되는 특별한 시퀀스이다. 바이너리 트리로 구현된 특별한 컨테이너라고 생각할 수
있다.
힙의 특징 : * 첫 번째 원소의 값은 범위에 속하는 원소들 중에서 가장 큰 값을 가지는 원소이다.
* 원소의 추가와 삭제를 로그 시간으로 할 수 있다.
make_heap(), push_heap(), pop_heap(), sort_heap() 4가지로 제공하며 sort_heap()은 실행된 이후는 힙이 아니다.
두 형태 모두 [beg, end) 범위의 원소들을 재배치하여 힙으로 구성한다.
op는 이항 조건자 op(elem1, elem2)로서 정렬기준으로 사용된다.
[beg,end) 범위 안에서 가장 큰 값을 가지는 첫 번째 원소를 마지막 위치로 이동시킨 후 [beg,end-1) 범위에 남아있는 원소들을 가지
고 새로운 힙을 만든다.
[beg, end) 범위의 원소들을 정렬한다.
이 알고리즘을 호출한 이후 더 이상 힙이 아니다.
정렬된 범위 알고리즘
정렬된 범위 알고리즘은 소스 범위가 정렬되어 있다는 가정을 바탕으로 한다.
기존의 정렬되어 있지 않은 소스 범위를 취하는 알고리즘보다 상당히 좋은 성능을 보장한다.
랜덤 액세스 반복자가 아니더라도 사용가능하다.
하지만 이런 경우에는 원하는 원소를 액세스하기 위해서 원소를 한 단계 한단계 거쳐가기 때문에 선형 복잡도를 가지게 된다.
원소의 검색
1) 원소의 존재 여부 판단
binary_search(beg, end, value), binary_search(beg, end, value, op)
[beg,end)안에 value와 같은 값이 존재하는지 판단한다.
op는 이항 조건자 op(elem1,elem2)로서 정렬 기준으로 사용된다.
2) 여러 개의 원소가 존재하는지 판단
include(beg, end, searchBeg, searchEnd), include(beg, end, searchBeg, searchEnd, op)
정렬된 범위[beg, end)가 정렬된 범위인 [searchBeg, searchEnd)의 모든 원소들을 포함하고 있는지 판단한다.
반환값이 true일 경우 [searchBeg,searchEnd) 안에 [beg, end) 안에 같은 값을 가지는 원소가 존재한다는 것을 말함.
3) 정렬 상태가 깨지지 않는 첫번째 또는 마지막 위치의 검색
lower_bound(beg, end, value, (op)), upper_bound(beg, end, value, (op))
lower_bound() 알고리즘은 value로 들오온 값보다 크거나 같은 같을 가지는 원소의 위치를 반환한다. 이 위치는 [beg,end)
범위 안에서 기존의 정렬상태를 깨트리지 않고 원소를 삽입할 수 있는 첫번째 위치이다.
upper_bound() 알고리즘은 value로 들어온 값보다 큰 값을 가지는 원소의 위치를 반환한다. 이 위치는 [beg,end)
범위 안에서 기존의 정렬상태를 깨트리지 않고 원소를 삽입할 수 있는 마지막 위치이다.
lower_bound와 upper_bound의 반환값을 동시에 얻고 싶다면, equal_range을 사용해야 한다.
4) 정렬 상태가 깨지지 않는 첫번째 또는 마지막 위치의 검색
equal_range(beg, end, value, (op))
lower_bound()와 upper_bound()가 바환하는 반복자들로 구성된 pair를 반환한다.
first -> 원소를 삽입할 수 있는 첫번째 위치
second -> 원소를 삽입할 수 있는 마지막 위치
원소의 병합
1) 정렬된 두 범위를 합치기
merge(source1Beg, source1End, source2Beg, source2End, destBeg, (op))
목적지 범위의 모든 원소들은 정렬된 순서를 가지게 된다.
호출자는 두 개의 소스 범위가 정렬되어 있다는 사실을 보장해야만 한다.
정렬되지 않은 범위에 대해서 호출하기 위해서는 merge() 대신에 copy()를 두번 호출해야한다.
두 범위 원소들의 합집합을 구하고자 한다면 set_union()을 사용해야 한다.
두 범위 원소들의 교집합을 구하고자 한다면 set_intersection()을 사용해야 한다.
2) 정렬된 두 범위의 합집한 계산
set_union(source1Beg, source1End, source2Beg, source2End, destBeg, (op))
목적지의 모든 원소들은 정렬된 순서를 가진다.
한 개 이상의 중복된 값이 존재한다면, 목적지 범위는 중복된 값을 가질 수 있다.( 더 많은 개수를 보유한 쪽을 따라가게 된다.)
3) 정렬된 두 범위의 차집합 계산
예) 1 2 2 4 6 7 7 9와 2 2 2 3 6 6 8 9 -> set_difference : 1 4 7 7
호출자는 범위가 정렬 기준에 따라서 정렬되어 있다는 사실은 보장해야 한다.
set_symmetric_difference(source1Beg, source1End, source2Beg, source2End, destBeg, (op))
대칭 차집합 : 첫 번째 범위에만 속해 있고 두 번째 범위에는 속하지 않는 원소들과 첫번째 구간에는 속해 있지 않고 두 번째
구간에만 속하는 원소로 구성된 집합이다.
예) 1 2 2 4 6 7 7 9 or 2 2 2 3 6 6 8 9 -> 1 2 3 4 6 7 7 8
4) 연속적으로 정렬된 범위의 병합
inplace_merge(beg1, endbe2, end2, (op))
[beg1, endbeg2]와 [end1beg2,end2]를 합쳐서 그 결과를 [beg1,end2]에 놓는다.
예) 1 2 3 4 5 6 7 1 2 3 4 5 6 7 8 -> 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 -> 정렬되서 합쳐진다.
수치 알고리즘
1) 한 시퀀스의 결과 계산<accumulate(beg, end,value)>
- [beg,end) 범위의 각각의 원소에 대해서 value = value + elem 최종 합을 반환한다.
2) 두 시쿼느의 내적 계산<inner_product(beg1, end1, beg2, value)>
- [beg, end) 범위의 각각의 원소와 beg2로 시작하는 두 번째 범위에 대해서 value = value + elem1*elem2를 호출하여 계산하고
최종 내적값을 반환한다.
예) //(0 + (1*1) + (2*2) + (3*3) + (4*4)
list<int> col1 -> 1~4까지 push_back()
inner_product(coll.begin(), coll.end(), coll.begin(), 0(초기값));
//(0+ (1*4) + (2*3) + (3*2) + (4*1)
inner_product(coll.begin(), coll.end(), coll.rbegin(), 0(초기값));
//(1* 1+1 * 2+2 * 3+3 * 4+4
inner_product(coll.begin(), coll.end(), coll.begin()
,0(초기값)
,multiplies<int>() //외적
,plus<int>()); // 내적
절대적인 값과 상대적인 값으로의 변경
1) 상대적인 값을 절대적인 값으로의 변경
partial_sum(beg, end, destBeg)
[beg, end)의 각 원소에 대해서 부분합을 계산하여 destBeg로 시작하는 목적지 범위에 결과를 기록한다.
예) a1, a2, a3 -> partial_sum(coll.begin(), coll.end(), ostream_iterator
a1 a1+a2 a1+a2+a3
2) 절대적인 값에서 상대적인 값으로의 변경
adjacent_difference(beg, end, destBeg) : [beg, end) 범위의 각 원소에 대해서 차이를 계산한다.
adjacent_difference(beg, end, destBeg, (op)) : 각 원소에 대해서 op를 호출한다.
예) adjacent_difference(coll.begin(), coll.end(), ostream_iterator
-> a1, a2-a1, a3-a2, a4-a3
adjacent_difference(coll.begin(), coll.end(), ostream_iterator<int>(cout, " "), plus<int>());
-> a1, a2+a1, a3+a2, a4+a3
스트링
스트링 정의
find 계열 함수의 반환 타입은 스트링 클래스 안에서 정의된 unsigned 정수 타입인 string::size_type
만약 검색에 실패한다면 특별한 값을 알려주는데 그것이 string::npos이다.
첫번째 인자는 문자열을 어디서붜 나눌 것인지에 대한 시작점 인덱스
두번째 인자는 자르고자 하는 문자들의 개수
“인덱스”를 지정하는 인자는 반드시 올바른 값을 가지고 있어야 한다.
문자들의 실제개수보다 큰 인덱스를 사용할 때는 out_of_range 에외를 발생시킨다.
find는 어떠한 인덱스도 허용하며, 만약 인덱스가 문자들의 개수보다 초과한다면 string::npos을 반환한다..
“문자들의 개수”를 지정하는 인자는 어떠한 값도 가질 수 있다. string::npos는 “모든 남아있는 문자들”과 동의어로 항상 동작한다.
size() STL 개념에 따른 원소의 개수를 반환, length()는 C-string의 strlen() 함수처럼 문자열의 길이를 반환해준다.
단어 추출과 역순출력
getline 함수는 스트림으로부터 입력을 읽어들여 문자열에 저장하는 특별한 함수
다음 라인의 끝까지 모든 문자들을 읽어들인다.
사용자들은 특별한 구분자에 의해서 구분하는 목적으로 getline()을 사용 할 수 있다.
예) getline(cin, line)
넘겨준 문자열 인자의 일부분이 아닌 문자의 첫번째 인덱스를 반환한다. 그러므로 위의 코드는 구분자에 해당하지 않은 첫번째
문자의 위치를 반환한다.
find 함수처럼 만약 인덱스를 찾지 못하면 string::npos가 반환된다.
첫 번째 인자로서 넘겨진 문자들 중 첫 번째 존재를 검사한다.
옵션으로 두 번째 인자는 문자열에 검색을 시작하는 위치를 지정하는데 사용됨.
예) string s(“HI Bill, I,m ill, so please pay the bill”);
s.find_first_of("il") -> 1을 반환한다.(첫번째 문자 'i', or 'l');
s.find_first_not_of("il") -> 0을 반환한다(첫번째 문자 'i' or 'l' 도 아니다.)
=> 조건에 맞는 index를 반환한다.
문자열은 단일 문자 액세스 [] 연산자로 이루어진다.
하지만 문자열의 인덱스가 올바른지 확인하지 않는다는 것을 명심해야한다. 사용자는 인덱스가 올바른지 확인해야만 한다.
문자에 액세스하는 보다 안전한 방법은 at() 멤버 함수를 사용하는 것이지만 이 멤버함수는 유효한 인덱스인지 검사를 수행하므로
속도면에서 불리해진다.
for(int i=endIdx-1; i>=static_cast
1) string::size_type이 unsigned 정수 타입이기 때문에 캐스팅이 없다면 singed값이 i가 unsigned가 되서 비교하기 때문에
무한루프에 빠질 수 있다. unsigned는 0보다 크거나 같기 때문에 항상 true가 된다.( i>=begIdx 비교시)
2) char_traits
스트링이 제공하지 않는 연산들
생성자와 소멸자
스트링과 C-String
1) 스트링의 내용을 문자들의 배열이나 C-string으로 변환하기 위해서는 3가지 방법이 존재
data() : 문자들의 배열로 스트링의 내용을 반환한다. ‘\0’ 문자가 덧붙여지지 않으므로 반환 타입은 올바른 C-string이 아니다.
c_str() : C-string처럼 스트링의내용을 반환한다. 따라서 ‘\0’ 문자가 덧붙여진다.
copy() : 호출자에 의해 제공된 문자 배여로 스트링의 내용을 복사한다. ‘\0’ 문자는 더해지지 않는다.
data(), c_str()은 스트링이 소유했던 배열을 반환한다. 따라서 호출자는 그 반환값에 대해서 메모리를 해제하거나 변경해서는 안된다.
data(), c_str()의 리턴값은 const char* 로 리턴하므로 이 부분을 명심해야한다.
사이즈와 용량
size(), length()
1) 스트링의 소유한 현재 문자 개수를 반환한다.
2) empty() 멤버 함수는 문자들의 개수가 0인지르 확인(size()==0, length()==0 < empty() -> 속도높다)
max_size()
1) 스트링이 소유할 수 있는 문자들의 최대 개수를 반환한다.
2) 스트링의 연산이 max_size()보다 큰 길이를 가지면 length_error 예외를 발생시킨다.
capacity()
1) 내부적인 메모리를 재할당하지 않고 가질 수 있는 문자열의 문자 개수를 반환한다.
2) 충분한 용량을 갖는 것은 두가지 이유에서 매우 중요하다.
재할당은 문자열의 문자들을 참조하는 모든 레퍼런스와 포인터, 그리고 반복자를 무효화 시킨다.
재할당은 시간이 걸린다.
재할당을 피하기 위해서 reserve() 멤버함수가 제공되며, 함수 사용하여 일정한 용량을 예약함으로써 레퍼런스, 포인터, 반복자
유효성을 보장함.
논바인딩-수축-요청(Nonbinding-shrink-request)
1) 현재의 용량보다 작은 사이즈의 인 reserve()를 호출함으로써 이러한 작업을 수행할 수 있다.
2) 단지 요청하는 것이기 때문에, 사용자가 용량을 감소시키길 원한다고 해서 이루어질 지는 보장할 수 없다.
1) 아무런 아큐먼트 없이 reserve()을 호출할 경우 ( s.reserve()) // 현재 사이즈에 맞게 용량을 줄이고 싶다.
원소의 액세스
스트링은 문자열이 소유한 문자들에 대해서 사용자가 읽거나 쓰기 액세스를 가지도록 허락한다.
사용자는 [], at()을 통해서 단일 문자에 액세스할 수 있다.
1) [], at()의 차이점
- []는 인자로 넘겨준 인덱스가 유효한지 아닌지 확인하지 않지만, at()은 검사를 한다.
- at()이 잘못된 인덱스를 호출되었다면 out_of_range 예외를 발생시킨다.
- [] 연산자의 상수 버전에서 마지막 문자 뒤의 위치는 유효하다. 이렇게 해서 호출할 경우 char '\0'을 반환한다.
const string cs("nico");
string s("nico");
s[s.length()]; // '\0'으로 판정된다.
cs[cs.length()]; //에러 : 정의되지 않은 행동
2) 스트링의 문자들을 참조하는 레퍼런스와 포인터들은 아래와 같은 연산으로 무효화될 수 있다.
- 값이 swap() 되는 경우
- 새로운 값이 operator>>() or getline()에 의해 읽혀지는 경우
- 내용들이 data() or c_str()에 의해 익스포트 되는 경우
- [] 연산자, at(), begin(), rbegin(), end(), 또는 rend()를 제외한 비상수 멤버 함수가 호출되는 경우
- [] 연산자, at(), begin(), rbegin(), end(), 또는 rend() 이후에 이들 함수들이 따라오는 경우
1) compare 멤버 함수로는 부분 문자열 비교도 가능하다. boolean이 아닌 정수값을 반환한다.
예) s.compare(1,2,"bcx",2,2) //s : abcd : "ab"가 "cd"보다 작다 < 0
1) 할당자(=, assign)
- 새로운 값을 할당해 주는 = 연산자를 사용하고 한개 이상의 아규먼트가 필요한 경우에는 assign 멤버 함수를 사용할 수 있다.
2) 값 교체하기(swap)
3) 빈 문자열 만들기
- 스트링의 모든 문자들을 제거하기 위해서 사용된다.
s = ""; s.clear() // 내용을 지운다 , s.erase() // 모든 문자들을 지운다.
- erase(pos), erase(beg, end) : 문자열을 삭제한다.
- clear() : 모든 문자들을 제거하여, 빈 문자열로 만든다. 12. 문자들의 삽입과 제거
- 문자들을 덧붙이기 위해서 +=, append(), push_back()을 사용
- append()는 사용자로 하여금 여러 아규먼트들을 사용해서 추가되는 값을 지정할 수 있다.
- append()의 또 다른 버전은 사용자로 하여금 두 반복자들을 지정해서 문자들의 범위로 추가할 수 있게 한다.
- push_back() 후위삽입자
- insert 멤버 함수는 문자들을 삽입할 수 있게 해주며 삽입될 위치의 인덱스를 필요로 한다.(단일문자나 숫자는 안됨)
insert(idx, num, c), insert(pos, num, c)
- replace
string s="i18n";
s.replace(1,2,"nternationalizatio"); s : internationalization
- substr() 멤버 함수를 사용함으로써 스트링에서부터 부분 문자열을 추출해 낼 수 있다.
예) string s("interchangeability")
s.substr() //s의 복사본을 반환한다.
s.substr(11) // "ability" 반환
s.substr(5,6) // "ability" 반환
s.substr(s.find('c')) // "changeability" 반환
- 스트링에 대해서도 다음과 같은 I/O 연산자들이 정의되어 있다.
1) operator >>은 입력 스트림으로부터 스트링을 읽어드린다.
2) operator <<은 출력 스트림에 스트링을 기록한다.
- >> 연산자는 다음의 상황이 발생할 때까지 모든 문자들을 읽어드린다.
1) 다음 문자가 공백일 경우
2) 스트림이 더 이상 올바른 상태가 아닌 경우(end-of file때문에)
3) 스트림의 현재 width()가 0보다 크고, width()만큼 읽어들인 경우
4) max_size()만큼의 문자들을 읽어들인 경우
연산자는 스트림의 width()를 0으로 설정한다.
1) 단일 문자, 문자 시퀀스(부분 문자열) 또는 문자의 특정한 세트 중의 하나
2) 앞, 뒤 방향으로
3) 특정 위치에서 시작하는 문자열
4) 검색 함수의 인자 설명
첫번째 인자는 항상 검색하고자 하는 값이다.
옵션으로 사용되는 두 번째 인자는 스트링의 어디서부터 검색을 시작할지를 명시하는 인덱스이다.
세번째 인자는 검색하고자 하는 값의 문자들의 개수이다.
만약 검색 함수가 실패한다면 string::npos를 반환한다.
npos값과 타입을 사용할 경우 조심해야할 것은 반환값의 타입이 int, unsigned가 아닌 항상 string::size_type을 사용해야한다는
것이다. 그렇지 않으면 string::npos의 비교는 동작하지 않을 것이다.
1) assign : [beg,end) 범위의 모든 문자들을 할당한다.
string s1("hello");
string s2("hong");
s1.assign(s2.begin(), s2.end()); //s1은 사라지고 s2의 hong오 덮어쓰기 되어진다.
템플릿 스트링 클래스 basic_string<>는 문자 타입과 문자 특성(trait), 그리고 메모리 모델의 인자를 갖는다.
string 타입은 char 타입을 위한 전문화 버전이고, wstring은 wchar_t 타입을 위한 전문화 버전이다.
vector들의 주된 목표는 컨테이너를 전체적으로 다루는 것이 아닌 컨테이너의 원소에 대해서 조작하고 다루는 것이다. 따라서 vector
의 구현은 컨테이너 안의 원소들과 동작하는 것에 최적화되어 있다.
스트링의 주된 목표는 컨테이너를 전체적으로 다루는 것이다. 따라서 스트링은 전체 컨테이너를 전달하고 할당하는 비용을 줄이는
것이다.
-> 예를 들어, 스트링은 대게 레퍼런스 카운팅을 사용하지만 vector는 결코 그렇지 않다.
1) 새롭게 알게 된 내용
- traits_type : 문자 특성의 타입
- value_type : 문자들의 타입
- size_type : 인덱스와 사이즈 값을 위한 unsigned 정수 타입
- difference_type : 차이값들을 위한 signed 정수 타입
- reference 문자 래퍼런스의 타입
- void clear(), string& erase() -> 반환값에 따라 다름( 문자 지우는 것에 대해서는 똑같은 동작을 수행함)
STL 추가적으로 알아낸 내용
vector, deque, list에 대해서 비교
vector
vector는 동적으로 확장/축소가 가능한 dynamic array로 구현되어 있다.
강점
● 개별 원소들을 position index로 접근이 가능하다 (상수 복잡도)
● 원소를 컨테이너의 끝에 삽입/제거 하는 것이 빠르다 (상수-아모타이즈드 복잡도)
● 어떠한 순서로도 원소들을 순회할 수 있다. 즉, Random access iterating이 가능함. (로그 복잡도)
일반적으로 vector는 다른 두 개의 시퀀스 컨테이너인 deque, list에 비해 개별 원소에 대한 접근 속도(why? 연속적인 메모리 공간에
저장하므로)와 컨테이너의 끝에서 삽입/제거하는 속도가 가장 빠르다
약점
● 컨테이너의 끝 위치가 아닌 곳에서 삽입/제거 수행시 그 성능은 deque/list에 비해 현저히 떨어진다.
주의 ● 동적으로 컨테이너의 크기가 확장/축소되는 것이 편하기는 하나, 확장시의 reallocation은 비용이 꽤 크다. ● capacity를 확장 시켜줄 수 있는 적절한 크기의 reserve로 인한 메모리 확보가 중요.
강점 ● 개별 원소들을 position index로 접근이 가능하다. ● 원소를 컨테이너의 끝 뿐 아니라, 앞에서도 삽입/제거 하는 것이 빠르다. ● 어떠한 순서로도 원소들을 순회할 수 있다.
약점 ● 컨테이너의 시작 / 끝 위치가 아닌 곳에서 삽입/제거 수행시 그 성능은 list에 비해 현저히 떨어진다.
장점 ● 저장 원소가 많고, 내부 capacity가 작은 경우 vector에 비해 확장 비용이 절감된다. : 전체가 재할당되기 보다, 늘어나야 될 크기만큼만의 chunk가 하나 더 할당되면 됨
단점 ● 컨테이너 처음부터 끝까지 연속 메모리 공간이 아니므로, vector에서 가능했던 원소들간 포인터 연산이 불가능하다.
● 원소들의 컨테이너 내 순서 이동이 빠르다. (상수 복잡도)
- vector와 deque와 다르게 list의 가장 큰 강점은 컨테이너 내 어느 위치에서도 원소의 삽입/제거가 빠르다는 것이다.
약점
● 원소의 position index로 직접 접근이 불가능하다. : 특정 원소에 접근하려면 처음이나 끝에서부터 선형 탐색을 하여야만 한다. ● 컨테이너 내 원소 순회는 forward/reverse 순회만 가능하며, 느리다. (선형 복잡도)
● 원소들간 상호 연결 정보를 위해 추가적인 메모리가 사용된다.(원소 수가 적을 수록 상대적으로 많은 메모리를 소비하게 됨.)
장점
● 미리 로딩해서 인덱스를 구성해서 사용할 경우는 상당히 유용(insert와 delete가 거의 일어나지 않는 경우)
단점 ● map의 요소가 늘어날수록 그 처리속도는 기하급수적으로 떨어짐 ● insert,delete가 최악.(insert, delete 시에 내부 구조를 재배열하기 때문에 느림)
1) value_type을 사용 : 암시적인 형 변환을 피하기 위해서 사용자는 value_type를 사용하여 정확한 타입을 전달해야함.
coll.insert(std::map<std::string,float>::value_type("otto", 22.3));
2) pair<> 사용
coll.insert(std::pair<std::string,float>("otto", 22.3))
3) make_pair() 사용
coll.insert(std::make_pair("otto", 22.3));
make_pair, value_type
make_pair의 경우 임시 pair를 만들어서 전달하므로 value_type보다는 속도의 저하가 오게 된다.
template<class _Ty1, class _Ty2> inline pair<_Ty1, _Ty2> make_pair(_Ty1 _Val1, _Ty2 _Val2) { // return pair composed from arguments return (pair<_Ty1, _Ty2>(_Val1, _Val2)); }
Originally published February 18, 2020
Latest update February 17, 2020
Related posts :