연습18(주의-거의 전체)

류한선·2025년 7월 1일

실기연습-2

목록 보기
66/95

좋아!
포인터 개념에서 가장 중요한 핵심은 “무엇이 복사되고, 무엇이 공유되는가?”,
함수에 넘길 때 어떤 건 바뀌고 어떤 건 바뀌지 않는가야.


📌 C 언어 포인터 정리:

“바뀌는 것”과 ❌ “바뀌지 않는 것”


✅ 바뀌는 것 (가리키는 대상 변경)

예시 1: 가리키는 값 변경

void modify(int *p) {
    *p = 999;
}
  • *p = 999; → p가 가리키는 대상(x)의 값을 직접 바꾸는 것
  • 원본 변수(x)가 변경됨

함수 밖에서도 변경됨


❌ 바뀌지 않는 것 (포인터 변수 자체의 재할당)

예시 2: 포인터 자체 변경

void change(int *p) {
    int y = 888;
    p = &y;
}
  • p = &y;p는 이제 y를 가리키지만,
  • 이것은 p만 바뀌고, 원래 포인터(ptr)는 그대로

함수 밖에서는 전혀 영향 없음


✅ 바꾸고 싶다면? → 포인터의 포인터 사용

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 = ...✅ 바뀜포인터 자체를 바꿈

좋아! 이제 포인터가 없는 JavaPython에서
👉 C의 포인터와 **비슷한 상황에서 "바뀌는 것"과 "바뀌지 않는 것"**을
완전히 대응되는 구조로 설명해줄게.


✅ Java / Python 에서 "포인터처럼 보이지만 다르게 동작하는" 경우


🔴 C와 가장 큰 차이점

  • Java와 Python에는 포인터가 없다
  • 하지만 참조(reference) 를 통해 객체는 비슷하게 동작함
  • 단, 기본형(int, float 등)은 값 복사 / 객체(List, 클래스 등)는 참조 복사

✅ 예시 1: Java에서 배열 = 참조, int = 값

📌 Java 코드

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      ← 배열은 참조형이라 내부 값은 바뀜

✅ 예시 2: Python에서 리스트는 참조, int는 값

📌 Python 코드

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      ← 리스트는 참조로 전달됨

❌ 예시 3: Java에서 참조 자체를 바꾸면 반영되지 않음

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 배열에는 아무 영향 없음!


❌ 예시 4: Python에서 리스트 자체를 바꿔도 반영 안 됨 (체크)

def reassign(lst):
    lst = [999, 888]  # 참조 변경 (새 리스트)

data = [1, 2, 3]
reassign(data)
print(data[0])  # ???

✅ 출력: 1

👉 lst = [...] 은 지역 변수의 참조를 바꾼 것
원래 data 리스트는 그대로
lst[0] = 999<- 바꿀거면 이렇게 한 자리씩


✅ 예시 5: 참조 자체를 바꾸려면 Java는 래퍼 클래스, Python은 return

📌 Java → 직접 참조 못 바꿈 → Wrapper 객체 필요

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
    }

📌 Python → return으로 대체 가능

def reassign(lst):
    return [999, 888]

data = [1, 2, 3]
data = reassign(data)  # 참조 교체
print(data[0])  # 999

📚 종합 비교표: C vs Java vs Python

동작 종류CJavaPython
기본형(int) 값 변경❌ 안 바뀜❌ 안 바뀜❌ 안 바뀜
객체 내부 값 변경 (배열, 리스트)✅ 바뀜 (*p)✅ 바뀜 (arr[0])✅ 바뀜 (lst[0])
참조 자체 변경 (p = ...)❌ 안 바뀜❌ 안 바뀜❌ 안 바뀜
참조 자체 변경하고 반영시키기**pp✅ Wrapper 필요✅ return 사용

✅ 핵심 정리 요약

  • Java / Python 모두 객체는 "참조"로 동작하지만,
    참조 변수 자체를 바꾸면 함수 밖에는 반영 안 됨
하고 싶은 일Java 방법Python 방법
내부 값 바꾸기그대로 가능그대로 가능
참조 교체하기Wrapper 클래스return 사용

좋아! 방금 말한 세 가지 중에서 하나씩 전부 다 해줄게.
즉, Java에서 Wrapper를 이용한 참조 변경,
Python에서 함수+참조 관련 실수 유형,
C에서 이중 포인터로 참조 바꾸는 실전 문제까지 각각 설명과 예제를 제공할게!


✅ ① Java: Wrapper 객체로 참조(주소) 바꾸기

❓ 목표

int[] arr = {1, 2, 3};
change(arr);   // 내부에서 arr = new int[]{9,9,9}; 하고 싶다

❌ 일반 방식으론 안 됨 (참조는 복사됨)

✅ 해결: 참조 자체를 Wrapper로 감싸서 바꾼다

🔸 예제 코드

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 = ...로 내부 참조를 변경하면 원본에 반영됨


✅ ② Python: 함수 + 참조 실수 유형 (체크)

❓ 헷갈리는 대표 실수들 정리


🔸 (1) 리스트 값 변경 (정상 작동)

def modify(lst):
    lst[0] = 999

a = [1, 2, 3]
modify(a)
print(a[0])  # ✅ 999

→ 리스트는 참조 전달, 내부 변경 가능


🔸 (2) 리스트 자체를 재할당 (❌ 반영 안 됨)

def reset(lst):
    lst = [0, 0, 0]  # 참조 교체 (함수 내부만)

a = [1, 2, 3]
reset(a)
print(a[0])  # ❌ 1

lst = [...]는 함수 내부에서만 참조 바뀜


🔸 (3) 올바르게 바꾸려면 return 사용

def reset(lst):
    return [0, 0, 0]

a = [1, 2, 3]
a = reset(a)
print(a[0])  # ✅ 0

🔸 (4) 딕셔너리도 마찬가지

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} (참조는 안 바뀜)

✅ ③ C: 이중 포인터로 참조 자체 바꾸기

❓ 목표: 포인터 자체가 가리키는 대상을 바꾸고 싶다


🔸 예제 코드

#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, Java, Python 참조 바꾸기 요령

언어참조 바꾸는 법예시
C**pp 사용*pp = &new_var
JavaWrapper 객체로 감싸기w.obj = new ...
Pythonreturn 활용해서 재할당a = func(a)

📌 마무리 핵심 요약

상황JavaPythonC
내부 값 변경가능가능가능 (*p)
참조 자체 변경불가 → Wrapper불가 → return 필요불가 → **pp 필요
값 복사 시 변화 없음기본형, new로 만든 참조숫자, 문자열기본형, 포인터 변수

좋아!
이번에는 **Python의 딕셔너리(dict)**를
📌 기초부터 실전까지 정확하고 깊게,
그리고 🔥 실수하기 쉬운 트랩/동작 원리/참조 구조까지
완전히 해부해서 설명해줄게.


✅ 1. 딕셔너리는 어떤 자료구조인가?

my_dict = {'name': 'Alice', 'age': 25}
  • Key-Value 쌍으로 구성된 해시 기반 자료형
  • ✅ 내부적으로는 Hash Table 사용 → 평균 시간복잡도 O(1)
  • ✅ key는 변경 불가능한(immutable) 자료형만 가능 (ex: str, int, tuple)
  • ✅ value는 어떤 자료형이든 가능

📌 2. 딕셔너리의 기본 동작

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'

✅ 3. 딕셔너리에서 자주 나오는 실수 트랩


🔥 트랩 1: 딕셔너리 get()[]의 차이

d = {'a': 1}

print(d.get('b'))      # ✅ None
print(d.get('b', 0))   # ✅ 0
print(d['b'])          # ❌ KeyError 발생

get()은 안전, []는 위험 (없으면 에러)


🔥 트랩 2: in 은 key만 검사한다

d = {'a': 1, 'b': 2}

print('a' in d)       # ✅ True
print(1 in d)         # ❌ False

in 은 key만 검사함
value를 검사하려면: 1 in d.values()


🔥 트랩 3: dict는 얕은 복사(shallow copy)

a = {'x': [1, 2]}
b = a.copy()
b['x'][0] = 999

print(a['x'])   # ❗ [999, 2]  → 내부 리스트는 같은 객체

copy()는 1차만 복사, 내부 객체는 공유
완전한 복사는 copy.deepcopy()


✅ 4. 딕셔너리의 주요 메서드 정리

메서드설명
get(key, default)키가 없을 경우 default 반환
keys()모든 키 반환
values()모든 값 반환
items()모든 (key, value) 쌍 반환
update()다른 딕셔너리 병합
pop(key)key 제거하고 반환
clear()모든 항목 제거

✅ 5. 참조와 함수 인자: 딕셔너리는 mutable

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 → 원본에는 영향 없음

✅ 6. 실전 트릭: setdefault, collections.defaultdict

🔸 setdefault 사용 예시

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]})

✅ 7. 딕셔너리의 복사 방식 정리

방법효과
dict.copy()얕은 복사 (내부 객체 공유됨)
copy.deepcopy()깊은 복사 (모든 계층 복사됨)
dict(d)얕은 복사

✅ 8. 딕셔너리 순회 방식

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개
🔥 기본 개념 + 트랩 + 응용 + 실수 유도
문제를 낼게.
각 문제마다 ✅ 정답과 ❗해설도 바로 따라올 거야.


💣 Python 딕셔너리 실전 트랩 문제 10선


✅ 문제 1. get()의 기본값 작동

d = {'a': 10}
print(d.get('b', 5))

A. 5
B. None
C. KeyError
D. 10

정답: A
get() 은 key 없으면 기본값 반환 (5)


✅ 문제 2. dict vs set 구분

x = {'a', 'b', 'c'}
print('a' in x)

A. True
B. False
C. Error
D. None

정답: A
❗ 중괄호 {} 는 요소만 있으면 set, 'a' in x는 True


✅ 문제 3. in 연산자는 무엇을 기준으로?

d = {'x': 1, 'y': 2}
print(2 in d)

A. True
B. False
C. Error
D. None

정답: B
in 은 key만 검사 → 2는 key 아님 → False


✅ 문제 4. 딕셔너리 얕은 복사

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는 공유됨


✅ 문제 5. KeyError 트랩

d = {'a': 1}
print(d['b'])

A. 1
B. None
C. Error
D. 0

정답: C (KeyError)
[]로 없는 key 접근 시 에러 발생


✅ 문제 6. setdefault로 누적

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]


✅ 문제 7. 함수 안에서 참조 바꾸기 실패

def reset(d):
    d = {'z': 0}

data = {'x': 1}
reset(data)
print(data)

A. {'z': 0}
B. {'x': 1}
C. {}
D. Error

정답: B
d = {...}은 함수 내부 변수만 바뀜 → 외부 딕셔너리는 그대로


✅ 문제 8. keys()의 정체는?

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())로 명시적 변환하면 일반 리스트가 됨


✅ 문제 9. 딕셔너리 내포(dictionary comprehension)

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


✅ 문제 10. del로 키 제거 후 접근

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 (집합 자료형) 에 대해
📌 기본 개념부터
⚠️ 실수하기 쉬운 트랩,
💡 딕셔너리와의 차이점,
🧠 실전 활용까지 완전히 해부해서 설명해줄게.


✅ 1. set이란?

s = {1, 2, 3}
  • 중복을 허용하지 않는 자료형
  • ✅ 순서가 없다 (정렬되지 않음)
  • ✅ 내부적으로 해시 기반이라서 탐색 속도가 빠름 (O(1))
  • setmutable (수정 가능), 내부에 immutable 타입만 저장 가능

📌 2. set 선언 방법

# 중괄호 사용
s = {1, 2, 3}

# set() 함수 사용 (빈 집합 만들 때는 이걸 써야 함!)
empty = set()        # ✅ OK
wrong_empty = {}     # ❌ 이건 dict임!
print(type(set()))   # <class 'set'>
print(type({}))      # <class 'dict'>

✅ 3. 주요 기능/연산자

연산/메서드설명예시
a & b or a.intersection(b)교집합set1 & set2
`abora.union(b)`합집합`set1set2`
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()

✅ 4. 자주 하는 실수 (트랩)

🔥 트랩 1: {}set이 아니라 dict

x = {}
print(type(x))   # ❌ dict

✅ 빈 집합은 set() 으로 만들어야 함


🔥 트랩 2: remove() vs discard() 차이

s = {1, 2, 3}

s.remove(4)   # ❌ KeyError
s.discard(4)  # ✅ 아무 일도 안 일어남

→ 실무에서는 안전하게 discard() 많이 사용


🔥 트랩 3: 리스트나 딕셔너리는 set 안에 못 들어감

s = set()
s.add([1, 2])   # ❌ TypeError (list는 mutable이라 hash 불가)

set은 해시 가능한 값만 저장 가능
int, str, tuple은 가능 / list, dict는 불가능


✅ 5. 중복 제거 용도로 자주 사용

nums = [1, 2, 2, 3, 1]
unique = set(nums)
print(list(unique))  # ✅ [1, 2, 3] (순서는 보장 안 됨)

✅ 6. 집합 연산 예시

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} (대칭차)

✅ 7. set vs dict 차이

구분setdict
구조key만 존재key → value
목적값의 존재 유무 체크키-값 매핑
빈 선언 방식set(){}
사용 예중복 제거, 빠른 탐색데이터 저장, 조회
내부 구조해시 테이블해시 테이블

✅ 8. 실전 예제: 두 리스트의 공통 요소 찾기

a = ['apple', 'banana', 'cherry']
b = ['banana', 'kiwi', 'apple']

result = list(set(a) & set(b))
print(result)  # ✅ ['banana', 'apple']

✅ 9. 실전 예제: 방문 기록 중복 제거

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의 요소가 될 수 없음

0개의 댓글