C++ - vector

mohadang·2022년 10월 9일
0

C++

목록 보기
14/48
post-thumbnail

Vector

  • 실무에서 많이 사용

  • 어떤 자료형도 넣을 수 있는 동적 배열

  • 그 안에 저장된 모든 요소들이 연속된 메모리 공간에 위치(Sequence Container)

  • 요소 수가 증가함에 따라 자동으로 메모리를 관리해 줌

  • 어떤 요소에도 임의로 접근(random access) 가능
    "한마디로 메모리 관리 자동으로 해주는 배열"

  • Ex)

std::vector<int> scores;
scores.reserve(2);

scores.push_back(30);
scores.push_back(50);

scores.pop_back()

std::cout << "Current capacity" << scores.capacity() << std::endl;
std::cout << "Current size" << scores.size() << std::endl;

scores.push_back();//맨 뒤에 추가
scores.pop_back();//맨 뒤에 제거

용량(capacity)과 크기(size)

  • capacity

    • vector에 할당된 요소 공간 수
    • scores.capacity();
  • size

    • vector에 실제로 들어 있는 요소 수
    • scores.size();
[10][20][ ]

크기 : 2
용량 : 3

vector의 용량 늘이기(reserve)

  • reserve()
    • vector의 용량을 늘린다.
    • "모든 STL 컨테이너에서 reserve 있으면 사용 하는 것이 좋음"
    • "불필요한 재 할당을 막기 위해 vector를 생성한 직후에 이 함수를 호출하자."
vector data(16);

for(int i = 0; i < 100; i++)
{
  data.push_back(i);
}

//재할당을 16,32,64,128 capacity로 reserve하게 된다. 쓸데없이 리소스가 낭비된다.
//얼마만큼 데이터를 넣을지 알고 있다면 처음부터 그만큼 reserve 하자.

vector data;
data.reserve(100)
  • 이렇게 생각하자 reserve 는 vector를 배열과 같은 메모리 구조와 성능을 구현하도록 보장 한다.
  • 배열 생성한다고 생각 하자 int data[100];

vector 생성 방식에 따른 capacity, size 차이

1) 생성자 방식

std::vector<int> scores(3);//실제 요소 3개 넣음
scores.push_back(30);
scores.push_back(50);
// capacity : 6
// size : 5;

2) reserve 방식

std::vector<int> scores;
scores.reserve(3);
scores.push_back(30);
scores.push_back(50);
// capacity : 3
// size : 2;

index

operator[](size_t n)

scores[i] = 3;

std::cout << names[i] << " ";
std::cout << myCats[i].GetScore() << " ";

scores.at(3)// 다음처럼 at 함수를 사용 할 수 도 있음.

resize

scores.resize(10);
크기도 용량도 10으로 만듬

인덱스 방식 순회의 단점

  • EX) 모든 요소 출력하기
int main()
{
  std::vector<int> scores;
  scores.reserve(2);

  scores.push_back(30);
  scores.push_back(50);

  for(size_t i = 0; i < scores.size(); ++i)
  {
    std::cout << scores[i] << std::endl;
  }
}
  • 위의 방식은 벡터에만 쓸 수 있음
    • 맵(map)에서는 인덱스로 operator[]를 쓸 수 없음
    • 그래서, STL 컨테이너를 순회할 때는 "반복자(iterator)"를 쓰는 게 표준 방식
    • 맵같은 녀석은 iterator로 밖에 순회 못함

EX) iterator 방식 순회

int main()
{
  std::vector<int> scores;
  scores.reserve(2);

  scores.push_back(30);
  scores.push_back(50);

  for(std::vector<int>::iterator iter = scores.begin(); 
      iter != scores.end(); ++iter)
  {
    std::cout << *iter << std::endl;
  }
}

반복자

  • 벡터 뿐만 아니라 컨테이너의 요소들을 순회 하는데 사용
  • begin()/end()
[10][10][20][30][40][5][ ]
  A                     A
  |                     |
begin()               end()
                      마지막 NULL 가리킴
  • 뒤에서부터 접근하고 싶으면
  • reverse iterator 사용하면 됨.
  • rbegin()/rend()
rend       rbegin
  |           |
  V          V
[ ][10][20][30][40][50][ ]
 A           A
 |           |
begin()     end()
  • const 요소를 받는 const iterator도 존재

메모리 재할당/복사 문제

  • EX) 특정 위치에 요소 삽입
std::vector<int> scores;

scores.reserve(4);
scores.push_back(10);// 10
scores.push_back(50);// 10, 50
scores.push_back(38);// 10, 50, 38
scores.push_back(100);// 10, 50, 38, 100

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

it++;
it = scores.insert(it, 80);// 10, 80, 50, 38, 100
  • 요소 삽입시 재할당 + 복사 문제
std::vector<int> scores;
scores.reserve(4);

// 10, 50, 38, 100 넣음

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

// -메모리-
// 0x1000 [10][50][38][100][다른 메모리, 사용 불가]
// 0x1014 [  ][  ][  ][  ][  ]

// it = scores.insert(it, 80);

// -메모리-
// 0x1000 [  ][  ][  ][  ][다른 메모리, 사용 불가]
// 0x1014 [80][10][50][38][100] <-- 메모리 재 할당해서 복사함
// - 복사 리소스 발생
// - 메모리 재할당이 일어났음
  • 특정 위치에 있는 요소 삭제하기
std::vector<int> scores;

scores.reserve(4);
scores.push_back(10);// 10
scores.push_back(50);// 10, 50
scores.push_back(38);// 10, 50, 38
scores.push_back(100);// 10, 50, 38, 100

std::vector<int>::iterator it;

it = scores.begin();

it = scores.erase(it);// 50, 38, 100
  • 요소 삭제시 복사? 재할당? 문제
    • 요소를 삭제했으면 그 빈 공백을 메우기 위해 복사 이동은 필요하다.
    • 재할당은 필요없다. 메모리 하나가 줄어든 것이니...

순환중에 요소 삭제하기

while(iter != scores.end())
{
  if(iter->GetClassName() == "Java")
  {
    iter = scores.erase(iter); // "요소 순환중에 이렇게 삭제가 가능 하다."

    // iter 대입하는 이유는 삭제 후 두에 요소들이 앞으로 한칸씩 당겨질텐데 
    // 지워진 위치를 가리키기 위해서...
    // 사실 벡터는 위치의 변환이 없을 테지만 다른 컨테이너들은 
    // 위치의 변환이 있을 수 도 있으니 대입해서 가리킨다.
  }
  else
  {
    iter++;
  }
}

요소 대입하기

vector<int> anotherScores;
anotherScores.assign(7,100);
// 100, 100, 100, 100, 100, 100, 100

vector 교환하기

vector<int> scores;
scores.reserve(2):

scores.push_back(85);
scores.push_back(73);// 85, 73

vector<int> anotherScores;
anotherScores.assign(7,100);// 100, 100, 100, 100, 100, 100, 100

// scores : 85, 73
// anotherScores : 100, 100, 100, 100, 100, 100, 100

scores.swap(anotherScores);

// scores : 100, 100, 100, 100, 100, 100, 100
// anotherScores : 85, 73

vector 축소하기

vector<int> scores;
scores.reserve(3):

scores.push_back(30);
scores.push_back(100);
scores.push_back(70);

scores.resize(2);
// 벡터의 크기를 2로 줄인다.
// 마지막 70이 날라감.
// vector의 크기를 바꾼다.
// 새 크기가 vector의 기존 크기보다 작으면, 초과분이 제거됨.
// 새 크기가 vector의 용량보다 크면 재할당이 일어남.

vector에서 모든 요소 제거하기

  • scores.clear();
  • vector를 싹 지운다.
  • 크기(size)는 0이 되고 용량은 변하지 않음.

개체 벡터

class Score
{
public:
  //
private:
  int mScore;
  string mClassName;
};
  :
int main()
{
  vector<Score> scores;
  scores.reserve(4);

  scores.push_back(Score(30, "C++"));// Object를 넣을때 복사를 해서 들어감.
  scores.push_back(Score(59, "Algorithm"));
  scores.push_back(Score(87, "Java"));
  scores.push_back(Score(41, "Android"));
}

개체를 직접 보관하는 벡터의 문제점

vector<Score> scores;
scores.reserve(1);
scores.push_back(Score(10, "C++"));

/*
메모리 상황
[10]["C++"][할당 불가 영역]
[  ][     ][             ]
*/

scores.push_back(Score(41, "Android"));

/*
메모리 상황
[  ][     ][할당 불가 영역]
[10]["C++"][41]["Android"] <-- vector 전체를 옮겨야 하는 상황

메모리 재할당 리소스 발생
메모리 복사 리소스 발생
*/


  • 복사가 더 큰 문제점이 개체 안에 여러 Object가 있다면 이 Object를 모두 복사해야 한다. 만약 Score 클래스에 엉청난 String 멤버 변수들이 많이 존재한다면...
    • vector<Score> scores2 = scores;
    • 게다가 scores에 엉청난 메모리가 있는데 이를 또 대입해서 복사를 하게 되면...
  • "문제를 해결하기 위해서는 벡터에 포인터를 저장하면 된다."
  • 포인터를 저장하는 벡터
vector<Score> scores;
scores.reserve(2);

scores.push_back(new Score(30, "C++"));
scores.push_back(new Score(87, "Java"));
scores.push_back(new Score(41, "Android"));

scores.clear();
// 이렇게 하는 순간 메모리 릭.
// scores안에 있는 메모리들 delete 해주는 것 잊었다.
  • 복사를 할 때 메모리 주소만 복사해서 리소스가 덜 든다.
  • 재할당도 메모리르 적게 먹기 때문에 재할당도 덜 발생 할 것이다.

장점

  • 순서에 상관 없이 요소에 임의적으로 접근 가능
  • 제일 마지막 위치에 요소를 빠르게 삽입 및 삭제

단점

  • 중간에 요소 삽입 및 삭제는 느림
  • 재할당 및 요소 복사에 드는 비용
profile
mohadang

0개의 댓글