Post

2026-03-25 TIL (22일차)

2026-03-25 TIL (22일차)

C++

pair을 이용하여 vector 선언

vector는 연속된 메모리 공간에 데이터를 저장하는 배열 기반 컨테이너입니다. 여기에 pair를 넣으면, 각 칸마다 두 개의 데이터가 묶인 하나의 쌍이 저장됩니다.

메모리 저장 형태와 특징

  • 저장되는 형태: 메모리 상에 (첫 번째 데이터, 두 번째 데이터), (첫 번째 데이터, 두 번째 데이터) 순서로 빈틈없이 배치됩니다.
  • 자료형의 자유: first와 second에 서로 다른 자료형을 넣을 수 있습니다. (예: int와 string, float와 bool 등)
1
2
3
4
5
6
7
8
9
10
11
12
    std::vector<std::pair<int, std::string>> userList;

    userList.push_back(std::make_pair(1, "Alice"));
    userList.push_back({2, "Bob"});
    userList.emplace_back(3, "Charlie");
   
    for (int i = 0; i < userList.size(); ++i) {
        // 벡터의 i번째 요소(pair)의 first와 second에 접근
        std::cout << "ID: " << userList[i].first 
                  << ", Name: " << userList[i].second << std::endl;
    }
}

장점

① 연관된 데이터 묶기
서로 관련이 있는 두 데이터를 하나의 리스트로 관리하고 싶을 때 유용합니다.

② 자동 정렬 (Sorting)
std::pair는 기본적으로 비교 연산자가 정의되어 있습니다. std::sort를 사용하면 편하게 정렬할 수 있습니다.


해시 테이블(Hash Table)

해시 테이블(Hash Table)은 컴퓨터 과학에서 데이터를 르게 찾아내기 위해 고안된 ‘키(Key)-값(Value)’ 기반의 자료구조입니다. 쉽게 비유하자면, 수만 권의 책이 꽂힌 도서관에서 책 제목(Key)을 말하자마자 사서가 “그 책은 7번 선반의 3번째 칸에 있습니다”라고 단번에 알려주는 안내 시스템과 같습니다.


해시 테이블의 상세 정의 및 핵심 구성 요소

해시 테이블은 해시 함수(Hash Function)를 사용하여 키를 특정 Index으로 변환하고, 이 Index을 주소 삼아 데이터를 저장하는 배열 형태의 자료구조입니다.

  • 키(Key): 데이터를 찾기 위한 고유한 식별자 (예: 유저 닉네임, 학번).
  • 해시 함수(Hash Function): 임의의 길이를 가진 키를 고정된 크기의 숫자(Index)로 바꿔주는 수학적 연산입니다.
  • 해시 값(Hash Value/Code): 해시 함수의 결과물로 나온 숫자입니다. 이 숫자가 배열의 Index가 됩니다.
  • 버킷(Bucket/Slot): 데이터가 저장되는 각각의 공간입니다.


해시 테이블의 특징

해시 테이블이 다른 자료구조(배열, 리스트, 트리)와 구별되는 특징은 탐색의 효율성입니다.

  • 상수 시간 탐색 ($O(1)$): 일반 배열은 처음부터 끝까지 훑어야 하지만($O(N)$), 해시 테이블은 키를 함수에 넣는 즉시 주소가 나오므로 데이터 양과 무관하게 거의 즉시 찾아냅니다.
  • 공간과 시간의 교환: 빠른 속도를 얻는 대신, 데이터를 띄엄띄엄 저장하므로 메모리 공간을 상대적으로 더 많이 차지합니다.


해시 충돌(Collision)

해시 테이블의 단점은 서로 다른 두 키가 해시 함수를 거쳤는데 똑같은 방 번호(Index)가 나오는 ‘해시 충돌’입니다. 이 현상이 발생하는 이유는 다음과 같습니다.

  1. 비둘기집 원리 (Pigeonhole Principle)
    • 입력될 수 있는 키(Key)의 종류는 무한대(예: 세상의 모든 문자열 조합)에 가깝지만, 해시 테이블의 방(Bucket) 개수는 컴퓨터의 한정된 메모리 때문에 유한합니다.
    • 10개의 비둘기집에 11마리의 비둘기를 넣으려면 반드시 한 집에는 2마리 이상이 들어가야 하듯, 무한한 키를 유한한 배열 인덱스로 압축하다 보면 필연적으로 겹치는 주소가 생깁니다.
  2. 해시 함수의 한계
    • 어떤 키가 들어와도 완벽하게 100% 다른 주소로 분산시켜주는 ‘완벽한 해시 함수’를 만드는 것은 현실적으로 불가능하거나 연산 비용이 너무 큽니다. 따라서 효율성을 위해 적당히 빠르고 잘 분산되는 함수를 쓰다 보면 우연히 같은 값이 계산될 확률이 존재합니다.

충돌 해결 방법

  • 체이닝 (Chaining): 충돌이 난 방 안에 연결 리스트(Linked List)를 만들어 데이터를 줄 세우는 방식.
  • 개방 주소법 (Open Addressing): 충돌이 나면 규칙에 따라 비어있는 옆 방을 찾아 들어가는 방식.


활용 예시

  • ① 데이터베이스 인덱싱: 수백만 명의 회원 정보 중 ‘ID’ 하나로 특정 회원을 즉시 찾아야 할 때 사용합니다. 로그인 시 비밀번호 대조 과정에서도 쓰입니다.
  • ② 컴파일러의 심볼 테이블: 코드 안의 수많은 변수 이름과 함수 이름을 메모리 주소와 연결해 줘야 할 때 사용합니다.
  • ③ 캐싱 (Caching): 웹 브라우저나 서버가 연산 결과를 저장해 두었다가, ‘URL’이나 ‘검색어’를 키로 하여 데이터를 빠르게 다시 가져올 때 사용합니다.
  • ④ 게임 개발 (Object Lookup): 수만 개의 아이템 중 특정 고유 번호(UID)를 가진 객체를 즉시 찾아 로직을 실행해야 할 때 필수적입니다. (std::unordered_map)


요약 해시 테이블은 무엇(Key)을 넣으면 어디(Index)에 있는지 바로 계산해 내는 자료구조입니다. 데이터가 정렬되어 있을 필요가 없고, 오직 찾는 속도($O(1)$)가 생명인 시스템 입니다.

String

게임 클라이언트/서버 개발 시 채팅, 세이브 파일, UI 텍스트 등 문자열을 다룰 때 필수적으로 알아야 할 C++ string의 핵심 기능과 성능 최적화 포인트 정리.


1. string의 본질: vector<char>의 사촌

C++에서 string은 연속된 메모리 공간에 문자(char)들이 모여 있는 컨테이너로, vector와 구조가 거의 동일하다.

  • 인덱스 접근 ($O(1)$): s[0], s[1]처럼 배열과 똑같이 빠른 속도로 특정 위치 문자에 접근 가능.
  • 메서드 공유: vector처럼 .size()로 길이를 구하거나, .push_back('!')으로 맨 뒤에 문자를 추가할 수 있다.
  • 문자열 전용 연산자:
    • +: 문자열 이어 붙이기 ("Hello" + " World")
    • ==, <, >: 사전순(알파벳순) 비교 가능.

2. 문자열 검색과 추출: find, npos, substr

find("검색어")string::npos (절대 규칙)

  • find는 문자열 안에서 검색어가 시작되는 인덱스(위치)를 반환한다.
  • string::npos의 정체: find가 검색어를 찾지 못했을 때 반환하는 값.
    • find의 반환형은 부호 없는 정수(size_t)이므로, -1이 들어가면 언더플로우가 발생해 아주 거대한 쓰레기값이 된다.
    • 주의: find 사용 직후 if (pos != string::npos)로 검사하지 않고 결과값을 그대로 배열 인덱스나 substr에 넣으면 메모리 에러(Crash)가 발생한다.

substr(시작위치, 길이) 콤보 활용

  • 원본은 그대로 둔 채, 원하는 부분만 복사해서 새로운 문자열을 만들어낸다.
  • 활용 (파일명/확장자 분리): game_save.dat에서 find('.')로 점의 위치를 찾은 뒤, substr을 이용해 파일명과 확장자를 분리할 때 유용하다.

3. 문자열 수정과 성능: replace vs substr

replace(시작위치, 길이, "새문자")

  • substr과 달리 원본 문자열 자체를 직접 수정한다.
  • 성능 주의점: 중간 내용을 지우고 길이가 다른 문자를 끼워 넣으면, 뒤에 있는 문자를 전부 밀거나 당겨야 하는 메모리 이동 비용이 발생한다. 루프 안에서 빈번하게 사용 시 성능 저하 우려가 있다.
  • 무한 루프 주의: 전체 치환 루프를 돌릴 때, 다음 검색 시작 위치(pos)를 반드시 ‘새로 끼워 넣은 단어의 길이’만큼 뒤로 밀어줘야 한다.

② 무엇이 더 빠를까?

  • 문자열을 이리저리 많이 뜯어고쳐야 한다면, 원본을 replace로 계속 건드리는 것보다 필요한 부분만 substr로 떼어내어 새로운 빈 문자열에 이어 붙이는 것이 성능상 유리할 때가 많다.

4. 대소문자 무시 검색: tolower

  • 원리: 아스키코드(ASCII) 테이블에서 영문 대·소문자는 정확히 32만큼 차이가 난다. ('A'=65, 'a'=97)
  • tolower(문자): 대문자가 들어오면 소문자로 변환해 주며, 이미 소문자거나 기호면 그대로 둔다.
  • 활용: 게임 채팅 필터 시스템에서 유저가 “Hack”, “HACK” 등 섞어 써도 잡아낼 수 있도록, ‘검색용 소문자 복사본’을 만들어 필터링을 진행할 때 필수적이다.

5. 데이터 파싱과 형변환: stringstream, stoi, to_string

네트워크 패킷이나 세이브 파일(텍스트)을 실제 게임 데이터(구조체)로 변환하는 핵심 과정.

  • stringstream + getline:
    • 긴 문자열을 스트림에 넣고 쉼표(,)나 공백 등 구분자를 기준으로 데이터를 조각(토큰)낸다.
  • stoi (문자열 $\rightarrow$ 숫자):
    • 조각난 "45"는 아직 글자일 뿐이므로, stoi("45")를 통해 실제 수학 계산이 가능한 정수 45로 변환해야 HP, 골드 등에 적용할 수 있다.
  • to_string (숫자 $\rightarrow$ 문자열):
    • 반대로 변경된 스탯(정수)을 다시 세이브 파일(텍스트)로 저장할 때 사용한다.

요약표

도구주요 역할 및 특징실무/게임 개발 활용 예시
string배열(vector)처럼 연속된 메모리를 쓰는 문자 컨테이너UI 텍스트, 캐릭터 이름 저장
string::npos찾기 실패 시 반환되는 쓰레기값 (조건문 체크 필수)검색 결과 검증 (버그 및 크래시 방지)
find / substr특정 문자의 위치를 찾고 복사본으로 추출에셋 파일명과 확장자 분리
replace원본 문자열을 직접 수정 (메모리 이동에 따른 성능 유의)채팅 금칙어를 ***로 치환
tolower문자를 소문자로 변환 (아스키코드 활용)대소문자 구별 없는 명령어/채팅 검색
stringstream긴 문자열을 특정 구분자(쉼표 등) 기준으로 조각냄네트워크 패킷 분해, CSV 데이터 읽기
stoi문자열(Text)을 실제 계산용 정수(Int)로 변환세이브 파일에서 HP, 골드 수치 읽기
to_string계산용 정수(Int)를 다시 문자열(Text)로 변환변경된 스탯을 세이브 데이터로 쓰기
This post is licensed under CC BY 4.0 by the author.