타입, 메서드, 인터페이스
var a *int
메서드가 리시버를 수정 => 반드시 포인터 리시버 사용
메서드가 nil 인스턴스를 처리할 필요 => 반드시 포인터 리시버 사용
메서드가 리시버를 수정하지 않음 => 값 리시버 사용 가능
이것은 타입에 선언된 다른 메서드에 따라 결정
같은 타입에 다른 리시버가 포인터 리시버라면 리시버를 수정하지 않는 메서드라도 포인터 리시버 사용 <= 일관성을 위해
위 c 는 포인터 리시버
아래 c 는 값 리시버
사실상 포인터 리시버는 (&C).Increment()이런식으로 사용해야 하는데 그냥 c.으로 해도 Go에서 자동 변환해줌.
포인터 리시버 - 원본이 변경됨
그냥 값 리시버 - 원본이 변경되지 않음.
이것 고려해서 리시버 사용할 것.
=> 위 예제의 경우 포인터리시버를 값 리시버로 변경하면 결과값이 제대로 나오지 않지만, 값 리시버를 포인터리시버로 변경하면 결과값 잘 나옴.
값 리시버 사용한 경우 복사본이든 원본이든 상관없는 내용이기때문.
package main
import (
"fmt"
"time"
)
type Counter struct {
total int
lastUpdated time.Time
}
func (c *Counter) Increment() {
c.total++
c.lastUpdated = time.Now()
}
func (c Counter) String() string {
return fmt.Sprintf("total: %d, last updated: %v", c.total, c.lastUpdated)
}
func doUpdateWrong(c Counter) { // 여기 c는 복사본 (함수의 인자가 포인터가 아니기 때문)
c.Increment() // 마찬가지로 파라미터로 넘겨받음. (복사본) -> wrong.
fmt.Println("in doUpdateWrong:", c.String())
}
func doUpdateRight(c *Counter) { //포인터로 받아서 원본이 수정되게 했음
c.Increment()
fmt.Println("in doUpdateRight:", c.String())
}
func main() {
var c Counter
doUpdateWrong(c)
fmt.Println("in main:", c.String())
doUpdateRight(&c) //아까는 직접 메서드를 호출했으니까 &안붙였지만 이번엔 함수의 인자를 넘겨받는거라 &붙여야함.
fmt.Println("in main:", c.String())
}
package main
import (
"fmt"
)
type IntTree struct { //이진트리 구조체
val int
left, right *IntTree //재귀적으로 설계됨
}
func (it *IntTree) Insert(val int) *IntTree { //it ; IntTree를 소문자로 받음 . (관례적으로)
if it == nil {
return &IntTree{val: val} //왼쪽 이름 오른쪽 파라미터
}
if val < it.val {
it.left = it.left.Insert(val)
} else if val > it.val {
it.right = it.right.Insert(val)
}
return it
}
func (it *IntTree) Contains(val int) bool {
switch {
case it == nil:
return false
case val < it.val:
return it.left.Contains(val)
case val > it.val:
return it.right.Contains(val)
default:
return true
}
}
func main() {
var it *IntTree
it = it.Insert(5)
it = it.Insert(3)
it = it.Insert(10)
it = it.Insert(2)
fmt.Println(it.Contains(2))
fmt.Println(it.Contains(12))
}
*타입 출력해보기
city := []{‘서울’, ‘부산’, ‘인천’, ‘대전’}
myCity := ‘부산’
switch myCity {
case city[0]:
“서울 출신이시네요”
Go에서는 아래와 같이 가능.
city := []{‘서울’, ‘부산’, ‘인천’, ‘대전’}
myCity := “부산”
switch myCity {
case 서울:
“서울 출신이시네요”
카테고리는 순서가 큰 상관 X.
iota 사용하면 아래와 같음. (같은 말 여러번 반복하지 않아서 가독성 좋고 편함)
값출력 위와 같이 되므로 MailCategory 직접 정수 할당한거랑 마찬가지.
그런데 열거형 사용하는 경우 숫자 사용할 거 별로 없을 것.
iota ; 키워드는 아니고 식별자임. -> 자동적으로 값을 채워준다.
8번 라인 - 메서드. 메서드니까 패키지 레벨에 선언해주어야 하구, 값 메서드 사용해서 생성.
승격해서 ID만으로 바로 할당 가능. 원래부터 매니저에 ID가 있었던 것처럼 사용가능하다.
employee의 메소드도 바로 사용 가능. (아래)
임베딩된 항복의 모든 항목이나 메서드는 승격되어 임베딩한 구조체에 포함되고 바로 실행도 가능하다.
-> 명시적으로 표현하기
암묵적 인터페이스
: Go의 유일한 추상 타입
인터페이스의 목적/용도
interface 키워드
인터페이스 리터럴
위 ...에는 인터페이스를 만족시키기 위한 구체적 타입에서 반드시 구현(implement)해야 할 메서드 시그니처 나열
Go에서 관례적으로 인터페이스 이름은 끝에 ‘er’을 붙인다
인터페이스는 모든 블록에서 선언 가능
덕 타이핑
-https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91
상속은 없지만 인터페이스 이용해서 간접적으로,,
package main
type LogicProvider struct {}
func (lp LogicProvider) Process(data string) string{ // 여기랑 11번라인이랑 같아서 연결됨. 암묵적으로 간접적으로.
return ""
}
type Logic interface {
Process(data string) string // 메서드 모양과 인터페이스 모양이 같다.
}
type Client struct {
L Logic
}
func (c Client) Program() {
data := 어디선가 데이터 얻어옴
c.L.Process(data)
}
func main() {
c := Client{
L: LogicProvider {},
}
c.Program()
}
아무타입이나 아무 값 인터페이스에 저장할 수 있다.
인터페이스 변수에 할당된 구체 타입 확인
구체 타입이 다른 인터페이스를 구현했는지 확인
타입 단언(Type Assertion)
타입 스위치 = 인터페이스.(type) + switch 문
Go의 동시성(Concurrency)
채널에 데이터를 쓰거나 읽을 수 없는 상태 -> blocking
기타
class Counter {
int cnt:
String name:
int getCnt()
void setCnt(int cnt)
String getName()
void setName(String name)
}
String myName = null -> 이건 O
myName.upper() ->이건 X
하지만 Go는 포인터 리시버로 (포인터 메서드로) nil 받을 수 있다. => nil에 대해서 메서드 호출 가능. 자바는 불가능.