그래 그리 쉽지는 않겠지

검색

검색 아이콘검색을 여는 아이콘

Tucker의 Go 언어 프로그래밍 Ch18 ~ Ch22

2023-10-21

이 글은 골든래빗 《 Tucker의 Go언어 프로그래밍 》의 18장~22장 써머리입니다.

Ch18 ~ Ch22

  • 슬라이스
  • 메서드
  • 인터페이스
  • 함수
  • 자료구조

# 슬라이스

동적 배열

  • 자동으로 배열 크기를 증가시키는 자료구조

선언

1
2
3
4
5
6
var slice []int

// 초기화
var slice1 = []int{1, 2, 3}

var slice2 = make([]int, 3)

# 슬라이스 동작 원리

내부 구현

1
2
3
4
5
6
7
type SliceHeader struct {
	Data uintptr // 실제 배열을 가리키는 포인터
	Len  int     // 요소 개수
	Cap  int     // 실제 배열 길이
}

var slice = make([]int, 3, 5) // len:3, cap: 5인 슬라이스 생성

make() 함수

heap 에 메모리를 할당하고 zero value 로 초기화

  • 슬라이스, 맵, 채널에 사용


요소 추가

append() 함수 사용

1
slice2 := append(slice, 3, 4, 5)
  • 요소가 추가된 새로운 슬라이스를 반환 (배열 포인터, len, cap 가 동일)

내부 동작

  • 남은 빈 공간(cap - len) 확인
    • 빈 공간 >= 추가되는 요소의 개수
      • 기존 슬라이스 배열에 그대로 추가
      • len 만 변경
    • 빈 공간 < 추가되는 요소의 개수
      • 기존 슬라이스 배열의 2배 크기의 배열을 생성
      • 기존 슬라이스 배열 요소를 새 배열에 모두 복사 후 요소 추가
      • 배열 포인터를 새 배열을 가리키는 포인터로 교체
      • 배열 포인터, len, cap 모두 변경

# 슬라이싱

배열 및 슬라이스 일부를 집어내어 슬라이스로 반환

1
slice[시작인덱스:끝인덱스(:최대인덱스)] // 끝인덱스 하나 전까지 포함
  • 배열 포인터: 기존 배열의 일부를 가리킴
  • len: 집어낸 길이
  • cap: 시작인덱스부터 배열(슬라이스)의 마지막까지 길이
    • 최대인덱스 정의시: 최대인덱스 - 시작인데스

# 슬라이스 복제

같은 값을 갖는 서로 다른 배열을 가리키는 슬라이스로 복제

append() 함수 사용

1
slice2 := append([]int{}, slice1...)

… 키워드

슬라이스를 요소를 풀어서 인수로 전달

  • 받는 쪽에서는 가변 길이 인수로 받음 (함수 내부에서는 다시 슬라이스 형태로 사용)


copy() 함수 사용

1
2
3
func copy(dst, src []Type) int // 실제 복사된 요소 개수 (최대: dst의 len)

cnt := copy(slice2, slice1)

# 슬라이스 요소 추가 및 삭제

요소 삭제

1
slice = append(slice[:idx], slice[idx+1:]...)

요소 추가

1
2
3
4
5
6
slice = append(slice[:idx], append([]int{100}, slice[idx+1:]...)...)

// 임시 슬라이스 없이
slice = append(slice, 0) // 맨 뒤에 요소 추가
copy(slice[idx+1:], slice[idx:])
slice[idx] = 100

# 슬라이스 정렬

기본 내장 패키지인 sort 패키지 사용

  • 슬라이스가 Len(), Less(), Swap() 메서드를 구현해야 함 (별칭 타입 사용)

example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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] }

...

// 정렬
sort.Sort(Students(s))

# 메서드

함수의 일종으로 구조체에 속함

선언

1
2
3
func (r Rabbit) info() int { // 리시버, 메서드명
	return r.width * r.height
}

리시버

메서드가 속하는 타입을 알려줌

  • 구조체, 별칭 타입

# 포인터 메서드 vs 값 타입 메서드

값 타입 리시버

리시버 타입의 모든 이 복사

  • 객체 속성이 바뀌면 다른 객체

포인터 리시버

포인터가 가리키고 있는 메모리의 주소값이 복사

  • 객체 속성이 바뀌더라도 같은 객체

# 인터페이스

구현을 포함하지 않은 메서드 집합

선언

1
2
3
4
type DuckInterface interface {
	Fly() // 메서드 집합
	...
}

# 덕 타이핑

인터페이스 구현

  • 인터페이스에 정의한 메서드 포함 여부로 결정
  • 명시적으로 나타내지 않음

서비스 사용자 중심 코딩

  • 서비스 제공자가 아닌 사용자가 인터페이스를 만들어서 사용 가능

💡 구체화된 객체와 관계를 맺은 뒤 추후에 인터페이스를 만들어서 사용 가능

# interface{}

가지고 있어야 할 메서드가 없기 때문에 모든 타입이 쓰일 수 있음

Type switch

switch t := v.(type)

  • 값 대신 타입을 비교
  • 빈 인터페이스를 사용해 타입에 따른 처리시에 사용

# 타입 변환

사용

1
2
3
4
5
// 구체화된 타입으로 타입 변환
t := a.(ConcreteType) // 인터페이스를 구현하고 있지 않으면 컴파일 타임 에러 발생

// 다른 인터페이스로 타입 변환
b := a.(BInterface)

❗런 타임 에러

구체화된 타입으로 타입 변환

  • 같은 인터페이스를 구현하고 있는 다른 구체화 타입으로 변환시

다른 인터페이스로 타입 변환

  • 인터페이스에 담긴 구체화된 타입이 변환하는 인터페이스를 구현하고 있지 않은 경우


타입 변환 성공 여부 반환

1
t, ok := a.(ConcreteType) // 변환 실패시 ConcreteType 의 기본값을 반환
  • 런 타임 에러가 발생하지 않음

# 함수

# 가변 인수 함수

인수 개수가 정해져 있지 않은 함수

  • ... 키워드를 사용하여 해당 타입 인수를 여러 개 받는 가변 인수임을 표시
  • 가변 인수는 함수 내부에서 슬라이스 타입으로 처리
1
2
3
4
// example
func sum(nums ...int) int

func Print(args ...interface{}) string // 빈 인터페이스를 사용하면 여러 타입을 섞어 사용 가능

# defer 지연 실행

1
defer 명령문
  • 함수가 종료되기 직전에 실행
  • 여러 defer 선언시 마지막에 선언된 defer 부터 역순으로 실행

# 함수 타입 변수

함수를 값으로 갖는 변수

  • 함수 시작 지점(함수를 가리키는 값)을 가짐

함수 타입

1
2
3
4
5
// 함수 정의로 표시
func (int, int) int // 매개변수명은 선택

// 별칭 타입 사용
type opFunc func (int, int) int


캡쳐

함수 리터럴(익명 함수) 외부 변수를 내부상태로 가져오는 것

값 복사가 아닌 참조 형태로 가져옴

  • 외부 변수의 주소값을 포인터 형태로 가져와 접근


의존성 주입

함수 타입 및 함수 리터럴을 통해 외부에서 로직을 주입

  • 인터페이스를 통해서도 구현 가능

# 자료구조

# 리스트

비연속 메모리를 사용해 요소를 저장

  • 각 데이터를 담고 있는 요소들을 포인터로 연결
1
2
3
4
5
type Element struct {
	Value interface{}
	Next *Element
	Prev *Element
}

배열 vs 리스트

동작배열리스트
맨 앞에 데이터 추가O(N)
∙ 각 요소를 한 칸씩 뒤로 밀어냄
∙ 맨 앞의 값을 변경
O(1)
∙ 맨 앞에 요소 추가하여 연결
특정 요소에 접근O(1)
∙ 배열 시작 주소 + (인덱스 x 타입 크기)
O(N)
∙ N-1 번 링크 타고 이동
  • 배열
    • 접근이 빈번할 때 유리
    • 데이터 지역성(데이터가 밀집한 정도)이 높음
  • 리스트
    • 빈번한 삽입, 삭제에 유리

FIFO 구조로 선입선출

스택

FILO 구조로 후입선출

링(환형 리스트)

처음과 끝이 연결된 리스트

  • 크기가 고정
  • 시작과 끝이 없고 현재 위치만 있음
  • 사용
    • 개수가 고정되고, 오래된 요소는 지워도 되는 경우에 적합

#

형태로 자료가 저장되는 자료구조

생성

1
2
// map[key]value
m := make(map[string]string)

순회

1
2
3
for k, v := range m { // k: 키, v: 값
	...
}

요소 삭제

1
delete(m, key) // m: 맵 변수, key: 삭제 키

요소 확인

1
2
// 요소가 없을 때와 값이 0(zero value)일때 구분
v, ok := m[3] // v: 값, ok: 존재 여부

# 맵의 원리

해시 함수

  • 같은 입력 -> 같은 결과
  • 입력값의 범위은 무한대, 결과는 특정 범위를 가짐
  • e.g.
    • 삼각함수
    • 나머지 연산

배열을 통한 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const M = 10

func hash(d int) int {
	return d % M
}

func main() {
	m : = [M]int{} 

	m[hash(23)] = 10
	m[hash(33)] = 50 // 해시 충돌: 다른 입력이지만 같은 출력
}

해시 충돌 해결

  • 값 대신 키와 값을 가지는 리스트를 저장

# References