- 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이 출력
}
'Go' 카테고리의 다른 글
[묘공단] Tucker의 Go 언어 프로그래밍 22장~23장 (0) | 2024.02.03 |
---|---|
[묘공단] Tucker의 Go 언어 프로그래밍 20장~21장 (0) | 2024.02.03 |
[묘공단] Tucker의 Go 언어 프로그래밍 15장~17장 (0) | 2024.01.29 |
[묘공단] Tucker의 Go 언어 프로그래밍 12장~14장 (0) | 2024.01.29 |
[묘공단] Tucker의 Go 언어 프로그래밍 8장~11장 (0) | 2024.01.23 |