학부생 시절, C++을 배우면서 시험 중 제일 힘들었던 항목이 포인터가 아니였을까 싶다.
‘*’ 이 표시가 어찌나 나를 헷갈리게 하던지… 지금와서야 포인터의 중요성을 깨닫고 있다.
일단 포인터를 배우기 전에 구조체, 일반 함수, 메서드의 개념을 정리해봤다.
// Human이라는 구조체를 선언하며, Human의 구조체에는 Name, Age, Salary가 있음
type Human struct {
Name string
Age int
Salary int
}
// 일반 함수 printHumanInfo를 선언하며, 해당 함수는 Human 구조체를 인자로 받고 info에 전달한다.
func printHumanInfo(info Human) {
fmt.Println(info.Name)
fmt.Println(info.Age)
fmt.Println(info.Salary)
}
// 메서드 humanInfo를 선언하며, (h Human)은 메서드의 리시버를 의미하고 Human이라는 구조체를 h라는 변수로 사용한다.
// 여기서 Human은 구조체의 종류를 의미할 뿐이지, 실제 값이 담긴 구조체가 아님
func (h Human) humanInfo() {
fmt.Println(h.Name)
fmt.Println(h.Age)
fmt.Println(h.Salary)
}
// main 함수로 techdog이라는 Human 구조체를 만들며 Name, Age, Salary를 설정한다.
// 일반함수와 메서드의 사용법은 아래처럼 나뉜다.
func main() {
techdog := Human{Name: "techdog", Age: 5, Salary: 1000}
printHumanInfo(techdog)
techdog.humanInfo()
}
위의 일반함수와 메서드의 쓰임새는 GPT님께서 정리해주셨다.
사용 기준 | 메서드 (func (h Human) methodName() ) | 일반 함수 (func functionName() ) |
---|
구조체의 필드를 다루는가? | ✅ 다룬다면 메서드 사용 | ❌ 다루지 않는다면 일반 함수 |
구조체와 강하게 연관된 동작인가? | ✅ 강하게 연관된 경우 메서드 | ❌ 독립적인 기능이면 일반 함수 |
구조체의 데이터를 수정해야 하는가? | ✅ 수정해야 한다면 *Human 포인터 리시버 사용 | ❌ 데이터를 변경하지 않는다면 일반 함수 가능 |
다른 구조체에서도 재사용할 수 있는가? | ❌ 특정 구조체에만 사용될 경우 | ✅ 여러 구조체에서 공통으로 사용 가능하면 일반 함수 |
구조체 인스턴스 없이도 호출 가능한가? | ❌ 인스턴스가 필요하면 메서드 | ✅ 인스턴스 없이 호출 가능하면 일반 함수 |
자 그렇다면 위의 상황의 경우에는 메서드와, 일반 함수 중 어떤게 더 알맞을까?
목적자체가 Human 구조체에서 Name, Age, Salary 값을 출력하기 위함이기에 구조체에 의존적이라 볼 수 있다.
그 말은 즉슨 메서드로 구현을 하는게 더 알맞다라고 판단된다
다시 본론으로 돌아와서 포인터를 분석해봤다.
포인터란?
포인터는 변수의 메모리 주소를 저장하는 변수이다.
변수의 값자체를 저장하는 것이 아닌 변수의 값이 저장된 메모리의 주소를 저장한다.
위의 의미를 좀 쉽게 풀어보자면
옛날에 갔었던 야경이 좋은 카페를 다시 가기위해, “목적지”라는 변수를 설정했다.
“목적지”라는 변수에 “techdog카페 서울점”을 넣으면 목적지 자체는 “techdog카페 서울점”이 된다.
만약 이 “tecodog카페 서울점”이 “nodog카페 서울점”으로 이름이 변경돼도, “목적지”라는 변수는 계속해서 “techdog카페 서울점”만을 찾을 것이다.
그러면 “목적지”라는 변수에 “techdog카페 서울점”의 주소인 “서울 372번지”를 넣으면 어떻게 될까?
“techdog카페 서울점”이 “nodog카페 서울점”으로 변경돼도 상관없다.
왜냐하면 “목적지”는 “주소”만을 가르키고 있기때문이다.
(위의 내용은 나의 뇌피셜로 적어놓은 부분이라, 어색하거나 이상할 수 있다.)
예시를 코드로 들어보자면
func main() {
//카페를 목적지로 설정함
GoodViewCafe := "techdog카페 서울점"
Destination := GoodViewCafe
fmt.Println("현재 목적지는 " + Destination)
//카페 이름이 바뀜
GoodViewCafe = "nodog카페 서울점"
//목적지 출력
fmt.Println("현재 목적지는 " + Destination)
}
// 현재 목적지는 techdog카페 서울점
// 현재 목적지는 techdog카페 서울점
위는 카페 이름을 목적지로 설정했을 경우이다
func main() {
//카페를 목적지의 주소로 설정함
GoodViewCafe := "techdog카페 서울점"
Destination := &GoodViewCafe
fmt.Println("현재 목적지는 " + *Destination)
//카페 이름이 바뀜
GoodViewCafe = "nodog카페 서울점"
//목적지 출력
fmt.Println("현재 목적지는 " + *Destination)
}
// 현재 목적지는 techdog카페 서울점
// 현재 목적지는 nodog카페 서울점
나는 개인적으로 이렇게 이해하니 포인터의 개념을 이해하기 쉬웠다.
이러한 포인터의 개념을 가지고 어떤식으로 활용되는지는 다음 편에 작성해보겠다.