STUDY/Go Lang

[GoLang] 가볍게 Go 입문하기 (2)

Jexists 2023. 10. 15. 17:20

이 글은 골든래빗 《Tucker 의 Go 언어프로그래밍》의 2단계 써머리입니다.

목차

  1. 배열
  2. 구조체
  3. 포인터
  4. 문자열
  5. 패키지
  • 숫자 맞추기 게임 만들기

1. 배열

→ 같은 타입의 데이터들로 이루어진 타입

→ 초깃값 지정하지 않을 경우 기본값으로 초기화

var nums [5]int → [0 0 0 0 0]

→ 초깃값 지정

days := [2]string{"monday", "tuesday"} → [monday, tuesday]

→ 지정한 초깃값만 초기화 되고 나머지는 기본값으로 초기화 var temps [5]float64 = [5]float64{24.3, 26.7} → [24.3 26.7 0 0 0]

→ 인덱스로 지청한 값만 초기화

var s = [5]int{1: 10, 3: 30} → [0 10 0 30 0]

→ 배열요소 갯수 생략 가능 (배열요소 개수는 초기화 되는 요소개수와 같음)

package main

import "fmt"

func main() {
	x := [...]int{10, 20, 30}
	y := [...]int{}

	fmt.Println(x, cap(x), len(x))
	// [10 20 30] 3 3
	fmt.Println(y, cap(y), len(y))
	// [] 0 0

	// 요소개수 변경 불가
	// x = [4]int{1, 2, 3, 4}
	// x = [...]int{1, 2, 3, 4}
}

→ 배열 선언시 개수는 항상 상수 (변수값으로 배열 개수 사용불가)

package main

import "fmt"

const Y int = 3

func main() {
	x := 5
	const z = 7
	a := [x]int{1, 2, 3, 4, 5} // ERROR (변수로 선언된것은 배열 길이로 사용 불가)
	b := [Y]int{1, 2, 3} // [1 2 3]
	c := [z]int{1, 2, 3} // [1 2 3 0 0 0 0]
	var d [10]int        // [0 0 0 0 0 0 0 0 0 0]
}

→ 연속된 메모리

요소위치 = 배열 시작 주소 + (인덱스 x 타입 크기)

→ 배열 복사: 타입이 같아야한다.

 

배열 vs 슬라이스

→ 가변적 특성때문에 슬라이스를 주로 사용하게 됨

배열(array) 슬라이스(slice)

타입 참조
용량(cap) 수정불가 수정가능
길이(len) 수정불가 수정가능
비교연산 가능 불가능
호출 복사 전달 참조 전달

range 키워드

→ for 반복문에서 배열, 문자열, 슬라이스, 맵, 채널 등 요소 순회

for index, value := range array {
	fmt.Println(index, value)
}

2. 구조체

→ 다른 타입의 값들을 변수 하나로 묶어주는 기능

→ 타입명이 대문자이면 패키지 외부로 공개되는 타입

→ 기본값(zero value)로 초기화

→ 포함된 필드: 구조체 안에 포함된 다른 구조체의 필드명을 생략하는 경우

→ 불필요한 메모리 낭비를 줄이려면 작은 크기 필드값을 앞에 배치

type 타입명 struct {
	필드면 타입
}

3. 포인터

→ 가리키는 데이터 타입 앞에 *을 붙여서 선언 var p *int

→ &을 이용해서 데이터 메모리 주소 알수있음 p = &a

→ 기본값 nil: 유효하지 않는 메모리 주솟값

4. 문자열

→ 큰따옴표("): 특수문자 동작, 한줄로 입력(\n사용해서 여러줄 표현)

→ 백쿼트(`): 특수문자 동작X, 여러줄 입력 가능

→ 표준 문자코드: UTF-8 (다국어 문자 지원)

→ 문자열 대소 비교시 문자열 길이와 상관없이 앞글자부터 (같은 위치에 있는 글자끼리) 비교

→ 연산 +, += 사용해서 문자열 연결 가능: string 합 연산시 새로운 메모리 공간 만들어서 두 문자열 합침 (주솟값 변경 -> 메모리 낭비)

→ string 구조(reflect패키지의 StringHeader): Data(unitptr:포인터) & Len(int:길이)

→ 불변(immutable): 일부만 변경 불가

package main

import "fmt"

func main() {
	str := "Hello 월드"
	runes := []rune(str)
	// 총 바이트 길이
	fmt.Printf("len(str) = %d\\n", len(str)) // len(str) = 12

	// 배열 요소 개수
	fmt.Printf("len(runes) = %d\\n", len(runes)) // len(runes) = 8
}

5. 패키지

→ 코드를 묶는 가장 큰 단위 → 함수, 구조체, 상수를 외부에 노출 → 연관된 함수 타압 등의 코드를 묶어서 배포

패키지 종류

→ main: 시작점을 포함한 패키지, main()함수 포함한 패키지

→ fmt: 표준 입출력

→ math: 수학관력

→ http: 인터넷 웹 프로토콜

→ crypto: 암호화 기능

→ net: 네트워크 기능

표준 패키지 웹사이트

외부 패키지 리스트

패키지 사용

→ import + 따옴표

→ 해당 패키지의 외부로 노출하는 함수, 구조체, 변수, 상수 등 사용 가능

→ 변수명, 함수명, 구조체명 첫글자가 대문자인 경우만 외부 노출

→ 패키지 명은 패키지 경로 마지막 폴더

→ 모든 문자 소문자 권장

→ 패키지 전역으로 선언된 첫글자 대문자인 변수, 상수, 타입, 함수, 메서드, 구조체, 필드명: 패키지 외부 공개

→ 함수 내부에 선언된 상수는 패키지 외부 공개 불가 (공개되는 함수일 경우에도 불가능)

→ 한개 패키지 사용 import "fmt"

→ 여러 패키지 사용: import ("패키지이름", "패키지이름")

import (
  "fmt"
  "os"
)

→ 겹치는 패키지: 패키지명 앞에 별칭 붙여서 사용

import (
  "text/template"
  htemplate "html/template"
)

패키지 설치

→ 기본 패키지: Go설치 경로에 포함

→ 깃허브와 같은 외부 저장소 패키지: 다운 후 GOPATH\pkg폴더

→ 모듈아래 위치한 패키지: 현재폴더

GO 모듈

→ Go 패키지들을 모아놓은 Go프로젝트 단위

→ Go 모듈 사용 기본 (Go1.16버전 이상)

→ go build하려면 Go 모듈 루트 폴더에 go.mod 파일 필수

→ go mod init 패키지명: go.mod파일 생성

→ go mod tidy: Go모듈에 필요한 패키지 찾아서 다운로드 & 필요한 패키지 정보 go.mod, go.sum 입력

→ 모듈명은 다른 외부 패키지 이름과 겹치지 않게 주의

→ 모듈명의 마지막 이름은 폴더명과 맞추기

go build

→ Go 모듈 루트 폴더에 go.mod 파일 필수

→ 실행파일 만들때 go.mod(모듈 이름과 Go버전, 필요한 외부 패키지 등 명시)와 go.sum(외부패키지 버전 정보) 통해 외부 패키지와 모듈 내 패키지 합쳐서 실행파일 생성

패키지 초기화

→ 패키지 import시 패키지의 모든 전역변수 초기화 후 init()함수 호출

→ init(): 입력 매개변수, 반환값 없는 함수

→ init()만 사용할 경우 밑줄_ 이용해 불러오기

숫자 맞추기 게임 만들기

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"time"
)

// 숫자 맞추기 게임
// - rand() 랜덤값 생성 패키지
// - rand.Intn() 0~N-1 사이의 값 생성

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() {

	// 랜덤한 숫자 생성 (0-99사이의 숫자)
	randTest := rand.Intn(100)
	fmt.Println("seedX :", randTest)
	// rand.Seed()가 0이라서 일정한 값 호출
	// 내부에 있는 시퀀스가 같아서 매번 호출할때마다 똑같음

	// 시드값(기반)을 주면 랜덤숫자 변경
	// 시드값도 똑같으면 값이 똑같음
	// 시드값이 바꿔져야 결과값이 바뀜
	rand.Seed(time.Now().UnixNano())

	// 랜덤시드로 하면 좋은것? -> 매번 변화하는 값: Time
	// time.Now().UnixNano() : t의 1970년 1월 1일 부터 현재까지 경과된 시간을 nano sec단위로 반환

	r := rand.Intn(100)
	cnt := 1

	fmt.Println("답 :", r)

	// 숫자 맞추기 게임
	for {
		fmt.Printf("숫자값을 입력하세요: ")
		n, err := InputIntValue()
		if err != nil {
			fmt.Println("숫자만 입력하세요")
		} else {
			// fmt.Println("입력하신 숫자는 ", n, " 입니다.")
			// fmt.Println(r)
			if n > r {
				fmt.Println("입력하신 숫자가 더 큽니다.")
			} else if n < r {
				fmt.Println("입력하신 숫자가 더 작습니다.")
			} else {
				fmt.Println("숫자를 맞췄습니다. 축하합니다. 시도한 횟수: ", cnt)
				break
			}
			cnt++
		}
	}
}