[Modern C++] 오른값(rvalue) 참조

 

우선 개념적으로 왼값(lvalue)와 오른값(rvalue)가 있다.

 

lvalue(left value) : 단일식을 넘어서 계속 지속되는 개체

rvalue(right value) : lvalue가 아닌 나머지 값(임시 값, 열거형, 람다, i++ 등)

 

이렇게만 보면 이해가 안될 텐데 구체적으로 왼값과 오른값이 뭘까?

 

1-1. 왼값, 오른값 설명

'='을 기준으로 나누게 값을 나누게 되는데 왼값의 설명을 다시 한번 쓰자면 

'단일식을 넘어서 계속 지속되는 개체'이다. 이게 무슨 뜻이냐면 15번 라인에 변수 a를 선언하고 이 라인을 넘어서

계속해서 값을 변경하거나 사용할 수 있는 개체라는 것이다. 이것이 왼값이다. 반대로 = 오른쪽에 있는 정수 3은

해당 라인 이후로는 더이상 지속되지 않는다. 일시적인 임시 값이라는 뜻이다.

 

 

2-1. 클래스 예시

클래스를 이용해 실습을 진행해보자.

Hoon 클래스를 만들고 따로 함수를 만들어서 해당 클래스를 받아주는 형식으로 코드를 만들었다.

27번 라인에서 함수에 lvalue를 넘겨주었고 당연히 정상 작동한다. 

28번 라인은 Hoon 클래스의 기본 생성자를 바로 넘겨주는 형태이며 rvalue에 해당한다. 에러가 발생했다.

30번 라인은 동일하게 기본 생성자를 넘겨주는 형태이지만 const 참조 타입으로 선언하니 에러가 발생하지 않았다.

const 하나가 붙었다고 오른값을 받아주는 걸 확인할 수 있었다. 그러면 const가 없는 28번 코드는 왜 오른값을

받아줄 수 없을까?

 

오른값이라는 것 자체가 곧 사라질 임시적인 값이라는 뜻이기 때문에 해당 값을 받아주어서 다른 여러 동작들을

하게 되면 위험 부담이 크다. 그렇기 때문에 const라는 안전장치를 붙여줘야지만 받아줄 수 있는 것이다.

 

하지만 const로 선언하게 되면 여러 제약이 생긴다.

이미 공부했듯이 const는 읽기 전용으로 만드는 것이다. 수정이 불가능하다. 21번 라인에 함수는 const 참조로 

만들었기 때문에 클래스를 받아주고 난 후에 클래스 내부에 있는 값을 수정한다던가 클래스 내부 함수 등을

수정할 수 없게 된다. 이런 문제를 해결할 수 있는 것이 C++11의 오른값 참조이다. 

 

 

3-1. 오른값 참조

23번 라인에 오른값 참조 코드를 넣어주었다. 오른값 참조를 하겠다고 선언하는 방법은 && 참조 두 개를

붙여서 선언하는 것이다.

 

34번 코드를 보면 기존에 왼값이었던 hoon 객체를 더 이상 넣어줄 수 없는 것을 확인할 수 있고

반대로 35번 코드를 보면 기존에 오른값이었던 Hoon 클래스의 생성자를 정상적으로 넣어줄 수 있는 것을 볼 수 있다.

 

오른값 참조는 다른 일반 참조 타입과 마찬가지로 주소를 넘겨준다. 

그렇기 때문에 오른값 참조로 받아주고 데이터를 수정하면 원본 데이터가 수정된다.

 

 

그렇다면 이 오른값 참조를 굳이 사용하는 이유는 뭘까?

가장 큰 이유는 사용자에게 전달하는 의미가 일반 참조 타입과 다르기 때문이다.

 

이게 무슨 말이냐 하면

일반 참조 타입의 경우 원본 데이터의 주소를 넘겨주고 해당 원본 데이터를 마음대로 수정하고 사용할 수 있을뿐더러

원본 데이터를 계속 사용한다는 암묵적인 힌트를 주는 것이다.

 

하지만 오른값 참조의 경우 원본 데이터의 주소를 넘겨주고 원본 데이터를 마음대로 수정하고 사용할 수 있는 것은

동일하나 해당 원본 데이터를 사용하고 난 후에는 더 이상 사용하지 않겠다는 암묵적인 힌트를 사용자에게 주는 것이다.

 

원본 데이터를 이후에 사용하지 않겠다는 것은 해당 원본 데이터를 받아들인 함수 등에서, 즉 외부에서 마음대로

원본 데이터를 수정하고 사용할 수 있다는 것이기 때문에 여러 이점들이 생긴다.

 

원본 데이터에 피해를 주지 않기 위해 깊은 복사를 통해 원본 데이터를 안전하게 두고 깊은 복사를 진행한 데이터를

수정하는 방식으로 사용했다면 오른값 참조 같은 경우 원본 데이터를 마음대로 이용해도 되기 때문에 깊은 복사가

아닌 얕은 복사를 진행해서 원본 데이터를 수정하면 된다. 왜냐하면 해당 원본 데이터는 이후에는 사용하지 않을 것

이기 때문이다.

 

 

이러한 오른값 참조는 다음과 같이 std::move를 통해 간단하게 사용할 수 있다.

4-1. std::move()

std::move를 사용하게 되면 44번 라인의 코드와 같이 동작하게 된다.

즉 왼값인 hoon1을 오른값 참조로 캐스팅하는 것이다. 오른값 참조는 위에서도 얘기했듯이 원본 데이터를

마음대로 사용해도 되고 이후에는 원본 데이터를 사용하지 않을 것이기 때문에 원본 데이터를 복사했다기보다

이동했다는 개념이라고 생각하면 된다. 그래서 이름이 move이다.

 

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

[Modern C++] 람다 표현식  (0) 2022.07.19
[Modern C++] 전달 참조  (0) 2022.07.18
[Modern C++] override, final  (0) 2022.07.15
[Modern C++] nullptr  (0) 2022.07.15
[Modern C++] 중괄호 초기화 { }  (0) 2022.07.14

 

[Modern C++] override, final

 

이번에는 오버라이드와 파이널에 대해 알아보자. 

중요한 개념이니 잘 숙지해두자.

 

override는 재정의라는 뜻으로 이미 존재하는 함수를 다르게 동작시키기 위해 재정의하는 것을 말한다.

이와 비슷한 개념으로 오버 로딩이 존재하는데 이미 공부했듯이 함수의 이름을 재사용하는 것을 말한다.

(즉 같은 함수 이름을 사용하지만 함수의 형태가 달라서 공존할 수 있다는 것이다.)

 

다시 본론으로 돌아와서 재정의 실습을 진행해보겠다.

 

 

1-1. 실습

두 개의 클래스를 정의하고 한쪽에서 다른 한쪽의 클래스를 상속받는 형태로 만들었다.

그리고 간단하게 문자열을 출력하는 함수를 만들었으며 자식 클래스에서 해당 함수를 재정의하는 형태이다.

메인 함수에서 포인터 클래스 타입으로 지정하고 동적할당을 이용해서 메모리 할당을 진행했으며 자식 클래스의

함수를 호출하는 모습이다. 당연하게도 출력은 자식 클래스의 함수가 호출된다.

 

하지만 여러 프로그래밍을 하다보면 한 개의 부모 클래스의 여러 자식 클래스가 존재하는 경우가 많다. 

그래서 부모 클래스의 포인터 타입으로 지정해서 코딩을 좀 더 효율적으로 하고 싶은 상황이 발생한다. 

그래서 다음과 같은 문제가 발생할 수 있다.

 

 

1-2. 실습

 효율적인 코딩을 위해 부모 클래스 포인터 타입으로 선언을 해주었지만 호출되는 함수는 재정의한 자식 클래스

함수가 아닌 부모 클래스의 함수가 호출되는 것을 알 수 있다. 이 문제를 해결하는 방법은 이전에도 공부한 적이 있는

가상 함수를 이용하는 방법이 있다.

 

 

1-3. 실습

15번 라인을 보면 부모 클래스의 함수를 가상 함수로 선언해주었다.

이렇게 가상 함수를 이용하게 되면 메인 함수에서 어떤 포인터 클래스 타입으로 선언하든 동적 할당된 원본의

함수가 호출된다. (이와 비슷한 느낌으로 최상위 부모 클래스 혹은 부모 클래스의 소멸자에는 virtual을 붙이는 것이

필수적이라는 걸 잊지말자!)

 

 

위의 코드에 아쉬운 점이 있다면 가독성적인 측면이 떨어진다는 점이다.

어느 부분에서 가독성이 떨어진다는 것일까?

 

1-3 이미지와 같은 코드에서 다른 사용자가 부모 클래스를 보지 않은 상황에서 자식 클래스만 확인했다고 가정해보자

그러면 Hellow라는 함수가 해당 자식 클래스에서 처음 시작된 함수인지 부모 클래스에도 같은 함수가 존재하는지 

알 수 있는 방법이 없다.(직접 부모 클래스의 내부를 확인하지 않는 이상) 이러한 부분에서 가독성이 떨어지며 또한

해당 함수가 어디에서 처음 시작됐는지에 대한 부분은 상당히 중요하다.

 

그러면 어느 부분부터 처음으로 함수가 시작됐는지가 왜 중요할까?

 

 

2-1. 실습
2-2. 실습

1-3 이미지의 코드에서 새로운 최상위 부모 클랙스가 추가되었다. 

Mother 클래스 또한 최상위 God 클래스를 상속받는다. 그리고 God 클래스의 함수는 가상 함수로

선언되어 있지 않다.

 

이러한 상태에서 메인 함수를 보면 최상위 클래스의 포인터 타입으로 동적 할당을 진행했다.

예전처럼 코드를 실행했지만 원본의 함수가 호출되는 것이 아닌 최상위 클래스의 함수가 호출되었다. 

최상위 클래스의 함수가 가상 함수가 아니기 때문에 이런 일이 발생하였으며, 중간의 Mother 클래스부터

가상 함수가 적용되었기 때문에 함수가 처음에 어디에서 시작되는지 아는 것이 상당히 중요하다.

 

이 문제를 해결하기 위해선 자식 클래스에서 처음 함수가 시작되는 것인지 부모 클래스에서 받아와서

사용하는 것인지 정보를 알려줄 필요성이 있다. 그래서 사용하는 것이 override이다. 이미지를 통해 알아보자.

 

 

3-1. 실습

36번 라인을 보면 함수 맨 끝에 override가 붙은 걸 확인할 수 있다. 

override가 붙게 되면 자식 클래스에서 처음 선언한 함수가 아니라 부모 클래스에서 해당 함수를

상속받아서 재정의해서 사용하고 있다는 뜻이 된다.

 

이렇게 되면 자식 클래스에서만 함수를 봐도 해당 함수가 자식 클래스에서 처음 선언한 함수가 아닌

부모 클래스에서 상속받은 함수라는 정보가 추가되는 것이다. 가독성이 좋아지며 코드가 오동작하는 것도

방지할 수 있는 안전장치인셈이다.

 

자식 클래스에서 override를 확인하고 부모 클래스로 이동했더니 똑같이 override가 있을 수 있다. 그렇다는 소리는

해당 부모 클래스 또한 상위 부모 클래스에게 함수를 상속받아서 재정의하고 있다는 뜻이다. 

 


 

이제 간단하게 final에 대해 알아보자.

final은 override와 동일하게 함수 뒤쪽에 붙여주면 되고, final을 붙이게 되면

해당 함수가 override 하고 있다는 힌트와 더불어서 해당 함수에서 마지막으로 사용할 것이고 

자식 클래스들에게 더 이상 재정의를 하게 만들어주지 않겠다는 의미이다.

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

[Modern C++] 전달 참조  (0) 2022.07.18
[Modern C++] 오른값(rvalue) 참조  (0) 2022.07.16
[Modern C++] nullptr  (0) 2022.07.15
[Modern C++] 중괄호 초기화 { }  (0) 2022.07.14
[Modern C++] auto  (0) 2022.07.14

 

[Modern C++] nullptr

 

nullptr은 누구나 사용하고 있을 정도로 널리 퍼져있고 유명하다.

Modern C++에서 nullptr이 등장하기 전에는 0 혹은 null이 사용되었는데 오늘은 예전에 쓰이던 방식과

nullptr의 차이를 알아보도록 하자.

 

 

1-1. 예전 방식

우선 예전 방식의 경우 이미지 1-1과 같은 문제가 있다.

0 or NULL값을 넣어주어도 결국엔 정수를 넣어주는 것이나 다름없기 때문에 포인터 형식의 함수가

호출되는 것이 아닌 int형 함수가 호출되는 것을 확인할 수 있다. 이러한 문제 때문에 사용자가 포인터 형식의

함수를 호출하고 싶은데 전혀 다른 정수형 함수가 호출되어서 문제가 될 수도 있다.

 

 

1-2. nullptr 사용

이번에는 nullptr을 넣어주었다. 결과는 예상대로 포인터를 받아주는 형식의 함수가 호출되는 것을

확인할 수 있다. 1-1 이미지처럼 nullptr를 사용하지 않으면 의도치 않게 잘못된 함수를 호출하거나 오동작이

일어날 수 있으므로 nullptr 사용은 필수라고 생각하면 된다.

 

 

nullptr은 가독성적인 측면에서도 도움이 된다. nullptr을 사용했다는 것은 포인터를 사용하고 있다는 것을

간접적으로 알려주는 것이나 다름 없기 때문에 사용하는 것이 여러모로 이득이 많다.

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

[Modern C++] 오른값(rvalue) 참조  (0) 2022.07.16
[Modern C++] override, final  (0) 2022.07.15
[Modern C++] 중괄호 초기화 { }  (0) 2022.07.14
[Modern C++] auto  (0) 2022.07.14
algorithm  (0) 2022.07.13

 

[Modern C++] 중괄호 초기화 { }

 

기본적으로 중괄호 초기화는 그리 어렵지 않다.

기존에 있던 문법에서 하나가 추가되었을 뿐이다.

 

1-1. 예시

1-1 이미지와 같이 C++11에서는 중괄호로 초기화하는 것이 추가되었다.

기존에는 vector 같은 컨테이너를 초기화할 때 중괄호를 사용할 수 없었지만 C++11에서 추가된

중괄호 초기화 덕분에 vector 또한 배열처럼 중괄호를 이용해서 한 번에 초기화가 가능해졌다.

 

 

2-1. vector 중괄호 초기화 사용

보는 바와 같이 기존에는 push_back이나 일반 괄호를 통해 초기화를 해주었지만 

중괄호 초기화 기능을 통해 일반 배열처럼 초기화가 가능해졌다.

 

 

중괄호 초기화 장점 정리

1. vector 등 컨테이너와 잘 어울린다.

2. 축소 변환을 방지한다.

    // 데이터 타입이 변환되는 과정에서 데이터 손실 등 무의식적으로 실행되던 코드들을 방지해준다.

 

 

물론 이러한 중괄호 초기화도 장점만 있는 것은 아니다. 단점 또한 존재하는데 조금 

문법적으로 알아야 할 것이 있다.

 

클래스나 컨테이너 등에서 중괄호 초기화를 사용해서 초기화를 해주면

initializer_list 타입의 생성자가 호출된다. 문제는 initializer_list 타입의 생성자의 우선순위가 매우 높아서

발생하는데 좀 더 쉽게 예시를 들어보겠다.

 

 

3-1. 단점 예시

우선 3-1 이미지와 같이 a 클래스를 명시적으로 만들어주었다.

그리고 기본 생성자와 정수 두 개를 받아주는 생성자, 마지막으로 initializer_list 타입의 생성자를 만들었다.

 

 

3-2. 단점 예시

그리고 메인 함수내에서 중괄호 초기화를 이용해 정수 데이터 두 개를 초기화하는 코드를 넣어주었다.

 

문제는 지금부터이다. 

사용자는 정수 두 개를 받아주는 생성자를 호출하기 위해 중괄호 초기화 기능을 이용하여 정수 두 개를

넣어주었지만 실행 되는 생성자는 initializer타입의 생성자가 호출된다. 그 이유는 위에서도 설명했듯이 

initializer의 우선순위가 매우 높기 때문에 다른 생성자를 무시하고 initializer 생성자가 호출되는 것이다.

 

이러한 문제들 때문에 중괄호 초기화 또한 만능이 아니며 무조건적으로 중괄호 초기화만을 사용하는 것도

옳은 방법이 아니다. 기존에 있던 문법들을 무시하며 의도치 않게 다르게 동작할 수도 있으니 사용 시 

주의가 필요하다.

 

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

[Modern C++] override, final  (0) 2022.07.15
[Modern C++] nullptr  (0) 2022.07.15
[Modern C++] auto  (0) 2022.07.14
algorithm  (0) 2022.07.13
STL - deque  (0) 2022.07.11

 

[Modern C++] auto

 

modern C++은 C++11 버전 이후부터를 modern C++이라고 한다.

C++11 이전과 이후는 엄청난 차이가 있기 때문에 C++11 문법을 잘 숙지하고 있어야 한다.

오늘은 그중에서 가장 자주 활용되고 중요한 auto에 대해 알아보자.

 

auto는 지금까지 몇 번 사용했듯이 컴파일러가 인자 타입을 추론해서 자동으로 넣어주는 기능을 지원한다.

추론의 기준은 넣어주는 데이터 타입에 따라서 하게 된다.

 

auto a = 3;

auto b = 3.14f;

auto c = "Hellow";

 

물론 추론 규칙이 복잡해지면 형식 타입을 추론하지 못해 에러가 발생하기도 한다.

auto* test1 = a; // a는 일반 정수 타입인데 auto 옆에 포인터 때문에 형식을 추론할 수 없다.

그렇기 때문에 auto만 붙여준다고 항상 100퍼센트 타입을 추론해주는 것이 아니다.

 

그리고  auto에서 가장 큰 특징이자 주의할 점이 있는데

기본 auto는 const와 &(참조)를 무시한다는 점이다.

 

예를 들면

1-1. test

7번 라인 같은 경우 데이터가 정수이기 때문에 자동으로 int형으로 지정될 것이며 

8번과 9번 라인에 각각 & 참조와 const int형을 선언해주었다.

 

그리고 11번과 12번 라인에서 auto를 이용해서 & 참조와 const int형 데이터를 받아주도록 했다.

결과적으로 test1과 test2에는 참조와 const int형이 지정되어야 하지만 일반 int형이 지정되었다.

 

이렇듯 auto는 const와 & 참조를 무시한다. 이 부분을 꼭 기억하자.

 

 

2-1. 해결법

const와 참조를 무시하는 특성을 해결하는 방법이다.

사용자가 명시적으로 참조와 const가 있어야 한다는 것을 알려줘서 해결할 수 있다.

 

 

일반적으로 auto를 자주 사용할 텐데

타이핑이 길어지는 타입 형태의 경우 auto를 사용하는 것이 추천되고 그 외에는 

일반 타입을 직접 타이핑하는 것이 가독성 측면에서 유리하다.

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

[Modern C++] nullptr  (0) 2022.07.15
[Modern C++] 중괄호 초기화 { }  (0) 2022.07.14
algorithm  (0) 2022.07.13
STL - deque  (0) 2022.07.11
STL - list  (0) 2022.07.08

 

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

+ Recent posts