5장 Go의 타입 시스템

  • Go는 정적 타입 프로그래밍 언어
  • primitive 타입의 경우 이름에 바이트 크기가 같이 있는 경우가 많음. (int32, int64 등)

사용자 정의 타입 (composite type)

  • c의 struct랑 비슷
  • type myType struct {
    field1 type1
    field2 type2
    }
    
  • 변수로 선언하면 모든 필드가 제로값으로 초기화됨

  • 제로값이 아니라 특정 값으로 초기화되어야하면 구조체 리터럴 및 변수 선언 연산자 사용

  • var1 := myType {
    field1: value1
    field2: value2
    }
    
  • var1 := myType{value1, value2}
  • 중첩 가능

  • primitive 타입을 base type으로 새로운 타입 정의 가능

  • type Duration int64

  • base type과 완전히 다른 별개의 타입으로 취급

Method

  • 타입에 행위를 정의하기 위한 방법. func 키워드와 함수 이름 사이에 추가 패러미터를 정의한 함수
  • func와 함수 이름 사이의 패러미터는 특별히 receiver이라고 부름
  • 그리고 이러한 receiver가 있는 함수는 method라고 지칭
  • method의 경우 일반적인 함수의 Package.Function 대신에 Type.Function 형태로 호출
  • 이 타입이 method의 receiver으로 전달됨.

Value receiver vs Pointer receiver

  • Value receiver
  • func (n myType) method() {}
  • receiver의 값을 복사해서 method로 전달함. 따라서 method 내부에서 아무리 변경하더라도 원본에는 영향이 없음.
  • pointer로도 바로 호출이 가능함. pointer를 사용해서 method를 호출하면 실질적으로는 (*myPointer).method() 와 동일
  • 불변 객체를 유지하고싶다면 Value receiver가 답. 변경이 발생할 때마다 복제본이 반환됨.
  • Pointer receiver
  • func (n *myType) method() {}
  • receiver의 pointer를 복사해서 method로 전달. 덕분에 method 내부에서 변경하면 원본 인스턴스의 값도 같이 변경됨
  • value으로도 바로 호출이 가능함. value을 사용해서 method를 호출하면 (&myValue).method() 와 동일
  • 기존 인스턴스의 상태를 변경해야한다면 Pointer receiver가 답. 변경이 발생하면 원본이 변경됨.

내장 타입 (built-in type)

  • 언어 차원에서 지원되는 타입들
  • 숫자, 문자열, boolean 등
  • primitive type이라고 말할 수도 있음
  • 뭔가 추가하거나 제거하면 기본값이 변경되지 않고 반드시 새로운 값 생성
  • 함수나 메소드에 전달할 때 복사된 value이 전달됨
  • built-in type에 대해서 method를 선언할 수 없음
  • built-in type을 base type으로 새로운 사용자 정의 타입을 선언해야함

참조 타입 (reference type)

  • 슬라이스, 맵, 채널, 인터페이스, 함수 타입 등
  • 변수와 함께 헤더 value 생성
  • 헤더는 해당 타입의 데이터 구조를 관리하기 위해 여러 pointer, value들을 필드로 가지고 있음
  • 슬라이스로 치면 length, capacity 및 내장 배열 pointer 등
  • 헤더는 함수로 전달하거나 리시버로 호출할 때 value 복사를 전제로 깔고 설계되었기 때문에 pointer로 쓰면 안됨
  • 문자열 또한 기술적으로는 참조 타입

구조체 타입

  • primitive, non-primitive 성질을 모두 가질 수 있음
  • 원하는 성질에 따라 built-in type, reference type을 위한 가이드라인을 준수해야 함
  • 가변 객체의 경우 메소드나 함수, 리시버에서 pointer를 사용
  • 불변 객체의 경우 메소드나 함수, 리시버에서 value 사용
  • 인터페이스 value이 다른 경우 value 타입 리시버에게 유연성을 제공해야하는게 유일한 예외

인터페이스 타입

  • 다형성을 위해서 사용
  • 행위를 선언하기 위한 타입
  • 사용자 정의 타입이 인터페이스에 정의된 행위를 메소드 형태로 구현해야 함 <- 구현 타입
  • 인터페이스 메소드를 구현한 사용자 정의 타입은 (== 메소드 집합에 동일하게 모두 있어야 함) 인터페이스 value에 대입 가능
  • 인터페이스는 2개의 word로 구성
  • 첫번째 word는 iTable에 대한 주소. iTable에는 저장된 데이터의 타입 정보, 메소드 집합 저장
  • 두번째 word는 실제 value에 대한 주소

메소드 집합

  • 주어진 타입 value이나 pointer에 관련된 메소드 집합. receiver의 종류에 따라 메소드가 value이냐 pointer냐가 정해짐
  • value는 value receiver를 가진 메소드들만 메소드 집합으로 포함
    • value의 주소를 확인할 수 없는 경우도 존재하기 때문 -> go에서 자동으로 바꿔서 실행시킬 수 없음
  • pointer는 value, pointer receiver 모두 메소드 집합으로 포함

다형성

  • 어떤 타입이든 인터페이스를 구현할 수 있음
  • 구현 타입이 인터페이스에 정의된 메소드를 구현하기만 하면 실제 타입이 뭐든간에 자유롭게 전달, 캐스팅이 가능

타입 임베딩

  • 기존의 타입을 확장하거나 동작을 변경
  • type outer struct {
        inner
    }
    
  • 기존에 선언된 타입을 새로운 구조체 타입의 내부에 선언하는 것
  • 새로운 타입은 특별히 outer type, 내부에 들어간 타입은 inner type이라고 지칭
  • inner type의 필드, 메소드 등 모든 것들이 outer type에게 적용됨. 덕분에 inner type의 구현한 인터페이스는 outer type도 자동으로 구현하게 됨
    • outer type에서 재정의해서 오버라이딩도 가능
    • inner type을 직접 접근해서 호출하는 것도 가능

노출, 비노출 식별자

  • 소문자로 시작하면 비노출
  • 대문자로 시작하면 노출
  • 만약 비노출 필드에 대해서 팩토리 함수를 제공한다면 New 로 이름 짓는게 규칙
    • 노출되지 않은 타입을 명시적으로 선언하는건 불가능하지만 단축 변수 선언 연산자로 정의하는 것은 가능
    • 식별자는 값이 아니기 때문
    • 단축 변수 선언 연산자는 타입을 추정하여 비노출 타입의 변수를 생성할 수 있기 때문
  • inner type이 비노출이더라도 outer type이 노출이면 outer type을 통해 접근 가능.
    • 이 경우 구조체 리터럴을 써서 inner type을 초기화할 순 없음
    • inner type을 직접 호출하는 것도 불가능

results matching ""

    No results matching ""