c++ 언어 A 부터 Z까지 - 네번째

 



인사


오늘은 네 번째 학습일지로, C++에서 유용하게 활용되는 vector 모듈에 대해 정리해보려 합니다. 실습을 통해 배운 내용을 중심으로, 벡터의 사용 방법과 이론적인 구조, 그리고 리스트와의 차이점까지 함께 다루고자 합니다.

1. 벡터 기본 사용법


C++의 vector는 동적으로 크기를 조절할 수 있는 배열로, Python의 리스트와 유사한 기능을 제공합니다. 아래는 기본적인 벡터의 선언, 삽입, 값 수정 등에 대한 예제입니다.

#include <stdio.h>
#include <vector>

void PrintVector(int n, std::vector<int>& v) {
 
  printf("%d: ", n);  
  for (auto a : v) {
    printf("%d ", a);
  }
  printf("\n");
}

// plus data
void InputVector(std::vector<int>& v) {
 
  int n;
  printf("1\n");
  scanf("%d", &n);
  v.push_back(n);
}

// change data
void ChangeVector(std::vector<int>& v) {
 
  printf("2\n");
  for (auto& a : v) {
    if (a == 0) a = 1;
  }
 
}

void PickVector(std::vector<int>& v) {
 
  int n, a;
  printf("3\n");
  scanf("%d %d", &n, &a);
  v[n] = a;
 
}

int main(void) {
 
  std::vector<int> v1{2, 3, 4};
  std::vector<int> v2(2);
  std::vector<int> v3(3, 4);
 
  // change value
  InputVector(v1);
  PrintVector(1, v1);
 
  // check value
  ChangeVector(v2);
  PrintVector(2, v2);
 
  // 원하는 인덱스의 값을 변환
  PickVector(v3);
  PrintVector(3, v3);
 
  return 0;
}

이 예제를 통해 vector는 크기를 정하지 않고도 값을 추가할 수 있으며, 기존 값 변경이나 인덱스 접근도 자유롭게 할 수 있다는 점이 인상적이었습니다.


2. 벡터의 구조 이해


벡터는 내부적으로 세 가지 멤버 변수를 가집니다:

  • m_data: 실제 데이터가 저장되는 공간

  • m_size: 현재 저장된 데이터의 개수

  • m_capacity: 현재 할당된 전체 공간의 크기

아래 예제를 통해 sizecapacity의 차이를 직접 확인할 수 있습니다.


#include <stdio.h>
#include <vector>


int main(void) {
 
  std::vector<int> v{1, 2, 3};
 
  v.pop_back();
 
  printf("current data: ");
  for (auto& a : v) {
    printf("%d ", a);
  }
  printf("\n");
 
  printf("vector size: %d\n", v.size());
  printf("vector capacity: %d\n", v.capacity());
 
  printf("current last value: %d\n", v[v.capacity() - 1]);
  printf("what value?: %d\n", v[v.capacity()]);
 
  return 0;
}


위 코드에서 벡터에서 값을 하나 제거했음에도 capacity는 그대로 유지된다는 것을 알 수 있었습니다. 이처럼 capacity는 벡터가 더 많은 데이터를 수용하기 위해 미리 확보한 메모리 공간이므로, size와는 구분해서 이해할 필요가 있습니다.

또한 v.clear()는 벡터를 비우지만 capacity는 그대로 유지되며, v.shrink_to_fit()는 capacity를 size에 맞춰 조절합니다. 벡터는 유연한 자료구조지만, 내부 메모리 관리까지 고려해야 디버깅에 어려움을 줄일 수 있습니다.

3. 배열과 벡터의 메모리 차이 비교


아래 예제에서는 new 연산자를 사용한 배열과 벡터를 함께 사용해 저장 크기를 비교해 보았습니다.

#include <stdio.h>
#include <vector>

void InputList(int n, int* student_lists, std::vector<int>& student_vector) {
 
  int i;
 
  for (i = 0; i < n; i++) {
    scanf("%d", &student_lists[i]);
    student_vector.push_back(student_lists[i]);
  }
 
}

int main(void) {
 
  // student
  int n;
  scanf("%d", &n);
 
  // list를 이용해서 학생의 정보를 저장
  int* student_lists = new int[n];
  // vector를 이용해서 학생정보 저장
  std::vector<int> student_vector;
 
  InputList(n, student_lists, student_vector);
 
  // 크기
  printf("list size: %d\n", sizeof(student_lists));
  printf("vector size: %d\n", sizeof(student_vector));
 
  delete[] student_lists;
 
  return 0;
}


벡터는 내부적으로 3개의 포인터(m_data, m_size, m_capacity)를 갖고 있기 때문에, 일반 배열보다 상대적으로 더 많은 메모리를 차지합니다. 실제로 배열 포인터 하나에 비해 약 3배 정도의 크기 차이가 발생했습니다.

4. 시간 복잡도 및 활용 전략


C++의 vector는 다음과 같은 특징을 가집니다:

  • push_back 연산은 평균적으로 O(1)의 시간 복잡도를 가짐

  • 임의 접근은 배열처럼 O(1)

  • 삽입/삭제는 위치에 따라 O(n)

따라서 아래와 같은 기준으로 사용하는 것이 효과적이라고 느꼈습니다.

  • 데이터 크기가 명확한 경우: new int[]로 배열 생성

  • 데이터 크기를 예측할 수 없거나 가변적인 경우: vector 사용

5. 기타 문법 - range-based for 문


마지막으로 for (auto a : v) 문법에 대해 소개합니다. 이 문법은 C++11부터 도입된 범위 기반 반복문으로, 자료형을 따로 지정하지 않아도 되고 가독성이 좋습니다.

만약 값을 직접 수정하고 싶다면 for (auto& a : v)처럼 참조형으로 선언하면 됩니다.

마무리


벡터는 C++에서 매우 유용하고 직관적인 자료구조입니다. 하지만 내부 동작을 정확히 이해하지 않으면 예기치 않은 버그가 발생할 수 있습니다. 이번 학습을 통해 벡터의 구조와 사용법, 메모리 관리에 대한 이해를 넓힐 수 있었습니다.

앞으로도 기본기를 충실히 쌓아가며, 실전에서 활용할 수 있도록 꾸준히 실습해 나가겠습니다. 오늘의 학습일지는 여기서 마무리합니다.



 

 







개발자 김동완

개발, 비전공, 백엔드, 풀스택, 프로그래밍

댓글 쓰기

다음 이전