2026-04-02(목) erase, Map, operator, std::unordered_set, Template, 동적할당, new T[], delete[], 얕은복사, 깊은복사

조범근·2026년 4월 2일

TIL

목록 보기
33/84

C++ Week 6

오늘 C++ 시험이 있었는데 두 문제는 못풀고 하나는 다 풀어놓고 & 하나 안 써서 틀린 것 같다. 거기가 내 수준이라고 생각한다.

Study

C++ 시험 문제 복기


Today I Learned


반복문에서 erase를 지우면 it은 어디를 가리킬까?

Overview


for (int i = 0; i < v.size(); ) 
{
    if (지워야 할 조건) 
    {
        v.erase(v.begin() + i); 
    }
    else 
    {
        ++i;
    }
}

벡터에서 특정칸 을 지우는 방법은 v.erase(v.begin() + i) 이렇게 시작부터 i의 칸을 지우는 것이다.
만약 이렇게해서 특정 칸을 지운다면 한칸씩 댕겨지기 때문에 또 똑같은 i를 체크를 해야한다 그렇지 않으면 한칸씩 건너뛰게 된다.

v.erase(v.begin() + i) 반복문의 ++i 증감식을 따로 빼줘야한다.





Map과 operator<

Overview

std::map은 내부적으로 균형 잡힌 이진 트리 구조를 가진다. 이진 트리의 핵심은 모든 노드가 "누가 더 작은가"를 기준으로 정렬되어 있어야 한다는 것이다.

Map은 두 값이 같은지 확인하기 위해 ==을 호출하지 않는다. 대신 오직 < 연산 하나로 다음 논리를 펼친다.

a가 b보다 작지 않고(!( a < b )), b도 a보다 작지 않다면 (!( b < a)), 둘은 논리적으로 같다라고 판정한다.

이게 동치성(Equivalence) 라는 것인데 "비교해 봤는데 어느 쪽도 더 작지 않다면, 둘은 같은 것으로 간주한다."는 위와 같은 논리가 동치성이다. 그래서 이 방식 덕분에 Map은 정렬과 검색을 동시에 수행할 수 있다.

커스텀 클래스의 Map의 Key로 쓰고싶다면?

커스텀 Struct나 Class로 Map을 받는다면? 그떄 operator<를 오버로딩해야 한다. 그렇지 않으면 Map은 판단을 하지못해 컴파일 에러를 뱉는다. operator== 는 위에 설명한 동치성의 이유로 사용하지 못한다.

Map을 꺼내 쓰는 방법

오늘 Map을 꺼내 쓰지 못해서 이거 for 반복문으로밖에 못 빼냈었나? 너무 불편하네 이런생각을 했다. 근데 끝나고 다시 AI한테 물어보니 find라는게 있었던걸 까먹고 있었다.
Map은 정렬이 잘 되어 있기 떄문에 find로 꺼내 쓰는게 정석!!





std::unordered_set

Overview

std::unordered_set은 데이터의 '고유성'은 보장하지만 '정렬'은 포기한 자료구조이다.

해시 테이블 (Hash Table)

std::set이 이진 트리를 사용하여 데이터를 정렬된 상태로 유지한다면, std::unorderd해시 테이블을 사용한다.

해시 테이블이란? 데이터의 이름(Key)를 넣으면 데이터가 저장된 위치(Index)를 즉시 계산해내는 자료구조이다.

데이터의 순서가 전혀 중요하지 않고, "이 값이 안에 있는가?"를 확인하는 속도가 생명일때 사용한다.

#include <unordered_set>
#include <unordered_map>

int main() {
    // 1. 선언: <Key 타입, Value 타입>
    std::unordered_map<std::string, int> Inventory;

    // 2. 데이터 삽입 (평균 O(1))
    Inventory["RedPotion"] = 10;
    Inventory["Gold"] = 5000;
    Inventory.insert({"MagicWand", 1});

    // 3. 데이터 탐색 (평균 O(1))
    std::string searchItem = "Gold";
    if (Inventory.find(searchItem) != Inventory.end()) {
        std::cout << searchItem << "의 개수: " << Inventory[searchItem] << std::endl;
    }

    // 4. 데이터 삭제
    Inventory.erase("RedPotion");

    return 0;
}

게임을 설계할 때 다음과 같은 상황에서 해시 테이블(unordered_map)을 쓰면 된다.

  1. 중복 검사 전용 장부 -> 숫자나 문자열의 중복을 빠르게 걸러내야 할 때
    ex) 평소에 알파벳 체크 같은걸 할때 bool isAlpha[26] = {false}이런거 대신 쓴다.

  2. 인벤토리 시스템 -> 아이템 이름을 Key로 넣으면 바로 개수가 튀어나오게 할 때

  3. 리소스 매니저 -> 파일 경로(String)을 넣으면 로드된 텍스트(Value) 주소를 바로 줄 때

  4. 몬스터 도감 -> 몬스터 ID를 넣으면 해당 몬스텉의 상세 스탯을 가져올 때

unorderd_set, unorderd_map, set 언제 뭘 써야할까




---ㅡ

얕은복사, 깊은복사, 동적할당 new, delete[] , template

Overview

class Myclass{
	int* ptr;
public:
	MyClass (int val){
    	ptr = new int(val);
    }
	MyClass Copy(const MyClass& other){
    	ptr = new int;
        *ptr = *(other.ptr);
		return *ptr
	}
};

깊은 복사, 얕은 복사, 동적할당 new, delete, unique_ptr에 지식이 좀 부족한 것 같다.오늘 시험을 보면서 깊은 복사를 하는 방법도 알았다.
이 위의 코드를 보면서 많은 질문이 있었는데 일단 개념 정리하고 질문하겠다.

  • 얕은 복사(Shallow Copy) : 열쇠만 복사하기
    얕은 복사는 객체가 가진 멤버 변수의 값을 있는 그대로 복사한다. 만약 멤버 변>수가 포인터(int* ptr)라면 그 안에 든 주소값만 복사된다.

얕은 복사는 Danglling Pointer, Double Free Error같은게 발생 할 수 있어 관리하기가 힘들다.

  • 깊은복사(Deep Copy) : 집을 새로 짓기
    깊은 복사는 단순히 주소만 베끼는 게 아니라, 똑같은 크기의 새로운 땅을 사고 그 내용물을 일일이 복제하는 방식.


1. * ptr = new int(val);에서 왜 `int`을 안 쓰는가?

주소값과 데이터 값의 자료형을 맞추는 아주 기초적인 규칙 때문이다.

`new int(val)` -> 메모리 어딘가에 공간을 만들고, 그 공간의 주소를 리턴. ( 자료형 : int* )
ptr -> 주소를 담는 바구니 ( 자료형 : int* )



2. 왜 ptr = new int; 후에 *ptr = 전에 값을 버리는 짓을 할까?

ptr = new int;땅을 사는 행위이지, 값을 넣기위한 행동이 아니다.
땅을 사고 건물을 올리기위한 기초라고 생각하면 된다.

  1. ptr = new int;(땅 매입) : 내 포인터(ptr)가 안전하게 사용할 수 있는 실제 메모리 공간을 확보하는 단계. 이 과정을 거치지 않으면 ptr은 유령 주소를 가리킨다.

  2. *ptr = *(other.ptr);(입주) : 이제 내가 확보한 안전한 땅(ptr)에, 상대방이 가진 데이터 알맹이(*(other.ptr))만 복사해서 채워 넣는 것

만약 땅을 사는 new없이 바로 *ptr = ...을 했으면 허공에 대고 "여기다가 10번 손님 받아!" 라고 소리치는 것과 같다.



3. *(other.ptr)에서 괄호는 뭘까

컴퓨터가 연산을 처리하는 우선순위 때문. C++은 점(.)을 별(*)보다 훨씬 먼저 계산한다.

  • 괄호가 있을때 *(other.ptr) :
    1. other 객체 안에 있는 ptr 변수(주소)를 먼저 꺼낸다.
    1. 그다음에 *를 보고 "그 주소 안으로 들어가!"라고 해석한다. (우리가 원하는 결과)
  • 괄호가 없을 때 *other.ptr :
    1. other.ptrr을 먼저 하려는데, 만약 other가 객체가 아니라 포인터였다면 여기서부터 꼬인다. 혹은 엉뚱한 연산 순서 때문에 컴파일러가 주소 자체를 값으로 오해할 수 있다.

이 괄호가 귀찮아서 만든 도구가 바로 **화살표(->)이다. 포인터라면 other->ptr 이라고 쓰면 괄호 없이도 안전하게 접근 할 수 있다.



new T[Capacity];는 뭘까

template<typename T>
class MyVector{
	T* Data;
    int Size;
    int capacity;
    
public:
	MyVector(int capacity = 2) : capacity(capacity){
    Size = 0;
    Data = new T[Capacity];
   }
};

Data = new T[Capacity];의 뜻은

Capacity의 값만큼 T크기의 Data배열을 만들라는 것이다.
괄호로적을때 []()의 적을때의 차이는 []는 괄호안의 길이만큼 T의 크기를 생성 이고, ()는 괄호안의 값을 T의 자료형에 집어 넣어라 라는 것

그리고 이렇게 Data(Capacity) 인 배열이 탄생하면 delete[] Data 이렇게 괄호를 넣어 지워줘야한다.

0개의 댓글