Go언어 입문 #6

학미새🐥·2022년 3월 17일
0
post-custom-banner

포인터

C언어와 닮은 언어이기 때문에 pointer 개념이 사용된다.

포인터

: 메모리 주소를 값으로 갖는 타입

var a int   //변수 a는 메모리 공간의 100번지(변수의 시작주소)를 '가리키고' 있다.
var p *int  //포인터 변수 선언 
p = &a  //a의 메모리 주소를 포인터 변수 p에 대입
  • p도 포인터 변수이기 때문에 자신만의 메모리 공간이 있다.
  • &변수명 : 변수의 주소값을 의미
  • 포인터 선언 시 : 값으로 가지고 있는 주소값의 공간을 차지하고 있는 해당 변수가 값으로 가지는 자료형도 함께 명시 (*자료형)
*p = 20
  • *포인터명 : 포인터가 가지고있는 값이 가리키고 있는 공간
    ex) p의 값이 100일 때, 메모리 공간의 100번지에 있는 변수공간의 값으로 20을 대입한다

ex)

func main() {
	var a int = 500
	var p *int

	p = &a

	fmt.Printf("p의 값: %p\n", p) //p의 값: 0xc0000be000
	fmt.Printf("p가 가리키는 메모리의 값: %d\n", *p)  //p가 가리키는 메모리의 값: 500

	*p = 100  //a = 100과 같은 것
	fmt.Printf("a의 값: %d\n", a)  //a의 값: 100
}
  • %p : 포인터의 주소값을 가짐. 16진수로 나타남

포인터 변수의 기본값 : nil

var p *int  // p = nil
if p != nil {
	// p가 nil이 아닌, 정상적인 메모리 주소를 가리키고 있을 때 실행
}
  • 포인터 변수를 선언할 때 초기화를 하지 않으면 default 값으로 설정된다.
  • 그 default값이 nil
  • 0과 같은 의미이지만, 0으로 사용해서는 안된다 -> p타입 : *int, 0타입 : int로 다르기 때문.

포인터를 쓰는 이유

type Data struct {
	value int
	data  [200]int
}

func ChangeData(arg Data) {
	arg.value = 999
	arg.data[100] = 999
}

func main() {
	var data Data

	ChangeData(data)
	fmt.Printf("value = %d\n", data.value)  //value = 0
	fmt.Printf("data[100] = %d\n", data.data[100])  //data[100] = 0
}

999라는 값을 대입했음에도 불구하고 0(default값이 나오는 이유)!

go언어에서 대입연산자 기능 : r-value의 메모리 공간크기 만큼의 l-value공간에 복사한다

따라서 복사한다는 것은, 결국 양쪽의 메모리 크기는 같지만 엄연히 다른 공간이라는 뜻.
그렇기 때문에 위의 예제에서
ChangeData(data)로 함수를 호출할 때, 전달되는 data는 실제 main함수 내에서 선언한 data 구조체가 아닌, 복사본이 전달되는 것이다.
ChangeData 함수가 실행되는 동안에는 복사본구조체의 값을 999로 바꾸는 것이기 때문에, 함수에서 main함수로 돌아온 뒤 원본 구조체의 값을 출력하면 변함없이 0이 나오는 것이다.

이를 해결하기 위해서는 포인터만 도입하면 된다.
을 복사하는 것이 아닌, 주소값을 복사하는 것이다.

type Data struct {
	value int
	data  [200]int
}

func ChangeData(arg *Data) {   // *추가!
	arg.value = 999
	arg.data[100] = 999
}

func main() {
	var data Data

	ChangeData(&data)   // &추가! 
	fmt.Printf("value = %d\n", data.value)  //value = 0
	fmt.Printf("data[100] = %d\n", data.data[100])  //data[100] = 0
}
  • * 추가 : 함수의 인자로 Data 구조체타입의 주소값을 받는다.
  • & 추가 : 함수를 호출할 때 인자로 data 구조체의 주소값을 보낸다.
    이렇게 될 경우,
    ChangeData 함수가 하는 일은
    Data 구조체 타입의 주소값을 인자로 받아,
    그 주소의 메모리 공간에 있는 값을 999로 설정하는 것
    이기 때문에,
    main함수와 ChangeData함수에서 동일한 메모리 공간을 다루게 되는 것이다

여기서 짚고 넘어가야할 사항,
그렇다면 ChangeData 함수 내부문들은 아무것도 고치지 않아도 되는가?
arg가 Data구초제타입의 변수였다가, Data구조체타입 변수의 주소값을 가지는 포인터 변수로 바뀌었는데, arg.value, arg.data[100]는 그대로 사용해도 될까?
=> Go언어에서는 이를 허용한다!
정석대로 사용하자면 (*arg).value, (*arg,data[100])으로 변경해야,
arg라는 포인터변수가 갖는 주소값이 가리키는 메모리 공간 내의 값에 접근하게 되는데,
그냥 포인터변수명 뒤에 점만 찍으면 알아서 의미해석을 해준다 ㅎㅎ

구조체 포인터 초기화

익숙한 형태의 구조체 포인터 초기화

var data Data
var p *Data = &data

-> 이런 방식으로 한줄로 줄일 수 있다.

var p *Data = &Data{}
  • Data{} : 구조체+빈중괄호는 구조체 생성자를 의미한다. 아무 값도 초기화하지 않았기 때문에 default값을 가지게 된다.
    즉,
  1. 구조체를 만들어서 구조체 변수를 선언한다
  2. 만든 구조체 변수의 주소값을 포인터 변수에 대입한다
    이 두 단계를
  3. 구조체를 만들자마자 해당 주소값을 포인터 변수에 대입한다
    의 한 단계로 줄여주는 것이다.
    구조체가 만들어지는 메모리 공간의 이름을 굳이 붙여주냐 마냐의 차이!

Instance

: 메모리에 할당된 데이터의 실체
1. "Data 인스턴스 하나가 만들어졌고, 포인터 변수 p가 가리킨다"

var p *Data = &Data{}

  1. "Data 인스턴스 하나포인터 변수가 가리킨다"
var p1 *Data = &Data{}
var p2 *Data = p1
var p3 *Data = p1

  1. "data1, data2, data3라는 인스턴스 세개"
var data1 Data
var data2 Data = data1
var data3 Data = data1

new() 내장함수

: 구조체를 생성할 때 사용할 수 있다

p1 := &Data{}
var p2 = new(Data)

1과 2는 같은 기능을 한다.

1은 중괄호 내에 초기값을 넣을 수 있다.
ex : &Student{"Lydia", 23}
2는 초기값을 넣을 수 없다. 무조건 default 값으로 초기화!

profile
뭐든 다해보려는 공대생입니다
post-custom-banner

0개의 댓글