반응형
18장. 슬라이스
18.1 슬라이스
18.1.1 슬라이스 선언
- 일반적인 배열은 처음 정한 길이에서 늘어나지 않는다.
var array [10]int
- 위에서 10개 보다 많이 저장하려면 더 큰 배열을 만들어서 값을 하나씩 복사해야 한다.
- 슬라이스의 선언 방법
- 초기화 하지 않으면 길이가 0인 슬라이스가 만들어지고, 슬라이스길이를 초과하면 런타임에러가 발생
var slice []int // 배열의 개술를 적지 않는다.
- 할당되지 않은 메모리 공간에 접근해서 프로그램이 비정상 종료되는 패닉이 발생한다.
package main
import "fmt"
func main() {
var slice []int
if len(slice) == 0 { // slice 길이가 0인지 확인
fmt.Println("slice is empty", slice)
}
slice[1] = 10 // 길이가 0이니데 두 번째 접근하려고 해서 패닉 발생
fmt.Println(slice)
}
slice is empty []
panic: runtime error: index out of range [1] with length 0
goroutine 1 [running]:
main.main()
/Users/krafton/Documents/GitHub/my-playground/tucker_golang/hello/ex18.1.go:12 +0x88
- {}를 이용해 초기화
- 5번째 요소가 2, 10번째 요소가 3
var slice1 = []int{1, 2, 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} // 배열 선언
var slice = []int{1, 2, 3} // 슬라이스 선언
- make() 를 이용한 초기화
- make 함수의 첫 번째 인수에는 만들고자 하는 타입, 두 번째 인수에는 길이
var slice = make([]int, 3)
18.1.2 슬라이스 요소 접근
- 대괄호 사이에 인덱스를 써서 요소에 접근
var slice = make([]int, 3)
slice[1] = 5
18.1.3 슬라이스 순회
var slice = []int{1, 2, 3}
for i := 0; i < len(slice); i++ {
slice[i] += 10
}
for i, v := range slice { // range로 각 요소를 순회할 수 있다. 첫 번째는 인덱스, 둘째 요솟값
slice[i] = v * 2
}
18.1.4 슬라이스 요소 추가 append()
package main
import "fmt"
func main() {
var slice = []int{1, 2, 3}
slice2 := append(slice, 4)
fmt.Println(slice)
fmt.Println(slice2)
}
[1 2 3]
[1 2 3 4]
18.2.5 여러값 추가
- append를 하면 슬라이스에 값들이 추가된 뒤 만들어진 새로운 슬라이스를 반환한다.
- 기존 슬라이스에 추가하고 싶으면 기존 슬라이스에 대입해서 변경해야 한다.
slice = append(slice, 3, 4, 5, 6, 7)
18.2 슬라이스 동작 원리
- reflect 패키지의 SliceHeader 구조체를 사용해 내부 구현을 살펴볼 수 있다.
- 슬라이스 구현은 3 필드로 구성된 구조체이다.
- Data uintptr : 실제 배열을 가리키는 포인터
- Len int : 요소 개수
- Cap int : 실제 배열의 길이
- 슬라이스가 실제 배열을 가리키는 포인터를 가지고 있어서 쉽게 크기가 다른 배열을 가리키도록 변경할 수 있다.
- 슬라이스 변수 대입 시 , 배열에 비해서 메모리나 속도에 이점이 있다.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
18.2.1 make() 함수를 이용한 선언
- len이 3이고, cap이 3이다.
- 총 배열 길이가 3, 요소개수가 3
var slice = make([]int, 3)
- 배열 길이는 5, 요소 개수는 3
- 총 5개 중 3개만 사용하고 2개는 비워둔 경우
var slice2 = make([]int, 3, 5)
18.2.2 슬라이스와 배열의 동작 차이
- 아래의 경우 둘 다 배열과 슬라이스를 매개변수로 받아 3 번째 값을 200으로 변경한다.
- 출력 결과를 보면 의도와 다르다.
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)
}
array : [1 2 3 4 5]
slice : [1 2 200 4 5]
18.2.3 동작 차이의 원인
- Go에서 모든 값의 대입은 복사로 일어난다.
- 함수에 인수로 전달될 때
- 다른 변수에 대입할 때
- 값의 이동은 모두 복사이다.
- 복사는 타입의 값이 복사된다.
- 포인터는 포인터의 값인 메모리주소,
- 구조체는 구조체의 모든 필드가 복사된다.
- 배열은 배열의 모든 값이 복사된다.
- array는 메모리 공간이 다른 새로운 배열로 복사된다.
- slice가 slice2로 복사될 때는 구조체의 각 필드값이 복사되어, 똑같은 메모리 주솟값을 가지게 된다.
18.2.4 append()를 사용할 때 발생하는 예기치 못한 문제 1
- append()함수가 호출되면 먼저 슬라이스에 값을 추가할 수 있는 빈 공간이 있는지 확인한다.
- 남은 빈 공간은 실제 배열 길이 cap 에서 슬라이스 요소 개수 len 을 뺀 값이다.
- 남은 빈 공간의 개수가 추가하는 값의 개수보다 크거나 같은 경우, 배열 뒷부분에 값을 추가한 뒤 len 값을 증가시킨다.
package main
import "fmt"
func main() {
slice1 := make([]int, 3, 5)
slice2 := append(slice1, 4, 5)
// cap() 함수를 이용해 슬라이스 capacity 값을 알 수 있다.
fmt.Println("slice1:", slice1, len(slice1), cap(slice2))
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))
}
slice1: [0 0 0] 3 5
slice2: [0 0 0 4 5] 5 5
After change second element
slice1: [0 100 0] 3 5
slice2: [0 100 0 4 5] 5 5
After append 500
slice1: [0 100 0 500] 4 5
slice2: [0 100 0 500 5] 5 5
18.2.5 append()를 사용할 때 발생하는 예기치 못한 문제 2
- 빈 공간이 없을 때 값을 추가하면 다른 경우가 생긴다.
- append() 함수가 호출되면 먼저 빈 공간이 충분한지 확인한다.
- 만약 충분하지 않으면 새로운 더 큰 배열을 마련한다.
- 일반적으로 2배 크기가 된다. 그 다음 기존 배열의 요소를 모두 새로운 배열에 복사한다.
- 그리고 새로운 배열의 뒤에서 새 값을 추가한다.
- cap은 새로운 배열의 길이 값이 되고, len은 기존의 길이에 추가한 개수만큼 더한 값이 되고,
- 포인터는 새로운 배열을 가리키는 슬라이스 구조체를 반환한다.
18.3 슬라이싱
- 배열의 일부를 집어내서 슬라이스를 반환한다.
- 끝 인덱스 -1 까지를 집어낸다.
- 결과로 배열 일부를 가리키는데 , 새로운 배열이 만들어지는 게 아니라 배열의 일부를 포인터로 가리키는 슬라이스를 만들어낸다.
array[startIdx:endindex]
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)) // 크기가 4이다. cap 길이는 array의 인덱스 1에서부터 배열의 마지막 인덱스까지의 길이를 갖게된다.
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) // 슬라이스에 값 추가
fmt.Println("After append 500")
fmt.Println("array:", array)
fmt.Println("slice:", slice, len(slice), cap(slice))
}
array: [1 2 3 4 5]
slice: [2] 1 4
After change second element
array: [1 100 3 4 5]
slice: [100] 1 4
After append 500
array: [1 100 500 4 5]
slice: [100 500] 2 4
18.3.1 슬라이싱으로 배열 일부를 가리키는 슬라이스 만들기
- 슬라이스는 배열을 가리키는 포인터, len, cap 필드로 구성되어 있으며,
- 포인터는 값으로 메모리 주소를 갖기 때문에 배열의 중간을 가리킬 수 있다.
- cap은 포인터가 가리키는 배열이 할당된 크기, 즉 안전하게 사용할 수 있는 남은 배열 개수를 나타낸다.
- 배열의 총 길이에서 시작 인덱스를 밴 만큼 가지게 된다.
- 빈 공간이 남아있으므로 array[2] 요솟값이 변경된다.
18.3.2 슬라이스를 슬라이싱하기
- cap은 배열의 전체 길이에서 시작 인덱스를 뺀 값이 되지만,
- 슬라이싱할 때 인덱스를 3개 사용해서 cap까지 조절할 수 있다.
slice[ 시작인덱스 : 끝인덱스 : 최대인덱스 ]
18.4 유용한 슬라이싱 기능 활용
18.4.1 슬라이스 복제
- 같은 배열을 가리켜서 문제가 발생할 수 있는 문제를 해결하는 방법은 슬라이스를 복제하는 것이다.
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
fmt.Println(slice1)
fmt.Println(slice2)
}
- 위 내용을 append 함수로 줄이면
slice2 := append([]int{}, slice1...}
- 내장 함수 copy()를 사용하면
- 첫 번째 인수로 복사한 결과를 저장하는 슬라이스 변수를 넣고
- 두 번째 인수로 복사 대상이 되는 슬라이스 변수를 넣는다.
func copy(dst, src []Type) int
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 슬라이스
fmt.Println("slice1:", slice1)
fmt.Println("slice2:", slice2)
fmt.Println("slice3:", slice3)
cnt1 := copy(slice2, slice1) // slice1을 slice2에 복사, 슬라이스 길이 중 더 작은 개수만큼 복사
cnt2 := copy(slice3, slice1) // slice1을 slice3에 복사
fmt.Println(cnt1, slice2) // cnt는 실제로 복사한 요소 개수를 반환한다.
fmt.Println(cnt2, slice3)
}
// slice1: [1 2 3 4 5]
// slice2: [0 0 0]
// slice3: [0 0 0 0 0 0 0 0 0 0]
// 3 [1 2 3]
// 5 [1 2 3 4 5 0 0 0 0 0]
18.4.2 요소 삭제
- 슬라이스 중간 요소를 삭제하고 중간 요소 이후 값을 앞당긴다.
- 그 뒤 맨뒤 값을 지운다.
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() 함수 사용
- 처음부터 idx 하나전까지 집은 슬라이스이며 지우고자 하는 인덱스 요소는 포함하지 않는다.
- 하나 뒤의 값부터 끝까지 슬라이스 한다.
slice = append(slice[:idx], slice[idx+1:]...)
18.4.3 요소 추가
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)
}
- append 함수를 사용하면
slice = append(slice[:idx}, append([]int{100}, slice[idx:]...)...)
- 불필요한 메모리 사용이 없으려면
- 임시 슬라이스는 불필요한 메모리가 사용되므로
slice = append(slice, 0) // 요소 하나 추가
copy(slice[idx+1:], slice[idx:]) // 한칸씩 밀려서 값 복사
slice[idx] = 100 // 남은 위치에 대입
18.5 슬라이스 정렬
18.5.1 int 슬라이스 정렬
package main
import (
"fmt"
"sort"
)
func main() {
s := []int{5, 2, 6, 3, 1, 4}
sort.Ints(s)
fmt.Println(s)
}
19장 메서드
- 메서드는 리시버를 func 키워드와 함수 이름 사이에 소괄호로 명시해야 한다.
- info() 메서드이고
- (r Rabbit) 이 리시버이다.
- 구조체 변수 r은 해당 메서드에서 매개변수처럼 사용된다.
- 리시버로는 모든 로컬 타입이 가능하다.
- 로컬타입 : 해당 패키지 안에서 type 키워드로 선언된 타입.
- 패키지 내 선언된 구조체. 별칭 타입들..
func (r Rabbit) info() int {
return r.width * r.height
}
fpackage 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)
}
40
- 메서드 정의는 같은 패키지 내 어디에도 위치할 수 있다.
- 리시버 타입이 선언된 파일 안에 정의하는게 일반적인 규칙이다.
- 메서드는 해당 리시버 타입에 속한다.
- 그래서 구조체의 필드처럼 . 연산자를 사용해 해당 타입에 속한 메서드를 호출할 수 있다.
19.1.1 별칭 리시버 타입
- 별칭 타입도 리시버가 될 수 있다.
- int 같은 내장 타입도 별칭 타입을 활용해서 메서드를 가질 수 있다.
package main
import "fmt"
type myInt int // 사용자 정의 별칭 타입
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 타입을 myInt로 타입 변환
}
19.2 메서드는 왜 필요한가?
- 함수와 다를게 뭔가
- 소속이 다르다.
- 메서드는 리시버에 속한다.
- 그러니까 메서드를 사용해서 데이터와 기능을 묶을 수 있다.
- 기능과 데이터를 묶어주는 역할, 즉 응집도를 높여주는 역할을 한다.
19.2.1 객체지향 : 절차 중심에서 관계 중심으로 변화
- 객체는 데이터와 기능을 갖는 타입을 말하고, 이 타입의 인스턴스를 객체 인스턴스 object instance 라고 한다.
- 이런 객체 인스턴스들이 서로 소통하고 관계를 맺음에 따라 객체 간 관계중심으로 프로그래밍 패러다임이 변화함.
- Object Oriented Programming OOP
- Go 언어에서는 클래스와 상속을 지원하지 않고, 메서드와 인터페이스만을 지원한다.
19.3 포인터 메서드 vs 값 타입 메서드
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 출력
}
- mainA 포인터 변수의 값, 즉 메모리 주솟값만 a1으로 복사된다.
- withdrawPointer() 메서드가 호출되면 mainA 포인터 변수가 갖는 값 , 즉 메모리 주소가 복사되기 때문에
- a1과 mainA는 같은 인스턴스를 가리킨다.
- 그래서 withdrawPointer() 메서드 내부에서 a1의 balance 를 변경하면 mainA도 같은 인스턴스를 가리키기 때문에 mainA의 balance도 변경된다.
- a1과 mainA는 같은 인스턴스를 가리킨다.
- mainA의 모든 값, 즉 account 구조체의 balance, firstName, lastName 모두 a2로 복사된다.
- 호출되는 과정에서 mainA의 모든 내용이 복사되기 때문에 withdrawValue() 내의 a2 변수와 main() 함수내의 mainA 변수는 서로 다른 메모리 주소를 가지게 된다.
- mainA와 a2는 서로 다른 인스턴스이다.
- 그래서 withdrawValue() 메서드에서 a2의 balance를 변경해도 mainA의 balance는 변경되지 않는다.
- 이를 해결하려면 withdrawReturnValue() 메서드처럼 변경된 값을 다시 반환해야 한다.
- account 구조체의 모든 값이 메서드 호출 시와 결괏값 반환시 두 번 복사된다.
- mainB는 메서드 호출 이후 변경된 값을 갖는 새로운 객체를 나타낸다.
- a3, mainA, mainB 모두 다른 메모리를 주소로 갖는 서로 다른 객체이다.
20. 인터페이스
20.1 인터페이스
- 인터페이스 interface , 메서드 구현을 포함한 구체화된 객체 concrete object가 아닌 추상화된 객체로 상호작용할 수 있다.
20.1.1 인터페이스 선언
- 인터페이스도 구조체처럼 타입 중 하나이기 때문에 type을 써줘야 한다.
- 변수 선언이 가능하고, 변수의 값으로 사용할 수 있다.
type DuckInterface interface {
Fly()
Walk(distance int) int
}
- 3가지 의무사항
- 메서드는 반드시 메서드 명이 있어야 한다.
- 매개변수와 반환이 다르더라도 이름이 같은 메서드는 있을 수 없다.
- 인터페이스에서는 메서드 구현을 포함하지 않는다.
type Sample interface {
String() string
String(int) string // 에러: String 메서드명이 겹친다.
_(x int) // 에러 : 메서드는 반드시 이름이 있어야 한다.
package main
import "fmt"
type Stringer interface { // Stiringer 인터페이스 선언
String() string // 매개변수 없이 string 타입을 반환하는 메서드 포함
} // 이제 매개변수 없이 string타입을 반환하는 String() 메서드를 포함한 모든 타입은
// Stringer 인터페이스로 사용 가능.
type Student struct {
Name string
Age int
}
func (s Student) String() string { // Student 의 String() 메서드
return fmt.Sprintf("안녕 ! 나는 %d살 %s라고 해", s.Age, s.Name)
}
func main() {
student := Student{ "철수", 12 } // Student 타입
var stringer Stringer // Stringer 타입
stringer = student // stringer 값으로 student 대입 Student는 String() 메서드 포함
// stringer 인터페이스 갖고 있는 String() 메서드 호출
fmt.Printf("%s\\n", stringer.String())
}
20.2 인터페이스 왜 쓰나?
- 인터페이스를 이용하면 구체화된 객체가 아닌 인터페이스만 가지고 메서드를 호출할 수 있다.
- 큰 코드 수정없이 필요에 따라 구체화된 객체를 바꿔서 사용할 수 있다.
- 아래와 같이 Sender 인터페이스를 사용한다.
- Sender인터페이스는 Send() 메서드만 포함하고
- SendBook() 함수는 Sender 인터페이스를 입력으로 받는다.
- koreaPost, fedex 모두 Send(string) 메서드를 갖고 있으므로 Sender 인터페이스로 이용가능하며 SendBook() 함수의 인수로 사용할 수 있다.
- 이렇게 하면 메서드 내부 구현을 알 필요 없이 코드를 유연하게 사용할 수 있다.
// Fedex 에서 제공한 패키지
package fedex
import "fmt"
// Fedex에서 제공한 패키지 내 전송을 담당하는 구조체
type FedexSender struct {
}
func (f *FedexSender) Send(parcel string) {
fmt.Printf("Fedex sends %v parce\\n", parcel)
}
// 우체국에서 제공한 패키지
package koreaPost
import "fmt"
// 우체국에서 제공한 패키지 내 전송을 담당하는 구조체
type PostSender struct {
}
func (k *PostSender) Send(parcel string) {
fmt.Printf("우체국에서 택배 %v를 보냅니다.\\n", parcel)
}
package main
import (
"tucker_golang/ch20/fedex"
"tucker_golang/ch20/koreaPost"
)
type Sender interface {
Send(parcel string)
}
func SendBook(name string, sender Sender) {
sender.Send(name)
}
func main() {
// 우체국 전송객체, Fedex 전송 객체 모두 SendBook 인수로 활용 가능
koreaPostSender := &koreaPost.PostSender{}
SendBook("어린 왕자", koreaPostSender)
SendBook("그리스인 조르바", koreaPostSender)
// Fedex 전송 객체를 만든다.
fedexSender := &fedex.FedexSender{}
SendBook("어린 왕자", fedexSender)
SendBook("그리스인 조르바", fedexSender)
}
우체국에서 택배 어린 왕자를 보냅니다.
우체국에서 택배 그리스인 조르바를 보냅니다.
Fedex sends 어린 왕자 parce
Fedex sends 그리스인 조르바 parce
20.2.1 추상화 계층
- 추상화 : 내부 동작을 감춰서 서비스 제공, 사용자 측 모두에게 자유를 주는 방식
- 인터페이스 : 추상화를 제공하는 추상화 계층
20.3 덕타이핑
- 덕타이핑 : 타입 선언시 인터페이스 구현여부를 명시적으로 나타낼 필요 없이 인터페이스에 정의한 메서드 포함여부만으로 결정
- 아래와 같이 별다른 명시없이 String() 메서드를 포함한 것만으로 Stringer 인터페이스로 사용할 수 있다.
type Stringer interface {
String() string
}
type Student struct {
...
}
func (s *Student) String() string {
...
}
20.4 인터페이스 기능 더 알기
20.4.1 인터페이스를 포함하는 인터페이스
- 인터페이스도 다른 인터페이스를 포함할 수 있다.
type Reader interface {
Read() (n int, err error)
Close() error
}
type Writer interface {
Write() (n int, err error)
Close() error
}
// Read(), Write(), Close() 메서드를 가지게 된다.
type ReadWriter interface {
Reader // Reader의 메서드 집합을 포함한다.
Writer // Writer의 메서드 집합을 포함하낟.
}
20.4.2 빈 인터페이스 interface{}를 인수로 받기
- interface{} 는 메서드를 가지고 있지 않은 빈 인터페이스이다.
- 그래서 모든 타입이 빈 인터페이스로 쓰일 수 있다. 어떤 값이든 받을 수 있는 함수, 메서드, 변숫값을 만들 때 사용한다.
- 아래의 경우 PrintVal() 함수 인수로 빈 인터페이스인 interface{}를 받고, 빈 인터페이스이기 때문에 모든 타입을 인수로 쓸 수 있다.
package main
import "fmt"
func PrintVal(v interface{}) {
switch t := v.(type) { // v의 타입에 따라 다른 로직을 수행한다.
case int:
fmt.Printf("v is int %d\\n", int(t))
case float64:
fmt.Printf("v is float64 %f\\n", float64(t))
default:
//그 외 타입인 경우 타입과 값을 출력한다.
fmt.Printf("Not supported type: %T:%v\\n", t, t)
}
}
type Student struct {
Age int
}
func main() {
PrintVal(10)
PrintVal(3.14)
PrintVal(Student{15})
}
v is int 10
v is float64 3.140000
Not supported type: main.Student:{15}
20.4.3 인터페이스 기본값 nil
- 인터페이스 변수의 기본값은 유효하지 않은 메모리 주소를 나타내는 nil 이다.
package main
type Attacker interface {
Attack()
}
func main() {
var att Attacker // 기본값 nil, 초깃값이 없어서 기본값이 된다.
att.Attack() // att가 nil 이기 때문에 런 타임 에러가 발생한다.
}
<aside> 💡 컴파일 타임에러와 런 타임 에러 컴파일 타임 에러 : 코드를 기계어로 전환하며 실행 파일로 만드는 중에 발생한 에러, 문법 오류 등 런 타임 에러 : 실행 도중 예기치 않은 문제로 발생하는 에러. 문법에 문제가 없으나 값이 비정상인 경우
</aside>
20.5 인터페이스 변환하기
20.5.1 구체화된 다른 타입으로 타입 변환하기
- 인터페이스를 다른 구체화된 타입으로 타입 변환할 수 있다.
- 인터페이스를 본래의 구체화된 타입으로 복원할 때 주로 사용한다.
- 인터페이스 변수 뒤에 점. 을 찍고 소괄호()안에 변경하려는 타입 써주기
var a Interface
t := a . (ConcreteType)
반응형
'Study' 카테고리의 다른 글
[스터디] Tucker의 Go 언어 프로그래밍 내용정리 24~26장 (0) | 2023.12.12 |
---|---|
[스터디] Tucker의 Go 언어 프로그래밍 내용정리 21~23장 (1) | 2023.12.03 |
[스터디] Tucker의 Go 언어 프로그래밍 내용정리 14~17장 (1) | 2023.11.18 |
[스터디] PKOS 스터디 1주차 Kops 개념 및 실습 (0) | 2023.03.11 |
[스터디] Terraform module을 이용한 여러 EKS 배포 (0) | 2022.12.10 |