C++ 입문노트
나는 Java, Kotlin이 주력 언어다. 업무에서도 이 두 가지를 쓰지만 C++를 알아두면 선택지가 넓어질 것 같아 입문해보려고 한다. LeetCode를 풀면서 C++ 숙련도를 올려보자.
오늘 배운 것
1. std::vector (동적 배열) 선언법
C++에서는 객체를 생성할 때 new를 무분별하게 사용하지 않는다.
잘못된 예: vector<int> res = new vector(); (자바 스타일)
C++에서 new를 쓰면 메모리 주소(포인터)를 반환하기 때문에 vector<int>* res처럼 선언해야 하고, 나중에 직접 delete로 메모리를 해제해야 한다.
올바른 예: vector<int> res(크기);
이렇게 선언하면 함수가 끝날 때 자동으로 메모리가 정리되는 지역 변수가 된다.
2. .size() 메서드
배열이나 리스트의 길이를 구할 때 언어마다 이름이 다르지만, C++에서는 size()를 표준으로 사용한다.
3. 참조자 (Reference, &)
코드의 vector<int>& nums에서 & 기호는 원본을 가져다 쓰겠다는 뜻이다.
vector<int> nums: 함수를 호출할 때 배열 전체를 복사 (메모리 낭비가 큼)vector<int>& nums: 원본 배열의 별명만 지어줌 (메모리 사용이 거의 없고 빠름)
4. 벡터의 초기화와 인덱스 접근
C++ 벡터는 선언할 때 크기를 미리 정해두면 일반 배열처럼 []를 사용해 접근할 수 있다.
vector<int> v(10);: 크기가 10이고 0으로 초기화된 벡터 생성. v[0]부터 v[9]까지 사용 가능.
크기를 정하지 않고 생성(vector<int> v;)했다면, v[i] = 10;처럼 대입할 수 없고 반드시 v.push_back(10);을 써야 한다.
5. 스택(Stack) vs 힙(Heap) - new의 의미가 다르다
Java에서 new는 일상적이지만, C++에서 new는 이 객체를 수동으로 관리하겠다는 선언에 가깝다.
Java: Solution sol = new Solution(); // GC(Garbage Collector)가 알아서 치워준다.
- C++ (Stack):
Solution sol;// 함수가 종료되면 메모리에서 자동으로 사라진다. - C++ (Heap):
Solution* sol = new Solution();// 사용 후delete sol;을 하지 않으면 메모리 누수가 발생한다.
6. 네임스페이스 (std::) - 가문 밝히기
C++의 표준 라이브러리(STL)는 모두 std 네임스페이스 안에 있다.
Java는 import 후 클래스 이름만 쓰면 되지만, C++은 std::vector, std::sort처럼 출처를 밝히는 것이 원칙이다.
코드 상단에 using namespace std;를 선언하면 생략할 수 있으나, 규모가 큰 프로젝트에서는 이름 충돌 방지를 위해 std::를 명시하는 습관이 좋다.
7. 범위 기반 for 루프 (Range-based for loop)
Kotlin의 for (num in nums)나 Java의 for (int num : nums)와 같은 문법을 C++11부터 지원한다.
for (int n : nums) {
// n은 nums 요소를 '복사'해서 가져온다.
}
for (int& n : nums) {
// &를 붙이면 '참조'로 가져온다.
// n을 수정하면 원본 nums도 수정되고, 복사 비용이 들지 않아 효율적이다.
}
Java/Kotlin -> C++ 대응표
| Java / Kotlin | C++ STL | 핵심 특징 |
|---|---|---|
ArrayList<T> | vector<T> | 연속 메모리, 인덱스 접근 O(1) |
LinkedList<T> | list<T> | 중간 삽입/삭제 O(1), 인덱스 접근 느림 |
Stack<T> | stack<T> | LIFO |
Queue<T> | queue<T> | FIFO |
Deque<T> | deque<T> | 양쪽 삽입/삭제 O(1) |
PriorityQueue<T> | priority_queue<T> | 기본 최대 힙 |
HashMap<K,V> | unordered_map<K,V> | 평균 O(1), 정렬 없음 |
TreeMap<K,V> | map<K,V> | 자동 정렬, O(logN) |
HashSet<T> | unordered_set<T> | 평균 O(1), 중복 없음 |
TreeSet<T> | set<T> | 정렬 유지, 중복 없음 |
코테에서 자주 쓰는 선택 기준
- "순서대로 담고 인덱스로 빠르게 접근" ->
vector - "키 기준 정렬된 순회 필요" ->
map/set - "정렬 필요 없고 존재 여부만 빠르게" ->
unordered_map/unordered_set - "최솟값/최댓값을 계속 꺼내야 함" ->
priority_queue - "BFS 레벨 탐색" ->
queue - "슬라이딩 윈도우에서 앞뒤 pop/push" ->
deque
꼭 알아둘 기본 시간복잡도 (암기용)
vector: 뒤push_back평균 O(1), 중간 삽입/삭제 O(N), 인덱스 접근 O(1)map/set: 삽입/삭제/탐색 O(logN)unordered_map/unordered_set: 평균 O(1), 최악 O(N)priority_queue: 삽입 O(logN), top 조회 O(1), pop O(logN)
실전 코드 패턴
#include <bits/stdc++.h>
using namespace std;
// 1) 최소 힙 (Java PriorityQueue 기본 동작과 유사)
priority_queue<int, vector<int>, greater<int>> minHeap;
// 2) 빈도수 카운트
unordered_map<int, int> freq;
for (int x : nums) freq[x]++;
// 3) key 정렬 순회가 필요할 때
map<int, int> ordered;
for (int x : nums) ordered[x]++;
// 4) 중복 제거 + 존재 여부 확인
unordered_set<int> seen;
if (seen.count(x)) {
// already exists
}
seen.insert(x);
bits/stdc++.h 없이 필요한 헤더만 include 하기
온라인 저지에서는 #include <bits/stdc++.h>가 편하지만, 표준 C++ 헤더는 아니기 때문에
환경에 따라 동작하지 않을 수 있다. 실무/이식성을 생각하면 필요한 헤더를 명시적으로 include하는 습관이 좋다.
#include <iostream> // cin, cout
#include <vector> // vector
#include <string> // string
#include <algorithm> // sort, max, min, lower_bound
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <map> // map
#include <set> // set
#include <queue> // queue, priority_queue
#include <stack> // stack
#include <deque> // deque
#include <utility> // pair
#include <limits> // numeric_limits
using namespace std;
자주 쓰는 알고리즘 유틸까지 넣고 싶다면 다음도 많이 사용한다.
<functional>:greater<>,less<>, 함수 객체<numeric>:accumulate,gcd(환경에 따라)<tuple>:tuple,tie
C++ STL 사용 시 자주 하는 실수
unordered_map은 순서를 보장하지 않는다. 출력 순서 기대하면 안 됨priority_queue는 기본이 최대 힙이다 (최소 힙은 comparator 필요)vector에 크기 없이v[i] = ...하면 런타임 에러 가능성 있음map[key]++는 key가 없으면 자동으로 0 생성 후 증가한다
정리하면, 코테에서는 대부분 vector + unordered_map/set + priority_queue 조합으로 시작하고,
"정렬된 상태 유지"가 필요할 때만 map/set로 바꾸면 판단이 빨라진다.