학습목표

  • C++ 표준 string 클래스
  • auto_ptr 템플릿, unique_ptr 템플릿, shared_ptr 템플릿
  • 표준 템플릿 라이브러리(STL)
  • 컨테이너 클래스
  • 이터레이터(iterator)
  • 함수 객체(functor)
  • STL 알고리즘
  • initializer_list 템플릿

16.1 string 클래스

string 클래스는 string 헤더 파일을 통해 지원됨
string 클래스에는 문자열 대입, 문자열 결합, 문자열 비교, 개별 문자에 대한 접근, 문자열 안에 있는 문자나 부분 문자열의 검색 등을 포함하는 오버로딩 연산자들, 여러가지 생성자들 및 그 밖의 문자열 관련 메소드들이 포함되어있음

문자열 생성

string의 생성자에는 여러 종류가 존재

  • string(const char * s) : 객체를 s가 지시하는 NBTS(null-byte-terminated-string)으로 초기화
  • string(size_type n, char c) : 문자 c로 모두 초기화된 원소 n개의 객체 생성
  • string(const string & str) : 객체를 string 객체 str로 초기화함(복사 생성자 사용)
  • string() : 크기가 0인 디폴트 객체 생성
  • string(const char * s, size_type n) : 객체를 s가 지시하는 NBTS로 초기화하되 최대 n개의 문자까지 진행
  • template<class Iter> string(Iter begin, Iter end) : 객체를 [begin, end)의 범위에 있는 값들로 초기화, begin과 end는 포인터와 비슷하게 위치를 지정하는 역할을 함
  • string(const string & str, size_type pos, size_type n = npos) : 객체를 string 객체 str로 초기화하는데 이 때 pos의 위치에서 시작해서 끝까지 가거나 n개의 문자만큼 진행함(단 str의 끝을 넘어갈 수는 없음)
  • string(string && str) noexcept : C++11 이상에서 지원, 객체를 string 객체 str로 초기화하며 str은 바뀔 수 있음
  • string(initializer_list<char> il) : C++11 이상에서 지원, string 객체를 초기자 목록 il에 있는 문자로 초기화
// str1.cpp

#include <iostream>
#include <string>

int main()
{
	using namespace std;
	string one("Lottery Winner!");
	cout << one << endl;

	string two(20, '$');
	cout << two << endl;
	
	string three(one);
	cout << three << endl;
	
	one += " Oops!";
	cout << one << endl;
	two = "Sorry! That was ";
	three[0] = 'P';
	
	string four;
	four = two + three;
	cout << four << endl;
	
	char alls[] = "All's well that ends well";
	string five(alls, 20);
	cout << five << "!\n";

	string six(alls + 6, alls + 10);
	cout << six << ", ";

	string seven(&five[6], &five[10]);
	cout << seven << "...\n";

	string eight (four, 7, 16);
	cout << eight << "in motion!" << endl;
	return 0;
}

오버로딩 연산자 + 사용시 임시 string 객체가 생성됨
배열의 이름과는 달리 객체의 이름은 객체의 주소로 간주되지 않기 때문에 string seven(five + 6, five + 10);과 같은 형태의 구문은 동작하지 않음


string 클래스 입력

C 스타일 문자열의 경우

  • cin >> info; : 한 단어 읽음
  • cin.getline(info, 100); : 한 행을 읽고 \n은 버림
  • cin.getline(info, 100, ':'); : :까지 읽고 :은 버림
  • cin.get(info. 100); : 한 행을 읽고 \n을 큐에 남겨둠

string 객체의 경우

  • cin >> stuff; : 한 단어를 읽음
  • getline(cin, stuff); : 한 행을 읽고 \n은 버림
  • getline(stuff, ':') : :까지 읽고 :은 버림

string 객체는 입력 문자에 따라 객체의 크기가 자동으로 조절됨
따라서 getline()string 버전은 입력 문자들의 개수를 제한하는 수치 매개변수를 생략할 수 있음
string::npos가 문자열의 최대 허용 크기이며, 이는 일반적으로 unsigned int의 최대 크기임
getline()은 C 스타일 문자열 입력 버전에서는 istream 클래스의 메소드이기 때문에 cin을 호출 객체로 받고, string 버전에서는 독립형 함수이기 때문에 cin을 함수 매개변수로 받음

string 클래스를 위한 getline() 함수는 세가지 조건 중 하나가 발생할때까지 문자를 읽어 문자열로 저장함

  • 파일의 끝을 만났을 때 : 입력스트림의 eofbit가 설정되며 fail()eof() 메소드가 true를 리턴함
  • 구분 문자(디폴트는 \n)에 도달했을 때 : 구분 문자는 입력스트림으로부터 제거되고 저장되지 않음
  • 가능한 최대 문자 수(string::npos 혹은 대입에 사용할 수 있는 메모리의 바이트 수 중 더 적은 것)를 읽었을 때 : 입력 스트림의 failbit가 설정되며 fail() 메소드가 true를 리턴함

string 클래스를 위한 operator>>() 함수는 구분 문자가 아닌 화이트스페이스 문자까지 읽고 그 문자를 입력 큐에 남겨둠
이 때 화이트스페이스란 isspace() 함수가 true를 리턴하는 모든 문자에 해당함


문자열 작업

length()size() 멤버는 모두 어떤 문자열에 들어있는 문자 수를 리턴함
length() 메소드는 string 클래스의 오래된 버전부터 사용해왔으며, size() 메소드는 STL 호환성을 위해 추가됨

string 클래스의 find 메소드는 여러가지 변형이 있음

  • size_type find(const string & str, size_type pos = 0) const : 호출한 문자열의 pos 위치에서부터 시작하여 처음으로 발생하는 부분 문자열 str을 검색, 발견시 첫 문자의 인덱스를 리턴하며 찾지 못한 경우 string::npos를 리턴함
  • size_type find(const char * s, size_type pos = 0) const : 호출한 문자열의 pos 위치에서부터 시작해 처음으로 발생하는 부분 문자열 s를 검색, 발견시 첫 문자의 인덱스를 리턴하며 찾지 못한 경우 string::npos를 리턴함
  • size_type find(const char * s, size_type pos = 0, size_type n) const : 호출한 문자열의 pos 위치에서부터 시작해 s에 있는 처음 n개의 문자로 구성되는 부분 문자열이 처음 나오는 것을 검색, 발견시 첫 문자의 인덱스를 리턴하며 찾지 못한 경우 string::npos를 리턴함
  • size_type find(char ch, size_type pos = 0) const : 호출한 문자열의 pos 위치에서부터 시작해 문자 ch가 처음 나오는 것을 검색, 발견시 문자의 인덱스를 리턴하며 찾지 못한 경우 string::npos를 리턴함

string 라이브러리는 그 외에도 관련된 메소드들을 제공

  • rfind() : 가장 마지막으로 발생하는 부분 문자열 또는 문자를 검색
  • find_first_of() : 호출한 문자열에서 매개변수에 있는 문자들 중 가장 먼저 발생하는 문자를 검색
  • find_last_of() : 호출한 문자열에서 매개변수에 있는 문자들 중 가장 마지막에 발생하는 문자를 찾음
  • find_first_not_of() : 호출한 문자열에서 매개변수에 없는 첫 문자를 검색
  • find_last_not_of() : 호출한 문자열에서 매개변수에 없는 가장 마지막 문자를 검색
// hangman.cpp

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;

const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal",
	"danger", "ensign", "florid", "garage", "health", "insult",
	"jackal", "keeper", "loaner", "manage", "nonce", "onset",
	"plaid", "quilt", "remote", "stolid", "train", "useful",
	"valid", "whence", "xenon", "yearn", "zippy"};

int main()
{
	using std::cout;
	using std::cin;
	using std::tolower;
	using std::endl;
	std::srand(std::time(0));
	char play;
	cout << "영어 단어 게임을 하시겠습니까? <y/n> ";
	cin >> play;
	play = tolower(play);
	while (play == 'y')
	{
		string target = wordlist[std::rand() % NUM];
		int length = target.length();
		string attempt(length, '-');
		string badchars;
		int guesses = 6;
		cout << "수수께끼 단어를 추측해 보십시오.\n"
			 << length << "개의 문자로 이루어져있습니다.\n"
			 << "한 번에 한 문자씩 추측하십시오.\n"
			 << "틀릴 수 있는 기회 : " << guesses << "번\n";
		cout << "추측하는 단어 : " << attempt << endl;
		while (guesses > 0 && attempt != target)
		{
			char letter;
			cout << "문자를 추측하십시오 : ";
			cin >> letter;
			if (badchars.find(letter) != string::npos
				|| attempt.find(letter) != string::npos)
			{
				cout << "이미 추측한 문자입니다. 다시 하십시오. \n";
				continue;
			}
			int loc = target.find(letter);
			if (loc == string::npos)
			{
				cout << "땡! 틀렸습니다.\n";
				--guesses;
				badchars += letter;
			}
			else
			{
				cout << "딩동댕! 맞았습니다. \n";
				attempt[loc] = letter;
				loc = target.find(letter, loc + 1);
				while (loc != string::npos)
				{
					attempt[loc] = letter;
					loc = target.find(letter, loc + 1);
				}
			}
			cout << "추측하는 단어 : " << attempt << endl;
			if (attempt != target)
			{
				if (badchars.length() > 0)
					cout << "틀리게 추측한 문자들 : " << badchars << endl;
				cout << "틀릴 수 있는 기회 : " << guesses << "번\n";
			}
		}
		if (guesses > 0)
			cout << "그렇습니다. 그것이 수수께끼 단어입니다.\n";
		else
			cout << "안타깝습니다. 수수께끼 단어는 " << target << "입니다.\n";
		cout << "게임을 다시 하시겠습니까? <y/n> ";
		cin >> play;
		play = tolower(play);
	}
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

string 클래스가 제공하는 그 밖의 기능

// str2.cpp

//#include "xxx.h"
#include <iostream>
#include <string>

int main()
{
	using namespace std;
	string empty;
	string small = "bit";
	string larger = "Elephants are a girl's mest friend";
	
	cout << "크기 : \n";
	cout << "\tempty : " << empty.size() << endl;
	cout << "\tsmall : " << small.size() << endl;
	cout << "\tlarger : " << larger.size() << endl;
	
	cout << "용량 : \n";
	cout << "\tempty : " << empty.capacity() << endl;
	cout << "\tsmall : " << small.capacity() << endl;
	cout << "\tlarger : " << larger.capacity() << endl;
	
	empty.reserve(50);
	cout << "empty.reserve(50) 이후 용량 : "
		 << empty.capacity() << endl;
	return 0;
}

하나의 문자를 문자열에 추가할 때, 이웃 메모리가 이미 사용중일 가능성이 있으므로 해당 문자열의 메모리 바로 뒤에 이어서 저장할 수는 없음
따라서 새로은 블록을 대입하고 이전의 내용을 새로운 위치로 복사해야 하는데, 여기서 발생하는 비효율을 막기위해 C++ 시스템들은 실제 문자열보다 훨씬 큰 메모리 블록을 대입하여 문자열이 추가될 수 있는 공간을 제공함
추가된 문자열이 그 크기마저 초과할시 프로그램은 두 배 크기의 새로운 블록을 대입하여 연속적인 크기조절 없이 문자열이 늘어날 수 있도록 더 많은 공간을 제공함

capacity() : 현재 블록의 크기를 리턴
reserve() : 그 블록을 위한 최소 크기를 사용자가 요청하게함

open() 메소드가 매개변수로 C 스타일 문자열을 요구하기 때문에 c_str() 메소드를 사용하여 string 객체와 동일한 내용을 가지는 C 스타열 문자열을 가리키는 포인터를 리턴할 수 있음


string 다양성

실제 문자열 라이브러리는 아래 템플릿 클래스에 기초하고있음

template <class charT, class traits = char _traits<charT>,
		  class Allocator = allocator<charT>>
basic_string {...};

typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;
typedef basic_string<char32_t> u32string;

클래스에 위와 같은 typedef들이 들어있기 때문에 char형 뿐만 아니라 여러 형태의 문자열도 사용할 수 있음
traits 클래스는 값을 비교하는 방법 등의 해당 문자형에 관한 고유의 특징을 서술함
Allocator 클래스는 메모리를 관리하기 위해 사용되며, newdelete를 통상적인 방법으로 사용함



16.2 스마트 포인터 템플릿 클래스

스마트 포인터는 포인터처럼 행동하는 클래스 객체로, 몇 가지 추가 기능을 지님
일반 포인터와 달리 수명이 다했을 때 스마트 포인터가 지시하는 메모리도 파괴자를 통해서 함께 해제할 수 있음

스마트 포인터의 사용

스마트 포인터를 사용하기 위해서는 템플릿을 정의하는 memory 헤더 파일을 소스 코드에 포함해야함

// smrtptrs.cpp

#include <iostream>
#include <string>
#include <memory>

class Report
{
	private:
		std::string str;
	public:
		Report(const std::string s) : str(s)
			{ std::cout << "객체가 생성되었습니다!\n"; }
		~Report() { std::cout << "객체가 삭제되었습니다!\n"; }
		void comment() const { std::cout << str << "\n"; }
};

int main()
{
	{
		std::auto_ptr<Report> ps (new Report("auto_ptr"));
		ps->comment();
	}
	{
		std::shared_ptr<Report> ps (new Report("shared_ptr"));
		ps->comment();
	}
	{
		std::unique_ptr<Report> ps (new Report("unique_ptr"));
		ps->comment();
	}
	return 0;
}

스마트 포인터 템플릿의 클래스들은 명시적 생성자를 가지고있음
따라서 암시적 변환은 허용되지 않으며, 포인터를 변환하기 위한 자동 형 변환은 없어도 됨
스마트 포인터 템플릿 클래스들은 일반 포인터처럼 동작하기 때문에 같은 타입의 포인터를 가리키기 위해 일반 포인터를 대입할 수 있으나 문제가 생길 수 있음


스마트 포인터 고려사항

auto_ptr<string> ps (new string("I reigned ..."));
auto_ptr<string> vocation;
vocation = ps;

위 코드는 같은 객체에 대해 두번 해제를 시도하기 때문에 허용되지 않음

  • 복사를 수행하도록 대입 연산자를 정의하여 두 포인터가 서로 다른 객체를 지시하도록 만들어 해결
  • 하나의 스마트 포인터만이 특정 객체를 소유할 수 있도록 소유권 개념을 도입하여 해결 - auto_ptr, unique_ptr에서 사용
  • 하나의 특정한 객체를 참조하는 스마트 포인터들이 몇 개인지 추적하는 스마트 포인터를 생성하여 해결하며, 이를 참조 카운팅(reference counting)이라고 함 - shared_ptr에서 사용
// fowl.cpp

#include <iostream>
#include <string>
#include <memory>

int main()
{
	using namespace std;
	auto_ptr<string> films[5] = 
	{
		auto_ptr<string> (new string("Fowl Balls")),
		auto_ptr<string> (new string("Duck Walks")),
		auto_ptr<string> (new string("Chicken RUns")),
		auto_ptr<string> (new string("Turkey Errors")),
		auto_ptr<string> (new string("Goose Eggs"))
	};
	auto_ptr<string> pwin;
	pwin = films[2];

	cout << "다음은 최고의 조류 영화상 후보입니다\n";
	for (int i = 0; i < 5; i++)
		cout << *films[i] << endl;
	cout << "수상자는 " << *pwin << "!\n";
	cin.get();
	return 0;
}

pwin = films[2]; 구문으로 인해 film[2]가 객체에 대한 소유권을 포기하고, 더이상 해당 객체에 접근하지 않음
따라서 이후에 films[2]가 가리키는 문자열을 출력시 문제가 발생함
unique_ptr 사용시에는 pwin = films[2];에서 컴파일 오류가 발생함
그러나 auto_ptr이 아닌 shared_ptr을 쓰면 정상적으로 출력됨

  • pwinfilms[2]가 같은 객체를 가리키며 참조 카운트가 올라감
  • pwin의 파괴자가 참조 카운트를 줄이고, shared_ptr의 배열 멤버들이 해제되며 films[2]의 파괴자로 인해 참조 카운트가 0이되어 메모리가 해제됨

auto_ptr보다 unique_ptr이 더 좋은 이유

auto_ptr<string> p1(new string("auto")); // #1
auto_ptr<string> p2; // #2
p2 = p1; // #3

#3에서 p2가 문자열 객체에 대한 소유권 인수시 p1은 소유권을 상실하고, 이로 인해 p1과 p2의 파괴자가 동일한 객체에 대한 메모리를 해제하는 것을 방지함
단, p1이 유효한 값을 가지지 않기 때문에 문제가 발생함

unique_ptr<string> p3(new string("auto")); // #4
unique_ptr<string> p4; // #5
p4 = p3; // #6

이 경우에는 #6에서 컴파일 에러가 발생함
따라서 unique_ptrauto_ptr보다 더 안전함

unique_ptr<string> demo(const char * s)
{
	unique_ptr<string> temp(new string(s));
	return temp;
}

unique_ptr<string> ps;
ps = demo("Uniquely special");

스마트 포인터를 다른 스마트 포인터에 대입시 타이밍이 맞지 않는 문제가 발생할 수 있음
따라서 위와 같이 함수를 정의할 경우 demo() 함수에서 임시 unique_ptr를 리턴하기 때문에 문제가 발생하지 않음

  • psunique_ptr이 리턴한 객체의 소유권을 얻음
  • 리턴된 unique_ptr은 삭제됨
  • demo()가 리턴한 unique_ptr은 임시이므로 유효하지 않은 값에 접근할 가능성이 없어 금지된 대입이 발생하지 않음

하나의 unique_ptr에서 다른 unique_ptr에 대입을 시도할시 원본 객체가 중복되어있다면 시도가 허용되지 않음
이와같은 선택적 행동으로 인해 unique_ptrauto_ptr보다 우수하며, 따라서 auto_ptr은 C++17이후 사용이 금지됨

unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1);

unique_ptr을 다른 unique_ptr로 대입할 수 있는 std::move() 표준 라이브러리 함수가 존재함
unique_ptr은 배열으로도 사용될 수 있기 때문에 newnew[], deletedelete[]를 모두 가지고있음


스마트 포인터 선택

프로그램이 하나의 객체에 대해 하나 이상의 포인터를 사용할시에는 shared_ptr을 선택해야함

  • 포인터의 배열과 특정 값을 지정하기 위한 보조 포인터들이 있는 프로그램
  • 동일한 3개의 객체를 가리키는 2개의 객체가 있는 프로그램
  • 포인터의 STL 컨테이너가 있는 프로그램 등

프로그램이 같은 객체를 가리키기 위해 다중 포인터를 필요로하지 않는다면 unique_ptr를 사용해야함
이는 new로 대입된 메모리를 리턴하는 함수의 리턴 타입에 가장 적절한 선택임
하나의 unique_ptr에서 다른 unique_ptr에 대입할 수 있는 상황과 동일한 상황인 경우 unique_ptrshared_ptr에도 대입할 수 있으나, 원본이 rvalue여야만함



16.3 표준 템플릿 라이브러리

표준 템플릿 라이브러리(STL Standard Template Library)는 컨테이너(container), 이터레이터(iterator), 함수 객체(function object), 알고리즘(algorithm)을 나타내는 템플릿들의 집합을 제공함

  • 컨테이너 : 배열과 같이 여러개의 값을 저장할 수 있는 구성 단위
  • 알고리즘 : 배열을 정렬하거나 특정 값 검색 등 특별한 작업들을 수행하기 위해 사용하는 방법
  • 이터레이터 : 컨테이너 안에서 위치를 옮길 수 있도록 도와주는 객체, 포인터의 일반화
  • 함수 객체 : 클래스 객체 / 함수 포인터

STL은 일반화 프로그래밍(generic programmin)의 패러다임을 나타냄

vector 템플릿 클래스

vector는 임의로 접근할 수 있는 비슷한 값들의 집합을 저장함
vector 템플릿 객체를 생성할 때는 사용할 데이터형을 나타내기 위해 통상적인 <type> 표기와 동적 메모리 대입을 사용함

// vect1.cpp

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

const int NUM = 5;

int main()
{
	using std::vector;
	using std::string;
	using std::cin;
	using std::cout;
	using std::endl;
	vector<int> ratings(NUM);
	vector<string> titles(NUM);

	cout << NUM << "개의 책 제목과 책 등급(0-10)을 입력하십시오.\n";
	int i;
	for (i = 0; i < NUM; i++)
	{
		cout << i + 1 << "번 책의 제목을 입력하십시오 : ";
		getline(cin, titles[i]);
		cout << i + 1 << "번 책의 등급(0-10)을 입력하십시오 : ";
		cin >> ratings[i];
		cin.get();
	}
	cout << "감사합니다. 당신은 다음과 같이 입력하셨습니다 : \n"
		 << "등급\t제목\n";
	
	for (i = 0; i < NUM; i++)
		cout << ratings[i] << "\t" << titles[i] << endl;
	return 0;
}

vector에서 할 수 있는 것

모든 STL 컨테이너들은 기본적인 메소드들을 제공함

  • size() : 컨테이너에 있는 원소들의 개수를 리턴
  • swap() : 두 컨테이너의 내용을 교환
  • begin() : 컨테이너에 있는 첫번째 원소를 참조하는 이터레이터를 리턴
  • end() : 컨테이너에 있는 마지막 원소 바로 다음(past-the-end)을 참조하는 이터레이터를 리턴
vector<double>::iterator pd;
vector<double> scores;
pd = scores.begin();
*pd = 22.3;
++pd;

이터레이터는 포인터의 일반화로 실제 포인터일 수도 있고, 내용 참조(operator*())나 증가(operator++())와 같이 포인터를 닮은 연산들이 정의되어있는 객체일 수도 있음
포인터를 이터레이터로 일반화할시 다양한 컨테이너 클래스들에 일관된 인터페이스를 제공할 수 있음

vector<double> scores;
vector<double> new_scores;
double temp;
while (cin >> temp && temp >= 0)
	scores.push_back(temp);
cout << scores.size() << "개의 점수가 입력되었습니다.\n";
scores.erase(scores.begin(), scores.begin() + 2);
...
scores.insert(scores.begin(), new_scores.begin() + 1, new_scores.end());

vector 템플릿 클래스는 일부 STL 컨테이너들만이 가지고 있는 몇가지 메소드를 가지고있음

  • push_back() : 벡터의 끝에 하나의 원소를 추가함
    이 때 메소드는 메모리 관리에 관여하여 벡터의 크기를 늘림
  • erase() : 지정된 범위를 벡터에서 삭제하며, 범위를 정의하는 2개의 이터레이터를 매개변수로 사용함
  • insert() : 이터레이터 매개변수를 3개 사용하여 첫번째 매개변수는 새로운 원소들이 삽입될 장소의 바로 앞 위치를 제공하며 두번째, 세번째 매개변수는 삽입에 사용할 범위를 정의함
// vect2.cpp

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

struct Review {
	std::string title;
	int rating;
};

bool FillReview(Review & rr);
void ShowReview(const Review & rr);

int main()
{
	using std::cout;
	using std::vector;
	vector<Review> books;
	Review temp;

	while (FillReview(temp))
		books.push_back(temp);
	int num = books.size();
	if (num > 0)
	{
		cout << "감사합니다. 당신은 다음과 같이 입력하셨습니다.\n"
			 << "등급\t제목\n";
		for (int i = 0; i < num; i++)
			ShowReview(books[i]);
		cout << "한번 더 출력한다 : \n"
			 << "등급\t제목\n";
		vector<Review>::iterator pr;
		for (pr = books.begin(); pr != books.end(); pr++)
			ShowReview(*pr);
		
		vector<Review> oldlist(books);
		if (num > 3)
		{
			books.erase(books.begin() + 1, books.begin() + 3);
			cout << "삭제한 후 : \n";
			for (pr = books.begin(); pr != books.end(); pr++)
				ShowReview(*pr);
			books.insert(books.begin(), oldlist.begin() + 1, oldlist.begin() + 2);
			cout << "삽입한 후 : \n";
			for (pr = books.begin(); pr != books.end(); pr++)
				ShowReview(*pr);
		}
		
		books.swap(oldlist);
		cout << "oldlist와 books를 교환한 후 : \n";
		for (pr = books.begin(); pr != books.end(); pr++)
			ShowReview(*pr);
	}
	else
		cout << "입력한 것이 없어, 얻은 것이 없습니다.\n";
	return 0;
}

bool FillReview(Review & rr)
{
	std::cout << "책 제목을 입력하십시오(끝내려면 quit를 입력) : ";
	std::getline(std::cin, rr.title);
	if (rr.title == "quit")
		return false;
	std::cout << "책 등급(0-10)을 입력하십시오 : ";
	std::cin >> rr.rating;
	if (!std::cin)
		return false;
	while (std::cin.get() != '\n')
		continue;
	return true;
}

void ShowReview(const Review & rr)
{
	std::cout << rr.rating << "\t" << rr.title << std::endl;
}

vector에서 할 수 있는 그 밖의 것

STL은 일반적인 작업을 위해 모든 컨테이너 클래스에 사용할 수 있는 멤버가 아닌 함수를 정의함
동일한 작업을 하는 비멤버 함수가 있더라도 클래스에 특화된 알고리즘을 위해 멤버 함수를 따로 만드는 경우도 있음

  • for_each() : 지시된 함수를 그 범위 안에있는 각 컨테이너 원소에 적용
    • 단, 지시된 함수는 컨테이너 원소들의 값을 변경할 수 없음
    • 3개의 매개변수를 사용, 첫번째 두번째 매개변수는 컨테이너의 범위를 정의하는 이터레이터
    • 세번째 매개변수는 함수를 지시하는 포인터(일반적으로는 함수 객체)
  • random_shuffle() : 범위 안에 있는 원소들을 무작위 순서로 재배치
    • 컨테이너 클래스가 임의 접근을 허용할 것을 요구함
    • 매개변수로 범위를 지정하는 두 개의 이터레이터를 사용
  • sort() : 두 가지 버전이 있음
    • 첫번째 버전은 범위를 지정하는 두 개의 이터레이터를 사용하며, 컨테이너에 저장되어있는 데이터형의 원소에 맞게 정의된 < 연산자를 사용하여 정렬함
    • 따라서 컨테이너의 원소들이 사용자 정의 객체라면 해당 데이터형에 맞게 정의된 operator<() 함수가 있어야함
    • 두번째 버전은 3개의 매개변수를 사용하며 첫번째와 두번째는 범위 지정 이터레이터, 세번째는 값을 비교하기 위해 operator<() 대신에 사용할 함수를 지시하는 포인터(일반적으로는 함수 객체)를 사용함
    • 이 때 해당 함수의 리턴값은 bool형으로 변환할 수 있어야함
// vect3.cpp

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

struct Review {
	std::string title;
	int rating;
};

bool operator<(const Review & r1, const Review & r2);
bool worseThan(const Review & r1, const Review & r2);
bool FillReview(Review & rr);
void ShowReview(const Review & rr);

int main()
{
	using namespace std;

	vector<Review> books;
	Review temp;

	while (FillReview(temp))
		books.push_back(temp);

	if (books.size() > 0)
	{
		cout << "감사합니다. 당신은 다음과 같이 "
			 << books.size() << "개의 책 등급을 입력하셨습니다.\n"
			 << "등급\t제목\n";
		for_each(books.begin(), books.end(), ShowReview);

		sort(books.begin(), books.end());
		cout << "책 제목을 기준으로 정렬 : \n등급\t제목\n";
		for_each(books.begin(), books.end(), ShowReview);

		sort(books.begin(), books.end(), worseThan);
		cout << "책 등급을 기준으로 정렬 : \n등급\t제목\n";
		for_each(books.begin(), books.end(), ShowReview);

		random_shuffle(books.begin(), books.end());
		cout << "무작위 순서로 다시 배치 : \n등급\t제목\n";
		for_each(books.begin(), books.end(), ShowReview);
	}
	else
		cout << "프로그램을 종료합니다.\n";
	return 0;
}

bool operator<(const Review & r1, const Review & r2)
{
	if (r1.title < r2.title)
		return true;
	else if (r1.title == r2.title && r1.rating < r2.rating)
		return true;
	else
		return false;
}

bool worseThan(const Review & r1, const Review & r2)
{
	if (r1.rating < r2.rating)
		return true;
	else
		return false;
}

bool FillReview(Review & rr)
{
	std::cout << "책 제목을 입력하십시오(끝내려면 quit를 입력) : ";
	std::getline(std::cin, rr.title);
	if (rr.title == "quit")
		return false;
	std::cout << "책 등급(0-10)을 입력하십시오 : ";
	std::cin >> rr.rating;
	if (!std::cin)
		return false;
	while (std::cin.get() != '\n')
		continue;
	return true;
}

void ShowReview(const Review & rr)
{
	std::cout << rr.rating << "\t" << rr.title << std::endl;
}

Range에 기초한 루프(C++11)

double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
for (double x : pries)
	cout << x << std::end;

for_each(book.vegin(), books.end(), ShowReview);
for (auto x : books) ShowReview(x);

for 루프 헤드에 컨테이너에 저장된 형으로 변수를 선언한 다음 컨테이너의 이름을 선언
그 후 루프의 몸체에 명명된 변수를 사용하여 각각의 저장소 요소에 접근함
for_each()와는 달리 range에 기초한 for는 저장소의 내용을 수정할 수 있으며, 이를 위해서는 참조 매개변수를 사용해야함

void InflateReview(Review &r) {r.rating++;}
for (auto & x : books) InflateReview(x);


16.4 일반화 프로그래밍

STL은 일반화 프로그래밍의 한 예로 알고리즘에 중점을 둠
일반화 프로그램의 목적은 데이터형과 무관한 코드를 작성하는 것으로, 템플릿을 사용하여 함수나 클래스를 일반형으로 정의할 수 있으나 STL은 알고리즘의 일반화된 표현을 제공함으로써 한발 더 나아감

이터레이터가 필요한 이유

템플릿이 알고리즘을 저장할 데이터형과 무관하게 만드는 것과 같이 이터레이터는 알고리즘을 사용할 컨테이너형과 무관하게 만듬

double * find_ar(double * ar, int n, const double & val)
{
	for (int i = 0; i < n; i++)
		if (ar[i] == val)
			return &ar[i];
	return 0;
}

struct Node
{
	double item;
	Node * p_next;
};

Node* find_ll(Node * head, const double & val)
{
	Node * start;
	for (start = head; start != 0; start = start->p_next)
		if (start->item == val)
			return start;
	return 0;
}

find_ar 함수에서 템플릿을 사용할경우 == 연산자를 지원하는 모든 데이터형의 배열에까지 일반화할 수 있음
그러나 이는 배열이라는 특정 데이터 구조에만 적용이 가능함
마찬가지로 find_ll 함수에서도 템플릿을 사용하면 == 연산자를 지원하는 모든 데이터형의 링크드 리스트에 일반화할 수 있으나, 여전히 링크드리스트라는 특정 데이터 구조에만 적용이 가능함

find 함수를 일반화하기 위해 이터레이터가 가져야하는 특성

  • 이터레이터가 참조하는 값에 접근하기 위해 내용 참조(dereference)를 할 수 있어야하며 즉, p가 이터레이터라면 *p가 정의되어야함
  • 한 이터레이터를 다른 이터레이터에 대입할 수 있어야하며 즉, pq가 이터레이터라면 p = q라는 표현이 정의되어야함
  • 한 이터레이터가 다른 이터레이터와 같은 것인지 비교할 수 있어야하며 즉 p == qp != q라는 표현이 정의되어야함
  • 이터레이터가 컨테이너에 들어있는 모든 원소들을 훑고 지나갈 수 있어야하며, 즉 ++pp++가 정의되어야함

이 때 단순 포인터가 위의 요구사항을 모두 만족시킬 수 있음

typedef double * iterator;
iterator find_ar(iterator begin, iterator end, const double * val)
{
	iterator ar;
	for (ar = begin; ar != end; ar++)
		if (*ar == val)
			return ar;
	return end;
}
struct Node
{
	double item;
	Node * p_next;
};

class iterator
{
	Node * pt;
	public:
		iterator() : pt(0) {}
		iterator(Node * pn) : pt(pn) {}
		double operator*() { return pt->item; }
		iterator& operator++()
		{
			pt = pt->p_next;
			return *this;
		}
		iterator operator++(int)
		{
			iterator tmp = *this;
			pt = pt->p_next;
			return tmp;
		}
};

iterator find_ll(iterator head, const double & val)
{
	iterator start;
	for (start = head; start != 0; ++start)
		if (*start == val)
			return start;
	return 0;
}

find_ar() 함수와 find_ll() 함수는 구조가 거의 같음
단, find_ar() 함수는 past-the-end를 가리키는 이터레이터를 사용하며 find_ll() 함수는 마지막 노드에 저장된 널 값을 사용함
따라서 링크드리스트가 공식적인 마지막 원소 뒤 바로 다음에 비공식적인 원소를 하나 더 갖게 만들어 past-the-end 원소를 갖게 할 경우 find_ar()find_ll의 알고리즘이 동일해짐
즉, past-the-end 원소를 요구함으로써 이터레이터들에 대한 요구사항을 컨테이너 클래스에 대한 요구사항으로 바꿈

STL에서 각각의 컨테이너 클래스들은 그 클래스에 맞는 이터레이터형을 정의함
이는 포인터, 객체 등 여러가지가 가능하며 이터레이터는 *++와 같이 필요한 연산을 제공함
이후 각 컨테이너 클래스는 컨테이너에 있는 마지막 값 바로 다음으로 증가되었을 때 이터레이터에 대입되는 값으로 past-the-end 마커를 가짐
각 컨테이너 클래스는 begin()end() 메소드를 가지며, ++연산을 가짐

vector<double>::iterator pr;
for (pr = scores.begin(); pr != scores.end(); pr++)
	cout << *pr << endl;

list<double>::iterator pr;
for (pr = scores.begin(); pr != scores.end(); pr++)
	cout << *pr << endl;

위와 같이 각 클래스를 위한 적절한 이터레이터를 정의하고 클래스들을 일관된 방식으로 설계할시 STL을 사용하여 내부적 표현이 서로 다른 여러 컨테이너들에 대해 동일한 코드를 작성할 수 있음

for (auto pr = scores.begin(); pr !- scores.end(); pr++)
	cout << *pr << endl;

C++ 자동 타입 추론을 통해서 코드를 더욱 간단하게 만들 수도 있음

일반화 알고리즘을 구체적인 사례에 적용하기 위해 알고리즘의 요구사항을 만족시키는 이터레이터들을 정의하고, 필요한 요구사항을 컨테이너 설계에 반영
즉, 기본적인 이터레이터 특성과 컨테이너 특성은 알고리즘이 부과하는 요구사항에 따름


이터레이터의 종류

알고리즘에 따라 이터레이터들에 대한 요구사항이 달라짐
이에따라 STL은 다섯가지 이터레이터를 정의함

  • 입력 이터레이터(input iterator)
  • 출력 이터레이터(output iterator)
  • 전방 이터레이터(forward iterator)
  • 전후방 이터레이터(bidirectional iterator)
  • 임의 접근 이터레이터(random access iterator)

위 이터레이터들은 모두 각 이터레이터에 맞게 * 연산자가 정의되어있어 내용 참조가 가능함
가능하면 오버로딩된 == 연산자를 사용하여 서로 같은지 비교할 수 있고, 가능하면 오버로딩된 != 연산자를 사용하여 서로 다른지 비교할 수 있음

입력 이터레이터

컨테이너에서 프로그램으로 들어가는 정보를 입력이라고 부름
따라서 입력 이터레이터는 컨테이너로부터 값을 읽기 위해 사용됨
입력 이터레이터의 내용 참조는 값을 읽는 것은 허용하나, 값을 변경하는 것은 허용하지 않음
++ 연산자의 접두어 버전과 접미어 버전을 둘 다 지원하여 컨테이너에 들어있는 모든 값에 접근할 수 있도록 허용함
일방향 이터레이터이기 때문에 증가시킬 수는 있으나 되돌릴 수는 없고, 증가된 후에 증가되기 전 값을 내용 참조할 수 있다는 보장은 없음

출력 이터레이터

프로그램에서 컨테이너로 정보를 보내기 위해 이터레이트를 사용하여 출력함
출력 이터레이터는 내용 참조를 통해 값을 읽지 못하고 다만 변경하는 것을 허용함
입력 이터레이터와 마찬가지로 일회성 알고리즘에 사용할 수 있음

전방 이터레이터

전방 이터레이터도 입/출력 이터레이터와 마찬가지로 컨테이너 속을 훑고 지나가기위해 ++ 연산자만 사용하며, 한번에 한 원소씩 전방으로만 진행할 수 있음
단, 입/출력 이터레이터와 달리 사용할 때마다 연속된 값들을 반드시 같은 순서로 훑고지나감
또한 전방 이터레이터가 증가된 후에도 따로 값을 저장해두었을시 이전 이터레이터 값을 내용 참조하여 항상 같은 값을 얻을 수 있음
읽기만 할 때와 변경할 때 모두 전방 이터레이터를 사용할 수 있음

전후방 이터레이터

전방 이터레이터가 가지고있는 모든 기능에 더불어 감소 연산자의 접두어 버전과 접미어 버전에 대한 기능이 추가됨

임의 접근 이터레이터

컨테이너에 들어있는 임의 원소로 직접 점프하기 위해 사용
전후방 이터레이터가 가지고있는 모든 기능에 더해 임의 접근을 지원하는 포인터 덧셈과 같은 연산, 원소들의 순서를 매기는데 사용할 관계 연산자들이 추가됨


이터레이터 계층

전방 이터레이터는 입출력 이터레이터의 기능을 모두 포함함
전후방 이터레이터는 전방 이터레이터의 기능에 감소 연산자가 추가됨
임의 접근 이터레이터는 전후방 이터레이터의 기능에 포인터 연산과 관계 연산자들이 추가됨
즉, 상위 계층의 이터레이터를 가진 컨테이너는 하위 계층의 이터레이터로 작성된 알고리즘을 사용할 수 있음
요구사항이 가장 적은 이터레이터를 사용하여 알고리즘을 작성해 가장 넓은 범위의 컨테이너들에 사용하도록 하는 것이 목적

이터레이터들은 정의된 데이터형이 아닌 개념적 특성화임
각 컨테이너 클래스는 iterator라는 클래스 사용 범위의 typedef형의 이름을 정의함


개념, 개량, 모델

전방 이터레이터의 특성을 갖는 클래스를 설계할 수 있으나, 컴파일러가 그 클래스만 사용하도록 제한할 수는 없음
전방 이터레이터가 데이터형이 아니라 요구사항들의 집합이기 때문에, 사용자가 설계한 이터레이터 클래스를 통해 그 요구사항을 충족시킬 수 있으나 단순 포인터를 가지고도 요구사항을 충족시킬 수 있기 때문
이러한 요구사항들의 집합을 개념(concept)이라는 단어로 표현함

개념은 상속과 비슷한 관계를 가지지만 C++의 상속 매커니즘을 적용할 수는 없음
STL 문건에서는 이러한 개념적인 상속을 개량(refinement)라고 표현하기도 함
이 때 어떠한 개념의 특별한 한 구현을 모델(model)이라고 부름

이터레이터 자격의 포인터

이터레이터는 포인터를 일반화한 것이기 때문에 포인터는 이터레이터의 모든 요구사항을 충족시킴
따라서 STL 알고리즘은 포인터에 기초를 두고있는 STL이 아닌 컨테이너들에 포인터를 적용할 수 있음
예를들어 포인터가 이터레이터이고 알고리즘들이 이터레이터에 기초하고 있기 때문에 STL을 일반 배열에 적용할 수 있음
마찬가지로 적절한 이터레이터와 past-the-end 마커를 제공할 경우 STL 알고리즘을 사용자가 설계하는 데이터 형식에 적용할 수 있음

copy(), ostream_iterator, istream_iterator

STL은 미리 정의되어잇는 이터레이터 몇가지를 제공함

  • copy() : 하나의 컨테이너에서 다른 컨테이너로 데이터를 복사하는 알고리즘
    • 첫번째 두번째 매개변수로는 입력 이터레이터를 사용하며 마지막 매개변수는 출력 이터레이터를 사용함
    • 단, 목적지 컨테이너에 있는 기존의 데이터 위에 쓰기 때문에 목적지 컨테이너의 크기가 충분히 커야함
  • ostream_iterator : 출력 스트림을 나타내는 이터레이트들의 템플릿
    • 다른 어떤 인터페이스를 STL이 사용하는 인터페이스로 변환하는 클래스나 함수를 뜻하는 어댑터(adapter)이기도 함
    • 첫번째 템플릿 매개변수는 출력 스트림으로 보내는 데이터형을 나타냄
    • 두번째 템플릿 매개변수는 출력 스트림이 사용하는 문자형을 나타냄
    • 첫번째 생성자 매개변수는 사용할 출력 스트림을 나타냄
    • 두번재 생성자 매개변수는 출력 스트림에 보내져 각 항목 뒤에 표시되는 분리자를 나타냄
  • istream_iterator : 입력 스트림을 나타내는 이터레이트들의 템플릿
    • 템플릿 매개변수는 ostream_iterator와 동일함
    • 단, 생성자 매개변수로 cin만을 사용하며, 입력 스트림을 사용한다는 것을 나타냄

기타 유용한 이터레이터들

vector 클래스는 past-the-end를 지시하는 reverse_iterator를 리턴하는 rbegin() 멤버 함수 및 첫번째 원소를 지시하는 reverse_iterator를 리턴하는 rend() 멤버 함수를 가지고있음
이 때 reverse_iterator를 증가시키면 본질적으로는 감소되는 성격을 가지고있음
따라서 copy(dice.rbegin(), dice.rend(), out_iter); 형식의 구문을 사용하면 reverse_iterator을 선언하지 않고도 컨테이너의 내용을 뒤집어 출력할 수 있음

// copyit.cpp

#include <iostream>
#include <iterator>
#include <vector>

int main()
{
	using namespace std;

	int casts[10] = {6, 7, 2, 9, 4, 11, 8, 7, 10, 5};
	vector<int> dice(10);
	copy(casts, casts + 10, dice.begin());

	cout << "주사위를 던져라!\n";
	ostream_iterator<int, char> out_iter(cout, " ");
	copy(dice.begin(), dice.end(), out_iter);
	cout << endl;

	cout << "역방향 이터레이터의 암시적 사용 : \n";
	copy(dice.rbegin(), dice.rend(), out_iter);
	cout << endl;

	cout << "역방향 이터레이터의 명시적 사용 : \n";
	vector<int>::reverse_iterator ri;
	for (ri = dice.rbegin(); ri != dice.rend(); ++ri)
		cout << *ri << ' ';
	cout << endl;
	
	return 0;
}

역방향 포인터는 먼저 감소시킨 후에 내용 참조를 함으로써 정상적인 범위를 참조함
이터레이터를 명시적으로 선언하는 방법과 STL 함수를 사용하는 방법 중에서는 후자를 선택해야 실수를 줄일 수 있음

// inserts.cpp

#include <iostream>
#include <string>
#include <iterator>
#include <vector>
#include <algorithm>

void output (const std::string & s) {std::cout << s << " ";}

int main()
{
	using namespace std;
	string s1[4] = {"fine", "fish", "fashion", "fate"};
	string s2[2] = {"busy", "bats"};
	string s3[2] = {"silly", "singers"};
	vector<string> words(4);
	copy(s1, s1 + 4, words.begin());
	for_each(words.begin(), words.end(), output);
	cout << endl;

	copy(s2, s2 + 2, back_insert_iterator<vector<string> >(words));
	for_each(words.begin(), words.end(), output);
	cout << endl;

	copy(s3, s3 + 2, insert_iterator<vector<string> >(words, words.begin()));
	for_each(words.begin(), words.end(), output);
	cout << endl;

	return 0;
}

back_insert_iterator, front_insert_iterator, insert_iterstor는 복사 과정을 삽입 과정으로 변환함

  • 단, back_insert_iterator의 경우 말미에 바른 삽입을 허용하는 컨테이너에만 사용할 수 있으며 front_insert_iterator는 선두에 고정 시간 삽입을 허용하는 컨테이너형에만 사용할 수 있음
  • 템플릿 매개변수로 컨테이너형을 사용함
  • 생성자 매개변수로 실제 컨테이너 식별자를 사용함

컨테이너의 종류

컨테이너 개념

컨테이너 개념은 모든 STL 컨테이너 클래스들이 충족시켜야하는 요구사항들의 집합을 서술함
컨테이너는 단일형의 다른 객체들을 저장하는 객체로서, 컨테이너에 저장된 데이터는 해당 컨테이너가 소유함
이는 컨테이너의 수명이 끝나면 해당 컨테이너에 저장되어있는 데이터의 수명도 끝난다는 것을 의미함
컨테이너에 저장할 객체는 복사생성(copy construction) 및 대입(assignment)가 가능한 데이터형이어야하며, 기본 데이터형들도 이 요구사항을 충족함

기본 컨테이너는 원소들의 저장 순서 및 순서의 유지를 보장하지 않으나 개념에 대한 개량으로 보장을 추가할 수 있음
모든 컨테이너는 공통적인 특성을 가지고있음
연산 수행시 복잡성에 따라 속도가 달라짐

  • 컴파일 시간 : 컴파일하는 동안에 연산이 수행되어 실행 시간은 사용되지않음
  • 고정 시간 : 연산이 실행 시간에 수행되고, 객체의 원소 수에 영향을 받지 않음
  • 비례 시간 : 연산 시간이 원소 수에 비례함

C++11의 컨테이너 요구 조건에 대한 추가사항

  • X u(rv); : 생성자 포스트 조건을 이동시키며, u는 생성전에 rv가 지녔던 값을 지님
  • X u = rv; : x u(rv);와 동일한 효과
  • a = rv; : 대입 포스트 조건을 이동시키며, a는 대입 전에 rv가 지녔던 값을 지님
  • a.cbegin() : 컨테이너의 첫번째 요소를 지칭하는 const_iterator를 리턴
  • a.cend() : 최종값인 const_iterator를 리턴

복사 연산의 경우 원본을 변경하지 않지만, 이동 연산은 원본을 변경할 수 있으며 복사 없이도 소유권을 이전할 수 있음
소스 객체가 일시적인 경우에는 복사에 비해 이동 연산이 보다 효율적임

시퀀스

STL의 컨테이너 중 deque, forward_list, list, queue, priority_queue, stack, vector이 시퀀스(sequence)에 속함
시퀀스 개념은 최소한 이터레이터가 전방 이터레이터 이상의 계층이어야 한다는 요구사항을 추가함
시퀀스는 원소들이 직선 순서로 배치되어야하며, 따라서 분기 구조는 시퀀스가 아님

vector

vector는 배열의 클래스 표현임
클래스는 원소들이 추가되거나 삭제될 때 vector 객체의 크기를 동적으로 변경하는 자동 메모리 관리 기능을 제공함
원소들에 대해 임의 접근을 제공하며, 고정 시간 연산으로 말미에 추가 및 삭제할 수 있음
선두 또는 중간에 삽입하거나 삭제하는 작업은 비례 시간 연산이 됨

vector 컨테이너는 시퀀스임과 동시에 가역성 컨테이너(reversible contatiner) 개념 모델이기 때문에 rbegin()rend() 클래스 메소드가 추가됨

deque

deque 템플릿 클래스는 double-ended queue의 줄임말으로써 양쪽에 끝이 있는 큐를 나타냄
vector와는 달리 선두에 원소를 삽입하거나 삭제하는 작업도 고정 시간 연산이 됨

list

list 템플릿 클래스는 이중 링크드 리스트를 나타냄
list는 어느 위치에서도 고정 시간 연산으로 삽입과 삭제가 가능함
list도 가역성 컨테이나 vector와는 달리 배열 표기 및 임의 접근을 지원하지 않음
벡터가 원소가 삽입되거나 삭제될시 동일한 위치의 다른 데이터를 지시하는 것과 달리, 리스트 이터레이터는 원소들이 삽입되거나 삭제된 후에도 같은 원소를 계속 지시함

// list.cpp

#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>

void outint (int n) {std::cout << n << " ";}

int main()
{
	using namespace std;
	list<int> one(5, 2);
	int stuff[5] = {1, 2, 4, 8, 6};
	list<int> two;
	two.insert(two.begin(), stuff, stuff + 5);
	int more[6] = {6, 4, 2, 4, 6, 5};
	list<int> three(two);
	three.insert(three.end(), more, more + 6);

	cout << "리스트 one : ";
	for_each(one.begin(), one.end(), outint);
	cout << endl << "리스트 two : ";
	for_each(two.begin(), two.end(), outint);
	cout << endl << "리스트 three : ";
	for_each(three.begin(), three.end(), outint);
	
	three.remove(2);
	cout << endl << "리스트 three에서 2들을 삭제 : ";
	for_each(three.begin(), three.end(), outint);
	
	three.splice(three.begin(), one);
	cout << endl << "접목 후의 리스트 three : ";
	for_each(three.begin(), three.end(), outint);
	cout << endl << "리스트 one : ";
	for_each(one.begin(), one.end(), outint);

	three.unique();
	cout << endl << "단일화 후의 리스트 three : ";
	for_each(three.begin(), three.end(), outint);

	three.sort();
	three.unique();
	cout << endl << "정렬과 단일화 후의 리스트 three : ";
	for_each(three.begin(), three.end(), outint);

	two.sort();
	three.merge(two);
	cout << endl << "정렬된 리스트 two를 리스트 three에 합병 : ";
	for_each(three.begin(), three.end(), outint);
	cout << endl;

	return 0;
}

list 템플릿 클래스는 리스트 지향적인 멤버 함수를 가지고있음

  • void merge(list<T, Alloc>& x) : 리스트 x를 호출한 리스트와 합병, 이 때 두 리스트는 모두 정렬되어있어야함
    정렬된 리스트는 호출된 리스트에 남고 x는 비게됨, 비례 시간 복잡성을 가짐
  • void remove(const T & val) : 리스트에 들어있는 모든 val을 삭제하며, 비례 시간 복잡성을 가짐
  • void sort() : < 연산자를 사용하여 리스트를 정렬하며, NlogN의 복잡성을 가짐
  • void splice(iterator pos, list<T, Alloc> x) : pos 위치 앞에 리스트 x의 내용을 삽입하며, x는 비워지고 고정 시간 복잡성을 가짐
  • void unique() : 같은 원소들의 연속된 그룹을 하나의 원소로 만들며, 비례 시간 복잡성을 가짐

forward_list(C++11)

forward_list는 단순 링크드 리스트(singly linked list)를 구현함
따라서 오직 전방 이터레이터만 필요하기 때문에 더 간단한 컨테이너임

queue

queue 템플릿 클래스는 어댑터 클래스에 속함
따라서 queue 템플릿은 그것의 기초가 되는 클래스(기본적으로는 deque)가 전형적인 큐 인터페이스를 나타내는 것을 허용함
queue 템플릿은 큐의 원소들에 대한 임의 접근을 허용하지 않고, 큐를 훑고가는 이터레이션도 허용하지 않는 대신 큐를 정의하는 기본적인 연산으로 제한함

  • bool empty() const : 큐가 비어있으면 true, 아니면 false를 리턴
  • size_type size() const : 큐에 들어있는 원소 수를 리턴
  • T& front() : 큐의 선두에 있는 원소에 대한 참조를 리턴
  • T& back() : 큐의 말미에 있는 원소에 대한 참조를 리턴
  • void push(const T& x) : 큐의 말미에 x를 삽입
  • void pop() : 큐의 선두에 있는 원소를 삭제

priority_queue

priority_queue도 어댑터 클래스이며 queue가 지원하는 연산을 지원함
가장 큰 항목이 큐의 선두로 나감
queue와는 달리 기초가 되는 디폴트 클래스가 vector

stack

stack도 어댑터 클래스이며, 기초가 되는 클래스(디폴트는 vector)에 전형적인 스택 인터페이스를 제공함
stack 템플릿은 스택의 원소들에 임의 접근을 허용하지 않으며 스택을 훑고 지나가는 이터레이션도 허용하지 않는 대신 스택을 정의하는 기본적인 연산으로 제한함

  • bool empty() const : 스택이 비어있으면 true, 아니면 false를 리턴
  • size_type size() const : 스택의 원소 수를 리턴
  • T& top() : 스택의 꼭대기에 있는 원소에 대한 참조를 리턴
  • void push(const T& x) : 스택의 꼭대기에 x를 삽입
  • void pop() : 스택의 꼭대기에 있는 원소를 삭제

array 템플릿(C++11)

고정된 크기를 지니기 때문에 STL 컨테이너는 될 수 없음
따라서 컨테이너 크기를 재조절하는 작업은 정의되지 않고, operator[] 혹은 at()과 같이 의미가 통하는 멤버 함수가 제공됨
copy()for_each()와 같은 표준 STL 알고리즘을 array 객체와 사용할 수 있음


결합 컨테이너

결합 컨테이너(associative container)는 컨테이너 개념의 또 다른 개량임
값에 키(key)를 결합하고, 키를 사용하여 해당 값을 찾음
원소들에 대한 빠른 접근을 제공하며 새로운 원소들을 삽입하는 것을 허용함
단, 삽입할 위치를 지정할 수는 없음
전형적으로 트리 구조를 이용하여 구현되기 때문에 데이터 항목을 더하거나 제거하는 것이 list만큼 간단하며 검색 속도는 월등히 빠름
STL에서는 set, multiset, map, multimap의 네 가지 결합 컨테이너를 제고함

set

가장 단순한 결합 컨테이너로 값의 데이터형이 키의 데이터형과 같음
키들은 고유하기 때문에 하나의 집합에는 하나의 키만이 존재함

  • set_union() : 합집합을 구함
  • set_intersection() : 교집합을 구함
  • set_difference() : 차집합을 구함
  • lower_bound() : 하나의 키 데이터형 값을 매개변수로 취해 해당 집합에서 키 매개변수보다 큰 것중에서 가장 작은 멤버를 지시하는 이터레이터를 리턴
  • upper_bound() : 하나의 키를 매개변수로 취해 해당 집합에서 키 매개변수보다 작은 것중에서 가장 큰 멤버를 지시하는 이터레이터를 리턴
// setops.cpp

#include <iostream>
#include <string>
#include <set>
#include <iterator>
#include <algorithm>

int main()
{
	using namespace std;
	const int N = 6;
	string s1[N] = {"buffoon", "thinkers", "for", "heavy", "can", "for"};
	string s2[N] = {"metal", "any", "food", "elegant", "deliver", "for"};

	set<string> A(s1, s1 + N);
	set<string> B(s2, s2 + N);

	ostream_iterator<string, char> out(cout, " ");
	cout << "집합 A : ";
	copy(A.begin(), A.end(), out);
	cout << endl;
	cout << "집합 B : ";
	copy(B.begin(), B.end(), out);
	cout << endl;

	cout << "A와 B의 합집합 : \n";
	set_union(A.begin(), A.end(), B.begin(), B.end(), out);
	cout << endl;

	cout << "A와 B의 교집합 : \n";
	set_intersection(A.begin(), A.end(), B.begin(), B.end(), out);
	cout << endl;

	cout << "A와 B의 차집합 : \n";
	set_difference(A.begin(), A.end(), B.begin(), B.end(), out);
	cout << endl;

	set<string> C;
	cout << "집합 C : \n";
	set_union(A.begin(), A.end(), B.begin(), B.end(),
			  insert_iterator<set<string> >(C, C.begin()));
	copy(C.begin(), C.end(), out);
	cout << endl;

	string s3("grungy");
	C.insert(s3);
	cout << "삽입한 후의 집합 C:\n";
	copy(C.begin(), C.end(), out);
	cout << endl;

	cout << "특정한 범위를 표시 : \n";
	copy(C.lower_bound("ghost"), C.upper_bound("spook"), out);
	cout << endl;

	return 0;
}

multiset

set과 비슷하지만 하나의 키가 여러개의 값과 결합될 수 있음

map

값의 데이터형과 키의 데이터형이 서로 다름
키당 하나의 값만 가지므로 고유한 키를 가지고있음

multimap

map과 비슷하지만 하나의 키가 여러개의 값과 결합될 수 있음

// multmap.cpp

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

typedef int KeyType;
typedef std::pair<const KeyType, std::string> Pair;
typedef std::multimap<KeyType, std::string> MapCode;

int main()
{
	using namespace std;
	MapCode codes;

	codes.insert(Pair(415, "샌프란시스코"));
	codes.insert(Pair(510, "오클랜드"));
	codes.insert(Pair(718, "브루클린"));
	codes.insert(Pair(718, "스태튼 섬"));
	codes.insert(Pair(415, "샌라파엘"));
	codes.insert(Pair(510, "버클리"));

	cout << "지역 코드가 415인 도시 수 : "
		 << codes.count(415) << endl;
	cout << "지역 코드가 718인 도시 수 : "
		 << codes.count(718) << endl;
	cout << "지역 코드가 510인 도시 수 : "
		 << codes.count(510) << endl;
	cout << "지역 코드\t도시\n";
	MapCode::iterator it;
	for (it = codes.begin(); it != codes.end(); ++it)
		cout << "\t" << (*it).first << "\t"
			 << (*it).second << endl;
	
	pair<MapCode::iterator, MapCode::iterator> range
		= codes.equal_range(718);
	cout << "지역 코드가 718인 도시들 : \n";
	for (it = range.first; it != range.second; ++it)
		cout << (*it).second << endl;
	
	return 0;
}

순서가 부여되지 않은 결합 컨테이너(C++11)

순서가 부여되지 않은 결합 컨테이너(unordered associative container)도 결합 컨테이너와 같이 키와 값을 결합하고 해당 키를 값을 찾는데 사용함
단, tree 구조에 기반을 둔 결합 컨테이너와는 달리 순서가 부여되지 않은 결합 컨테이너는 hash table 형태의 데이터 구조에 기반을 둠
컨테이너로 요소를 추가하거나 삭제하는 속도가 더욱 빨라지고 풍부한 검색 알고리즘을 지니는 장점이 있음



16.5 함수 객체(Functor)

펑크터(functor)는 함수 객체(function object)를 뜻하는 단어로 함수처럼 ()과 함께 사용할 수 있는 객체를 의미함
일반 함수의 이름, 함수를 지시하는 포인터, operator()()와 같이 ()연산자가 오버로딩된 클래스 객체가 모드 펑크터가 될 수 있음

class Linear
{
	private:
		double sloope;
		double y0;
	public:
		Linear(double _s1 = 1, double _y = 0)
			: slope(_s1), y0(_y) {}
		double operator()(double x) { return y0 + slope * x; }
};

Linear f1;
Linear f2(2.5, 10.0);
double y1 = f1(12.5);
double y2 = f2(0.4);

위와 같은 클래스를 정의했을시 Linear 클래스 객체들은 오버로딩된 () 연산자를 이용하여 함수처럼 사용할 수 있게됨
for_each()의 마지막 매개변수가 객체일시 해당 객체는 자신의 오버로딩된 ()연산자를 호출함

펑크터 개념

펑크터의 개념은 다음과 같이 정의됨

  • 제너레이터(generator) : 매개변수 없이 호출하는 함수
  • 단항 함수(unary function) : 하나의 매개변수로 호출하는 함수
  • 이항 함수(binary function) : 두 개의 매개변수로 호출하는 함수
  • bool값을 리턴하는 단항함수는 조건(predicate)임
  • bool값을 리턴하는 이항함수는 이항 조건(binary predicate)임
// functor.cpp

#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>

template<class T>
class TooBig
{
	private:
		T cutoff;
	public:
		TooBig(const T & t) : cutoff(t) {}
		bool operator()(const T & v) { return v > cutoff; }
};

void outint(int n) {std::cout << n << " ";}

int main()
{
	using std::list;
	using std::cout;
	using std::endl;

	TooBig<int> f100(100);
	int vals[10] = {50, 100, 90, 180, 60, 210, 415, 88, 188, 201};
	list<int> yadayada(vals, vals + 10);
	list<int> etcetera(vals, vals + 10);
	cout << "원래의 리스트 : \n";
	for_each(yadayada.begin(), yadayada.end(), outint);
	cout << endl;
	for_each(etcetera.begin(), etcetera.end(), outint);
	cout << endl;

	yadayada.remove_if(f100);
	etcetera.remove_if(TooBig<int>(200));
	cout << "정비된 리스트 : \n";
	for_each(yadayada.begin(), yadayada.end(), outint);
	cout << endl;
	for_each(etcetera.begin(), etcetera.end(), outint);
	cout << endl;

	return 0;
}

두개의 매개변수를 사용하는 템플릿 함수를 클래스를 사용해 하나의 매개변수로 사용하는 함수 객체로 변환함
즉, 하나의 값은 함수 매개변수로 전달하고 다른 매개변수는 클래스 생성자에 의해 설정함


미리 정의된 펑크터

transform() 함수는 네 개의 매개변수를 사용함

  • 첫번째, 두번째 매개변수는 컨테이너 안의 범위를 지정하는 이터레이터
  • 세번째 매개변수는 결과를 복사할 위치를 지정하는 이터레이터
  • 네번째 매개변수는 새로운 원소들로 만들기 위해 그 범위에있는 각 원소에 적용할 함수
const int LIM = 5;
double arr1[LIM] = {36, 39, 42, 45, 48};
vector<double> gr8(arr1, arr1 + LIM);
vector<double> m8(...);
ostream_iterator<double, char> out(cout, " ");
transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>());

두 배열을 transform() 함수를 통해 더할시, double형의 +는 함수가 아닌 내장된 연산자이므로 매개변수로 사용할 수 없음
따라서 두 수를 더하는 함수를 새로 정의하여 사용할 수 있으나, STL이 이미 여러 템플릿 클래스 함수 객체들을 정의하고 있기 때문에 이를 사용하는 것이 권장됨
이들은 functional 헤더 파일에 정의되어있음

  • + : plus / - : minus / * : multiplies / / : divides / % : modulus
  • - : negate / == : equal_to / != : not_equal_to
  • > : greater / < : less / >= : greater_equal / <= : less_equal
  • && : logical_and / !! : logical_or / ! : logical_not

어댑터블 펑크터와 함수 어댑터

STL에는 순응성 제네레이터(adaptable generator), 순응성 단항 함수(adaptable unary function), 순응성 이항 함수(adaptable binary function), 순응성 조건(adaptable predicate), 순응성 이항 조건(adaptable binary predicate)의 다섯가지 개념이 존재함

펑크터는 매개변수형과 리턴형을 식별하는 typedef 멤버가 있을경우 순응성이 됨
이때 typedef 멤버에는 result_type, frist_argument_type, second_argument_type 등이 있음
펑크터는 순응성이기 때문에 이러한 typedef 멤버들의 존재를 가정하는 함수 어댑터 객체에 사용할 수 있음

순응성 이항 함수를 binder1stbinder2nd 클래스를 사용해 순응성 단항 함수로 자동 변환시킬 수 있음
또한 이를 bind1st() 함수를 이용해 더 간소화할 수 있음

binder1st(f2, val) f1;
bind1st(multiplies<double(), 2.5>;
transform(gr8.begin(), gr8.end(), out, bind1st(multiplies<double>(), 2.5));

f2는 순응성 이항함수이고, f2(val, x)f1(x)와 동일함
bind1st는 이항함수 multiplies()를 매개변수에 2.5를 곱하는 단항함수로 변환함

// funadap.cpp

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <functional>

void Show(double);
const int LIM = 6;

int main()
{
	using namespace std;
	double arr1[LIM] = {28, 29, 30, 35, 38, 59};
	double arr2[LIM] = {63, 65, 69, 75, 80, 99};
	vector<double> gr8(arr1, arr1 + LIM);
	vector<double> m8(arr2, arr2 + LIM);
	cout.setf(ios_base::fixed);
	cout.precision(1);

	cout << "gr8 :\t";
	for_each(gr8.begin(), gr8.end(), Show);
	cout << endl;
	cout << "m8 :\t";
	for_each(m8.begin(), m8.end(), Show);
	cout << endl;

	vector<double> sum(LIM);
	transform(gr8.begin(), gr8.end(), m8.begin(), sum.begin(), plus<double>());
	cout << "sum : \t";
	for_each(sum.begin(), sum.end(), Show);
	cout << endl;

	vector<double> prod(LIM);
	transform(gr8.begin(), gr8.end(), prod.begin(), bind1st(multiplies<double>(), 2.5));
	cout << "prod : \t";
	for_each(prod.begin(), prod.end(), Show);
	cout << endl;

	return 0;
}

void Show(double v)
{
	std::cout.width(6);
	std::cout << v << ' ';
}


16.6 알고리즘

알고리즘 함수 설계에는 일반화 성분이 존재함

  • 일반형을 제공하기 위해 템플릿을 사용
  • 컨테이너에 들어있는 데이터에 접근하기 위한 일반화 표현을 제공하기 위한 이터레이터를 사용

알고리즘 그룹

STL의 알고리즘 라이브러리는 네 그룹으로 나뉘어져있음

  • 변경 불가 시퀀스 연산 : 특정 범위에 들어있는 각 원소에 작용, 컨테이너를 변경하지 않으며 find(), for_each()등이 속함
  • 변경 가능 시퀀스 연산 : 특정 범위에 들어있는 각 원소에 작용, 컨테이너의 내용을 변경할 수 있으며 transform(), random_shuffle(), copy()등이 속함
  • 정렬 및 그와 관련된 연산 : sort() 등의 정렬 함수 및 집합 연산을 포함한 기타 여러가지 함수가 속함
  • 일반화환 수치 연산 : 특정 범위에 있는 내용들을 더하고, 두 컨테이너의 내적 / 부분합 / 인접차를 계산하는 함수들이 포함되며 일반적으로 연산들이 배열의 특성을 가짐

처음 세 그룹은 algorithm 헤더 파일에 정의되어있으며, 네번째는 numeric 헤더 파일에 별개로 정의되어있음


알고리즘의 일반적인 특성

알고리즘은 템플릿 매개변수의 이름을 통해 그 매개변수가 모델링하는 개념을 나타냄

알고리즘은 알고리즘의 결과가 놓이는 위치를 기준으로 분류할 수 있음

  • 제자리에서 작용 : sort() 함수와 같이 결과가 원본 데이터가 있던 장소를 동일하게 차지하며, 제자리 알고리즘(in-place algorithm)이라고 부름
  • 복사본 생성 : copy() 함수와 같이 연산 결과를 다른 위치로 보내며, 복사 알고리즘(copying algorithm)이라고 부름
    • 이 때, copy()와 같이 제자리 버전과 복사 버전을 둘 다 가지고있는 경우 함수 이름 앞에 _를 붙이는 것이 STL의 관행임
    • 복사 알고리즘은 관행적으로 마지막으로 복사된 값 바로 다음 위치를 지시하는 이터레이터를 리턴함

STL과 string 클래스

string 클래스는 STL의 일부는 아니나 STL을 염두에 두고 설계됨
string 클래스는 begin(), end(), rbegin(), rend()멤버를 가지고있으며 STL 인터페이스를 사용할 수 있음

// strgst1.cpp

#include <iostream>
#include <string>
#include <algorithm>

int main()
{
	using namespace std;
	string letters;
	cout << "글자 그룹을 입력하십시오 (끝내려면 quit) : ";
	while (cin >> letters && letters != "quit")
	{
		cout << letters << "의 모든 치환들 : " << endl;
		sort(letters.begin(), letters.end());
		cout << letters << endl;
		while (next_permutation(letters.begin(), letters.end()))
			cout << letters << endl;
		cout << "다음 시퀀스를 입력하십시오 (끝내려면 quit) : ";
	}
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

함수와 컨테이너 메소드

일반적으로는 STL 메소드와 STL 함수 중 메소드를 사용하는 것이 권장됨

  • 특별한 컨테이너를 위해 최적화되어있음
  • 멤버 함수이기 때문에 템플릿 클래스의 메모리 관리 기능을 이용할 수 있으며, 필요한 경우 컨테이너 크기를 조절할 수 있음
// listrmv.cpp

#include <iostream>
#include <list>
#include <algorithm>

void Show(int);
const int LIM = 10;

int main()
{
	using namespace std;
	int ar[LIM] = {4, 5, 4, 2, 2, 3, 4, 8, 1, 4};
	list<int> la(ar, ar + LIM);
	list<int> lb(la);

	cout << "오리지널 리스트의 내용 : \n\t";
	for_each(la.begin(), la.end(), Show);
	cout << endl;

	la.remove(4);
	cout << "remove() 메소드를 사용한 후 : \n";
	cout << "la : \t";
	for_each(la.begin(), la.end(), Show);
	cout << endl;

	list<int>::iterator last;
	last = remove(lb.begin(), lb.end(), 4);
	cout << "remove() 함수를 사용한 후 : \n";
	cout << "lb : \t";
	for_each(lb.begin(), lb.end(), Show);
	cout << endl;

	lb.erase(last, lb.end());
	cout << "erase() 메소드를 사용한 후 : \n";
	cout << "lb : \t";
	for_each(lb.begin(), lb.end(), Show);
	cout << endl;
	return 0;
}

void Show(int v)
{
	std::cout << v << ' ';
}

remove() 메소드는 리스트의 원소 개수를 같이 줄임
그러나 remove() 함수는 여전히 원소 개수를 유지하며, 빈 자리는 리스트 앞쪽으로 옮겨진 값의 중복이기 때문에 따로 처분해야함


STL 사용하기

// usealgo.cpp

#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <cctype>
using namespace std;

char toLower(char ch) { return tolower(ch); }
string & ToLower(string & st);
void display(const string & s);

int main()
{
	vector<string> words;
	cout << "단어들을 입력하십시오(끝내려면 quit) : \n";
	string input;
	while (cin >> input && input != "quit")
		words.push_back(input);

	cout << "다음과 같은 단어들을 입력하셨습니다.\n";
	for_each(words.begin(), words.end(), display);
	cout << endl;

	set<string> wordset;
	transform(words.begin(), words.end(),
			  insert_iterator<set<string> >(wordset, wordset.begin()), ToLower);
	cout << "\n단어들의 알파벳순 리스트 : \n";
	for_each(wordset.begin(), wordset.end(), display);
	cout << endl;

	map<string, int> wordmap;
	set<string>::iterator si;
	for (si =wordset.begin(); si != wordset.end(); si++)
		wordmap[*si] = count(words.begin(), words.end(), *si);

	cout << "\n단어별 빈도 : \n";
	for (si = wordset.begin(); si != wordset.end(); si++)
		cout << *si << " : " << wordmap[*si] << endl;

	return 0;
}

string & ToLower(string & st)
{
	transform(st.begin(), st.end(), st.begin(), toLower);
	return st;
}

void display(const string & s)
{
	cout << s << " ";
}

알파벳순 단어 리스트를 만들기 위해 sort()unique()를 사용할 수도 있으나, sort()가 제자리 알고리즘이기 때문에 원본이 덮어씌워지는 문제가 있음
따라서 set<string> 객체를 생성하고 삽입 이터레이터를 사용해 벡터에서 집합으로 복사하였으며, 집합은 자동으로 내용을 정렬하는데 더불어 하나의 키만을 허용하므로 unique() 함수 호출의 효과도 적용됨
대소문자를 구분하지 않기 위해 transform() 함수로 ToLower() 함수를 사용함
맵 클래스는 저장된 값들에 접근하는 인덱스 역할을 하는 키들과 함께 배열 표기를 사용할 수 있으므로, wordset 컨테이너에 들어있는 문자열을 *si로 받아와 wordmap의 키로 사용함



16.7 기타 라이브러리

vector와 valarray, 그리고 array

vector : 컨테이너 클래스와 알고리즘으로 구성된 시스템의 일부
정렬, 삽입, 재배치, 검색, 다른 컨테이너로의 데이터 전송 등 컨테이너 지향적인 액티비티들을 지원
valarray : 수치 계산을 지향하며 STL의 일부가 아님
array : 기존 배열형의 대체, STL 알고리즘을 쉽게 적용할 수 있음

배열 원소들에 수를 곱할 경우

  • STL 접근방식 : transfrom(v.begin(), v.end(), v.begin(), bind1st(multiplies<double>(), 2.5));
  • valarray 접근방식 : 대입 연산자들을 오버로딩하여 v = 2.5 * v; 사용가능
    • 이처럼 valarray는 수학 연산에 대한 명쾌한 표기상의 이점을 가지고있음
    • 그러나 vector 클래스의 메소드들을 사용할 수 없으므로 융통성이 부족함
    • STL의 경우 약간의 형태 변형을 통해 사용할 수 있음
// valvect.cpp

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

int main()
{
	using namespace std;
	vector<double> data;
	double temp;

	cout << "수들을 입력하세요 (끝내려면 <= 0) : \n";
	while (cin >> temp && temp > 0)
		data.push_back(temp);
	sort(data.begin(), data.end());

	int size = data.size();
	valarray<double> numbers(size);
	int i;
	for (i = 0; i < size; i++)
		numbers[i] = data[i];

	valarray<double> sq_rts(size);
	sq_rts = sqrt(numbers);
	valarray<double> results(size);
	results = numbers + 2.0 * sq_rts;

	cout.setf(ios_base::fixed);
	cout.precision(4);
	for (i = 0; i < size; i++)
	{
		cout.width(8);
		cout << numbers[i] << " : ";
		cout.width(8);
		cout << results[i] << endl;
	}
	cout << "프로그램을 종료합니다.\n";
	return 0;
}

slice 클래스 객체는 배열 인덱스로 사용할 수 있으며, 하나의 값이 아닌 값들로 이루어진 부분 집합을 나타냄

  • slice(start, number, stride) 형태로 사용
  • start - 첫번째 원소 인덱스, number - 선택된 원소 개수, stride - 원소 사이 간격
  • 1차원 valarray 객체를 사용하여 2차원 데이터를 나타낼 수 있음
// vslice.cpp

#include <iostream>
#include <valarray>
#include <cstdlib>

const int SIZE = 12;
typedef std::valarray<int> vint;
void show(const vint & v, int cols);

int main()
{
	using std::slice;
	using std::cout;
	vint valint(SIZE);

	int i;
	for (i = 0; i < SIZE; ++i)
		valint[i] = std::rand() % 10;
	cout << "원래의 배열 : \n";
	show(valint, 3);

	vint vcol(valint[slice(1, 4, 3)]);
	cout << "제 2열 : \n";
	show(vcol, 1);

	vint vrow(valint[slice(3, 3, 1)]);
	cout << "제 2행 : \n";
	show(vrow, 3);

	valint[slice(2, 4, 3)] = 10;
	cout << "마지막 열을 값 10으로 설정 : \n";
	show(valint, 3);

	cout << "제 1열을 그 다음 두 열의 합으로 설정 : \n";
	valint[slice(0, 4, 3)] = vint(valint[slice(1, 4, 3)]) 
							+ vint(valint[slice(2, 4, 3)]);
	show(valint, 3);

	return 0;
}

void show(const vint & v, int cols)
{
	using std::cout;
	using std::endl;

	int lim = v.size();
	for (int i = 0; i < lim; ++i)
	{
		cout.width(3);
		cout << v[i];
		if ( i % cols == cols - 1)
			cout << endl;
		else
			cout << ' ';
	}
	if (lim % cols != 0)
		cout << endl;
}

initializer_list 템플릿(C++11)

initializer_list 문장을 사용하여 STL 컨테이너를 value 목록에 초기화하는데 사용할 수 있음

  • std::vector<double> payments{45.99, 39.23, 19.95, 89.01};과 같은 형태로 사용
  • 컨테이너 클래스에는 intializer_list<T> 매개변수를 취하는 생성자가 존재
  • initializer_list 요소들은 반드시 하나의 형태여야하며, 이를 위해 컴파일러가 컨버전을 함
  • initializer_list의 이터레이터형은 상수형이므로 목록 안의 값을 바꿀 수 없음
// ilist.cpp

#include <iostream>
#include <initializer_list>

double sum(std::initializer_list<double> il);
double average(const std::initializer_list<double> & ril);

int main()
{
	using std::cout;
	cout << "목록 1 : 합계 = " << sum({2,3,4})
		 << ", 평균 = " << average({2,3,4}) << '\n';

	std::initializer_list<double> dl = {1.1, 2.2, 3.3, 4.4, 5.5};
	cout << "목록 2 : 합계 = " << sum(dl)
		 << ", 평균 = " << average(dl) << '\n';

	dl = {16.0, 25.0, 36.0, 40.0, 64.0};
	cout << "목록 3 : 합계 = " << sum(dl)
		 << ", 평균 = " << average(dl) << '\n';
	return 0;
}

double sum(std::initializer_list<double> il)
{
	double tot = 0;
	for (auto p = il.begin(); p != il.end(); p++)
		tot += *p;
	return tot;
}

double average(const std::initializer_list<double> & ril)
{
	double tot = 0;
	int n = ril.size();
	double ave = 0.0;
	if (n > 0)
	{
		for (auto p = ril.begin(); p != ril.end(); p++)
			tot += *p;
		ave = tot / n;
	}
	return ave;
}


연습문제

  1.  class RQ1
     {
         private:
             char * st;
         public:
             RQ1() { st = new char[1]; strcpy(st, ""); }
             RQ1(const char * s) { st = new char[strlen(s) + 1]; strcpy(st, s); }
             RQ1(const RQ1 & rq) { st = new char[strlen(rq.st) + 1]; strcpy(st, rq.st); }
             ~RQ1() { delete [] st; }
             RQ & operator=(const RQ & rq);
     };
    
     class RQ2
     {
         private:
             string st;
         public:
             RQ2() : st("") {}
             RQ2(const string s) : st(s) {}
             ~RQ2() {}
     }
    

    string 객체가 자체적으로 메모리를 관리하기 때문에 명시적 복사 생성자, 파괴자, 대입 연산자가 필요하지 않음

  2. 메모리 관리를 사용자가 신경쓰지 않아도 되며 객체를 다른 객체에 대입할 수 있음

  3.  void change_str(string& st)
     {
         transform(st.begin(), st.end(), st.begin(), ::toupper);
     }
    
  4. auto_ptr<int> pia (new int[20]); : int형과 new []가 매치되지 않음
    auto_ptr<string> (new string); : 포인터 이름이 정의되지 않음
    int rigue = 7; auto_ptr<int> pr (&rigue); : new로 대입한 메모리가 아님
    auto_ptr dbl (new double); : 포인터형을 지정하지 않음

  5. 스택은 LIFO이기 때문에 원하는 골프클럽을 한번에 꺼낼 수 없음

  6. 집합은 값을 하나씩만 저장하므로 중복된 스코어가 존재할 수 없음

  7. 이터레이터 사용시 포인터와 비슷한 인터페이스를 가진 객체를 사용하여 배열과 다른 형식의 데이터를 훑고 지나갈 수 있음

  8. STL 함수를 STL 컨테이너 클래스들에 대한 이터레이트 및 보통 포인터에도 사용할 수 있게 함으로써 일반성을 높이기 위해

  9. 한 객체를 다른 객체에 대입 가능, 자체적인 메모리 관리, at() 메소드를 이용하여 경계 검사 가능

  10. sort() 함수와 random_shuffle() 함수는 임의 접근 이터레이터를 요구하지만 list 객체는 임의 접근이 불가능함
    리스트 템플릿 클래스의 sort() 메소드를 사용하여 정렬할 수 있으나 random_shuffle()에 대응하는 메소드는 없음

  11. 15가 10보다 큰지 비교하여 botrue 대입

Tags:

Categories:

Updated: