C++ < STL (기초) >

2026. 3. 10. 23:22TIL

C++ STL이란?

STL(Standard Template Library)은 C++ 표준 라이브러리의 일부로, 컨테이너, 알고리즘, 반복자 등의 템플릿 기반 구성 요소를 포함한다.

 

STL을 잘 활용하면 다양한 자료구도와 알고리즘을 직접 구현하지 않고도 사용할 수 있다.


컨테이너

컨테이너는 데이터를 저장하고 관리하기 위한 자료구조를 의미한다. 컨테이너는 배열처럼 데이터를 담는 역할을 하지만, 데이터의 저장 방식과 제공하는 기능에 따라 여러 종류로 나뉜다.

 

컨테이너의 특징

  • 템플릿 기반 자료구조
    • STL컨테이너는 대부분 템플릿으로 구현되어 있다.
    • 따라서 하나의 컨테이너로 정수, 실수, 문자열 등 다양한 타입의 데이터를 저장할 수 있다.
  • 자동 메모리 관리
    • 컨테이너는 내부적으로 메모리를 자동 관리한다.
    • 개발자는 데이터 관리 로직에 집중할 수 있다.
  • 반복자(Iterator) 제공

 

컨테이너의 구조는 다음과 같다.

컨테이너는 데이터를 저장하는 공간이고, 반복자는 데이터에 접근하기 위한 도구이다.

 

이 컨테이너를 사용하는 이유는 다음과 같은 장점 때문이다.

  • 다양한 자료구조를 쉽게 사용할 수 있다.
  • 메모리 관리가 자동으로 이루어진다.
  • 반복자를 통해 데이터 접근 방식이 통일된다.
  • 코드의 안정성과 생산성이 높아진다.

다음은 컨테이너 중에서 벡터와 맵을 알아보려고 한다.


벡터

벡터(vector)는 C++ STL에서 제공하는 대표적인 순차 컨테이너이다.

배열과 매우 비슷한 구조를 가지고 있지만, 크기가 자동으로 조절된다는 점에서 일반 배열과 차이가 있다.

일반 배열은 처음 선언할 때 크기를 정하면 이후에 변경하기 어렵지만, 벡터는 원소가 추가되거나 삭제되면 내부적으로 메모리를 재할당하면서 크기를 자동으로 관리한다.

 

벡터의 특징

  • 템플릿 기반 컨테이너
    • 벡터는 템플릿 클래스로 구현되어 있기 때문에 특정 타입에 제한되지 않는다. 다양한 타입의 데이터를 하나의 컨테이너 구조로 저장할 수 있다.
//예시
vector<int> numbers;
vector<double> values;
vector<string> words;
  • 크기가 자동으로 조절되는 동적 배열
    • 벡터는 내부적으로 동적 배열구조로 동작한다.
  • 임의 접근이 가능
    • 벡터는 배열과 마찬가지로 인덱스를 이용해 특정 위치의 데이터에 바로 접근할 수 있다.
  • 끝에서 삽입 / 삭제가 효율적
    • 벡터는 맨 뒤에서 원소를 추가하거나 삭제하는 작업이 효율적이다.
    • 중간 위치에서 삽입이나 삭제가 발생하면 뒤에 있는 원소들을 이동해야 한다.

 

벡터의 선언

벡터는 다양한 방법으로 선언할 수 있다.

 

기본선언

vector<int> vec;

빈 벡터가 생성되며 초기 크기는 0이다.

 

특정 크기와 초기값으로 선언

vector<int> vec(5, 10);

크기는 5에 모든 값을 10으로 채운 

[10, 10, 10, 10, 10] 의 벡터가 생성된다.

 

초기화 리스트 사용

vector<int> vec = {1,2,3,4,5};

{} 중괄호를 이용해서 직접 초기 데이터를 넣을 수 있다.

 

벡터 복사

vector<int> vec1 = {1,2,3,4,5};
vector<int> vec2(vec1);

기존 벡터의 복사본을 만들 때 사용된다. 메모리 값은 서로 독립되어 있다.

 

2차원 벡터

vector<vector<int>> vec2D(3, vector<int>(4,7));

7 7 7 7

7 7 7 7

7 7 7 7

이렇게 행이 3, 열이 4, 모든 값을 7로 채운 2차원 벡터를 선언할 수 있다.

 

push_back()

vec.push_back(10);
vec.push_back(20);
vec.push_back(30);

이런식으로 벡터의 맨 뒤에 원소를 추가할 수 있다.

 

[ ]

[10]

[10 20]

[10 20 30]

 

pop_back()

[10 20 30]
↓
pop_back()
↓
[10 20]

벡터의 맨 마지막 원소를 제거한다.

 

size()

현재 벡터에 저장된 원소 개수를 반환한다.

vec.size()

[10 20 30]

size = 3

 

erase()

특정 위치의 원소를 삭제할 때 사용한다.

vec.erase(vec.begin() + 1);

---------------
[10 20 30 40 50]

20 삭제

[10 30 40 50]

또는 범위를 삭제할 수도 있다.

vec.erase(vec.begin()+1 , vec.begin()+3)

--------------
[10 20 30 40 50]
↓
[10 50]

 

하지만 벡터의 특성상 맨 뒤에서 삭제/삽입이 효율적이다. erase는 중간 데이터를 이동하는 데에 비용이 큰 연산이다.

 

벡터 사용의 장점

  • 배열처럼 빠른 인덱스 접근 가능
  • 크기가 자동으로 확장되는 동적 배열
  • 메모리를 자동으로 관리
  • STL과 함께 편리하게 데이터 처리 가능

맵 (Map)

은 키(key)와 값(value)을 한 쌍으로 저장하는 STL 컨테이너이다.

배열이 인덱스를 이용해 데이터를 찾는 구조라면, 맵은 특정 키를 이용해 원하는 값을 빠르게 찾을 수 있는 자료구조이다.

 

예를 들면 전화번호부처럼 생각해볼 수 있다.

이름(Key)      전화번호(Value)

Alice      →   010-1234-1111
Bob        →   010-2222-3333
Charlie    →   010-4444-5555

이런식으로 키를 이용해 값을 찾는 구조가 바로 맵이다.

 

특징

  • 데이터는 항상 Key - value 쌍(pair)으로 저장된다.
  • Key를 기준으로 자동으로 정렬된다.
20 → Banana
5  → Apple
15 → Cherry
10 → Grapes

↓↓↓↓↓
5  → Apple
10 → Grapes
15 → Cherry
20 → Banana

따라서 사용자가 정렬을 할 필요가 없다.

  • 같은 키는 하나만 존재할 수 있다. 중복된 키를 허용하지 않는다.
map<int,string>

1 → Apple
1 → Banana   (불가능)

같은 키를 넣으면 기존 값이 덮어쓰여지거나 삽입이 실패한다.

 

맵의 선언

맵 선언의 형식은 다음과 같다.

map<KeyType , ValueType>

예시
map<int, string>
map<string, int>
map<string, string>

이렇게 Key 타입과 Value 타입 두 가지를 지정해야 한다.

 

맵의 주요 동작

insert()

맵에 새로운 Key - Value 데이터를 추가하는 함수이다.

myMap.insert(make_pair(1, "Apple"));

make_pair를 이용해서 pair 객체를 생성한 후 추가할 수 있다.

 

myMap.insert({4, "Dog"});

{} 중괄호를 사용해서 추가할 수 있다.

 

myMap[7] = "Giraffe";
myMap[8] = "Horse";
myMap[9] = "Iguana";

[]대괄호를 사용해서 추가하는 방식으로 가장 많이 사용되는 방식이다.

 

find()

맵에서 특정 Key가 존재하는지 찾는 함수이다.

auto it = myMap.find(key);

동작 방식

Key 존재   → 해당 위치 iterator 반환
Key 없음   → map.end() 반환

 

size()

맵에 저장된 Key-Value 쌍의 개수를 반환한다.

map<int, string> myMap;

    myMap[1] = "Apple";
    myMap[2] = "Banana";
    myMap[3] = "Cherry";

    cout << "Map size: " << myMap.size() << endl;

 

출력값 : 3

 

erase()

특정 Key를 가진 데이터를 삭제한다.

myMap.erase(2);

삭제 전
1 → Apple
2 → Banana
3 → Cherry

삭제 후
1 → Apple
3 → Cherry

존재하지 않는 키 삭제
myMap.erase(40)

결과
Key 40 not found. No deletion performed.

존재하지 않는 키는 삭제되지 않는다.

 

clear()

맵의 모든 데이터를 삭제한다.

 

 

핵심 정리

  • Key와 Value가 쌍(pair)형태로 저장된다.
  • key 기준으로 자동 정렬된다.
  • 같은 Key는 중복될 수 없다.
  • Key를 이용해 데이터를 빠르게 검색할 수 있다.
  • 삽입 / 삭제 / 탐색을 위한 다양한 변수( insert, find, erase, clear ) 를 제공한다.

알고리즘 (Algorithm)

STL에서는 다양한 범용 알고리즘을 제공한다.

이 알고리즘들은 특정 컨테이너에 종속되지 않고 배열, vertor, string 등 여러 컨테이너에서 동일한 방식으로 사용할 수 있다.

 

예를 들어 데이터를 정렬하는 기능, 특정 값을 찾는 기능 등 같은 것들을 직접 구현하지 않고 STL 함수로 바로 사용할 수 있다.

 

sort

sort는 컨테이너 내부 데이터를 정렬하는 함수이다.

기본 타입(int, double 등)은 기본적으로 오름차순 정렬이다.

사용자가 직접 정렬 기준을 정의할 수도 있다.

 

기본 정렬

#include <iostream>
#include <algorithm>
using namespace std;

int main() {

    int arr[] = {5, 2, 9, 1, 5, 6};
    int size = sizeof(arr) / sizeof(arr[0]);

    sort(arr, arr + size);

}

여기서 sort(arr, arr + size);에서

 

arr < 시작위치

arr + size < 끝 위치

 

이다. 이는 정렬할 범위를 지정하는 것이다.

 

내림차순

큰 숫자가 앞에 오도록 함수를 만들면 된다.

bool compare(int a, int b)
{
    return a > b;
}

sort(arr, arr + size, compare);

이런식으로

sort(시작, 끝, 정렬함수)를 사용해서 정렬할 수 있다.

 

vector 정렬

vector도 동일한 방식으로 정렬할 수 있다.

vector<int> vec = {5,2,9,1,5,6};

sort(vec.begin(), vec.end());

// vec.begin() < 시작
// vec.end() < 끝

내림차순도 위와 마찬가지로 함수를 지정해서 

 

sort(vec.begin(), vec.end(), compare);

 

로 선언하면 내림차순 지정이 가능하다.

 

Class 객체 정렬

class Person
{
    string name;
    int age;
};

클래스가 이렇게 지정되어 있고,

 

정렬 기준을

1. 나이 오름차순

2. 나이가 같으면 이름 오름차순

 

이렇게 지정해보려고 한다.

bool compareByAgeAndName(const Person& a, const Person& b)
{
    if(a.getAge() == b.getAge())
        return a.getName() < b.getName();

    return a.getAge() < b.getAge();
}

함수를 선언하고,

vector<Person> people = {
        Person("Alice", 30),
        Person("Bob", 25),
        Person("Charlie", 35),
        Person("Alice", 25)
    };

    sort(people.begin(), people.end(), compareByAgeAndName);

클래스를 인스턴스화한 후 정렬 함수를 사용하면 정상적으로 

 

Alice (25)
Bob (25)
Alice (30)
Charlie (35)

정렬된 모습을 확인할 수 있었다.

 

find

find는 컨테이너 내부에서 특정 값을 찾는 알고리즘 함수이다.

 

find(시작, 끝, 찾을값)

사용 형식은 다음과 같다.

 

벡터에서 값 찾기

auto it = find(vec.begin(), vec.end(), 30);

아까와 같이 auto it로 반복자를 이용해서 30을 찾아보려고 한다.

 

10 20 30 40 50에서 저 코드를 실행시키면

위치 : 2 가 출력된다.

 

배열에서 값 찾기

auto it = find(arr, arr + size, 4);

arr + size에서 size는

arr / arr[0]으로 구한 배열의 크기이다.

 

arr + size를 해주는 이유는 이 find에서 찾는 끝 부분은 포함이 되지 않아 끝 부분의 전까지 순환한다.

그래서 vec.end() 또한 벡터의 마지막값보다 하나 더 뒤의 위치를 가리킨다.

 

숫자 배열이 1 2 3 4 5 라고 하면

위 코드의 실행 값은 

위치 : 3 

으로 인덱스3이 출력될 것이다.

 

문자열에서 문자 찾기

string str = "hello world";

auto it = find(str.begin(), str.end(), 'o');

다음과 같은 문자열에서도 find를 이용해서 찾을 수 있다.

 

위 코드의 실행 결과로는

o의 위치가 인덱스4이므로 

위치 : 4

가 출력될 것이다.

 

문자열의 찾기에서는 가장 앞에 있는 글씨만 찾는다.

 

find 동작 방식

  • find(first, last, value) 형식으로 사용한다.
  • 찾으면 해당 위치의 반복자(iterator)를 반환한다.
  • 찾지 못하면 last iterator를 반환한다.
    • if ( it != end() ) 조건을 이용해서 찾았는지 확인할 수 있다.

 

알고리즘 핵심 정리

  • STL은 컨테이너와 독립적으로 동작하는 알고리즘을 제공한다.
  • 대표적인 알고리즘
    • sort() : 데이터 정렬
    • find() : 특정 값 탐색
  • iterator(반복자)를 이용해 다양한 컨테이너에서 동일한 방식으로 사용할 수 있다.
  • 사용자 정의 함수를 이용해 정렬 기준을 직접 설정할 수 있다.

반복자 ( Iterator ) 

컨테이너(Vercor, map 등)와 알고리즘(sort, find 등)을 살펴보았는데, 이 두 가지가 함께 동작할 수 있는 이유는 바로 반복자(Iterator) 때문이다.

 

반복자는 컨테이너 내부 원소를 가리키는 객체로, 컨테이너의 내부 구조를 몰라도 일관된 방식으로 데이터에 접근할 수 있도록 해준다.

 

즉, 반복자는 컨테이너와 알고리즘 사이를 연결해주는 역할을 한다.

컨테이너  ←→  반복자  ←→  알고리즘
vector        iterator      sort
map                         find
list

 

반복자의 개념

반복자는 컨테이너 안의 데이터를 가리키는 포인터와 비슷한 개념이다.

vector<int>

[10] [20] [30] [40]
  ↑
iterator

벡터가 다음과 같다면 반복자는 현재 위치의 데이터를 가리킨다.

따라서

*it   → 현재 값 접근
++it  → 다음 요소 이동

이렇게 연산이 가능하다.

 

순방향 반복자

순방향 반복자(forward iterator)는 컨테이너의 첫 번재 원소부터 마지막 원소가지 순차적으로 이동하는 반복자이다.

 

반복자를 사용할 때 가장 기본이 되는 함수는 다음 두 가지이다.

begin()  → 첫 번째 원소를 가리킴
end()    → 마지막 원소 다음 위치

아까 find에서 설명했듯 end()는 마지막 원소의 다음 위치이다.

 

vector

[1] [2] [3] [4] [5]
 ↑                  ↑
begin()            end()

그렇기 때문에 반복문을 다음과 같이 만들 수 있다.

 

for(iterator = begin(); iterator != end(); ++iterator)

 

1부터 10까지 반복한다고 했을 때, 10은 아직 end()에 도달하지 못했으므로 10까지는 순환 후 반복문이 종료된다.

 

맵에서 순방향 반복자

맵에서는 반복자가 Key - Value 쌍을 가리킨다.

 

for(auto it = scores.begin(); it != scores.end(); ++it)
{
    cout << it->first;
    cout << it->second;
}

여기서 

it -> first 는 Key

it -> second 는 Value이다.

 

find와 반복자

auto it = find(words.begin(), words.end(), target);

target에 설정된 단어를 찾는다.

 

it != end()는 찾았을 경우,

it == end()는 찾지 못 했을 경우에 it에 반환된다.

 

역방향 반복자

역방향 반복자(reverse iterator)는 컨테이너의 마지막 원소부터 첫 번째 원소까지 거꾸로 순회하는 반복자이다.

 

사용되는 함수로는 

순방향 반복자의 앞에 " r "을 붙여 사용한다.

 

rbegin() → 마지막 원소
rend()   → 첫 원소 이전 위치

find나 sort 등에

 

sort(rbegin(), rend());

 

를 하면 역방향으로 정렬된다.

 

벡터 역방향 반복자

vector<int> numbers = {10,15,20,25,30};

for(auto it = numbers.rbegin(); it != numbers.rend(); ++it)
{
    if(*it % 2 == 0)
        cout << *it;
}

이렇게 사용하면

 

30 25 20 15 10 순서대로 순환하게 되고,

 

출력은

30 20 10 이 출력된다.

 

맵 역방향 반복자, 역방향 find

전부 begin(), end()를 -> rbegin(), rend()를 이용해서 사용하면 역방향으로 참조한다.

 

핵심 정리

  • 반복자는 컨테이너 내부 데이터를 가리키는 객체이다.
  • 알고리즘은 반복자를 이용해 컨테이너와 독립적으로 동작한다.
  • begin() : 첫번째 원소
  • end() : 마지막 원소 다음 위치
  • rbegin() : 마지막 원소(역방향)
  • rend() : 첫 원소 이전 위치(역방향)
  • *it : 현재 값 접근
  • ++it : 다음 원소 이동