C++의 동적 배열
vector , list, unorderd map 에 관해 알아보자.
c++의 동적 배열 = STL ( 표준 템플릿 라이브러리 / standard template library)
벡터 vector
유니티를 이용하신 분이라면 너무나 익숙한 벡터다.
하지만 또 다른 벡터라 눈물이 흐른다..
#include<vector>를 해주어야 한다.
벡터 방식은 배열과 비슷하다.
C#의 list와는 다르게 벡터는 연속되어져 있음.
벡터의 삽입과 출력
int main()
{
vector<int> list;
list.push_back(1);
list.push_back(2);
list.push_back(3);
}
int형 벡터 list를 선언해주고 1,2,3을 순서대로 넣었다.
안에 넣은 내용을 출력하기 위해서는 아래와 같은 코드를 써야한다.
1. 배열처럼 이용
for (int i = 0; i < list.size(); i++)
{
cout << list[i] << endl;
}
0부터 벡터의 사이즈까지 for문을 돌리며 출력한다.
익숙한 형태라 편했다.
2. iterator를 이용
for (vector<int>::iterator iter = list.begin(); iter != list.end(); iter++)
{
cout << *iter << endl;
}
iterator(반복자) 를 이용하여 벡터의 begin 부터 end가 아닐때까지 돌아주며 출력한다.
begin은 벡터의 첫번째 주소값을 리턴하지만, end는 마지막 주소값이 아니다.
iter에 주소값이 들어갔기 때문에 *붙여서 출력해준다.
3. auto
for (auto iter = list.begin(); iter != list.end(); iter++)
{
cout << *iter << endl;
}
C# 의 var와 같이 C++은 auto를 쓸 수 있다.
뒤에 반환되는 값의 형태로 리턴해준다.
명시적인 표현방법이 아니기 때문에 선호하지 않는 사람들도 있다.
벡터 값 지우기
list.removeat() 해버리고 싶지만 깐깐한 c++님에게 이런건 없다 ㅎ
int main()
{
vector<int> list;
list.push_back(1);
list.push_back(2);
list.push_back(3);
list.pop_back();
list.erase(list.begin());
for (vector<int>::iterator iter = list.begin(); iter != list.end(); iter++)
{
cout << *iter << endl;
}
}
1,2,3을 차례로 넣고 pop_back을 해주면 맨 뒤를(3을) 날려버린다.
stack과 유사 한것같다..
list.erase는 iterator를 써야한다. list.begin()을 넣어주면 제일 첫번째를 지워버린다.
list.erase(list.begin()+1);
이런것도 가능하다.
암튼 지워보면..
2만 남은것을 알 수 있다.
특정 값만 지우고 싶다면..
int main()
{
vector<int> list;
list.push_back(1);
list.push_back(2);
list.push_back(3);
for (auto iter = list.begin(); iter != list.end(); )
{
if(*iter == 2)
{
iter = list.erase(iter);
continue;
}
iter++;
}
for(auto iter = list.begin(); iter != list.end(); iter++)
{
cout << *iter << endl;
}
}
위 코드와 같이 작성해주면 된다.
중간에 iter = list.erase(iter); 라는 코드가 이해가 안됐는데 , 메모리 할당에 대한 얘기를 듣고나니 어느정도 이해가 됐다.
벡터는 연속되기 때문에 애초에 상자를 놓을 자리를 넉넉하게 잡는다.
만약 벡터에 값이 추가가 됐는데 상자가 연속되지 못하면 새로운 터를 잡고나서 기존 터를 없애버린다.
반대로 삭제할때도 벡터는 연속되어야 하기 때문에,
1. (A / B / C) 라는 상자가 있는 공간이 있는데 B가 삭제 됐다.
2. 벡터는 연속되어야 하기 때문에 (A / C)가 되어야 하는데 , (A / 빈자리 / C) 가 되어버렸다.
3. 벡터는 곧바로 (A / C) 를 만들 공간을 새로 만들고, (A / X / C) 가 있던 공간을 없애버린 후 이사를 간다.
4. 이사를 갔기때문에 벡터의 주소값이 바뀌어버렸다..
위의 과정이 생기기 때문에 iter에 지우고 난 후에 이사간 주소를 받아서 다시 for문을 돌려야 하는것이다.
벡터의 클래스
int main()
{
vector<Animal> list;
list.push_back(Animal(10));
}
이렇게 아까 int형 자료를 넣듯이 넣으면 문제가 있다.
클래스를 push_back으로 생성하면 생성하고 복사해서 값을 넣는다.
그래서 push_back은 일반적인 데이터 저장에 써야 좋다.
그럼 벡터로 클래스는 어떻게 생성해야 좋냐면..
int main()
{
vector<Animal> list;
list.emplace_back(10); // 클래스형 생성한 값 그 자체가 그대로 담김.
}
위 코드와 같이 emplace_back을 이용해주면 좋다.
생성한 값 그 자체가 들어간다.
복사는 이루어지지 않는것을 볼 수 있다.
잠깐 c++ 다른 이야기,, 객체지향 언어에서 Malloc - Free 를 안쓰는 이유
Malloc 으로 할당할때 class의 생성자가 호출이 안되며
Free로 메모리를 해제 해 줄때 소멸자가 호출이 안됨
그러므로 New- Delete를 많이 쓴다고함.
C++의 LIST
#include<list>를 해주어야 한다.
list와 vector를 비교해보면
속도면에서는 벡터가 연속되어져 있기 때문에 더 빠르다.
하지만 LIST는 노드끼리 연결이 되어있기 때문에 공간 상의 효율이 극대화 된다.
자료가 자주 바뀌지 않는다면 vector가 더 유리하다.
List의 삽입과 출력
int main()
{
list<int> List;
//리스트는 양방향으로 구현되어 있음.
List.push_back(2);
List.push_front(1);
List.push_back(3);
}
양방향으로 구현되어 있기 때문에 벡터와달리 push_front도 가능하다.
//사이즈는 얻어 올 수 있지만 벡터처럼 인덱스로 접근이 불가능 하다.
for (int i = 0; i < List.size(); i++)
{
cout << List[i] << endl; // <- 불가능!!
}
벡터처럼 인덱스로 접근이 불가능 하기 때문에 위의 코드는 안된다.
for (list<int>::iterator iter = List.begin(); iter != List.end(); iter++)
{
cout << *iter << endl;
}
벡터와 똑같이 iterator를 쓰자.
리스트 값의 삭제
List.erase(List.begin()); // 가능
List.erase(++List.begin()); // 가능
List.erase(List.begin()+1); // 불가능
for (auto iter = List.begin(); iter != List.end();)
{
if (*iter == 2)
{
iter = List.erase(iter);
continue;
}
iter++;
}
벡터와 별 다를게 없다.
위의 코드처럼 이용하거나, 아래 코드처럼 for문에 조건을 넣어서 삭제를 한다.
iter로 다시 주소값을 받아줘야 한다.
Unordered_map
#include<unordered_map>를 해주어야 한다.
C#의 해쉬맵, 딕셔너리를 떠올리면 된다.
원래는 그냥 map을 썼는데 map은 자동으로 key값을 정렬을 한다.
정렬 알고리즘이 동작하며 정렬을 할때 시간이 좀 걸리는데
실제 사용할때는 딱히 정렬이 필요가 없는 경우가 많아서 일반적으로는 unordered_map을 쓴다고 한다.
Unordered_map의 삽입과 출력
int main()
{
unordered_map<int, string> list; // key와 value로 이루어짐.
list.insert(pair<int,string>(1,"One"));
list.insert({ 2,"Two" }); // 유니폼 초기화
list.insert({ 3,"삼" });
list[3] = "Three"; //3번 키 값이 없으면 추가를 해주고, 이미 키 값이 있다면 이것으로 변경 됨.
for (unordered_map<int, string>::iterator iter = list.begin(); iter != list.end(); iter++)
{
pair<int, string> p = *iter;
cout << p.first << " : " << p.second << endl;
}
}
C#의 딕셔너리 처럼 <>안에 자료형을 넣어서 선언한다.
언오더드 맵에 값을 넣을때는 list.insert를 이용하는데 방법은 다양하다.
1. pair를 이용해준다.
2. 유니폼 초기화를 통해 넣어준다.
3. 배열에 값을 넣듯이 넣어준다.
벡터와 리스트와는 조금 다르게 출력을 해야하는데..
pair<자료형,자료형> 변수명 = *iter 를 선언해주고,
pair의 변수명. first (key값) / pair의 변수명.second(value값) 을 이용해야 한다.
Unordered_map의 삭제
for (unordered_map<int, string>::iterator iter = list.begin(); iter != list.end();)
{
pair<int, string> p = *iter;
if (p.second == "Two")
{
iter = list.erase(iter);
continue;
}
iter++;
}
벡터, 리스트와 너무나도 비슷하다.
다른건 pair를 선언하고 조건을 검사 후에 삭제하는 것 뿐이다.
C++ 의 유니폼 초기화
int a = {0};
위의 코드에서 언오더드 맵에 값을 넣는것처럼 대부분 이런식으로 중괄호를 이용해주면 초기화가 된다고 한다.
이것을 유니폼 초기화라고 한다.
C++의 foreach?
C#에는 편리한 반복문이 있다.
Foreach문인데 범위형으로 반복을 해준다.
foreach(int a in list)
{
//Todo
}
거의 똑같은것이 C++에도 존재한다고 하는데 아래와 같다.
/*
for (unordered_map<int, string>::iterator iter = list.begin(); iter != list.end(); iter++)
{
pair<int, string> p = *iter;
cout << p.first << " : " << p.second << endl;
}
*/
//위의 코드와 같다
for (pair<int, string> p : list)
{
cout << p.first << " : " << p.second << endl;
}
foreach라고 쓰지 않고 그냥 for라고 똑같이 쓰면서 foreach의 in 대신에 ' : ' 를 쓰면 된다.
벡터 리스트 언오더드 맵 전부 사용 가능하다.
정말 편리했다.
'프로그래밍 공부 > C++ 프로그래밍' 카테고리의 다른 글
Direct X 4일차💻 <WINAPI> #2 (0) | 2022.06.19 |
---|---|
WINAPI 추가정리 (0) | 2022.06.19 |
Direct X 3일차💻 <WINAPI> #1 (0) | 2022.06.19 |
Direct X 수업 2일차💻 스마트 포인터 (0) | 2022.06.15 |
direct X 수업 1일차💻 C++의 클래스 / 그 외 (0) | 2022.06.15 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!