algorithm

 

자료구조는 데이터를 저장하는 구조를 뜻하며

알고리즘은 데이터를 어떻게 사용할 것인가를 뜻한다.

 

C++에서는 다양한 알고리즘이 존재하는데 현업에서 자주 사용하는 알고리즘은 다음과 같다.

find
find_if
count
count_if
all_of
any_of
none_of
for_each
remove
remove_if

이제부터 하나씩 활용해보자.

 


 

1-1. find

find를 이용한 특정 데이터 찾는 방법이다.

배열의 시작과 끝을 넣어주고 찾는 정수를 넣어주면 된다.

반환 값은 iterator를 반환해주기 때문에 auto it 변수로 반환 값을 받아주었다.

 

 

1-2. find_if

특정 조건에 맞는 데이터를 찾아주는 find_if 이다.

find_if는 배열의 시작과 끝을 넣어주고 마지막엔 함수 객체를 넣어주는 것이 가장 이상적이다.

이번에는 구조체 struct와 operator를 이용해서 조건을 넣어주었다.

람다식을 이용하면 좀 더 코드를 간결하게 할 수 있지만 이 부분은 람다식을 공부한 이후에 정리하도록 하겠다.

 

 

2-1. count

find와 거의 비슷한 형태를 가지고 있는 count이다.

특정 데이터의 개수를 찾아서 반환해준다.

 

 

2-2. count_if

마찬가지로 특정 조건에 맞는 데이터의 개수를 반환해준다.

find_if와 크게 다른 점은 없다.

 

 

3-1. all_of / any_of / none_of

세 가지 기능은 묶어서 알아보자.

find나 count 형태와 같으며 사용자가 특정 조건을 넣어주면 true or false를 반환한다.

 

 

4-1. for_each

for_each는 위와 같은 활용 이외에도 데이터를 처음부터 끝까지 스캔하고 싶을 때 자주 사용된다.

 

 

5-1. remove / remove_if

remove와 remove_if는 사용 시 주의가 필요하다.

동작이 조금 특이하게 일어나는데, 5-1 이미지를 자세히 보면 remove를 호출하고 난 뒤 생각했던 데이터가 아닌

이상한 데이터들이 들어가 있는 걸 확인할 수 있다.

 

기존 v 벡터 배열의 데이터는 직접 [ 1 4 7 4 5 ]로 세팅해주었다.

99번 라인에서 정수 4를 remove 해주고 난 뒤 v 벡터의 데이터를 살펴보면 다음과 바뀌어있다.

[ 1 7 5 4 5 ]

왜 이렇게 되었을까?

 

이것을 알기 위해선 remove가 특이하게 동작하는 것을 자세히 살펴보아야 한다.

해당 컴파일러에서 remove를 선택하고 F12를 통해 코드를 상세히 보거나 구글링을 통해 remove의 동작 방식을

살펴보게 되면 remove 같은 경우 특정 데이터 혹은 특정 조건에 해당하는 값을 없애는 것이 아니라 조건에 맞는

데이터 이외에 유효한 값을 앞으로 이동시키고 남은 유효하지 않은 데이터의 시작 위치를 반환하게 된다.

 

99번 라인이 실행되면 기존 v 벡터의 데이터 [1 4 7 4 5 ] 중에서 유효한 값인 1, 7, 5의 값이 앞으로 이동하게 되고

반환 값으로 불필요한 데이터(4, 5)의 시작 위치인 4의 주소를 반환하게 된다.

 

remove_if 또한 마찬가지이다. 조건으로 홀수를 삭제하도록 넣어주었고 [ 1 7 5 4 5 ] 데이터에서 4를 제외한

전부가 홀수이기 때문에 유효한 값인 4만 맨 앞으로 이동하게 되고 불필요한 데이터(7, 5, 4, 5)의 시작 위치인

7의 주소를 반환하게 된다.

 

remove를 하게 되어도 실제 벡터에서 데이터가 삭제되는 것이 아니기 때문에 정말로 불필요한 데이터를 

삭제하고 싶다면 다음과 같이 erase를 사용하도록 하자

불필요한 데이터의 시작 위치인 7부터 벡터 끝까지 데이터를 삭제하는 코드이다.

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

[Modern C++] 중괄호 초기화 { }  (0) 2022.07.14
[Modern C++] auto  (0) 2022.07.14
STL - deque  (0) 2022.07.11
STL - list  (0) 2022.07.08
STL - vector  (0) 2022.07.05

 

deque

 

데크는 list와 vector의 중간 정도라고 생각하면 된다. 

vector처럼 배열 형식을 가지고 있지만 저장 공간이 꽉 찼을 때 독립적인 저장 공간을 하나 더 만든다.

vector의 경우 새로운 메모리 공간을 이전보다 크게 잡고 이전에 데이터를 복사해와서 연속적으로 데이터를

저장하지만 데크는 메모리 공간을 독립적으로 여러 개 만드는 형식이다. 그래서 vector의 특성과 list의 특성이

섞여 있다고 생각하면 된다.

 

ex) vector  

[ 1 1 1 ] // 최대 공간이 3개인 배열에서 데이터를 추가한다면 이 데이터는 복사 후 사라짐

[  1 1 1 2    ] // 이런 식으로 다른 메모리 공간으로 이동해 기존보다 1.5배 정도 크게 공간을 잡아줌

 

ex) deque

[ 1 1 1 ] // 마찬가지로 3개의 공간을 가지고 있는 상태에서 push_back 사용 시

[ 2       ] // 기존 메모리 공간을 유지하고 새로운 메모리 공간에 데이터를 잡아준다.

// push_front 함수를 사용해도 push_back과 동일하게 새로운 메모리 공간을 가져온다.

 

 

deque의 경우 중간 삽입/삭제는 느리게 동작하지만

처음, 끝 삽입/삭제는 빠르게 동작한다.

 

처음, 끝은 위에서 설명했던대로 새롭게 메모리 공간을 할당해서 데이터를 추가하거나 삭제하면 끝이지만

중간 삽입/삭제의 경우 vector처럼 다른 데이터들을 당겨오거나 밀어내거나 해야 하는 상황이 발생하기 때문에

비효율적이다.

 

그리고 마지막은 임의접근이다.

deque의 경우 임의 접근은 빠르다.

각 할당된 데이터 공간을 다르게 기억해두고 특정 데이터에 접근할 때 기억해두었던 데이터 공간을 호출해서

임의 접근을 하기 때문에 빠르게 동작할 수 있다.

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

[Modern C++] auto  (0) 2022.07.14
algorithm  (0) 2022.07.13
STL - list  (0) 2022.07.08
STL - vector  (0) 2022.07.05
템플릿 기초 - 클래스 템플릿  (0) 2022.07.05

 

list (연결 리스트)

 

기본적으로 list는 vector와 유사하지만 기본적으로 차이점이 존재한다.

vector는 동적 배열 방식을 사용하고 list는 노드 방식을 사용한다.

list는 동적 배열이 아니기 때문에 capacity를 사용하지 않는다.

list는 vector와 다르게 push_front를 지원한다.

list는 vector처럼 배열의 특정 인덱스에 접근하여 값을 수정하거나 가져올 수 없다.

list는 front 함수와 back 함수를 사용해 처음 데이터와 마지막 데이터를 가져올 수 있다.

vector와 마찬가지로 begin과 end 함수를 사용할 수 있다. 

등등~

 

 

list 또한 다음과 같은 부분을 이해하는 것이 중요하다.

- list 동작 원리

- 중간 삽입/삭제

- 처음, 끝 삽입/삭제

- 임의 접근

 

 

1-1. list 사용

기본적으로 vector와 그게 다르지 않게 사용할 수 있다. 

다른 점이 있다면 중간 삽입/삭제 등이 vector보다 수월하다는 것이다.

라인 24~29번까지 여러 기능들을 지원하고 vector에서는 지원하지 않는 기능들까지 지원하는 걸

알 수 있다.

 


list 원리 이해하기

 

vector는 데이터를 연속된 공간에 배치해야 하는 강제사항이 존재한다.

하지만 list는 그런 강제사항이 없다. 그래서 데이터들을 연속되지 않는 공간에 배치할 수 있다.

그러면 list 같은 경우 다음 데이터의 위치를 어떻게 알고 이동하는 것일까?

 

기본적으로 list는 포인터를 이용해서 다음 데이터의 위치 주소를 가지고 있다.

예를 들어 첫 번째 데이터는 본인의 데이터와 두 번째 데이터의 위치 주소를 가지고 있다는 소리이다.

list에서는 이러한 데이터와 주소를 저장하는 칸 단위를 '노드'라고 부른다.

 

한쪽 방향으로만 노드에서 노드로 이동할 수 있다면 단일 list

노드끼리 양방향으로 이동할 수 있다면 이중 list

노드끼리 양방향 이동뿐만 아니라 첫 번째 노드와 마지막 노드끼리도 이동할 수 있다면 원형 list

라고 한다. (STL - list는 이중 list로 구현되어 있다.)

 


처음, 중간, 끝 삽입/삭제

vector에서의 중간 삽입/삭제는 연속된 저장 공간에 데이터를 할당해야 하는 강제성 때문에

많은 양의 데이터를 한 칸씩 밀어내거나 당겨야 하는 매우 비효율적인 상황이 발생한다고 했다.

하지만 list에서는 노드라는 개념을 사용하기 때문에 데이터가 연속된 저장 공간에 할당되어 있지 않고

이전과 이후 데이터의 위치 주소를 가지고 해당 데이터로 이동하게 된다. 

그러므로 list에서 삽입/삭제는 vector보다 매우 효율적으로 작동하게 되고 실제로 데이터 배열 중간에

새로운 데이터를 삽입한다고 했을 때 이전 데이터의 주소와 이후 데이터의 주소를 맞춰주기만 하면 된다.

 

하지만 이러한 노드 방식 또한 단점이 존재한다. 바로 임의 접근이다. 

vector의 경우 연속된 저장 공간에 데이터가 할당되어 있기 때문에 특정 데이터에 바로 접근할 수 있지만

list의 경우 특정 데이터에 접근하려면 별다른 효율적인 방법이 없기 때문에 처음부터 주소를 타고 이동해가면서

찾는 방법 밖에 없다.

 

위와 같은 특징 때문에 list는 여러 칸을 연속해서 이동하는 기능들은 막혀있다.

 

한 가지 중요한 개념이 있다.

list는 중간 삽입/삭제가 빠르지만 임의 접근은 느리다. 

그렇다면 중간에 특정 데이터를 삭제하려고 했을 때 결국엔 임의 접근을 사용해서 그 데이터에 

접근해야 하기 때문에 전체적으로 보면 느린 것이 아닐까? 

 

맞는 말이다.

특정 위치에 있는 데이터를 중간에 삽입/삭제하는 것은 임의 접근을 사용해야 해서 비효율적이다.

그러면 어째서 중간 삽입/삭제는 빠르다고 하는 것일까

 

그 이유는 데이터를 push_back 할 때 특정 데이터의 정보를 iterator에 보관할 수 있고 

이렇게 iterator에 보관한 데이터 정보를 이용해서 나중에 특정 데이터에 빠르게 접근하여

중간 삽입/삭제를 할 수 있다. 이런 방법을 이용하기 때문에 중간 삽입/삭제가 빠르다는 것이지

아무런 정보도 없이 임의 접근을 해서 중간 삽입/삭제를 하는 것은 비효율적이다.

 

 

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

algorithm  (0) 2022.07.13
STL - deque  (0) 2022.07.11
STL - vector  (0) 2022.07.05
템플릿 기초 - 클래스 템플릿  (0) 2022.07.05
템플릿 기초 - 함수 템플릿  (0) 2022.07.04

 

vector

 

STL (Standard Template Library)에서 가장 많이 활용되는 vector에 대해 알아보자.

(STL의 개념은 프로그래밍할 때 필요한 자료구조나 알고리즘들을 템플릿으로 제공하는 라이브러리이다.)

 

vector는 STL의 컨테이너 중 하나이다. 여기서 컨테이너란 자료구조를 뜻하며 쉽게 말하면

데이터를 저장하는 객체이다.

 

vector는 워낙 다양한 기능들을 가지고 있기 때문에 원리에 대한 이해가 중요하다.

 

- vector의 동작 원리 (size / capacity)

- 중간 삽입/삭제

- 처음/끝 삽입/삭제

- 임의 접근

 

위와 같이 원리에 대해 공부해보자.


vector의 동작 원리 (size / capacity)

 

1-1. vector 기초
1-2. 결과

vector는 동적 배열로 동작을 하게 된다.

일반적인 배열은 시작 시에 사이즈가 정해지고 나중에 해당 사이즈의 변경이 필요할 때 

마음대로 바꿀 수 없다는 단점이 존재한다. 이러한 단점을 보완하는 것이 동적 배열이다.

즉 배열을 유동적으로 사용하는 것이 가능하다. 

 

그러면 어떻게 배열을 유동적으로 사용하는 것일까? 이 부분에 대한 이해가 중요한 포인트이다.

일단 두 가지 로직이 존재한다.

 

1) 처음에 메모리를 할당할 때 여유분을 두고 할당한다.

2) 여유분까지 메모리가 꽉 찼다면 메모리를 추가적으로 증설한다.

 

위 두 가지 로직을 기준으로 동작하게 되며 해당 사항을 좀 더 이해하려면 vector에서 

size와 capacity를 살펴봐야 한다.

 

size는 실제 사용 데이터 개수를 나타낸다.

capacity는 여유분을 포함한 총 용량 개수를 나타낸다.

 

size는 말 그대로 실제 사용 데이터 개수를 나타내기 때문에 데이터가 증가하는 대로 

차례대로 증가하지만 capacity 같은 경우 총 용량을 나타내기 때문에 기존 capacity에서 1.5배 혹은 2배 

만큼의 추가적인 여유분을 포함해서 나타내게 된다.

 

ex size

1, 2, 3, 4, 5, 6, 7, 8....

 

ex capacity

1, 2, 3, 4, 6, 9, 13, 19, 28, 42, 63....

 

위에서 메모리를 추가적으로 증설한다고 했는데 조금 더 자세히 말하자면 여유분까지 메모리가 꽉 찼다면

다른 메모리 장소에 이동해서 좀 더 큰 크기의 메모리를 할당해주고 기존의 값을 그대로 복사해오며 기존의 할당된

메모리는 반환된다. 하지만 기존의 할당된 메모리의 크기가 아주 크다면 복사해오는 것 자체가 매우 부담되는 일이고

또한 여유분들 어느 정도로 잡아놔야 하는지 예측할 수 없는 부분이기 때문에 곤란하다. 

 

이러한 문제를 해결하기 위해 동적 배열에서는 기존 capacity 크기에서 몇 배씩 큰 크기만큼의

여유분을 잡아주게 되는 것이다. 


크기  조정 함수

 

다음은 초반 capacity의 크기를 지정해줄 수 있는 함수이다.

capacity 크기 조정

처음부터 capacity를 너무 작게 할당해주면 비효율적으로 복사가 계속 일어나게 되므로 이것을 해결하기 위해

사용자가 원하는 크기만큼 초반에 메모리를 할당하게 해주는 함수이다.

 

 

다음은 size의 크기를 지정해줄 수 있는 함수이다.

size 크기 조정

reserve와 비슷한 개념으로 이번에는 size, 즉 실제 사용 중인 데이터 개수를 지정해 줄 수 있는 함수이다.

resize를 하게 되면 자동적으로 capacity의 크기 또한 같은 크기로 바뀌게 된다.

resize 이후에는 size와 capacity가 동일하게 되므로 이후에 push_back 같은 벡터 함수를 사용하게 되면

여유분을 추가하게 되며 복사가 일어난다. 그렇기 때문에 resize로 설정한 크기를 사용하고 싶다면 

push_back이 아니라 일반 배열 사용하는 것처럼 데이터를 넣어서 사용하면 된다.

 

 

다음은 vector 선언과 동시에 resize를 해주는 방법이다.

선언과 동시에 resize 해주기

7번 라인의 뜻은 v 벡터를 선언함과 동시에 1000 크기만큼 resize를 해주고 해당 초기 값을 전부 0으로

해주겠다는 뜻이다.

 

8번 라인은 위에서 선언했던 v 벡터를 v2 벡터에 그대로 넣어줘서 같은 resize 및 초기 값을 가지도록 한 것이다.


할당된 메모리 반환

 

할당된 size와 capacity를 날려주는 방법은 다음과 같다.

 

v.clear(); // size, 실제 사용 중인 메모리를 모두 반환하는 함수 단 capacity는 그대로 유지

 

vector<int>().swap(v); // 임시적인 벡터를 잠시 생성하고 swap 함수를 통해 기존의 v 벡터와 메모리를 swap 한다.

해당 라인을 벗어나면 임시로 만들었던 벡터는 사라지고 해당 임시 벡터의 메모리가 v 벡터로 들어갔기 때문에

size와 capacity 모두 0이 된다. 실전에서는 잘 사용하지 않는 꼼수 같은 개념이지만 알아두자.


삽입, 삭제와 반복자 개념

 

중간 삽입/삭제, 처음/끝 삽입/삭제를 알아보자.

우선 삽입, 삭제를 이해하기 위해선 반복자를 알아야 한다.

 

반복자는 STL의 구성물 중 하나이며 컨테이너마다 반복자를 하나씩 들고 있다.

쉽게 생각해서 포인터와 유사한 개념이다. 컨테이너의 데이터를 가리키고, 다음/이전 데이터로

이동이 가능하다. 

 

2-1. 반복자

이미지 2-1과 같이 포인터와 유사하게 사용할 수 있다.

 

 

3-1. 벡터 end 함수 특성

벡터의 begin 함수의 경우 벡터 배열의 시작 지점 주소를 가지고 있었지만

end  함수 같은 경우 벡터 배열의 유효한 값이 들어있는 끝 지점이 아닌 배열 자체가 끝나는 지점의 주소를 갖기 때문에

해당 end 함수는 사용 시에 주의가 필요하다.

 

 

그러면 어떻게 사용해야 할까?

4-1. begin, end 함수 사용법

이미지 4-1 라인 30~33번까지 실전에서도 많이 사용하는 방법이다.

begin으로 배열 시작 주소를 it 변수에 넣어주고 end 함수가 될 때까지 1씩 증가시키면서

배열 내용을 출력하는 형식이다. 배열 안에 데이터로 접근하기 위해 포인터를 붙여주었다.

30번 라인 끝 부분에 it++ 보다는 성능적으로 ++it가 약간 더 우위에 있다.

그러니 되도록이면 ++it를 사용하도록 하자.

 

이미지 4-1 라인 30~33번을 보면 iterator를 사용하니 뭔가 복잡해 보인다. 

그러면 왜 이렇게 사용하는 것일까

이유는 간단하다 iterator는 벡터뿐만 아니라 다른 컨테이너에도 공통적으로 존재하는 개념이다.

그렇기 때문에 라인 30~33번처럼 만들게 되면 상황에 따라 컨테이너를 부분을 수정해서 수월하게

사용할 수도 있기 때문에 해당 방법이 권장된다.

 

 

다음은 일반 iterator 이외에 다른 기능을 하는 iterator이다.

5-1. reverse_iterator / const_iterator

라인 35번의 reverse는 배열의 시작과 끝을 반대로 출력하기 위한 reverse이다.

그리고 라인 40번의 const는 이미 알다시피 값을 변경할 필요가 없을 때 상수화 시켜주기 위한 

const이다. 이외에도 다양한 기능들이 존재함.


★ 이제부턴 삽입/삭제에 관해 알아보도록 하자

우선 벡터 같은 배열 형식은 중간 삽입/삭제가 매우 비효율적이다.

이유는 배열의 특성 때문인데 일단 배열은 원소(데이터)가 하나의 메모리 블록에 

연속하게 저장된다는 특성을 가지고 있다. 그런데 중간에 데이터를 삽입하거나 삭제하게 되면

배열에 연속하게 저장되어 있던 다른 데이터들을 모두 한 칸씩 밀거나 당기는 상황이 발생하게 된다.

만약 여기서 저장된 데이터가 아주 크다면 매우 비효율적인 이동을 하게 된다는 것이다.

 

처음/끝 삽입/삭제 또한 비슷한 개념이다. 

처음에 데이터를 삽입/삭제 하게 되면 기존에 저장되어 있던 데이터들을 모두 한 칸씩 밀어내야 하거나

당겨야 하는 상황이 발생하기 때문에 비효율적이고 반대로 끝 삽입/삭제 같은 경우 배열 끝부분에 데이터를

삽입하거나 삭제해도 기존 데이터의 이동이 발생하지 않기 때문에 효율적인 방법이라고 할 수 있다.

 

임의 접근은 배열에서의 개념과 동일하다.

벡터 또한 배열이기 때문에 배열의 특정 위치에 있는 데이터에 접근하여 해당 데이터를 사용하거나

수정하는 일이 가능하다. (다른 컨테이너는 임의 접근 불가능)

ex) v[3] = 1; // 4번째 데이터의 값을 1로 수정

 

 

비효율적이라곤 하나 상황에 따라 중간 삽입이 필요한 경우도 있다. 

그럴 때 사용하는 것이 vector의 insert 함수이다.

6-1. insert / erase 함수

위의 이미지 같이 사용할 수 있다. insert는 삽입이고 erase가 삭제하는 함수이다.

중요한 부분은 삽입/삭제 이후에 그대로 끝나는 것이 아니라 반환 값이 있다는 점이다.

삽입/삭제가 일어났을 경우 해당 위치를 반환 값으로 뱉어주기 때문에 해당 반환 값을 이용하는 방법도

존재한다.

 

 

이번에는 반환 값을 이용하는 방법이다.

처음부터 배열의 데이터를 쭉 스캔하면서 3이라는 데이터가 있으면 해당 3을 가지고 있는 부분을

일괄 삭제하는 방법을 알아보자. 실전에서도 자주 사용되는 방법이다.

7-1. 스캔 후 특정 데이터 일괄 삭제(에러발생 버전)

이미지 7-1 처럼 코드를 짜면 에러가 발생한다.

왜 에러가 발생하는 것일까?

에러가 발생한 곳은 iterator 삭제 이후 그 다음 값을 스캔하기 위해 호출하는 부분에서 발생했다.

에러가 발생한 이유는 iterator가 가지고 있는 ptr 값 이외에 다양한 정보(어느 컨테이너에 소속되어 있는지 등등)도

다 같이 삭제되기 때문에 삭제 이후로는 어떠한 형태로도 사용되면 안되는데 for문에 의해 스캔이 계속 되기 때문에 

에러가 발생한 것이다.

 

 

그러면 어떻게 해결할 수 있을까?

답은 삽입/삭제 이후에 반환 값을 활용하는 것이다.

7-2. 스캔 후 특정 데이터 일괄 삭제(정상 버전)

라인 54번을 보면 반환 값, 즉 삭제 이후에 해당 위치를 다시 it1에 넣어주는 모습이다.

코드를 자세히 보면 조금 이상한 점이 있다. 라인 50번 맨 마지막에 ++it1이 라인 56번으로 이동하였다.

그 이유는 스캔에서 놓치는 부분이 있기 때문이다. 

 삭제 이후에 위치를 다시 it1에 넣어주었으면 나머지 데이터들은 빈자리를 채우면서 당겨오게 된다.

그래서 삭제 위치에 바로 옆에 있던 데이터가 들어오게 되고 해당 데이터는 스캔을 하지 못하고 다음 데이터로

스캔이 이동하게 된다는 것이다. 그래서 ++it1를 else로 넣어줌으로써 삭제가 일어나지 않았으면 ++을 진행하고

삭제가 발생했으면 ++을 하지 않고 삭제한 위치에 새로 들어온 데이터를 다시 한번 스캔해주는 것이다.

 

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

STL - deque  (0) 2022.07.11
STL - list  (0) 2022.07.08
템플릿 기초 - 클래스 템플릿  (0) 2022.07.05
템플릿 기초 - 함수 템플릿  (0) 2022.07.04
함수 객체  (0) 2022.07.03

 

클래스 템플릿

 

템플릿 종류

1) 함수 템플릿

2) 클래스 템플릿

 

이번에는 클래스 템플릿에 대해 알아보자.

 

1-1 클래스 템플릿

기본적으로 클래스 템플릿 또한 함수 템플릿과 크게 다르지 않다.

typename T를 선언하여 어떠한 타입도 받아줄 수 있게 만들어줬다. 

 

이미지 1-1 22번과 30번 라인 같은 경우 Random 클래스 객체를 생성할 때 어떠한 타입인지 컴파일러가

알 수 없으니 명시적으로 인자 타입을 따로 넣어준 모습니다.

 

여기서 주의할 점은 무조건 typename을 붙여야 하는 건 아니다.

필요하다면 template<> 안에 typename 이외에 것을 넣어줄 수 있다.

 

 

2-1

이미지 2-1을 보면 template<> 안에 size를 받아주는 것을 볼 수 있다.

즉 사용자가 원하는 만큼의 크기를 외부에서 받아주는 것이다.

이미지와 같은 방식 이외에 기본 값을 설정해 줄 수도 있다. 

ex) template<typename T, int size = 10>

 

이런 식으로 클래스 템플릿을 사용해서 Random 클래스를 a1과 a2 객체로 만들어서 각각 사용하고 있는데

중요한 점은 a1 클래스와 a2 클래스는 완전 다른 클래스이다. 같은 Random 클래스를 사용해서 객체를 생성해주었지만

템플릿 인자가 서로 다르기 때문에 둘은 각각 따로 만들어진 다른 클래스인 것이다. 

템플릿 인자가 완벽히 같다면 동일한 클래스로 본다.

 


 

템플릿 특수화

 

3-1

함수 템플릿 때와 마찬가지로 템플릿 특수화를 적용할 수 있다. 

일반적인 템플릿 규칙 이외에 다른 규칙을 적용하고 싶을 때, 즉 예외를 만들고 싶을 때 사용할 수 있다.

고정적인 인자 타입을 사용할 것이기 때문에 라인 19번에 typename T를 없애주었고

특수한 상황임을 알리기 위해서 Random 클래스명 옆에 인자 타입과 사이즈를 추가해 주었다.

이후에 double 인자 타입이 사용된다면 설정한 특수 규칙이 실행될 것이다.

 

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

STL - list  (0) 2022.07.08
STL - vector  (0) 2022.07.05
템플릿 기초 - 함수 템플릿  (0) 2022.07.04
함수 객체  (0) 2022.07.03
함수 포인터 / typedef  (0) 2022.07.03

 

템플릿 기초 - 함수 템플릿

 

템플릿 : 함수나 클래스를 찍어내는 틀이라고 생각하면 된다.

1) 함수 템플릿

2) 클래스 템플릿

 

두 종류가 존재한다.

이 글에서는 함수 템플릿에 대해 알아보겠다.

 

 

1-1 함수 템플릿

코딩을 하다 보면 이미지 1-1과 같이 아주 비슷한 동작을 하는 함수를 오버 로딩해서 사용할 때가 있다.

하지만 코드 양이 많아지다 보면 매번 번거롭게 오버 로딩을 하기가 힘들 수 있다. 이런 때 사용하는 것이

함수 템플릿이다.

 

 

2-1
2-2 결과

2-1 이미지를 보면 템플릿을 사용하겠다고 선언하고 해당 타입 네임을 'T'로 정해두었다.

그리고 기존에 있던 함수 모양을 그대로 가지고 오고 타입 부분에 타입 네임 T를 넣어두면 된다.

결과창을 보면 정상적으로 동작하는 것을 확인할 수 있다. 

그렇다면 따로 타입별로 함수를 선언하지도 않았는데 어떻게 알고 각자의 타입에 따라서 실행되는 것일까?

따로 사용자가 명시적으로 선언하지 않으면 컴파일러가 자동으로 해당 매개 값 타입에 따라서 실행하게 된다.

이것을 컴파일러가 타입을 추론한다고 표현한다.

참고로 'typename'부분을 'class'라고 명시해도 동일하게 동작한다. 즉 typename을 사용하거나 class를 사용하거나

둘 다 같은 기능을 하니 상황에 따라 사용자에 따라 맞춰서 사용하면 된다.

 

 

3-1

사용자가 명시적으로 타입을 지정해줄 수 있다. 13번 라인에 int로 선언해주었기 때문에

매개 값으로 실수가 오더라도 int 타입으로 실행된다.

 

 

4-1
4-2 결과

이번에는 void가 아닌 매개 값 두 개를 받아서 하나의 값을 출력하는 형태이다. 

지금까지는 하나의 인자 타입만을 받아왔는데 서로 다른 인자 타입을 동시에 받아주는 것도 가능할까?

 

 

5-1
5-2 결과

typename을 두 개 사용하여 서로 다른 인자 타입을 받아줄 수 있다.

 


템플릿 특수화

 

템플릿 특수화는 일반적인 규칙(이미지 2-1의 4~9번 라인) 이외에 특정 타입에 대해서만

다른 규칙을 적용하고 싶을 때 사용한다.

 

 

6-1
6-2
6-3 결과

Knight 클래스 타입에 대해서만 다른 규칙을 적용한다.

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

STL - vector  (0) 2022.07.05
템플릿 기초 - 클래스 템플릿  (0) 2022.07.05
함수 객체  (0) 2022.07.03
함수 포인터 / typedef  (0) 2022.07.03
디버깅 연습문제  (0) 2022.07.02

 

함수 객체

 

함수 객체란?

함수처럼 동작하는 객체를 말한다.

함수처럼 동작하기 위해서는 함수 호출 때 사용하는 () 괄호 연산자를 오버 로딩해야 한다.

 

1-1
1-2

 

이렇게 하면 이제 함수 포인터의 단점과 다르게 

상태 값을 가지고 있으면서 함수처럼 호출할 수 있다.

 

 

2-1
2-2

 

이런 식으로 응용도 가능하다. int 값을 받아서 불리언 값을 뱉는 형식이며

true를 리턴으로 주었다. 

 

이러한 함수 객체의 가장 큰 장점은 실행할 것을 만들어두는 시점과 실제 실행하는 시점을 다르게

가져갈 수 있다는 것이다. 쉽게 말해 일감을 만들어두고 실행은 나중에 마음대로 할 수 있다는 것이다.

보통 MMORPG 서버 쪽에서 이러한 함수 객체를 유용하게 사용한다.

'프로그래밍 언어 공부 > C++' 카테고리의 다른 글

템플릿 기초 - 클래스 템플릿  (0) 2022.07.05
템플릿 기초 - 함수 템플릿  (0) 2022.07.04
함수 포인터 / typedef  (0) 2022.07.03
디버깅 연습문제  (0) 2022.07.02
객체지향 Text RPG 만들기  (0) 2022.06.28

 

함수 포인터

 

typedef int aaa; // 자료형 재정의 aaa를 int와 같이 사용하겠다는 뜻

// aaa fn1 = 0;

 

typedef int* bbb; // 주소 바구니 자료형 재정의

// bbb fn2 = &fn1;

 

typedef int ccc[20];

// ccc fn3

// int가 20개 존재하는 배열의 명칭을 fn3로 선언

 

typedef int(*A)(int, int); // 일반 함수 포인터

// 두 개의 int 매개 값을 받아서 하나의 int 값을 출력하는 함수 포인터

// A fn4;

// fn4(1, 2); 이런 식으로 사용 가능


class X

{

public:

 // 멤버 함수

int Get(int a, int b)

{

}

 

public:

 // 멤버 변수

}

 

typedef int(X::*MA)(int, int); // 멤버 함수 포인터

// MA fn5;

// fn5 = &X::Get;

// X x1;

// (x1.*fn5)(1, 2);

 

// X* x2 = new X();

// (x2->*fn5)(1, 2);

// delete x2;

 

typedef을 사용하지 않고 한 번에 선언이 가능하다.

 

int (X::*ma)(int, int);

// ma = &X::Get;

// X* x3 = new X();

// (x3->*ma)(1, 2);

// delete x3;

 

함수 포인터는 동작을 넘겨줄 때 유용하다.

하지만 함수 포인터는 시그니처(함수의 모양, 받아주는 매개 값과 출력하는 값 등을 말함)가 맞지 않으면 사용할 수 없다.

거기에 함수 포인터는 상태를 가지지 않는다. 상태라 함은 클래스 내부의 멤버 변수의 값 등을 말한다. 즉 데이터를 

가질 수 없다. 

 

+ Recent posts