안녕하세요, 12월의 시작을 함께 맞이한 고랭 스터디입니다!
이번 주는 17장 숫자 맞추기 게임 만들기, 18장 슬라이스를 공부했습니다!
Ch 17. 숫자 맞추기 게임
처음으로 Project 형식의 챕터를 공부했습니다! 숫자 맞추기 게임의 조작법입니다.
math/rand 패키지
랜덤한 숫자를 얻으려면 math/rand 패키지를 임포트해야 합니다.
func Intn(n int) int
0 ~ n-1 사이의 int 타입 랜덤값을 생성하는 함수입니다. 단, 컴퓨터의 논리회로와 산술연산은 랜덤값을 만들기 적합하지 않고 이는 단지 유사 랜덤값입니다. 랜덤값이 산출되는 초깃값이 같기 때문에 실행할 때마다 매번 랜덤 시드를 다른 값으로 설정해야합니다.
func Seed(seed int64)
랜덤 시드를 설정하는 함수입니다.
time 패키지
현재 시각 값을 랜덤 시드값으로 설정하면 매번 다른 랜덤값 생성할 수 있습니다.
func Now() Time
현재 시각을 Time 객체로 반환하는 함수입니다. Seed() 함수의 인수 타입은 int64기 때문에 타입변환이 필요합니다.
func (t Time) UnixNano() int64
Time 객체를 int64 타입으로 변환하는 함수입니다. UTC 시간 기준 1970년 1월 1일부터 Time 객체가 나타내는 시각까지 경과한 시 간을 나노초 단위로 나타낸 값입니다.
랜덤한 숫자 생성하기
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(100)
fmt.Println(n)
}
숫자값 입력받기
package main
import (
"bufio"
"fmt"
"os"
)
var stdin = bufio.NewReader(os.Stdin)
func InputIntValue() (int, error) {
var n int
_, err := fmt.Scanln(&n)
if err != nil {
stdin.ReadString('\n')
}
return n, err
}
func main() {
for {
fmt.Printf("숫자값을 입력하세요:")
n, err := InputIntValue()
if err != nil {
fmt.Println("숫자만 입력하세요.")
} else {
fmt.Println("입력하신 숫자는 ", n, " 입니다.")
}
}
}
숫자 대신 문자를 입력받으면 에러를 반환합니다. 표준 입력 스트림을 비우고 다시 입력을 받습니다. 이 코드에서 for문으로 무한루프를 돌기 때문에 사용자가 강제종료를 해야 프로그램이 종료됩니다.
숫자 맞추기 완성하기
위의 내용을 모두 합쳐 숫자 맞추기 게임을 구현했습니다!
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"time"
)
var stdin = bufio.NewReader(os.Stdin)
func InputIntValue() (int, error) {
var n int
_, err := fmt.Scanln(&n)
if err != nil {
stdin.ReadString('\n')
}
return n, err
}
func main() {
rand.Seed(time.Now().UnixNano())
r := rand.Intn(100)
cnt := 1
for {
fmt.Printf("숫자값을 입력하세요:")
n, err := InputIntValue()
if err != nil {
fmt.Println("숫자만 입력하세요.")
} else {
if n > r {
fmt.Println("입력하신 숫자가 더 큽니다.")
} else if n < r {
fmt.Println("입력하신 숫자가 더 작습니다.")
} else {
fmt.Println("숫자를 맞췄습니다. 축하합니다. 시도한 횟수:", cnt)
break
}
cnt++
}
}
}
Ch 18. 슬라이스
슬라이스는 Go 언어에서 제공하는 동적 배열입니다.
18.1 슬라이스
슬라이스 선언
var array [10]int
var slice []int
배열과 비슷하지만 [ ] 안에 배열의 개수를 넣지 않고 선언합니다.
슬라이스 초기화
1. { } 이용하기
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}
배열과 같이 { }를 사용해서 요솟값을 지정해 초기화할 수 있습니다.
2. make() 함수 이용하기
var slice = make([]int, 3)
make() 함수의 인수에 만들고자 하는 타입과 길이를 넣습니다. 이 때 각 요솟값은 타입의 기본값입니다.
슬라이스 요소 접근 & 순회
배열과 같습니다.
var slice = []int{1, 2, 3}
for i := 0; i < len(slice); i++ {
slice[i] += 10
}
for i, v := range slice {
slice[i] = v * 2
}
슬라이스 요소 추가 - append()
기존 배열은 한 번 길이가 정해지면 늘릴 수 없지만 슬라이스는 요소를 추가해 길이를 늘릴 수 있습니다.
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)
}
append() 함수의 인수로 추가하고자 하는 슬라이스와 요솟값을 넣습니다. 요솟값은 하나만 추가할 수도, 여러 값을 추가할 수도 있습니다.
18.2 슬라이스 동작 원리
슬라이스의 내부 구현입니다.
type SliceHeader struct{
Data uintptr //실제 배열을 가리키는 포인터
Len int //요소 개수
Cap int //실제 배열의 길이
}
make() 함수를 이용한 선언
var slice = make([]int, 3)
var slice2 = make([]int, 3, 5)
각 슬라이스가 가리키는 배열입니다.
슬라이스와 배열의 동작 차이
func changeSlice(slice2 []int) {
slice2[2] = 200
}
func main() {
slice := []int{1, 2, 3, 4, 5}
changeSlice(slice)
fmt.Println("slice:", slice)
}
slice: [1 2 200 4 5]
slice의 3번째 값이 200으로 바뀌었습니다!
동작 차이의 원인
slice의 모든 필드를 slice2에 복사해서 slice와 slice2는 똑같은 메모리 주솟값을 가집니다. 즉, 같은 배열 데이터를 가리키게 됩니다. 따라서 slice2의 3번째 값 변경은 slice의 3번째 값 변경과 같습니다.
append()를 사용할 때 발생하는 예기치 못한 문제
append() 함수 호출하면 슬라이스에 값을 추가할 수 있는 빈 공간이 있는지 먼저 확인합니다. (남은 빈 공간 = cap - len)
1. 빈 공간이 충분하다!
slice1 := make([]int, 3, 5)
slice2 := append(slice1, 4, 5)
slice1과 slice2는 같은 배열을 가리킵니다.
2. 빈 공간이 부족하다!
slice1 := []int{1, 2, 3}
slice2 := append(slice1, 4, 5)
slice2는 크기가 더 큰 배열을 새로 마련해 가리킵니다. 즉, 둘은 서로 다른 배열을 가리킵니다.
18.3 슬라이싱
array[startIdx:endIndex]
슬라이싱이란 배열의 일부를 잡아내는 기능입니다. 시작인덱스부터 끝인덱스-1까지의 배열 일부를 나타내는 슬라이스르 를 반환합니다. 새로운 배열이 만들어지는 게 아니라 일부를 포인터로 가리킬 뿐입니다. 반환되는 슬라이스의 len = 끝인덱스 - 시작인덱스, cap = 배열의 총 길이 - 시작인덱스
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2]
처음부터 슬라이싱 : 시작인덱스 생략 가능
slice2 := slice1[0:3]
slice2 := slice1[:3]
끝까지 슬라이싱 : 끝인덱스 생략 가능
slice2 := slice[2:len(slice1)]
slice2 := slice[2:]
전체 슬라이싱 : 시작인덱스, 끝인덱스 생략 가능
array := [5]int{1, 2, 3, 4, 5}
slice := array[:]
인덱스 3개로 cap 크기 조절하기
slice[시작인덱스:끝인덱스:최대인덱스]
반환되는 slice의 cap = 최대인덱스 - 시작인덱스
18.4 유용한 슬라이싱 기능 활용
슬라이스 복제
기본형 : 같은 크기 슬라이스 생성 → 모든 요솟값 복사
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1))
for i, v := range slice1 {
slice2[i] = v
}
append() 함수로 코드 개선
slice2 := append([]int{}, slice1...)
배열이나 슬라 이스 뒤에 ...를 하면 모든 요솟값을 넣어준 것과 같습니다.
copy() 함수로 코드 개선
func copy(dst, src []Type) int
copy() 함수의 인수로 목적지 슬라이스, 대상 슬라이스를 받고 실제로 복사된 요소 개수를 반환합니다.
요소 삭제
기본형 : 중간 요소 삭제 → 중간 요소 이후의 값을 앞당겨서 삭제되 요소 채우기 → 맨 마지막값 지우기
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]
append() 함수로 코드 개선
slice = append(slice[:idx], slice[idx+1]...)
요소 추가
기본형 : : 슬라이스 맨 뒤에 요소 하나 추가 → 맨 뒤값부터 삽입하 려는 위치까지 한 칸씩 뒤로 밀어주기 → 삽입하는 위치의 값 바꿔주기
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
append() 함수로 코드 개선
slice = append(slice[:idx], append([]int{100}, slice[idx:]...)...)
append() 함수가 중첩으로 사용되어 불필요한 메모리가 소요됩니다.
copy() 함수로 코드 개선
slice = append(slice, 0) // 맨 뒤에 요소 추가
copy(slice[idx+1:], slice[idx:]) // 값 복사
slice[idx] = 100 // 값 변경
임시 슬라이스를 사용하지 않아 불필요한 메모리가 필요하지 않습니다.
18.5 슬라이스 정렬
int 슬라이스 정렬
import (
"fmt"
"sort"
)
func main() {
s := []int{5, 2, 6, 3, 1, 4}
sort.Ints(s)
fmt.Println(s)
}
sort 패키지의 Ints() 함수를 사용하여 []int 슬라이스를 정렬합니다.
구조체 슬라이스 정렬
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
}
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)
}
Sort() 함수를 사용하기 위해서 Len(), Less(), Swap() 메서드가 필요합니다. []Student의 별칭 타입 Students을 생성하고 Len(), Less(), Swap() 메서드를 포함시켜 정렬 인터페이스를 포함하도록 합니다. Sort() 함수 호출 시 []Student를 Students 타입으로 변환 후 인수로 사용합니다. 아직 메서드와 인터페이스 다루지 않아서 구조체 슬라이스는 이런 식이다~만 알아도 된다고 합니다! ^^
이렇게 6주차의 숫자 맞추기 게임, 슬라이스까지 알아보았습니다. 슬라이스부터 Go 레벨업하기!라서 앞으로 더 유용한 고급 문법을 다루게 될 예정입니다!
12월의 시작을 고랭으로 해서 색달랐습니다~~ 12월은 정말 정말 바쁘겠지만 화이팅해봅시다!
'3-1기 스터디 > Golang' 카테고리의 다른 글
[10주차] 단어 검색 프로그램 만들기, 객체지향 설계 원칙 SOLID (0) | 2022.01.22 |
---|---|
[9주차] 고루틴과 동시성 프로그래밍, 채널과 컨텍스트 (0) | 2022.01.20 |
[5주차] Chapter 15~16. 문자열, 패키지 (0) | 2021.11.30 |
[4주차] Go 언어의 배열, 구조체, 포인터 (0) | 2021.11.22 |
[3주차] Go언어의 if, switch, for (0) | 2021.11.11 |
댓글