1. map 기초

- key, value의 쌍 저장.

- key는 중복될 수 없다.

- 키 기준으로 binary search tree 로 자동 정렬한다.


insert

키는 중복으로 삽입할 수 없고 삽입의 결과 iterator를 반환한다.


operator[]


key 에 대응하는 값을 참조로 반환하고, 키가 없으면 새 요소를 삽입한다.


map<string, int> scoreMap;

int value = scoreMap["coco"]; 일 경우 예외가 발생하진 않으므로 안전하지 않다.


find

키를 기준으로 반복자를 검색한다. 못찾을 경우 end iterator를 반환한다.


swap

두 맵의 키와 값을 바꿔준다.


clear

전체 맵을 삭제한다.


erase

한 요소를 제거한다.


2. set 기초

map과 거의 같지만 key가 아니라 value 자체가 키가 됨으로 중복되지 않아야 한다.


3. map 활용

- 키값으로 클래스를 사용할 수 있지만 정렬을 해야함으로 operator< 연산자를 클래스에 만들어야 한다.

- 다른 사람이 만든 구조체나 클래스를 키로 사용할 경우 연산자를 재정의 할 수 없으므로 별도의 연자 구조체를 만들어야 한다.

struct Comparer

{

bool operator()(const className& left, const className& right) const

{

....

return ...

}

}

1. 기본 함수


reserve 와 resize의 차이.

reserve는 vector의 용량을 늘리는 것으로 적절한 필요 공간을 알경우 새로운 저장 공간 재할당을 막을 수 있으니 잘 사용하자.

실제 값이 할당되는 것은 아니다. 이지 크기가 더 큰 경우에는 용량을 줄여도 허용되지 않는다.


resize는 vector의 크기을 조정하는데 reserve와는 이미 값을 있는 경우에 값을 삭제해 버리니 주의해야 한다.


operator[](size_t n)

지정된 위치의 요소를 참조로 반환한다. 즉 값을 변경할 수 있다.


iterator

STL container 를 순회할 때는 반복자를 쓰는게 좋다.


for(std::vector<int>::iterator iter = scores.begin(0; iter != scores.end(); ++iter)

{

std::cout << *iter << " ";

}


루프를 거꾸로 돌 경우에는 rbegin(), rend() 를 사용한다.


insert

값을 삽입할 수 있다. 아래와 같이 하면 첫번째로 80 이 삽입된다.


std::vector<int>::iterator it = scores.begin();

it = scores.insert(it, 80);


assign

입력값을 연속으로 대입한다. 아래와 같이 하면 숫자 100 이 7개가 추가된다.


vector<int> scores;

scores.assign(7, 100);


swap

두 vector의 값을 서로 교환할 수 있다.


vector<int> scores1;

...

vector<int> scores2;

...


scores1.swap(scores2);


clear

vector를 지운다. 크기는 0이 되지만 용량은 줄이지 않는다.


erase

특정 iterator에 있는 값을 삭제한다.


*iterator

iterator 의 값을 얻을 수 있다. iterator 자체는 포인터 처럼 사용할 수 있다.


2. 사용

- vector 에 개체를 직접 보관할 경우 메모리 복사 발생 시 비용이 높아지게 됨으로 포인터를 저장하는 것이 일반적이다. 물론 이때 메모리 해제를 관리해야 한다.


vector<Score*> scores;

...


for(Score* pScore : scores)

{

delete pScore;

}


scores.cleare();


c++ 이전에 문자열 처리는 단지 null-terminated char현 배열이었다. 이런 불편함을 해결하기 위해 c++에서는 std::string 클래스가 사용된다.



string에서 전통적인 c style의 함수를 사용하기 위해서 두가지 함수가 사용된다. 차이점은 C++ 17부터 data함수가 리턴값으로 const형을 사용하지 않는 char*를 사용할 수 있게 되었다. 역시 함수의 인자로 넘겨줄때 필요할 수 있겠다. 하지만 이와같은 함수는 오로지 기존의 legach c-style의 함수를 사용할 때만 사용해야 할 것이다.



string을 초기화하는 여러가지 방식을 설명한다.

Line 18: brace초기화 방법을 통해 특정 문자열로 바로 초기화 할 수 있다.

Line 21: 생성자 초기화를 통해 임을 글자를 반복하여 초기화 할 수 있다.

Line24: 다른 문자열을 기반으로 임의 영역을 초기화 할 수 있다.



숫자를 스트링으로 변환시 to_string을 이용할 수 있다. 다형성을 지원하여 모든 종류의 숫자를 스트링으로 변형하는데 아쉽게도 아직까지는 자릿수는 지정할 수 없는듯 하다.



스트링 안에 캐릭터들에 접근하여 값을 변경할 수 있다. 샘플 코드에서는 std::toupper 로 사용했었는데. 실제 코드에서는 컴파일 에러가 나서 한참 헤매다가 전역 함수로서 처리하여 테스트를 통과 시켰다.



부분 문자열을 추출하는 함수다. 첫번째 인자로 인덱스 두번째 인자로 count 를 사용할 수 있는제 Line50, 53을 보듯이 count는 범위를 벗어나도 문제가 발생하지 않는다.


스트링은 연산자 재 정의를 통해 비교할 수 있고, Compare함수를 통해 비교할 수 있다. 대신 Compare 함수를 이용하면 좀 더 상세하게 비교할 수 있다. 비교시 인자보다 클경우는 0보다 큰 값을, 동일할 경우 0값을, 작을 경우 0보다 작은 값을 리턴한다. 문자열의 크다, 작다는 각 대응 글자마다 비교하면서 내부 값을 가지고 비교하는 것이다.

포인터에 대한 깊이 있는 설명은 얇은 책 한권은 나올만한 분량일 것이다. 여기서는 간략히 Modern C++에서 추가된 개념에 대해서만 정리하자. 전통적인 포인터 사용법외에 두가지 중요한 포인터 관리 크래스가 추가되었다.




포인터 연산에서 뺄셈은 가능하다. 그 뺄셈은 두 포인터 주소의 차이를 포인터의 크기로 나눈 것인테 ptrdiff_t 형이 추가 되었다. 결국 이것은 내부적으로는 int64나 int형이 재정의된 것이다. 어떤 특별한 장점이 있다기 보다는 포인터의 차이값이란 의미를 강조하기 위한 것으로 보인다.



uniq_ptr 클래스가 추가되었다. 포인터를 유일하게 관리하는 클래스이며 scope를 벗어나면 자동으로 메모리에서 해제가 된다.

라인 15, 16 : 명시적으로 선언하여 메모리를 할당하기 보다는 make_unique를 통해 하는 것을 권장하고 있다. 사실 라인 15에서 선언하는 방식이 문제가 있는 것은 아니지만 함수의 인자로 선언하면서 동시에 할당할 경우 문제를 일을킬 수 있다고 한다. 가급적 라인 16의 방식으로 사용하도록 하자.


라인 20: get 함수를 이용하여 직접적으로 포인터 주소를 가져올 수 있다.

라인 24, 25: release 함수는 포인터 주소를 리턴하면서 객체의 내부 값을 초기화 하는 것이다. 사용자가 직접 메모리를 해제해 주어야 한다. 실질적으로 어떤 경우 이런 함수가 필요한지는 잘 모르겠다. 별로 쓸일이 없을듯. 내부의 메모리를 실질적으로 해제까지 하는 것으로는 reset 함수를 사용하면 된다.



shared_ptr는 포인터를 다른 쉐어드 포인터에 넘겨줄 수 있다. 내부적으로 reference counter를 가지고 있어 모든 공유된 포인터 객체가 범위를 벗어나면 메모리를 해제하게 된다.

라인 36: reference counter를 확인할 수 있다.



레퍼런스는 포인터처럼 직접적으로 주소를 접근하지만 보통의 변수명 처럼 사용할 수 있다. 기존의 C++과 다르지 않은 사용법이지만 새로운 for문을 이용하여 객체 복사를 하지 않고 배열의 값을 변경할 수 있음을 테스트 해보았다.


배열과 반복문에 대한 부분이다. 아직까지는 C++의 기초적인 부분을 이어가고 있다.



brace 를 이용하여 동일하게 배열을 초기화 할 수 있다. 초기화시 배열의 갯수보다 적은 경우 자동으로 초기값이 입력된다. 많으면 컴파일타임에 에러가 발생한다.



배열의 사이즈를 알 수 있는 함수가 추가되었다. 이제 비 직관적인 sizeof 를 이용할 필요가 없어졌다!



만세이!!. for 문이 진화했다. 배열을 인식하여 루프를 돌 수 있게 되었다. 참고로 위에 145는 일일히 계산한 것은 아니다. 적당한 값을 입력하고 실행하여 테스트가 실행하면 계산값을 보여주며 실패하는데 이 숫자를 Test Explorer창에서 확인한 것이다.



이제 장점을 알다 못해 인이 배길 정도의 초기화이다. 자리 수 보다 적게 하면 적절한 초기값이 설절된다.



드디어 다차원 배열이 나오고 역시 초기화로 설명 시작이다. 테스트 코드를 보면 초기화의 기능을 알 수 있을 것이다.

라인 53 부터의 주석은 배열의 사이즈를 초기화를 통해서 지정할 수 있음을 보여준것이다.



STL을 이용한 배열이 나왔다. Vector와의 차이점은 한번 선언하고 나면 크기를 조절할 수 없다. STL array 전통적인 배열과 비교했을때 오버헤드가 없다고 한다.

line 67: array 도 초기화를 동일하게 할 수 있으며,

line 74: 멤버함수 fill을 통해 전체 배열을 동일한 값으로 설정할 수 있다.

line 77: 멤버함수 size를 배열의 크기를 알 수 있다.



배열은 ==, <, > 연사자를 이용해서 비교할 수 있다.

line 86: 두 배열의 사이즈가 같고, 각 요소의 값을 서로 같을 경우에 두 배열은 같다고 판단한다.

line 88: 두 배열의 각 요소를 일대일 비교하면서 서른 다른 값이 처음 나올때 그 두 요소값을 비교한다.

line 90: 전통적인 배열과 다르게 assign 연산자를 통해 배열의 값을 복사 할 수 있다.

line 93: 만약 호환성을 위해 전통적인 방식의 배열을 인자로 넘겨줘야 할 경우 멤버함수 data를 이용해서 얻을 수 있다.

분기문에 대한 설명이다. 이부분은 Modern C++도 재미있는 키워드를 추가한 것 이외에는 변경사항이 크게 없어 보인다. 단 이 추가된 부분은 매우 코드를 짜임새 있게 해 줄 것 같다.



if 문이 위와 같이 변수 생성을 포함할 수 있다. 그리고 그 변수의 생존 범위는 if 문 까지가 된다.




swtich 문도 동일한 기능을 추가하고 있다.

기본 데이타를 조작하는 방식에 대한 설명이다. Modern C++ 이전과 크게 달라진 것은 없어보이지만 몇가지 재미있는 기법과 키워드들이 추가되었다.



이전 장에서 배운 것 처럼 변수를 brace를 이용하여 초기화하고 bit masking 하는 법을 보여주고 있다.

라인 6 : 이진수로 초기화 하면서 구분자로 각각의 논리적인 영역을 표시하여 가독성을 높일 수 있다. 쓰면 쓸수록 괜찮은 기법이다. 이거 언제부터 가능해진거지? 책에서는 C11, 14, 17을 구분하면서 설명하지 않아 언제부터 가능해진 것인지 알기 어렵다.




이거는 사실 특별할 것 없는데 그동안 & 연산과 | 연산 이외에는 비트 연산을 해본적이 없어서 한번 테스트 해 보았다. 어떤 경우 이 연산이 필요한지는 아직도 모르겠다.



enum 값이 타입을 가질 수 있게 되었다. 더블어 enum도  class를 붙여줘야 한다. 내부적으로 어떤식으로 구현했는지 가늠해 볼 수 있는 부분이다.  enum 이 Type Safe 해 졌고, 열거형 변수를 사용하려면 :: 인 scope 지정자를 사용해야 한다.

이전의 enum 의 경우에는 하나의 네임스페이스 안해서는 열거형 변수명이 달라야만 했다. 하지만 class화 되면서 이젠 동일해서 컴파일에 문제가 발생하지 않는다. 또한 강제로 캐스팅 할 수 없고 명시적으로 static_cast 를 이용해야맘 한다.



이 코드는 어떤식으로 테스트 해야할 지 몰라 그냥 코딩만 해 두었다. 위 코드처럼 별명을 붙일 수 있다. 물론  이 기능은 typedef 과 동일할 거지만 = 기호로 인해 좀 더 가독성이 높아진것 같다.



전역변수의 경우 :: scope 연산자를 이용하여 좀더 가독성이 전역 변수임을 확인할 수 있다.

괄호를 이용하여 초기화 한다. 초기화에 별다를 게 뭐 있다고 이런 기능이 추가 됐나 하면서 내용을 읽어보았다.

-------------------------------------------------------------------------------------------



라인 8을 보자. 괄호안에 값을 넣지 않으면 적절한 기본값으로 설정된다. 만약 괄호가 없었다면 어떤 쓰레기값이 있을지 모르겠다.

라인 15를 보자. 자리수를 그룹화 하여 표현할 수 있다. 물론 안해도 그만이지만 훨씬 더 가독성이 높아졌다. 물론 자리수를 3 자리나 4자리 아니 아무렇게나 해도 이의(?)를 제기하지는 않는단다.



괄호를 이용하면 변수가 허용하는 값 범위내에서는 초기화가 가능하다.




괄호를 이용해야만 16진수를 입력할 수 있는 것은 아니지만, 16진수를 입력할 수 있다. 8진수는 숫자 0, 2진수는 숫자 0 과 B를 분이면 된다.



sizeof  키워드는 글자 그대로 메모리에 차지하는 공간의 크기를 물어보는 것이다. 입력값이 실제 숫자이든, 자료형이던간에 그대로 할당되는 메모리 공간을 알려준다.



c++을 하다보면 땔레야 땔 수 없는 수치함수 들이다. 이참에 잘 이해하고 넘어가 보자.

라인 57 에서 절대값은 쉽다. 그저 음수를 양수로 바꿔주는 것이다. 그다음 ceil과 foor 인데, 우리말로 하면 올림과 자름정도 되겠다. 근데 그 올림과 자름이 0을 기준으로 하고 있다.

라인 65 부터는 exp, log, log10을 설명한다. 지수, 자연로그 등을 표현한다. 라인 71 부터는 root와 제곱이다. 지금 예를 들은 것은 딱 떨어지므로 EXPECT_EQ로 비교하였다. 실제로는 line 65처럼 비교해야 할 거로 생각된다.

라인 76은 정수와 가까운 쪽으로 처리된다. 즉 반올림이라고 할 수 있다. 숫자가 정확히 가운데를 가리킬경우 바깥쪽으로 정해진다.

그 외에 sin, cos, tan 값이 있고 인자는 항상 radian 값을 사용한다.

static_cast keyword를 이용한 명시적 변환히다. C++ 17에 국한된 연산자는 아니고 C++ 스타일의 cast 기법이다.

static_cast 는 컴파일 타임에 에러를 만든는 반면에 dynamic_cast는 런타임시 사용되며 상속관계가 있어야 사용할 수 있다.


+ Recent posts