본문 바로가기

Go

[묘공단] Tucker의 Go 언어 프로그래밍 18장~19장

  • 18장 슬라이스
  • 19장 메서드

 

18장 슬라이스

 

배열 : var a [4] int

 

슬라이스 : make([]byte,5)

 

 

 

 

 

  • 아래 코드는 배열과 슬라이스의 차이, 슬라이스의 선언방법이다. 
var array [10]int // 배열선언
var slice []int // slice선언(길이가 0인 슬라이스)

var slice1 = []int{1,2,3} // slice선언(길이가 3인 슬라이스)
var slice2 = []int{1, 5:2 , 10:3} // {1,0,0,0,0,2,0,0,0,0,3}



var array = [...]int{1,2,3} // array
var slice = []int{1,2,3}  	// slice


var slice = make([]int, 3)
slice[1] = 5



var slice = []int{1,2,3}  	// slice
for i := 0; i<len(slice); i++) {
	slice[i] += 10; // 각 요소에 10 더하기
}

for i,v := range slice {
	slice[i] = v * 2	// 각 요소에 2곱하기
}

// append를 사용하여 값을 추가할수있다.
slice2 := append(slice,4)
fmt.Println(slice2)

// append를 사용하여 동시에 여러값을 추가할수있다.
slice3:= append(slice,5,6,7,8)
fmt.Println(slice3)

 

 

  • 아래소스에서 slice는 길이가 0이기때문에 1번째 위치에 값을 넣으면 오류가 발생한다.
//ch18/ex18.1/ex18.1.go
package main

import "fmt"

func main() {
	var slice []int

	if len(slice) == 0 { // ❶ slice 길이가 0인지 확인
		fmt.Println("slice is empty", slice)
	}

	slice[1] = 10 // ❷ 에러 발생
	fmt.Println(slice)
}

 

 

  • 배열과 달리 Slice에서는 append를 사용하여 요소를 추가할수있다.
//ch18/ex18.2/ex18.2.go
package main

import "fmt"

func main() {

	var slice = []int{1, 2, 3} // ❶ 요소가 3개인 슬라이스

	slice2 := append(slice, 4) // ❷ 요소 추가

	fmt.Println(slice)
	fmt.Println(slice2)
}

 

 

 

  • append 가변인자를 받기때문에 동시에 여러요소를 추가할수있다.
//ch18/ex18.3/ex18.3.go
package main

import "fmt"

func main() {
	var slice []int

	for i := 1; i <= 10; i++ { // ❶ 요소를 하나씩 추가
		slice = append(slice, i)
	}

	slice = append(slice, 11, 12, 13, 14, 15) // ❷ 한 번에 여러 요소 추가
	fmt.Println(slice)
}

 

 

  • Slice는 내부구조가 아래와 같다. 실제 데이타가 들어있는 Data와 요소개수인 Len, 실제배열의 길이인 Cap으로 구성되어있다.

 

 

  • 아래의 소스에서 changeArray, changeSlice 두 함수 값을 복사한다. 
  • array는 복사된 배열의 값을 변경해도 원본에는 영향을 주지 않지만 slice는 해당 데이타의 주소를 넘겨주기때문에 값을 변경하면 원본의 배열에도 변경이 된다. 
  • changArray에서는 해당 함수가 호출될때 배열이 복사되어전달된다.(40바이트)
  • changeSlice에서는 해당 함수가 호출될때 Slice가 복사되는데, 위에서 보면 알겠지만 24바이트 고정된 사이즈이다. 
//ch18/ex18.4/ex18.4.go
package main

import "fmt"

func changeArray(array2 [5]int) { // ❶ 배열을 받아서 세 번째 값 변경
	array2[2] = 200
}

func changeSlice(slice2 []int) { // ❷ 슬라이스를 받아서 세 번째 값 변경
	slice2[2] = 200
}

func main() {
	array := [5]int{1, 2, 3, 4, 5}
	slice := []int{1, 2, 3, 4, 5}

	changeArray(array)
	changeSlice(slice)

	fmt.Println("array:", array)
	fmt.Println("slice:", slice)
}

 

 

  • append()는 슬라이스에 요소를 추가한 새로운 슬라이스를 반환한다.  (추가될 충분한 빈공간이 있으면 새로운슬라이스를 반환하지 않는다.) 
  • 아래 소스를 보면 append를 호출하여 slice1, slice2가 같은 데이타를 공유하고 있다.
  • 이럴경우 한쪽을 바꾸면 다른쪽도 바뀌게 된다.
//ch18/ex18.5/ex18.5.go
package main

import "fmt"

func main() {
	slice1 := make([]int, 3, 5) // ❶ len:3 cap:5 슬라이스를 만듭니다

	slice2 := append(slice1, 4, 5)
	// cap() 함수를 이용해 슬라이스 capacity 값을 알 수 있습니다.
	fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))

	slice1[1] = 100 // ❷ slice2까지 바뀝니다.

	fmt.Println("After change second element")
	fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))

	slice1 = append(slice1, 500) // ❸ 역시 slice2까지 바뀝니다.

	fmt.Println("After append 500")
	fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))
}

 

 

 

  • 아래의 소스에서는 위와 비슷하지만, 이번에는 append를 호출하였는데 충분한 공간이 없어서 새로운 저장소를 추가하여 slice2에 할당한다.
  • 그래서 서로 각각 다른 저장소를 가지고 있기때문에 값을 바꾸더라도 다른쪽에 영향을 주지 않는다.
//ch18/ex18.6/ex18.6.go
package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3} // ❶ len:3 cap:3 슬라이스 생성

	slice2 := append(slice1, 4, 5) // ❷ append() 함수로 요소 추가

	fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))

	slice1[1] = 100 // ❸ slice1 요솟값 변경

	fmt.Println("After change second element")
	fmt.Println("slice:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))

	slice1 = append(slice1, 500) // ➍ slice1 요솟값 변경

	fmt.Println("After append 500")
	fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
	fmt.Println("slice2:", slice2, len(slice2), cap(slice2))
}

 

 

  • 아래는 슬라이싱에 관련된  예제이다. 슬라이싱은 배열의 일부를 집어내는 기능을 말한다.
  • array[1:2]명령으로 array의 2번째 값만 slice하였다. (len:1 cap:4) 
  • 같은 공간을 두 변수가 같이 사용하기때문에 한쪽의 값을 바꾸면 다른쪽도 값이 변경된다. 
//ch18/ex18.7/ex18.7.go
package main

import "fmt"

func main() {
	array := [5]int{1, 2, 3, 4, 5}

	slice := array[1:2] // ❶ 슬라이싱

	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))

	array[1] = 100 // ❷ array의 두 번째 값 변경

	fmt.Println("After change second element")
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))

	slice = append(slice, 500) // ❸ slice에 값 추가

	fmt.Println("After append 500")
	fmt.Println("array:", array)
	fmt.Println("slice:", slice, len(slice), cap(slice))
}

 

 

 

  • 두 슬라이스가 서로 같은 메모리를 사용하는 문제를 방지 하기 위해서 아래 소스처럼 make()를 사용하여 slice1과 같은 길이의 슬라이스를 생성하고 모든 요소를 복사하여, 두 슬라이스가 서로 다른 메모리를 가리키도록 만들수있다.
//ch18/ex18.8/ex18.8.go
package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}

	slice2 := make([]int, len(slice1)) // ❶ slice1과 같은 길이의 슬라이스 생성

	for i, v := range slice1 { // ❷ slice1의 모든 요소값 복사
		slice2[i] = v
	}

	slice1[1] = 100 // ❸ slice1 요솟값 변경
	fmt.Println(slice1)
	fmt.Println(slice2)
}

 

 

 

  • 위의 방식이 번거롭다면 아래의 소스처럼 copy()를 사용하여 slice를 복사할수있다.
//ch18/ex18.9/ex18.9.go
package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := make([]int, 3, 10) // ❶ len:3, cap:10 슬라이스
	slice3 := make([]int, 10)    // ❷ len:10, cap:10 슬라이스

	cnt1 := copy(slice2, slice1) // ❸ slice1을 slice2에 복사합니다.
	cnt2 := copy(slice3, slice1) // ❹ slice1을 slice3에 복사합니다.

	fmt.Println(cnt1, slice2)
	fmt.Println(cnt2, slice3)
}

 

 

 

 

  • 만약 slice의 중간에 값을 삭제하려면 어떻게 해야할까?
  • 아래의 소스처럼 2번째 인덱스를 삭제하려면 3번째 이상의 모든 요소를 앞자리에 한개씩 앞당기고, 슬라이스 기능을 사용해서 마지막 요소를 잘라낸다.
//ch18/ex18.10/ex18.10.go
package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5, 6}
	idx := 2 // 삭제할 인덱스

	for i := idx + 1; i < len(slice); i++ { // ❶ 요소 앞당기기
		slice[i-1] = slice[i]
	}

	slice = slice[:len(slice)-1] // ❷ 슬라이스로 마지막 값을 잘라줍니다.

	fmt.Println(slice)
}

 

append를 사용하여 아래와 같이 한줄로 처리할수있다.

slice = append(slice[:2], slice[3:]...)

 

 

  • 슬라이스의 중간에 새로운 값을 넣으려면 어떻게 해야할까?
  • 아래 소스처럼 맨뒤에 0을 추가하고, 추가하려는 위치에서부터 맨뒤까지 값을 하나씩 옮겨주고, 맨마지막에 중간의 빈공간에 새로운 값을 넣는다. 
//ch18/ex18.11/ex18.11.go
package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5, 6}

	// ❶ 맨 뒤에 요소 추가
	slice = append(slice, 0)

	idx := 2 // 추가하려는 위치

	// ❷ 맨 뒤부터 추가하려는 위치까지 값을 하나씩 옮겨줍니다.
	for i := len(slice) - 2; i >= idx; i-- {
		slice[i+1] = slice[i]
	}

	// ❸ 값 변경
	slice[idx] = 100

	fmt.Println(slice)
}

 

 

위의 소스를 아래와 같이 한줄로 처리할수있다.

slice = append(slice[:2], append([]int{100}, slice[2:]...)...)

 

 

  • 아래는 슬라이스의 정렬하는 예제이다. 
//ch18/ex18.12/ex18.12.go
package main

import (
	"fmt"
	"sort"
)

func main() {
	s := []int{5, 2, 6, 3, 1, 4} // ❶ 정렬되지 않은 슬라이스
	sort.Ints(s)                 // ❷ 정렬
	fmt.Println(s)
}

 

 

  • 정렬하려는 슬라이스의 멤버가 구조체라면 Len(), Less(), Swap()메서드를 직접 구현해야한다. 
//ch18/ex18.13/ex18.13.go
package main

import (
	"fmt"
	"sort"
)

type Student struct {
	Name string
	Age  int
}

// ❶ []Student의 별칭 타입 Students
type Students []Student

func (s Students) Len() int           { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Age < s[j].Age } // ❷ 나이 비교
func (s Students) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

func main() {
	s := []Student{
		{"화랑", 31}, {"백두산", 52}, {"류", 42},
		{"켄", 38}, {"송하나", 18}}

	sort.Sort(Students(s)) // ❸ 정렬
	fmt.Println(s)
}

 

 

 

19장 메서드

 

  • 메서드는 일반함수와 달리 함수명앞에 리시버를 지정하여 , 해당 리시버타입에 포함된다.

  • 아래 소스는 일반함수와 메서드 함수의 호출하는 방법에 대한 예제이다.
  •  withdrawMethod() 메서드를 선언하여 account 의 필드에 접근할때처럼 . 을 사용하여 해당 메서드를 호출할수있다.
//ch19/ex19.1/ex19.1.go
package main

import "fmt"

type account struct {
	balance int
}

func withdrawFunc(a *account, amount int) { // 일반 함수 표현
	a.balance -= amount
}

func (a *account) withdrawMethod(amount int) { // 메서드 표현 ❶
	a.balance -= amount
}

func main() {
	a := &account{100} // balance가 100인 account 포인터 변수 생성

	withdrawFunc(a, 30) // 함수 형태 호출

	a.withdrawMethod(30) // 메서드 형태 호출 ❷

	fmt.Printf("%d \n", a.balance)
}

 

 

  • int타입의 별칭인 myInt를 선언하여, 메서드를 가질수있도록 하였다.
//ch19/ex19.2/ex19.2.go
package main

import "fmt"

// ❶ 사용자 정의 별칭 타입
type myInt int

// ❷ myInt 별칭 타입을 리시버로 갖는 메서드
func (a myInt) add(b int) int {
	return int(a) + b
}

func main() {
	var a myInt = 10       // myInt 타입 변수
	fmt.Println(a.add(30)) // ❸ myInt 타입의 add() 메서드 호출
	var b int = 20
	fmt.Println(myInt(b).add(50)) // ❹ int 타입을 타입변환
}

 

 

  • 아래의 소스는 메서드를 사용하는 3가지 방법에 대한 예제이다.
  • 포인터 타입 메서드
    • 해당 메서드가 호출될때 account 의 주소값이 복사된다. 
    • 해당 메서드안에서 account의 값을 변경하면 해당 메서드를 호출한 account의 값도 변경된다. 왜냐하면 서로 같은 메모리공간을 공유하기 때문이다.
  • 값 타입 메서드
    • 해당 메서드가 호출될때 account의 복사본 전달된다. 그래서 해당 메서드안에서 어떤작업을 하더라도, 원본 account의 값은 변하지 않는다.
  • 변경된 값을 반환하는 값 타입 메서드
    • 해당 메서드가 호출될때 account의 복사본이 전달되고, 해당 복사본이 사용된후에 다시 해당 복사본의 복사본이 리턴된다. 

 

//ch19/ex19.3/ex19.3.go
package main

import "fmt"

type account struct {
	balance   int
	firstName string
	lastName  string
}

// 포인터 타입 메서드
func (a1 *account) withdrawPointer(amount int) {
	a1.balance -= amount
}

// 값 타입 메서드
func (a2 account) withdrawValue(amount int) {
	a2.balance -= amount
}

// 변경된 값을 반환하는 값 타입 메서드
func (a3 account) withdrawReturnValue(amount int) account {
	a3.balance -= amount
	return a3
}

func main() {
	var mainA *account = &account{100, "Joe", "Park"}
	mainA.withdrawPointer(30)
	fmt.Println(mainA.balance) // 70 이 출력

	mainA.withdrawValue(20)    // 포인터 변수 값타입 메서드 호출 - ①
	fmt.Println(mainA.balance) // 여전히 70이 출력

	var mainB account = mainA.withdrawReturnValue(20)
	fmt.Println(mainB.balance) // 50이 출력

	mainB.withdrawPointer(30)  // 값 변수 포인터타입 메서드 호출 - ②
	fmt.Println(mainB.balance) // 20이 출력
}