728x90
반응형

STL이란?

C++의 **STL(Standard Template Library)**은 데이터를 저장하고 조작하기 위한 표준 템플릿 라이브러리입니다. STL은 C++ 프로그래밍에서 효율적이고 간결한 코드를 작성하는 데 매우 유용합니다.

 

 

STL 구성 요소

STL은 크게 3가지 주요 구성 요소로 나뉩니다:

  1. 컨테이너 (Containers)
    • 데이터를 저장하는 객체.
    • 예: vector, list, deque, set, map 등.
  2. 알고리즘 (Algorithms)
    • 데이터를 정렬, 검색, 변경하는 데 사용되는 함수들.
    • 예: sort, find, binary_search, reverse, accumulate 등.
  3. 이터레이터 (Iterators)
    • 컨테이너의 요소를 순회하는 데 사용되는 포인터와 비슷한 객체.
    • 예: begin(), end(), rbegin(), rend() 등.

컨테이너 먼저 알아보도록 하겠습니다.

 

컨테이너는 쉽게 말하면 데이터를 담는 자료구조를 말합니다.

 

모든 컨테이너는 템플릿으로 구현되어 있습니다. 즉 타입에 상관없이 사용 가능 합니다.

 

모든 컨테이너는 메모리 관리를 내부적으로 합니다. 따라서 사용시 메모리 관리를 고려하지 않아도 됩니다.

 

모든 컨테이너는 반복자를 가지고 있습니다. 따라서 컨테이너 내부 세부적인 구현사항을 알지 못해도 모든 컨테이너를 동일한 문법으로 접근할 수 있습니다.

 

컨테이너는 데이터를 저장하는 구조입니다. 주로 3가지 유형으로 나뉩니다:

1.1 시퀀스 컨테이너 (Sequence Containers)

데이터를 순서대로 저장하는 컨테이너.

vector 동적 배열, 크기가 자동으로 조정됨. 가장 많이 사용됨.
deque 양쪽 끝에서 삽입과 삭제가 빠름.
list 이중 연결 리스트. 임의 접근은 느리지만 삽입과 삭제가 빠름.

 

이중 벡터에 대해 자세히 알아보겠습니다.

벡터는 배열과 매우 유사한 컨테이너 입니다.

대표적인 특징을 알아보면 다음과 같습니다.

 

타입에 종속되지 않는 템플릿 클래스로 구현 됨

 

삽입되는 원소 개수에 따라 내부 배열의 크기가 자동 관리 됨

 

임의접근이 가능함(인덱스를 톻해 특정 위치에 접근)

 

삽입/삭제는 맨 뒤에 하는게 좋음(배열의 특성)

 

간단하게 기본 생성 빛 특정값으로 초기화를 하는 코드를 보겠습니다.

#include <vector>
using namespace std;

// 1. 기본 생성 및 초기화 없이 정의
vector<int> vec1;

// 2. 특정 크기와 초기값으로 벡터 정의
vector<int> vec2(5, 10); // 크기 5, 모든 원소가 10으로 초기화

//메인 함수 생략

 

다음은 리스트로 초기화하여 벡터를 정의하는 코드입니다.

이 많을때는 사용하기 어렵고 초기화하는 값의 개수가 적을 때 보통 사용합니다.

#include <vector>
using namespace std;

// 3. 리스트 초기화로 벡터 정의
vector<int> vec3 = {1, 2, 3, 4, 5};

//메인 함수 생략

 

다른 벡터를 복사하거나 대입하는 방법도 있습니다.

기존에 생성된 벡터의 복사본을 만들 때 많이 사용 합니다.(당연히 깊은 복사입니다.)

#include <vector>
using namespace std;

// 다른 벡터를 기반으로 복사 초기화
vector<int> vec3 = {1, 2, 3, 4, 5};
vector<int> vec4(vec3); // vec3의 복사본 생성
//vector<int> vec4 = vec3 하면 대입이 됨
//메인 함수 생략

 

 

이제 벡터에서 제공하는 메서드 중에 많이 사용하는 메서드에 대해 알아보겠습니다

 

 push_back 벡터의 맨 끝에 원소를 추가하는 메서드 입니다. 원소의 개수가 늘어남에 따라 크기는 자동으로 증가하기 때문에 신경쓸 필요 없습니다.

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

int main() {
    vector<int> vec;
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);

    cout << "Vector after push_back: ";
    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}
// 출력: Vector after push_back: 10 20 30

 

 

pop_back 벡터의 맨 끝에 원소를 제거하는 메서드 입니다. 벡터의 맨 끝 원소가 제거되므로 크기가 줄어 듭니다.

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

int main() {
    vector<int> vec = {10, 20, 30};
    vec.pop_back();  // 마지막 요소(30) 제거

    cout << "Vector after pop_back: ";
    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}
// 출력: Vector after pop_back: 10 20

 

 

size 현재 벡터의 크기를 확인할 때 사용합니다. 보통 벡터의 전체 원소 대해 무언가 해야할 때 유용하게 사용됩니다.

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

int main() {
    vector<int> vec = {10, 20, 30};
    cout << "Size of vector: " << vec.size() << endl;

    vec.push_back(40);
    cout << "Size after push_back: " << vec.size() << endl;

    vec.pop_back();
    cout << "Size after pop_back: " << vec.size() << endl;

    return 0;
}
// 출력:
// Size of vector: 3
// Size after push_back: 4
// Size after pop_back: 3

 

erase 특정위치의 원소를 제거하는 함수입니다. 벡터의 특성상 시간이 많이 걸리는 함수 이므로 되도록이면 사용하지 않는게 좋습니다.

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

int main() {
    vector<int> vec = {10, 20, 30, 40, 50};

    // 두 번째 요소 제거 (index 1)
    vec.erase(vec.begin() + 1);

    cout << "Vector after erasing second element: ";
    for (int num : vec) {
        cout << num << " ";
    }
    cout << endl;

    // 2~4 번째 요소 제거 (index 1~3)
    vec.erase(vec.begin() + 1, vec.begin() + 4);

    cout << "Vector after erasing range: ";
    for (int num : vec) {
        cout << num << " ";
    }
    return 0;
}
// 출력:
// Vector after erasing second element: 10 30 40 50
// Vector after erasing range: 10 50

여기서 나오는

for (int num : vec) 이 부분은 기억해두시는게 좋습니다.

for(int num = 0;num < vec.size();num++) 과 동일합니다.

 

다음으로는

Map 

핸드폰 연락처를 생각해봅시다.

 이름을 검색해서 그에 해당되는 전화번호를 알아냅니다. 이런 것들을 편하게 구현할 수 있는 컨테이너가 맵 입니다.

배열이 인덱스를 활용해서 특정 위치의 값을 찾아줬다면, 맵은 인덱스 대신 키(Key)를 사용하고 값과 쌍을 이루고 있는 것을 볼수 있습니다.

map의 특징으론 다음과 같습니다.

 

키-값 쌍이 하나의 타입이 됩니다.

 

키 값을 기준으로 데이터가 자동 정렬됩니다.

 

중복된 키 값을 허용되지 않습니다.

 

맵의 다양한 선언방법을 예시로 알아보겠습니다.

 

#include <iostream>
#include <map>
#include <vector>
using namespace std;

int main() {
    // 1. 기본적인 map 선언 (키: string, 값: int)
    map<string, int> studentScores;

    // 2. map 선언과 동시에 초기화 (키: int, 값: string)
    map<int, string> idToName = {
        {1, "Alice"},
        {2, "Bob"},
        {3, "Charlie"}
    };

    // 3. map 선언 (키: char, 값: double)
    map<char, double> gradeToGPA;

    // 4. map 선언 (키: int, 값: vector<int>)
    map<int, vector<int>> studentMarks;

    // 5. map 선언 (키: pair<int, int>, 값: string)
    map<pair<int, int>, string> coordinateToName;

    return 0;
}

 

다음은 기존맵을 다른맵에 복사하는 예시입니다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    // 원본 map
    map<int, string> originalMap;
    originalMap[1] = "Apple";
    originalMap[2] = "Banana";
    originalMap[3] = "Cherry";

    // 1. 복사 생성자 사용
    map<int, string> copiedMap1(originalMap);

    // 2. 대입 연산자 사용
    map<int, string> copiedMap2;
    copiedMap2 = originalMap;

    // 3. insert 사용
    map<int, string> copiedMap3;
    copiedMap3.insert(originalMap.begin(), originalMap.end());

    return 0;
}

 

맵에서 사용하는 매서드 중 가장 많이 사용되는 것들을 알아보겠습니다.

map 은 key순으로 자동 정렬 됩니다. 이것은 사용자가 특별히 제어하지 않아도 자동으로 계속해서 정렬된다고 보시면 됩니다.

키 순으로 데이터가 출력되는 것을 보여주는 예시입니다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    // map 선언 및 값 추가
    map<int, string> myMap;
    myMap[5] = "E";
    myMap[2] = "B";
    myMap[8] = "H";
    myMap[1] = "A";
    myMap[3] = "C";

    // map 내용 출력
    cout << "Map의 내용 (키 값 기준으로 자동 정렬):" << endl;
    for (map<int, string>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
        cout << it->first << ": " << it->second << endl;
    }

    return 0;
}

 

insert

make_pair를 통해 키-값 쌍을 만들고 이를 insert를 만들어서 추가할 수 있습니다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap;

    // insert를 사용하여 키-값 삽입
    myMap.insert(make_pair(1, "Apple"));
    myMap.insert(make_pair(2, "Banana"));
    myMap.insert(make_pair(3, "Cherry"));

    // map 출력
    for (map<int, string>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
        cout << it->first << ": " << it->second << endl;
    }

    return 0;
}

 

find 함수를 통해 특정 키 값 존재여부를 확인할 수 있습니다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "Apple";
    myMap[2] = "Banana";

    // 요소 검색
    map<int, string>::iterator it = myMap.find(2);
    if (it != myMap.end()) {
        cout << "Found: " << it->first << ": " << it->second << endl;
    } else {
        cout << "Key not found!" << endl;
    }

    return 0;
}

 

size는 벡터와 같이 크기의 갯수입니다. size에서는 키-값 쌍의 갯수를 확인해줍니다. 예시는 넘어가도록 하겠습니다.

 

clear

맵에 있는 모든 원소를 삭제하려고 할 떄 사용하는 메서드 입니다. clear는 맵 뿐 아니라 모든 컨테이너에 존재 합니다.

#include <iostream>
#include <map>

using namespace std;

int main() {
    map<int, string> myMap;
    myMap[1] = "Apple";
    myMap[2] = "Banana";

    // 모든 요소 삭제
    myMap.clear();

    // map의 크기 출력
    cout << "Map size after clear: " << myMap.size() << endl;

    return 0;
}

 

 

다음으로

알고리즘

STL 알고리즘은 컨테이너 데이터를 처리하는 데 사용됩니다. <algorithm> 헤더에서 제공됩니다.

 

sort 데이터를 정렬.
find 특정 값을 검색.
binary_search 이진 검색.
reverse 데이터를 뒤집음.
accumulate 값들의 합을 계산.

 

이 중 sort 와 find 를 자세히 살펴보겠습니다.,

컨테이너 내부의 데이터를 정렬해주는 함수 입니다. 기본 타입의 경우 정렬기준을 정해주지 않을 경우 오름차순으로 정렬되며, 사용자가 정렬기준을 정의해서 사용할 수 있습니다. 정렬의 기준을 제시해야 하는 경우 함수를 추가 작성하는데요.

어렵지 않습니다. 아래만 기억 하면 됩니다.

 

정렬기준 함수가 true를 반환 하면 순서를 유지한다

 

두개의 인자를 받을 떄 앞의 인자가 앞의 원소 뒤의 인자가 뒤의 원소이다.

 

간단하게 오름차순으로 정렬하는 예시만 보겠습니다.

#include <iostream>
#include <algorithm> // sort 함수 포함
using namespace std;

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

    // 오름차순 정렬
    sort(arr, arr + size);

    // 결과 출력
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    return 0;
}

 

다음으로는 find 입니다.

컨터이너 내부에서 특정 원소를 찾아주는 함수 입니다. 찾은 경우 해당 원소가 위치하는 특정 원소의 위치를 알려주고 찾지 못한 경우 컨테이너의 맨 마지막 다음 위치를 반환합니다.(찾지 못했다는 의미)

벡터에서 특정값을 찾는 예시로 알아보겠습니다,.

#include <iostream>
#include <vector>
#include <algorithm> // find 함수 포함
using namespace std;

int main() {
    vector<int> vec = {10, 20, 30, 40, 50};

    // 특정 값 30을 찾음
    auto it = find(vec.begin(), vec.end(), 30);

    if (it != vec.end()) {
        cout << "값 30이 벡터에서 발견됨, 위치: " << (it - vec.begin()) << endl;
    } else {
        cout << "값 30이 벡터에 없음" << endl;
    }
    return 0;
}

 

 

다음으로 반복자에 대해 알아보겠습니다.

지금까지 컨테이너와 알고리즘에 대해서 알아봤는데요! 컨테이너의 내부구조는 매우 다르지만, 우리는 대부분 알고리즘을 동일한 코드를 활용해서 사용할 수 있었습니다. 즉 컨테이너 구현에 의존하지 않고(구현을 잘 모른 상태에서도) 알고리즘을 활용하는데 전혀 지장이 없었습니다.

순방향 반복자

순방향 반복자는 앞에 원소부터 뒤에 원소대로 순회하는 반복자입니다. 맨 처음 위치는 begin(), 맨 마지막 다음위치는 end()가 됩니다. 맨 마지막 다음위치를 end()로 정한 이유는, 알고리즘 구현의 효율성을 위함 입니다. 예를 들어 맨 마지막 원소까지 탐색 했는데 찾지 못한 경우를 나타내야 하는 경우 end()를 반환 합니다.

벡터에서 순방향 반복자를 사용하는 예시입니다.

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

int main() {
    vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // 순방향 반복자를 사용해 짝수만 출력
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        if (*it % 2 == 0) {
            cout << *it << " ";
        }
    }
    return 0;
}
// 출력: 2 4 6 8 10

 

다음으로는 역방향 반복자입니다.

 

역방향 반복자는 앞에원소부터 뒤에 원소대로 순회하는 반복자입니다. 맨 마지막 위치는 rbegin(), 맨 처음 이전위치는 rend()가 됩니다.

예를 들어 맨 처음 원소까지 탐색했는데 찾지 못한 경우를 나타내야 하는 경우 rend()를 반환 합니다.

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

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

    // 역방향 반복자로 짝수만 출력
    for (auto it = numbers.rbegin(); it != numbers.rend(); ++it) {
        if (*it % 2 == 0) {
            cout << *it << " ";
        }
    }

    return 0;
}
// 출력: 30 20 10
728x90
반응형