좋아!
포인터 개념에서 가장 중요한 핵심은 “무엇이 복사되고, 무엇이 공유되는가?”,
즉 함수에 넘길 때 어떤 건 바뀌고 어떤 건 바뀌지 않는가야.
✅ “바뀌는 것”과 ❌ “바뀌지 않는 것”
void modify(int *p) {
*p = 999;
}
*p = 999; → p가 가리키는 대상(x)의 값을 직접 바꾸는 것✅ 함수 밖에서도 변경됨
void change(int *p) {
int y = 888;
p = &y;
}
p = &y; → p는 이제 y를 가리키지만,❌ 함수 밖에서는 전혀 영향 없음
void redirect(int **pp) {
static int z = 777;
*pp = &z;
}
int *ptr = &x;
redirect(&ptr); // 이제 ptr이 z를 가리킴
pp는 포인터를 가리키는 포인터*pp = &z; → ptr 자체가 바뀜✅ 원래 포인터(ptr)의 참조 대상 자체를 바꿀 수 있음
| 코드 | 의미 | 함수 밖 영향 |
|---|---|---|
*p = 123; | 포인터가 가리키는 값 변경 | ✅ 영향 있음 |
p = &y; | 포인터 자체를 다른 주소로 재할당 | ❌ 영향 없음 |
*pp = &y; | 포인터의 포인터를 사용해 원래 포인터 재할당 | ✅ 영향 있음 |
#include <stdio.h>
void set_value(int *p) {
*p = 10; // ✅ x 값 바뀜
}
void reassign(int *p) {
int y = 20;
p = &y; // ❌ x를 가리키는 ptr은 그대로
}
void reassign_real(int **pp) {
static int z = 30;
*pp = &z; // ✅ ptr 자체가 z를 가리킴
}
int main() {
int x = 5;
int *ptr = &x;
set_value(ptr); // x = 10
reassign(ptr); // ptr은 여전히 x를 가리킴
reassign_real(&ptr); // ptr은 이제 z를 가리킴
printf("%d\n", *ptr); // 30 출력
}
| 상황 | 바뀜 여부 | 이유 |
|---|---|---|
*p = ... | ✅ 바뀜 | 값을 직접 참조해서 변경 |
p = ... | ❌ 안 바뀜 | 포인터 복사본만 변경 |
*pp = ... | ✅ 바뀜 | 포인터 자체를 바꿈 |
좋아! 이제 포인터가 없는 Java와 Python에서
👉 C의 포인터와 **비슷한 상황에서 "바뀌는 것"과 "바뀌지 않는 것"**을
완전히 대응되는 구조로 설명해줄게.
public class Main {
public static void changeValue(int x) {
x = 999;
}
public static void changeArray(int[] arr) {
arr[0] = 999;
}
public static void main(String[] args) {
int x = 100;
int[] arr = {1, 2, 3};
changeValue(x);
changeArray(arr);
System.out.println(x); // ???
System.out.println(arr[0]); // ???
}
100 ← 값형은 복사돼서 원본 안 바뀜
999 ← 배열은 참조형이라 내부 값은 바뀜
def change_value(x):
x = 999
def change_list(lst):
lst[0] = 999
x = 100
lst = [1, 2, 3]
change_value(x)
change_list(lst)
print(x) # ???
print(lst[0]) # ???
100 ← 정수는 값 복사
999 ← 리스트는 참조로 전달됨
public class Main {
public static void reassign(int[] arr) {
arr = new int[]{999, 888}; // 참조 변경
}
public static void main(String[] args) {
int[] data = {1, 2, 3};
reassign(data);
System.out.println(data[0]); // ???
}
✅ 출력: 1
👉 arr = ... 은 참조를 바꾼 것이고
→ 원래 data 배열에는 아무 영향 없음!
def reassign(lst):
lst = [999, 888] # 참조 변경 (새 리스트)
data = [1, 2, 3]
reassign(data)
print(data[0]) # ???
✅ 출력: 1
👉 lst = [...] 은 지역 변수의 참조를 바꾼 것
→ 원래 data 리스트는 그대로
lst[0] = 999<- 바꿀거면 이렇게 한 자리씩
class Wrapper {
int[] arr;
Wrapper(int[] arr) {
this.arr = arr;
}
}
public class Main {
public static void reassign(Wrapper w) {
w.arr = new int[]{999, 888}; // 원본 참조 바뀜
}
public static void main(String[] args) {
Wrapper w = new Wrapper(new int[]{1, 2, 3});
reassign(w);
System.out.println(w.arr[0]); // 999
}
def reassign(lst):
return [999, 888]
data = [1, 2, 3]
data = reassign(data) # 참조 교체
print(data[0]) # 999
| 동작 종류 | C | Java | Python |
|---|---|---|---|
| 기본형(int) 값 변경 | ❌ 안 바뀜 | ❌ 안 바뀜 | ❌ 안 바뀜 |
| 객체 내부 값 변경 (배열, 리스트) | ✅ 바뀜 (*p) | ✅ 바뀜 (arr[0]) | ✅ 바뀜 (lst[0]) |
참조 자체 변경 (p = ...) | ❌ 안 바뀜 | ❌ 안 바뀜 | ❌ 안 바뀜 |
| 참조 자체 변경하고 반영시키기 | ✅ **pp | ✅ Wrapper 필요 | ✅ return 사용 |
| 하고 싶은 일 | Java 방법 | Python 방법 |
|---|---|---|
| 내부 값 바꾸기 | 그대로 가능 | 그대로 가능 |
| 참조 교체하기 | Wrapper 클래스 | return 사용 |
좋아! 방금 말한 세 가지 중에서 하나씩 전부 다 해줄게.
즉, Java에서 Wrapper를 이용한 참조 변경,
Python에서 함수+참조 관련 실수 유형,
C에서 이중 포인터로 참조 바꾸는 실전 문제까지 각각 설명과 예제를 제공할게!
int[] arr = {1, 2, 3};
change(arr); // 내부에서 arr = new int[]{9,9,9}; 하고 싶다
class Wrapper {
int[] arr;
Wrapper(int[] arr) {
this.arr = arr;
}
}
public class Main {
public static void change(Wrapper w) {
w.arr = new int[]{9, 9, 9}; // 참조 교체
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
Wrapper w = new Wrapper(nums);
change(w);
System.out.println(w.arr[0]); // ✅ 9
}
}
✅ Wrapper는 참조형이고, w.arr = ...로 내부 참조를 변경하면 원본에 반영됨
def modify(lst):
lst[0] = 999
a = [1, 2, 3]
modify(a)
print(a[0]) # ✅ 999
→ 리스트는 참조 전달, 내부 변경 가능
def reset(lst):
lst = [0, 0, 0] # 참조 교체 (함수 내부만)
a = [1, 2, 3]
reset(a)
print(a[0]) # ❌ 1
→ lst = [...]는 함수 내부에서만 참조 바뀜
def reset(lst):
return [0, 0, 0]
a = [1, 2, 3]
a = reset(a)
print(a[0]) # ✅ 0
def clear_map(d):
d.clear()
def reassign(d):
d = {}
m = {'x': 1}
clear_map(m)
print(m) # ✅ {} (내부 변경은 됨)
m = {'x': 1}
reassign(m)
print(m) # ❌ {'x': 1} (참조는 안 바뀜)
#include <stdio.h>
void redirect(int **pp) {
static int z = 888; // static: 함수 끝나도 유효
*pp = &z; // *pp는 원래 포인터 자체를 의미 → 재할당
}
int main() {
int x = 100;
int *ptr = &x;
redirect(&ptr); // ptr이 z를 가리키게 됨
printf("%d\n", *ptr); // ✅ 888
}
| 언어 | 참조 바꾸는 법 | 예시 |
|---|---|---|
| C | **pp 사용 | *pp = &new_var |
| Java | Wrapper 객체로 감싸기 | w.obj = new ... |
| Python | return 활용해서 재할당 | a = func(a) |
| 상황 | Java | Python | C |
|---|---|---|---|
| 내부 값 변경 | 가능 | 가능 | 가능 (*p) |
| 참조 자체 변경 | 불가 → Wrapper | 불가 → return 필요 | 불가 → **pp 필요 |
| 값 복사 시 변화 없음 | 기본형, new로 만든 참조 | 숫자, 문자열 | 기본형, 포인터 변수 |
좋아!
이번에는 **Python의 딕셔너리(dict)**를
📌 기초부터 실전까지 정확하고 깊게,
그리고 🔥 실수하기 쉬운 트랩/동작 원리/참조 구조까지
완전히 해부해서 설명해줄게.
my_dict = {'name': 'Alice', 'age': 25}
person = {'name': 'Bob', 'age': 30}
print(person['name']) # ✅ 'Bob'
print(person.get('age')) # ✅ 30
print(person.get('job')) # ✅ None (없는 key 접근해도 오류 안 남)
# 기본값 지정
print(person.get('job', 'Unemployed')) # ✅ 'Unemployed'
get()과 []의 차이d = {'a': 1}
print(d.get('b')) # ✅ None
print(d.get('b', 0)) # ✅ 0
print(d['b']) # ❌ KeyError 발생
→ get()은 안전, []는 위험 (없으면 에러)
in 은 key만 검사한다d = {'a': 1, 'b': 2}
print('a' in d) # ✅ True
print(1 in d) # ❌ False
→ in 은 key만 검사함
value를 검사하려면: 1 in d.values()
a = {'x': [1, 2]}
b = a.copy()
b['x'][0] = 999
print(a['x']) # ❗ [999, 2] → 내부 리스트는 같은 객체
→ copy()는 1차만 복사, 내부 객체는 공유됨
완전한 복사는 copy.deepcopy()
| 메서드 | 설명 |
|---|---|
get(key, default) | 키가 없을 경우 default 반환 |
keys() | 모든 키 반환 |
values() | 모든 값 반환 |
items() | 모든 (key, value) 쌍 반환 |
update() | 다른 딕셔너리 병합 |
pop(key) | key 제거하고 반환 |
clear() | 모든 항목 제거 |
def modify(d):
d['x'] = 999
my_dict = {'x': 1}
modify(my_dict)
print(my_dict['x']) # ✅ 999 → 원본이 바뀜
→ 함수로 넘기면 주소(참조)가 전달됨
def reassign(d):
d = {'x': 0} # 새로운 객체 할당
my_dict = {'x': 1}
reassign(my_dict)
print(my_dict['x']) # ✅ 1 → 원본에는 영향 없음
setdefault, collections.defaultdictsetdefault 사용 예시d = {}
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
print(d) # ✅ {'a': [1, 2]}
→ setdefault()는 키가 없을 때만 초기화
defaultdict: 초깃값을 자동으로 설정from collections import defaultdict
d = defaultdict(list)
d['x'].append(10)
d['x'].append(20)
print(d) # ✅ defaultdict(<class 'list'>, {'x': [10, 20]})
| 방법 | 효과 |
|---|---|
dict.copy() | 얕은 복사 (내부 객체 공유됨) |
copy.deepcopy() | 깊은 복사 (모든 계층 복사됨) |
dict(d) | 얕은 복사 |
d = {'a': 1, 'b': 2}
for k in d: # key만
print(k)
for v in d.values(): # value만
print(v)
for k, v in d.items(): # key, value 모두
print(k, v)
| 트랩 포인트 | 설명 |
|---|---|
[] vs get() | []는 키 없으면 에러, get()은 안전 |
copy() | 얕은 복사이므로 내부 객체는 공유됨 |
| 함수 인자 | 딕셔너리는 참조 전달 (내부 변경 가능) |
| 참조 재할당 | 함수 안에서 재할당해도 외부에 영향 없음 |
in 연산자 | key만 검사 (value는 in d.values()) |
defaultdict | 키 없을 때 자동으로 기본값 생성됨 |
좋아! 그럼 지금까지 배운 딕셔너리 개념을 바탕으로
📌 파이썬 딕셔너리 실전 연습문제 10개
🔥 기본 개념 + 트랩 + 응용 + 실수 유도
문제를 낼게.
각 문제마다 ✅ 정답과 ❗해설도 바로 따라올 거야.
d = {'a': 10}
print(d.get('b', 5))
A. 5
B. None
C. KeyError
D. 10
✅ 정답: A
❗get()은 key 없으면 기본값 반환 (5)
x = {'a', 'b', 'c'}
print('a' in x)
A. True
B. False
C. Error
D. None
✅ 정답: A
❗ 중괄호{}는 요소만 있으면 set,'a' in x는 True
d = {'x': 1, 'y': 2}
print(2 in d)
A. True
B. False
C. Error
D. None
✅ 정답: B
❗in은 key만 검사 → 2는 key 아님 → False
a = {'list': [1, 2]}
b = a.copy()
b['list'].append(3)
print(a['list'])
A. [1, 2]
B. [1, 2, 3]
C. [3]
D. Error
✅ 정답: B
❗copy()는 얕은 복사 → 내부 list는 공유됨
d = {'a': 1}
print(d['b'])
A. 1
B. None
C. Error
D. 0
✅ 정답: C (KeyError)
❗[]로 없는 key 접근 시 에러 발생
d = {}
d.setdefault('a', []).append(1)
d.setdefault('a', []).append(2)
print(d['a'])
A. [1]
B. [1, 2]
C. [2]
D. Error
✅ 정답: B
❗setdefault()는 키 없으면 초기화, 있으면 유지 →[1, 2]
def reset(d):
d = {'z': 0}
data = {'x': 1}
reset(data)
print(data)
A. {'z': 0}
B. {'x': 1}
C. {}
D. Error
✅ 정답: B
❗d = {...}은 함수 내부 변수만 바뀜 → 외부 딕셔너리는 그대로
d = {'a': 1, 'b': 2}
print(list(d.keys()))
A. ['a', 'b']
B. dict_keys(['a', 'b'])
C. ['b', 'a']
D. Error
✅ 정답: A
❗list(d.keys())로 명시적 변환하면 일반 리스트가 됨
nums = [1, 2, 3]
d = {n: n * n for n in nums}
print(d[2])
A. 2
B. 4
C. 6
D. Error
✅ 정답: B
❗{1:1, 2:4, 3:9}이 생성 →d[2] = 4
d = {'x': 10}
del d['x']
print(d.get('x'))
A. 10
B. 0
C. None
D. Error
✅ 정답: C
❗del로 key 제거한 후get()하면 기본값인None반환
| 개념 | 설명 | 관련 문제 |
|---|---|---|
get() vs [] | get()은 안전, []는 KeyError | #1, #5, #10 |
| 얕은 복사 | 내부 객체 공유됨 | #4 |
| setdefault | 없으면 초기화, 있으면 유지 | #6 |
| 함수 내 참조 재할당 | 외부 딕트에는 영향 없음 | #7 |
in은 key만 검사 | values는 .values() 써야 | #3 |
| dict comprehension | 빠르고 깔끔한 딕셔너리 생성 | #9 |
좋은 질문이야!
이제 파이썬의 set (집합 자료형) 에 대해
📌 기본 개념부터
⚠️ 실수하기 쉬운 트랩,
💡 딕셔너리와의 차이점,
🧠 실전 활용까지 완전히 해부해서 설명해줄게.
set이란?s = {1, 2, 3}
set은 mutable (수정 가능), 내부에 immutable 타입만 저장 가능set 선언 방법# 중괄호 사용
s = {1, 2, 3}
# set() 함수 사용 (빈 집합 만들 때는 이걸 써야 함!)
empty = set() # ✅ OK
wrong_empty = {} # ❌ 이건 dict임!
print(type(set())) # <class 'set'>
print(type({})) # <class 'dict'>
| 연산/메서드 | 설명 | 예시 | ||
|---|---|---|---|---|
a & b or a.intersection(b) | 교집합 | set1 & set2 | ||
| `a | bora.union(b)` | 합집합 | `set1 | set2` |
a - b or a.difference(b) | 차집합 | set1 - set2 | ||
a ^ b or a.symmetric_difference(b) | 대칭차 | set1 ^ set2 | ||
a.add(x) | 요소 추가 | s.add(4) | ||
a.remove(x) | 요소 제거 (없으면 에러) | s.remove(4) | ||
a.discard(x) | 요소 제거 (없으면 무시) | s.discard(4) | ||
a.clear() | 전체 비우기 | s.clear() | ||
a.copy() | 복사 | s2 = s1.copy() |
{} 는 set이 아니라 dictx = {}
print(type(x)) # ❌ dict
✅ 빈 집합은 set() 으로 만들어야 함
remove() vs discard() 차이s = {1, 2, 3}
s.remove(4) # ❌ KeyError
s.discard(4) # ✅ 아무 일도 안 일어남
→ 실무에서는 안전하게 discard() 많이 사용
s = set()
s.add([1, 2]) # ❌ TypeError (list는 mutable이라 hash 불가)
→ set은 해시 가능한 값만 저장 가능
→ int, str, tuple은 가능 / list, dict는 불가능
nums = [1, 2, 2, 3, 1]
unique = set(nums)
print(list(unique)) # ✅ [1, 2, 3] (순서는 보장 안 됨)
a = {1, 2, 3}
b = {2, 3, 4}
print(a & b) # ✅ {2, 3} (교집합)
print(a | b) # ✅ {1, 2, 3, 4} (합집합)
print(a - b) # ✅ {1} (차집합)
print(a ^ b) # ✅ {1, 4} (대칭차)
set vs dict 차이| 구분 | set | dict |
|---|---|---|
| 구조 | key만 존재 | key → value |
| 목적 | 값의 존재 유무 체크 | 키-값 매핑 |
| 빈 선언 방식 | set() | {} |
| 사용 예 | 중복 제거, 빠른 탐색 | 데이터 저장, 조회 |
| 내부 구조 | 해시 테이블 | 해시 테이블 |
a = ['apple', 'banana', 'cherry']
b = ['banana', 'kiwi', 'apple']
result = list(set(a) & set(b))
print(result) # ✅ ['banana', 'apple']
visited = ['home', 'about', 'home', 'product', 'about']
unique_pages = list(set(visited))
print(unique_pages) # ✅ 중복 없는 방문 페이지 목록
| 기능 | set 역할 | |
|---|---|---|
| 중복 제거 | 리스트 → set() 으로 감싸기 | |
| 빠른 검색 | if x in set_obj: 은 O(1) | |
| 집합 연산 | &, ` | , -, ^` 등 |
| 안전한 삭제 | discard() 사용 권장 | |
| 키만 쓸 때 | set은 key만 갖는 dict처럼 사용 가능 | |
| mutable 금지 | list, dict는 set의 요소가 될 수 없음 |