값에 의한 전달 vs 참조에 의한 전달
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 21번째.
Go는 값에 의한 전달(pass-by-value) 언어입니다. 변수를 함수에 전달할 때, Go는 해당 값의 복사본을 생성합니다. 즉, 함수 내부에서의 변경 사항은 원래 변수에 영향을 미치지 않습니다.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (변경되지 않음)
}구조체에도 동일하게 적용됩니다. 구조체를 함수에 전달하면 모든 필드가 복사되므로, 함수 내부에서의 수정 사항은 손실됩니다.
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (변경되지 않음)
}원본 값을 수정하려면 대신 포인터를 전달하세요. 포인터 자체는 복사되지만 여전히 동일한 메모리 주소를 가리키므로 원본 데이터를 변경할 수 있습니다.
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (수정됨!)
}원본 값을 수정해야 하거나, 비용이 많이 드는 복사를 피하기 위해 큰 구조체를 전달할 때는 포인터를 사용하세요. 의도치 않은 변경으로부터 원본 데이터를 보호하고 싶을 때는 값을 사용하세요.
챌린지
쉬움구조체를 값으로 전달하는 것과 포인터로 전달하는 것의 차이를 보여주는 쇼핑카트 시스템을 구축해 보겠습니다. 포인터 매개변수만이 원본 데이터를 수정할 수 있는 이유를 직접 확인하게 될 것입니다.
코드는 두 개의 파일로 구성됩니다:
cart.go:Name(string),Price(float64),Quantity(int) 필드를 가진CartItem구조체를 정의합니다. 수량을 업데이트하려는 두 개의 함수를 만듭니다:UpdateQuantityByValue는CartItem(값 전달)과 새로운 수량을 인자로 받아, 아이템의 수량을 새로운 값으로 설정합니다.UpdateQuantityByPointer는*CartItem(포인터 전달)과 새로운 수량을 인자로 받아, 아이템의 수량을 새로운 값으로 설정합니다.
CartItem에 가격과 수량을 곱한 값을 반환하는Total메서드를 만듭니다.main.go: 입력으로부터 아이템 상세 정보를 읽고, CartItem을 생성한 다음 두 가지 업데이트 방식을 시연합니다. 먼저 값 전달 함수로 업데이트를 시도하고 결과를 보여준 뒤, 포인터 전달 함수로 업데이트하고 결과를 다시 보여줍니다.
다음 입력이 제공됩니다:
- 1행: 아이템 이름
- 2행: 가격 (float)
- 3행: 초기 수량 (integer)
- 4행: 새로 설정할 수량 (integer)
출력 형식:
Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]예를 들어, Laptop, 999.99, 1, 3이 주어지면 출력은 다음과 같아야 합니다:
Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97값 업데이트 후에는 수량이 1로 유지되지만(함수가 복사본을 수정함), 포인터 업데이트 후에는 3으로 변경되는 것(함수가 원본을 수정함)을 확인하세요. 합계는 소수점 둘째 자리까지 표시합니다.
치트 시트
Go는 **값에 의한 전달(pass-by-value)** 언어입니다. 변수를 함수에 전달할 때, Go는 해당 값의 복사본을 생성하므로 함수 내부에서의 변경 사항은 원래 변수에 영향을 주지 않습니다.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (변경되지 않음)
}구조체에도 동일하게 적용됩니다. 구조체를 함수에 전달하면 모든 필드가 복사됩니다:
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (변경되지 않음)
}원래 값을 수정하려면 **포인터**를 전달하세요. 포인터 자체는 복사되지만, 여전히 동일한 메모리 주소를 가리킵니다:
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (수정됨!)
}원래 값을 수정해야 하거나 비용이 많이 드는 복사를 피하기 위해 큰 구조체를 전달할 때는 포인터를 사용하세요. 의도치 않은 변경으로부터 원래 데이터를 보호하려면 값을 사용하세요.
직접 해보기
package main
import (
"fmt"
)
func main() {
// 입력 읽기
var name string
var price float64
var initialQty, newQty int
fmt.Scanln(&name)
fmt.Scanln(&price)
fmt.Scanln(&initialQty)
fmt.Scanln(&newQty)
// TODO: 입력된 값으로 CartItem 생성
// TODO: 다음 형식을 사용하여 초기 상태 출력:
// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)
// TODO: UpdateQuantityByValue를 사용하여 수량 업데이트 시도
// 그런 다음 결과 출력 (수량이 변하지 않음에 유의)
// TODO: UpdateQuantityByPointer를 사용하여 수량 업데이트
// 그런 다음 결과 출력 (수량이 변함에 유의)
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서