jepi.tistory.com · web viewsjjung/stl about this site ... 이곳은 최근 표준화 작업을...

191
Official URL: http://oopsla.snu.ac.kr/~sjjung/stl About this site ... 이이이 이이 이이이 이이이 이이 ISO/ANSI C++이 이이이 이이 C++ 이이이이이(standard C++ library) 이 이이이이 이이이이. 이이 이이이이이 이이이 이이/이이이이 이이이, 이이 '이이'이이이 이이이이 이이 이이이이 이이 C++ 이이이이이이 이이이이이 이이이이 이 이이이이 이이 이이이 이이이 이이, 이이이이 이이 이이이 이이이이 이이 이이 이이이이. 이 이이이이 C++ 이이이 IS(International Standard)이 이이이이 이이 이이이 이이 이이이이 이이이이 이이이이이 이이이, 이이 이이이 이이이이 이이 이이이 이이이이 이이이이이이. 이이 이이이 이이이이 이이 이이이 이이이 이이이이이 이이 이이이이이이이. 이이이이이 이이이 이이이 이이이이이, 이이 이이 이이이이 이 이이이 이이이이 이이이 이이이이이이. 이이 이이이이이이 이이이 이이이이 이이이이이 이이이 이이(이이, 이이, 이이, 이이, 이이이이, 이이)이이이 이이이이 이이이 이이이이이 이이이이이이. Bookmark 이이이 이이이이이이 이이 이이이 이이이이이 이이이. http://oopsla.snu.ac.kr/~sjjung/stl http://welcome.to/c++stl Copyright © 1998-2002, Seung-Jin Jung(이이이), All rights reserved. MS-Word version by baboneo 1

Upload: others

Post on 12-Jan-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Official URL: http://oopsla.snu.ac.kr/~sjjung/stlAbout this site ...

이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++ library)를 설명하고 있습니다. 현재 지속적으로 내용을 수정/보완하고 있으며, 특히 '보충'이라는 아이콘이 붙은 부분에는 표준 C++ 라이브러리를 사용하면서 주의해야 할

사항이나 각종 팁들을 다루고 있고, 앞으로도 계속 추가할 예정이니 많은 관심 바랍니다.

이 사이트는 C++ 표준이 IS(International Standard)로 확정되기 전에 쓰여진 참고 자료들을

바탕으로 제작되었기 때문에, 현재 표준과 일치하지 않는 부분이 있을지도 모르겠습니다. 이런

부분을 발견하신 분은 저에게 메일로 알려주시면 즉각 반영하겠습니다.

개인적으로 상당히 주의를 기울였으나, 양이 워낙 방대하여 혹 잘못된 부분들이 있을지

모르겠습니다. 혹시 둘러보시다가 잘못된 사항들을 발견하시면 사소한 것들(문법, 철자, 폰트, 색깔, 띄어쓰기, 용어)이라도 망설이지 마시고 알려주시면 고맙겠습니다.

Bookmark

이곳을 찾아오시려면 다음 주소로 들어오시면 됩니다. http://oopsla.snu.ac.kr/~sjjung/stl http://welcome.to/c++stl

Copyright © 1998-2002, Seung-Jin Jung(丁承鎭), All rights reserved.

MS-Word version by baboneo

1

Page 2: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

[ 목 차 ]

1 장 소개 12 장 string2 장 반복자(iterator) 13 장 Generic 알고리듬

3 장 함수(function)와 조건자(predicate) 14 장 정렬 콜렉션 알고리듬

4 장 컨테이너(container) 클래스 15 장 할당기(allocator)

5 장 vector 와 vector<bool> 16 장 컨테이너와 generic 알고리듬 작성

6 장 list 17 장 Trait 인자

7 장 deque 18 장 예외 처리(exception handling)

8 장 set, multiset, bitset 19 장 auto_ptr9 장 map 과 multimap 20 장 complex

10 장 stack 과 queue 21 장 numeric_limits11 장 priority_queue 부록 Standard C++ Library 관련 자료

1 장 : 소개 o 1.1 표준 C++ 라이브러리 (standard C++ library) 란 ? o 1.2 표준 C++ 라이브러리 (Standard C++ Library) 와 다른 라이브러리와의

차이점

o 1.3 비객체지향설계의 장단점 o 1.4 STL 의 구조 o 1.5 STL 맛보기

2 장 : 반복자 (iterator) o 2.1 반복자에 대한 소개 o 2.2 반복자의 종류

2.2.1 입력 반복자 (input iterator) 2.2.2 출력 반복자 (output iterator) 2.2.3 순방향 반복자 (forward iterator) 2.2.4 양방향 반복자 (bidirectional iterator) 2.2.5 임의 접근 반복자 (random access iterator)

2

Page 3: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

2.2.6 역 반복자 (reverse iterator) o 2.3 스트림 반복자 (stream iterator)

2.3.1 입력 스트림 반복자 (input stream iterator) 2.3.2 출력 스트림 반복자 (output stream iterator)

o 2.4 삽입 반복자 (insert iterator) o 2.5 반복자 연산 (iterator operation)

3 장 : 함수 (function) 와 조건자 (predicate) o 3.1 함수 (function) o 3.2 조건자 (predicate) o 3.3 함수 객체 (function object) o 3.4 함수 어댑터 (function adaptor) o 3.5 부정자 (negator) 와 바인더 (binder)

4 장 : 컨테이너 (container) 클래스 o 4.1 개요 o 4.2 컨테이너 선택하기 o 4.3 메모리 관리 이슈들 o 4.4 컨테이너에 포인터 저장하기 ( 문제점과 해결책 ) o 4.5 STL 에 없는 컨테이너 타입들

5 장 : vector 와 vector<bool> o 5.1 vector 데이터 추상 (data abstraction)

5.1.1 Include 화일 o 5.2 vector 연산

5.2.1 vector 의 선언과 초기화 5.2.2 타입 정의 5.2.3 벡터의 첨자 연산 5.2.4 확장 및 사이즈 변환 연산 5.2.5 원소의 삽입과 삭제 5.2.6 반복자 5.2.7 소속 검사 연산 5.2.8 정렬 연산 5.2.9 유용한 generic 알고리듬들

o 5.3 부울 벡터 o 5.4 예제 프로그램 - 에라토스테네스의 체

3

Page 4: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

6 장 : list o 6.1 list 데이터 추상 (data abstraction)

6.1.1 Include 화일 o 6.2 list 연산

6.2.1 list 의 선언과 초기화 6.2.2 타입 정의 6.2.3 list 에 원소 집어넣기 6.2.4 원소 삭제하기 6.2.5 확장 및 사이즈 변환 연산 6.2.6 접근과 반복자 6.2.7 소속 검사 연산 6.2.8 정렬 연산 6.2.9 검색 연산 6.2.10 In Place 변환 6.2.11 기타 연산

o 6.3 예제 프로그램 - An Inventory System

7 장 : deque o 7.1 deque 데이터 추상 (data abstraction)

7.1.1 Include 화일 o 7.2 deque 연산 o 7.3 예제 프로그램 - Radix Sort

8 장 : set , multiset , bitset o 8.1 set 데이터 추상 (data abstraction)

8.1.1 Include 화일 o 8.2 set 과 multiset 연산

8.2.1 set 의 선언과 초기화 8.2.2 타입 정의 8.2.3 삽입 8.2.4 set 에서의 삭제 8.2.5 검색과 카운팅 8.2.6 반복자 8.2.7 set 연산 8.2.8 기타 generic 알고리듬

o 8.3 예제 프로그램 - 철자 검사기

4

Page 5: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

o 8.4 bitset 추상 (abstraction) 8.4.1 Include 화일 8.4.2 bitset 의 선언과 초기화 8.4.3 접근과 검사 8.4.4 set 연산 8.4.5 변환

9 장 : map 과 multimap o 9.1 map 데이터 추상 (data abstraction)

9.1.1 Include 화일 o 9.2 map 과 multimap 연산

9.2.1 map 의 선언과 초기화 9.2.2 타입 정의 9.2.3 삽입과 접근 9.2.4 삭제 9.2.5 반복자 9.2.6 검색과 카운팅 9.2.7 원소 비교 9.2.8 기타 map 연산

o 9.3 예제 프로그램 9.3.1 전화 데이터베이스 9.3.2 그래프 9.3.3 A Concordance

10 장 : stack 과 queue o 10.1 개요 o 10.2 stack 데이터 추상 (data abstraction)

10.2.1 Include 화일 10.2.2 stack 의 선언과 초기화 10.2.3 예제 프로그램 - RPN 계산기

o 10.3 queue 데이터 추상 (data abstraction) 10.3.1 Include 화일 10.3.2 queue 의 선언과 초기화 10.3.3 예제 프로그램 - Bank Teller 시뮬레이션

5

Page 6: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

11 장 : priority_queue o 11.1 priority_queue 데이터 추상 (data abstraction)

11.1.1 Include 화일 o 11.2 priority_queue 연산

11.2.1 priority_queue 의 선언과 초기화 o 11.3 응용 - Event-Driven 시뮬레이션

11.3.1 아이스크림 가게 시뮬레이션

12 장 : string o 12.1 string 추상 (abstraction)

12.1.1 Include 화일 o 12.2 string 연산

12.2.1 string 의 선언과 초기화 12.2.2 Resetting Size and Capacity 12.2.3 대입 , 덧붙이기 (append), 교환 (swap) 12.2.4 문자 접근 12.2.5 반복자 12.2.6 삽입 , 삭제 , 치환 (replacement) 12.2.7 복사와 서브 string 12.2.8 비교 12.2.9 검색

o 12.3 예제 함수 - Split a Line into Words

13 장 : generic 알고리듬 o 13.1 개요

13.1.1 Include 화일 o 13.2 초기화 알고리듬

13.2.1 시퀀스를 초기값으로 채우기 13.2.2 시퀀스를 다른 시퀀스에 복사하기 13.2.3 발생기가 생성한 값으로 시퀀스 초기화하기 13.2.4 두개의 구간에 속한 원소들 서로 뒤바꾸기

o 13.3 검색 연산 13.3.1 조건을 만족하는 원소 찾기 13.3.2 연속적으로 중복인 원소 찾기 13.3.3 시퀀스로부터 어떤 값의 첫번째 발생 찾기 13.3.4 시퀀스에서 부시퀀스 찾기

6

Page 7: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.3.5 부시퀀스의 마지막 발생 찾기 13.3.6 최대 또는 최소 원소 찾기 13.3.7 병렬 시퀀스에서 처음으로 일치하지 않는 원소 찾기

o 13.4 In-Place 변환 13.4.1 시퀀스내의 원소 뒤집기 13.4.2 어떤 값을 특정 값으로 치환하기 13.4.3 중간지점을 중심으로 원소들 돌리기 13.4.4 시퀀스 둘로 쪼개기 13.4.5 시퀀스내에 순열 생성하기 13.4.6 두개의 이웃하는 시퀀스를 하나로 합치기 13.4.7 시퀀스내의 원소들을 임의로 재배치하기

o 13.5 삭제 알고리듬 13.5.1 필요없는 원소 삭제하기 13.5.2 비슷한 값들의 런 (run) 삭제하기

o 13.6 스칼라 생성 알고리듬 13.6.1 조건을 만족하는 원소의 갯수 세기 13.6.2 시퀀스를 하나의 값으로 유추하기 13.6.3 일반화된 내적 13.6.4 쌍별로 두개의 시퀀스를 비교하기 13.6.5 사전식 비교 (lexical comparison)

o 13.7 시퀀스 생성 알고리듬 13.7.1 한개 또는 두개의 시퀀스 변환하기 13.7.2 부분합 (partial sum) 13.7.3 인접차 (adjacent difference)

o 13.8 기타 알고리듬 13.8.1 콜렉션 내의 모든 원소에 함수 적용하기

18 장 : 예외 처리 (exception handling) o 18.1 개요

18.1.1 Include 화일 o 18.2 표준 exception 계층도 o 18.3 exception 사용하기 o 18.4 예제 프로그램

20 장 : complex o 20.1 개요

7

Page 8: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

20.1.1 Include 화일 o 20.2 복소수의 생성과 그 사용법

20.2.1 복소수의 선언 , 극형식과 보수 구하기 20.2.2 복소수의 실수부와 허수부 20.2.3 복소수의 사칙연산 20.2.4 복소수의 비교 20.2.5 스트림 입출력 20.2.6 복소수의 놈 (norm), 절대값 , 위상각 - norm() , abs() ,

arg() 20.2.7 삼각함수 20.2.8 초월함수

o 20.3 예제 프로그램 - 이차방정식의 근

8

Page 9: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

1 장: 소개

1.1 표준 C++ 라이브러리(standard C++ library)란?

국제 표준 기구(International Standards Organization, ISO)와 미국 국가 표준 기관

(American National Standards Institute, ANSI)은 C++ 프로그래밍 언어의 표준화 작업을

마쳤다(표준 번호: ISO/IEC 14882). 이 표준화 과정에서 가장 중요한 부분의 하나가 바로 「

표준 C++ 라이브러리(standard C++ library)」이며, 이 라이브러리는 많은 양의 클래스와

함수들을 제공하고 있다.

ANSI/ISO 표준 C++ 라이브러리는 다음을 포함하고 있다.

많은 양의 데이터 구조와 알고리듬. 특히 이 부분만 따로 「표준 템플릿 라이브러리

(standard template library, STL)」라고 부른다. 입출력 스트림

locale 기능

string 템플릿 클래스

complex 템플릿 클래스

numeric_limits 템플릿 클래스

메모리 관리 기능

Language support 기능

예외 처리(exception handling) 기능

수치 배열용으로 최적화된 valarray 클래스

1.2 표준 C++ 라이브러리(Standard C++ Library)와

다른 라이브러리와의 차이점

표준 C++ 라이브러리는 표준 데이터 구조에 대한 클래스 정의와 이러한 데이터 구조를 다룰 때

주로 사용되는 알고리듬들이 대부분을 차지하고 있다. 이러한 클래스 정의와 알고리듬들을

STL(Standard Template Library)이라고 한다. STL 의 구조와 설계는 대부분의 다른 C++

9

Page 10: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

라이브러리와는 거의 모든 면에서 완전히 다르다. 실례로, STL 은 캡슐화(encapsulation)를

피하고 있고, 상속(inheritance)을 거의 사용하고 있지 않다.

캡슐화(encapsulation)는 객체지향 프로그래밍의 트레이드 마크에 해당한다. 데이터와 함수를

객체로 묶는다는 개념은 소프트웨어 개발에서의 가장 강력한 원리이며, 실제로 가장 주된

테크닉이다. encapsulation 을 적절히 잘만 사용하면, 지나치게 복잡한 소프트웨어 시스템도

다루기 적절한 크기로 나누어서 개발팀에 속한 각각의 프로그래머에게 할당할 수 있다.

상속(inheritance)은 코드 공유와 소프트웨어 재사용을 가능케하는 강력한 기법이다. 하지만, 예를 들어, GUI 에서 두가지 형태의 윈도우는 하나의 공통된 베이스 윈도우 클래스로부터

상속될 수 있고, 각각의 서브 클래스는 각기 필요한 자기만의 특징을 제공할 수 있다. 또 다른

예로, 객체지향 컨테이너 클래스는 공통된 behavior 를 보장하며, 더 일반적인 클래스로부터

상속하고, 공통된 멤버 함수를 추출함으로써, 코드 재사용을 지원할 수 있다.

STL 의 설계자는 객체지향 방법을 피했으며, 공통된 데이터 구조를 사용하여 수행하는 작업들을

데이터구조의 표현과 분리하였다. 따라서, STL 을 '알고리듬의 집합'과 이들 알고리듬을

사용하여 다루는 '데이터 구조들의 집합'으로 보는 것이 적절하다고 하겠다.

1.3 비객체지향설계의 장단점

표준 C++ 라이브러리의 STL(Standard Template Library) 부분은 설계시 일부러 객체지향

방식을 따르지 않았다. 개발자는 비객체지향적으로 설계된 STL 의 장점과 단점들을 잘

파악함으로써, 라이브러리를 효과적으로 사용할 수 있을 것이다. 이들 중 몇가지를 살펴보자. 소스 코드 크기의 축소

STL 에는 약 50 여개의 다양한 알고리듬과 10 여개의 주요 데이터 구조들이 들어 있다. 이렇게 알고리듬과 데이터 구조를 분리함으로써, 소스코드의 크기를 줄이고, 유사한

형태의 작업들이 상이한 형태의 인터페이스를 가지게 될 위험을 줄이는 효과를

가져온다. 만약 이렇게 분리를 하지 않는다면, 서로 다른 데이터 구조 각각에 대해 모든

알고리듬들을 또 구현해야 하며, 결국 수 백개의 멤버함수를 필요로하게 될 것이다.

유연성

알고리듬과 데이터를 분리함으로써 얻게 되는 장점은 STL 의 알고리듬들이 기존의 C++ 포인터와 배열에도 사용할 수 있다는 것이다. C++ 배열은 객체가 아니기 때문에, 클래스 계층구조내에 encapsulate 되어 있는 알고리듬은 이러한 기능을 가질 수 없다.

10

Page 11: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

효율성

일반적으로 표준 C++ 라이브러리는 그리고, 특히 STL 은 C++ 응용 프로그램을

개발하는데 대한 저수준 접근법을 제공한다. 이와 같은 저수준 접근법은 특정

프로그램이 효율적인 코딩과 수행속도에 중점을 두고 있을 때 유용하다.

반복자(iterator): 불일치(mismatch)와 무효화(invalidation)

표준 C++ 라이브러리 데이터 구조는 반복자라 불리는 포인터와 비슷한 객체를

사용하여 컨테이너의 내용물을 가리킨다(2 장에서 자세히 설명). 이 라이브러리의

아키텍쳐에서는 이들 반복자들이 같은 컨테이너로부터 온것인지를 증명하기가

불가능하다. 고의든 우연이든 한 컨테이너의 시작 반복자를 다른 컨테이너의 끝

반복자와 같이 사용하게 되면 어떤 일이 일어나게 될 지 장담할 수 없게 된다.

그리고, 반복자는 자신과 연관된 컨테이너에 대해 삽입이나 반복을 수행한 뒤에, 무효화

될 수도 있다는 사실을 프로그래머는 반드시 기억하고 있어야 한다. 이렇게 무효화된

반복자를 검사도 않고 사용하게 되면 예상치 못한 결과를 초래하게 된다.

표준 C++ 라이브러리에 익숙해져야 반복자와 관련된 에러의 수를 줄일 수 있다.

템플릿: 에러와 코드 확대('code bloat')

템플릿 알고리듬을 사용함으로써 유연성과 강점을 얻게 되지만, 다른 한편으로

디버깅이 힘들어진다. generic 알고리듬의 인자 리스트에서의 에러는 템플릿 확장

과정에서 깊숙히 정의되어 있는 내부함수에 대한 컴파일러 에러를 유발하게 되어 매우

이해하기가 힘들어지게 된다. 알고리듬이 요구하는 바를 잘 이해하여야, 표준

라이브러리를 성공적으로 사용할 수 있다.

STL 은 템플릿에 크게 의존하고 있기 때문에, STL 을 사용한 프로그램은 생각했던

것보다 덩치가 훨씬 커질 수 있다.(보통 'code bloat'라 지칭) 특정 템플릿 클래스를

개체화하는데 드는 비용을 인식하여 적절한 결정을 해야 이러한 문제를 최소화할 수

있다(역시 어려운 문제). 컴파일러가 템플릿을 처리하는데 있어 좀더 똑똑해진다면, 이러한 문제들을 줄일 수 있을 것이다.

문제점: 다중 쓰레딩

다중 쓰레드 환경하에서 표준 C++ 라이브러리를 사용할 때는 조심해야 한다. 반복자는

컨테이너와는 독립적으로 존재하기 때문에 쓰레드들간에 안전하게 전달할 수가 없다.

11

Page 12: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

1.4 STL 의 구조

소프트웨어를 구성하는 요소들을 3 차원 좌표계로 표시한다면 아래 그림과 같이 나타낼 수 있을

것이다. 첫번째 차원은 데이터 타입(int, double 등등)을 나타내며, 두번째 차원은 서로 다른

컨테이너(vector, list 등등)를 나타내며, 세번째 차원은 컨테이너에 대한 여러 알고리듬(검색, 정렬,삭제 등등)을 나타낸다. 각 차원의 크기가 각각 i, j, k 라고 한다면, i*j*k 가지의 코드를

모두 작성해야 한다. 데이터 타입을 인자로 사용하는 템플릿 기능을 사용하면, j*k 가지로 줄일

수 있다. 더 나아가서, 알고리듬을 여러가지 컨테이너에 두루 사용되도록 만든다면, j+k 가지의

코드를 생성하면 된다. 예를 들어, double, int, string 타입의 원소를 담고 있는 vector, list, set 컨테이너에 대해 정렬, 검색, 삭제 알고리듬을 만들려면, 총 27 가지의 알고리듬을 만들어야

하지만, 아래 그림과 같이 데이터 타입, 컨테이너, 알고리듬을 모두 분리함으로써, 3 가지의

컨테이너와 3 가지의 알고리듬만을 고안하면 되는 것이다. 아래 그림의 파란 점은 '정수' 'list'를

'정렬'하는 알고리듬을 나타내고 있다.

라이브러리를 이와 같은 구조로 만든다면 소프트웨어 설계 작업량을 상당히 줄일 수 있다. 뿐만

아니라 라이브러리가 제공하는 요소들을 사용자가 만든 요소들과 같이 사용하는 것이 아주

자연스러워진다. 다시 말해서, 라이브러리에서 제공하는 알고리듬을 사용자가 정의한

컨테이너에 사용할 수 있으며, 사용자가 만든 알고리듬을 라이브러리가 기본적으로 제공하는

여러 컨테이너에 적용할 수 있게 된다.

1.5 STL 맛보기

이 절에서는 표준 C++ 라이브러리의 대부분을 차지하는 STL 에 대한 감을 잡기 위해 간단한

프로그램을 예로 들어 설명한다. 일단, 평범한 C++ 프로그램에서 시작하여 표준 C++ 라이브러리가 제공하는 특징들을 하나씩 적용해 가면서, 프로그램을 버전업하기로 하자.

1.5.1 프로그램 1: STL 을 전혀 사용하지 않음

12

Page 13: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

다음은 정수값들을 입력으로 받아들여서, 이들을 정렬하고, 결과를 출력하는 아주 간단한 C++ 프로그램이다.

#include <stdlib.h>#include <iostream>

// qsort()의 인자로 쓰일 비교함수

inline int cmp(const void *a, const void *b){    int aa = *(int *)a;    int bb = *(int *)b;    return (aa < bb) ? -1 : (aa > bb) ? 1 : 0;}

int main(){    const int size = 1e5;    int array[size];        // 100,000 개의 정수로 이루어진 배열

    // 입력

    int n = 0;    while (cin >> array[n])        n++;    n--;

    // 정렬

    qsort(array, n, sizeof(int), cmp);

    // 출력

    for (int i = 0; i < n; i++)        cout << array[i] << endl;}

1.5.2 프로그램 2: 컨테이너, 반복자, 알고리듬

13

Page 14: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

STL 은 여러 종류의 컨테이너들을 제공한다. 컨테이너란 객체들을 담아둘 수 있는 객체를

말하는데, 여기서는 vector 를 사용해보도록 하자. vector 는 배열과 비슷하지만, 필요에 따라

스스로 사이즈를 늘릴 수 있다는 점에서 배열과 차이가 난다. 따라서, 선언할 때 배열처럼 미리

사이즈를 정해주지 않아도 된다. vector 는 push_back() 연산을 제공하는데, 이 연산은

vector 의 맨 뒤에 원소를 집어 넣는 일을 한다. size() 멤버함수는 vector 에 담긴 원소의

갯수를 반환한다.

STL 에서는 sort() 알고리듬을 이용하여 컨테이너를 정렬한다. 앞에서도 말했지만, STL 은

객체지향적으로 설계된 라이브러리와는 달리, 알고리듬과 컨테이너를 완전히 분리하고 있다. 다시 말해서, vector 컨테이너만을 위한 정렬 알고리듬이 따로 있는 것이 아니라, 컨테이너

종류와는 무관하게(보통 'orthogonal'이라는 용어를 사용) 사용할 수 있는 알고리듬을

제공한다는 것이다.

알고리듬을 컨테이너에 적용하기 위해서 필요한 것이 바로 반복자(iterator)이다. 반복자는

컨테이너내의 특정 위치를 가리키는 포인터와 비슷한 개념이라고 보면 되며, 이 반복자를 통해

컨테이너내의 원소들을 다양한 방법으로 다루게 되는 것이다. 다시말해, 반복자는 알고리듬과

컨테이너를 연결하는 매개체이다. begin()은 컨테이너의 맨 첫번째 원소의 위치를 가리키는

반복자를 리턴한다. 비슷하게, end()는 컨테이너의 맨 마지막 원소의 위치를 가리키는

반복자를 리턴한다. 이 두 반복자와 알고리듬을 이용하여 컨테이너의 원소들을 정렬한다. 방금

설명한 것들을 이용하여 프로그램 1 을 다음과 같이 고칠 수 있다.

#include <iostream>#include <vector>#include <algorithm>

using namespace std;

int main(){    vector<int> v;

    // 입력

    int input;    while (cin >> input)        v.push_back(input);

14

Page 15: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

    // 정렬

    sort(v.begin(), v.end());

    // 출력

    for (int i = 0; i < v.size(); i++)        cout << v[i] << endl;}

컨테이너(container), 알고리듬(algorithm), 반복자(iterator) 이 세가지가 STL 에서 가장

중요한 것들이며, 특히 반복자를 능숙하게 다루어야 STL 을 잘 활용할 수 있을 것이다.

1.5.3 프로그램 3: 반복자 어댑터

#include <iostream>#include <vector>#include <algorithm>

using namespace std;

int main(){    vector<int> v;    istream_iterator<int> start(cin), end;    back_insert_iterator<vector<int> > dest(v);

    // 입력

    copy(start, end, dest);

    // 정렬

    sort(v.begin(), v.end());

    // 출력

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n"));}

15

Page 16: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

2장: 반복자(iterator)

2.1 반복자에 대한 소개

▶ 반복자(iterator)

반복자(iterator)는 포인터와 상당히 비슷하며, 컨테이너에 저장되어 있는 원소들을 참조할 때

사용한다.

반복자(iterator)란 개념은 표준 라이브러리가 제공하는 컨테이너 클래스와 알고리듬을 사용할

때 반드시 알아두어야 할 중요한 개념이다. 좀 추상적으로 말하자면, 반복자란 컨테이너에

저장되어 있는 모든 원소들을 전체적으로 훑어나갈 때 사용하는, 일종의 포인터와 비슷한

객체라고 할 수 있다. 알고리듬마다 각기 다른 방식으로 컨테이너를 훑어가기 때문에, 반복자에도 여러가지 종류가 있게 된다. 프로그래머는 표준 라이브러리에서 제공하는

컨테이너들에 알맞는 반복자를 생성할 수 있다. 특정 알고리듬이 어떤 컨테이너와 함께 사용할

수 있는가는 어떤 종류의 반복자를 인자로 요구하느냐에 따라 결정된다.

▶ 구간(range)

구간(range)이란 컨테이너에 담긴 값들의 시퀀스를 나타낸다. 구간은 반복자 한쌍으로

나타내며, 이 반복자들이 각각 시퀀스의 시작과 끝을 가리킨다.

포인터가 기존 프로그래밍에서 다양한 방법으로 사용되듯이, 반복자도 다양한 목적으로 사용될

수 있다. 포인터가 특정 메모리 위치를 참조하는데 사용될 수 있듯이, 반복자도 특정 값을

지정하는데 사용될 수 있으며, 연속적인 메모리 영역을 두개의 포인터로 나타내듯이, 한쌍의

반복자는 값들의 구간(range)을 설정하는데 사용될 수 있다. 그러나, 반복자의 경우에는, 설정되는 범위내의 값들이 반드시 물리적으로 연속적이어야 할 필요가 없다. 단, ① 이 두개의

반복자가 같은 컨테이너로부터 생성된 것이어야 하며, ② 두번째 반복자는 첫번째 반복자

이후에 와야한다. 그리고, 첫번째 반복자에서 두번째 반복자까지 차례로 컨테이너 내부의

원소들을 처리하게 되므로, 논리적인 연속성을 지닌다고 할 수 있겠다.

기존의 포인터는 상황에 따라, 널(Null)값을 가질 수가 있는데, 이는 아무것도 가리키지

않는다는 의미이다. 반복자도 마찬가지이다. 널 포인터를 참조하는 것이 논리적인 에러를

유발할 수 있듯이, 어떤 값도 가리키지 않는 반복자를 참조하는 것은 에러를 유발한다.

C++ 프로그램에서 메모리의 한 영역을 표시하기 위해 두개의 포인터를 사용하는 경우, 끝을

나타내는 포인터는 영역의 일부로 간주되지 않는 것이 보통이다. 예를 들어서, 크기가 10 인

16

Page 17: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

배열 x 를 x 에서 x+10까지의 메모리 영역에 걸쳐있다고 표현할 수 있는데, x+10 에 위치한

원소는 배열의 일부분이 아니다. 대신, x+10 이라는 포인터 값은 past-the-end 라고 불리운다. 반복자도 구간을 설정하는데 있어 이와 비슷한 방법을 사용한다. 두번째 값은 구간의 일부로

인정되지 않는다. 대신에, 두번째 값은 past-the-end 원소라고 하며, 구간의 마지막 원소

다음에 위치한 원소를 지칭한다. 메모리를 가리키는 포인터의 경우처럼, 이 past-the-end 원소가 실질적인 값을 가질 수도 있고, 또 어떤 때는 past-the-end 를 위해 따로 정해둔 값을

사용하기도 한다. 어쨌든, 범위의 끝을 나타내는 반복자를 참조하는 것은 피해야 한다.

▶ end()는 끝이 아니다?

컨테이너를 다룰 때 자주 쓰이는 end()라는 멤버함수가 있는데, 이 때, end()가 가리키는 것은

컨테이너의 맨 마지막 원소가 아니라는 점에 항상 주의해야 한다. end()가 가리키고 있는 것은

맨 마지막 원소의 바로 다음번 원소이다. 따라서, 이러한 반복자를 past-the-end 반복자라

부르는 것이다. 종점을 지나쳐버린 곳을 가리키는 반복자라는 뜻이다. end() 멤버함수를 통해

얻어지는 반복자는 결과적으로 아무 의미가 없는 것을 가리키고 있는 셈이다. 따라서, 이

반복자가 가리키는 것을 참조하면 예상치 못한 오류를 초래하게 된다. 이렇듯 반복자를 다룰

때는 항상 조심해야 한다. 그리고, 참고로 아무 원소를 가지고 있지 않은 컨테이너의 begin()과

end()는 같아진다. 따라서, 다음과 같은 코드가 가능하다.   bool empty(const STL_Container& container) {   return container.begin() == container.end();   }

기존의 포인터와 같이, 반복자를 수정하는 기본적인 연산은 증가 연산자(++)이다. 어떤

시퀀스의 마지막 원소를 가리키는 반복자에 증가 연산자를 적용시키면, 이 반복자는 past-the-end 값을 가지게 된다. 만약에 반복자 i 에 유한번의 증가 연산자를 적용하여, 반복자 i 가 j 와

같아질 수 있다면, 반복자 j 는 반복자 i 로부터 도달가능(reachable)하다고 부른다.

▶ 반복자 구간(iterator range)

반복자 2 개를 사용하여 컨테이너의 특정 구간에 속한 원소들을 나타내고자 한다면, 두번째

반복자가 첫번째 반복자로부터 도달가능(reachable)해야 한다. 그렇지 않으면, 에러가

발생한다. 이는 알고리듬이나 컨테이너 내부에서 검사하지 않기 때문에, 프로그래머가 책임져야

할 부분이다.

구간을 사용하여 컨테이너 원소들 전체를 지칭할 수 있는데, 이는 첫번째 원소를 가리키는

반복자와 끝을 가리키는 반복자로 구성한다. 또한, 구간을 사용하여 컨테이너내의 일부

17

Page 18: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

시퀀스를 지칭할 수도 있다. 두개의 반복자를 사용하여 구간을 지정할 때는, 두번째 반복자는

첫번째 반복자로부터 도달가능(reachable)해야 한다. 알고리듬에서는 이 조건을 확인하지

않고 사용하기 때문에, 이 조건이 만족되지 않으면, 에러를 유발하게 된다.

이제부터는 표준 라이브러리에서 사용되는 반복자들의 종류와 반복자 관련 함수들을 설명한다.

2.2 반복자의 종류

표준 라이브러리에서 사용되는 반복자들은 기본적으로 다음 5가지 형태로 나눌 수 있다.

입력 반복자(input iterator) 읽기만 가능, 순방향 이동

출력 반복자(output iterator) 쓰기만 가능, 순방향 이동

순방향 반복자(forward iterator) 읽기/쓰기 모두 가능, 순방향 이동

양방향 반복자(bidirectional iterator) 읽기/쓰기 모두 가능, 순방향/역방향 이동

임의 접근 반복자(random access iterator)

읽기/쓰기 모두 가능, 임의접근

반복자는 계층적으로 분류된다. 순방향 반복자는 입력 반복자와 출력 반복자를 필요로 하는

곳이라면 언제든지 사용될 수 있으며, 양방향 반복자는 순방향 반복자가 올 수 있는 자리에 올

수 있으며, 양방향 반복자를 필요로 하는 상황에서는 임의접근 반복자를 사용해도 된다.

반복자의 두번째 성질은 자신과 연결된 컨테이너의 원소들을 변경할 수 있는 능력을 가지고

있는지의 여부이다. 상수 반복자는 읽기용으로만 사용될 수 있는 반복자이며, 갱신용으로는

사용될 수 없다. 따라서, 쓰기만 가능한 출력 반복자는 절대 상수가 될 수 없으며, 반면에 읽기만

가능한 입력 반복자는 언제나 상수 반복자가 되는 것이다. 다른 반복자들은 어떤 식으로

생성되었는가에 따라 상수 반복자가 될 수도 있고, 그렇지 않을 수도 있다. 동시에 상수이면서

상수가 아닌 양방향 반복자가 있으며, 동시에 상수이면서 상수가 아닌 임의접근 반복자가 있는

등 여러가지가 있다.

아래 표는 반복자 종류별로 컨테이너에 의해서 생성되는 방식과 가능한 연산들을 정리한 것이다.  

18

Page 19: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

반복자 종류 생성되는 방식 읽기 접근 쓰기 증감 비교

입력 반복자

(input iterator)istream_iterator =*

p->   ++ == !

=

출력 반복자

(output iterator)ostream_iterato

r  inserter 

front_inserter  back_inserter

    *p=

++  

순방향 반복자

(forward iterator)  =*

p-> *p

=++ == !

=

양방향 반복자

(bidirectional iterator) 

list set과 multiset

map과

multimap

=*p

-> *p=

++ -- == !=

임의접근 반복자

(random access iterator) 

일반 포인터

vector deque

=*p

->[]

*p=

++ --+  - += -

=

== !=

<  > <= >=

2.2.1 입력 반복자(input iterator)

입력 반복자(input iterator)는 가장 단순한 형태의 반복자이다. 입력 반복자의 성질을 이해하기

위해, 예제 프로그램을 보자. find()라는 generic 알고리듬은 순차 검색을 하여 컨테이너에

포함된 특정 값을 찾아내는 일을 한다. 컨테이너의 내용은 first 와 last 의 두 반복자로

지정된다. first 가 last 와 같지 않는 동안에는, first 가 가리키는 값을 찾고자 하는 값과

비교한다. 만약에 같다면, 찾아낸 원소를 가리키는 반복자를 리턴한다. 같지 않으면, first 반복자를 증가시키고, 루프 주기를 한번 돌게 된다. range 전체를 모두 검사했는데도 원하는

값을 찾지 못하면, find()는 end-of-range 반복자를 반환한다.

template <class InputIterator, class T>InputIterator find (InputIterator first, InputIterator last, const T& value){

19

Page 20: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   while (first != last && *first != value)       ++first;   return first;}

이 알고리듬은 입력 반복자가 갖춰야 할 세가지 요구사항을 설명하고 있다. 반복자는 다른 반복자와의 상등여부를 비교할 수 있어야 한다. 같은 위치를 가리키면

같은 것이고, 그렇지 않다면 다른 것이다. 반복자는 * 연산자를 사용하여 반복자가 가리키는 값을 얻을 수 있어야 한다. 반복자는 ++ 연산자를 사용하여 다음 원소를 가리킬 수 있도록 증가될 수 있어야 한다.

입력 반복자에는 다음 세가지 종류가 있다.

일반 포인터. 일반적으로 사용되는 그냥 평범한 포인터들은 입력 반복자로 사용될 수가 있다. 포인터를 참조하고 증가시킬 수 있으므로, 임의의 값을 접근할 수 있으며, 따라서 포인터들은 입력 반복자나 출력 반복자로 사용할 수 있다. 예를 들어, 다음은 정수 배열안에서 7 을 찾는 코드이다.

▶ 일반 포인터와 반복자

C++ 포인터는 임의접근 반복자와 기능면에서 동일하기 때문에, 표준 라이브러리의 generic 알고리듬은 표준 라이브러리에서 제공되는 컨테이너뿐만 아니라 C++ 배열에도 사용될 수

있다.

int data[100];   ...int *where = find(data, data + 100, 7);

상수 포인터는 선언시에 키워드 const를 붙이면 되며, 상수 포인터에 의해 참조되는 배열의

원소는 수정이 불가능하다. const int *first = data;const int *last = data + 100;// can't modify location returned by the followingconst int *where = find(first, last, 7);

컨테이너 반복자(container iterator). 표준 라이브러리가 제공하는 다양한 컨테이너로부터

생성된 반복자들은 모두 입력 반복자가 갖추어야 할 조건을 만족한다. 콜렉션의 첫번째 원소는

begin()이라는 멤버 함수를 통해서 얻을 수 있으며, past-the-end 를 가리키는 반복자는

20

Page 21: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

end()라는 멤버 함수를 통해서 얻을 수 있다. 예를 들어, 다음은 정수 리스트에서 7 을 찾는

코드이다.

list<int>::iterator where = find(aList.begin(), aList.end(), 7);반복자를 지원하는 각각의 컨테이너들은 클래스 선언안에 'iterator'라는 이름을 가진 타입을

제공해야 한다. 이것을 사용함으로써, 반복자를 일관된 형태로 선언할 수 있다. 만약에

접근하고자 하는 컨테이너가 상수이거나, const_iterator를 사용하여 선언하면, 그

반복자는 상수 반복자가 된다.

입력 스트림 반복자(input stream iterator). 표준 라이브러리는 입력 반복자를 사용하여

입력 스트림에 대해 작업을 할 수 있는 방법을 제공한다. 이는 istream_iterator 클래스를

통해 제공되는데 2.3.1 절 에서 더 자세히 다루도록 한다.

2.2.2 출력 반복자(output iterator)

출력 반복자(output iterator)는 입력 반복자와는 반대되는 기능을 가진다. 출력 반복자는

시퀀스에 값을 대입하는데 사용될 수 있지만, 값을 접근하는데는 사용될 수 없다. 예를 들어, 한

시퀀스에서 다른 시퀀스로 값들을 복사하는 generic 알고리듬에서 출력 반복자를 사용할 수

있다.

template <class InputIterator, class OutputIterator>OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result) {    while (first != last)        *result++ = *first++;    return result;}

▶ 병렬 시퀀스

많은 수의 generic 알고리듬들이 두개의 병렬 시퀀스를 다루는 것들이다. 대부분의 경우, 두번째 시퀀스는 반복자 한쌍 대신 시작 반복자 한개만을 사용하여 표시된다. 이런 경우에는

두번째 시퀀스가 적어도 첫번째 시퀀스만큼의 원소 갯수를 가지고 있다고 가정하고 있다. 다만, 이 조건은 프로그래머가 책임져야 할 부분이다.

여기서는 두개의 range 가 사용되었는데, 하나는 한쌍의 반복자로 표현된 소스 range 이고, 하나는 목적지 range 이다. 그러나, 목적지 range 는 한개의 반복자로만 표현이 되어있다. 이는

21

Page 22: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

목적지가 소스쪽의 모든 값들을 수용할 수 있을만큼 충분히 크다고 가정하는 것이다. 따라서, 만약에 목적지가 충분한 공간을 확보하고 있지 않다면, 에러가 발생할 수 있다.

이 알고리듬이 설명하고 있듯이, 출력 반복자는 자신이 가리키는 원소들을 수정할 수 있다. 출력

반복자는 * 연산자를 사용할 때, 대입연산의 대상이 되는 방식으로만 사용될 수 있으며, 자신이

가리키는 원소들을 리턴하거나 접근하기 위해 사용될 수 없다.

앞에서 말했듯이, 표준 라이브러리가 제공하는 컨테이너가 생성한 반복자뿐만 아니라, 일반적인

포인터도 출력 반복자의 한 예가 될 수 있다.(일반 포인터는 임의접근 반복자이므로, 동시에

출력 반복자가 될 수 있다.) 그러므로, 예를 들어, 다음 코드는 평범한 C 형태의 배열에서 표준

라이브러리 vector 로 원소들을 복사한다.

int data[100];vector<int> newdata(100);   ...copy (data, data+100, newdata.begin());

istream_iterator가 입력 반복자 방식을 사용하여 입력 스트림을 다루는 방법을 제공하는

것과 마찬가지로, 표준 라이브러리는 ostream_iterator 타입을 제공한다. 이는 반복자

방식으로 출력 스트림에 값들을 쓰도록 해준다. 이에 관해서는 2.3.2절에서 더 자세히 다루도록

한다.

출력 반복자의 또 다른 형태는 '삽입 반복자'이다. 삽입 반복자는 출력 반복자의 참조/대입하고

증가시키는 연산을 컨테이너에 대한 삽입연산으로 바꾼다. 이렇게 함으로써 copy()와 같은

연산들을 list 와 set 과 같은 가변길이 컨테이너들과 함께 사용할 수 있게 된다. 삽입 반복자에

관해서는 2.4절에서 보다 자세히 다룬다.

2.2.3 순방향 반복자(forward iterator)

순방향 반복자(forward iterator)는 입력 반복자와 출력 반복자의 특징을 결합한 것이다. 값을

접근하고 갱신하는 것이 가능하게 된 것이다. 순방향 반복자를 사용하는 함수로 replace() generic 알고리듬이 있는데, 이는 원하는 값을 찾아 다른 값으로 대치하는 작업을 한다. 이

알고리듬은 다음과 같이 작성될 수 있다.

template <class ForwardIterator, class T>void replace (ForwardIterator first, ForwardIterator last,               const T& old_value, const T& new_value){

22

Page 23: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

    while (first != last) {       if (*first == old_value)           *first = new_value;       ++first;    }}

일반적인 포인터도 순방향 반복자로 사용될 수 있다. 다음 예는 정수 vector에서 나타나는 모든

7을 11로 바꾼다. replace(aVec.begin(), aVec.end(), 7, 11);

2.2.4 양방향 반복자(bidirectional iterator)

양방향 반복자(bidirectional iterator)는 순방향 반복자와 상당히 비슷하다. 다만, 양방향

반복자는 감소 연산자(--)를 지원하므로, 컨테이너내의 원소들을 순방향이나 역방향으로

이동하는 것이 허용된다는 점에서 차이가 난다. 예를 들어, 컨테이너 내의 값들의 순서를 뒤집어

새로운 컨테이너로 옮기는 함수에서는 양방향 반복자를 사용한다.

template <class BidirectionalIterator, class OutputIterator>OutputIterator reverse_copy (BidirectionalIterator first,                             BidirectionalIterator last,                             OutputIterator result) {    while (first != last)     *result++ = *--last;    return result;}

last 인자가 처음 가리키는 값은 콜렉션의 일부가 아니므로, 먼저 감소시킨후 (--last) 값을

참조(*)해야 한다.

다음 reverse_copy() 함수는 연결 리스트의 값들을 뒤집어, 그 결과를 vector 로 옮긴다.

list<int> aList; ....vector<int> aVec(aList.size());reverse_copy(aList.begin(), aList.end(), aVec.begin());

23

Page 24: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

2.2.5 임의접근 반복자(random access iterator)

어떤 알고리듬은 단순히 앞뒤 방향으로 값들을 접근하는 능력 이상의 기능을 필요로 한다. 임의접근 반복자(random access iterator)는 첨자에 의한 접근이 가능하고, 다른 반복자와의

차(이 경우에는 두 반복자 사이에 존재하는 원소의 수를 얻을 수 있다)를 구할 수 있고, 산술연산을 통해 값을 수정할 수 있는 등, 기존의 일반 포인터가 했던 모든 것을 할 수 있다.

일반 포인터를 사용할 때, 산술 연산은 메모리와 관련된다. 즉, x+10 은 x 부터 10 개 원소

이후의 메모리위치를 나타낸다. 반복자의 경우에도 논리적인 의미는 그대로 유지된다. 그러나, 물리적 주소는 달라지게 된다.

임의접근 반복자를 사용하는 알고리듬에는 정렬과 이진검색과 같은 연산등을 포함한다. 예를

들어, 다음 알고리듬은 컨테이너의 원소들을 임의로 섞는다. 이 알고리듬은 표준 라이브러리가

제공하는 random_shuffle()보다 좀 단순하긴 하지만, random_shuffle()과

비슷하다.

template <class RandomAccessIterator>void mixup(RandomAccessIterator first, RandomAccessIterator last){   while (first < last) {      iter_swap(first, first + randomInteger(last - first));      ++first;   }}

▶ randomInteger()

여기서 설명한 randomInteger() 함수는 이후에 나오는 예제 프로그램에서 자주 사용될

것이다.

이 프로그램은 first 반복자가 last 가 될 때까지 루프를 반복한다. 오직 임의 접근 반복자만이

관계 연산자를 이용하여 서로 비교가 가능하며, 이외의 다른 반복자들은 오직 상등 여부만을

판단할 수 있을 뿐이다. 매번 루프를 돌 때마다 last - first 라는 수식을 통해 양 끝 사이에

속하는 원소들의 갯수를 얻게되며, randomInteger() 함수를 사용하여 0 과 인자값사이에

속하는 난수를 얻을 수 있다. 이 함수는 표준 난수 발생기를 사용하여, 다음과 같이 작성할 수

있다.

// n 보다 작고 0 보다 크거나 작은 정수형 난수를 리턴

24

Page 25: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

unsigned int randomInteger(unsigned int n){   return rand() % n;}

이 난수값은 first 반복자 위치에 더해져서, 컨테이너 내에서 임의의 위치를 가리키게 된다. 그리고, 이 반복자가 가리키는 값을 first가 가리키는 원소와 맞바꾸게 된다.

2.2.6 역 반복자(reverse iterator)

반복자는 자신과 연결된 컨테이너상에서의 순서를 지니고 있다. vector 와 map 에서는 첨자를

증가시키면서 순서가 정해지고, set 에서는 크기가 증가하는 방향으로 순서가 정해지고, list에서는 값들이 첨가되는 방식에 따라 순서가 정해진다.

역 반복자(reverse iterator)는 표준 반복자가 부여받은 순서와는 반대되는 순서로 값들을

접근한다. 즉, vector 나 list 에서 역 반복자는 마지막 원소를 맨 먼저 접근하고, 첫번째 원소를

맨 마지막에 접근한다. set 에서는 가장 큰 원소가 맨 먼저 얻어지고, 가장 작은 원소가 마지막에

접근된다. 엄격히 말해서, 역 반복자는 새로운 종류의 반복자가 아니다. 따라서, 역 양방향

반복자나, 역 임의접근 반복자등등이 있을 수 있다.

list, set, map 타입은 역 양방향 반복자를 생성하는 멤버 함수를 한쌍 가지고 제공하고 있다. rbegin()과 rend() 함수는 해당 컨테이너를 역순으로 훑는 반복자를 생성한다. 역 반복자에

대한 증가연산은 후퇴를 의미하고, 감소연산은 전진을 의미한다.

유사하게, vector 와 deque 타입에서는 역 임의접근 반복자를 생성하는 멤버 함수를 가지고

있다.(이름은 똑같이 rbegin()과 rend()이다.) 증가연산 뿐만아니라, 첨자와 덧셈 연산 역시

후퇴를 의미한다.

2.3 스트림 반복자(stream iterator)

스트림 반복자는 반복자 연산을 이용하여 입력 또는 출력 스트림을 접근하는데 사용된다.

2.3.1 입력 스트림 반복자(input stream iterator)▶ 스트림 반복자(Stream Iterators)

입력 스트림 반복자는 반복자 연산을 통해 입력 스트림으로부터의 read 작업을 수행하게 해주는

것이다. 이와 마찬가지로, 출력 스트림 반복자는 반복자 연산이 수행될때 출력 스트림으로 write

25

Page 26: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

작업이 이루어지게 된다.

입력 반복자를 설명할 때 언급했듯이, 표준 라이브러리는 입력 스트림을 입력 반복자로

변환하는 방식을 제공한다. 이는 istream_iterator 에 의해 제공된다. 선언할 때, 4 개의

템플릿 인자가 필요한데, 원소의 타입, 스트림 문자의 타입, 문자 trait 타입, 그리고 원소간의

폭을 나타내는 값에 대한 타입이 필요하다. 마지막 2 개는 디폴트로 char_traits<charT>와 ptrdiff_t 를 가진다. 이 디폴트 인자는 대부분의 경우에 제대로 작동한다. istream_iterator 의 생성자로 들어가는 인자는 단 하나인데, 이는 접근할 스트림을

나타낸다. 입력 스트림 반복자에 대해 증가 연산자(++)가 적용될 때마다 스트림으로부터

새로운 값을 읽어(>> 연산자 사용) 저장한다. 이 값은 참조 연산자(*)를 사용하여 얻을 수

있다. 생성자에 아무런 인자도 넣지 않고 istream_iterator 에 의해 생성된 값은 입력의

끝을 나타내는 반복자 값으로 사용된다. 다음은 정수들을 지닌 화일로부터 가장 먼저 나타나는

7 을 찾는 코드이다.

istream_iterator<int> intstream(cin), eof;istream_iterator<int>::iterator where =          find(intstream, eof, 7);

입력 스트림을 위한 반복자가 가리키는 원소는 스트림에서의 다음 원소가 요청될 때까지만

유효하다. 또한, 입력 스트림 반복자는 입력 반복자이기 때문에, 원소들에 대한 접근만이

가능하며, 대입에 의한 갱신은 불가능하다. 마지막으로, 원소들은 오직 한번 순방향으로만

접근이 가능하다. 스트림의 내용을 한번 이상 읽고 싶다면, 각 pass마다 별도의 반복자를

생성해야 한다.

▶ 최종 표준에서 변경된 사항

최종 표준에서는 istream_iterator 클래스와 ostream_iterator 클래스의 두번째 템플릿

인자도 디폴트 인자로 바뀌었다. 따라서, 기존에 istream_iterator<int, char>와 같이 쓰던

것을 istream_iterator<int>와 같이 간단히 쓸 수 있게 되었다. 이 사항은 Stroustrup의 'The C++ Programming Language, 3ed' 5쇄부터 제대로 반영되어 있다. 아래 링크를 클릭해서

'pg 558'과 'pg 559'에 관련된 부분을 참고하기 바란다. 4 쇄에서 5 쇄로 넘어가면서 바뀐

사항들

2.3.2 출력 스트림 반복자(output stream iterator)

출력 스트림 반복자(output stream iterator) 방식은 입력 스트림 반복자와 비슷하다. 값들이

반복자로 대입될 때마다, 그 값은 >> 연산자를 사용하여 해당 출력 스트림으로 출력된다. 출력

스트림 반복자를 생성하기 위해서는 생성자의 인자로 출력 스트림을 지정해야 한다. 출력

26

Page 27: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

스트림으로 쓰여지는 값들은 >> 연산자를 인식할 수 있어야 한다. 즉, 해당 값에 대해 >> 연산자가 정의되어 있어야 한다. 생성자의 생략 가능한 두번째 인자는 출력되는 값들 사이의

분리자로 사용될 문자열을 나타낸다. 다음 예는 vector 의 원소값들을 표준 출력으로 복사하는

예이다. 출력되는 값들을 공백문자로 구분하고 있다.

copy(newdata.begin(), newdata.end(),       ostream_iterator<int>(cout, " "));

입출력 스트림 반복자와 표준 라이브러리가 제공하는 다양한 알고리듬을 혼합하여 간단한 화일

변환 알고리듬을 만들어볼 수 있다. 다음은 표준입력으로부터 정수들이 담긴 화일을 읽어들여, 이 화일에 들어있는 7이란 값을 제거하고, 나머지는 표준 출력으로 출력하고, 출력되는 값들

사이에 개행문자를 출력하는 간단한 프로그램이다. int main() {   istream_iterator<int> input(cin), eof;   ostream_iterator<int> output(cout, "\n");

   remove_copy(input, eof, output, 7);}

2.4 삽입 반복자(insert iterator)

출력 반복자에 의해 참조되는 값에 대입을 하면, 그 위치의 내용을 덮어쓰게 된다. 예를 들어, 다음과 같이 copy() 함수를 호출하면, 선언문에서 두번째 vector 에 대한 메모리 공간이 이미

확보하였는데, 이쪽 vector 에서 다른쪽 vector 로 값들을 옮기게 된다.

vector<int> a(10);vector<int> b(10);   ...copy(a.begin(), a.end(), b.begin());

list 와 같은 데이터구조에서도 이와 같은 방식으로 덮어쓰게 된다. 아래 c 라고 이름붙은 list 는

적어도 10 개의 원소는 가지고 있다고 가정할 때, 리스트의 처음 10 개의 원소들은 vector a 의

내용으로 바뀌게 된다. list<int> c;   ...

27

Page 28: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

copy(a.begin(), a.end(), c.begin());list 와 set 과 같은 구조에서는 원소가 새로 추가되면, 동적으로 크기가 커지게 되는데, 기존의

위치에 덮어쓰는 것보다는 삽입하는 것이 더 적절할 때가 많다. 삽입 반복자라고 불리는 일종의

어댑터를 사용하여 copy()와 같은 알고리듬을 사용할 때 원소들을 덮어쓰지 않고, 해당

컨테이너로 삽입하게 된다. 반복자의 출력 연산은 해당 컨테이너로의 삽입 연산으로 바뀌게

된다. 다음 예는 vector 의 원소들을 공백 list 에 삽입하는 코드이다. list<int> d;

copy(a.begin(), a.end(), front_inserter(d));

삽입 반복자에는 3 가지 형태가 있는데, 이들 모두 복사 연산을 삽입 연산으로 바꾸는데

사용된다. 위에서처럼, front_inserter() 사용하여 얻어진 반복자는 해당 컨테이너의

앞쪽에 값들을 삽입한다. back_inserter()를 통해 얻은 반복자는 해당 컨테이너의 뒤쪽에

값들을 삽입한다. 두가지 형태 모두 list 와 deque 와 함께 사용될 수 있으나, set 과 vector 에는

사용될 수 없다.

세번째 형태는 가장 일반적인 형태로 inserter 가 있다. inserter 는 두개의 인자를 취하는데, 컨테이너와 컨테이너내의 반복자가 그것이다. 이 형태는 원소들을 컨테이너의 특정 위치로

복사한다. (list 의 경우에는 원소들이 지정된 위치의 바로 앞쪽에 복사된다). 이 형태는 set 과

map뿐만 아니라 앞에서 언급한 두 형태와 같이 사용될 수 있는 모든 구조들과 같이 사용될 수

있다.

다음은 삽입 반복자의 세가지 형태의 사용을 모두 설명하고 있다. 처음에는, 3,2,1 을 빈 list 의

맨 앞에 삽입한다. 결과적으로, 1,2,3 으로 구성된 list 가 얻어진다. 다음에는 7,8,9 를 list 의

끝에 삽입한다. 마지막으로 find() 연산을 사용하여 7 의 위치를 가리키는 반복자를 찾아, 그

앞에 4,5,6 을 삽입한다. 결과는 1 부터 9까지의 숫자들을 순서대로 가지는 list 가 된다.

int main(){   int threeToOne[] = {3, 2, 1};   int fourToSix[] = {4, 5, 6};   int sevenToNine[] = {7, 8, 9};

   list<int> aList;

28

Page 29: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   // first insert into the front   // note that each value becomes new front   copy(threeToOne, threeToOne+3, front_inserter(aList));

   // then insert into the back   copy(sevenToNine, sevenToNine+3, back_inserter(aList));

   // find the seven, and insert into middle   list<int>::iterator seven = find(aList.begin(), aList.end(), 7);   copy(fourToSix, fourToSix+3, inserter(aList, seven));

   // copy result to output   copy(aList.begin(), aList.end(),          ostream_iterator<int,char>(cout, " "));   cout << endl;}

여기서, inserter(aList, aList.begin())과 front_inserter(aList)에 의해 생성된

반복자들간에 중요하고도 미묘한 차이가 있음을 주목해야 한다. inserter(aList, aList.begin()) 호출하게 되면 값들을 list의 앞에 순서를 그대로 유지하여 삽입하는 반면에, front_inserter(aList)는 삽입되는 각각의 값들이 list에 가서는 맨앞에 삽입된다는 점이다. 결과적으로 front_inserter(aList)는 원래 값들이 유지했던 순서가 뒤집히게 되며, inserter(aList, aList.begin())는 원래 순서를 그대로 유지한다.

2.5 반복자 연산(iterator operation)

표준 라이브러리는 반복자를 다루는데 사용되는 두개의 함수를 제공한다. advance() 함수는

하나의 반복자와 하나의 숫자값을 인자로 받아서, 반복자를 주어진 값만큼 이동시킨다.

void advance(InputIterator& iter, Distance& n);임의접근 반복자의 경우에, 위 함수는 iter + n; 과 같은 효과를 가진다. 그러나, 위 함수는

모든 형태의 반복자에 대해 적용될 수 있다. 이동거리는 순방향 반복자의 경우에는 반드시

양수이어야 하고, 양방향 반복자나 임의접근 반복자의 경우에는 양수나 음수이면 된다. 위

연산은 임의접근 반복자에 대해서만 효율적(constant time)이다. 다른 경우에는 반복자에 대해

증가 연산자(++)나 감소 연산자(--)를 호출하는 루프로 advance()를 구현한다.

29

Page 30: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

advance() 함수는 해당 반복자에 대해 연산이 유효한지에 대해서는 따로 검사하지 않는다.

두번째 함수인 distance()는 두 반복자간의 거리를 반환한다.

void distance(InputIterator first, InputIterator last,              Distance& n);

결과값은 레퍼런스로 넘어간 세번째 인자로 반환되며, first를 last로 옮길 때, ++연산자가

수행되는 횟수만큼 증가한다. 따라서, 이 함수를 호출하기 전에 세번째 인자를 통해 넘겨지는

변수가 제대로 초기화가 되었는지를 항상 확인해야 한다.

3장: 함수(function)와 조건자(predicate)

3.1 함수(function)

표준 라이브러리에서 제공하는 알고리듬중에는 함수를 인자로 요구하는 것들이 많다. 예를

들어, for_each() 알고리듬은 컨테이너에 담긴 각각의 원소에 인자로 넘겨받은 함수를

적용한다. 아래는 printElement() 함수를 사용하여 정수 list 의 원소들을 출력하는 코드이다.

void printElement(int value){   cout << "The list contains " << value << endl;}

int main() {   list<int> aList;      ...   for_each(aList.begin(), aList.end(), printElement);}

이항 함수(binary function)는 두개의 인자를 취하는데, 서로 다른 두 시퀀스에서 값들을

하나씩 가져와 함수에 넘겨준다. 예를 들어, 문자열 list 와 정수 list 가 주어지고, 문자열 list 에

담긴 각각의 문자열을 정수 list 에 담긴 숫자만큼 복사한다고 할 때, 표준 라이브러리에서

30

Page 31: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

제공하는 transform() 함수를 사용하면 쉽게 할 수 있다. 이를 위해 우선 원하는 기능을 가진

이항 함수를 정의한다. // 'base'를 'number' 갯수만큼 복사한다. string stringRepeat(const string& base, int number){   string result;   // 'result'는 처음에 비어 있다.   while (number--)  result += base;   return result;}

그리고, 다음과 같이 transform()을 호출하면 원하는 결과를 얻을 수 있다. list<string> words;list<int> counts;   ...transform(words.begin(), words.end(), counts.begin(),   words.begin(), stringRepeat);

문자열 list (one, two, three)과 정수 list (3, 2, 3)를 인자로 하여transform()을

호출하면, (oneoneone, twotwo, threethreethree)의 결과를 얻을 수 있다.

3.2 조건자(predicate)

조건자(predicate)는 불값(true/false) 또는 정수값을 리턴하는 함수이다. 기존 C 에서의

관행에 따라, 영이 아닌 정수값은 참으로 간주하고, 영인 경우에는 거짓으로 간주한다. 다음은

정수값을 인자로 받아 윤년이면 참을, 아니면 거짓을 리턴하는 함수이다.(아래 윤년 프로그램은

안재형님이 제공해 주셨습니다.)

// 'year'가 윤년에 해당하면 참을 리턴한다.bool isLeapYear(unsigned int year){ bool flag = false;

if ((year % 4) == 0) { // 4년마다 윤년이고, flag = true; if ((year % 100) == 0) { // 그중 100년마다 윤년이 아니고, flag = false;

31

Page 32: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

if ((year % 400) == 0) { // 그중 400년마다 윤년

flag = true; } } }

return flag;}

generic 알고리듬의 인자로 조건자를 사용할 수 있는데, find_if() 알고리듬을 예로 들어보자. 이 함수는 조건자를 만족하는 첫번째 원소를 가리키는 반복자를 리턴하는데, 만약 이 조건자를

만족하는 원소가 없으면 end-of-range 값을 리턴한다. 이 알고리듬을 사용하여, 다음과 같이

년도들로 구성된 list(aList)에서 첫번째로 발견된 윤년의 위치를 리턴하는 코드를 만들 수

있다. list<int>::iterator firstLeap =    find_if(aList.begin(), aList.end(), isLeapYear);

3.3 함수 객체(function object)

함수 객체(function object)는 괄호 연산자를 멤버함수로 가지는 클래스의 객체이다. 함수

대신에 함수 객체를 사용하는 것이 더 편리한 경우가 많은데, 함수 객체를 함수로 사용하면, 함수가 호출될 때마다 함수 객체의 괄호 연산자가 호출된다. 예를 들어, 다음 클래스 정의를

살펴보자.

class biggerThanThree {public:   bool operator()(int val) { return val > 3; }};

이 biggerThanThree 클래스의 객체를 함수 호출 형태를 사용하여 참조하게 되면, 멤버함수로 정의된 괄호 연산자가 호출된다. 이번에는 이 클래스를 일반적인 용도에 쓸 수

있도록 다듬어 보자. 생성자와 데이터 멤버를 추가한다. class biggerThan {public:   const int testValue;   biggerThan(int x) : testValue(x) { }

32

Page 33: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   bool operator()(int val) { return val > testValue; }};

이렇게 함으로써 'X보다 큰가?'에 해당하는 함수 객체를 만들 수 있고, 여기서 X의 값은 함수

객체를 생성할 때 정할 수 있다. 조건자(predicate)를 인자로 요구하는 generic 함수에 함수

객체를 인자로 넘길 수 있는데, 다음은 list에서 12보다 큰 첫번째 값의 위치를 찾아내는

코드이다. list<int>::iterator firstBig =   find_if(aList.begin(), aList.end(), biggerThan(12));

일반 함수 대신에 함수 객체를 사용하는 가장 큰 이유로 세가지를 들 수 있는데, 첫째로, 새로운

함수를 만들지 않고 표준 라이브러리에서 제공되는 함수 객체를 사용하자는 것이고, 둘째로, 인라인 함수를 호출함으로써 수행속도를 향상하고자 하는 것이고, 셋째로, 함수 객체로 하여금

자신이 가지고 있는 상태정보를 접근하고, 갱신할 수 있도록 하자는 것이다. 프로그래머

입장에서는 세번째가 가장 매력적인 이유가 아닐까 싶다. 그럼, 각각의 예를 들어보자. A. 재사용성

다음 표는 표준 라이브러리에서 제공되는 함수 객체를 설명하고 있다. 이들 함수 객체를

사용하기 위해서는, #include <functional>을 통하여 반드시 헤더 화일을 포함시키도록 한다.  

이름 연산

산술 함수(arithmetic function)

plus  minus  multiplies  divides  modulus  negate

x + y x - y x * y x / y x % y

-x

비교 함수(comparison function)

equal_to not_equal_to

x == y  x != y 

33

Page 34: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

greater less greater_equal less_equal

x > y  x < y 

x >= y  x <= y

논리 함수(logical function)

logical_and logical_or logical_not

x && y  x || y 

!x

이것들이 어떻게 사용되는지 몇가지 예를 들어 살펴보자. 첫번째 예는 plus()를 사용하여

정수들로 구성된 두개의 list(listOne, listTwo)를 원소별로 더하여, 그 결과를 첫번째

list(listOne)에 배치한다.

// 이항 연산자인 경우: transform()의 네번째 인자에 연산의 결과값이 들어감

transform(listOne.begin(), listOne.end(), listTwo.begin(),    listOne.begin(), plus<int>());

두번째 예는 logical_not()을 사용하여 불값으로 이루어진 vector내의 모든 원소를

부정하는 예이다. // 단항 연산자인 경우: transform()의 세번째 인자에 연산의 결과값이 들어감

transform(aVec.begin(), aVec.end(), aVec.begin(),   logical_not<bool>());

클래스 정의의 위치

표준 라이브러리가 위 표에서 보여준 함수들을 정의할 때 사용하는 베이스 클래스들은 새로운

함수 객체들(단항, 이항)을 만들 때 필요하다. 이들 베이스 클래스들은 다음과 같다.

template <class Arg, class Result>struct unary_function {   typedef Arg argument_type;   typedef Result result_type;};

template <class Arg1, class Arg2, class Result>

34

Page 35: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

struct binary_function {   typedef Arg1 first_argument_type;   typedef Arg2 second_argument_type;   typedef Result result_type;};

이들 함수들을 사용하는 예는 6.3 절 에서 살펴본다. 여기서는 "Widget" 타입과 정수 타입을

인자로 받고, widget의 아이디 번호와 정수값을 서로 비교하는 이항 함수를 정의해보자. struct WidgetTester : binary_function<Widget, int, bool> {public:   bool operator()(const Widget& wid, int testid) const      { return wid.id == testid; }};

B. 성능함수 대신에 함수 객체를 사용하는 두번째 이유로 수행속도의 향상을 들었는데, 많은 경우에

위에서 예로든 transform()처럼 함수 객체의 호출을 in-line으로 대치함으로써 함수 호출의

오버헤드를 없앨 수 있게 된다.

함수 객체를 사용하여 레퍼런스 저장하기

C. 유연성("smart function")

함수대신에 함수 객체를 사용하는 세번째 이유는 함수를 매번 호출할 때마다 이전 호출시의

상태를 기억해야 될 상황이 있기 때문이다. 이러한 예로, generate() 알고리듬에 사용되는

발생기(generator)를 생성할 때를 들수 있는데, 여기서 발생기란 매번 호출될 때마다 다른 값을

반환하는 함수를 말한다. 가장 많이 사용되는 형태의 발생기는 난수 발생기를 들 수 있겠지만, 이외에도 여러가지 용도로 쓰일 수 있다. 시퀀스 발생기는 단순히 1,2,3,4 와 같이 자연수의

수열을 반환한다. 이 함수 객체를 APL 에 있는 이와 비슷한 연산의 이름을 따서, iotaGen 이라

하고, 다음과 같이 정의할 수 있겠다.

class iotaGen {public:   iotaGen(int start = 0) : current(start) { }   int operator()() { return current++; }private:   int current;};

35

Page 36: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

iotaGen 객체는 생성자의 초기값이나 디폴트 값(0)을 유지하고 있다가, 함수호출 연산자가

호출될 때마다, 현재 값을 반환하고 자신의 값을 하나 증가시킨다. 이 객체를 사용하여 다음과

같이 표준 라이브러리 함수 generate()를 호출하면 20개의 원소를 가진 vector를 1부터 20까지의 값으로 초기화할 수 있다.

vector<int> aVec(20);generate(aVec.begin(), aVec.end(), iotaGen(1));

3.4 함수 어댑터(function adaptor)

함수 어댑터(function adaptor)는 함수를 함수 객체로 사용할 수 있도록 전역함수 또는

멤버함수를 바꿔주는 클래스의 객체이다(함수 객체는 함수 또는 함수 객체의 행동을 바꾸는데

사용할 수도 있다. 이는 다음 절에서 다루도록 한다). 각각의 함수 어댑터는 전역 함수 또는 멤버

함수를 인자로 하는 생성자를 가지고 있다.

pointer_to_unary_function 과 pointer_to_binary_function 템플릿은 하나 또는

두개의 인자를 가지는 전역 함수를 개작하는데 사용된다. 이들 어댑터는 직접 적용할 수도 있고, ptr_fun 함수 템플릿을 사용하여 적절한 어댑터를 자동으로 생성할수도 있다. 예를 들어, 그냥

단순한 times3() 함수를 개작하여, 이를 정수 vector 에 적용할 수 있다.

int times3(int x){  return 3 * x;}

int a{} {1,2,3,4,5};vector<int> v(a, a+5), v2;

transform(v.begin(), v.end(), v2.end(), ptr_fun(times3));또는, 어댑터를 적용하여 새롭게 개작된 함수 객체를 vector 로 넘겨준다.

pointer_to_unary_function<int, int> pf(times3);transform(v.begin(), v.end(), v2.end(), pf);

이렇게 하는 경우, 컴파일러가 ptr_fun 을 사용하여 pointer_to_unary_function 이

요구하는 타입을 추론할 수 있다는 장점이 있다. (?)

36

Page 37: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

'mem_fun' 부류의 템플릿은 전역함수가 아닌 멤버함수를 개작하는데 사용된다. 예를 들어, list의 set 을 가지고 있고, set 내의 각 list 를 정렬하고 싶다면, mem_fun_t 나 mem_fun 을

사용하여 set 내의 각 list 에 list 정렬함수를 적용할 수 있다.

set<list<int>* > s;

// set 원소들을 list 들로 초기화한다

...// set 에 속해있는 list 들을 각각 정렬한다

for_each(s.begin(),s.end(),mem_fun(&list<int>::sort));

// 이제 set 에 속한 각각의 list 들이 모두 정렬되었다.This is necessary because the generic sort algorithm cannot be used on a list. This is also the simplest way to access any polymorphic characteristics of an object held in a standard container. For instance I might invoke a virtual draw function on a collection of objects that are all part of the canonical 'shape' hierarchy like this:

// shape 상속구조

class shape {   virtual void draw();};

class circle : public shape {   void draw();};

class square : public shape {  void draw();};

// vector 에 shape 들을 집어넣는다

circle c;square s;vector<shape*> v;v.push_back(&s);v.push_back(&c);

37

Page 38: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

// vector 에 담긴 각각의 shape 에 대해 draw()를 호출한다

for_each(v.begin(),v.end(), mem_fun(&shape::draw));전역함수 어댑터와 비슷하게, 멤버함수 어댑터는 각각 하나의 클래스 템플릿과 이와 관련된

함수 템플릿으로 구성된다. 클래스가 실질적인 어댑터이며, 함수가 그 클래스의 인스턴스를

도중에 생성함으로써 클래스의 사용을 단순하게 만들어준다. (?)예를 들어, 위에서

mem_fun_t 를 만들어서 for_each() 알고리듬으로 넘겨줄수도 있었다. mem_fun_t<shape> mf(&shape::draw);for_each(v.begin(), v.end(), mf);

이번에도, mem_fun 함수 템플릿이 컴파일러로 하여금 mem_fun_t 가 필요로하는 타입을

알아낼 수 있도록 함으로써 mem_fun_t 어댑터의 사용을 단순화한 것을 볼 수 있다.(?)

라이브러리는 인자를 가지지 않는 함수와 1 개의 인자를 가지는 함수들에 대해서 멤버 함수

어댑터를 제공한다. 하지만, 그 이상의 인자를 가지는 함수들에 대해서도 쉽게 확장이 가능하다.

3.5 부정자(negator)와 바인더(binder)

부정자(negator)와 바인더(binder)는 기존에 있던 함수 객체로부터 새로운 함수 객체를

만드는데 사용되는 함수 어댑터이다. 이들은 다른 함수나 generic 알고리듬을 호출하기에 앞서

인자 리스트를 구성하는 과정의 일부로 사용되는 것이 보통이다.

부정자 not1()와 not2()는 각각 단항과 이항 조건자 객체를 인자로 받아들여, 원래 값의

반대값을 내뱉는 새로운 함수 객체를 생성한다. 예를 들어, 앞절에서 정의한 WidgetTester 함수 객체를 이용하면, 함수객체

not2(WidgetTester())는 widget 검사기와 같은 인자를 가지고, widget 검사기가 참일때 거짓을, 거짓일때 참을

반환하는 이진 조건자를 만들어낸다. 부정자는 unary_function 과 binary_function 클래스의 하위 클래스로 정의된 함수 객체하고만 사용된다.

바인더는 두개의 인자를 가지는 함수를 받아서, 첫번째 인자나 두번째 인자를 특정 값으로

바인드시켜 한개의 인자를 가지는 함수를 만들어 낸다. 이때, 바인더의 인자로 넘어오는 함수는

binary_function 클래스의 하위 클래스에 속해야 한다. bind1st() 바인더는 첫번째

인자를 바인드하고, bind2nd()는 두번째 인자를 바인드한다.

38

Page 39: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

예를 들어, 바인더 bind2nd(greater<int>(), 5)는 5 보다 큰지를 검사하는 함수객체를

만들어내며, 다음과 같이 쓰일 수 있다. 아래 예는 list 에서 가장 먼저 발견되는 5 보다 큰 수를

가리키는 반복자를 반환하는 코드이다.

list<int>::iterator where = find_if(aList.begin(), aList.end(),           bind2nd(greater<int>(), 5));

바인더와 부정자를 결합해서, 다음과 같은 함수객체를 생성할 수 있다. 이 함수객체는 인자가 3으로 나눠지면 참을, 그렇지 않으면 거짓을 반환한다. 아래 코드는 list 로부터 3 의 배수를

제거한다. list<int>::iterator where = remove_if(aList.begin(), aList.end(),           not1(bind2nd(modulus<int>(), 3)));

아래에서 바인더는 이진 함수 WidgetTester() 함수를 호출할 때 widget 번호를 고정시켜서, widget 만을 유일한 인자로 취하는 함수를 만들어낸다.

list<Widget>::iterator wehave =    find_if(on_hand.begin(), on_hand.end(),       bind2nd(WidgetTester(), wid));

4장: 컨테이너(container) 클래스

4.1 개요

표준 라이브러리는 약 10 가지 종류의 컨테이너를 제공한다. 이 장에서는 이들 컨테이너의

종류와 각각의 성질에 대해 알아보고, 실제 문제를 해결하는데 있어 어떤 컨테이너를 선택해야

할지에 관해 언급하고자 한다. 이후의 절에서는 각 컨테이너에 대해서 보다 자세히 다루기로

한다.

다음 표는 표준 라이브러리가 제공하는 10 가지 형태의 컨테이너를 나열하고, 각 컨테이너의

주요 특징들을 덧붙여 놓았다.  

이름 특징

vector 임의접근이 가능하며, 뒤에서의 삽입이 빠름

39

Page 40: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

list 위치에 상관없이 삽입과 삭제가 빠름

deque 임의접근이 가능하며, 앞과 뒤에서의 삽입이 빠름

set 원소들을 순서대로 관리하며, 소속검사와 삽입,삭제가 빠름

multiset 중복을 허용하는 set

map 키를 통해 값을 접근하며, 삽입과 삭제가 빠름

multimap 중복키를 허용하는 map

stack top 에서만 삽입과 삭제가 가능

queue 삽입은 뒤쪽에서, 삭제는 앞쪽에서

priority_queue

가장 큰값의 접근과 삭제가 빠름

4.2 컨테이너 선택하기

다음 질문들은 특정 문제를 풀고자 할 때 어떤 종류의 컨테이너를 선택하는 것이 좋은가에 관한

몇가지 선택 기준을 제시하고 있다.

콜렉션내의 값들을 어떤 방식으로 접근하는가?

임의접근이 필요하다면, vector 와 deque 를 사용하라. 순차접근만으로 충분하다면, 다른 컨테이너를 써도 무방하다.

콜렉션내의 값들에 순서를 매길 것인가? 값들이 나열되는 방식에는 여러가지가 있다. 컨테이너가 유지되는 동안 계속 순서가

유지되어야 한다면, set 을 선택해야 한다. set 에 삽입을 하면 자동적으로 순서에 맞게

놓여진다. 반면에, 순서가 어느 한시점에서만 중요시된다면, list 나 vector 에 값들을

삽입하고, 적절한 때에 해당 컨테이너를 정렬을 하는 것이 더 수월하다. 만약에

데이터구조 내에서 유지되는 값들의 순서가 삽입하는 순서에 관계가 있다면, stack이나 queue 또는 list 가 최상의 선택이 될 것이다.

실행중에 데이터 구조의 크기가 광범위하게 변하는가? 그렇다면, list 나 set 를 선택하는 것이 가장 좋다. vector 나 deque 은

콜렉션으로부터 원소들을 제거한 뒤에도 커다란 버퍼를 계속 유지하고 있다. 만약에,

40

Page 41: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

콜렉션의 크기가 상대적으로 고정되어 있다면, vector 나 deque 가 같은 수의 원소를

지니고 있는 list 나 set 보다는 적은 메모리를 사용한다.콜렉션의 크기를 추정할 수 있는가?

vector 는 원하는 크기의 메모리 블럭을 미리 할당받을 수 있다(reserve() 멤버함수를 사용). 이는 다른 컨테이너에서는 제공되지 않은 기능이다.

어떤 값이 콜렉션내에 포함되어 있는지를 자주 확인하는가? 만약에 그렇다면, set 이나 map 을 선택하는 것이 좋겠다. set 이나 map 에서는 어떤

값이 컨테이너에 속해 있는지를 검사하는 것이 매우 빠르지만, 다른 종류의

콜렉션에서는 이를 검사하기 위해서 컨테이너에 저장된 모든 원소들을 일일이 비교해야

한다.콜렉션에 대해 인덱싱이 가능한가? 다시말해, 콜렉션을 키와 값의 쌍으로 생각할 수 있는가?

키들이 0 과 어떤 상한선 사이의 정수값이라면, vector 와 deque 를 선택해야 한다. 반면에, 키값들이 어떤 순서가 있는 데이터형이라면(문자나 문자열 또는 사용자 정의

타입과 같은) map 을 사용한다.값들간에 서로 연관성이 있는가?

표준 라이브러리가 제공하는 컨테이너에 속한 모든 값들이 다른 값과 같은지를 비교할

수 있어야 한다. 하지만, 모든 컨테이너가 less-than 연산자를 제공할 필요는 없다. 그러나, less-than 연산자를 사용해서는 순서가 유지될 수 없는 값들은 set 이나 map에 저장하면 안된다.

콜렉션으로부터 가장 큰 값을 찾아서 제거하는 연산이 자주 일어나는가? 그렇다면, priority_queue 가 최선의 컨테이너이다.

데이터 구조의 어느 위치로 값들이 삽입되고 제거되는가? 중간쯤에서 값들이 삽입되고 제거된다면, list 가 최선의 선택이다. 값들이 앞쪽에만

삽입된다면, deque 나 list 가 더 좋겠고, 끝에서만 삽입과 삭제가 이루어진다면, stack이나 queue 가 좋을 것이다.

두개 이상의 수열을 하나로 합치는 일이 자주 일어나는 연산인가? 그렇다면, set 이나 list 가 좋은 선택이 될 것이다. 이 둘중에 어느 것을 선택할 지는

순서가 유지되는가의 여부에 따라 결정하면 된다. 두개의 set 을 합치는 것은 매우

효율적인 연산이다. 콜렉션의 순서가 유지되지 않고, list 가 제공하는 효율적인

splice() 멤버함수를 사용하면, list 를 선택하는 것이 좋겠다. 왜냐하면, 이 연산은

다른 컨테이너에서는 제공하지 않는 연산이기 때문이다.대부분의 경우에 주어진 문제에 대해서 여러가지 컨테이너가 사용될 수 있는데, 그럴때는 실제

수행시간을 비교해서 어느 것이 나은가를 결정하는 것도 한 방법이 될 것이다.

41

Page 42: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

4.3 메모리 관리 이슈들

표준 라이브러리의 컨테이너(container)는 템플릿으로 설계되었기 때문에, 다양한 타입의

원소들을 관리할 수 있다. 이들 타입에는 정수형이나 문자형과 같은 기초적인 데이터 타입이나

포인터, 또는 사용자 정의(user-defined) 타입들이 있다. 컨테이너는 레퍼런스(reference)를

담을 수 없다. 일반적으로 컨테이너의 메모리 관리는 프로그래머와는 무관하게 표준 컨테이너

클래스가 알아서 자동으로 관리한다.

값들은 복사 생성자(copy constructor)를 사용하여 컨테이너에 배치된다. 대부분의 컨테이너

클래스의 경우, 자신이 관리하고 있는 원소들의 타입은 기본 생성자(default constructor)를

정의하고 있어야 한다. copy()와 같이 컨테이너로의 복사를 수행하는 generic 알고리듬들은

대입 연산자(assignment operator)를 사용한다.

복사 생성자나 대입 연산자(=, assignment operator)를 사용하면, 컨테이너 A 전체가 새로운

컨테이너 B 로 복사되는 경우가 발생하는데, 이 경우 컨테이너 A 내의 모든 원소(객체)들은 각

원소들에 정의되어 있는 복사 생성자나 대입 연산자를 사용하여 컨테이너 B 로 복사가 된다. 그

결과가 'deep copy'가 될지, 'shallow copy'가 될지는 대입 연산자를 프로그래머가 어떻게

정의했는지에 달려 있다. 대부분의 컨테이너 클래스가 데이터 구조를 위해 내부적으로 사용하는

메모리는 자동적이고 효율적으로 할당되고 반환된다.

컨테이너의 원소 타입에 대해 소멸자(destructor)가 정의되어 있다면, 컨테이너로부터 값들을

삭제할 때, 이 소멸자가 호출된다. 컨테이너 전체가 소멸되는 경우에는, 남아있는 원소들에

대해서 각각 소멸자를 호출한다.

포인터 값을 담고 있는 컨테이너에 관해 몇가지 언급하기로 하자. 이러한 콜렉션이 그리 드문

것은 아니다. 예를 들어, 클래스나 하위클래스의 인스턴스를 나타내는 값들을 저장할 때는

포인터의 콜렉션이 유일한 방법이다. 이러한 콜렉션은 11.3 절 의 예에서 살펴볼 수 있다.

이러한 경우에 컨테이너는 포인터 값 자체만 관리하면 된다. 포인터가 참조하는 값에 대한

메모리 관리는 프로그래머가 책임져야 할 부분이다. 즉, 컨테이너가 해당 메모리에 대한

포인터를 간직하고 있는 동안에는 그 메모리를 반환해서는 안되며, 일단 포인터가 컨테이너에서

삭제되면 그 포인터가 가리키고 있던 메모리를 올바르게 반환하는 작업들은 프로그래머

스스로가 담당해야 하는 것이다.

42

Page 43: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

컨테이너에 포인터를 저장하는 문제에 관해서는 다음 절에서 별도로 다루기로 한다.

4.4 컨테이너에 포인터 저장하기(문제점과 해결책)

STL 컨테이너는 포인터 저장용으로 설계된 것이 아니라, 객체를 담을 목적으로 설계된 자료

구조이다. 만일 여러분이 작성하고 있는 프로그램에서 STL 을 사용하고 있고, STL 컨테이너에

포인터를 저장할 필요가 있는 사람들에게는 이 말이 상당히 아쉽게 들릴 것이다. 그래서, 되도록이면 컨테이너에 포인터를 저장하지 않는 것이 좋으나, 어쩔 수 없이 포인터를

저장해야만 한다면(실제로 이런 경우가 흔히 발생한다), 이 절에서 설명할 내용을 잘 기억하고

있어야 한다. 뉴스 그룹이나 기타 여러 사람들이 말하는 것과는 달리, STL 컨테이너에 객체

대신에 포인터를 저장한다고 해서 크게 문제가 될 것은 없다. 하지만, 실제로 컨테이너에

포인터를 저장하는 경우에 메모리 누수(memory leak)와 같은 여러가지 골치아픈 문제들을

일으킬 소지가 있는 것은 사실이므로, 되도록이면 피하는 것이 좋다. 하지만, 다음과 같은

예외적인 상황에서는 컨테이너에 포인터를 저장할 수 있다.

객체를 프로그램 이곳 저곳에 중복해서 만들어 놓기에는 객체의 크기가 너무 큰 경우나, 이미 메모리 힙(heap)에 생성되어 있는 크키가 큰 객체를 컨테이너에 추가하고자 하는

경우. 즉, 크기가 큰 객체의 복사는 부담스럽기 때문에 이를 피하려는 경우다. 하나의 객체를 다른 여러 컨테이너에 저장하려는 경우. 본인이 생각하기에는 이것이

가장 흔한 경우가 아닐까 한다. base object 로부터 상속된 여러 다양한 객체들을 한 컨테이너에 저장하려는 경우. 물론, CAD 시스템에서 이러한 경우가 흔히 있는데, Smalltalk 와 달리 C++에서는

진정한 heterogeneous 컨테이너를 요구한다는 것은 좀 무리라는 것을 염두할 필요가

있다. 그렇다고 불가능한 것은 아니다. (예를 들어, '도형'이라는 기초 클래스(base class)로 부터 상속된 '삼각형', '사각형', '원', '타원'이라는 여러 하위 클래스(subclass)의 객체들을 한 컨테이너에 담고자 하는 경우)

이러한 예외적인 상황에서 컨테이너에 포인터를 저장하기로 결정하였다면, 다음에 열거한

사항들을 유의하기 바란다. 1. 지역 변수(local variable)를 가리키는 포인터를 저장하지 말 것. 이것은 굳이

컨테이너에 포인터를 저장할 때 뿐만 아니라, C 나 C++ 프로그래머라면 반드시

유의해야할 아주 기본적인 사항이다. 2. 포인터가 가리키는 객체들에 대한 소멸자(destructor)는 당신이 책임져야 할 부분이다.

당신이 의도한 바에 따라 당신만의 소멸자를 정의해 주어야 한다는 뜻이다.

43

Page 44: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

3. 일부 STL 컨테이너에서는 컨테이너에 담긴 원소들에 대해 '<' 연산자를 이용하여 비교

연산을 수행하는데, 만약 이러한 컨테이너에 포인터를 담는다면, 포인터간의 대소

비교는 아무런 의미가 없으므로, 이를 반드시 고려하고 있어야 한다. 이 경우가 'smart pointer'가 아주 긴요하게 쓰일 수 있는 경우다.

4. set 이나 map 과 같이 STL 컨테이너를 생성할 때, 비교 함수(comparator)를 템플릿

인자로 요구하는 컨테이너에 포인터를 저장하고자 할 때는 반드시 그에 따르는 적절한

비교 함수를 만들어서 넘겨주어야 한다. 아래 예제를 참고하라.

4.4.1 4번 유의 사항에 대한 예제

일단 다음 코드를 한번 찬찬히 살펴보기 바란다. 특히 빨간 부분으로 표시된 'compare'에

주목하라. #include <iostream>#include <list>#include <set>#include <algorithm>

struct compare { bool operator()(const int* i1, const int* i2) const { return *i1 < *i2; }};

void print(int* i) { cout << " " << *i; }

int main(){ list<int*> list1;

// list1 을 정수 0,1,4,9,16 각각에 대한 포인터로 초기화

for(int i = 0; i < 5; ++i) list1.push_back(new int(i * i));

// list1 의 내용을 출력

cout << "List of int*: ("; for_each(list1.begin(), list1.end(), print);

44

Page 45: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

cout << ")" << endl;

// list1 에 있던 정수 포인터들을 set1 으로 옮긴다. set1 을 선언할 때, 포인터가

// 아닌, 포인터가 가리키는 정수간의 비교가 이루어지도록 사용자가 작성한

// 비교 함수(comparator)를 사용해야 한다. set<int*, compare> set1; copy(list1.begin(), list1.end(), insert_iterator<set<int*, compare> >(set1, set1.begin()));

// set1 의 내용을 출력

cout << "Set of int* : ["; for_each(set1.begin(), set1.end(), print); cout << "]" << endl;

return 0;}

▶ 실행결과

List of int*: ( 0 1 4 9 16)Set of int* : [ 0 1 4 9 16]일단 설명의 간편함을 위해서, 객체의 포인터 대신에 int의 포인터를 담은 list 컨테이너를 예로

들어 설명했다. 앞에서 지적했던 유의사항 4번을 명심하라. set, multiset, map과 같은

컨테이너에 포인터를 저장하고자 할 때는, 포인터가 가리키는 객체들간에 비교가 이루어지도록

비교 함수(comparator)를 프로그래머가 직접 제공해줘야 한다. 이렇게 하지 않으면 아무 의미

없는 포인터간의 비교 연산이 일어나게 된다. 4.4.2 2번 유의 사항에 대한 예제

컨테이너에 포인터를 담으려고 할 때는, 각각의 포인터가 가리키는 메모리 영역을 반환하는

코드를 반드시 추가해야 한다. 만약에 이렇게 하지 않으면, stack에 위치한 컨테이너 객체가

소멸될 때, 포인터가 저장된 메모리 영역만 반환되므로, 메모리 누수(memory leak)가

발생하게 된다. 즉, 컨테이너는 포인터가 저장된 메모리 영역을 복사하고 삭제할 뿐이며, 포인터가 가리키는 객체가 차지하는 메모리 영역에 대해서는 책임지지 않는다. 다음은 이러한

역할을 위해 템플릿으로 구현된 삭제 전용 함수이다. template <class ForwardIterator> void sequence_delete(ForwardIterator first, ForwardIterator last) { while (first != last)

45

Page 46: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

delete *first++; }

template <class ForwardIterator> void map_delete(ForwardIterator first, ForwardIterator last) { while (first != last) delete (*first++).second; }따라서, 위 4.4.1절에서 예로 든 코드는 메모리 누수 문제를 안고 있는 코드이다. 왜냐하면, set 컨테이너에 담긴 int*가 가리키는 메모리 영역을 반환하지 않았기 때문이다. 따라서, sequence_delete를 이용한 다음 코드를 끝에 추가해야 완벽한 프로그램이 된다. ... sequence_delete(set1.begin(), set1.end()); sequence_delete(list1.begin(), list1.end()); ...

4.4.3 2번 유의 사항에 대한 추가 예제

#include <iostream>#include <vector>#include <algorithm>

class object { char *str;public: static int count; object() { str = 0; count++; } object(const object& o) { cout << "-- ctor called, "; str = new char[10]; strcpy(str, o.str); count++; cout << count << " object(s) remains." << endl; } object(const char *s) { cout << "-- ctor called, "; str = new char[10]; strcpy(str, s); count++; cout << count << " object(s) remains." << endl; }

46

Page 47: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

~object() { cout << "-- dtor called, "; delete[] str; count--; cout << count << " object(s) remains." << endl; }

friend ostream& operator<<(ostream& os, object o) { return os << o.str; } friend ostream& operator<<(ostream& os, object *o) { return os << o->str; }};

int object::count = 0;

template <class ForwardIterator>void sequence_delete(ForwardIterator first, ForwardIterator last){ while (first != last) delete *first++;}

void print(object *o) { cout << o << endl; }

int main(){ vector<object*> v;

v.push_back(new object("obj1")); v.push_back(new object("obj2"));

cout << "Print elements..." << endl; for_each(v.begin(), v.end(), print);

cout << "Delete elements..." << endl; sequence_delete(v.begin(), v.end());

47

Page 48: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

return 0;}

▶ 실행결과

-- ctor called, 1 object(s) remain(s).-- ctor called, 2 object(s) remain(s).Print elements...obj1obj2Delete elements...-- dtor called, 1 object(s) remain(s).-- dtor called, 0 object(s) remain(s).정적 클래스 멤버인 count는 object 객체의 갯수를 검사하기 위해서 사용되었다.

4.4.4 포인터 싸개(pointer wrapper)#include <iostream>#include <list>#include <set>#include <deque>#include <algorithm>

// 다음에 정의한 class X 의 객체에 대한 포인터를

// 여러 종류의 STL 컨테이너에 저장하려고 함.class X { int i_;public: X(int i) : i_(i) { } X(const X& x) : i_(x.i_) { } ~X() { }

X& operator=(const X& x) { i_ = x.i_; } int operator()() const { return i_; }};

bool operator==(const X& x1, const X& x2) { return x1() == x2(); }bool operator<(const X& x1, const X& x2) { return x1() < x2(); }

48

Page 49: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

// STL 컨테이너에 저장할 wrapper 클래스

class XPtrWrapper { X* x_;public: XPtrWrapper(X* x = 0) : x_(x) { } XPtrWrapper(const XPtrWrapper& xw) : x_(xw.x_) { } ~XPtrWrapper() { }

XPtrWrapper& operator=(const XPtrWrapper& xw) { x_ = xw.x_; } const X* operator()() const { return x_; } // for const XPtrWrapper X* operator()() { return x_; } // for non-const XPtrWrapper};

bool operator==(const XPtrWrapper& xw1, const XPtrWrapper& xw2){ return (xw1() && xw2()) ? *xw1() == *xw2() : false;}

bool operator<(const XPtrWrapper& xw1, const XPtrWrapper& xw2){ return (xw1() && xw2()) ? *xw1() < *xw2() : false;}

void print(const XPtrWrapper& xw) { cout << " " << (*xw())(); }

int main(){ // XPtrWrapper 5 개를 배열에 저장(각각의 wrapper 는 0,1,4,9,16 를 가리킴) XPtrWrapper bucket[5]; for(int i = 0; i < 5; i++) bucket[i] = XPtrWrapper(new X(i * i)); random_shuffle(bucket, bucket + 5);

// list 로 XPtrWrapper 들을 복사

list<XPtrWrapper> list1; copy(bucket, bucket + 5,

49

Page 50: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

back_insert_iterator<list<XPtrWrapper> >(list1) );

// 출력

cout << "List of XPtrWrapper: ("; for_each(list1.begin(), list1.end(), print); cout << " )" << endl;

// set 으로 XPtrWrapper 들을 복사

// 앞에서도 말했지만, set 을 선언할 때는 비교함수(comparator)를 반드시 지정

set<XPtrWrapper, greater<XPtrWrapper> > set1; copy(list1.begin(), list1.end(), insert_iterator<set<XPtrWrapper, greater<XPtrWrapper> > > (set1, set1.begin()) );

// 출력

cout << "Set of XPtrWrapper : ["; for_each(set1.begin(), set1.end(), print); cout << " ]" << endl;

// deque 로 XPtrWrapper 들을 복사

deque<XPtrWrapper> deque1; copy(list1.begin(), list1.end(), back_insert_iterator<deque<XPtrWrapper> > (deque1) );

// 출력

cout << "Deque of XPtrWrapper : ("; for_each(deque1.begin(), deque1.end(), print); cout << " )" << endl;

return 0;}

▶ 실행결과

50

Page 51: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

List of XPtrWrapper: ( 4 0 16 1 9 )Set of XPtrWrapper : [ 16 9 4 1 0 ]Deque of XPtrWrapper : ( 4 0 16 1 9 )

4.5 STL 에 없는 컨테이너 타입들

전통적으로 많이 사용되고 있는 컨테이너 타입들중 표준 라이브러리에는 들어 있지 않은 것들이

꽤 있다. 그 이유는 대부분의 경우, 이미 제공된 컨테이너들을 적절하게 이용하면 보다 아주

광범위한 용도로 다양하게 사용될 수 있기 때문이다.

우선 tree 콜렉션이 없다. 그러나, set 타입은 내부적으로 이진 검색트리의 형태를 사용하여

구현되어 있다. tree 를 이용하여 해결되는 많은 문제들은 set 데이터형으로 적절히 대체할 수

있다.

set 데이터 타입은 명시적으로 순서적이고, 순서를 매길수 없는 값들의 콜렉션(예를 들면, 복소수의 콜렉션)에 대해서는 set 연산(합집합,교집합)의 수행을 위한 어떤 대책도 마련되어

있지 않다. 그러한 경우에는 list 를 대신 사용할 수 있는데, 이렇게 하려면, generic 알고리듬을

사용할 수 없기 때문에, 특별한 set 연산 함수를 작성해야할 필요가 생긴다.

다차원 배열도 없다. 그러나, vector 를 원소로 하는 vector 를 이용하면 된다.

그래프가 없다. 그렇지만, graph 에 대한 표현은 맵을 원소로 하는 맵으로 만들어 낼 수 있다. 이런 타입의 구조는 9.3.2절의 예제 프로그램에서 설명한다.

sparse 배열도 없다. 이 문제에 대한 해결책은 9.3.2절에서 다룰 graph 표현 방식이 될 수

있겠다.

해쉬 테이블도 없다. 해쉬 테이블은 접근과 삭제 연산을 첨자 연산으로 바꿈으로써, amortized 상수 시간내에 접근,삽입,삭제가 가능하도록 한다. 그러나, 해쉬 테이블은 list 나 set 을 원소로

51

Page 52: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

하는 vector 나 deque 로 만들어낼 수 있다. 이와 비슷한 예를 7.3절에서 radix 정렬 문제를

다룰 때 살펴보기로 하겠다.(이 예제는 값을 인덱스로 바꾸는 해쉬 함수는 가지고 있지 않다.)

간단히 말해서, 생각할 수 있는 모든 컨테이너 타입을 다 제공하지는 못하지만, 표준

라이브러리가 제공하는 컨테이너들을 응용하면 많은 문제들에서 필요로 하는 컨테이너들을

대부분 표현할 수 있고, 더 나아가 표준 라이브러리에서 제공하는 컨테니이들을 가지고, 새로운

컨테이너를 작성할 수도 있을 것이다.

5 장: vector 와 vector<bool>

5.1 vector 데이터 추상(data abstraction)

vector 컨테이너 클래스는 기존 C 배열의 개념을 일반화시킨 것이다. 배열과 마찬가지로, vector 도 첨자로 접근이 가능하며, 이 때 첨자의 범위는 0 부터 (원소의 갯수 - 1)까지이다. 또한 [] 연산자를 이용해서 vector 에 값을 대입하거나 vector 로부터 값을 추출할 수 있다. 그러나, 이러한 공통점외에 배열과 vector 는 다음과 같은 차이점을 가지고 있다.

vector 는 일반 배열보다 자신에 관한 정보를 더 많이 가지고 있다. 특히, vector 의

크기나 잠정적으로 가질 수 있는 원소의 갯수에 관한 정보를 얻을 수 있다. vector 의 크기는 동적으로 변할 수 있다. 새로운 원소를 vector 의 끝이나 중간에

삽입할 수 있다. 메모리 관리도 효율적이고 자동적으로 이루어진다. 그러나, vector 중간에 원소를 삽입할 때는 list 처럼 효율적이지 않다는 점에 주목해야 한다. 삽입

연산을 많이 수행하는 경우라면, vector 보다는 list 를 사용하는 것이 좋다.

표준 라이브러리의 vector 컨테이너 클래스는 7 장 에서 다룰 deque('덱'이라고 발음한다.)과

여러모로 비교가 된다. vector와 같이 deque도 인덱싱이 가능한 자료구조이다. 이 둘의 가장

큰 차이점은 deque은 컨테이너의 앞과 끝에서의 삽입이 효율적인 반면에, vector는 끝에서

삽입할 때만 효율적이라는 것이다. 대개는 둘중 어느 것을 사용해도 상관없지만, 일반적으로

vector를 사용하는 것이 좀더 작은 실행화일을 만들 수 있는 반면에, deque은 수행되는 연산의

종류에 따라 조금 빠른 프로그램을 만들기도 한다. 5.1.1 Include 화일

vector를 사용하려면 vector 헤더화일을 포함시켜야 한다.    #include <vector>

52

Page 53: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

5.2 벡터 연산

이 절에서는 vector가 제공하는 멤버 함수들에 관해 좀 더 자세히 살펴본다. 앞으로도 계속

언급하겠지만, 멤버함수는 기초적인 연산들을 제공하는 반면에, 13 장 과 14장에서 소개할

generic 알고리듬을 사용함으로써 표준 라이브러리가 제공하는 자료구조들을 보다 유용하게

사용할 수 있게 된다. 5.2.1 벡터의 선언과 초기화

vector는 템플릿 클래스이므로 vector에 담을 원소의 타입을 명시해 주어야 한다. 원소의

타입은 integer나 double과 같은 primitive 타입이나, 포인터 타입이 될 수도 있고, 사용자

정의 타입이 될 수도 있다. 사용자 정의 타입의 경우에는 기본 생성자가 반드시 정의되어 있어야

한다. 복사 생성자도 명시적으로든 묵시적으로든 반드시 존재해야 한다. ▶ 원소 타입이 지켜야 할 조건

vector가 담고 있는 원소들은 반드시 디폴트 생성자(인자가 없는 생성자)와 복사 생성자를

정의하고 있는 것이라야 한다. 그리고, vector 클래스의 멤버 함수에서는 사용하지 않지만, 일부 generic 알고리듬에서 원소들간의 상등 관계나 대소 관계를 필요로 하기 때문에, == 연산자와 < 연산자를 정의해 두는 것이 좋다.

배열과 마찬가지로, vector 도 자신이 담게 될 원소의 갯수를 정수 인자로 하여 선언하는 것이

가장 흔한 경우다.

   vector<int> vec_one(10);(이와 같은 상황에서 vector를 생성하기 위해 사용되는 생성자는 키워드 explicit로 선언된다. 이렇게 함으로써 이 생성자가 변환 연산자로 사용되는 것을 막을 수 있다. 이렇게 하지 않으면, 본의아니게 정수를 vector로 변환하는 경우가 발생할 수 있기 때문이다.)

vector 를 생성하기 위해서 사용할 수 있는 생성자에는 여러가지가 있다. vector 의 사이즈 뿐만

아니라, vector 의 원소들을 초기화하는데 사용되는 상수값을 제공하는 생성자도 있다. 사이즈가 명시되지 않으면, vector 는 아무런 원소를 포함하지 않은 상태로 생성되게 되며, 원소가 추가될 때 자동적으로 사이즈가 증가하게 된다. 복사 생성자는 다른 vector 로부터

클론을 생성할 수 있다.

   vector<int> vec_two(5, 3);      // 복사 생성자

53

Page 54: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   vector<int> vec_three;          // 가장 일반적인 형태

   vector<int> vec_four(vec_two);  // 대입에 의한 초기화

시작 반복자와 끝 반복자 pair를 사용하여 다른 콜렉션의 원소들로 vector를 초기화할 수 있다. 이들 인자로 들어가는 반복자의 형태는 아무것이나 가능하며, 반복자를 제공하는 컨테이너에

담긴 값들이기만 하면 이들 값들을 가지고 초기화가 가능하다.    vector<int> vec_five(aList.begin(), aList.end());▶ 생성자와 반복자

Because it requires the ability to define a method with a template argument different from the class template, some compilers may not yet support the initialization of containers using iterators. In the mean time, while compiler technology catches up with the standard library definition, the Rogue Wave version of the standard library will support conventional pointers and vector iterators in this manner.

vector 간의 대입도 가능하며, 이때 대입 연산자의 좌변은 우변 vector 의 복사본을 가지게

된다.

   vec_three = vec_five;

assign() 멤버함수는 대입 연산자와 비슷하지만, 더 기능이 많다. 인자가 더 많을 경우도

있다. 대입과 마찬가지로, 컨테이너에 담긴 값들은 인자로 명시된 값들로 바뀌게 된다. 두가지

형태의 assign()이 있는데, 첫번째는 다른 컨테이너의 서브 시퀀스를 가리키는 두개의

반복자를 취한다. 이 서브 시퀀스로부터의 값들은 받는 쪽에서의 새로운 값들이 된다. assign()의 두번째 형태는 갯수와 컨테이너 원소 타입의 값이(이것은 생략 가능) 인자로

제공된다. 함수호출을 한뒤에는 count 로 명시된 갯수의 원소들만 가지게 되고, 이 원소들은

기본값과 같거나, 명시한 초기값과 같게 된다.

   vec_six.assign(list_ten.begin(), list_ten.end());   vec_four.assign(3, 7);      // '7'을 3 개 대입

   vec_five.assign(12);        // '0'을 12 개 대입

원소의 타입이 소멸자를 정의하고 있다면, 콜렉션으로부터 이들 원소가 삭제될 때, 이 소멸자를

호출하게 된다.

마지막으로, swap() 연산자를 통해 두 vector 간에 각자 가지고 있던 원소들을 모조리

바꿔치기 할 수도 있다. 인자로 들어가는 컨테이너는 수신자의 값들을 가지게 되고, 수신자는

54

Page 55: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

인자로 들어가는 컨테이너의 값들을 가지게 된다. 이 swap()은 매우 효율적이기 때문에, 원소별로 전송할 때는 반드시 이것을 사용해야 한다.

vec_three.swap(vec_four);5.2.2 타입 정의

vector 클래스는 많은 수의 타입 정의를 포함하고 있다. 이들은 선언문에서 가장 많이

사용된다. 예를 들어, 정수 vector의 반복자는 다음과 같이 선언할 수 있다. vector<int>::iterator location;

iterator 뿐만 아니라 다음과 같은 타입들도 정의되어 있다.  

value_type 벡터가 관리하는 원소들의 타입

const_iterator 하부 시퀀스를 변경할 수 없는 반복자

reverse_iterator 역 이동 반복자

const_reverse_iterator

위 두가지 성질을 동시에 가지는 반복자

reference 벡터 원소에 대한 참조

const_reference 원소를 변경할 수 없는 참조

size_type 컨테이너의 사이즈를 참조할 때 사용되는 비부호 정수형

difference_type 반복자간의 거리차를 나타낼 때 사용되는 부호 정수형

allocator_type 벡터의 메모리를 관리하는데 사용되는 할당기 타입

5.2.3 벡터의 첨자 연산

특정 인덱스 위치에서 vector 가 관리하는 값을 접근하거나 변경할 때는 첨자 연산자를

사용하며, 이는 일반 배열과 동일하다. 또한, 인덱스 값의 유효성에 대한 검사를 하지 않는다. 상수 vector 를 인덱싱하면 상수 레퍼런스가 생성된다. 유요한 인덱스 범위를 벗어나는 vector를 인덱싱할 때는 어떤 결과가 나올지 예측할 수 있다.

   cout << vec_five[1] << endl;   vec_five[1] = 17;

첨자 연산자를 대신해서, 멤버함수 at()을 사용할 수 있다. 이 함수는 첨자 연산자와 동일한

인자를 가지고 동일한 값을 반환한다.

55

Page 56: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

front() 멤버 함수는 vector 의 맨 앞에 있는 원소를 반환하고, back()은 맨 마지막 원소를

반환한다. 이 두 함수 모두 상수 vector 에 적용하면, 상수 레퍼런스를 반환한다.

   cout << vec_five.front() << " ... " << vec_five.back() << endl;5.2.4 확장 연산과 사이즈 변환 연산

일반적으로 vector 에는 세가지의 서로 다른 '사이즈'가 존재한다. 첫째로, 현재 vector 가

가지고 있는 원소의 갯수이다. 둘째로 새로운 메모리 할당없이 vector 가 확장될 수 있는 최대

사이즈이다. 세째는 vector 가 가질 수 있는 사이즈의 상한선이다. 이들 세값들은 각각 size(), capacity(), max_size() 멤버함수에 의해 얻을 수 있다.

   cout << "size: " << vec_five.size() << endl;   cout << "capacity: " << vec_five.capacity() << endl;   cout << "max_size: " << vec_five.max_size() << endl;max_size()는 사용가능한 메모리의 양 또는 size_type 자료형이 나타낼 수 있는 최대값에

의해 제한된다. 현재 사이즈와 capacity를 규명하기가 더 어렵다. 다음 절에서 살펴보겠지만, 다양한 방법으로 vector로부터 원소들을 추가하거나 삭제할 수 있다. vector로부터 원소들을

제거할 때, vector에 대한 메모리는 반환되지 않으며, 따라서, size는 줄어도 capacity는

그대로이다. 기존에 확보된 capacity를 넘지 않으면, 삽입으로 인해 새로 메모리를 할당하지는

않는다.

Memory Management 

삽입으로 인해 capacity 가 넘치게 되면, vector 원소들을 담을 새로운 블럭을 할당하게 된다. 그리고 나서, 원소 타입에 대한 대입 연산자를 사용하여 값들을 새로 할당된 메모리로 복사하고, 예전 메모리는 삭제한다. 이는 상당히 비싼 연산이므로, vector 데이터 타입은 프로그래머에게

vector 의 capacity 를 정할수 있는 방법을 제공한다. reserve() 멤버 함수는 vector 가

적어도 주어진 사이즈까지는 자라날 것이라는 것을 나타내는 vector 에 대한 지시자이다. reserve() 인자로 주어진 값이 현재 capacity 보다 크면, 메모리 반환이 일어나고, 인자로

주어진 값이 새로운 capacity 가 된다. (??) capacity 가 이미 인자값을 초과하고 있다면, 메모리 반환은 일어나지 않는다. reserve()를 호출해도 vector 의 사이즈와 원소값들은

바뀌지 않는다.(단, 메모리 반환이 일어나면 원소값들을 이동하는 경우는 제외한다.)

   vec_five.reserve(20);메모리 반환이 일어나면, vector들의 원소를 참조하는 모든 레퍼런스, 포인터, 반복자들은 모두

무효가 된다.

56

Page 57: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

empty() 멤버 함수는 현재 vector 의 사이즈가 0 일 때 참이 된다. (vector 의 capacity 와는

무관하다.) 이함수를 사용하는 것이 size()의 리턴값과 0 을 비교하는 것보다 훨씬 효과적이다.

   cout << "empty is " << vec_five.empty() << endl;

resize() 멤버 함수는 vector 의 사이즈를 인자값으로 만들어버린다. 이때 필요하다면 vector의 끝부분의 원소값들이 삭제되거나 첨가된다. 옵션인 두번째 인자는 새로운 원소가 추가될

때의 초기값들로 사용된다. 만약 원소 타입에 대한 소멸자가 정의되어 있다면, 콜렉션으로부터

제거되는 원소값들에 대해 소멸자가 호출된다.

   // become size 12, adding values of 17 if necessary   vec_five.resize (12, 17);

5.2.5 원소의 삽입과 삭제

앞에서 언급했던 바와 같이, vector 클래스는 사이즈가 증가하거나 감소할 수 있다는 점에서

일반 배열과 다르다. 삽입으로 인해 vector의 원소의 수가 현재 원소값들을 담는데 사용되는

메모리 블럭의 capacity를 초과하게 되면, 새로운 메모리 블럭이 할당되어 이곳으로

원소값들을 복사한다.

Costly Insertions 

push_back()을 사용하면 vector 의 끝에 새 원소를 첨가할 수 있다. 현재 할당된 메모리에

공간이 남아 있다면, 이 연산은 매우 효율적이다(상수시간).

   vec_five.push_back(21);   // add element 21 to end of collection

이에 대응되는 삭제 연산으로 pop_back()이 있다. 이 연산은 vector 의 사이즈를 줄이고, capacity 에는 변화를 주지 않는다. 컨테이너 타입이 소멸자를 정의하고 있다면, 삭제되는

원소에 대해 소멸자를 호출하게 된다. pop_back() 연산도 매우 효율적이다. (deque 클래스는 콜렉션의 앞과 뒤에서 삽입과 삭제를 허용한다.) 이들 함수들은 deque 를 자세히

설명하고 있는 7 장에서 설명한다.

insert() 멤버 함수를 사용하면 좀더 일반적인 삽입 연산을 행할 수 있다. 삽입할 위치는

반복자로 지정하고, 삽입은 명시된 위치의 바로 앞에서 일어난다. 정해진 갯수의 상수 원소들은

57

Page 58: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

단 한번의 함수 호출로 삽입될 수 있다. 한번의 호출로 한블럭의 원소들을 삽입하는 것은 하나씩

삽입하는 것보다 훨씬 효율적이다. 한번의 호출로 기껏해야 한번의 할당만 수행하면 되기

때문이다.

   // find the location of the 7   vector<int>::iterator where =          find(vec_five.begin(), vec_five.end(), 7);   vec_five.insert(where, 12);    // then insert the 12 before the 7   vec_five.insert(where, 6, 14); // insert six copies of 14   vec_five.insert(where, vec_three.begin(), vec_three.end());

Iterator Invalidation 

vector 의 마지막 원소를 삭제하는 pop_back() 멤버 함수뿐만 아니라, 위치를 지정하는

반복자를 이용하여 vector 의 중간에서 원소들을 삭제하는 함수들도 있다. erase()가 바로 그

함수이다. 두가지 형태가 있는데, 첫째는 한개의 반복자를 인자로 받아 한개의 원소값을

제거하고, 둘째는 한쌍의 반복자를 인자로 받아 이 반복자가 가리키는 범위내의 모든 값들을

삭제한다. vector 의 사이즈는 줄어들고, capacity 는 줄어들지 않는다. 컨테이너 타입이

소멸자를 정의하고 있다면, 삭제되는 값에 대해 소멸자를 호출한다.

   vec_five.erase(where);                                    // erase from the 12 to the end   where = find(vec_five.begin(), vec_five.end(), 12);   vec_five.erase(where, vec_five.end());

5.2.6 반복자

begin()과 end() 멤버 함수는 컨테이너에 대한 임의접근 반복자를 리턴한다. 이들 연산들이

반환하는 반복자들도 원소를 삽입하거나 삭제한 뒤에 무효가 될 수 있다. rbegin()과

rend() 멤버 함수는 앞과 비슷한 반복자를 반환하지만, 이들 반복자는 반대방향으로 원소들을

접근한다. 만약 컨테이너가 상수로 선언되어 있다거나, 대입 연산자의 대상이나 인자가

상수라면, 이들 연산들은 상수 반복자들을 반환할 것이다.

5.2.7 소속 검사 연산

vector는 자신이 특정값을 포함하고 있는지를 결정하는데 사용되는 연산을 직접 제공하지

않는다. 그러나, find()나 count()(13.3.1절과 13.6.1절) generic 알고리듬들이 이러한

목적으로 사용될 수 있다. 예를 들어, 다음 명령문들은 정수 vector가 17이라는 수를 포함하고

58

Page 59: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

있는지를 검사하고 있다. int num = 0;count (vec_five.begin(), vec_five.end(), 17, num);

if (num)   cout << "contains a 17" << endl;else   cout << "does not contain a 17" << endl;

Initializing Count 

5.2.8 정렬 연산

vector는 자신이 관리하는 원소들을 자동으로 순서를 유지시키지 않는다. 그러나, sort() generic algorithm을 사용하여 vector내의 원소들을 순서대로 나열할 수 있다. 가장 간단한

형태의 정렬은 비교할 때 원소타입에 대해 less-than 연산자를 사용한다. 또다른 generic 알고리듬은 프로그래머가 명시적으로 비교 연산자를 지정할 수 있도록 하고 있다. 에를 들어, 다음은 오름차순이 아닌 내림차순으로 원소들을 배치하고 있다.

// sort ascendingsort(aVec.begin(), aVec.end());

// sort descending, specifying the ordering function explicitlysort(aVec.begin(), aVec.end(), greater<int>() );

// alternate way to sort descendingsort(aVec.rbegin(), aVec.rend());

14장에서 설명하고 있는 많은 연산들이 ordered 콜렉션을 담고 있는 vector들에 적용될 수

있다. 예를 들어, 두개의 vector가 merge() generic 알고리듬을 사용하여 합쳐질 수 있다. (14.6절)

// merge two vectors, printing outputmerge(vecOne.begin(), vecOne.end(), vecTwo.begin(), vecTwo.end(),   ostream_iterator<int,char>(cout, " "));

vector를 정렬할 때는 find()와 같이 선형 순회 알고리듬 대신에 보다 효과적인 이진 검색

알고리듬을 사용한다.(?) 5.2.9 유용한 generic 알고리듬들

13장에서 설명하는 대부분의 알고리듬들이 vector와 같이 사용될 수 있다. 다음 표는

이들중에서 보다 유용한것들만을 모아놓은 것이다. 예를 들어, vector에서의 최대값은 다음과

59

Page 60: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

같이 알아낼 수 있다. vector<int>::iterator where =    max_element (vec_five.begin(), vec_five.end());cout << "maximum is " << *where << endl;

 

용도 이름

주어진 초기값으로 벡터를 채운다. fill

수열을 복사한다. copy

발생기(generator)가 생성한 값을 벡터에 집어넣는다. generate

조건을 만족하는 원소를 찾는다. find

연속적으로 중복된 원소를 찾는다.  adjacent_find

벡터내에서 서브 시퀀스를 찾는다. search

최대 또는 최소 원소를 찾는다. max_element, min_element

원소의 순서를 뒤집는다. reverse

원소들을 새로운 값들로 대치한다. replace

가운데점을 중심으로 원소들을 순환시킨다. rotate

원소들을 두그룹으로 쪼갠다. partition

순열(permutation)을 생성한다. next_permutation

벡터내에서의 ... inplace_merge

벡터내의 원소들을 임의로 섞는다. random_shuffle

조건을 만족하는 원소들의 갯수를 센다. count

벡터로부터의 정보를 가지고 하나의 값을 만들어 낸다. accumulate

두 벡터의 내적을 구한다. inner_product

두벡터를 한쌍씩 비교하여 같은지를 검사한다. equal

사전식 비교 lexicographical_compare 

벡터에 변환을 적용한다. transform

60

Page 61: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

값들의 부분합을 구한다. partial_sum

이웃하는 값들의 차를 구한다. adjacent_difference

각 원소들에 대해 함수를 수행한다. for_each

5.3 부울 벡터

비트값들의 벡터는 표준 라이브러리에서는 특별한 경우로 취급하여, 값들이 효과적으로 pack될 수 있다. 부울 벡터, vector<bool>을 위한 연산들은 일반 벡터 연산의 superset 이고, 단지 구현이 더 효율적이다.

부울 벡터에서는 flip()이란 멤버 함수가 추가되었다. 호출되었을 때, 이 함수는 벡터의 모든

비트를 뒤집는다. 또한 부울 벡터는 내부값을 레퍼런스로 리턴하며, 이 레퍼런스에 대해서도

flip() 멤버 함수를 지원한다. 아래 코드의 세번째 줄이 이를 설명하고 있다.

   vector<bool> bvec(27);   bvec.flip();               // 모든 비트를 flip    bvec[17].flip();           // 17 번 비트를 flipvector<bool>은 추가적으로 swap() 멤버 함수를 지원하는데, 이 함수는 한쌍의

레퍼런스가 가리키는 두개의 값을 서로 바꾼다.    bvec.swap(bvec[17], bvec[16]);★ 주의: 위에서 설명한 멤버 함수 flip()과 swap()이 실제로 위와 같이 지원되는지 아직

확인하지 못했으니, 착오 없으시기 바랍니다. 현재 g++ 2.8.1에서는 swap()이 지원하지

않고 있고, 모든 비트를 flip하는 경우도 지원하지 않는 것 같습니다. 따라서, 이것이 표준에서

지원되지 않는 이유때문인지, g++이 아직 이부분을 구현에 반영하지 않은 것인지는 좀더

살펴봐야 하겠습니다. 이점에 관해 아시는 분은 연락주시면 고맙겠습니다.

5.4 예제 프로그램 - 에라토스테네스의 체

이절에서 설명할 예제는 '에라토스테네스의 체'라고 하는 솟수를 찾는데 사용하는 아주

전통적인 알고리즘이다(제 또래 분들은 아마 예전 중학교 수학 시간에 배웠을 것임). 일단

솟수를 찾고자 하는 범위까지의 수들을 정수 vector 에 담기로 한다. 이 알고리즘의 아이디어는

61

Page 62: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

소수가 될 수 없는 수들을 하나씩 지워 나가면 (0 으로 세팅), 가장 마지막에 남는 수들이 솟수가

된다는 것이다. 이를 위해서는 루프를 돌면서 각각의 값들이 1 로 세팅된 수들의 배수인 것들을

하나씩 지워나가면 된다. 바깥쪽 루프가 끝나면 남아있는 수들이 솟수가 되는 것이다. 이

알고리즘의 프로그램은 다음과 같다.

int main(){   // 각 숫자들을 1 로 모두 세팅한다.   const int sievesize = 100;   vector<int> sieve(sievesize, 1);

   // 1 로 세팅된 값들 각각에 대해 이 수의 배수들을 0 으로 세팅한다.   for (int i = 2; i * i < sievesize; i++)      if (sieve[i])         for (int j = i + i; j < sievesize; j += i)            sieve[j] = 0;

   // 1 로 세팅된 숫자들만 출력한다.   for (int j = 2; j < sievesize; j++)      if (sieve[j])         cout << j << " ";   cout << endl;}

6장: list

6.1 list 데이터 추상(data abstraction)

vector 데이터 구조는 다른 것에 비해 상대적으로 사이즈가 고정되어 있는 컨테이너이다. vector의 사이즈를 동적으로 변화시킬 수 있는 연산이 제공되긴 하지만, 이러한 연산들은

비싸기 때문에 자주 사용해서는 안된다. 그리고, 대부분의 경우에, 콜렉션의 사이즈는 미리

예측하기가 힘들고, 실행 중에도 상당히 다양하게 변화한다. 따라서, 이러한 경우에는 다른

데이터 구조를 도입해야 하는데, 이 절에서는 그러한 상황에서 사용될 수 있는 데이터 구조의

62

Page 63: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

하나인 list에 관해 살펴보기로 한다.

list 는 일렬로 나열된 원소들을 추상화한 개념이다. list 의 맨앞쪽과 맨뒷쪽에 새로운 값을

삽입하거나 삭제할 수 있으며, 반복자로 위치를 지정하여 list 의 중간에서도 원소들을

추가하거나 삭제할 수 있다. 모든 경우에 대해서, 삽입 연산과 삭제 연산은 항상 효율적이며, 이는 콜렉션이 포함하고 있는 원소들의 갯수와는 상관없이 상수 시간(constant time)내에

수행된다는 것을 의미한다. 그리고, list 는 선형 구조이다. list 의 원소들은 첨자에 의해서는

접근이 불가능하고, 원소들을 접근하려면 모든 값들을 선형적으로 순회해야 한다.

6.1.1 Include 화일

list를 사용할 때는 반드시 list 헤더 화일을 포함해야 한다.    #include <list>

6.2 list 연산

리스트 데이터 타입이 제공하는 멤버 함수에 관해 아래에서 자세히 다룬다. 멤버 함수가

기초적인 연산을 제공하지만, 13 장 과 14장에서 설명할 generic 알고리듬을 사용함으로써

데이터 구조를 보다 유용하게 사용할 수 있게 된다. 6.2.1 list의 선언과 초기화

메모리 관리

리스트를 선언하는 방법은 여러가지가 있다. 가장 간단한 형태는 콜렉션이 관리할 원소의

타입을 명시함으로써 리스트를 선언하는 방법이다. 원소의 타입은 integer 나 double 과 같은

primitive 타입이나, 포인터 타입이 될 수도 있고, 사용자 정의 타입이 될 수도 있다. 후자의

경우, 사용자 정의 타입은 반드시 기본 생성자를 반드시 정의해야 한다. 복사 생성자도

명시적으로나 묵시적으로 반드시 존재해야 한다. 이런 식으로 선언된 콜렉션은 아무런 원소도

가지지 않게 된다.

   list<int> list_one;   list<Widget *> list_two;   list<Widget> list_three;또 다른 형태로는 일정수의 동일한 원소를 가지는 콜렉션을 만드는 선언이 있다. 이러한 용도로

63

Page 64: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

사용되는 생성자는 explicit로 선언하여, 변환 연산자로 사용될 수 없도록 한다. 이렇게

함으로써, 정수가 list로 변환되지 않도록 한다. 이러한 형태의 생성자는 두개의 인자를

취하는데, 하나는 사이즈이고, 하나는 초기값이다. 두번째 인자는 생략가능하다. 생성될 원소의

갯수만이 인자로 주어지면, 이들 값들은 기본 생성자를 사용하여 초기화되고, 두개 인자가 모두

주어지면, 두번째 인자로 초기화된다.    list<int> list_four(5);  // five elements, initialized to zero   list<double> list_five(4, 3.14);   // 4 values, initially 3.14   list<Widget> wlist_six(4);  // default constructor, 4 elements   list<Widget> list_six(3, Widget(7));  // 3 copies of Widget(7)

리스트는 다른 콜렉션에 담긴 원소들로 초기화될 수 있는데, 이때는 처음과 끝을 가리키는

한쌍의 반복자를 사용하게 된다. 인자들은 어떤 종류의 반복자를 사용하더라도 상관없으므로, 반복자를 지원하는 컨테이너이기만 하면, 이 컨테이너에 담긴 원소들로 리스트를 초기화할 수

있는 것이다. 이렇게 하려면, 템플릿을 사용한 멤버 함수를 specialize 할 수 있어야 하기

때문에, 어떤 컴파일러들은 이를 지원하지 않을 수 있다. 이러한 경우에는, copy() generic 알고리듬을 사용한 별도의 방법을 사용한다. copy()를 사용하여 list 를 초기화할 때는 삽입

반복자를 구성하여 copy 연산이 수행하는 출력 연산을 리스트로의 삽입 연산으로 바꿔야 한다. (2.4 절 참고) 삽입자는 두개의 인자를 요구하는데, 하나는 값이 삽입될 리스트이고, 하나는

값이 놓여지게 될 위치를 가리키는 반복자이다. 삽입 반복자는 현존하는 리스트의 임의 위치로

원소들을 복사하는데 사용될 수도 있다.

   list<double> list_seven(aVector.begin(), aVector.end());

   // the following is equivalent to the above   list<double> list_eight;   copy(aVector.begin(), aVector.end(),           inserter(list_eight, list_eight.begin()));6.2.3절에서 설명하는 insert() 연산도 반복자가 가리키는 값들을 리스트에 배치하는데

사용될 수 있다. 삽입 반복자는 generator(13.2.3절 참고)가 생성하는 값들의 수열로 리스트를

초기화하는데 사용될 수 있다. 다음 코드는 이를 설명하고 있다.    list<int> list_nine;             // initialize list 1 2 3 ... 7   generate_n(inserter(list_nine, list_nine.begin()),        7, iotaGen(1));복사 생성자를 사용하여 다른 리스트가 가지고 있는 값들로 리스트를 초기화할 수 있다. 대입

연산자도 같은 작업을 한다. 두가지 경우 모두 원소 타입에 대한 대입 연산자를 사용하여 각각의

64

Page 65: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

새로운 값들을 복사한다.    list<int> list_ten(list_nine);            // copy constructor   list<Widget> list_eleven;   list_eleven = list_six;        // values copied by assignment

assign() 멤버 함수는 대입 연산자와 유사하지만, 더 기능이 많고, 때로는 더 많은 인자를

필요로 한다. 대입과 마찬가지로, 컨테이너에 들어있던 값들은 삭제되어, 인자로 명시된 값으로

대체된다. assign()에는 두가지 형태가 있다. 첫째는 다른 컨테이너의 부시퀀스를 명시하는

두개의 반복자를 인자로 취한다. 이 부시퀀스에 담긴 값들이 수신자의 새로운 원소가 되는

것이다. assign()의 두번째 형태는 갯수와 컨테이너의 원소 타입인 값을 인자로 취한다. 두번째 인자는 생략가능하다. 이것을 호출하고 나면, 컨테이너는 디폴트값 또는 인자로 명시한

초기값으로 첫번째 인자로 주어진 갯수만큼의 원소를 포함하게 된다.

   list_six.assign(list_ten.begin(), list_ten.end());   list_four.assign(3, 7);          // three copies of value seven   list_five.assign(12);            // twelve copies of value zero

마지막으로, swap()이란 연산은 두 리스트간에 서로가 가지고 있는 내용을 완전히 바꾸는

작업을 한다. 인자로 명시된 컨테이너는 수신자의 값들을 가지게 되고, 수신자는 인자로 명시된

컨테이너의 값들을 가지게 된다. swap()은 매우 효율적인 연산이어서, 원소별 전송시에는

우선적으로 사용되어야 한다.

   list_ten.swap(list_nine);        // exchange lists nine and ten6.2.2 타입 정의

list 클래스는 많은 타입 정의들을 가지고 있다. 이것들은 주로 선언문에서 많이 사용된다. 예를

들어, 정수 list를 위한 반복자는 다음과 같이 선언할 수 있다. list<int>::iterator location;

iterator뿐만 아니라 다음과 같은 타입들이 정의되어 있다.  

value_type list에 담긴 원소의 타입

const_iterator 해당 시퀀스를 변경할 수 없는 반복자

reverse_iterator 뒷방향으로 이동하는 반복자

const_reverse_iterator

상수 반복자와 역 반복자를 합쳐 놓은 반복자

65

Page 66: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

reference 원소에 대한 레퍼런스

const_reference 원소를 변경할 수 없는 레퍼런스

size_type 컨테이너의 사이즈에 사용되는 비부호형 정수 타입

difference_type 반복자간의 거리에 사용되는 부호형 정수 타입

allocator_type list가 메모리 관리에 사용하는 할당기 타입

6.2.3 list에 원소 집어넣기

list 에 값들을 집어넣은 방법에는 여러가지가 있다. 원소들은 거의 대부분 리스트의 앞쪽이나

뒤쪽에서 추가된다. 이러한 작업들은 각각 push_front()와 push_back() 연산에 의해

수행된다. 이들 연산들은 두가지 타입의 컨테이너 모두에 대해서 효율적(상수시간)이다.

   list_seven.push_front(1.2);   list_eleven.push_back(Widget(6));

앞서 6.2.1절에서, 삽입 반복자와 함께 copy()나 generate() generic 알고리듬을

사용하여, 반복자가 지칭하는 리스트상의 위치에 값을 삽입하는 법에 관해 설명했다. insert()란 멤버 함수도 있는데, 이는 삽입자를 구축할 필요가 없다. 앞으로 간단히 설명하겠지만, begin()과 end() 함수가 만들어내는 반복자가 가지는 값들은 각각 리스트의 시작과 끝을

가리킨다. 이들 중 하나를 사용한 삽입은 각각 push_front()와 push_back()에

해당한다. 만약에 단 한개의 반복자만을 지정하면, 디폴트 원소가 삽입된다.

// insert default type at beginning of listlist_eleven.insert(list_eleven.begin()); // insert widget 8 at end of listlist_eleven.insert(list_eleven.end(), Widget(8));

반복자의 무효화

반복자는 리스트의 중간 위치를 지정할 수도 있다. 반복자를 만들어내는 방법에도 여러가지가

있다. 예를 들어, find() generic 알고리듬과 같은 13.3절에 설명되어 있는 검색 연산을

사용한 결과를 이용할 수가 있다. 새로운 값은 반복자가 지정한 위치 바로 앞에 삽입된다. insert() 연산은 값이 삽입된 위치를 가리키는 반복자를 반환한다. 위에서는 이 결과값이

무시되었다.

66

Page 67: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   //  find the location of the first occurrence of the    //     value 5 in list   list<int>::iterator location =         find(list_nine.begin(), list_nine.end(), 5);   // and insert an 11 immediate before it   location = list_nine.insert(location, 11);인자로 받은 값을 정해진 갯수만큼 삽입하는 것 또한 가능하다. 이러한 형태의 insert()는

값들의 위치를 결과로 내놓지는 않는다.    line_nine.insert(location, 5, 12);       // insert five twelves마지막으로, 한쌍의 반복자가 가리키는 시퀀스 전부를 리스트에 삽입할 수도 있다. 이경우에도, insert()의 반환값이 없다.      // insert entire contents of list_ten into list_nine     list_nine.insert(location, list_ten.begin(), list_ten.end());

하나의 리스트를 다른 리스트에 접목하는 방법에는 여러가지가 있다. 접목은 아이템이 수신자

list 에 첨가됨과 동시에 인자로 넘어온 list 로부터 삭제가 일어난다는 점에서 삽입과 다르다. 이렇기 때문에, 접목은 매우 효율적이고, 적절한 때에 반드시 사용되어야 한다. 삽입에서처럼, splice() 멤버 함수는 반복자를 사용하여, 접목이 일어나는 수신자 리스트에서의 위치를

가리킨다. 인자는 리스트 전체 또는 리스트내의 한 원소(반복자로 지칭) 또는 리스트의 부시퀀스

(한쌍의 반복자로 지칭)가 될 수 있다.

   // splice the last element of list ten   list_nine.splice(location, list_ten, list_ten.end());    // splice all of list ten   list_nine.splice(location,  list_ten);   // splice list 9 back into list 10   list_ten.splice(list_ten.begin(), list_nine,       list_nine.begin(), location);

두개의 순서화된 리스트는 merge() 연산을 통해 합쳐질 수 있다. 인자로 넘겨진 리스트의

값들은 순서화된 리스트로 합쳐지고 나면, 인자로 넘겨진 리스트는 비워지게 된다. 이 때의

merge 는 'stable'하다. 다시 말해서, 원소들은 원래 속해 있던 리스트에서의 상대적인 순서를

그대로 유지한다. 14.6절에서 살펴볼 같은 이름을 가진 generic 알고리듬에서처럼 merge() 멤버 함수는 두가지 형태가 지원된다. 두번째 형태는 값들을 순서짓기 위해서 인자로 제공되는

이항 함수를 사용한다. 모든 컴파일러가 두번째 형태를 지원하지는 않는다. 만약 이 두번째

67

Page 68: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

형태가 필요하지만 지원되지 않는다면, 좀 비효율적이긴 하지만, 더 일반적인 generic 알고리듬을 사용할 수 있겠다.

   // merge with explicit compare function   list_eleven.merge(list_six, widgetCompare);

   //the following is similar to the above   list<Widget> list_twelve;   merge(list_eleven.begin(), list_eleven.end(),       list_six.begin(), list_six.end(),       inserter(list_twelve, list_twelve.begin()), widgetCompare);   list_eleven.swap(list_twelve);

6.2.4 원소 삭제하기

리스트에 원소를 추가하는 방법이 여럿 있듯이, 리스트로부터 원소를 삭제하는 방법에도

여러가지가 있다. 원소를 삭제하는데 가장 많이 쓰이는 연산은 pop_front()와

pop_back()으로, 각각 리스트의 시작과 끝에서 하나의 원소를 삭제한다. 이들 멤버 함수는

단순히 주어진 원소를 삭제하기만 하고, 별다른 결과값은 반환하지 않는다. 원소들에 대해

소멸자가 정의되어 있다면, 이들 원소가 삭제될 때 소멸자가 호출된다. 삭제하기 전에 값들을

살펴보기 위해서는 front()나 back() 멤버 함수를 사용한다.

erase() 연산은 반복자가 가리키는 값을 삭제하는데 사용된다. 리스트의 경우, 인자로 주어진

반복자와 동일한 지점을 가리키고 있는 다른 반복자들은 삭제가 일어난 뒤에는 무효가 된다. 하지만, 다른 지점을 가리키는 반복자들은 영향을 받지 않는다. 한쌍의 반복자가 가리키는

부시퀀스 전체를 삭제할 때도 erase()를 사용할 수 있다. 첫번째 반복자에서부터 마지막

반복자 바로 이전까지의 값들이 리스트로부터 삭제된다. 리스트 중간에서 원소를 삭제하는 것은

vector 나 deque 에서와는 달리 효율적인 연산이다.

   list_nine.erase(location);

   // erase values between the first occurrence of 5    // and the following occurrence of 7   list<int>::iterator location =       find(list_nine.begin(), list_nine.end(), 5);   list<int>::iterator location2 =        find(location, list_nine.end(), 7);

68

Page 69: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   list_nine.erase(location, location2);

remove() 멤버 함수는 리스트로부터 주어진 값과 같은 모든 원소들을 삭제한다. remove_if()는 주어진 조건을 만족하는 모든 값들을 삭제한다. 이들 대신에, 13.5.1절에서

설명할 remove()나 remove_if() generic 알고리듬을 사용하는 방법도 있다. 이들

generic 알고리듬들은 리스트의 사이즈를 감소시키지 않으며, 대신에 원소들을 리스트의

앞쪽에 가져다 놓고, 리스트의 나머지 부분은 내버려두고, 변경되지 않은 첫번째 원소의 위치를

가리키는 반복자를 반환한다. 이 반환값은 erase()와 함께 사용되어 나머지 값들을

삭제하는데 사용할 수 있다.

   list_nine.remove(4);                         // remove all fours   list_nine.remove_if(divisibleByThree);     //remove any div by 3

   // the following is equivalent to the above   list<int>::iterator location3 =     remove_if(list_nine.begin(), list_nine.end(),               divisibleByThree);   list_nine.erase(location3, list_nine.end());

unique() 연산은 리스트에서 연속적으로 같은 값이 나오면 첫번째 원소를 제외한 나머지를

모두 삭제한다. 리스트가 정렬되어 있을 필요는 없다. 또 다른 형태의 unique()는 인자로

이항 함수를 취하는데, 이 함수를 이용하여 이웃하는 원소들을 비교하여 함수가 참을

리턴값으로 반환하면 두번째 값을 삭제한다. remove_if()에서 처럼, 모든 컴파일러가 이

두번째 형태의 unique()를 지원하지는 않는다. 이럴때는 좀더 일반적인 unique() generic 알고리듬을 사용하면 된다(13.5.2절 참고). 다음 예는 이항 함수가 greater-than 연산인

경우로, 앞쪽의 원소보다 작은 값들은 모두 삭제한다.

   // remove first from consecutive equal elements   list_nine.unique();

   // explicitly give comparison function   list_nine.unique(greater<int>());

   // the following is equivalent to the above   location3 =         unique(list_nine.begin(), list_nine.end(), greater<int>());

69

Page 70: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   list_nine.erase(location3, list_nine.end());6.2.5 확장 및 사이즈 변환 연산

size() 멤버 함수는 컨테이너가 담고 있는 원소들의 갯수를 리턴한다. empty() 함수는

컨테이너가 비어있을 때 참을 리턴하고, size()를 0 과 비교하는 것보다 더 효율적이다.

   cout << "Number of elements: " << list_nine.size () << endl;   if ( list_nine.empty () )      cout << "list is empty " << endl;   else      cout << "list is not empty " << endl;

resize() 멤버 함수는 리스트의 사이즈를 인자로 넘겨준 값으로 바꾼다. 이 때, 필요하다면

collection 의 끝부분에서 값들을 추가로 더하거나 삭제한다. 생략 가능한 두번째 인자는 새로

원소가 추가될 필요가 있는 경우에 초기값으로 사용된다.

   // become size 12, adding values of 17 if necessary   list_nine.resize (12, 17);

6.2.6 접근과 반복자

front()와 back() 멤버 함수는 각각 컨테이너의 처음 원소와 마지막 원소를 반환한다. 이때

삭제는 하지 않는다. 리스트에 대해서, 다른 원소를 접근하려면, 원하는 원소가 맨 앞이나 끝에

오게 될 때까지 원소들을 지우거나, 반복자를 이용함으로써 가능하다.

리스트와 함께 사용할 수 있는 반복자에는 세가지 형태가 있다. begin()과 end() 함수는

순방향으로 이동할 수 있는 반복자를 만들어 내는데, 리스트의 경우에 begin()과 end()는

양방향 반복자를 만들어 낸다. rbegin()과 rend()는 역순으로 이동할 수 있는 반복자를

만들어내는데, 리스트의 끝에서부터 앞쪽으로 이동한다.

6.2.7 소속 검사 연산

리스트 데이터 타입은 특정 값이 자신에게 속해있는지를 판별하는데 사용되는 메쏘드를 직접

제공하지 않는다. 그러나, find()나 count() generic 알고리듬(13.3.1절과 13.6.1절 참고)를 이런 용도로 사용할 수 있다. 다음은 17이란 수가 정수 리스트에 포함되어 있는지를

검사하는 예이다. int num = 0;count(list_five.begin(), list_five.end(), 17, num);if (num > 0)

70

Page 71: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   cout << "contains a 17" << endl;else   cout << "does not contain a 17" << endl;

if (find(list_five.begin(), list_five.end(), 17) != list_five.end())   cout << "contains a 17" << endl;else   cout << "does not contain a 17" << endl;

6.2.8 정렬 연산

sort() 멤버 함수는 원소들을 오름차순으로 정렬한다. '<' 연산자 이외의 비교 연산자를

원한다면, 인자로 제공하면 된다. list_ten.sort( );                  // place elements into sequencelist_twelve.sort(widgetCompare);       // sort with widget compare                                       // function

일단 리스트가 정렬되고 나면, 정렬 콜렉션용 generic 알고리듬을 리스트에 사용할 수 있다. 이들 알고리듬에 관해서는 14장에서 자세히 설명한다.

6.2.9 검색 연산

13.3절에서 설명할 find(), find_if(), adjacent_find(), mismatch(), max_element(), min_element(), search() 등의 다양한 형태의 검색 함수들을

리스트에 사용할 수 있다. 모든 경우에 이들은 반복자를 결과값으로 리턴하며, 이 반복자를

이용하여 원소를 참조하거나, 뒤에 이어지는 연산에서 인자로 사용될 수 있다.

검색 결과의 검사

6.2.10 In Place 변환

어떤 것들은 멤버 함수로 제공되고, 어떤 것들은 13 장 에서 설명할 generic 함수들을

이용하기도 한다.

리스트의 경우에는, reverse() 멤버 함수가 원소들을 순서를 뒤집는다.

   list_ten.reverse();                 // elements are now reversedtransform() generic 알고리듬(13.7.1 절 )을 사용하여, 입력 컨테이너와 결과 컨테이너를

같은 것으로 지정하면, 컨테이너내의 모든 값들을 바꿀 수 있다. 예를 들어, 다음은 리스트의 각

원소를 1씩 증가시키는 예이다. 자신이 필요로하는 단항 함수를 만들기 위해서, 이항 정수덧셈

함수가 1이라는 값에 연결되어 있는 것을 볼 수 있다. 두개의 시퀀스를 병렬적으로 다루는

71

Page 72: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

transform()은 비슷한 방법으로 사용하면 된다.    transform(list_ten.begin(), list_ten.end(),       list_ten.begin(), bind1st(plus<int>(), 1));이와 비슷하게, replace()와 replace_if() 함수(13.4.2 절 참고)는 리스트의 원소들을 특정

값으로 치환하는데 사용된다. 순환(13.4.3 절 )과 분할(13.4.4 절 )도 리스트에 적용될 수 있다.    // find the location of the value 5, and rotate around it   location = find(list_ten.begin(), list_ten.end(), 5);   rotate(list_ten.begin(), location, list_ten.end());   // now partition using values greater than 7   partition(list_ten.begin(), list_ten.end(),          bind2nd(greater<int>(), 7));next_permutation()과 prev_permutation() 함수(13.4.5 절 )는 각각 다음번

순열과 이전 순열을 생성하는데 사용된다.    next_permutation (list_ten.begin(), list_ten.end());

6.2.11 기타 연산

for_each() 알고리듬(13.8.1 절 )은 콜렉션 내의 모든 원소에 함수를 적용한다. 이것이

사용되는 예는 deque 데이터 구조에 관한 절의 radix sort 예제 프로그램에 잘 나타나 있다.

accumulate() generic 알고리듬은 콜렉션으로부터 하나의 값을 만들어 낸다(13.6.2 절 참고). 예를 들면, 정수 리스트에 속한 모든 원소들의 합을 구하는데 사용될 수 있다. accumulate()를 좀 색다르게 사용한 예도 마찬가지로 radix sort 예에서 살펴볼 수 있다.

   cout << "Sum of list is: " <<          accumulate(list_ten.begin(), list_ten.end(), 0) << endl;두개의 리스트를 서로 비교하는 것도 가능하다. 사이즈가 같고 대응되는 원소가 서로 같다면 두

리스트는 같다고 본다. 사전적으로 한쪽이 작은경우에 그 리스트는 나머지 리스트보다 작다고

한다(13.6.5 절 ).

6.3 예제 프로그램 - An Inventory System

We will use a simple inventory management system to illustrate the use of several list operations. Assume a business, named WorldWideWidgetWorks, requires a software system to manage their supply of widgets. Widgets are simple devices, distinguished by different identification numbers:

72

Page 73: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

class  Widget {public:   Widget(int a = 0) : id(a) { }   void operator = (const Widget& rhs) { id = rhs.id; }   int id;   friend ostream & operator << (ostream & out,const Widget & w)      { return out << "Widget " << w.id; }   friend bool operator == (const Widget& lhs, const Widget& rhs)      { return lhs.id == rhs.id; }   friend bool operator< (const Widget& lhs, const Widget& rhs)      { return lhs.id < rhs.id; }};

The state of the inventory is represented by two lists. One list represents the stock of widgets on hand, while the second represents the type of widgets that customers have backordered. The first is a list of widgets, while the second is a list of widget identification types. To handle our inventory we have two commands; the first, order(), processes orders, while the second, receive(), processes the shipment of a new widget.

class inventory {public:   void order (int wid);     // process order for widget type wid   void receive (int wid);   // receive widget of type wid in shipmentprivate:   list<Widget> on_hand;   list<int> on_order;};

When a new widget arrives in shipment, we compare the widget identification number with the list of widget types on backorder. We use find() to search the backorder list, immediately shipping the widget if necessary. Otherwise it is added to the stock on hand.

void inventory::receive (int wid){   cout << "Received shipment of widget type " << wid << endl;   list<int>::iterator weneed =       find (on_order.begin(), on_order.end(), wid);    if (weneed != on_order.end()) 

73

Page 74: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   {      cout << "Ship " << Widget(wid)            << " to fill back order" << endl;      on_order.erase(weneed);   }   else      on_hand.push_front(Widget(wid));}

When a customer orders a new widget, we scan the list of widgets in stock to determine if the order can be processed immediately. We can use the function find_if() to search the list. To do so we need a binary function that takes as its argument a widget and determines whether the widget matches the type requested. We can do this by taking a general binary widget-testing function, and binding the second argument to the specific widget type. To use the function bind2nd(), however, requires that the binary function be an instance of the class binary_function. The general widget-testing function is written as follows:

class WidgetTester : public binary_function<Widget, int, bool> {public:   bool operator () (const Widget & wid, int testid) const      { return wid.id == testid; }};

The widget order function is then written as follows: void inventory::order (int wid){   cout << "Received order for widget type " << wid << endl;   list<Widget>::iterator wehave =          find_if (on_hand.begin(), on_hand.end(),             bind2nd(WidgetTester(), wid));   if (wehave != on_hand.end())    {      cout << "Ship " << *wehave << endl;      on_hand.erase(wehave);   }   else 

74

Page 75: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   {      cout << "Back order widget of type "  << wid  << endl;      on_order.push_front(wid);   }}

7장: deque

7.1 deque 데이터 추상(data abstraction)

deque은 `double-ended queue'의 약자이고, `덱(deck)'이라고 발음한다. 전통적으로, 이

용어는 콜렉션의 앞과 뒤 어느 곳에서나 삽입과 삭제가 가능한 데이터 구조들을 지칭하는데

사용되었는데, deque 컨테이너 클래스는 이것뿐만 아니라 더 많은 것을 할 수 있다. 실제로, deque 데이터 구조의 능력은 vector 클래스와 list 클래스가 제공하는 것들을 모두 포함한다.

vector 와 같이 deque 은 인덱싱이 가능하다. 콜렉션 내에서의 위치를 키로

사용함으로써, 첨자로 값들을 접근할 수 있다. (물론 list 클래스에서는 제공하지 않는

기능이다.) list 와 같이 deque 의 앞과 뒤에 값을 효율적으로 추가할 수 있다. (vector

클래스에서는 vector 의 맨뒤에서 값을 추가하는데 있어 효율적이다.) list 와 vector 클래스에서와 같이, deque 가 관리하는 시퀀스의 중간에서 값들을

삽입할 수 있다. 이러한 삽입 연산은 list 에서도 효율적인 연산이지만, vector 에서는

조금 더 비용이 드는 연산이다.

간단히 말해서, deque은 vector와 list를 필요로 하는 곳에서 모두 사용할 수 있다. vector와

list 대신에 deque를 사용하게 되면, 종종 프로그램의 성능이 더 나아진다. 어느 데이터 구조를

선택할지에 관해서는 4.2 절 을 참고하기 바란다. 7.1.1 Include 화일

deque 데이터 타입을 사용하는 프로그램은 모두 deque 헤더 화일을 사용해야 한다. #include <deque>

7.2 deque 연산

75

Page 76: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

deque 는 vector 와 같은 방식으로 선언하고, 클래스 내부에는 vector 와 동일한 타입 정의를

가지고 있다.

begin()과 end() 멤버 함수는 양방향 반복자를 제공하는 리스트와는 달리, 임의접근

반복자를 리턴한다.

삽입(insert(), push_front(), push_back())은 deque 의 원소를 가리키는 반복자나

레퍼런스들을 무효화시킬 수도 있다. 이는 vector 데이터 타입과 같이 list 에서의 삽입보다 더

제한적이다.

deque 내의 원소 타입에 소멸자가 정의되어 있다면, deque 로부터 원소를 삭제할 때, 이

소멸자가 호출될 것이다.

deque 데이터 타입이 임의접근 반복자를 제공하기 때문에, vector 에 적용되는 모든 generic 알고리듬은 deque 에도 사용될 수 있다.

vector 는 커다란 하나의 메모리 블럭에 원소들을 가지고 있는 반면, deque 은 많은 수의 좀더

작은 블럭들을 사용한다. 메모리 블럭의 사이즈에 제한을 두고 있는 시스템에서 이는 매우

중요한 사항이며, 이러한 이유때문에 deque 은 vector 보다 더 많은 원소들을 포함할 수 있다.

값들을 삽입할 때는, 콜렉션내의 원소와 연결되어 있던 인덱스가 바뀌게 된다. 예를 들어, 3 번

위치에 값을 삽입하면, 원래 3 번 자리에 있던 값은 4 번자리를 차지하게 되고, 4 번 자리에 있던

값은 5 번자리로 차례로 옮겨지게 된다.

7.3 예제 프로그램 - Radix Sort

The radix sort algorithm is a good illustration of how lists and deques can be combined with other containers. In the case of radix sort, a vector of deques is manipulated, much like a hash table.

Radix sorting is a technique for ordering a list of positive integer values. The values are successively ordered on digit positions, from right to left. This is accomplished by copying the values into "buckets," where the index for the bucket is given by the position of the digit being sorted. Once all digit positions have been examined, the list must be sorted.

76

Page 77: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

The following table shows the sequences of values found in each bucket during the four steps involved in sorting the list 624 852 426 987 269 146 415 301 730 78 593. During pass 1 the ones place digits are ordered. During pass 2 the tens place digits are ordered, retaining the relative positions of values set by the earlier pass. On pass 3 the hundreds place digits are ordered, again retaining the previous relative ordering. After three passes the result is an ordered list.  

bucket  pass 1  pass 2  pass 3 

0  730  301  78 

1  301  415  146 

2  852 624, 426 

269 

3  593  730  301 

4  624  146 415, 426 

5  415  852  593 

6 426, 146 

269  624 

7  987  78  730 

8  78  987  852 

9  269  593  987 

The radix sorting algorithm is simple. A while loop is used to cycle through the various passes. The value of the variable divisor indicates which digit is currently being examined. A boolean flag is used to determine when execution should halt. Each time the while loop is executed a vector of deques is declared. By placing the declaration of this structure inside the while loop, it is reinitialized to empty each step. Each time the loop is executed, the values in the list are copied into the appropriate bucket by executing the function copyIntoBuckets() on each value. Once distributed into the buckets, the values are gathered back into the list by means of an accumulation.

77

Page 78: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

void radixSort(list<unsigned int> & values){   bool flag = true;   int divisor = 1;      while (flag) {      vector< deque<unsigned int> > buckets(10);      flag = false;      for_each(values.begin(), values.end(),             copyIntoBuckets(...));      accumulate(buckets.begin(), buckets.end(),             values.begin(), listCopy);      divisor *= 10;      }}

The use of the function accumulate() here is slightly unusual. The "scalar" value being constructed is the list itself. The initial value for the accumulation is the iterator denoting the beginning of the list. Each bucket is processed by the following binary function:

list<unsigned int>::iterator       listCopy(list<unsigned int>::iterator c,          deque<unsigned int> & lst){   // copy list back into original list, returning end   return copy(lst.begin(), lst.end(), c);}

The only difficulty remaining is defining the function copyIntoBuckets(). The problem here is that the function must take as its argument only the element being inserted, but it must also have access to the three values buckets, divisor and flag. In languages that permit functions to be defined within other functions the solution would be to define copyIntoBuckets() as a local function within the while loop. But C++ has no such facilities. Instead, we must create a class definition, which can be initialized with references to the appropriate values. The parenthesis operator for this class is then used as the function for the for_each() invocation in the radix sort program.

class copyIntoBuckets {

78

Page 79: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

public:   copyIntoBuckets      (int d, vector< deque<unsigned int> > & b, bool & f)          : divisor(d), buckets(b), flag(f) {}

   int divisor;   vector<deque<unsigned int> > & buckets;   bool & flag;

   void operator () (unsigned int v)   {   int index = (v / divisor) % 10;       // flag is set to true if any bucket        // other than zeroth is used       if (index) flag = true;        buckets[index].push_back(v);   }};

8장: set, multiset, bitset

8.1 set 데이터 추상(data abstraction)

Sets, Ordered and Not 

set 은 값들의 콜렉션이다. set 데이터 구조를 구현하는데 사용되는 컨테이너는 순서를

유지하여 값들을 관리하기 때문에, 원소의 삽입과 삭제뿐만 아니라, 특정 값이 콜렉션에

들어있는지의 여부를 검사하는데 있어 최적화되어 있다. 이러한 연산들은 각각 logarithmic 횟수로 수행된다. 반면에 list, vector, deque 에서는 각각의 연산들이 최악의 경우에

컨테이너에 담긴 모든 원소들을 살피게 될수도 있다. 이러한 이유때문에, 삽입, 삭제와 값의

포함여부에 대한 검사가 중요한 문제에서는 set 을 선택해야 한다. list 에서처럼, set 도

79

Page 80: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

사이즈에 제한을 받지 않고, 콜렉션으로부터 원소들을 첨가하거나 삭제할 때 늘어나거나

줄어든다.

표준 라이브러리는 두가지 종류의 set 을 제공하는데, set 컨테이너에서는 모든 값들이

유일해야 하고, set 에 이미 들어있는 값을 중복 삽입하는 것은 무시가 된다. 한편, multiset 컨테이너에서는 같은 값이 여러개 있어도 이를 허용한다.

8.1.1 Include 화일

set이나 multiset을 사용할 때는 set 헤더화일을 반드시 포함시켜야 한다.    #include <set>

8.2 set 과 multiset 연산

Set 과 Bag

set 과 multiset 이 제공하는 멤버 함수들을 자세히 알아본다. 멤버 함수들이 기초적인 연산들을

제공하지만, 13 장 과 14 장에서 설명하는 generic 알고리듬을 사용함으로써 이들 데이터

구조를 좀더 유용하게 사용할 수 있다.

8.2.1 set의 선언과 초기화

set는 템플릿 타입으로 첫번째 인자로 원소 타입을, 두번째 인자로 키값을 비교하는 연산자를

명시해주어야 한다. 두번째 인자는 생략가능한데, 따로 명시하지 않으면, 키타입에서 정의하고

있는 less-than 연산자를 사용한다. 원소의 타입은 int 또는 double과 같은 primitive 타입이나, 포인터 타입 또는 사용자 정의 타입이 될 수 있다. 원소 타입은 상등 연산자(==)와

less-than 연산자(<)를 가지고 있어야 한다.

Initializing Sets with Iterators

set 은 초기값없이 선언될 수도 있고, 한쌍의 반복자를 써서 다른 컨테이너에 담긴 값들로

초기화할 수도 있다. 이 두가지 경우 모두 비교 함수를 인자로 사용할 수 있으며, 생략가능하다. 이 비교함수는 템플릿 인자로 명시된 비교 함수를 덮어쓴다. 이런식으로 비교함수를

지정함으로써, 값은 같지만 순서를 다르게 매기는 두개 이상의 set 을 다루는 경우에 유용하다.

80

Page 81: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

set 멤버 함수들이 여러개 개체화되는 것을 막을 수 있기 때문이다. 복사 생성자를 사용하여

다른 set 의 복사본이 되는 set 을 만들 수 있다.

   set <int> set_one;

   set <int, greater<int> > set_two;   set <int> set_three(greater<int>());

   set <gadget, less<gadget> > gset;   set <gadget> gset(less<gadget>());

   set <int> set_four (aList.begin(), aList.end());   set <int> set_five       (aList.begin(), aList.end(), greater<int>());

   set <int> set_six (set_four);                // copy constructor

한 set 을 다른 set 에 대입할 수도 있고, swap() 연산을 써서 두 set 간의 값들을 교환할 수도

있다.

   set_one = set_five;   set_six.swap(set_two);

8.2.2 타입 정의

set과 multiset 클래스는 많은 타입 정의들을 포함한다. 이들은 선언문에서 가장 많이

사용된다. 예를 들어, 정수 set에 사용할 반복자는 다음과 같이 선언된다.    set<int>::iterator location;iterator뿐만 아니라, 다음 타입들도 정의되어 있다.  

value_type The type associated with the elements the set maintains.

const_iterator An iterator that does not allow modification of the underlying sequence.

reverse_iterator An iterator that moves in a backward direction.

const_reverse_iterator

A combination constant and reverse iterator.

81

Page 82: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

reference A reference to an underlying element.

const_reference A reference to an underlying element that will not permit modification.

size_type An unsigned integer type, used to refer to the size of containers.

value_compare A function that can be used to compare two elements.

difference_type A signed integer type, used to describe the distance between iterators.

allocator_type An allocator used by the container or all storage management.

8.2.3 삽입

Pair 데이터 타입

list 나 vector 와 달리 set 에 원소를 새로 추가하려면 단한가지 방법밖에 없다. insert() 멤버

함수를 써서 set 이나 multiset 에 값을 삽입할 수 있는 것이다. multiset 의 경우에는 방금

삽입한 값을 가리키는 반복자를 리턴한다. set 의 경우에는 pair 를 리턴하는데, 첫번째 필드는

반복자이고, 두번째 필드는 부울값으로 원소가 삽입되었는 지의 여부를 표시한다. set 에서는

원소를 삽입할 때 이미 콜렉션내에 해당 원소가 있으면, 삽입이 이루어지지 않기 때문이다.

   set_one.insert(18);

   if (set_one.insert(18).second)      cout << "element was inserted" << endl;   else      cout << "element was not inserted " << endl;다른 컨테이너에 들어있는 값들을 삽입할 때는 한쌍의 반복자를 사용한다.    set_one.insert(set_three.begin(), set_three.end());pair 데이터 구조는 값들의 투플이다. 첫번째 값은 first라는 필드명으로 접근하고, 두번째

값은 second라는 필드명으로 접근한다. make_pair()라는 함수는 pair 클래스의 개체를

만드는 작업을 간편하게 해준다. template <class T1, class T2>

82

Page 83: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

struct pair {   T1 first;   T2 second;   pair (const T1 & x, const T2 & y) : first(x), second(y) { }};

template <class T1, class T2>inline pair<T1, T2> make_pair(const T1& x, const T2& y)   { return pair<T1, T2>(x, y); }

예를 들어, 새로운 원소의 키부분이 이미 존재하는 키값인지 알아보기 위해서, 키값들이 서로

일치하는지를 검사할 때, 상등(==) 연산자를 사용하는 것이 아니라, 비교 함수를 사용한다. 키값들의 순서를 매기기위해 사용되는 비교 함수가 양방향으로 모두 거짓이면, 두 키값은 서로

같다고 본다. 다시 말해서, Compare(key1, key2)가 거짓이고, Compare(key2, key1)도

거짓이면, key1과 key2는 상등이라고 보는 것이다. 8.2.4 set에서의 삭제

set에서 값을 삭제할 때는 erase() 멤버 함수를 사용한다. 인자는 특정 값이거나, 값을

가리키는 반복자 또는 값들의 range를 지정하는 한쌍의 반복자일 수 있다. 첫번째 형태를

multiset에 사용할 때는 인자로 주어진 값과 일치하는 모든 원소들이 삭제되고, 리턴값은

삭제된 원소들의 갯수를 의미한다.    // erase element equal to 4   set_three.erase(4);

   // erase element five      set<int>::iterator five = set_three.find(5);   set_three.erase(five);      // erase all values between seven and eleven   set<int>::iterator seven = set_three.find(7);   set<int>::iterator eleven = set_three.find(11);   set_three.erase (seven, eleven);컨테이너가 가지고 있는 원소들의 타입이 소멸자를 가지고 있다면, 원소가 삭제되기 전에

소멸자를 호출하게 된다. 8.2.5 검색과 카운팅

size() 멤버함수는 컨테이너가 가지고 있는 원소의 갯수를 반환한다. empty() 멤버 함수는

컨테이너가 비어 있을 때 참을 리턴하는데, size()를 0과 비교하는 것보다 대체로 더 빠르다.

83

Page 84: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

find() 멤버 함수는 특정값을 인자로 취해서 set 내에 해당 값이 존재하면, 그 값의 위치를

가리키는 반복자를 리턴하고, set 내에 존재하지 않으면, end-of-set 에 해당하는 반복자를

리턴한다(이는 end()가 리턴하는 값과 같은 것이다). 같은 값이 여러개 존재할 수 있는

multiset 의 경우에는 이들 값중에서 적절한 값을 리턴한다.

   set<int>::iterator five = set_three.find(5);   if (five != set_three.end())       cout << "set contains a five" << endl;lower_bound()와 upper_bound() 멤버 함수는 multiset과 같이 쓰일 때 아주 쓸모가

있다. set에 사용하면, 단지 find() 함수를 흉내내는 것에 지나지 않는다. lower_bound() 멤버 함수는 인자로 주어진 키값과 일치하는 첫번째 원소를 리턴하고, 반면에

upper_bound() 멤버 함수는 키값과 일치하는 마지막 원소를 지나서 첫번째 원소를

반환한다. 마지막으로, equal_range() 멤버 함수는 lower bound와 upper bound를

포함하는 반복자의 pair를 리턴한다.

count() 멤버 함수는 인자로 주어지는 값과 일치하는 원소의 갯수를 리턴한다. set 의

경우에는 이 값이 0 또는 1 인 반면, multiset 의 경우에는 음수가 아닌 임의의 값이 될 수 있다. 0 이 아닌 정수값은 참으로 간주하므로, count() 함수를 원소의 포함 검사에 사용할 수도

있다. 또는 find()의 리턴값을 end-of-collection 반복자와 비교하여 포함 검사를 할 수도

있다.

   if (set_three.count(5))      cout << "set contains a five" << endl;

8.2.6 반복자

No Iterator Invalidation

begin()과 end() 멤버 함수는 set 과 multiset 에 대한 반복자를 생성한다. 이들 함수들이

만들어내는 반복자들은 상수이어야 하는데, 이는 set 의 원소에 새로운 값을 대입하게 되어 set에 대한 순서 관계가 깨지는 것을 막기 위해서이다. 이들 반복자들을 사용하여, set 을 선언했을

때 명시한 비교 연산자로 정해진 순서대로 원소들을 접근할 수 있다. rbegin()과 rend() 멤버 함수는 역순으로 원소들을 접근하는 반복자들을 생성한다.

8.2.7 set 연산

전통적인 집합 연산인 부분집한 검사, union, intersection, difference 연산들은 멤버함수로

제공되지 않지만, 대신에 ordered 구조에 사용되는 generic 알고리듬으로 구현되어 있다. 이들

84

Page 85: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

함수들에 관해서는 14.7절에 자세히 설명되어 있다. 다음은 이들 함수들이 set과 multiset 클래스와 함께 사용하는 법을 간략히 설명하고 있다.

8.2.7.1 부분집합 검사

includes()함수는 한 집합이 다른 집합의 부분집합인지 알아보는데 사용된다. multiset의

경우에는 두번째 집합내에서 일치하는 원소의 갯수가 첫번째 원소의 갯수를 초과해야 한다. 인자의 갯수는 포함되는 집합을 나타내는 반복자 한쌍과 포함하는 집합을 나타내는 반복자

한쌍해서, 모두 4개가 필요하다.    if (includes(set_one.begin(), set_one.end(),      set_two.begin(), set_two.end()))         cout << "set_one is a subset of set_two" << endl;원소를 비교하는데 사용되는 것은 less-than 연산자(<)이며, set을 선언할 때 명시한 비교

연산자를 사용하는 것이 아니다. 이것이 맘에 안들면, 다른 형태의 includes() 함수를

사용하면 된다. 이것은 두 집합의 원소의 순서를 매기기 위해서 사용되는 비교 함수가 인자로

추가되어, 총 5개의 인자를 필요로 한다. 8.2.7.2 합집합과 교집합

set_union() 함수는 두 집합의 합집합을 구성하는데 사용된다. 연산에 관여되는 두집합은

반복자의 쌍으로 표시하고, 합집합의 결과는 5번째 인자로 명시된 출력 반복자로 복사된다. 결과를 set으로 구성하기 위해서, 삽입 반복자를 사용하여 출력 반복자를 만들어야 한다.(삽입

반복자에 관해서는 2.4절을 참고)    // union two sets, copying result into a vector   vector<int> v_one (set_one.size() + set_two.size());

   set_union(set_one.begin(), set_one.end(),       set_two.begin(), set_two.end(), v_one.begin());

   // form union in place   set<int> temp_set;   set_union(set_one.begin(), set_one.end(),       set_two.begin(), set_two.end(),       inserter(temp_set, temp_set.begin()));   set_one.swap(temp_set);  // temp_set will be deletedset_intersection() 함수도 이와 비슷하며, 두집합의 교집합을 형성한다.

includes() 함수에서처럼 원소를 비교할 때는 선언시 명시한 비교 연산자대신에 less-than 연산자(<)를 사용한다. 이것이 부적절하다고 생각되면, 6 번째 인자로 비교 연산자를 요구하는

다른 형태의 set_union()과 set_intersection()을 사용하면 된다.

85

Page 86: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

multiset 의 합집합 연산은 두집합을 합병하는 연산과는 구별되어야 한다. 한 집합이 7 을 3 개

포함하고 있고, 다른 집합이 7 을 2 개 포함하고 있다고 가정하자. 이들의 합집합은 7 을 3 개만

포함하지만 합병한 결과에는 5 개가 포함된다. 합병은 merge() 함수를 이용한다(14.6절

참고). 이 함수의 인자는 set_union() 함수와 동일하다.

8.2.7.3 차집합

차집합 연산에는 두가지 형태가 있다. 단순 차집합은 첫번째 집합의 원소중에서 두번째 집합에

속하지 않는 원소들을 나타낸다. 대칭 차집합은 두번째 집합의 원소를 제외한 첫번째 집합의

원소들과 첫번째 집합의 원소를 제외한 두번째 집합의 원소들을 합한 것이다. 이들은 각각

set_difference()와 set_symmetric_difference() 함수로 구성된다. 이들 함수들은

set_union()와 사용법이 비슷하다. 8.2.8 기타 generic 알고리듬

set은 순서를 매기고, 상수 반복자를 가지기 때문에, 13절과 14절에 설명하고 있는 generic 함수중에는 set에 적용할 수 없거나 별로 유용하지 않는 것들이 많다. 그러나, 다음 표는 set 데이터 타입과 함께 사용될 수 있는 몇가지 함수들을 보여주고 있다.  

용도 이름해당

Copy one sequence into another  copy 13.2.2

Find an element that matches a condition  find_if 13.3.1

Find a sub-sequence within a set  search 13.3.3

Count number of elements that satisfy condition 

count_if 13.6.1

Reduce set to a single value  accumulate

13.6.2

Execute function on each element  for_each 13.8.1

86

Page 87: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

8.3 예제 프로그램 - 철자 검사기

A simple example program that uses a set is a spelling checker. The checker takes as arguments two input streams; the first representing a stream of correctly spelled words (that is, a dictionary), and the second a text file. First, the dictionary is read into a set. This is performed using a copy() and an input stream iterator, copying the values into an inserter for the dictionary. Next, words from the text are examined one by one, to see if they are in the dictionary. If they are not, then they are added to a set of misspelled words. After the entire text has been examined, the program outputs the list of misspelled words.

void spellCheck (istream & dictionary, istream & text){   typedef set <string, less<string> > stringset;   stringset words, misspellings;   string word;   istream_iterator<string> dstream(dictionary), eof;

   // first read the dictionary   copy (dstream, eof, inserter(words, words.begin())); 

   // next read the text   while (text >> word)      if (! words.count(word))         misspellings.insert(word);

   // finally, output all misspellings   cout << "Misspelled words:" << endl;   copy (misspellings.begin(), misspellings.end(),      ostream_iterator<string>(cout, "\n"));}

An improvement would be to suggest alternative words for each misspelling. There are various heuristics that can be used to discover alternatives. The technique we will use here is to simply exchange adjacent letters. To find these, a call on the following function is inserted into the loop that displays the misspellings.

void findMisspell(stringset & words, string & word)

87

Page 88: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

{   for (int I = 1; I < word.length(); I++) {      swap(word[I-1], word[I]);      if (words.count(word))          cout << "Suggestion: " << word << endl;      // put word back as before      swap(word[I-1], word[I]);      }}

8.4 bitset 추상(abstraction)

bitset은 set과 vector의 cross이다. vector<bool>과 같이, 추상은 이진값의 집합이다. 그러나, 논리적 bit별 연산을 사용하여 bitset상에 set 연산을 수행할 수 있다. bitset 클래스는

원소를 접근하는데 사용할 반복자를 제공하지 않는다. 8.4.1 Include 화일

#include <bitset>8.4.2 bitset의 선언과 초기화

bitset은 템플릿 클래스이지만, 템플릿 인자가 타입이 아니라 정수값이 된다. 이 값은 set이

포함하는 비트의 갯수를 나타낸다. bitset<126> bset_one;        // create a set of 126 bits

좀 다른 기법으로 set의 사이즈를 생성자의 인자로 명시할 수도 있다. 실제 사이즈는 템플릿

인자로 사용된 값과 생성자 인자로 사용된 값 중 더 작은 값으로 정해진다. 이렇게 하는 것은

프로그램에서 두개 이상의 서로 다른 사이즈의 bit vector를 포함하는 경우에 유용하다. 템플릿

인자로 더 큰값을 사용함으로써, 해당 클래스에 대한 메쏘드를 한번만 생성하게 된다. 그러나, 실제 사이즈는 생성자가 결정하게 된다.

bitset<126> bset_two(100);   // this set has only 100 elements생성자의 세번째 형태는 '0'과 '1'이라는 문자로 이루어진 문자열을 인자로 받는다. 이 문자열에

속하는 문자와 같은 갯수의 원소를 가지는 bitset을 만들고, 문자열에 명시된 값들로

초기화한다. bitset<126> small_set("10101010");   // this set has 8 elements

8.4.3 접근과 검사

bitset의 각각의 bit는 첨자 연산을 사용하여 접근이 가능하다. bit가 1인지 0인지를 알아보기

위해서는 test() 멤버 함수를 사용한다. bitset에 한 bit가 'on'인지를 알아보기 위해서는

88

Page 89: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

any() 멤버 함수를 사용하고 불값을 리턴한다. any()의 반대는 none() 멤버 함수이다.    bset_one[3] = 1;   if (bset_one.test(4))      cout << "bit position 4 is set" << endl;   if (bset_one.any())      cout << "some bit position is set" << endl;   if (bset_one.none()) cout << "no bit position is set" << endl;set() 함수는 특정 bit를 세팅하는데 사용된다. 'bset_one.set(I)'는 'bset_one[I] = true'과 동일하다. 아무 인자 없이 이 함수를 호출하면 모든 비트를 'on'으로 세팅한다. reset() 함수도 사용법이 비슷한데, 인자로 명시된 위치의 비트를 'off'로 세팅하고, 인자가

없으면, 모든 비트를 'off'로 세팅한다. flip() 함수도 제공되는데 비트를 가리키는 레퍼런스에

대한 멤버 함수로서 제공된다.    bset_one.flip();   // flip the entire set   bset_one.flip(12);    // flip only bit 12   bset_one[12].flip();     // reflip bit 12size() 멤버 함수는 bitset의 사이즈를 리턴하고, count() 멤버 함수는 세팅되어 있는

비트들의 갯수를 리턴한다. 8.4.4 set 연산

bitset에 대한 set 연산은 bit-wise 연산자를 사용하여 구현되는데, 정수값에 대해 수행되는

방식과 비슷하다.

bitset 에 부정 연산자(~ 연산자)를 적용하면 인자로 지정된 bitset 의 원소들의 반대값을

포함하는 새로운 bitset 을 리턴한다.

두 bitset 의 교집합은 and 연산자(& 연산자)를 통해 이루어진다. 대입형태의 연산자도

가능하며, 이때 좌변이 두집합의 disjunction 이 된다.

   bset_three = bset_two & bset_four;   bset_five &= bset_three;두 집합의 합집합도 or 연산자(| 연산자)를 사용하여 비슷한 방식으로 이루어진다. exclusive-or는 exclusive-or 연산자(^ 연산자)를 사용하여 이루어진다.

왼쪽 쉬프트 연산자(<< 연산자)과 오른쪽 쉬프트 연산자(>> 연산자)는 bitset 을 왼쪽 또는

오른쪽으로 쉬프트하는데 사용된다. 한 비트를 왼쪽으로 'n'만큼 쉬프트하면, 새로운 비트 위치

I 는 이전의 I-n 의 값이 된다(?). 새로운 위치들은 0 값들로 쉬프트된다.

89

Page 90: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

8.4.5 변환

to_ulong() 멤버 함수는 bitset을 'unsigned long'으로 바꾼다. 더 많은 원소를 가지는

bitset에 이 연산을 수행하면 에러를 발생한다.

to_string() 멤버 함수는 bitset 을 'string' 타입으로 바꾼다. 이때 string 은 bitset 의

원소갯수만큼의 문자를 가지게 된다. 각각의 0 비트는 문자 '0'으로 바꾸고, 1 비트는 문자 '1'로

바꾼다.

9 장: map 과 multimap

9.1 map 데이터 추상(data abstraction)

Map 대신 사용되는 다른 명칭들

vector 나 deque 와 마찬가지로 map 도 인덱싱이 가능하다. 그러나, map 은 두가지점에서

이들과 차이가 난다. 첫째로, map 에서는 인덱스 값(키값)이 반드시 정수일 필요는 없고, ordered 데이터 타입이면 가능하다. 예를 들어, 실수나 string 으로 인덱싱이 가능하다. 비교연산자를 가지고 있는 데이터 타입이면 키로서 사용이 가능하다. vector 나 deque에서처럼, 첨자 연산자로 원소들을 접근할 수 있고, 물론 다른 방법도 있다. 두번째 차이점은

map 은 ordered 데이터 구조란 점이다. 이는 원소들이 키값에 따라 순서가 정해져서

관리된다는 말이다. 값들을 순서를 매겨 관리하기 때문에, 주어진 키에 해당하는 원소를 아주

빨리 찾아낼 수 있다(검색 시간은 log 시간). list 와 마찬가지로, map 도 사이즈에 제한이

없지만, 새 원소들이 추가되거나 삭제될 때, 확장하거나 축소한다. 대부분의 경우, map 은 pair를 관리하는 set 으로 봐도 무방하다.

표준 라이브러리가 제공하는 map 에는 두가지 종류가 있다. map 은 키가 반드시 유일해야

한다. 즉, 키와 그에 대응되는 값이 서로 일대일대응이 되어야 한다는 것이다. map 에서는 이미

존재하는 키값을 가지는 원소를 삽입할 때 이는 무시된다. 한편으로 multimap 은 같은 키로

인덱싱되는 여러개의 서로 다른 원소들을 허용한다. 이 두가지는 모두 삽입, 삭제, 접근 연산이

90

Page 91: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

상대적으로 빠르다.(log 시간)

Pairs 

9.1.1 Include 화일

map이나 multimap을 사용할 때는 map 헤더화일을 포함해야 한다.    #include <map>

9.2 map 과 multimap 연산

map과 multimap 데이터 타입이 제공하는 멤버 함수에 관해 자세히 살펴본다. 멤버 함수들이

기초적인 연산들을 제공하는 반면에, 13절과 14절에 설명하고 있는 generic 알고리듬을

사용함으로써 데이터 구조를 보다 유용하게 사용할 수 있게 된다. 9.2.1 map의 선언과 초기화

map의 선언은 표준 라이브러리에서 자주 보아왔던 방식을 따른다. map은 템플릿 데이터

구조이며, 템플릿의 인자로 키의 타입, 키에 대응되는 값의 타입, 키를 비교하는데 사용될 비교

연산자를 지정해야 한다. 만약에 컴파일러가 디폴트 템플릿 인자(아직은 많은 회사에서

지원하지 않는 C++의 새특징)를 지원하면, 방금 언급한 템플릿 인자 중 세번째 것은 생략이

가능하고, 생략되는 경우에는 키의 타입에 정의되어 있는 less-than 연산자로 지정된다. map은 초기값없이 선언될 수 있고, 한쌍의 반복자를 사용하여 다른 컨테이너에 담긴 값들로 초기화

될 수도 있다. 후자의 경우에, 반복자는 pair 타입의 값을 가리키고 있어야 하며, 이때 첫번째

필드는 키(key)로 간주되고, 두번째 필드는 값(value)으로 간주되는 것이다. 복사 생성자를

사용하여 다른 map의 복사본을 만들어 낸다.    // map indexed by doubles containing strings   map<double, string, less<double> > map_one;   // map indexed by integers, containing integers      map<int, int> map_two(aContainer.begin(), aContainer.end());   // create a new map, initializing it from map two   map<int, int> map_three(map_two);   // copy constructormap을 다른 map에 대입할 수 있으며, swap() 연산을 사용하여 두 map간에 값을 서로

교환할 수 있다. 9.2.2 타입 정의

map과 multimap은 많은 타입 정의들을 포함하고 있으며, 이들은 주로 선언문에서 많이

91

Page 92: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

사용된다. 예를 들어, string을 정수로 매핑하는 map에 사용되는 반복자는 다음과 같이

선언된다. map<string, int>::iterator location;

'iterator' 뿐만 아니라, 다음과 같은 타입들이 정의되어 있다.  

key_type map을 인덱싱할 때 사용하는 키의 타입

value_type 맵이 담고 있는 데이터 즉 key/value pair 타입

const_iterator 자신이 가리키는 원소를 변경할 수 없는 상수 반복자

reverse_iterator 역방향으로 이동하는 역반복자

const_reverse_iterator

상수 반복자와 역 반복자의 성질을 모두 가지는 반복자

reference A reference to an underlying value.

const_reference A reference to an underlying value that will not permit the element to be modified.

size_type An unsigned integer type, used to refer to the size of containers.

key_compare A function object that can be used to compare two keys.

value_compare A function object that can be used to compare two elements.

difference_type A signed integer type, used to describe the distances between iterators.

allocator_type An allocator used by the container for all storage management.

9.2.3 삽입과 접근

insert() 연산을 사용하여 map 이나 multimap 에 값들을 삽입한다. 이때 인자는 반드시 키와

값의 pair 이어야 한다. 이때 사용되는 pair 는 map 에 정의되어 있는 value_type 이라는

데이터 타입을 사용하여 만든다.

   map_three.insert(map<int>::value_type(5, 7));한쌍의 반복자를 사용하여 삽입을 수행할 수도 있는데, 다음과 같이 다른 컨테이너로부터 값을

92

Page 93: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

가져올 수 있다.    map_two.insert(map_three.begin(), map_three.end());

map(multimap 은 제외)에서는 첨자 연산을 통해 값을 접근하거나 삽입할 수 있다. 단순히

키를 첨자로 사용하면 해당 값을 접근할 수 있고, 첨자 연산의 결과에 대입을 함으로써 키에

대응되는 값을 변화시킬 수 있다.

   cout << "Index value 7 is " << map_three[7] << endl;      // now change the associated value   map_three[7] = 5;   cout << "Index value 7 is " << map_three[7] << endl;

9.2.4 삭제

키값을 가지고 map 과 multimap 으로부터 값들을 삭제할 수 있다. multimap 에서는 키와

연관된 모든 원소를 삭제한다. 삭제될 원소를 반복자로 지정할 수도 있다. 예를 들면, find() 연산의 결과로 얻은 반복자를 사용할 수도 있다. 한쌍의 반복자를 사용하여 지정한 range 내의

원소들을 모두 지우는 것도 가능하다.

   // erase the 4th element 4   map_three.erase(4);   // erase the 5th element    mtesttype::iterator five = map_three.find(5);   map_three.erase(five);      // erase all values between the 7th and 11th elements   mtesttype::iterator seven = map_three.find(7);   mtesttype::iterator eleven = map_three.find(11);   map_three.erase(seven, eleven);원소타입이 소멸자를 정의하고 있다면, 콜렉션으로부터 키와 값을 제거하기 전에 이 소멸자가

호출될 것이다. 9.2.5 반복자

No Iterator Invalidation 

begin()과 end() 멤버 함수는 map 과 multimap 의 경우 양방향 반복자를 생성한다. 이렇듯, map 과 multimap 에서 사용되는 반복자들은 양방향 반복자이므로 대소 비교(<, >,

93

Page 94: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

<=, >=)가 허용되지 않는다는 점에 유의하자(2.2 절 의 표를 참고). map 이나 multimap 에

사용되는 반복자를 참조하면 키(key)와 값(value)의 pair 를 얻게 된다. 따라서, 필드명으로

first 를 사용하면 키를, second 를 사용하면 값(value)을 얻을 수 있다. 첫번째 필드는

상수라서 변경할 수 없지만, 두번째 필드는 주어진 키(key)와 연결된 값(value)을 변화시키는데

사용된다. 원소들은 키 필드의 순서에 따라 접근된다(?).

rbegin()과 rend() 멤버 함수는 역방향으로 원소들을 생성하는 반복자들을 반환한다.

9.2.6 검색과 카운팅

size() 멤버 함수는 컨테이너가 가지고 있는 원소의 갯수를 리턴한다. empty() 멤버 함수는

컨테이너가 비었을 때, true 를 리턴하고, size()를 zero 와 비교하는 것보다 대체로 빠르다.

find() 멤버함수는 키를 인자로 취해서, 키/값 pair 를 가리키는 반복자를 리턴한다. multimap의 경우에는, 가장 먼저 일치하는 값을 리턴한다. 두경우 모두, 원하는 값을 찾지 못할 때는, past-the-end 반복자를 리턴한다.

   if (map_one.find(4) != map_one.end())      cout << "contains a 4th element" << endl;

lower_bound() 멤버 함수는 인자로 주어진 키와 일치하는 첫번째 원소를 리턴하고, upper_bound() 멤버 함수는 인자로 주어진 키와 일치하는 마지막 원소 바로 직후의 원소를

리턴한다. 마지막으로, equal_range() 함수는 lower bound 와 upper bound 를 담고

있는 반복자의 pair 를 리턴한다. 이들 연산을 사용하는 예는 이 절의 마지막에 주어진다.

count() 멤버 함수는 인자로 주어진 키값과 일치하는 원소의 갯수를 리턴한다. map 의

경우에는 결과값이 항상 0 또는 1 인 반면에, multimap 의 경우에는 음수가 아닌 값이면

결과값이 될 수 있다. 단순히 콜렉션이 주어진 키로 인덱싱되는 원소를 포함하는지의 여부만을

확인하고 싶을 때는 find()의 결과값을 end-of-sequence 반복자와 비교하는 것보다는

count()를 사용하는 것이 쉬울 때가 더 많다.

   if (map_one.count(4))      cout << "contains a 4th element" << endl;

94

Page 95: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

9.2.7 원소 비교

인자가 필요없는 key_comp()와 value_comp() 멤버함수는 각각 key 타입 또는 value 타입의 원소를 비교할 때 사용되는 함수 객체를 반환한다. 비교할때 사용되는 값들이 콜렉션이

포함되어 있을 필요는 없으며, 이 함수들은 컨테이너에 아무런 영향을 미치지 않는다.

if (map_two.key_comp (i, j)) // map_two.key_comp()(i, j) ?   cout << "element i is less than j" << endl;

9.2.8 기타 map 연산

map과 multimap은 ordered 콜렉션이고, map에 대한 반복자는 pair를 반환하기 때문에, 13절과 14절에서 설명하는 함수들이 사용하기에 무의하거나 곤란한 것들이 많다. 그러나, for_each(), adjacent_find(), accumulate()들 각각은 나름대로 쓸모가 있다. 하지만, 인자로 제공되는 함수들은 키/값 pair를 인자로 취하는 것이라야 한다는 것을 반드시

기억해야 한다.

9.3 예제 프로그램

We present three example programs that illustrate the use of maps and multimaps. These are a telephone database, graphs, and a concordance.

9.3.1 전화 데이터베이스

A maintenance program for a simple telephone database is a good application for a map. The database is simply an indexed structure, where the name of the person or business (a string) is the key value, and the telephone number (a long) is the associated entry. We might write such a class as follows:

typedef map<string, long, less<string> > friendMap;typedef friendMap::value_type entry_type;

class telephoneDirectory {public:   void addEntry (string name, long number)   // add new entry to                                              // database      { database[name] = number; }         void remove (string name)   // remove entry from database

95

Page 96: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      { database.erase(name); }      void update (string name, long number)   // update entry      { remove(name); addEntry(name, number); }         void displayDatabase()   // display entire database      { for_each(database.begin(), database.end(), printEntry); }      void displayPrefix(int);   // display entries that match prefix      void displayByPrefix();   // display database sorted by prefix   private:   friendMap database;};

Simple operations on our database are directly implemented by map commands. Adding an element to the database is simply an insert, removing an element is an erase, and updating is a combination of the two. To print all the entries in the database we can use the for_each() algorithm, and apply the following simple utility routine to each entry:

void printEntry(const entry_type & entry)   { cout << entry.first << ":" << entry.second << endl; }

We will use a pair of slightly more complex operations to illustrate how a few of the algorithms described in Section 13 can be used with maps. Suppose we wanted to display all the phone numbers with a certain three digit initial prefix[1] . We will use the find_if() function (which is different from the find() member function in class map) to locate the first entry. Starting from this location, subsequent calls on find_if() will uncover each successive entry.

void telephoneDirectory::displayPrefix(int prefix){   cout << "Listing for prefix " << prefix << endl;   friendMap::iterator where;   where =       find_if (database.begin(), database.end(),             checkPrefix(prefix));   while (where != database.end()) {

96

Page 97: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      printEntry(*where);      where = find_if (++where, database.end(),             checkPrefix(prefix));      }   cout << "end of prefix listing" << endl;}

For the predicate to this operation, we require a boolean function that takes only a single argument (the pair representing a database entry), and tells us whether or not it is in the given prefix. There is no obvious candidate function, and in any case the test prefix is not being passed as an argument to the comparison function. The solution to this problem is to employ a technique that is commonly used with the standard library, defining the predicate function as an instance of a class, and storing the test predicate as an instance variable in the class, initialized when the class is constructed. The desired function is then defined as the function call operator for the class:

int prefix(const entry_type & entry)   { return entry.second / 10000; }

class checkPrefix {public:   checkPrefix (int p) : testPrefix(p) { }   int testPrefix;   bool operator () (const entry_type & entry)      { return prefix(entry) == testPrefix; }};

Our final example will be to display the directory sorted by prefix. It is not possible to alter the order of the maps themselves. So instead, we create a new map with the element types reversed, then copy the values into the new map, which will order the values by prefix. Once the new map is created, it is then printed.

typedef map<long, string, less<long> > sortedMap;typedef sortedMap::value_type sorted_entry_type;

void telephoneDirectory::displayByPrefix(){   cout << "Display by prefix" << endl;   sortedMap sortedData;

97

Page 98: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   friendMap::iterator itr;   for (itr = database.begin(); itr != database.end(); itr++)      sortedData.insert(sortedMap::value_type((*itr).second,             (*itr).first));   for_each(sortedData.begin(), sortedData.end(),            printSortedEntry);}

The function used to print the sorted entries is the following: void printSortedEntry (const sorted_entry_type & entry)       { cout << entry.first << ":" << entry.second << endl; }

9.3.2 그래프

A map whose elements are themselves maps are a natural representation for a directed graph. For example, suppose we use strings to encode the names of cities, and we wish to construct a map where the value associated with an edge is the distance between two connected cities. We could create such a graph as follows:

typedef map<string, int> stringVector;typedef map<string, stringVector> graph;

const string pendleton("Pendleton");   // define strings for                                        // city namesconst string pensacola("Pensacola");const string peoria("Peoria");const string phoenix("Phoenix");const string pierre("Pierre");const string pittsburgh("Pittsburgh");const string princeton("Princeton");const string pueblo("Pueblo");

graph cityMap;      // declare the graph that holds the map

cityMap[pendleton][phoenix] = 4;   // add edges to the graphcityMap[pendleton][pueblo] = 8;cityMap[pensacola][phoenix] = 5;cityMap[peoria][pittsburgh] = 5;cityMap[peoria][pueblo] = 3;cityMap[phoenix][peoria] = 4;

98

Page 99: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

cityMap[phoenix][pittsburgh] = 10;cityMap[phoenix][pueblo] = 3;cityMap[pierre][pendleton] = 2;cityMap[pittsburgh][pensacola] = 4;cityMap[princeton][pittsburgh] = 2;cityMap[pueblo][pierre] = 3;

The type stringVector is a map of integers indexed by strings. The type graph is, in effect, a two-dimensional sparse array, indexed by strings and holding integer values. A sequence of assignment statements initializes the graph.

A number of classic algorithms can be used to manipulate graphs represented in this form. One example is Dijkstra's shortest-path algorithm. Dijkstra's algorithm begins from a specific city given as an initial location. A priority_queue of distance/city pairs is then constructed, and initialized with the distance from the starting city to itself (namely, zero). The definition for the distance pair data type is as follows:

struct DistancePair {   unsigned int first;   string second;   DistancePair() : first(0) { }   DistancePair(unsigned int f, const string & s)      : first(f), second(s) { }};

bool operator < (const DistancePair & lhs, const DistancePair & rhs)   { return lhs.first < rhs.first; }

In the algorithm that follows, note how the conditional test is reversed on the priority queue, because at each step we wish to pull the smallest, and not the largest, value from the collection. On each iteration around the loop we pull a city from the queue. If we have not yet found a shorter path to the city, the current distance is recorded, and by examining the graph we can compute the distance from this city to each of its adjacent cities. This process continues until the priority queue becomes exhausted.

void shortestDistance(graph & cityMap, 

99

Page 100: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      const string & start, stringVector & distances){   // process a priority queue of distances to cities   priority_queue<DistancePair, vector<DistancePair>,                   greater<DistancePair> > que;   que.push(DistancePair(0, start));      while (! que.empty()) {      // pull nearest city from queue      int distance = que.top().first;      string city = que.top().second;      que.pop();              // if we haven't seen it already, process it      if (0 == distances.count(city)) {                // then add it to shortest distance map         distances[city] = distance;                // and put values into queue         const stringVector & cities = cityMap[city];         stringVector::const_iterator start = cities.begin();         stringVector::const_iterator stop = cities.end();         for (; start != stop; ++start)             que.push(DistancePair(distance + (*start).second,                                 (*start).first));         }      }}

Notice that this relatively simple algorithm makes use of vectors, maps, strings and priority_queues. priority_queues are described in greater detail in Section 11.

9.3.3 A ConcordanceA concordance is an alphabetical listing of words in a text, that shows the line numbers on which each word occurs. We develop a concordance to illustrate the use of the map and multimap container classes. The data values will be maintained in the concordance by a multimap, indexed by strings (the words) and will hold integers (the line numbers). A multimap is employed because the same word will often appear on multiple different lines; indeed, discovering such

100

Page 101: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

connections is one of the primary purposes of a concordance. Another possibility would have been to use a map and use a set of integer elements as the associated values.

class concordance {   typedef multimap<string, int less <string> > wordDictType;public:   void addWord (string, int);   void readText (istream &);   void printConcordance (ostream &);   private:   wordDictType wordMap;};

The creation of the concordance is divided into two steps: first the program generates the concordance (by reading lines from an input stream), and then the program prints the result on the output stream. This is reflected in the two member functions readText() and printConcordance(). The first of these, readText(), is written as follows:

void concordance::readText (istream & in){   string line;   for (int i = 1; getline(in, line, '\n'); i++) {      allLower(line);      list<string> words;      split (line, " ,.;:", words);      list<string>::iterator wptr;      for (wptr = words.begin(); wptr != words.end(); ++wptr)         addWord(*wptr, i);      }}

Lines are read from the input stream one by one. The text of the line is first converted into lower case, then the line is split into words, using the function split() described in Section 12.3. Each word is then entered into the concordance. The method used to enter a value into the concordance is as follows:

void concordance::addWord (string word, int line){

101

Page 102: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   // see if word occurs in list    // first get range of entries with same key   wordDictType::iterator low = wordMap.lower_bound(word);   wordDictType::iterator high = wordMap.upper_bound(word);   // loop over entries, see if any match current line   for ( ; low != high; ++low)      if ((*low).second == line)         return;   // didn't occur, add now   wordMap.insert(wordDictType::value_type(word, line));}

The major portion of addWord() is concerned with ensuring values are not duplicated in the word map if the same word occurs twice on the same line. To assure this, the range of values matching the key is examined, each value is tested, and if any match the line number then no insertion is performed. It is only if the loop terminates without discovering the line number that the new word/line number pair is inserted.

The final step is to print the concordance. This is performed in the following fashion:

void concordance::printConcordance (ostream & out){   string lastword("");   wordDictType::iterator pairPtr;   wordDictType::iterator stop = wordMap.end();   for (pairPtr = wordMap.begin(); pairPtr != stop; ++pairPtr)         // if word is same as previous, just print line number      if (lastword == (*pairPtr).first)         out << " " << (*pairPtr).second;      else {   // first entry of word         lastword = (*pairPtr).first;         cout << endl << lastword << ": " << (*pairPtr).second;         }   cout << endl; // terminate last line}

An iterator loop is used to cycle over the elements being maintained by the word

102

Page 103: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

list. Each new word generates a new line of output - thereafter line numbers appear separated by spaces. If, for example, the input was the text:  

It was the best of times,

it was the worst of times.

The output, from best to worst, would be:  

best: 1

it: 1 2

of: 1 2

the: 1 2

times: 1 2

was: 1 2

worst: 1

10장: stack과 queue

10.1 개요

많은 사람들이 stack 과 queue 데이터 추상에 대해서는 잘 이해하고 있다. 책상위에 놓은

서류철이나 찬장에 놓인 접시들이 stack 의 전형적인 예가 될 것이다. 이들 모두 맨 위쪽에 있는

것들은 접근하기가 가장 쉽다는 특징을 가진다. 콜렉션에 새로운 아이템을 추가하는 가장 쉬운

방법은 맨위에 올려 놓는 것이다. 이런식으로 하면, stack 에서 제거되는 아이템은 가장 최근에

103

Page 104: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

stack 에 추가된 아이템이 된다.

LIFO 와 FIFO  

한편, 일상생활에서 살펴볼 수 있는 queue 의 예로 은행에 선 줄이나 영화관 앞에 선 줄등을 들

수 있다. 여기서 사람이 새로 줄을 설 때처럼 삽입은 queue 의 뒤에서 이루어지고, 관객이

영화관에 들어가는 것처럼 아이템의 삭제는 queue 의 앞에서 이루어진다. queue 에서의

삭제순서는 stack 과는 반대이다. queue 에서는 삭제된 아이템이 queue 에 가장 오랫동안

있었던 원소이다.

표준 라이브러리에서는 stack 과 queue 가 모두 어댑터(adaptor)이고, 이들은 실질적으로

값을 저장하는데 사용하는 컨테이너를 바탕으로 만들어진다. stack 은 vector, list, deque으로부터 만들어지고, queue 는 list 나 deque 으로부터 만들어진다. stack 이나 queue 가

담고 있는 원소들은 '<'와 '=='연산자를 모두 인식해야 한다.

stack 이나 queue 는 반복자를 정의하고 있지 않기 때문에, 하나씩 값들을 제거해보지

않고서는 콜렉션의 원소들을 살펴볼 수 없다. 이들이 반복자를 구현하고 있지 않다는 사실은 12장과 13 장에 설명된 generic 알고리듬들 중 많은 것들을 사용할 수 없다는 것을 의미한다.

10.2 stack 데이터 추상(data abstraction)

stack은 전통적으로 다음 연산들을 구현한 객체로 정의되어 있다.  

empty() stack이 비었으면 참(true)을 반환

size() stack에 담긴 원소의 갯수를 반환

top() stack의 맨 위에 위치한 원소를 반환(지우지는 않는다)

push(newElement)

stack의 맨 위에 원소를 새로 추가

pop() stack의 맨 위에 위치한 원소를 삭제(반환하지는 않는다)

104

Page 105: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

10.2.1 Include 화일

front 원소를 접근하는 연산과 삭제하는 연산은 별도의 연산으로 제공된다는 점을 명심해야

한다. stack을 사용하는 프로그램은 stack을 밑받침하고 있는 컨테이너 타입(예를 들면, vector)에 대한 헤더 화일뿐만아니라 stack 헤더화일을 포함해야 한다.    #include <stack>   #include <vector>

Right Angle Brackets 

10.2.2 stack의 선언과 초기화

stack의 선언은 두개의 인자를 명시해야 한다. 하나는 stack이 담게될 원소의 타입이고, 나머지

하나는 원소들을 담는데 사용할 컨테이너이다. stack은 컨테이너로 vector나 deque를 가장

많이 쓰고, list도 컨테이너로 사용할 수 있다. deque를 사용하면 좀 더 빠르고, vector를

사용하면 크기가 좀 작아진다. 다음 예는 stack에 대한 선언이다.    stack<int, vector<int> > stackOne;   stack<double, deque<double> > stackTwo;   stack<Part*, list<Part* > > stackThree;   stack<Customer, list<Customer> > stackFour;마지막 예는 프로그래머가 정의한 Customer 타입의 stack을 생성한다.

10.2.3 예제 프로그램 - RPN 계산기

A classic application of a stack is in the implementation of calculator. Input to the calculator consists of a text string that represents an expression written in reverse polish notation (RPN). Operands, that is, integer constants, are pushed on a stack of values. As operators are encountered, the appropriate number of operands are popped off the stack, the operation is performed, and the result is pushed back on the stack.

We can divide the development of our stack simulation into two parts, a calculator engine and a calculator program. A calculator engine is concerned with the actual work involved in the simulation, but does not perform any input or output operations. The name is intended to suggest an analogy to a car engine, or a computer processor - the mechanism performs the actual work, but the user of the mechanism does not normally directly interact with it. Wrapped around this is the calculator program, which interacts with the user, and passes appropriate instructions to the calculator engine.

105

Page 106: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

We can use the following class definition for our calculator engine. Inside the class declaration we define an enumerated list of values to represent each of the possible operators that the calculator is prepared to accept. We have made two simplifying assumptions: all operands will be integer values, and we will handle only binary operators.

class calculatorEngine {public:   enum binaryOperator {plus, minus, times, divide};      int currentMemory ()           // return current top of stack      { return data.top(); }         void pushOperand (int value)   // push operand value on to stack      { data.push (value); }         void doOperator (binaryOperator);   // pop stack and perform                                       // operator   protected:   stack< int, vector<int> > data;};

Defensive Programming

The member function doOperator() performs the actual work. It pops values from the stack, performs the operation, then pushes the result back onto the stack.

void calculatorEngine::doOperator (binaryOperator theOp){   int right = data.top();   // read top element   data.pop();   // pop it from stack   int left = data.top();   // read next top element   data.pop();   // pop it from stack   switch (theOp) {

106

Page 107: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      case plus: data.push(left + right); break;      case minus: data.push(left - right); break;      case times: data.push(left * right); break;      case divide: data.push(left / right); break;   }}

The main program reads values in reverse polish notation, invoking the calculator engine to do the actual work:

int main() {   int intval;   calculatorEngine calc;   char c;      while (cin >> c)      switch (c) {         case '0': case '1': case '2': case '3': case '4':         case '5': case '6': case '7': case '8': case '9':            cin.putback(c);            cin >> intval;            calc.pushOperand(intval);            break;                  case '+':  calc.doOperator(calculatorEngine::plus);            break;            case '-':  calc.doOperator(calculatorEngine::minus);            break;            case '*':  calc.doOperator(calculatorEngine::times);            break;            case '/':  calc.doOperator(calculatorEngine::divide);            break;

         case 'p':  cout << calc.currentMemory() << endl;            break;

107

Page 108: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

            case 'q':  return; // quit program      }}

10.3 queue 데이터 추상(data abstraction)

queue는 일반적으로 다음 연산들을 구현한 객체로서 정의된다.  

empty() queue가 비었으면 참(true)을 반환

size() queue에 담긴 원소의 갯수를 반환

front() queue의 맨 앞에 위치한 원소를 반환(삭제는 않는다)

back() queue의 맨 뒤에 위치한 원소를 반환

push(newElement)

queue의 맨 뒤에 원소를 새로 추가

pop() queue의 맨 앞에 위치한 원소를 삭제(반환하지는 않는다)

10.3.1 Include 화일

queue의 맨앞에 위치한 원소를 접근하는 연산과 삭제하는 연산은 별도의 연산으로 수행된다는

점을 주목해야 한다. queue를 사용하는 프로그램은 queue의 컨테이너 타입(예를 들어, list)에 대한 헤더파일뿐만 아니라, queue 화일을 포함해야 한다.

   #include <queue>   #include <list>

10.3.2 queue의 선언과 초기화

queue에 대한 선언은 값을 담고 있는 컨테이너와 원소 타입을 명시해야 한다. queue는

컨테이너 타입으로 list나 deque를 가장 많이 사용한다. list를 사용하면 코드가 작아지는

반면에, deque를 사용하면 코드가 좀 빨라진다. 다음은 queue를 선언하는 예이다.    queue<int, list<int> > queueOne;   queue<double, deque<double> > queueTwo;   queue<Part*, list<Part* > > queueThree;   queue<Customer, list<Customer> > queueFour;

108

Page 109: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

마지막 예는 프로그래머가 정의한 Customer의 queue를 생성한다. stack 컨테이너에서 처럼, queue에 저장된 모든 객체는 '<'와 '==' 연산자를 이해해야 한다.

queue 는 반복자를 구현하지 않기 때문에, 13절과 14절에서 설명하는 generic 알고리듬들은

queue 에 적용할 수 없다.

10.3.3 예제 프로그램 - Bank Teller 시뮬레이션

Queues are often found in businesses, such as supermarkets or banks. Suppose you are the manager of a bank, and you need to determine how many tellers to have working during certain hours. You decide to create a computer simulation, basing your simulation on certain observed behavior. For example, you note that during peak hours there is a ninety percent chance that a customer will arrive every minute.

We create a simulation by first defining objects to represent both customers and tellers. For customers, the information we wish to know is the average amount of time they spend waiting in line. Thus, customer objects simply maintain two integer data fields: the time they arrive in line, and the time they will spend at the counter. The latter is a value randomly selected between 2 and 8. (See Section 2.2.5 for a discussion of the randomInteger() function.)

class Customer {public:   Customer (int at = 0) : arrival_Time(at),          processTime(2 + randomInteger(6)) {}   int arrival_Time;   int processTime;      bool done()      // are we done with our transaction?       { return --processTime < 0; }         operator < (const Customer & c)   // order by arrival time      { return arrival_Time < c.arrival_Time; }         operator == (const Customer & c)   // no two customers are alike      { return false; }

109

Page 110: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

};Because objects can only be stored in standard library containers if they can be compared for equality and ordering, it is necessary to define the < and == operators for customers. Customers can also tell us when they are done with their transactions.

Tellers are either busy servicing customers, or they are free. Thus, each teller value holds two data fields; a customer, and a boolean flag. Tellers define a member function to answer whether they are free or not, as well as a member function that is invoked when they start servicing a customer.

class Teller {public:   Teller() { free = true; }      bool isFree()   // are we free to service new customer?      { if (free) return true;        if (customer.done())           free = true;        return free;       }

   void addCustomer(Customer c)   // start serving new customer      {   customer = c;         free = false;      }

private:   bool free;   Customer customer;};

The main program is then a large loop, cycling once each simulated minute. Each minute a new customer is, with probability 0.9, entered into the queue of waiting customers. Each teller is polled, and if any are free they take the next customer from the queue. Counts are maintained of the number of customers serviced and the total time they spent in queue. From these two values we can determine,

110

Page 111: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

following the simulation, the average time a customer spent waiting in the line. int main() {   int numberOfTellers = 5;   int numberOfMinutes = 60;   double totalWait = 0;   int numberOfCustomers = 0;   vector<Teller> teller(numberOfTellers);   queue< Customer, deque<Customer> > line;      for (int time = 0; time < numberOfMinutes; time++) {      if (randomInteger(10) < 9)         line.push(Customer(time));      for (int i = 0; i < numberOfTellers; i++) {         if (teller[i].isFree() & ! line.empty()) {            Customer & frontCustomer = line.front();            numberOfCustomers++;            totalWait += (time - frontCustomer.arrival_Time);            teller[i].addCustomer(frontCustomer);            line.pop();            }         }      }   cout << "average wait:" <<          (totalWait / numberOfCustomers) << endl;}

By executing the program several times, using various values for the number of tellers, the manager can determine the smallest number of tellers that can service the customers while maintaining the average waiting time at an acceptable amount.

11장: priority_queue

11.1 priority_queue 데이터 추상(data abstraction)

111

Page 112: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

priority queue 는 값들의 콜렉션으로부터 가장 큰 값을 신속하게 찾거나 제거할 필요가

빈번하게 발생하는 상황에서 유용하게 사용할 수 있다. 일상에서 찾아볼 수 있는 priority queue 의 예로 아직 처리되지 않은 일들의 목록을 들 수 있다. '책상 정리'와 같은 일들은 그리

긴박한 사항이 아니므로, 임의로 연기할 수 있지만, '월요일까지 보고서 마치기'나 '기념일을

위한 꽃사기'와 같은 작업은 시간이 중요하므로 가장 급히 해결해야할 일들이다. 따라서, 중요도에 따라 수행할 작업들을 정리하고, 가장 급박한 것을 골라 수행한다.

A Queue That is Not a Queue

컴퓨터와 관련된 예로는 대기 프로세스의 리스트를 유지하는 운영체제에서 살펴볼 수 있다. 여기서 각 원소와 연결된 값은 작업의 우선순위이다. priority queue 에 프로세스들을

유지함으로써, 높은 순위를 가지는 작업들은 낮은 순위의 작업들보다 우선적으로 실행한다.

시뮬레이션 프로그램들은 앞으로 발생할 사건(event)들의 priority queue 를 사용한다. 시뮬레이션은 가상 '시계'를 유지하고, 각각의 사건(event)은 사건이 일어날 시간을 가지고

있다. 이렇게 사건들의 집합에서 가장 작은 시간값을 가지는 원소가 다음에 시뮬레이션될

원소이다. 이들외에도 priority queue 가 사용되는 예는 많이 있다.

11.1.1 Include 화일

priority queue를 사용하는 프로그램은 컨테이너 타입(예를 들어 vector)에 대한 헤더화일과

queue 헤더 화일을 포함시켜야 한다.    #include <queue>   #include <vector>

11.2 priority_queue 연산

priority queue는 T 타입의 원소를 가지고, 다음 5가지의 연산을 구현하는 데이터 구조이다.  

push(T)

컨테이너에 새로운 값을 추가

top() 컬렉션내의 가장 작은 원소의 레퍼런스를 리턴

112

Page 113: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

pop() 컬렉션에서 가장 작은 원소를 삭제

size() 컬렉션내에 포함된 원소들의 수를 리턴

empty()

컬렉션의 empty이면 true를 리턴

디폴트 less-than(<) 연산자를 사용하든, 템플릿 인자나 생성자의 인자로 지정한 비교 함수를

사용하든지간에, T 타입의 원소들은 서로 비교가 가능해야 한다. 두번째 형태는 나중에 뒤에서

설명할 예제 프로그램에서 설명하기로 한다. 표준 라이브러리의 다른 컨테이너와 마찬가지로, priority_queue 에도 두개의 생성자가 있다. 기본 생성자는 인자없이 또는 생략가능한

비교연산자가 있으면 된다. 또다른 생성자는 한쌍의 반복자를 취하여 다른 컨테이너의 값들로

초기화한다. 여기서도, 생략가능한 세번째 인자로 비교함수를 사용한다.

priority queue 데이터 타입은 컨테니어 클래스를 바탕으로 구성되며, 이 컨테이너가 실제로

priority queue 의 값들을 담게 된다. priority_queue 를 구성하는데 사용되는 컨테이너는

vector 와 deque 가 있다.

11.2.1 priority_queue의 선언과 초기화

다음은 여러가지 priority_queue 를 선언하는 예이다.

priority_queue< int, vector<int> > queue_one;priority_queue< int, vector<int>, greater<int> > queue_two;priority_queue< double, deque<double> >       queue_three(aList.begin(), aList.end());priority_queue< eventStruct, vector<eventStruct> >       queue_four(eventComparison);priority_queue< eventStruct, deque<eventStruct> >       queue_five(aVector.begin(), aVector.end(), eventComparison);

vector로 구축된 queue는 좀 더 작은 반면, 만약 수행중에 queue내의 원소의 갯수가 큰폭으로

변하는 경우에는 deque로 구축된 queue가 좀 더 빨라진다. 그러나, 이들간의 차이는

경미하며, 어느 형태든 대부분의 상황에서 잘 작동한다.

priority queue 데이터 구조는 반복자를 만들 줄 모르기 때문에, 13 장 에서 설명하는

알고리듬의 일부만이 priority queue 와 같이 사용될 수 있다. 값들을 반복하는 대신에, priority queue 를 사용하는 전형적인 알고리듬은 콜렉션이 빌때까지(empty()를 사용하여

113

Page 114: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

검사) 데이터 구조로부터 값들을 반복적으로 끌어내는 (top()과 pop() 연산을 사용) 루프를

구성한다. 다음 절의 예제 프로그램에서 이의 사용에 관해 설명할 것이다.

priority_queue 는 내부적으로 heap 이라고 불리는 자료구조를 만들어 구현하며, 개념적으로 봤을 때 heap 은 각 노드의 값이 자식 노드들의 값보다 작거나 같은 속성을 가지는

이진 트리이다.

11.3 응용 - Event-Driven 시뮬레이션

An extended example will illustrate the use of priority queues. The example illustrates one of the more common uses for priority queues, which is to support the construction of a simulation model.

A discrete event-driven simulation is a popular simulation technique. Objects in the simulation model objects in the real world, and are programmed to react as much as possible as the real objects would react. A priority queue is used to store a representation of "events" that are waiting to happen. This queue is stored in order, based on the time the event should occur, so the smallest element will always be the next event to be modeled. As an event occurs, it can spawn other events. These subsequent events are placed into the queue as well. Execution continues until all events have been processed.

Finding Smallest Elements

Events can be represented as subclasses of a base class, which we will call event. The base class simply records the time at which the event will take place. A pure virtual function named processEvent will be invoked to execute the event.

class event {public:   event (unsigned int t) : time(t) { }   const unsigned int time;   virtual void processEvent() = 0;};

114

Page 115: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

The simulation queue will need to maintain a collection of different types of events. Each different form of event will be represented by a different subclass of class event. Not all events will have the same exact type, although they will all be subclasses of class event. (This is sometimes called a heterogeneous collection.) For this reason the collection must store pointers to events, instead of the events themselves. (In theory one could store references, instead of pointers, however the standard library containers cannot hold references).

Since comparison of pointers cannot be specialized on the basis of the pointer types, we must instead define a new comparison function for pointers to events. In the standard library this is accomplished by defining a new structure, the sole purpose of which is to define the function invocation operator (the () operator) in the appropriate fashion. Since in this particular example we wish to use the priority queue to return the smallest element each time, rather than the largest, the order of the comparison is reversed, as follows:

struct eventComparison {   bool operator () (event * left, event * right) const      { return left->time > right->time; }};

We are now ready to define the class simulation, which provides the structure for the simulation activities. The class simulation provides two functions. The first is used to insert a new event into the queue, while the second runs the simulation. A data field is also provided to hold the current simulation "time."

Storing Pointers versus Storing Values

class simulation {public:   simulation () : eventQueue(), time(0) { }

   void scheduleEvent (event * newEvent)      { eventQueue.push (newEvent); }

   void run();

115

Page 116: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   unsigned int time;

protected:   priority_queue<event *, vector<event *>, eventComparison> eventQueue;};

Notice the declaration of the priority queue used to hold the pending events. In this case we are using a vector as the underlying container. We could just as easily have used a deque.

The heart of the simulation is the member function run(), which defines the event loop. This procedure makes use of three of the five priority queue operations, namely top(), pop(), and empty(). It is implemented as follows:

void simulation::run(){   while (! eventQueue.empty()) {      event * nextEvent = eventQueue.top();      eventQueue.pop();      time = nextEvent->time;      nextEvent->processEvent();      delete nextEvent;   // free memory used by event      }}

11.3.1 아이스크림 가게 시뮬레이션

To illustrate the use of our simulation framework, this example program gives a simple simulation of an ice cream store. Such a simulation might be used, for example, to determine the optimal number of chairs that should be provided, based on assumptions such as the frequency that customers will arrive, the length of time they will stay, and so on.

Our store simulation will be based around a subclass of class simulation, defined as follows:

class storeSimulation : public simulation {public:

116

Page 117: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   storeSimulation()      : freeChairs(35), profit(0.0), simulation() { }

   bool canSeat (unsigned int numberOfPeople);   void order(unsigned int numberOfScoops);   void leave(unsigned int numberOfPeople);

private:   unsigned int freeChairs;   double profit;} theSimulation;

There are three basic activities associated with the store. These are arrival, ordering and eating, and leaving. This is reflected not only in the three member functions defined in the simulation class, but in three separate subclasses of event.

The member functions associated with the store simply record the activities taking place, producing a log that can later be studied to evaluate the simulation.

bool storeSimulation::canSeat (unsigned int numberOfPeople)   // if sufficient room, then seat customers{   cout << "Time: " << time;   cout << " group of " << numberOfPeople << " customers arrives";   if (numberOfPeople < freeChairs) {      cout << " is seated" << endl;      freeChairs -= numberOfPeople;      return true;      }   else {      cout << " no room, they leave" << endl;      return false;      }}

void storeSimulation::order (unsigned int numberOfScoops)   // serve icecream, compute profits

117

Page 118: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

{   cout << "Time: " << time;   cout << " serviced order for " << numberOfScoops << endl;   profit += 0.35 * numberOfScoops;}

void storeSimulation::leave (unsigned int numberOfPeople)   // people leave, free up chairs{   cout << "Time: " << time;   cout << " group of size " << numberOfPeople <<          " leaves" << endl;   freeChairs += numberOfPeople;}

As we noted already, each activity is matched by a subclass of event. Each subclass of event includes an integer data field, which represents the size of a group of customers. The arrival event occurs when a group enters. When executed, the arrival event creates and installs a new instance of order event. The function randomInteger() (see Section 2.2.5) is used to compute a random integer between 1 and the argument value.

class arriveEvent : public event {public:   arriveEvent (unsigned int time, unsigned int groupSize)      : event(time), size(groupSize) { }   virtual void processEvent ();private:   unsigned int size;};

void arriveEvent::processEvent(){               // see if everybody can be seated   if (theSimulation.canSeat(size))      theSimulation.scheduleEvent         (new orderEvent(time + 1 + randomInteger(4), size));

118

Page 119: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

}An order event similarly spawns a leave event.

class orderEvent : public event {public:   orderEvent (unsigned int time, unsigned int groupSize)      : event(time), size(groupSize) { }   virtual void processEvent ();private:   unsigned int size;};

void orderEvent::processEvent(){               // each person orders some number of scoops   for (int i = 0; i < size; i++)      theSimulation.order(1 + rand(3));   theSimulation.scheduleEvent      (new leaveEvent(time + 1 + randomInteger(10), size));};

Finally, leave events free up chairs, but do not spawn any new events. class leaveEvent : public event {public:   leaveEvent (unsigned int time, unsigned int groupSize)      : event(time), size(groupSize) { }   virtual void processEvent ();private:   unsigned int size;};

void leaveEvent::processEvent (){               // leave and free up chairs   theSimulation.leave(size);}

To run the simulation we simply create some number of initial events (say, 30 minutes worth), then invoke the run() member function.

int main() {   // load queue with some number of initial events

119

Page 120: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   unsigned int t = 0;   while (t < 30) {      t += rand(6);      theSimulation.scheduleEvent(         new arriveEvent(t, 1 + randomInteger(4)));      }

   // then run simulation and print profits   theSimulation.run();   cout << "Total profits " << theSimulation.profit << endl;}

12장: string

12.1 string 추상(abstraction)

string 은 인덱싱이 가능한 문자들의 시퀀스이다. 비록, string 이 vector 의 서브클래스는

아니지만, 5 장 에서 설명한 대부분의 vector 연산들을 string 에 적용할 수 있다. 게다가, string은 vector 연산뿐만 아니라 유용하고 강력한 고수준 연산들을 추가로 제공한다.

표준 라이브러리의 string 은 실제로는 basic_string 템플릿 클래스이다. 템플릿 인자는

string 컨테이너를 구성하는 문자의 타입을 나타낸다. 이렇게 함으로써, 표준 라이브러리는

일반적으로 많이 쓰이는 8 비트 아스키 문자들뿐만 아니라 16 비트 광폭 문자(wide character)들과 같은 것들도 다룰 수 있는 기능을 제공한다. string 과 wstring(광폭 string) 데이터

타입은 다음과 같이 typedef 를 거친 basic_string 들이다.

   typedef basic_string<char> string;   typedef basic_string<wchar_t> wstring;

▶ string과 wstring

이 이후로는 string에 대해서만 설명하겠지만, 여기서 설명하는 모든 연산들은 wstring에도

똑같이 적용된다.

120

Page 121: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

방금 말했듯이, string 은 여러면에서 문자 vector 와 비슷하다. vector 와 마찬가지로, string에도 두가지 종류의 사이즈가 있다. 하나는 string 이 실제로 담고 있는 문자들의 갯수를

나타낸다. 다른 하나는 버퍼를 새로 할당하지 않고 string 에 저장할 수 있는 문자의 최대갯수를

나타내며, capacity 라고 칭한다. vector 에서처럼, string 의 capacity 도 동적으로 변하는

양이다. string 연산을 수행하는 도중에 string 에 저장된 문자의 갯수가 string 의 capacity 를

초과하게 되면, 내부적으로 버퍼를 새로 할당하여 string 의 capacity 를 증가시킨다. 이때 모든

과정들은 겉으로 드러나지 않으며, 프로그래머가 관여할 필요도 없다.

12.1.1 Include 화일

string을 사용하는 프로그램들은 string 헤더 화일을 포함해야 한다.    #include <string>

12.2 string 연산

이 절에서는 string을 생성하거나 조작하는데 사용되는 표준 라이브러리 연산들을 살펴본다. 12.2.1 string의 선언과 초기화

string을 선언하는 가장 간단한 형태는 단순히 변수 이름만을 지정하거나, string의 초기값을

같이 제공하는 방법이 있다. 이 형태는 9.3.2 절 에 주어진 그래프 예제에서 가장 많이 사용한

방법이다. 복사 생성자는 다른 string 값을 초기값으로 사용한다.    string s1;                       // 변수 이름만 지정

   string s2("a string");           // 초기값 제공

   string s3 = "initial value";     // 초기값 제공

   string s4(s3);                   // 복사 생성자 이용

이러한 경우에는 capacity는 처음에는 저장된 문자의 갯수와 정확히 같다. 명시적으로 초기

capacity를 지정할 수 있는 생성자가 있으며, 또한 capacity 값을 초기화하고 같은 문자를

여러개 포함하도록 string을 초기화할 수 있다.    string s6("small value", 100);   // 11 개의 문자를 포함, 100 개까지 담을 수 있음

   string s7(10, '\n');             // 10개의 개행문자로 초기화

▶ 반복자를 이용한 초기화

한쌍의 반복자를 사용하여 컨테이너를 초기화할 수 있다는 것은 컨테이너를 선언할 때 사용한

템플릿 인자와는 무관한 별도의 템플릿 인자를 사용하여 템플릿 멤버 함수를 선언할 수 있는

컴파일러가 지원되어야 한다는 것을 의미한다.

121

Page 122: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

마지막으로, 다른 컨테이너 클래스에서처럼 string 도 한쌍의 반복자를 사용하여 초기화할 수

있다. 반복자가 가리키는 시퀀스는 적절한 타입의 원소들을 가지고 있어야 한다.

   string s8(aList.begin(), aList.end());12.2.2 사이즈(size)와 용량(capacity) 고치기

vector 데이터 타입과 마찬가지로, string 의 현재 사이즈는 size() 멤버 함수를 통해 알아볼

수 있고, 현재 capacity 는 capacity()를 사용한다. capacity 는 reserve() 멤버 함수를

사용하여 변경할 수 있고, 필요하면 용적을 조절하여 string 이 인자로 명시한 만큼의 문자를

담도록 할 수 있다. max_size() 멤버 함수는 할당할 수 있는 가장 큰 string 사이즈를

반환한다. 대개 이값은 사용가능한 메모리 양에 의해 제한된다.

   cout << s6.size() << endl;   cout << s6.capacity() << endl;   s6.reserve(200);                    // capacity 를 200 으로 바꾼다

   cout << s6.capacity() << endl;   cout << s6.max_size() << endl;

length() 멤버함수는 size() 멤버함수와 동일하다. resize() 멤버 함수는 string 의

사이즈를 변경할 수 있고, 이 과정에서 string 의 끝부분이 잘릴 수도 있고, 새로 문자들이

첨가될 수도 있다. resize()의 두번째 인자는 새로 추가되는 문자를 명시할 수 있으며, 생략도

가능하다.

   s7.resize(15, '\t');                // 맨 끝에 탭 문자들을 추가하여 사이즈를 늘린다

   cout << s7.length() << endl;        // 사이즈는 이제 15

empty() 멤버 함수는 string 에 문자가 없을 때 참이 되며, size()를 0 이랑 비교하는 것보다

일반적으로 빠르다.

   if (s7.empty())       cout << "string is empty" << endl;

12.2.3 대입(assign), 덧붙이기(append), 교환(swap)string 변수에 다른 string을 대입할 수가 있고, C 스타일의 문자 배열이나 개별 문자도 대입이

가능하다.    s1 = s2;   s2 = "a new value";   s3 = 'x';

122

Page 123: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

+= 연산자도 이들 세가지 형태의 인자들과 함께 사용이 가능하며, 우변값이 좌변의 끝에

덧붙인다.    s3 += "yz";                  // s3 는 이제 "xyz"

더 일반적인 형태의 함수로 assign()과 append() 멤버 함수가 있다. 이들은 첫번째 인자로

주어진 string 의 일부분만 append 되도록 하는데, 두번째 인자로 명시된 위치에서 세번째

인자로 주어진 갯수만큼을 append 한다.

   s4.assign(s2, 0, 3);         // s2 의 처음 3 문자를 s4 에 대입한다

   s4.append(s5, 2, 3);         // 2, 3, 4 번 위치에 있는 문자들을 덧붙인다

덧셈 연산자 +는 두개의 string을 연결할 때 사용한다. + 연산자는 왼쪽 인자의 복사본을

만들고, 이 복사본에 오른쪽 인자를 덧붙인다.    cout << (s2 + s3) << endl;   // s2 와 s3 을 덧붙인 것을 출력

표준 라이브러리의 모든 컨테이너들과 마찬가지로, 두 string 의 내용은 swap()을 사용하여

서로 바꿀 수 있다.

   s5.swap(s4);                 // s4 와 s5 를 바꾼다

12.2.4 문자 접근

string 에 담긴 개별적인 문자들은 첨자 연산을 사용하여 접근하거나 대입한다. at() 멤버함수는 인자로 주어진 값이 size()보다 크거나 같으면 out_of_range 예외(exception)가

throw 된다는 것을 제외하곤 첨자연산과 동일하다.

   cout << s4[2] << endl;        // s4 의 2 번 위치의 문자를 출력

   s4[2] = 'x';                  // 2 번 위치의 문자를 변경

   cout << s4.at(2) << endl;     // 변경된 값을 출력

c_str() 멤버 함수는 널문자로 끝나는 문자 배열을 리턴하고, 이 배열에 담긴 문자들은 string에 담긴 문자들과 동일하다. 이는 예전의 C 스타일의 문자배열을 가리키는 포인터를 필요로하는

함수와 함께 string 을 사용할 수 있도록 해준다. 게다가, c_str()가 리턴하는 값은, 재할당이

일어날 수 있는 연산(append()나 insert())을 수행한 뒤에, 유효하지 않을 수가 있다. data() 멤버 함수는 string 을 구성하는 문자 버퍼에 대한 포인터를 반환한다.

   char d[256];   strcpy(d, s4.c_str());        // s4 를 배열 d 에 복사

123

Page 124: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

12.2.5 반복자

begin()과 end() 멤버 함수는 string의 처음과 끝을 가리키는 임의접근 반복자를 리턴한다. 반복자가 가리키는 값들은 각각의 문자들이다. rbegin()과 rend()는 역방향 반복자(?)를

리턴한다. ▶ 반복자의 무효화

append나 insert와 같이 내부 string 버퍼의 재할당을 초래할 수 있는 연산을 수행한 뒤에는

반복자가 가리키는 값이 유효하다고 보장할 수 없다. 12.2.6 삽입, 삭제, 치환(replacement)

string 의 멤버 함수인 insert()와 erase()는 vector 함수 insert()와 erase()와

비슷하다. vector 와 같이 반복자들을 인자로 넘겨주고, 이들 인자가 가리키는 구간을 삽입하고

삭제한다. replace() 함수는 erase()와 insert()를 합쳐 놓은 것과 같으며, 특정 구간을

새로운 값으로 치환한다.

   s2.insert(s2.begin()+2, aList.begin(), aList.end());   s2.erase(s2.begin()+3, s2.begin()+5);   s2.replace(s2.begin()+3, s2.begin()+6, s3.begin(), s3.end());추가로, 반복자로 구현하지 않은 함수들도 있는데, insert 멤버 함수는 위치와 string을 인자로

취하여, string을 주어진 위치에 삽입한다. erase() 함수는 두개의 정수 인자로 위치와 길이를

취하고 명시된 문자들을 삭제한다. replace() 함수는 string과 길이(생략가능)와 더불어

두개의 정수 인자를 취하여, 명시한 구간을 string으로 대치한다. 만약에 길이가 명시적으로

주어지면, string의 앞쪽부터 주어진 길이만큼에 해당하는 string으로 대치한다.    s3.insert(3, "abc");      // 3 번 위치 뒤에 "abc" 삽입

   s3.erase(4, 2);           // 4, 5 번 위치 삭제

   s3.replace(4, 2, "pqr");  // 4, 5 번 위치를 "pqr"로 치환

12.2.7 복사와 서브string

copy() 멤버 함수는 substring 을 만들어 이를 첫번째 인자로 주어진 곳에 대입한다. substring 을 가리키는 구간은 처음 위치만 명시하거나, 위치와 길이를 명시한다.

   s3.copy(s4, 2);           // s3 의 2 번 위치부터 끝까지를 s4 에 복사

   s5.copy(s4, 2, 3);        // s5 의 2, 3, 4 번 위치를 s4 에 복사

substr() 멤버 함수는 현재 string 의 일부분을 나타내는 string 을 리턴한다. 구간은

처음위치로만 명시하거나, 위치와 길이로 명시한다.

124

Page 125: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   cout << s4.substr(3) << endl;       // 3 번 위치부터 끝까지 출력

   cout << s4.substr(3, 2) << endl;    // 3, 4 번 위치 출력

12.2.8 비교

▶ string의 비교

비록 compare() 함수가 있긴 하지만, compare() 멤버함수를 직접 호출하는 경우는 거의

없을 것이다. 대신에, 비교 연산자를 사용하는 것이 더 일반적이다. 어차피 이 비교 연산자에서도

compare()를 사용한다.

compare() 멤버 함수는 수신자와 인자 string 간의 사전적인 비교를 수행하는데 사용된다. 다른 함수와 비슷하게, 처음위치만 명시하거나 처음위치와 길이를 같이 명시할 수 있다. 사전식

배열을에 대해서는 13.6.5 절 을 참고한다. 수진자가 인자보다 사전배열상으로 더 작으면 음수를

리턴하고, 같으면 0 을, 크면 양수를 리턴한다.

관계 연산자나 상등연산들(<, <=, >=, >, ==, !=)은 모두 비교 멤버 함수를 사용하여

정의된다. 임의의 두 string 간이나, string 과 C 스타일의 character literal(?)간에도 비교가

가능하다.

12.2.9 검색

find() 멤버 함수는 현 string 에서 인자로 주어진 string 이 처음으로 나타나는 곳을 찾아낸다. 정수값을 두번째 인자로 하여 검색의 시작점을 명시할 수도 있다(생략 가능하다). 함수가

일치부분을 찾으면, 현 string 에서의 일치부분의 시작위치를 리턴한다. 못찾으면, string 의

합당한 첨자를 벗어나는 값을 리턴한다. rfind() 함수가 비슷하지만, 끝에서부터 거꾸로

스캔한다.

   s1 = "mississippi";   cout << s1.find("ss") << endl;              // 2 를 리턴

   cout << s1.find("ss", 3) << endl;           // 5 를 리턴

   cout << s1.rfind("ss") << endl;             // 5 를 리턴

   cout << s1.rfind("ss", 4) << endl;          // 2 를 리턴

find_first_of(), find_last_of(), find_first_not_of(), find_last_not_of()는

인자로 주어진 string 을 일반적인 문자열로 보지 않고 문자의 집합으로 본다. 다른 함수들처럼, 하나 또는 두개의 정수 인자들(생략가능)을 사용하여 문자열의 서브스트링을 명시할 수 있다. 이들 함수는 인자로 주어진 문자집합에 속하는(또는 속하지 않는) 첫번째(또는 마지막) 문자를

125

Page 126: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

찾는다. 원하는 문자를 찾게 되면 주어진 문자의 위치를 리턴하고, 찾지 못하면 첨자의

유효범위를 벗어나는 값을 리턴한다.

   i = s2.find_first_of("aeiou");            // 첫번째 모음 찾기

   j = s2.find_first_not_of("aeiou", i);     // 다음번 자음 찾기

12.3 예제 함수 - 텍스트 라인을 단어들로 쪼개기

이 절에서는 텍스트 한 줄을 여러개의 단어들로 쪼개는 함수를 하나 정의한다. 이를 통해, 앞에서 설명한 string 함수들을 응용해 보자. 이 함수는 9.3.3 절 의 concordance 예제에서도

사용했던 함수이다.

이 함수는 세개의 인자가 필요하다. 처음 두개는 string 인데, 첫번째 인자는 텍스트 라인 한줄을

담고 있고 두번째 인자는 단어들을 구분하는 문자인 구분자들을 담고 있다. 세번째 인자는

string 의 list 로, 라인으로부터 추출한 단어들이 담기게 된다. 즉, 세번째 인자가 이 함수의 수행

결과를 담게 된다.

void split(string& text, string& separators, list<string>& words){   int n = text.length();   int start, stop;

   start = text.find_first_not_of(separators);   while ((start >= 0) && (start < n)) {      stop = text.find_first_of(separators, start);      if ((stop < 0) || (stop > n)) stop = n;      words.push_back(text.substr(start, stop - start));      start = text.find_first_not_of(separators, stop+1);   }}이 함수는 먼저 구분자가 아닌 첫번째 문자를 찾는 일부터 시작한다. while 루프내에서는 그

뒤에 나타나는 구분자에 속하는 문자를 찾는다. 만약 구분자를 찾지 못하면 string의 끝을

사용한다. 이들 둘간의 차가 한 단어를 구성하고, substring 연산으로 복사하여 세번째 인자로

삽입한다. 그리고나서, 다음 단어의 첫번째 위치를 찾고, 다음번 루프를 반복한다. 'start' 변수가 string의 범위를 벗어나면 수행을 마친다.

126

Page 127: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13장: generic 알고리듬

13.1 개요

13 장 과 14 장에서는 표준 라이브러리가 제공하는 generic 알고리듬에 대해 설명한다. 아래

표는 13 장 에서 설명할 알고리듬의 이름과 용도를 요약해 놓은 것이며, 알고리듬들을 용도별로 분류하였다.  

이름 용도

초기화 알고리듬 - 13.2 절

fill 시퀀스를 초기값으로 채우기

fill_n n개의 자리를 초기값으로 채우기

copy 시퀀스를 다른 시퀀스에 복사하기

copy_backward 시퀀스를 다른 시퀀스에 복사하기

generate 생성기(generator)를 사용하여 시퀀스를 초기화하기

generate_n 생성기(generator)를 사용하여 n개의 자리를 초기화하기

swap_ranges 두 병렬 시퀀스의 내용 뒤바꾸기

검색 알고리듬 - 13.3 절

find 인자값과 일치하는 원소 찾기

find_if 조건을 만족하는 원소 찾기

adjacent_find 연달아 중복된 원소 찾기

find_first_of 시퀀스내에서 다른 시퀀스에 속하는 멤버중 가장 먼저 발견되는

것 착지

find_end 시퀀스내에서 서브시퀀스의 마지막 발생 찾기

search 시퀀스내에서 서브 시퀀스 찾기

127

Page 128: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

max_element 시퀀스에서 최대값 찾기

min_element 시퀀스에서 최소값 찾기

mismatch 두 시퀀스를 비교하여 불일치되는 곳 찾기

in-place 변환 - 13.4 절

reverse 시퀀스의 원소 뒤집기

replace 특정값들을 다른 값으로 치환

replace_if 조건을 만족하는 원소들을 치환

rotate 한 점을 중심으로 원소들을 순환

partition 원소들을 두그룹으로 쪼개기

stable_partition 순서를 그대로 유지하며 쪼개기

next_permutation 다음 순열 생성하기

prev_permutation 이전 순열 생성하기

inplace_merge 두개의 이웃한 시퀀스를 하나로 합치기

random_shuffle 시퀀스 내의 원소들을 임의로 재배치하기

삭제 알고리듬 - 13.5 절

remove 조건을 만족하는 원소 삭제

unique 중복되는 원소들 중 첫번째 것만 남기고 모두 삭제

스칼라 생성 알고리듬 - 13.6 절

count 값과 일치하는 원소들을 카운트

count_if 조건을 만족하는 원소들을 카운트

accumulate 시퀀스로부터 스칼라값 얻기

inner_product 두 시퀀스의 내적

equal 두 시퀀스의 상등 검사

lexicographical_compare

두 시퀀스를 비교

128

Page 129: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

시퀀스 생성 알고리듬 - 13.7 절

transform 각 원소들을 변환

partial_sum 부분합들의 시퀀스를 생성

adjacent_difference 인접차들의 시퀀스를 생성

기타 연산 - 13.8 절

for_each 콜렉션내의 원소 각각에 대해 함수를 적용

이 장에서는 각각의 알고리듬을 설명할 때, 간단한 예제를 들어 설명할 것이다.

13.1.1 Include 화일

generic 알고리듬을 사용하려면 적절한 헤더 화일을 포함시켜야 한다. 대다수의 함수가

algorithm 헤더화일에 정의되어 있다. accumulate(), inner_product(), partial_sum(), adjacent_difference()는 numeric 헤더화일에 정의되어 있다.    #include <algorithm>   #include <numeric>

13.2 초기화 알고리듬

가장 먼저 다루게 될 알고리듬은 새로 생성한 시퀀스를 특정값으로 초기화할 때 사용되는

것들이다. 적어도 다음 세개의 알고리듬은 반드시 기억하자. fill()     (13.2.1 절 ) copy()     (13.2.2 절 ) generate() (13.2.3 절 )

13.2.1 시퀀스를 초기값으로 채우기

fill()과 fill_n() 알고리듬은 시퀀스를 정해진 값으로 초기화하거나 재초기화하는데 사용된다. 프로토타입은 다음과 같다.

void fill(ForwardIterator first, ForwardIterator last, const T&);void fill_n(OutputIterator, Size, const T&);

129

Page 130: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

▶ 초기화 알고리듬들간의 차이점

모든 초기화 알고리듬은 컨테이너내에 담겨 있던 기존 원소들을 덮어쓴다. 이들 초기화

알고리듬들은 초기화할 때 사용되는 값들을 어디서 가져오느냐에 따라 차이가 난다. fill() 알고리듬은 한개의 값으로 여러 원소들을 초기화하고, copy() 알고리듬은 다른 컨테이너에

담긴 값들로 초기화하고, generate() 알고리듬은 함수를 호출하여 얻어낸 값으로 초기화한다.

다음 예제 프로그램은 이들 알고리듬의 다양한 사용법을 보여주고 있다.

void fill_example(){   // 예 1, 배열을 초기화한다.   char buffer[100], * bufferp = buffer;   fill(bufferp, bufferp + 100, '\0');   fill_n(bufferp, 10, 'x');

   // 예 2, list 를 초기화한 뒤, 추가로 덧붙인다.   list<string> aList(5, "nothing");   fill_n(inserter(aList, aList.begin()), 10, "empty");

   // 예 3, list 내의 값들을 덮어쓴다.   fill(aList.begin(), aList.end(), "full");

   // 예 4, 콜렉션의 일부를 채운다.   vector<int> iVec(10);   generate(iVec.begin(), iVec.end(), iotaGen(1));   vector<int>::iterator & seven =          find(iVec.begin(), iVec.end(), 7);   fill(iVec.begin(), seven, 0);}

예1에서는 문자 배열을 선언하였다. fill() 알고리듬을 호출하여 이 배열을 모두 널문자들로

초기화한다. 이중 처음 10자리를 문자 'x'로 치환하기 위해서 fill_n() 알고리듬을 사용한다. fill() 알고리듬은 시작 반복자와 past-the-end 반복자를 인자로 요구하지만, fill_n() 알고리듬은 시작 반복자와 초기화할 갯수를 인자로 사용한다.

130

Page 131: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

예 2 는 삽입 반복자(2.4절 참고)와 fill_n() 알고리듬을 사용하여 list 와 같은 가변길이

컨테이너를 초기화하고 있다. 위 예에서 list 는 처음에 5 개의 원소를 가지고 있고, 이들 모두

"nothing"이라는 문자열로 초기화되어 있다. 그 다음, fill_n()을 호출하여 문자열

"empty"를 10 개 삽입하였다. 결과적으로 list 는 15 개의 원소를 가지게 된다.

예 3 과 예 4 는 fill() 사용하여 컨테이너의 값들을 변화시키는 법을 설명하고 있다. 예 3 은 예 2에서 생성한 list 의 모든 원소들(총 15 개)을 문자열 "full"로 덮어쓴다.

예 4 는 list 의 일부만 덮어쓰는 것을 보여준다. 우선 generate() 알고리듬과 iotaGen 함수

객체(3.3절 참고)를 사용하여, 1 2 3 ... 10 으로 vector 를 초기화한다. 그리고나서, find() 알고리듬(13.3.1절)을 사용하여 원소 7 를 찾아, vector 타입의 반복자에 해당 위치를

저장한다. 그 다음에, fill()을 호출하여 처음부터 원소 7 바로 앞까지를 모두 0 으로 치환한다. 이 결과 vector 는 처음 여섯개는 0 을, 그 다음에는 7, 8, 9, 10 의 값을 가지게 된다.

fill()과 fill_n() 알고리듬은 표준 라이브러리가 제공하는 모든 컨테이너 클래스에 사용할 수

있다. set 과 같은 정렬 컨테이너는 삽입 반복자와 함께 사용해야 한다.

13.2.2 시퀀스를 다른 시퀀스에 복사하기

▶ 여러개의 복사본을 덧붙이는 방법

copy() 알고리듬의 리턴값은 복사된 시퀀스의 끝을 가리키는 포인터이다. 값을 여러개

덧붙이려면, copy() 연산의 리턴값을 다음 copy()의 시작 반복자로 사용하면 된다.

copy()와 copy_backward() 알고리듬은 기능이 다양하여 여러 용도로 사용된다. 아마

표준 라이브러리에서 가장 많이 사용되는 알고리듬일 것이다. 이들의 프로토타입은 다음과

같다.

OutputIterator copy(InputIterator first, InputIterator last,         OutputIterator result);

BidirectionalIterator copy_backward    (BidirectionalIterator first, BidirectionalIterator last,    BidirectionalIterator result);

복사 알고리듬은 다음과 같은 용도에 주로 사용된다. 전체 시퀀스를 새로운 시퀀스에 복사한다. 현 시퀀스의 부시퀀스를 생성한다.

131

Page 132: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

시퀀스에 원소를 추가한다. 입력에서 출력으로 시퀀스를 복사한다. 시퀀스를 다른 형태로 바꾼다.

다음 예제 프로그램은 이 두 알고리듬을 설명하고 있다. void copy_example(){   char *source = "reprise";   char *surpass = "surpass";   char buffer[120], *bufferp = buffer;

   // 예 1, 그냥 단순한 복사

   copy(source, source + strlen(source) + 1, bufferp);

   // 예 2, 자기 자신으로의 복사

   copy(bufferp + 2, bufferp + strlen(buffer) + 1, bufferp);

   int buflen = strlen(buffer) + 1;   copy_backward(bufferp, bufferp + buflen, bufferp + buflen + 3);   copy(surpass, surpass + 3, bufferp);

   // 예 3, 출력으로 복사

   copy(bufferp, bufferp + strlen(buffer),             ostream_iterator<char, char>(cout));   cout << endl;

   // 예 4, copy()를 사용하여 배열을 list 로 바꾼다.   list<char> char_list;   copy(bufferp, bufferp + strlen(buffer),             inserter(char_list, char_list.end()));   char *big = "big ";   copy(big, big + 4, inserter(char_list, char_list.begin()));

   char buffer2[120], *buffer2p = buffer2;   *copy(char_list.begin(), char_list.end(), buffer2p) = '\0';   cout << buffer2 << endl;

132

Page 133: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

}예1에서는 copy()를 호출하여 변수 source가 가리키는 문자열을 buffer로 복사하고, 따라서 buffer는 "reprise"를 가지게 된다. 복사가 끝나는 지점은 마지막 널문자를

지나서이고, 따라서 널문자도 복사된다.

copy() 연산은 자기 자신에게로의 복사를 허용한다. 단, 목적지 반복자가 소스 반복자 구간에

속하지 않아야 한다. 예 2 에서 복사는 buffer 의 2 번 위치에서 시작하여 끝까지 진행되고, 문자들은 buffer 의 맨앞으로 복사된다. 결과적으로 buffer 는 "prise"를 가지게 된다.

예 2 의 뒷부분은 copy_backward() 알고리듬을 설명하고 있다. 이 함수는 copy() 알고리듬과 같은 작업을 수행하지만, 시퀀스의 끝에서부터 앞쪽으로 복사를 거꾸로 해 나간다. 위 예에서는 buffer 에 "priprise"가 남게 된다. 그리고 나서, 다음에 이어지는 copy() 연산에

의해 처음 3 개의 문자가 "sur"로 수정되어, buffer 에 "surprise"가 남게 된다.

▶ copy_backward()

copy_backward() 알고리듬에서의 "backward"는 원소들이 뒤집힌다는 의미가 아니라, 복사되는 순서가 거꾸로라는 의미이다. 복사된 뒤에 원소들의 상대적인 위치는 바뀌지 않는다.

예 3 은 copy()를 사용하여 값을 출력 스트림으로 옮기는 것을 설명한다(2.3.2절 참고). 이

경우에 목적지는 출력 스트림 cout 에 대해 생성된 ostream_iterator 이다. 입력도 이와

비슷한 방법을 사용할 수 있다. 예를 들어, 입력 스트림에 들어있는 모든 단어를 list 로 복사하는

방법은 copy() 알고리듬을 아래와 같이 호출하면 된다.

list<string> words;istream_iterator<string, char> in_stream(cin), eof;// 최근 표준에서는 istream_iterator<string>으로 써도 된다.

133

Page 134: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

copy(in_stream, eof, inserter(words, words.begin()));// words.begin()보다는 words.end()가 좋을 것 같은데...

이 기법은 8.3절에서 설명한 철자 검사기에서 사용된다.

복사는 스트림을 다른 타입으로 바꾸는데 사용할 수도 있다. 예를 들어, 예 4 에서의 호출은 문자

배열, buffer 에 담긴 문자들을 문자 list 로 복사한다. inserter()를 호출하여, 삽입 반복자를

만들고, 이를 이용하여 list 에 값들을 삽입한다. copy()의 첫번째 호출은 2 번 예제에서 생성한

"surprise" 문자열을 list 에 삽입한다. copy()의 두번째 호출은 "big " 문자열을 list 의 앞에

삽입한다. 따라서, list 에는 "big surprise"가 담기게 된다. copy()의 마지막 호출은

이전과는 반대로 list 내의 문자들을 문자 buffer 로 복사한다.

13.2.3 발생기가 생성한 값으로 시퀀스 초기화하기

발생기(generator)는 연속적인 호출을 통해 일련의 값들을 리턴하는 함수이다. 아마 가장

익숙한 발생기로 난수 발생기를 들 수 있을 것이다. 그러나, 이외에도 시퀀스를 초기화하는 등의

여러가지 용도에 맞도록 발생기를 구성할 수 있다.

fill()과 fill_n()과 같이 generate()과 generate_n()은 시퀀스를 초기화하거나

재초기화하는데 사용된다. 그러나, 이들 알고리듬은 고정된 인자값대신에 발생기가 생성한

값들을 사용한다. 이 두 알고리듬의 프로토타입은 다음과 같다.

void generate(ForwardIterator, ForwardIterator, Generator);void generate_n(OutputIterator, Size, Generator);

다음 예는 시퀀스를 초기화하는 generate() 알고리듬의 다양한 사용예를 보여주고 있다. // 'L_ddd' 형태의 유일한 라벨을 생성하는 발생기

string generateLabel(){   static int lastLabel = 0;   char labelBuffer[80];   ostrstream ost(labelBuffer, 80);   ost << "L_" << lastLabel++ << '\0';   return string(labelBuffer);}

void generate_example()

134

Page 135: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

{   // 예 1, 라벨의 list 를 생성

   list<string> labelList;   // list 선언시 사이즈를 명시하지 않았으므로 삽입반복자를 사용

   generate_n(inserter(labelList, labelList.begin()),          4, generateLabel);

   // 예 2, 정수들의 시퀀스를 생성

   vector<int> iVec(10);   // vector 선언시 사이즈를 명시했으므로 일반 반복자를 사용

   generate(iVec.begin(), iVec.end(), iotaGen(2));   generate_n(iVec.begin(), 5, iotaGen(7));}

발생기는 한개 이상의 정적 변수에 이전 상태를 저장하는 간단한 함수로 구현할 수 있다. 위

예의 맨 앞에 있는 generateLabel() 함수가 그 예이다. 이 함수는 유일한 라벨을 생성한다

(컴파일러에서 이와 비슷한 작업을 한다). generateLabel() 함수를 호출할 때마다, 유일한

번호를 가지는 L_ddd 형태의 새로운 라벨을 만들어 낸다. lastLabel 이란 이름을 가진 변수는

static 으로 선언되었기 때문에, 이 변수의 값은 다음번 호출시에도 그대로 남게 된다. 예 1 은

발생기와 generate_n()알고리듬을 사용하여 list 를 네개의 라벨로 초기화하고 있다.

3 장에서 설명한 바와 같이, 표준 라이브러리에서의 함수는 함수호출 연산자(operator())에

반응하는 객체이다. 이 사실을 이용하면, 클래스를 함수로 만들어 버릴 수 있다. 3.3절에서

설명한 iotaGen 클래스가 그 예이다. iotaGen 함수 객체는 정수 시퀀스를 만드는 발생기를

생성한다. 예 2 는 이 시퀀스를 사용하여 vector 를 2 에서 11까지의 정수값으로 초기화한 뒤, generate_n() 함수를 호출하여 vector 의 처음 다섯자리를 7 부터 11까지의 값으로

덮어쓰고 있다. 결과적으로, vector 에는 7 8 9 10 11 7 8 9 10 11 을 포함하게 된다.

13.2.4 두개의 구간에 속한 원소들 서로 뒤바꾸기

템플릿 함수 swap()은 타입이 동일한 두 객체의 값을 교환하는데 사용된다. swap()은

다음과 같이 정의되어 있다.

template <class T> void swap(T& a, T& b){   T temp(a);

135

Page 136: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   a = b;   b = temp;}

iter_swap() 함수는 swap()을 보다 일반화한 것으로 교환할 값을 반복자로 가리킨다. swap_ranges() 알고리듬은 교환의 적용범위를 전체 시퀀스로 확장한 것이다. 첫번째

시퀀스에 속하는 값들을 두번째 시퀀스에 속하는 값들과 교환한다. swap_ranges() 알고리듬의 프로토타입은 다음과 같다.

ForwardIterator swap_ranges    (ForwardIterator first, ForwardIterator last,          ForwardIterator first2);

▶ 병렬 시퀀스

많은 알고리듬들이 두 병렬 시퀀스에 대해 연산을 수행한다. 대부분의 경우, 두번째 시퀀스를

지정할 때는 시작 반복자와 끝 반복자를 모두 명시하지 않고, 시작 반복자만 명시하는 것이

보통이다. 그리고, 두번째 시퀀스는 최소한 첫번째 시퀀스보다 길어야 한다(알고리듬이

검사하지 않음). 이 조건을 만족하지 못하면, 에러가 발생한다.

두번째 시퀀스를 나타내는 구간은 시작 반복자로만 명시한다. 두번째 구간은 적어도 첫번째

구간만큼의 원소를 가져야 한다. 예제 프로그램에서 이 두함수를 설명한다.

void swap_example(){   // 먼저 두개의 시퀀스를 생성

   int data[] = {12, 27, 14, 64}, *datap = data;   vector<int> aVec(4);   generate(aVec.begin(), aVec.end(), iotaGen(1));

   // swap()과 iter_swap()   swap(data[0], data[2]);   vector<int>::iterator last = aVec.end(); last--;   iter_swap(aVec.begin(), last);

   // 시퀀스 전체를 교환

   swap_ranges(aVec.begin(), aVec.end(), datap);}

136

Page 137: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.3 검색 연산

이번에 살펴볼 알고리듬들은 시퀀스에서 주어진 조건을 만족하는 원소를 찾을 때 사용되는

것들이다. 보통 검색의 결과는 복사(13.2.2 절 ), 분할(13.4.4 절 ), in-place 합병(13.4.6 절 )과

같은 연산의 인자로 다시 사용된다.

이 절에서 설명할 검색 알고리듬들은 검색 조건을 만족하는 첫번째 원소를 가리키는 반복자를

리턴한다. 이 리턴값은 보통 다음과 같이 반복자 변수에 저장한다.

   list<int>::iterator where;   where = find(aList.begin(), aList.end(), 7);검색 조건을 만족하는 '모든' 원소를 찾으려면, 루프를 작성해야 한다. 이 루프에서 이전 검색의

결과값에서 하나 전진하고(안그러면, 이전검색의 결과를 다시 얻게 된다), 이번 검색의

결과값은 다음번 검색의 시작점으로 사용된다. 예를 들어, adjacent_find() 예제 프로그램

(13.3.2 절 )에서 따온 다음 루프는 인자로 주어진 string에서 연속적으로 반복되는 문자들을

모두 출력한다. ▶ 검색 결과의 검사

모든 검색 알고리듬은 검색 조건을 만족하는 원소를 찾지 못하면 end-of-sequence 반복자를

반환한다. end-of-sequence 반복자를 참조하는 것은 예상치 못한 결과를 초래하기 때문에, 이

결과값을 사용하기 전에 그것이 end-of-sequence 반복자가 아닌지를 검사하는 것이 중요하다.

   while ((where = adjacent_find(where, stop)) != stop) {      cout << "double " << *where << " in position "          << where - start << endl;      ++where; // 검색결과값을 하나 전진시켜 다음번 검색에 사용

   }많은 검색 알고리듬들이 원소 비교에 사용할 함수를 인자로 가지는데, 만약 이 인자가

생략된다면, 컨테이너에 속한 원소 타입의 상등 연산자(== 연산자)를 사용한다. 앞으로

알고리듬을 설명할 때 생략가능한 인자는 각진 브래킷내([])에 쓰기로 한다. 13.3.1 조건을 만족하는 원소 찾기

find()와 find_if() 두 알고리듬은 조건을 만족하는 첫번째 원소를 찾는데 사용한다. 이 두

알고리듬의 프로토타입은 다음과 같다.

137

Page 138: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

InputIterator find_if(InputIterator first, InputIterator last,      Predicate); // 조건자를 사용

InputIterator find(InputIterator first, InputIterator last,      const T&); // 인자값을 사용

find_if() 알고리듬은 조건자를 인자로 취하는데, 조건자는 불값을 리턴하는 함수라면 모두

가능하다(3.2절). find_if() 알고리듬은 조건을 만족하는 첫번째 원소를 가리키는 반복자를

리턴한다. 조건을 만족하는 원소를 찾지 못하면, 두번째 인자로 넘겨준 past-the-end 반복자를

리턴한다. 결과값이 반복자이기 때문에, 실제값을 얻기 위해서는 반드시 참조 연산자(* 연산자)를 사용해야 한다. 이는 예제 프로그램에서 설명할 것이다.

find() 알고리듬은 조건자 대신에 특정값을 인자로 사용하고, 이 인자값과 동일한 시퀀스내의

첫번째 원소를 찾아 리턴하고, 이때 주어진 데이터 타입의 상등 연산자(== 연산자)를 사용하여

비교를 한다.

set 과 map 의 검색

다음은 이들 알고리듬을 사용한 예제 프로그램이다.

void find_test(){   int vintageYears[] = {1967, 1972, 1974, 1980, 1995};   int *start = vintageYears;   int *stop = start + 5;   int *where = find_if(start, stop, isLeapYear);   if (where != stop)      cout << "first vintage leap year is " << *where << endl;   else      cout << "no vintage leap years" << endl;   where = find(start, stop, 1995);   if (where != stop)      cout << "1995 is position " << where - start          << " in sequence" << endl;   else      cout << "1995 does not occur in sequence" << endl;

138

Page 139: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

}13.3.2 연속적으로 중복된 원소 찾기

adjacent_find() 알고리듬은 시퀀스내에서 바로 다음에 따라오는 원소와 일치하는 첫번째

원소를 찾는데 사용된다. 예를 들어, 시퀀스가 1 4 2 5 6 6 7 5 를 담고 있다면, adjacent_find() 알고리듬은 첫번째 6 을 가리키는 반복자를 리턴할 것이다. 조건을

만족하는 원소가 없으면, end-of-sequence 반복자를 리턴한다. 알고리듬의 프로토타입은

다음과 같다.

ForwardIterator adjacent_find(ForwardIterator first,    ForwardIterator last [, BinaryPredicate ] );

처음 두개의 인자는 검색이 이루어지는 시퀀스를 명시한다. 세번째 인자(생략가능)는 반드시

이항 조건자이어야 한다(불값을 리턴하는 이항 함수). 만약에 세번째 인자가 주어지면, 이항

함수를 사용하여 이웃하는 원소들을 검사하고, 주어지지 않으면, 상등 연산자(== 연산자)를

사용한다.

예제 프로그램은 텍스트 문자열에서 이웃하는 문자를 찾는 것이다. 예제 텍스트에서는 5, 7, 9, 21, 37 번 위치에서 중복되는 문자들이 나타난다. 같은 위치가 반복해서 리턴되지 않도록

루프내에 증가 연산이 필요하게 된다.

void adjacent_find_example(){   char *text = "The bookkeeper carefully opened the door.";   char *start = text;   char *stop = text + strlen(text);   char *where = start;

   cout << "In the text: " << text << endl;   while ((where = adjacent_find(where, stop)) != stop) {      cout << "double " << *where          << " in position " << where - start << endl;      ++where;   }}

139

Page 140: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.3.3 시퀀스로부터 어떤 값의 첫번째 발생 찾기

find_first_of() 알고리듬은 시퀀스에서 다른 시퀀스에도 속해 있는 첫번째 원소를 찾는데

사용한다.

ForwardIterator1 find_first_of         (ForwardIterator1 first1, ForwardIterator1 last1,         ForwardIterator2 first2, ForwardIterator2 last2         [, BinaryPredicate pred ] );

이 알고리듬은 [first1, last1)에 포함되어 있는 원소들 중, [first2,last2)에도 포함되어 있는

첫번째 원소를 가리키는 새 반복자를 리턴한다. 원소를 찾지 못하면, 두번째 인자를 리턴한다. 결과값이 반복자이므로, 참조 연산자(* 연산자)를 사용하여 값을 구해야 한다. 이는 예제

프로그램에 설명되어 있다.

Searching Sets and Maps

다음 예제 프로그램은 이 알고리듬을 설명하고 있다.

void find_test(){   int vintageYears[] = {1967, 1972, 1974, 1980, 1995};   int requestedYears[] = [1923, 1970, 1980, 1974 };   int *start = vintageYears;   int *stop = start + 5;   int *where = find_first_of (start, stop,                            requestedyears,requestedyears+4 );   if (where != stop)      cout << "first requested vintage year is " << *where << endl;   else      cout << "no requested vintage years" << endl;}

// The output would indicate 1974.두개의 시퀀스를 다루는 많은 알고리듬과는 달리 이들 알고리듬은 첫번째 시퀀스에 대해서뿐만

아니라, 두 시퀀스 모두에 대해 시작 반복자와 끝 반복자를 사용한다.

140

Page 141: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

equal()과 mismatch() 알고리듬과 마찬가지로, find_first_of()의 또 다른 버전은

이항 조건자를 인자로 취하여, 두개의 시퀀스로부터 원소를 비교하는데 사용한다.

13.3.4 시퀀스에서 부시퀀스 찾기

search()와 search_n() 알고리듬은 시퀀스내에서 특정 부시퀀스의 시작위치를 찾는데

사용된다. 이해하기 가장 쉬운 예로 문자열에서 특정 부문자열을 찾는 문제를 들 수 있겠다. 인자들은 적어도 순방향 반복자이어야 한다.

ForwardIterator search    (ForwardIterator first1, ForwardIterator last1,    ForwardIterator first2, ForwardIterator last2    [, BinaryPredicate ]);

Speed of Search

예를 들어 "dreams and aspirations"라는 문자열에서 "ration" 문자열의 위치를 찾는다고

하자. 이 문제에 대한 해결책은 예제 프로그램에 나와 있다. 원하는 문자열을 찾을 수 없으면, 첫번째 시퀀스의 past-the-end 반복자를 리턴한다.

void search_example(){   char *base = "dreams and aspirations";   char *text = "ration";

   char *where = search(base, base + strlen(base),          text, text + strlen(text));

   if (*where != '\0')      cout << "substring position: " << where - base << endl;   else      cout << "substring does not occur in text" << endl;}

두개의 시퀀스를 다루는 알고리듬과는 달리 이 알고리듬은 첫번째 시퀀스뿐만 아니라 두번째

시퀀스에 대해서도 시작 반복자와 끝 반복자를 모두 사용한다.

141

Page 142: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

equal()과 mismatch() 알고리듬과 마찬가지로, search()의 다른 버전은 두개의

시퀀스를 비교할 때 사용할 이항 조건자를 취한다(생략가능).

13.3.5 부시퀀스의 마지막 발생 찾기

find_end() 알고리듬은 시퀀스 내에서 특정 부 시퀀스가 마지막으로 나타나는 시작점을

찾는데 사용한다. 가장 이해하기 쉬운 예로, 문자열에서 특정 부문자열을 찾는 문제를 들 수

있다. 인자는 적어도 순방향 반복자이어야 한다.

ForwardIterator find_end    (ForwardIterator first1, ForwardIterator last1,    ForwardIterator first2, ForwardIterator last2    [, BinaryPredicate ]);

Speed of Find_end

예를 들어, "The road less traveled" 문자열에서 "le" 문자열이 마지막으로 나타나는 위치를

찾는다고 하자. 이 문제의 해결방법은 예제 프로그램에 나타난다. 원하는 문자열을 찾지 못하면, 첫번째 시퀀스의 past-the-end 반복자를 리턴한다.

void find_end_example(){   char *base = "The road less traveled";   char *text = "le";

   char *where = find(base, base + strlen(base),          text, text + strlen(text));

   if (*where != '\0')      cout << "substring position: " << where - base << endl;   else      cout << "substring does not occur in text" << endl;}

두개의 시퀀스를 다루는 다른 알고리듬과는 다르게, 이 알고리듬은 첫번째 시퀀스뿐만 아니라, 두번째 시퀀스에 대해서도 시작 반복자와 끝 반복자를 모두 사용한다.

142

Page 143: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

find_first_of(), search()와 마찬가지로, find_end()의 다른 버전은 두 시퀀스의

원소를 비교할 때 사용하는 이항 조건자를 인자로 취하며, 생략가능하다.

13.3.6 최대 또는 최소 원소 찾기

max()와 min() 함수는 두 값의 최대값과 최소값을 알아내는데 사용한다. 이들 함수는 less-than 연산자(< 연산자) 대신에 사용할 비교 함수로 세번째 인자를 사용한다.

template <class T>    const T& max(const T& a, const T& b [, Compare ] );template <class T>    const T& min(const T& a, const T& b [, Compare ] );

max()와 min() 함수를 보다 일반화시킨 max_element()와 min_element() 알고리듬은 시퀀스 전체에 적용이 가능하다. 이들 함수들의 인자는 입력 반복자들이다.

ForwardIterator max_element (ForwardIterator first,       ForwardIterator last [, Compare ] );ForwardIterator min_element (ForwardIterator first,       ForwardIterator last [, Compare ] );

Largest and Smallest Elements of a Set

이 알고리듬들은 각각 시퀀스내에서 최대값과 최소값을 가리키는 반복자를 리턴한다. 최대값

또는 최소값이 하나 이상일 경우에는 첫번째 값이 리턴된다. 두 알고리듬 모두 세번째 인자로

디폴트 연산자 대신 사용할 비교 연산자를 취하며, 생략가능하다.

예제 프로그램은 이들 알고리듬을 사용하는 예를 보인 것이다. split() 함수(12.3절 참고)를

사용하여 문자열을 단어들로 쪼개고 있다. randomInteger() 함수는 2.2.5절에 설명되어

있다.

void max_min_example(){   // make a vector of random numbers between 0 and 99   vector<int> numbers(25);   for (int i = 0; i < 25; i++)

143

Page 144: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      numbers[i] = randomInteger(100);

   // print the maximum   vector<int>::iterator max =       max_element(numbers.begin(), numbers.end());   cout << "largest value was " << * max << endl;

   // example using strings   string text =       "It was the best of times, it was the worst of times.";   list<string> words;   split(text, " .,!:;", words);   cout << "The smallest word is "          << * min_element(words.begin(), words.end())         << " and the largest word is "         << * max_element(words.begin(), words.end())         << endl;}

13.3.7 병렬 시퀀스에서 처음으로 일치하지 않는 원소 찾기

mismatch() 알고리듬은 이름에서 알 수 있듯이, 두개의 시퀀스가 서로 같은지를 판별하는

equal() 알고리듬(13.6.4절)과 반대되는 함수이다. 대신에, mismatch() 알고리듬은

반복자들의 pair 를 리턴하는데, 이들 반복자는 각각 두 시퀀스에서 다른 원소가 나타나는

첫번째 위치를 나타낸다(pair 구조에 대해서는 9.1절 참고). 두번째 시퀀스는 끝 반복자 없이

시작 반복자로만 표시하며, 두번째 시퀀스는 적어도 첫번째 시퀀스만큼의 원소를 가지고 있어야

한다(이는 알고리듬내에서 검사하지 않는다). 일치하지 않는 원소를 찾기도 전에 첫번째

시퀀스가 끝이 나면, 결과값으로 리턴되는 pair 에는 첫번째 시퀀스의 끝 반복자와 두번째

시퀀스에서 맨 마지막으로 살펴본 원소를 가리키는 반복자가 담기게 된다.(두번째 시퀀스는

모두 조사될 필요가 없다.) mismatch()의 인자와 리턴값은 다음과 같다.

pair<InputIterator, InputIterator> mismatch    (InputIterator first1, InputIterator last1,       InputIterator first2 [, BinaryPredicate ] );

원소들은 병렬적으로 하나씩 검사하며, 두 시퀀스가 달라지는 지점을 발견하면, 두개의 다른

원소의 위치를 가리키는 반복자를 담은 pair를 구성하여 리턴한다. 일치하지 않는 원소를

찾기도 전에 첫번째 시퀀스가 끝이 나면, 결과값으로 리턴되는 pair에는 첫번째 시퀀스의 끝

144

Page 145: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

반복자와 두번째 시퀀스에서 맨 마지막으로 조사된 원소를 가리키는 반복자가 담기게 된다.(두번째 시퀀스는 모두 조사될 필요가 없다.)

예제 프로그램은 이 알고리듬의 사용예를 보여주고 있다. mismatch_test() 예제함수는

두개의 string 을 인자로 취한다. 이들은 사전식으로 비교되어 서로의 상대적인 순서를 표시하는

메시지가 출력된다(?). mismatch() 알고리듬은 두번째 시퀀스가 첫번째 시퀀스만큼은

되어야 하므로, 두 string 의 길이를 비교한뒤 두번째 문자열이 첫번째보다 짧으면 인자를

바꾼다. mismatch()를 호출한 뒤에는 결과 pair 의 각 필드값을 적절한 변수에 할당하여, 이들의 상대적인 순서를 결정하는데 쓰인다.

   void mismatch_test(char *a, char *b)   {      pair<char *, char *> differPositions(0, 0);      char *aDiffPosition;      char *bDiffPosition;      if (strlen(a) < strlen(b)) {         // make sure longer string is second         differPositions = mismatch(a, a + strlen(a), b);         aDiffPosition = differPositions.first;         bDiffPosition = differPositions.second;      }      else {         differPositions = mismatch(b, b + strlen(b), a);         // note following reverse ordering         aDiffPosition = differPositions.second;         bDiffPosition = differPositions.first;      }      // compare resulting values      cout << "string " << a;      if (*aDiffPosition == *bDiffPosition)         cout << " is equal to ";      else if (*aDiffPosition < *bDiffPosition)         cout << " is less than ";      else         cout << " is greater than ";      cout << b << endl;

145

Page 146: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   }mismatch() 알고리듬의 두번째 형태는, 원소들을 비교할 때 == 연산자 대신에 사용할

이항 함수를 네번째 인자로 취한다.

13.4 In-Place 변환

이번에 설명할 알고리듬들은 원래의 저장 위치로부터 이동하지 않고 시퀀스를 수정하고

변환하는데 사용되는 것들이다. replace()와 같은 몇몇 루틴들은 본래의 in-place 변환

알고리듬뿐만 아니라 복사 버전들을 가지고 있다. 어떤 루틴들은 원래의 시퀀스를 유지할

필요가 있다면, 시퀀스가 복사본을 먼저 생성하고 나서, 변환을 적용해야한다. 예를 들어, 다음은 vector 를 뒤집은 것을 새롭게 할당된 vector 에 배치하는 방법을 보여주고 있다.

   vector<int> newVec(aVec.size());   copy(aVec.begin(), aVec.end(), newVec.begin()); // first copy   reverse(newVec.begin(), newVec.end());          // then reversetransform()(13.7.1 절 )이나 partial_sum()(13.7.2 절 )과 같은 시퀀스 생성 연산

알고리듬들은 입력과 출력을 같은 반복자로 명시하여 직접 시퀀스를 직접 수정하는데 사용한다.

13.4.1 시퀀스내의 원소 뒤집기

reverse() 알고리듬은 시퀀스내의 원소들을 뒤집어, 마지막 원소를 첫번째 원소로 만들고, 첫번째 원소를 마지막 원소로 만든다. 인자들은 양방향 반복자이어야하고, 값은 리턴되지

않는다.

   void reverse (BidirectionalIterator first,      BidirectionalIterator last);예제 프로그램은 이 알고리듬의 두가지 용도을 설명하고 있다. 처음것은 문자 배열을 뒤집는

것이다. reverse() 알고리듬은 두번째 예에서 보는 바와 같이 list에도 사용할 수 있다. list는

먼저 2에서 11까지의 값으로 초기화 되고(이는 3.3 절 에서 소개한 iotaGen 함수 객체를

사용하였다.), 이어서 뒤집기를 하여, list는 11부터 2까지의 값을 가지게 된다. 그러나, list 데이터 구조는 reverse() 멤버 함수도 가지고 있음을 기억하기 바란다.

void reverse_example(){   // example 1, reversing a string   char *text = "Rats live on no evil star";

146

Page 147: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   reverse(text, text + strlen(text));   cout << text << endl;

   // example 2, reversing a list   list<int> iList;   generate_n(inserter(iList, iList.begin()), 10, iotaGen(2));   reverse(iList.begin(), iList.end());}

13.4.2 어떤 값을 특정 값으로 치환하기

replace()와 replace_if() 알고리듬은 특정 원소들을 새로운 값으로 교체하는데 사용한다. 두 알고리듬 모두 아무리 교체가 많이 일어나도, 교체되는 원소들은 결국 같은 값을 가진다. replace() 알고리듬은 특정 값과 같은 값들은 모두 새로운 값으로 교체한다. replace_if() 주어진 조건자를 만족하는 모든 원소들을 새로운 값으로 교체한다. 인자로 주어지는 반복자는

순방향 반복자이어야 한다.

replace_copy()와 replace_copy_if()들은 replace(), replace_if()들과

비슷하지만, 전자들은 원래 시퀀스들은 그냥 건드리지 않으며, 갱신된 값들은 새 시퀀스(타입이

달라도 됨)에 배치한다는 점이 다르다.

void replace(ForwardIterator first, ForwardIterator last,          const T&, const T&);void replace_if(ForwardIterator first, ForwardIterator last,          Predicate, const T&);OutputIterator replace_copy(InputIterator, InputIterator,          OutputIterator, const T&, const T&);OutputIterator replace_copy(InputIterator, InputIterator,          OutputIterator, Predicate, const T&);

예제 프로그램에서, vector는 먼저 0 1 2 3 4 5 4 3 2 1 0 으로 초기화된다. replace()를

호출하여 3을 7로 바꾸어 0 1 2 7 4 5 4 7 2 1 0 이 되게 한다. 이번에는 replace_if()를

호출하여 짝수를 모두 9로 교체하여 9 1 9 7 9 5 9 7 9 1 가 되게 한다. void replace_example()       // illustrate the use of the replace algorithm{   // make vector 0 1 2 3 4 5 4 3 2 1 0

147

Page 148: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   vector<int> numbers(11);   for (int i = 0; i < 11; i++)      numbers[i] = i < 5 ? i : 10 - i;

   // replace 3 by 7   replace(numbers.begin(), numbers.end(), 3, 7);

   // replace even numbers by 9   replace_if(numbers.begin(), numbers.end(), isEven, 9);

   // illustrate copy versions of replace   int aList[] = {2, 1, 4, 3, 2, 5};   int bList[6], cList[6], j;   replace_copy(aList, aList+6, &bList[0], 2, 7);   replace_copy_if(bList, bList+6, &cList[0],         bind2nd(greater<int>(), 3), 8);}예제 프로그램은 replace_copy() 알고리듬의 사용법도 설명하고 있다. 먼저 2 1 4 3 2 5 의 값들을 담은 배열을 생성한다. 2를 7로 교체하여 7 1 4 3 7 5 로 만든다. 다음에 3보다 큰

모든 값들을 8로 교체하여 8 1 8 3 8 8 로 수정한다. 후자의 경우에는 bind2nd() 어댑터를

사용하여 이항 함수인 greater-than 함수의 두번째 인자를 3으로 고정시켜 x > 3를 의미하는

단항 함수로 만들었다. 13.4.3 중간지점을 중심으로 원소들 돌리기

시퀀스를 순환한다는 것은 시퀀스를 두분으로 나누고, 이두부분의 위치를 바꾸어 두부분내의

원소들의 상대적인 순서는 유지되도록 하는 것이다. 예를 들어, 다음 1부터 10까지의 시퀀스를

생각해보자.

1 2 3 4 5 6 7 8 9 10

원소 7 주위로 순환을 하려면, 7 부터 10까지의 값들을 앞쪽으로 옮기고, 1 부터 10까지의

값들을 뒤쪽으로 옮기면 된다. 결과는 다음과 같다.

7 8 9 10 1 2 3 4 5 6

148

Page 149: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

rotate() 알고리듬을 호출할 때는 시작점, 중간점, past-the-end 위치를 순방향 반복자로

명시한다.

void rotate(ForwardIterator first, ForwardIterator middle,    ForwardIterator last);

앞부분은 시작점부터 중간점 바로 앞까지이며, 뒷부분은 중간점부터 past-the-end까지이다. 바로 앞에 예에서 알 수 있듯이, 앞부분과 뒷부분의 길이가 같을 필요는 없다.

void rotate_example()        // illustrate the use of the rotate algorithm{   // create the list 1 2 3 ... 10   list<int> iList;   generate_n(inserter(iList, iList.begin()), 10, iotaGen(1));

   // find the location of the seven   list<int>::iterator & middle =          find(iList.begin(), iList.end(), 7);

   // now rotate around that location   rotate(iList.begin(), middle, iList.end());

   // rotate again around the same location   list<int> cList;   rotate_copy(iList.begin(), middle, iList.end(),      inserter(cList, cList.begin()));}

예제 프로그램은 먼저 1부터 10까지의 정수를 담은 list를 생성하고, find() 알고리듬(13.3.1절)을 사용하여 원소 7의 위치를 찾는다. 이결과를 순환의 중간점으로 사용한다.

rotate()의 두번째 형태는 원래 시퀀스내에서 돌리지 않고, 원소들을 새로운 시퀀스에

복사하는 것이다. 이는 위 예제프로그램에서 다루고 있는데, 중간점(이번에는 3 을 가리키고

있다.)을 중심으로 다시한번 순환시키고 있다. 결과는 3 4 5 6 7 8 9 10 1 2 가 된다. iList 에

담긴 값들은 전혀 변하지 않는다.

13.4.4 시퀀스 둘로 쪼개기

분할(partition)은 주어진 조건을 만족하는 모든 원소들을 시퀀스의 한쪽끝으로 옮기는 것이다.

149

Page 150: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

원소를 분할하는 것은 qucksort와 같은 정렬 알고리듬에서는 기본적인 단계이다. BidirectionalIterator partition    (BidirectionalIterator, BidirectionalIterator, Predicate);

BidirectionalIterator stable_partition    (BidirectionalIterator, BidirectionalIterator, Predicate);

표준라이브러리가 지원하는 분할의 형태에는 두가지가 있다. 첫번째는 partition()이

제공하는 것으로 원소들을 두부분으로 나누는 것에만 책임을 진다. 결과값으로 두 그룹사이의

마지막(?) 중간점을 나타내는 반복자를 리턴한다. 이 중간점은 첫번째 그룹의 끝을 하나 지난

곳이다.

Partitions

예제 프로그램에서 vector 는 처음에 1 부터 10까지의 값을 순서대로 담고 있다. 분할을

수행하여, 짝수 원소들을 앞쪽으로, 홀수 원소들을 뒤쪽으로 옮기게 된다. vector 는 10 2 8 4 6 5 7 3 9 1 을 가지게 되고, 결과값으로 중간점(원소 5)을 가리키는 반복자를 반환한다.

void partition_example(){   // first make the vector 1 2 3 ... 10   vector<int> numbers(10);   generate(numbers.begin(), numbers.end(), iotaGen(1));

   // now put the even values low, odd high   vector<int>::iterator result =       partition(numbers.begin(), numbers.end(), isEven);   cout << "middle location " << result - numbers.begin() << endl;

   // now do a stable partition   generate (numbers.begin(), numbers.end(), iotaGen(1));   stable_partition (numbers.begin(), numbers.end(), isEven);}

분할로 나누어진 각 영역에 속하는 원소들의 상대적인 순서가 분할 이전의 순서와 다를 수도

있다. 예를 들어, 분할 이전에 4가 8앞에 있었지만, 분할 이후에는 8이 앞으로 올 수 있다는

150

Page 151: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

것이다. 분할의 두번째 형태는 stable_partition()에 의해 제공되는데 이 알고리듬은 분할

이후에도 분할 이전의 순서들을 그대로 유지한다. 예를 들어, 위 예에서는 2 4 6 8 10 1 3 5 7 9 가 된다. stable_partition()은 partition() 알고리듬보다 조금 느리며, 더 많응

메모리를 필요로 한다. 따라서, 원소들의 순서가 별 상관이 없다면, partition()을 사용해야

한다. 13.4.5 시퀀스내에 순열 생성하기

Ordering Permutations

순열(permutation)은 값들의 재배치이다. 서로 비교가 가능한 값들이라면(정수, 문자, 단어

등등), 시퀀스의 순열을 모두 만들수 있다. 예를 들어, 값이 2 개면 2 개의 순열을, 3 개면 6 개를, 4 개면 24 개의 순열을 만들 수 있게 된다. 순열을 만들어내는 알고리듬은 다음 프로토타입을

가진다.

bool next_permutation (BidirectionalIterator first,       BidirectionalIterator last, [ Compare ] );

bool prev_permutation (BidirectionalIterator first,       BidirectionalIterator last, [ Compare ] );

예제 프로그램에서의 두번째 예는 정수 대신에 문자 배열의 포인터를 사용한 것만 다를뿐, 설명하고자 하는 내용은 같다. 단 이 경우에는 비교함수로 다른 것을 사용해야 한다. 디폴트

비교 연산자는 포인터 주소 자체를 비교하기 때문이다.

bool nameCompare (char * a, char * b) { return strcmp(a, b) <= 0; }

void permutation_example()   // illustrate the use of the next_permutation algorithm{      // example 1, permute the values 1 2 3   int start [] = { 1, 2, 3};   do      copy (start, start + 3,             ostream_iterator<int,char> (cout, " ")), cout << endl;   while (next_permutation(start, start + 3));

151

Page 152: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

      // example 2, permute words   char * words = {"Alpha", "Beta", "Gamma"};   do      copy (words, words + 3,          ostream_iterator<char *,char> (cout, " ")), cout << endl;   while (next_permutation(words, words + 3, nameCompare));

      // example 3, permute characters backwards   char * word = "bela";   do      cout << word << ' ';   while (prev_permutation (word, &word[4]));   cout << endl;}

에제 프로그램의 3번 예는 역 순열 알고리듬을 설명하고 있는데, 이는 순열을 거꾸로 생성한다. 이 예제에서는 순열의 중간에서 시작하는데, "bela"의 나머지 순열들은 beal, bale, bael, aleb, albe, aelb, aebl, able, abel이다.

13.4.6 두개의 이웃하는 시퀀스를 하나로 합치기

병합(merge)은 두개의 ordered 시퀀스를 하나의 ordered 시퀀스로 합치고, 필요하다면

원소들을 끼워넣어 새로운 list 를 만들어 낸다. inplace_merge() 알고리듬은 시퀀스가

이웃하는 두부분으로 나눠져있어야 하고, 각각은 정렬되어 있어야 한다. 병합을 통해 두부분을

하나로 합치고, 필요하다면 원소들을 이동시킨다(merge() 알고리듬은 별도의 두 시퀀스를

하나로 합칠 때 사용한다). inplace_merge()에 대한 인자들은 양방향 반복자이어야 한다.

void inplace_merge(BidirectionalIterator first,    BidirectionalIterator middle,   BidirectionalIterator last [, BinaryFunction ] );

예제 프로그램은 vector, list에 대해 inplace_merge() 알고리듬을 사용하는 예를 보이고

있다. vector는 0 2 4 6 8 1 3 5 7 9의 시퀀스를 가지고 있다. find()(13.3.1 절 )을 호출하여

홀수가 시작하는 위치를 찾는다. inplace_merge()를 사용하여 두개의 시퀀스를 하나로

합친다. void inplace_merge_example()      // illustrate the use of the inplace_merge algorithm{

152

Page 153: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   // first generate the sequence 0 2 4 6 8 1 3 5 7 9   vector<int> numbers(10);   for (int i = 0; i < 10; i++)      numbers[i] = i < 5 ? 2 * i : 2 * (i - 5) + 1;

   // then find the middle location   vector<int>::iterator midvec =       find(numbers.begin(), numbers.end(), 1);

   // copy them into a list   list<int> numList;   copy(numbers.begin(), numbers.end(),         inserter (numList, numList.begin()));   list<int>::iterator midList =          find(numList.begin(), numList.end, 1);

   // now merge the lists into one   inplace_merge(numbers.begin(), midvec, numbers.end());   inplace_merge(numList.begin(), midList, numList.end());}

13.4.7 시퀀스내의 원소들을 임의로 재배치하기

random_shuffle() 알고리듬은 시퀀스내의 원소들을 임의로 재배치한다. 정확히 n 번의

교체가 수행되며, 여기서 n 번은 시퀀스가 담고 있는 원소의 갯수를 나타낸다. 물론, 결과는

예측할 수 없다. 인자들은 임의 접근 반복자들이어야 하므로, 이 알고리듬은 vector, deque 또는 일반 포인터들하고만 사용이 가능하다. list, set, map 과는 함께 사용할 수 없다.

void random_shuffle (RandomAccessIterator first,    RandomAccessIterator last [, Generator ] );

이 알고리듬의 다른 버전은 세번째 인자를 추가로 제공하며, 생략이 가능하다. 이 값은 반드시

난수 발생기이어야 한다. 이 발생기는 인자로 양수 m을 취하고, 0과 m-1 사이의 값을

반환한다. generate() 알고리듬에서처럼, 이 난수 함수는 함수 호출 연산자를 가지는 임의의

객체이면 된다. void random_shuffle_example ()   // illustrate the use of the random_shuffle algorithm{

153

Page 154: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   // first make the vector containing 1 2 3 ... 10   vector<int> numbers;   generate(numbers.begin(), numbers.end(), iotaGen(1));

   // then randomly shuffle the elements   random_shuffle (numbers.begin(), numbers.end());

   // do it again, with explicit random number generator   struct RandomInteger {   {      operator()(int m) { return rand() % m; }   } random;

   random_shuffle (numbers.begin(), numbers.end(), random);}

13.5 삭제 알고리듬

What is a Name? 

다음 두 알고리듬은 처음 보게 되면 다소 혼란스러울 것이다. 둘다 시퀀스에서 특정 값들을

제거해야 한다. 하지만, 실제로 이들 알고리듬은 시퀀스의 사이즈를 감소시키지 않는다. 이 두

알고리듬은 삭제대상이 아닌 값들을 시퀀스의 앞쪽으로 몰고, 이 값들의 끝을 가리키는

반복자를 리턴한다. 이 반복자의 뒷부분에 있는 값들은 원래 시퀀스에 있던 값들이고 아무런

변경도 가해지지 않은 것들이다. generic 알고리듬은 자신이 어떤 컨테이너에 대해 작업을

하는지를 알지 못하기 때문에 이러한 일들이 필요하다. 알고리듬은 오직 generic 반복자만을

가지고 있을 뿐이다. 이는 generic 알고리듬을 사용할 때 감수해야 할 부분이다. 대부분의 경우

사용자는 리턴값으로 얻은 반복자를 해당 컨테이너의 erase() 멤버함수의 인자로 사용하여

반복자가 가리키는 값부터 시퀀스의 끝까지 모두 삭제한다.

간단한 예를 통해 설명해 보자. 1 2 3 4 5 6 7 8 9 10 와 같이 시퀀스에서 짝수들을 삭제한다고

하자. remove_if() 알고리듬을 수행하고 나면 시퀀스는 다음과 같이 된다.

154

Page 155: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

1 3 5 7 9 | 6 7 8 9 10

중간에 표시된 막대기는 remove_if() 알고리듬이 리턴하는 반복자의 위치이다. 막대기

앞쪽에 있는 5 개의 원소는 우리가 원하는 결과이고, 막대기 뒤쪽에 있는 5 개의 원소는 그냥

원래 시퀀스에서 동일한 위치에 있던 값들이다. 이 반복자와 end-of-sequence 반복자를

erase()의 인자로 넘겨주어서, 필요없는 값들을 삭제하고 원하는 결과를 얻을 수 있게 된다.

지금 설명한 알고리듬들은 둘다 복사 버전을 따로 가지고 있다. 이들 알고리듬의 복사버전은

원래 시퀀스는 그대로 놔두고, 삭제하고 남게 되는 값들을 다른 시퀀스로 출력한다.

13.5.1 필요없는 원소 삭제하기

remove() 알고리듬은 시퀀스로부터 필요없는 값들을 삭제한다. find() 알고리듬과 같이 이

알고리듬도 특정 값을 명시하거나, 조건을 명시할 수 있다. 프로토타입은 다음과 같다.

ForwardIterator remove    (ForwardIterator first, ForwardIterator last, const T &);ForwardIterator remove_if    (ForwardIterator first, ForwardIterator last, Predicate);

remove() 알고리듬은 삭제대상에서 제외되는 값들을 시퀀스의 앞쪽으로 이동시키며, 이때

이미 앞쪽에 있던 원소들을 덮어쓰게 된다. 삭제되지 않는 원소들은 상대적인 순서를 그대로

유지한다. 일단 모든 원소들을 한번 훑고 나면, 시퀀스의 나머지 부분은 전혀 변경되지 않는다. 알고리듬의 결과로 리턴되는 반복자는 새 시퀀스의 끝을 가리킨다. 예를 들어, 시퀀스 1 2 4 3 2 로부터 원소 2를 삭제하면, 시퀀스 1 4 3 3 2 가 되고, 결과로 리턴되는 반복자는 두번째 3을

가리키게 된다. 이 반복자를 erase() 멤버 함수의 인자로 사용하여 나머지 값(3과 2)들을

삭제하게 된다.

알고리듬의 복사 버전은 값들을 출력 시퀀스로 복사하며, 원래 시퀀스는 전혀 손을 대지 않는다.

OutputIterator remove_copy          (InputIterator first, InputIterator last,         OutputIterator result, const T &);

OutputIterator remove_copy_if          (InputIterator first, InputIterator last,         OutputIterator result, Predicate);

remove()의 사용은 다음 프로그램에서 볼 수 있다.

155

Page 156: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

void remove_example ()   // illustrate the use of the remove algorithm{   // create a list of numbers   int data[] = {1, 2, 4, 3, 1, 4, 2};   list<int> aList;   copy (data, data+7, inserter(aList, aList.begin()));

      // remove 2's, copy into new list   list<int> newList;   remove_copy (aList.begin(), aList.end(),       back_inserter(newList), 2);

      // remove 2's in place   list<int>::iterator where;   where = remove (aList.begin(), aList.end(), 2);   aList.erase(where, aList.end());

      // remove all even values   where = remove_if (aList.begin(), aList.end(), isEven);   aList.erase(where, aList.end());}

13.5.2 비슷한 값들의 런(run) 삭제하기

unique() 알고리듬은 선형 시퀀스를 훑어가면서, 연속적으로 같은 원소들이 나타나는 그룹을

발견하면 첫번째 원소만 남겨두고 나머지 원소들을 모두 삭제한다. 시퀀스는 순방향 반복자를

사용하여 인자로 넘겨준다.

ForwardIterator unique (ForwardIterator first,    ForwardIterator last [, BinaryPredicate ] );

알고리듬은 콜렉션을 훑어가면서, 연산 결과를 시퀀스의 앞쪽으로 옮기고, 이미 앞쪽에 있던

원소들은 덮어쓰게 된다. 일단 작업이 끝나게 되면, 시퀀스의 나머지 원소들은 건드리지 않고

그냥 내버려 두게 된다. 예를 들어, 1 3 3 2 2 2 4와 같이 구성된 시퀀스는 1 3 2 4 | 2 2 4 로

바뀌게 된다. 막대기는 결과값으로 리턴되는 반복자의 위치를 나타내며, 중복이 제거된

시퀀스의 끝이자 오른쪽에 남겨진 원소들의 시작을 가리킨다. 대부분의 컨테이너에서처럼

알고리듬이 리턴하는 값은 뒤따라 오는 연산의 인자로 사용되는데, erase() 멤버 함수의

156

Page 157: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

인자로 사용하여 필요없는 원소들을 삭제하게 된다. 이는 예제 프로그램에서 잘 나타나 있다.

알고리듬의 복사 버전은 중복이 제거된 원소들을 출력 반복자로 옮기고, 원래 시퀀스는 전혀

건드리지 않는다. list 나 multiset 을 변환할 때는 삽입 연산자를 사용하여, 출력 반복자의 복사

연산을 삽입 연산으로 바꿀 수 있다.

OutputIterator unique_copy       (InputIterator first, InputIterator last,       OutputIterator result [, BinaryPredicate ] );

이들은 다음 예제 프로그램에서 살펴 볼 수 있다. void unique_example()   // illustrate use of the unique algorithm{   // first make a list of values   int data[] = {1, 3, 3, 2, 2, 4};   list<int> aList;   set<int> aSet;   copy (data, data+6, inserter(aList, aList.begin()));

   // copy unique elements into a set   unique_copy (aList.begin(), aList.end(),      inserter(aSet, aSet.begin()));

   // copy unique elements in place   list<int>::iterator where;   where = unique(aList.begin(), aList.end());

   // remove trailing values   aList.erase(where, aList.end());}

13.6 스칼라 생성 알고리듬

157

Page 158: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

이번에 설명할 알고리듬은 전체 시퀀스로부터 하나의 스칼라 값을 얻어내는 알고리듬들이다.

여기서 accumulate()와 inner_product() 알고리듬은 다른 알고리듬처럼 algorithm 헤더 화일에 포함되어 있지 않고, numeric 헤더 화일에 포함되어 있다는 것을 기억하기 바란다.

13.6.1 조건을 만족하는 원소의 갯수 세기

count()와 count_if() 알고리듬은 각각 주어진 값 또는 주어진 조건을 만족하는 원소의

갯수를 알아내는데 사용한다. 각각의 알고리듬은 두가지 형태가 있는데, 하나는 일치되는

원소를 리턴값으로 반환하고, 다른 하나는 카운트 변수(대부분 정수)에 대한 레퍼런스를 인자로

받아서, 이 변수의 값을 증가시킨다(이 경우에는 값을 리턴하지 않는다). (후자의 경우 최근

표준에 남아 있는지를 확인할 것!)

전자의 경우는 최근 표준에서 채택되었고, 후자는 예전에 사용되던 것으로 두가지 이유때문에

아직도 남아있게 되었다. 첫째 이유는 backward compatibility 때문이고, 두번째 이유는

전자에 해당하는 알고리듬이 partial specialization 을 필요로 하는데, 아직은 이를 지원하는

컴파일러가 거의 없기 때문이다.

iterator_traits<InputIterator>::distance_typecount (InputIterator first, InputIterator last, const T& value);

iterator_traits<InputIterator>::distance_typecount_if (InputIterator first, InputIterator last, Predicate pred);

void count (InputIterator first, InputIterator last,             const T&, Size &);void count_if (InputIterator first, InputIterator last,          Predicate, Size &);(후자의 경우 표준에서 채택되었는지 확인할 것!)

The Resulting Count

다음 예제 프로그램은 예전의 count() 알고리듬을 사용한 예이다. count()를 호출하여

주어진 문자열에서 'e'의 갯수를 알아내고, count_if()를 호출하여 모음의 갯수를 알아내고

있다.

158

Page 159: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

void count_example()      // illustrate the use of the count algorithm{   int eCount = 0;   int vowelCount = 0;   char *text = "Now is the time to begin";   count(text, text + strlen(text), 'e', eCount);   count_if(text, text + strlen(text), isVowel, vowelCount);   cout << "There are " << eCount << " letter e's " << endl         << "and " << vowelCount << " vowels in the text:"         << text << endl;}

13.6.2 시퀀스를 하나의 값으로 유추하기

accumulate() 알고리듬이 생성하는 결과값은 시퀀스의 각 원소 사이에 이항 연산자를 놓아

얻어낸 값이다. 디폴트 연산자는 덧셈 연산자(+)이고, 이것은 다른 이항 함수로 바꿀 수 있다. 초기값(항등원)은 반드시 제공해야 한다. 이 값은 빈 시퀀스에 알고리듬을 적용했을 때의

리턴되는 값에 해당하며, 첫번째 계산할 때의 왼쪽 인자로 사용된다.

ContainerType accumulate (InputIterator first, InputIterator last,          ContainerType initial [, BinaryFunction ] );

예제 프로그램은 accumulate()를 사용하여 정수 vector에 담긴 값들의 합과 곱을

생성한다. 합의 경우에는 초기값(항등원)으로 0을, 디폴트 연산자 +를 사용하면 되고, 곱의

경우에는 초기값(항등원)으로 1을, 곱셉 연산자(times)를 4번째 인자로 별도로 명시하면 된다. void accumulate_example ()// illustrate the use of the accumulate algorithm{   int numbers[] = {1, 2, 3, 4, 5};// first example, simple accumulation   int sum = accumulate (numbers, numbers + 5, 0);   int product =          accumulate (numbers, numbers + 5, 1, times<int>());   cout << "The sum of the first five integers is " << sum << endl;   cout << "The product is " << product << endl;// second example, with different types for initial value   list<int> nums;

159

Page 160: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   nums = accumulate (numbers, numbers+5, nums, intReplicate);}list<int>& intReplicate (list<int>& nums, int n)      // add sequence n to 1 to end of list{   while (n) nums.push_back(n--);   return nums;}

초기값(항등원)이나 이항 함수의 리턴값이 반드시 컨테이너 타입과 일치할 필요는 없다. 이는

위의 2번 예에서 볼 수 있다. 여기서 초기값은 공 list이다. 이항 함수(위 예제의 끝에 정의되어

있음)는 list와 정수값을 인자로 취하고, list에 반복적으로 값들을 삽입한다. 삽입되는 값들은

인자로 주어진 정수값부터 1까지의 값을 포함하는 시퀀스이다. 예를 들어, 1 2 3 4 5를

입력으로 받으면, 1 2 1 3 2 1 4 3 2 1 5 4 3 2 1 가 결과 list가 된다. 13.6.3 일반화된 내적

n개의 원소를 가지는 두개의 시퀀스가 있다고 하고, 각각 a1, a2, ..., an과 b1, b2, ..., bn이라고 하자. 두 시퀀스의 내적은 대응되는 원소끼리 곱해서 이를 더한 것으로 a1*b1 + a2*b2 + ... + an * bn 으로 계산된다. 내적은 많은 과학 계산에서 나타난다. 예를 들어, 행과 열의 내적은 행렬곱 알고리듬의 핵심 연산이다. 일반화된 내적은 기본적으로 같은

계산과정을 사용하지만, 합과 곱 연산자를 다른 이항 함수로 바꿀 수 있다. 표준 라이브러리는

내적을 계산하기 위해 다음 알고리듬을 제공한다. ContainerType inner_product    (InputIterator first1, InputIterator last1,   InputIterator first2, ContainerType initialValue      [ , BinaryFunction add, BinaryFunction times ] );

inner_product() 알고리듬의 처음 3 개의 인자는 두개의 입력 시퀀스를 정의한다. 두번째

시퀀스는 시작 반복자만 명시하고, 적어도 첫번째 시퀀스만큼의 원소를 가지고 있어야 한다. 그

다음 인자는 합 연산자의 초기값(항등원)이다. 이것은 accumulate() 알고리듬에서

사용하는 항등원과 유사하다. 일반화된 내적 함수에서는 마지막 두개의 인자는 각각 덧셉

연산자와 곱셉 연산자 대신에 사용할 이항 함수가 된다.

예제 프로그램에서의 두번째 예는 인자로 함수를 사용하는 예를 보이고 있다. 곱셈 연산을 상등

검사로 바꾸고, 덧셈은 논리합(or)로 바꾸었다. 따라서, 같은 쌍이 하나라도 있으면 참이고, 그렇지 않으면 거짓을 리턴하게 된다. or 대신에 and 를 사용하면 모든 쌍이 같을 때만 참을

리턴하게 되어, 다음에 설명할 equal() 알고리듬과 똑같은 효과를 가지게 된다.

160

Page 161: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

void inner_product_example ()      // illustrate the use of the inner_product algorithm{   int a[] = {4, 3, -2};   int b[] = {7, 3, 2};

   // example 1, a simple inner product   int in1 = inner_product(a, a+3, b, 0);   cout << "Inner product is " << in1 << endl;

   // example 2, user defined operations   bool anyequal = inner_product(a, a+3, b, true,         logical_or<bool>(), equal_to<int>());   cout << "any equal? " << anyequal << endl;}

13.6.4 쌍별로 두개의 시퀀스를 비교하기

equal() 알고리듬은 각 쌍별로 두 시퀀스의 상등여부를 판별하는데 사용한다. 이항 조건자를

바꿈으로써, 두 시퀀스의 상등 여부를 다양하게 판별할 수 있다. 인자는 단순히 입력 반복자이면

된다.

bool equal(InputIterator first, InputIterator last,          InputIterator first2 [, BinaryPredicate] );

Equal and Mismatch

equal()은 알고리듬은 두번째 시퀀스가 첫번째 시퀀스만큼의 원소를 가지고 있다고

가정한다. 각각 대응되는 값들끼리 상등 검사를 하여 모두 같을 때 참을 리턴한다. 이

알고리듬은 상등 테스트 대신에 다른 이항 함수를 사용하여 모든 쌍이 이항 함수를 통해 주어진

조건을 만족하면 참을 리턴한다. 예제 프로그램에서는 greater_equal() 함수를 사용하고

있으며, 첫번째 시퀀스의 값들이 두번째 시퀀스내에 대응되는 값보다 항상 크거나 같을 때만

참을 리턴하게 된다.

void equal_example ()   // illustrate the use of the equal algorithm

161

Page 162: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

{   int a[] = {4, 5, 3};   int b[] = {4, 3, 3};   int c[] = {4, 5, 3};

   cout << "a = b is: " << equal(a, a+3, b) << endl;   cout << "a = c is: " << equal(a, a+3, c) << endl;   cout << "a pair-wise greater-equal b is: "       << equal(a, a+3, b, greater_equal<int>()) << endl;}

13.6.5 사전식 비교(lexical comparison)두 시퀀스의 사전식 비교의 아주 흔한 예로 두개의 단어를 비교하여 사전상에 나타난 순으로

배치하는 것을 들 수 있다. 두개의 단어를 비교할 때는 각 시퀀스의 원소(즉, 문자)들을 상대

시퀀스에 대응되는 문자와 비교한다. 두문자가 일치하면, 알고리듬은 다음 문자를 살피게 되고, 일치하지 않으면, 더 앞에 있는 문자를 포함하는 단어가 사전상으로 앞에 위치하게 된다. 예를

들어, everybody는 everything 보다 작게 되는데, 이는 everybody의 b가 everything의 t보다 작기 때문이다. 둘중의 한 시퀀스가 다른 시퀀스보다 먼저 끝나게 되면 끝나는

시퀀스가 다른 시퀀스보다 작은것으로 결정한다. 예를 들어, every는 everybody와

everything 보다 앞에 있게 되고, eve 뒤에 오게 된다. 마지막으로, 두 시퀀스가 동시에

끝나고 모든 문자들이 서로 같으면, 두 단어를 같은 것으로 결정한다.

lexicographical_compare() 알고리듬은 이것을 구현한 것으로, 첫번째 시퀀스가

두번째 시퀀스보다 순서상으로 앞에 있으면 참, 그렇지 않으면 거짓을 리턴한다. 이 알고리듬은

어떤 시퀀스에라도 적용할 수 있으며, array, string, vector, list 또는 표준 라이브러리가에서

사용되는 다른 데이터 구조에도 사용할 수 있다.

bool lexicographical_compare    (InputIterator first1, InputIterator last1,   InputIterator first2, InputIterator last2 [, BinaryFunction ] );

lexicographical_compare() 알고리듬은 두개의 시퀀스를 인자로 취하는 대부분의

다른 알고리듬과는 달리 두 시퀀스에 대해서 시작 반복자와 past-the-end 반복자를 모두

사용한다. 생략 가능한 5번째 인자로는 두 시퀀스의 원소를 서로 비교할 때 사용하는 이항

함수를 취한다.

예제 프로그램은 이 알고리듬을 문자열과 정수 배열에 적용한 예를 보이고 있다.

162

Page 163: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

void lexicographical_compare_example()   // illustrate the use of the lexicographical_compare algorithm{   char *wordOne = "everything";   char *wordTwo = "everybody";

   cout << "compare everybody to everything " <<      lexicographical_compare(wordTwo, wordTwo + strlen(wordTwo),         wordOne, wordOne + strlen(wordOne)) << endl;

   int a[] = {3, 4, 5, 2};   int b[] = {3, 4, 5};   int c[] = {3, 5};

   cout << "compare a to b:" <<       lexicographical_compare(a, a+4, b, b+3) << endl;   cout << "compare a to c:" <<       lexicographical_compare(a, a+4, c, c+2) << endl;}

13.7 시퀀스 생성 알고리듬

이 절에서 설명할 알고리듬은 모두 변환을 수행하여 주어진 시퀀스로부터 새로운 시퀀스를

생성하는데 사용되는 것들이다. 대부분의 경우 출력 시퀀스는 출력 반복자를 통해 가리키는데, 이는 이들 알고리듬을 사용하여 주어진 시퀀스(예, vector)를 덮어 쓸 수도 있다는 것을

의미한다. 또는, 삽입 연산자(2.4절)를 사용하여, set 이나 list 와 같은 가변 길이 구조에 새

원소들을 삽입하는 것도 가능하다. 마지막으로, 종종 어떤 경우에는 출력 반복자가 입력

반복자로 지정된 시퀀스와 같을 수가 있는데 이렇게 되면, in-place 변환을 하게 된다.

다른 함수들이 대부분 algorithm 헤더화일에 선언되어 있는 것과는 달리, partial_sum()과 adjacent_difference() 함수는 numeric 헤더 화일에 선언되어 있다.

163

Page 164: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.7.1 한개 또는 두개의 시퀀스 변환하기

transform() 알고리듬은 하나의 시퀀스를 변환시키는데 사용할 수도 있고, 두개의 시퀀스에

원소들의 쌍별로 이항 함수를 적용하여 새로운 시퀀스를 만들어 내기도 한다. 프로토타입은

다음과 같다.

OutputIterator transform (InputIterator first, InputIterator last,   OutputIterator result, UnaryFunction);OutputIterator transform    (InputIterator first1, InputIterator last1,   InputIterator first2,  OutputIterator result, BinaryFunction);

첫번째 형태는 시퀀스 각각의 원소에 단항 함수를 적용하는 것이다. 아래 주어진 예제

프로그램에서는 이를 사용하여 연결 list에 담긴 값들과 부호가 반대인 값들로 이루어진 vector를 만들어낸다. 입력 반복자와 출력 반복자를 같게 하면, 변환은 in-place로 적용되며, 예제

프로그램의 2번째 transform()이 이런식으로 사용되고 있다.

두번째 형태는 두개의 시퀀스를 인자로 받아서, 쌍별로 이항 함수를 적용한다. 이때 두번째

시퀀스는 적어도 첫번째 시퀀스만큼의 원소를 가지고 있어야 한다. 출력 반복자로 명시되는

결과 시퀀스는 두개의 입력 시퀀스중의 하나거나 또는, 제삼의 다른 시퀀스도 가능하다.

int square(int n) { return n * n; }

void transform_example ()// illustrate the use of the transform algorithm{// generate a list of value 1 to 6   list<int> aList;   generate_n (inserter(aList, aList.begin()), 6, iotaGen(1));

// transform elements by squaring, copy into vector   vector<int> aVec(6);   transform (aList.begin(), aList.end(), aVec.begin(), square);

// transform vector again, in place, yielding 4th powers   transform (aVec.begin(), aVec.end(), aVec.begin(), square);   

164

Page 165: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

// transform in parallel, yielding cubes   vector<int> cubes(6);   transform (aVec.begin(), aVec.end(), aList.begin(),      cubes.begin(), divides<int>());}

13.7.2 부분합(partial sum)

시퀀스의 부분합이란 바로 앞에 있는 원소들의 값을 모두 더하여 새로운 원소를 만들고, 이들

원소들로 구성된 새로운 시퀀스를 만들어내는 것이다. 예를 들어, vector 1 3 2 4 5 의

부분합은 새 vector 1 4 6 10 15 가 된다. 원소 4 는 1 + 3 으로 계산되었으며, 원소 6 은 1 + 3 + 2 로 게산된 것이다. 알고리듬 이름에 'sum'이란 단어가 사용되었지만, 실제로 이항 함수는

덧셈연산뿐만 아니라 임의의 이항함수가 모두 가능하다. 예제 프로그램에서는 부분곱으로 이를

설명하고 있다. 프로토타입은 다음과 같다.

OutputIterator partial_sum       (InputIterator first, InputIterator last,       OutputIterator result [, BinaryFunction] );

입력 반복자와 출력 반복자를 같게 함으로써 부분합 연산을 in-place 변환 연산으로 바꿀 수

있다. void partial_sum_example ()// illustrate the use of the partial sum algorithm{   // generate values 1 to 5   vector<int> aVec(5);   generate (aVec.begin(), aVec.end(), iotaGen(1));

   // output partial sums   partial_sum (aVec.begin(), aVec.end(),      ostream_iterator<int> (cout, " ")), cout << endl;

   // output partial products   partial_sum (aVec.begin(), aVec.end(),      ostream_iterator<int> (cout, " "),      times<int>());}

165

Page 166: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.7.3 인접차(adjacent difference)

시퀀스의 인접차(adjacent difference)는 시퀀스의 현재 원소와 바로 앞 원소와의 차를 새로운

시퀀스의 원소로 만드는 것이다. 새로운 시퀀스의 첫번째 원소는 바뀌지 않는다. 예를 들어, (1, 3, 2, 4, 5)와 같은 시퀀스는 (1, 3-1, 2-3, 4-2, 5-4)로 변환되고, 이런식으로 하여 (1, 2, -1, 2, 1)와 같은 시퀀스를 얻게 된다.

partial_sum() 알고리듬에서와 마찬가지로, 임의의 이항 함수가 사용될 수 있으므로, 'difference'라는 단어가 그리 정확한 것은 아니다. 예를 들어, 이 시퀀스의 인접합(adjacent sum)은 (1, 3+1, 2+3, 4+2, 5+4)가 된다. 인접차 알고리듬의 프로토타입은 다음과 같다.

OutputIterator adjacent_difference (InputIterator first,    InputIterator last, OutputIterator result [, BinaryFunction ]);

입력 반복자와 출력 반복자를 같게 하여, 공차 연산을 in-place 변환으로 바꿀 수 있다. void adjacent_difference_example()// illustrate the use of the adjacent difference algorithm{// generate values 1 to 5   vector<int> aVec(5);   generate (aVec.begin(), aVec.end(), iotaGen(1));

// output adjacent differences   adjacent_difference (aVec.begin(), aVec.end(),      ostream_iterator<int,char> (cout, " ")), cout << endl;

// output adjacent sums   adjacent_difference (aVec.begin(), aVec.end(),      ostream_iterator<int,char> (cout, " "),      plus<int>() );}

13.8 기타 알고리듬

마지막으로 이절에서는 나머지 알고리듬을 설명한다.

166

Page 167: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

13.8.1 콜렉션 내의 모든 원소에 함수 적용하기

for_each() 알고리듬은 3 개의 인자를 취한다. 처음 2 개는 계산에 쓰일 시퀀스를 나타내는

반복자이고, 3 번째 인자는 한개의 인자를 가지는 함수이다. for_each() 알고리듬은

시퀀스내의 값에 함수를 적용하는데, 이때 시퀀스내의 담긴 각각의 값을 적용할 함수의 인자로

넘겨주게 된다. 프로토타입은 다음과 같다.

Function for_each    (InputIterator first, InputIterator last, Function);

예를 들어, 다음 코드는 print_if_leap() 함수를 사용하여 1900년과 1997년 사이에

나타나는 윤년을 출력한다.    cout << "leap years between 1990 and 1997 are: ";   for_each (1990, 1997, print_if_leap);   cout << endl;

Results Produced by Side Effect

인자로 주어지는 함수는 시퀀스내의 각 원소에 대해 단 한번씩만 호출되어야 한다. for_each() 알고리듬은 3 번째 인자를 결과로 리턴하지만, 보통 잘 쓰이지 않는다.

다음 예는 포도주의 양조 년도를 나타내는 정수값의 배열을 훑어가면서, 윤년에 해당하는 것을

출력하는 코드이다.

   int vintageYears[] = {1947, 1955, 1960, 1967, 1994};   ...

   cout << "vintage years which were also leap years are: ";   for_each(vintageYears, vintageYears + 5, print_if_leap);   cout << endl;side effect가 반드시 출력일 필요는 없다. 대문자의 갯수를 세는 countCaps() 함수를 가지고

있다고 하자. int capCount = 0;

void countCaps(char c) { if (isupper(c)) capCount++; }다음 예제는 문자열에 포함된 대문자의 갯수를 세고 있다.    string advice = "Never Trust Anybody Over 30!";

167

Page 168: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

   for_each(advice.begin(), advice.end(),countCaps);   cout << "upper-case letter count is " << capCount << endl;

18장: 예외 처리(exception handling)

18.1 개요

표준 C++ 라이브러리는 에러 처리를 위한 클래스들을 제공하는데, 이 클래스들은 C++의 예외

처리(exception handling) 기능을 사용하고 있다. 표준 C++ 라이브러리는 크게 논리 에러

(logic error)와 실행시 에러(runtime error)로 나누어 에러 모델을 구현하고 있다.

logic error 는 프로그램의 내부 로직상의 문제로 인해 발생하는 에러를 의미하며, 이러한

에러들은 사전에 충분히 예방할 수 있는 에러이다.

runtime error 는 대개의 경우, 사전에 예방하기가 힘든 에러이다. 다시 말해서, 실행해보기

전까지는 어떤 문제가 발생할지 예측하기가 힘든 에러라는 것이다. 이러한 에러들은

프로그램에서 제어할 수 있는 영역을 벗어나 있는 외부 프로그램 실행 환경상의 요인 때문에

발생하는 에러들이다. 조금 심한 예를 들면, 네트워크 선로의 문제라든지, 하드웨어적인 결함이

발생한 경우와 같은 것들을 들 수 있겠다.

18.1.1 Include 화일

#include <stdexcept>

18.2 표준 exception 계층도

표준 라이브러리는 앞에서 설명했던 것처럼 크게 두 종류의 에러 모델을 여러개의 클래스로

구현하고 있다. 이 클래스들은 stdexcept 헤더 화일에 정의되어 있으며, 이를 이용하여

라이브러리가 throw 한 exception 들을 catch 하거나, 여러분이 작성한 코드내에서

exception 을 throw 할 수 있다. 클래스들은 상속관계로 서로 연결되어 있으며, 이들간의

상속관계는 다음과 같다. exception

o logic_error

168

Page 169: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

domain_error invalid_argument length_error out_of_range

o runtime_error range_error overflow_error

logic_error 클래스와 runtime_error 클래스는 exception 클래스를 상속하고 있으며, 이외의 모든 exception 관련 클래스들은 logic_error 클래스와 runtime_error 클래스 중

하나를 상속하고 있다.

18.3 exception 사용하기

표준 라이브러리의 컴포넌트에서 명시적으로 throw 한 모든 exception 은 표준 exception hierarchy 에 속한 클래스들의 객체들이다. 이들 클래스의 레퍼런스 매뉴얼을 보면 어떤 함수가

어떤 exception 을 throw 하는지 살펴볼 수 있는데, 이렇게 살펴보고 나서, 특정 exception 을

catch 할지, 아니면, throw될 것으로 예상되는 모든 exception 을 catch 할지(base class 인

exception 을 명시)를 결정하면 된다.

예를 들어, string 의 멤버 함수 insert 를 호출하는데 있어서, 삽입 위치를 나타내기 위해

주어지는 인자값이 부적절한 값을 가지게 될지 모르는 상황에서는 다음과 같이 코딩을 작성해야

할 것이다.

string s;int n;...try { s.insert(n,"Howdy");} catch (const exception& e){ // exception 처리 루틴

}

169

Page 170: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

exception 을 throw 하고 싶다면, 적절한 타입의 exception 을 생성하고, 적당한 에러

메시지를 부여한 뒤에 throw 하기만 하면 된다. 예를 들면 다음과 같다. ...if (n > max) throw out_of_range("Your past the end, bud");

exception 클래스는 모든 exception 관련 클래스의 base 클래스이며, 표준 인터페이스를

정의하고 있다. 이 인터페이스는 what()이라는 멤버 함수를 포함하고 있는데, 이 함수는

exception 에 함께 담겨져 있는 에러 메시지를 추출하여 리턴한다. 그렇기 때문에, 이 함수는

catch 절에서 특히 자주 사용된다. 예제는 18.4 절 에서 살펴 볼 수 있다.

base exception 을 throw 할 때는 아래와 같이 하면 된다.

throw exception;

그런데, 위와 같은 코드는 실제 상황에서는 그다지 자주 사용하지 않는다. 왜냐하면, 이

exception 을 catch 하는 입장에서는 해당 exception 이 어떤 타입의 에러를 의미하는지 알

방도가 없기 때문이다. 따라서, 실제 상황에서는 위와 같이 base exception 을 throw 하는

경우는 거의 없고, 대신에 exception 클래스의 하위 클래스인 logic_error 클래스나 또

이것의 하위 클래스인 out_of_range 클래스와 같은 기타의 것들을 사용하게 된다. 물론, 프로그래머가 이들 클래스를 상속하여 새로운 exception 클래스를 정의할 수도 있다. 예를

들면 다음과 같다.

class bad_packet_error : public runtime_error{ public: bad_packet_error(const string& what);};

if (bad_packet()) throw bad_packet_error("Packet size incorrect");

위 예제는 표준 C++ 라이브러리의 exception 클래스들이 프로그래머에게 기본적인 에러

모델을 제공하고 있으며, 이것을 기반으로 프로그래머들은 자신의 애플리케이션에 적합한 에러

탐지와 통보를 위한 메쏘드를 작성할 수 있어야 한다는 것을 보여주고 있다.

170

Page 171: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

18.4 예제 프로그램

#include <stdexcept>#include <string>

static void f() { throw runtime_error("a runtime error"); }

int main (){ string s; try { s.replace(100, 1, 1, 'c'); } catch (const exception& e) { cout << "Got an exception: " << e.what() << endl; }

try { f(); } catch (const exception& e) { cout << "Got an exception: " << e.what() << endl; } return 0;}

20장: complex

20.1 개요

171

Page 172: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

complex 클래스는 템플릿 클래스이다. 복소수와 복소수 연산들이 필요할 때 사용하면 된다. complex 클래스는 C++을 다루는 책들에서 클래스를 정의하는 방법을 설명할 때 자주

쓰이는 예제라서, C++을 공부하셨던 분들이라면 한번쯤 정의해봤을 클래스일 것이다. C++ 표준에 포함된 complex 클래스는 템플릿으로 정의되어 있고, 복소수에 정의되어 있는

연산들이 C++에서 제공하는 수치 관련 타입들(int, float, double, long double, ...)과

사용하는데 아무런 제약이 없도록 만들어져 있다.

20.1.1 Include 화일

복소수를 사용하는 프로그램은 complex 헤더화일을 포함시켜야 한다.

#include <complex>

20.2 복소수의 생성과 그 사용법

여기서는 복소수를 생성하는 방법과 복소수들간의 연산에 관해 설명하기로 한다.

20.2.1 복소수의 선언, 극형식, 보수 구하기

complex 타입의 객체를 선언하기 위해서는 먼저 복소수의 실수부와 허수부의 타입을

무엇으로 할지를 결정해야 한다. 복소수의 실수부와 허수부의 타입은 템플릿 인자로 넘겨주며, 이때, 인자의 타입은 C++에서 제공하는 부동소수 데이터 타입만이 허용된다. 즉, float, double, long double 세가지만 가능하다는 것이다.

complex 클래스는 네가지 형태의 생성자를 가지고 있다. 1)인자가 주어지지 않으면, 실수부와 허수부가 모두 0 인 복소수를 생성하고, 2)인자가 하나만 주어지면, 이는 실수부의

값으로 할당되며, 허수부는 0 인 복소수를 생성한다. 3)인자가 두개인 경우에는 실수부가 첫번째

인자의 값을 허수부가 두번째 인자의 값을 가지는 복소수를 생성한다. 4)마지막은 복사

생성자로서, 다른 복소수를 인자로 받아 실수부와 허수부가 동일한 복소수를 생성해낸다.

complex<double> com1; // 0 + 0i complex<double> com2(3.14); // 3.14 + 0i complex<double> com3(1.5, 3.14); // 1.5 + 3.14i complex<double> com4(com2); // 3.14 + 0i (com2 와 동일)

172

Page 173: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

당연한 말이지만, 복소수간에는 서로 대입이 가능하다. 그리고, 복소수 타입의 변수에 실수값을

대입할 수 있는데, 이 때 실수부는 우변의 값을 가지게 되고, 허수부는 0 값을 가지게 된다. 복소수 변수에 실수값을 대입할 때는 실수를 복소수로 바꿔주는 변환 생성자(conversion constructor)가 필요하게 되는데, 이 때 앞에서 설명한 인자가 하나인 생성자를 사용한다.

com1 = com3; // com1: 1.5 + 3.14i com3 = 2.17; // com3: 2.17 + 0i

polar() 함수는 극형식(크기와 위상각으로 표현)에 해당하는 복소수값을 알아내는데 사용한다.

com4 = polar(5.6, 1.8); // 5.6 * ( cos(1.8) + sin(1.8) i)

conj()는 인자로 주어진 복소수의 보수를 구할 때 사용한다. 주어진 복소수가 x+yi 라면, 이것의 보수는 x-yi 가 된다.

complex<double> com5 = conj(com3); // com5: 1.5 - 3.14i

▶ 함수와 멤버 함수

복소수에 관련된 연산들은 real()과 imag()를 제외하면(이들은 멤버함수), 나머지는 모두

멤버함수가 아닌 일반 함수를 통해 수행된다. 20.2.2 복소수의 실수부와 허수부

멤버함수로 제공되는 real()와 imag()는 각각 복소수의 실수부와 허수부를 반환한다. 이들은 멤버함수뿐만 아니라, 일반 함수의 형태로 호출할 수도 있다. 다음 두 줄은 완전히

똑같은 결과를 출력한다.

cout << com1.real() << "+" << com1.imag() << "i" << endl; cout << real(com1) << "+" << imag(com1) << "i" << endl;

20.2.3 복소수의 사칙연산

+, -, *, / 연산자들은 각각 복소수의 덧셈, 뺄셈, 곱셈, 나눗셈을 수행하는데 사용한다. 이들

연산자는 두 복소수간의 사칙연산을 수행하며, 물론 복소수와 실수간의 사칙연산에도 사용할 수

있다. +=, -=, *=, /= 연산자도 정의되어 있다.

cout << com1 + com2 << endl; cout << com1 - 3.14 << endl; cout << 2.75 * com2 << endl; com1 += com3 / 2.0;

173

Page 174: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

단항 연산자 +와 -도 정의되어 있다. 단항연산자 -는 실수부와 허수부의 부호를 바꿔준다.

20.2.4 복소수의 비교

== 연산자와 != 연산자를 사용하여 복소수간의 상등관계를 알아볼 수 있다. 두 복소수의

상등관계는 실수부와 허수부가 모두 같을 때 성립한다. 단 복소수의 비교시에 반드시 유의할

점이 한가지 있는데, 이는 복소수간에는 대소 관계가 정의되어 있지 않다는 것이다. 따라서, 복소수간에는 <, >, <=, >=와 같은 비교 연산자를 적용할 수 없다.

20.2.5 스트림 입출력

복소수는 입출력 스트림을 이용한 읽기, 쓰기가 가능하다. 쉽게 말해, '<<'연산자와 '>>' 연산자를 이용하여 복소수의 출력과 입력을 수행할 수 있다는 것이다. 복소수를 출력할 때는

양쪽에 괄호를 씌워서 출력하는데, 예를 들어, 복소수 '3+4i'는 (3,4)와 같이 출력되며, '3'과

같이 허수부가 0 인 복소수의 경우에는 (3)으로 출력된다.

20.2.6 복소수의 놈(norm), 절대값, 위상각 - norm(), abs(), arg()norm() 함수는 복소수의 놈(norm)값을 반환한다. 놈(norm)이란 실수부와 허수부의 제곱의

합이다. abs() 함수는 복소수의 절대값을 반환하며, 놈(norm)의 제곱근에 해당한다. 이 두

함수는 모두 멤버함수가 아닌, 복소수를 인자로 가지는 일반 함수이다.

cout << norm(com2) << endl; cout << abs(com2) << endl;

복소수의 유향 위상각은 arg()를 사용하여 얻어낼 수 있다.

cout << com4 << " in polar coordinates is " << arg(com4) << " and " << norm(com4) << endl;

20.2.7 삼각함수

기존에 실수형 타입에 대해 정의되어있던 삼각함수들(sin(), cos(), tan(), sinh(), cosh(), tanh())은 복소수를 인자로 받아들일 수 있도록 확장되었다. 이들 각각은 복소수를

인자로 받아 복소수값을 반환한다.

174

Page 175: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

20.2.8 초월함수

초월함수(exp(), log(), log10(), sqrt())들도 복소수를 인자로 받을 수 있게 확장되었다. 복소수를 인자로 받아 복소수를 반환한다.

표준 라이브러리에서는 여러가지 형태의 지수함수 pow()를 정의하고 있는데, 밑이

복소수이고 지수가 정수/복소수/실수인 것, 밑이 실수이고 지수가 복소수인 것들이 있다.

20.3 예제 프로그램 - 이차방정식의 근

이차방정식 ax2 + bx + c = 0 의 근은 다음 공식으로 구할 수 있다.

x = (-b + sqrt(b2 - 4ac)) / 2a     또는

x = (-b - sqrt(b2 - 4ac)) / 2a

다음 함수는 이차방정식을 구성하는 각항의 계수를 인자로 받아 두근을 구한 뒤, 이들을 pair 객체에 담아 반환한다.

typedef complex<double> dcomplex;

pair<dcomplex, dcomplex> quadratic(dcomplex a, dcomplex b, dcomplex c) { dcomplex root = sqrt(b * b - 4.0 * a * c); a *= 2.0; return make_pair((-b + root) / a, (-b - root) / a); }

175

Page 176: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

부록: Standard C++ Library 관련 자료

  References

The Second Committee Draft( 일명 CD2)

STL에 관해 언급하는 글등에서, 특히 뉴스그룹에서 'CD2'란 단어를 자주 접하셨을 겁니다. C++ 언어에 관한 semantic이나 기타 라이브러리 구현 이슈등이 논란이 될 경우 많이 언급하곤 합니다. 이

CD2란 것은 1996년 12월에 나온 것으로 당시 draft 단계에 있던 C++ 표준에 관한 문서입니다. IS(International Standard)가 최종 확정된 현 시점에서는 돈으로 공식 문서를 사지 않는 한, 일반

사람들이 얻을 수 있는 가장 최근의 C++ 표준 관련 문서라고 할 수 있습니다.

【참고 1】 C++ IS 문서는 인터넷에서 카드로 $18 에 구입할 수 있으며, 즉시 다운로드 할 수

있습니다. PDF 형식으로 되어 있고, 총 776 페이지입니다. (ANSI Electronic Standards Store) 【참고 2】 CD2 는 HTML 이외에도 PS, PDF 등 다양한 형식으로 구할 수 있습니다. (The ISO/ANSI C++ Draft)

STL Caveats

이곳은 96년 ?월호 'C++ Report'지에 실렸던 기사로, STL이 지니고 있는 문제점을 지적하고

있습니다. '컴파일러의 이해하기 힘든 에러메시지'와 '실행코드가 너무 커진다'는 것이 그

문제점들입니다. 96년도에 씌여진 기사지만, 아직까지도 이들 문제점은 명쾌한 해결을 보지 못하고

있습니다. STL는 많은 장점에도 불구하고, 실제 프로그램 개발자 측면에서 봤을때, 컴파일러와 코드

사이즈에 관한 문제점은 STL의 대중성에 상당한 장애로 작용할 수 있겠습니다.

SGI Standard Template Library Programmer's Guide

실리콘 그래픽스(SGI) 사에서 관리하고 있는 사이트로 STL에 관한 매우 방대한 자료를 제공하고

있습니다. 여기서 다루고 있는 STL은 SGI에서 자체적으로 구현한 STL이지만, 표준과 그다지 많은

차이는 없습니다. STL에 관한 자료가 부족했던 때부터 사람들이 가장 많이 참조했던 사이트였다고

생각됩니다. Tutorial이 아니고, 필요한 것을 찾아보는 manual의 성격을 띄고 있어, 초보자들에겐 좀

부담스러울 것입니다.

Dinkum Standard Template Library Reference

P.J. Plauger가 작성한 STL 레퍼런스입니다. P.J. Plauger는 MS Visual C++ 컴파일러에 탑재된

standard C++ library를 구현한 사람이고, 현재 Dinkumware사에 있습니다.

176

Page 177: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

■ VC++ Header File Fixes - MS Visual C++ v5.0과 v6.0에 포함된 Standard C++ Library의

버그 수정판(v3.06) (2000 년 7 월 31 일자 )

유명 STL implementation (무료 or 유료)

■ SGI STL implementation (free) http://www.sgi.com/tech/stl/download.html 특징: efficiency, thread safety, exception safety

■ STLport (free) http://www.stlport.org 오픈 소스이며, SGI STL 에 기반하고 있습니다.

■ RougeWave Standard C++ Library 대부분의 UNIX 서버 벤더들의 compiler 에게 제공되고 있으며, Boroland 의 C++ 컴파일러에

탑재되어 있는 표준 C++ 라이브러리가 이것을 바탕으로 하고 있습니다. Sun, HP, SGI, Compaq, Tandem, Fujitsu, ...

참고로 MS Visual C++의 표준 C++ 라이브러리는 Dinkumware 에서 제공합니다.

Iterators in the Standard C++ Library

이곳은 STL에서 가장 중요한 반복자(iterator)에 관해 설명하고 있습니다. 96년 11월/12월호 'C++ Report' 잡지에 실렸던 기사입니다.

ObjectSpace STL examples

이곳에는 ObjectSpace 라는 회사에서 판매하는 STL<ToolKit>이라는 라이브러리에 포함된

예제들을 공개한 것입니다. 약 300여개의 예제들을 살펴볼 수 있는데, C++ 표준이 확정되기 전에

만들어진 것이라, 표준과 약간 다를 수 있지만, 초보자들에게는 많은 도움이 될 것입니다.

Phil Ottewell's STL Tutorial

개인이 만들어 놓은 STL 튜토리얼 사이트입니다.

Microsoft Visual C++: Standard C++ Library Reference

Visual C++에서 제공하는 Standard C++ Library에 관한 정보를 얻을 수 있습니다. (Library reference의 내용은 Dinkumware 사의 그것과 동일)

  Newsgroups

177

Page 178: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

comp.std.c++ (google newsgroups) |  FAQ

comp.std.c++은 ANSI/ISO C++ 표준화 과정에서의 기술적인 문제들을 토의하고, C++ 언어와

라이브러리의 설계(design)와 표준화에 관해 토의하고 있습니다. 현재 C++의 표준화 작업이 마무리

되었으므로, 지금은 standard C++ library 전반에 걸친 다양한 토의가 이루어지고 있습니다. 단순히

C++ 프로그래밍 테크닉과 같은 C++에 대한 토의는 comp.lang.c++에서 이루어집니다.

comp.lang.c++ (google newsgroups) |  FAQ

comp.lang.c++은 C++에 관한 다양하고 자유로운 토의가 이루어집니다.

comp.lang.c++.moderated (google newsgroups)

comp.lang.c++.moderated는 특정 컴퓨팅환경에 국한되지 않는, C++에 관한 심도있는 토의가

이루어집니다. 유용한 트릭이나 테크닉들, C++과 관련된 소프트웨어 공학, C++의 설계 철학이나

design pattern등에 관한 주제도 다루어집니다. comp.lang.c++과 달리 이곳 성격에 맞지 않는

글들은 중재자(moderator)들에 의해 삭제당합니다. 따라서, 내용들이 상당히 진지하고 수준있는

글들이 많습니다.

gnu.g++.help (google newsgroups) |  FAQ, GCC Home

GNU C++ 컴파일러(g++)를 포함하고 있는 GCC(GNU Compiler Collection)와 그의 라이브러리에

관련된 사항들을 질문하고 토의합니다. GCC의 최신 버전은 2001년 6월 18일에 release된 3.0입니다.

microsoft.public.vc.stl (google newsgroups)

MS Visual C++ 컴파일러에서의 STL 사용과 관련된 내용들을 다루고 있습니다.

  Books

STL Tutorial and Reference Guide, Second Edition by David R. Musser, Gillmer J.

Derge, Atul Saini

C++ 표준이 확정되기 이전인 1996년에 First Edition이 출간되었는데, 2001년에

드디어, Second edition이 나왔습니다. STL 관련 서적중 가장 권위있는 필진을

자랑합니다. (first author가 David Musser이고 서문은 Alexander Stepanov가

써줬습니다.) 오늘날의 STL을 있게 한 핵심 멤버들이 이책에 참여한 만큼, STL의

설계 철학에 관한 설명이 돋보입니다. 책 제목처럼 레퍼런스 가이드로서도 손색이

없습니다.

The C++ Standard Library: A Tutorial and Reference by Nicolai M. Josuttis

178

Page 179: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

책 제목처럼 C++ 표준 라이브러리 전반에 관한 내용을 담고 있습니다. STL이 C++ 표준 라이브러리에서 차지하는 비중이 워낙 큰지라 대부분의 지면이 STL에

할애되어 있지만, 이외에도 I/O 스트림과 Internationalization에 관한 내용도

포함되어 있습니다. 내용이 알기 쉽게 설명되어 있어, STL을 시작하려는 분들에게

적합합니다.

Designing Components with the C++ STL by Ulrich Breymann

STL 프로그래밍 기법을 설명하고 있습니다. Musser의 책은 manual 성격이라면, 이책은 프로그래밍 기법에 치중하고 있습니다. 1998년에 출간되었다가, 2000년에

다시 업데이트 되었습니다.

The C++ Programming Language, Special Edition by Bjarne Stroustrup

C++ 언어의 창시자가 직접 쓴 책입니다. 현재 3rd Edition의 수많은 오류를

수정한 'Special Edition'이 나와 있으니, 아직 구입하지 못하신분은 하드 커버의

'Special Edition'을 구입하시기 바랍니다. '3rd Edition'을 가진 분들은 이곳을

방문하셔서 틀린 부분을 고치시기 바랍니다.

C++ Primer, Third Edition by Stanley B.Lippman, Jose Lajoie

Lippman의 그 유명한 "C++ Primer"의 3판. 아마 웬만한 프로그래머들은

stroustrup책과 이책 둘 중 하나는 가지고 있을 겁니다. 이곳을 방문하셔서 이

책에서 발견된 오류를 수정하시기 바랍니다. PDF 문서입니다. An interview with Microsoft's new Visual C++ Architect Stanley Lippman

  C++ Gurus

Alexander Stepanov

179

Page 180: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Home Page Unknown

E-mail Unknown

STL을 창시한 사람으로, 소련 모스크바 출신이며 generic programming의 신봉자이고, Meng Lee 등과 함께 standard template library를 처음 구현한 사람입니다. 다음은 A. Stepanov와의 인터뷰

내용입니다. STL의 창시자로부터 STL의 구현 philosophy를 직접 들을 수 있다는 건 행운입니다.■ Dr. Dobb's Journal과의 인터뷰(1995년)■ 이탈리아의 한 잡지사와의 인터뷰(19??년)

David R. Musser (photo)

Home Page http://www.cs.rpi.edu/~musser/

E-mail [email protected]

STL이 태동하던 시기때부터 STL에 깊숙히 관여해온 사람입니다. STL의 첫번째 구현작업에 동참했고, ANSI/ISO C++ 표준이 STL을 채택하는데 많은 공헌을 하였습니다. 현재 교수로 재직중입니다.

Bjarne Stroustrup (photo)

Home Pagehttp://www.research.att.com/~bs/homepage.html or http://www.research.att.com/info/bs

E-mail [email protected]

두말이 필요없는 사람입니다. C++의 창시자로 현재 AT&T 연구소에서 근무하고 있습니다. 상당히

고집이 세다고 알려져 있으며, Stepanov가 만든 standard C++ library를 C++ 표준에 포함시키는데

커다란 영향력을 행사한 인물입니다.

Andrew R. Koenig (photo)

Home Page http://www.research.att.com/info/ark

E-mail [email protected]

글을 많이 쓰는 편이라, 잡지나 뉴스그룹을 탐독하시는 분들에게는 낯익은 인물입니다. ISO/ANSI C++ Standard 회의에서 Stepanov의 STL을 소개하도록 한 인물입니다. 이때부터 STL이 표준에서

논의되기 시작했습니다.

Scott Meyers (photo)

Home Page http://www.aristeia.com/

E-mail [email protected]

Addison-Wesely의 Effective 시리즈를 통해 상당히 유명해진, C++ 컨설턴트입니다. 올해 6월달에

180

Page 181: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

STL을 주제로 "Effective STL"이란 책을 발간했습니다.

Nathan C. Myers

Home Page http://www.cantrip.org/ncm.html

E-mail [email protected]

ISO/ANSI C++ Standards committee 회원이었으며, traits 기법을 처음 제안한 사람입니다. C++ Report 지에 많은 기고를 하고 있습니다.

181

Page 182: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

반복자(iterator)

반복자(iterator)는 포인터와 상당히 비슷하며, 컨테이너에 저장되어 있는 원소들을 참조할 때

사용한다.

구간(range)

구간(range)이란 컨테이너에 담긴 값들의 시퀀스를 나타낸다. 구간은 반복자 한쌍으로

나타내며, 이 반복자들이 각각 시퀀스의 시작과 끝을 가리킨다.

반복자 구간(iterator range)

반복자 2 개를 사용하여 컨테이너의 특정 구간에 속한 원소들을 나타내고자 한다면, 두번째

반복자가 첫번째 반복자로부터 도달가능(reachable)해야 한다. 그렇지 않으면, 에러가

발생한다. 이는 알고리듬이나 컨테이너 내부에서 검사하지 않기 때문에, 프로그래머가 책임져야

한다.

end()는 끝이 아니다?

컨테이너를 다룰 때 자주 쓰이는 end()라는 멤버함수가 있는데, 이 때, end()가 가리키는 것은

컨테이너의 맨 마지막 원소가 아니라는 점에 항상 주의해야 한다. end()가 가리키고 있는 것은

맨 마지막 원소의 바로 다음번 원소이다. 따라서, 이러한 반복자를 past-the-end 반복자라

부르는 것이다. 종점을 지나쳐버린 곳을 가리키는 반복자라는 뜻이다. end() 멤버함수를 통해

얻어지는 반복자는 결과적으로 아무 의미가 없는 것을 가리키고 있는 셈이다. 따라서, 이

반복자가 가리키는 것을 참조하면 예상치 못한 오류를 초래하게 된다. 이렇듯 반복자를 다룰

때는 항상 조심해야 한다. 그리고, 참고로 아무 원소를 가지고 있지 않은 컨테이너의 begin()과

end()는 같아진다. 따라서, 다음과 같은 코드가 가능하다.

  bool empty(const STL_Container& container) {   return container.begin() == container.end();   }

182

Page 183: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

일반 포인터와 반복자

Because ordinary pointers have the same functionality as random access iterators, most of the generic algorithms in the standard library can be used with conventional C++ arrays, as well as with the containers provided by the standard library.

병렬 시퀀스

많은 수의 generic 알고리듬들이 두개의 병렬 시퀀스를 다루는 것들이다. 대부분의 경우, 두번째 시퀀스는 반복자 한쌍 대신 시작 반복자 한개만을 사용하여 표시되어진다. 이런

경우에는 두번째 시퀀스가 적어도 첫번째 시퀀스만큼의 원소 갯수를 가지고 있다고 가정하고

있다. 다만, 이 조건은 프로그래머가 책임져야 할 부분이다.

randomInteger()

여기서 설명한 randomInteger() 함수는 나중에 나오는 예제 프로그램에서 자주 사용될

것이다.

스트림 반복자(Stream Iterators)

An input stream iterator permits an input stream to be read using iterator operations. An output stream iterator similarly writes to an output stream when iterator operations are executed.

최종 표준에서 변경된 사항

최종 표준에서는 istream_iterator 클래스와 ostream_iterator 클래스의 두번째 템플릿

인자도 디폴트 인자로 바뀌었다. 따라서, 기존에 istream_iterator<int, char>와 같이 쓰던

것을 istream_iterator<int>와 같이 간단히 쓸 수 있게 되었다. 하지만, 아직 대부분의

컴파일러에서는 이를 지원하지 않을 지도 모른다. 최신 g++ 컴파일러에서는 이를 지원한다. 이

183

Page 184: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

사항은 현재 국내에 수입(3쇄까지만 수입)되어 있는 Stroustrup 의 'The C++ Programming Language, 3ed'에도 반영되어 있지 않으며, 5쇄부터는 제대로 반영되어 있다. 아래 링크를

클릭해서 'pg 558'과 'pg 559'에 관련된 부분을 참고하기 바란다.4 판에서 5 판으로 넘어가면서 바뀐 사항들

클래스 정의의 위치

unary_function 과 binary_function 에 대한 클래스 정의는 헤더화일 functional 에 있다. 즉

#include <functional>하면 된다.

함수 객체를 사용하여 레퍼런스 저장하기

A more complex illustration of the use of a function object occurs in the radix sorting example program given as an illustration of the use of the list data type in Section 6.3. In this program references are initialized in the function object, so that during the sequence of invocations the function object can access and modify local values in the calling program.

A Hot Idea 

The idea described here by the term binder is in other contexts often described by the term curry. This is not, as some people think, because it is a hot idea. Instead, it is named after the computer scientist Haskell P. Curry, who used the concept extensively in an influential book on the theory of computation in the 1930's. Curry himself attributed the idea to Moses Sch_nfinkel, leaving one to wonder why we don't instead refer to binders as "Sch_nfinkels."

원소 타입이 지켜야 할 사항

vector 가 담고 있는 원소들은 반드시 디폴트 생성자(인자가 없는 생성자)와 복사 생성자를

정의하고 있는 것이라야 한다. 그리고, 비록 vector 클래스의 멤버 함수에서 사용하지

않는다하더라도, 일부 generic 알고리듬에서 원소들간의 상등 관계나 대소 관계를 필요로 하기

184

Page 185: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

때문에, == 연산자와 < 연산자를 정의해 두는 것이 좋다.

생성자와 반복자

Because it requires the ability to define a method with a template argument different from the class template, some compilers may not yet support the initialization of containers using iterators. In the mean time, while compiler technology catches up with the standard library definition, the Rogue Wave version of the standard library will support conventional pointers and vector iterators in this manner.

메모리 관리

A vector stores values in a single large block of memory. A deque, on the other hand, employs a number of smaller blocks. This difference may be important on machines that limit the size of any single block of memory, because in such cases a deque will be able to hold much larger collections than are possible with a vector.

Costly Insertions

Even adding a single element to a vector can, in the worst case, require time proportional to the number of elements in the vector, as each element is moved to a new location. If insertions are a prominent feature of your current problem, then you should explore the possibility of using containers, such as lists or sets, which are optimized for insert operations.

Iterator Invalidation

Once more, it is important to remember that should reallocation occur as a result of an insertion, all references, pointers, and iterators that denoted a location in the now-deleted memory block that held the values before reallocation become invalid.

Initializing Count

185

Page 186: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Note that count() returns its result through an argument that is passed by reference. It is important that this value be properly initialized before invoking this function.

메모리 관리

Note that if you declare a container as holding pointers, you are responsible for managing the memory for the objects pointed to. The container classes will not, for example, automatically free memory for these objects when an item is erased from the container.

Iteration Invalidation

Unlike a vector or deque, insertions or removals from the middle of a list will not invalidate references or pointers to other elements in the container. This property can be important if two or more iterators are being used to refer to the same container.

검색 결과의 검사

The searching algorithms in the standard library will always return the end of range iterator if no element matching the search condition is found. Unless the result is guaranteed to be valid, it is a good idea to check for the end of range condition.

Sets, Ordered and Not

Although the abstract concept of a set does not necessarily imply an ordered collection, the set data type is always ordered. If necessary, a collection of values that cannot be ordered can be maintained in, for example, a list.

Set 과 Bag

186

Page 187: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

흔히 multiset 은 'bag'라고도 불리운다.

Initializing Sets with Iterators

As we noted in the earlier discussion on vectors and lists, the initialization of containers using a pair of iterators requires a mechanism that is still not widely supported by compilers. If not provided, the equivalent effect can be produced by declaring an empty set and then using the copy() generic algorithm to copy values into the set.

Pair 데이터 타입

만약 map 은 사용하지 않고 pair 데이터 타입만을 사용하려면, utility 헤더화일을

포함시키면 된다.

No Iterator Invalidation

Unlike a vector or deque, the insertion or removal of values from a set does not invalidate iterators or references to other elements in the collection.

Map 대신 사용되는 명칭들

보통 'map'과 같이 키를 통해 데이터를 찾을 수 있도록 되어 있는 구조를 'dictionary' 또는

'table' 또는 'associative array'라 부르기도 한다.

Pairs

8.2.3 절 을 참고하세요.

No Iterator Invalidation

187

Page 188: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Unlike a vector or deque, the insertion or removal of elements from a map does not invalidate iterators which may be referencing other portions of the container.

LIFO 와 FIFO

stack 은 흔히 'LIFO 구조'라 불리우며, queue 는 'FIFO 구조'라고 불리운다. LIFO 는 'Last In, First Out(후입선출)'의 약자로, stack 에 가장 나중에 삽입된 원소가 가장 먼저 삭제된다는

것을 의미하며, FIFO 는 'First In, First Out(선입선출)'의 약자로, queue 에 가장 먼저 삽입된

원소가 가장 먼저 삭제된다는 것을 의미한다.

Right Angle Brackets

Note that on most compilers it is important to leave a space between the two right angle brackets in the declaration of a stack; otherwise they are interpreted by the compiler as a right shift operator.

Defensive Programming

A more robust program would check to see if the stack was empty before attempting to perform the pop() operation.

A Queue That is Not a Queue

The term priority queue is a misnomer, in that the data structure is not a queue, in the sense that we used the term in Section 10, since it does not return elements in a strict first-in, first-out sequence. Nevertheless, the name is now firmly associated with this particular data type.

Initializing Queues from other containers

As we noted in earlier sections, support for initializing containers using a pair of iterators requires a feature that is not yet widely supported by compilers. While we

188

Page 189: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

document this form of constructor, it may not yet be available on your system.

Information on ...

Information on Heaps. Details of the algorithms used in manipulating heaps will not be discussed here, however such information is readily available in almost any textbook on data structures.

Finding Smallest Elements

We describe the priority queue as a structure for quickly discovering the largest

element in a sequence. If, instead, your problem requires the discovery of the smallest element, there are various possibilities. One is to supply the inverse operator as either a template argument or the optional comparison function argument to the constructor. If you are defining the comparison argument as a function, as in the example problem, another solution is to simply invert the comparison test.

Storing Pointers versus Storing Values

Other example programs in this tutorial have all used containers to store values. In this example the container will maintain pointers to values, not the values them-selves. Note that a consequence of this is that the programmer is then responsible for managing the memory for the objects being manipulated.

string 과 wstring

이 이후로는 string 에 대해서만 설명하겠지만, 여기서 설명하는 모든 연산들은 wstring 에도

똑같이 적용된다.

반복자를 이용한 초기화

189

Page 190: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Remember, the ability to initialize a container using a pair of iterators requires the ability to declare a template member function using template arguments independent of those used to declare the container. At present not all compilers support this feature.

반복자의 무효화

append 나 insert 와 같이 내부 string 버퍼의 재할당을 초래할 수 있는 연산을 수행한 뒤에는

반복자가 가리키는 값이 유효하다고 보장할 수 없다.

string 의 비교

비록 compare() 함수가 있긴 하지만, compare() 멤버함수를 직접 호출할 경우는 거의 없을

것이다. 대신에, 비교 연산자를 사용하는 것이 더 일반적이다. 어차피 이 비교 연산자에서도

compare()를 사용한다.

초기화 알고리듬들간의 차이점

모든 초기화 알고리듬은 컨테이너내에 담겨 있던 기존 원소들을 덮어쓴다. 이들 초기화

알고리듬들은 초기화할 때 사용되는 값들을 어디서 가져오느냐에 따라 차이가 난다. fill() 알고리듬은 한개의 값으로 여러 원소들을 초기화하고, copy() 알고리듬은 다른 컨테이너에

담긴 값들로 초기화하고, generate() 알고리듬은 함수를 호출하여 얻어낸 값으로 초기화한다.

여러개의 복사본을 덧붙이는 방법

copy() 알고리듬의 리턴값은 복사된 시퀀스의 끝을 가리키는 포인터이다. 값을 여러개

덧붙이려면, copy() 연산의 리턴값을 다음 copy()의 시작 반복자로 사용하면 된다.

copy_backward()

190

Page 191: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

copy_backward() 알고리듬에서의 "backward"는 원소들이 뒤집힌다는 의미가 아니라, 복사되는 순서가 거꾸로라는 의미이다. 복사된 뒤에 원소들의 상대적인 위치는 바뀌지 않는다.

병렬 시퀀스

많은 알고리듬들이 두 병렬 시퀀스에 대해 연산을 수행한다. 대부분의 경우, 두번째 시퀀스를

지정할 때는 시작 반복자와 끝 반복자를 모두 명시하지 않고, 시작 반복자만 명시하는 것이

보통이다. 그리고, 두번째 시퀀스는 최소한 첫번째 시퀀스보다 길어야 한다(알고리듬이

검사하지 않음). 이 조건을 만족하지 못하면, 에러가 발생한다.

검색 결과의 검사

모든 검색 알고리듬은 검색 조건을 만족하는 원소를 찾지 못하면 end-of-sequence 반복자를

반환한다. end-of-sequence 반복자를 참조하는 것은 예상치 못한 결과를 초래하기 때문에, 이

결과값을 사용하기 전에 그것이 end-of-sequence 반복자가 아닌지를 검사하는 것이 중요하다.

set 과 map 의 검색

These algorithms perform a linear sequential search through the associated structures. The set and map data structures, which are ordered, provide their own find() member functions, which are more efficient. Because of this, the generic find() algorithm should not be used with set and map.

Searching Sets and Maps

The basic_string class provides its own versions of the find_first_of and find_end algorithms, including several convience overloads of the basic pattern indicated here.

Speed of Search

191

Page 192: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

In the worst case, the number of comparisons performed by the algorithm search() is the product of the number of elements in the two sequences. Except in rare cases, however, this worst case behavior is highly unlikely.

Speed of Find_end

As with search, n the worst case, the number of comparisons performed by the algorithm find_end() is the product of the number of elements in the two sequences.

Largest and Smallest Elements of a Set

The maximum and minimum algorithms can be used with all the data types provided by the standard library. However, for the ordered data types, set and map, the maximum or minimum values are more easily accessed as the first or last elements in the structure.

Partitions

While there is a unique stable_ partition() for any sequence, the partition() algorithm can return any number of values. The following, for example, are all legal partitions of the example problem.

2 4 6 8 10 1 3 5 7 9

10 8 6 4 2 5 7 9 3 1

2 6 4 8 10 3 5 7 9 1

6 4 2 10 8 5 3 7 9 1.

Ordering Permutations 

192

Page 193: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Permutations can be ordered, with the smallest permutation being the one in which values are listed smallest to largest, and the largest being the sequence that lists values largest to smallest. Consider, for example, the permutations of the integers 1 2 3. The six permutations of these values are, in order:

1 2 3

1 3 2

2 1 3

2 3 1

3 1 2

3 2 1

Notice that in the first permutation the values are all ascending, while in the last permutation they are all descending.

What is a Name?

The algorithms in this section set up a sequence so that the desired elements are moved to the front. The remaining values are not actually removed, but the starting location for these values is returned, making it possible to remove these values by means of a subsequent call on erase(). Remember, the remove algorithms do not actually remove the unwanted elements.

The Resulting Count

Note that if your compiler does not support partial specialization then you will not have the versions of the count() algorithms that return the sum as a function result, but instead only the versions that add to the last argument in their parameter list, which is passed by reference. This means successive calls on these functions can be used to produce a cumulative sum. This also means that you must initialize the variable passed to this last argument location prior to calling one of

193

Page 194: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

these algorithms.

Equal and Mismatch

By substituting another function for the binary predicate, the equal and mismatch algorithms can be put to a variety of different uses. Use the equal() algorithm if you want a pairwise test that returns a boolean result. Use the mismatch() algorithm if you want to discover the location of elements that fail the test.

Results Produced by Side Effect

The function passed as the third argument is not permitted to make any modifications to the sequence, so it can only achieve any result by means of a side effect, such as printing, assigning a value to a global or static variable, or invoking another function that produces a side effect. If the argument function returns any result, it is ignored.

More Sorts

Yet another sorting algorithm is provided by the heap operations, to be described in Section 14.8.

Heaps and Ordered Collections

Note that an ordered collection is a heap, but a heap need not necessarily be an ordered collection. In fact, a heap can be constructed in a sequence much more quickly than the sequence can be sorted.

함수와 멤버 함수

복소수에 관련된 연산들은 real()과 imag()를 제외하면(이들은 멤버함수) 나머지는 모두

멤버함수가 아닌 일반 함수를 통해 이루어진다.

194

Page 195: jepi.tistory.com · Web viewsjjung/stl About this site ... 이곳은 최근 표준화 작업을 마친 ISO/ANSI C++에 포함된 표준 C++ 라이브러리(standard C++

Two Mechanisms, One Purpose

For reasons of compatibility, the numeric_limits mechanism is used as an addition to the symbolic constants used in older C++ libraries, rather than a strict replacement. Thus both mechanisms will, for the present, exist in parallel. However, as the numeric_limits technique is more uniform and extensible, it should be expected that over time the older symbolic constants will become outmoded.

195