백엔드, 기타/Golang

Golang 3. 1)포인터와 구조체

데브힐러 2020. 8. 19. 22:39
반응형

1. 포인터

 

  var ptrValue *int 과 같이 선언만 한 빈 포인터형 변수는 바로 사용할 수 없다. new 함수로 메모리를 할당해서 사용한다.

 

  •   선언
 var numPtr *int = new(int) //포인터 선언 → new 함수로 메모리에 공간 할당
	*numPtr = 1 //*를 사용해 포인터 역참조 하여 값 1 대입
	fmt.Println(*numPtr) //1 출력

 

 

  • 변수 앖에 &를 붙이면 해당 변수의 메모리 주소를 뜻한다.
 var num int = 1
	var numPtr *int = &num

	fmt.Println(numPtr)  //동일한 메모리 주소 출력
	fmt.Println(&num)   //동일한 메모리 주소 출력

 

 

  • 일반 자료형과 포인터 변수의 매개변수의 영향 비교

 

     1) 일반 자료형인 매개변수 n으로 1을 넘겨주었다. compare 함수 scope 안에 지역변수 n = 3이 영향을 미치지 않는다.

         1 출력🐶

func compare(n int) {
	n = 3
}
func main(){
	var n int = 1
	compare(n)
	fmt.Println(n) //1 출력
}

 

     2) 포인터형 매개변수n으로 1을 넘겨주었다. 

         3 출력 🤨

func compare(n *int) {
	*n = 3
}
func main(){
	var n int = 1
	compare(&n)
	fmt.Println(n) //3 출력
}

 

 

 

2. 구조체

 

  • 선언
type Rectangle struct {
	width int
	height int
}
func main (){
   //구조체 인스턴스 생성
    var rect Rectangle
    
    //지역 변수 형태가 아닌 포인터에 메모리 공간 할당하는 법
	var rect1 *Rectangle
	rect1 = new(Rectangle)
	rect2 := new(Rectangle) //구조체 포인터 선언과 메모리 할당

	//구조체 인스턴스 생성과 동시에 값 초기화하기
	var rect4 Rectangle = Rectangle{10, 20}
	rect5 := Rectangle{45, 62}
	rect6 := Rectangle{width: 22, height: 53}
}

 

 

  • 구조체 생성자 패턴 활용하기

new 함수로 구조체의 메모리를 할당하는 동시에 값을 초기화 하는 방법은 없다. 그라너, 다음과 같은 패턴을 사용하여 다른 언어의 생성자를 흉내낼 수 있다.

 

type Rectangle struct {
	width int
	height int
}
func NewRectangle(width, height int) *Rectangle{
	return &Rectangle{width, height}
}
func main (){
	rect := NewRectangle(22, 44)
	fmt.Println(rect)
	fmt.Println(*rect)
	fmt.Println(&rect)
	/*출력
	&{22 44}
	{22 44}
	0xc00000e028*/
}

 

 

코드를 줄여 다음과 같은 방식도 가능하다.

rect := &Rectangle{20, 10} //구조체를 초기화한 뒤 메모리 주소를 대임
fmt.Println(rect) // &(20, 10)

Golang에서 지역 변수를 계속 참조하고 있다면 스코프를 벗어나더라도 변수가 해제되지 않는다.

 

 

 #사각형의 넓이 구하기

type Rectangle struct {
	width int
	height int
}
func rectangleArea(rect *Rectangle) int { //매개변수로 구조체 포인터를 받음
	return rect.width * rect.height
}
func main (){
	rect := Rectangle{30, 40}
	area := rectangleArea(&rect) //구조체의 포인터를 넘
	fmt.Println(area)
}

 

 

 함수의 매개변수에 구조체 포인터가 아닌 일반적인 형태(구조체 인스턴스)로 넘겨주면 값이 모두 복사되므로 주의해야 한다.

func rectangleScaleA(rect *Rectangle, factor int) {
	rect.width = rect.width * factor
	rect.height = rect.height * factor
	//포인터이므로 원래의 값이 변경
}

func rectangleScaleB(rect Rectangle, factor int) {
	rect.width = rect.width * factor
	rect.height = rect.height * factor
	//값이 복사되었으므로 원래의 값에는 영향을 미치지 않음
}

func main (){
	rect1 := Rectangle{30, 40}
	rectangleScaleA(&rect1, 10) //구조체의 포인터를 넘김
	fmt.Println(rect1)

	rect2 := Rectangle{30, 40}
	rectangleScaleB(rect2, 10)
	fmt.Println(rect2)

	/*출력
	{300 400}
	{30 40}*/
}

 

 

 #구조체에 메서드 연결하기

 func (리시버명 *구조체_타입) 함수명()

 리턴값_자료형{}

func (rect *Rectangle) area() int { //리시버 변수 정의(연결 할 구조체 지정)
	return rect.width * rect.height
	//리시버 변수를 사용하여 현재 인스턴스에 접근할 수 있음
}

func main(){
	rect := Rectangle{10, 20}
	fmt.Println(rect.area())
}

 메서드 안에서는 리시버 변수를 통해 현재 인스턴스의 값에 접근할 수 있다. 그리고 구조체 인스턴스에 .을 사용하여 연결된 메서드를 호출한다.

 

 리시버로 정의한 변수에는 메서드가 포함된 구조체의 인스턴스 포인터가 들어온다. 따라서 리시버 변수를 통해서 현재 인스턴스의 필드 값을 가져오거나 변결 할 수 있다.

 즉, 리시버는 C++의 this포인터 또는 Java의 this 키워드와 비슷하게 기능한다.

 

 

 

 #구조체 임베딩

 구조체에서 임베딩을 사용하면 상속과 같은 효과를 낼 수 있다.

 1) 학생이 사람을 가지고 있는 Has-a관계

//사람과 학생 구조체
type Person struct {
	name string
	age int
}

func (p *Person) greeting() {
	fmt.Println("hello~~")
}

type Student struct {
	p Person //필드 명
	school string
	grade int
}

func main() {
	var s Student
	s.p.greeting() // hello~~
}

 

 

 2) 학생이 사람이 동일한 Is-a 관계

type Person struct {
	name string
	age int
}

func (p *Person) greeting() {
	fmt.Println("hello~~")
}

type Student struct {
	Person // 필드명 없이 타입만 선언하면 포함(Is-a)
	school string
	grade int
}

func main() {
	var s Student
	s.Person.greeting() // hello~~
	s.greeting() // hello~~
}

 

 

 임베디드 오버라이딩


func (p *Student) greeting() { //메서드 오버라이딩
    fmt.Println("how are you~~")
}

s.greeting() // how are you~~

 

 

 

반응형