Go 기본 문법 정리 (변수, 자료형부터 고루틴, 채널까지)

2020, Sep 22    

패키지

  • 작업폴더의 src폴더 하위에 위치함
  • 폴더명과 패키지 이름은 동일하게 하는 것을 권장
  • main 이라는 이름으로 생성할 경우 실행 프로그램으로 인식
package main

//패키지 import
import "fmt"
//아래와 같이도 선언 가능
import (
	"math/rand"
)

변수 & 자료형

  • var 키워드로 변수 선언
  • var [변수명] [자료형]
  • 자료형은 사용하지 않아도 타입추론 가능
// 변수 선언 및 초기화
var s string = "Hamletshu"
// 자료형 없이 변수 선언 및 초기화
var s2 = "자료형 선언 안했음"
// 여러 개의 변수를 한꺼번에 선언 및 초기화 방법1
var s3, su, f = "Hamletshu", 123, 3.14
// 해당 값 출력
fmt.Println(s2)
fmt.Println(s3, su, f)

//여러개의 변수를 한꺼번에 선언 및 초기화 방법2
var (
	a1 = "Hamletshu"
	a2 = 123
	a3 = 3.14
)
//해당값 출력
fmt.Println(a1, a2, a3)

포인터

  • C언어에서 쓰는 포인터와 동일
  • 변수 메모리의 주소값을 나타냄
var x = 5
var address = &x//x변수의 주소값 넣기 
fmt.Println(address) //주소가 표시됨 ex) 0xc0000120a0
var y = *address
fmt.Println(y) //역참조 주소안에 있는 값을 가져옴 5

함수

//함수 기본 구조
func add(a int, b int) {
	//a와 b를 더한 값 출력
	fmt.Println(a + b)
}

//함수 호출 (main 함수에서 호출한다고 가정, 이하 동일)
add(1, 2)

//a, b의 자료형이 같으므로 이렇게 자료형을 선언해도 됨
func add2(a, b int) {
	fmt.Println(a + b)
}

add2(1, 2)

//다중 반환이 가능하고 아래와 같이 리턴 자료형을 선언해주면 됨
func add3(a, b int) (int, int) {
	return a + b, a - b
}

//다중 반환된 결과값을 각각의 변수에 저장 후 출력
var return1, return2 = add3(3, 2)
	fmt.Println(return1, return2)

//네임드 리턴이 가능함 (반환하는 값의 이름을 미리 헤더에 명시함 add, sub)
func add4(a, b int) (add, sub int) {
	add = a + b
	sub = a - b
	return
}

//다중 반환된 결과값을 각각의 변수에 저장 후 출력
var return3, return4 = add4(100, 50)
fmt.Println(return3, return4)

// 변수에 함루슷 바인드하고 값처럼 사용 가능
var adder = func(a, b int) int {
	return a + b
}

//익명 함수를 변수에 바인드하고 값처럼 사용 가능
var moon = func(a, b string) (str1, str2 string) {
	str1 = a + "12341234"
	str2 = b + "43214321"
	return
}

var asdf1, asdf2 = moon("asdf", "asdf")
	fmt.Println(asdf1, asdf2)

//함수를 다른 함수의 인자로 사용 가능
func exec(oper func(int, int) int, a, b int) int {
	return oper(a, b)
}

var asdf3 = exec(adder, 3, 2)
fmt.Println(asdf3)

//가변함수 지원
func roopAdder(inputs ...int) (sum int) {
	for _, v := range inputs {
		sum += v
	}
	return
}

fmt.Println(roopAdder(1, 2, 3, 4, 6, 7, 87, 8, 8, 8, 8, 8, 8, 8, 8))

//클로저는 함수 밖 변수를 참조하는 함수값
func closureAdder() func(int) int {
	sum := 0 // 클로저
	return func(x int) int {
		sum += x
		return sum //클로저 반환
	}
}

sumClosure := closerAdder()
//클로저를 통해 sum변수에 접근가능하고 최신값을 기억함
fmt.Println(sumClosure(1)) //1
fmt.Println(sumClosure(2)) //3
fmt.Println(sumClosure(3)) //6

배열

배열

  • 크기가 정해진 동일한 자료형의 집합
//배열 선언 (3개의 크기의 int 자료형 배열)
var arr [3]int

//배열 번지별 값 세팅
arr[0] = 1
arr[1] = 2
arr[2] = 3
// 값 출력
fmt.Println("arr :", arr[0], arr[1], arr[2])

//배열 선언과 동시에 번지별 값 초기화
var arr2 = [3]int{1, 2, 3}
//값 출력
fmt.Println("arr2 :", arr2[0], arr2[1], arr2[2])

//배열의 길이 구하기
fmt.Println(len(arr))

//배열의 지정된 범위 구하기
fmt.Println(arr[0:3])

//처음 번지부터 선택
fmt.Println(arr[:3])

//끝번지까지 선택
fmt.Println(arr[1:])

슬라이스

  • 크기가 정해지지 않은 동일한 자료형의 집합
//슬라이스 (동적배열)
var sli= []int{1, 2, 3, 4, 5}
fmt.Println(sli)
var sli2= []int{}
fmt.Println(sli2)

//초기값이 비어있는 슬라이스 생성(갯수 지정)
var sli3 = make([]int, 5)
fmt.Println(sli3)

//배열의 용량 확인
fmt.Println(cap(sli3))

//배열 크기 늘리기
sli3 = append(sli3, 1,2,3) // [1 0 0 0 0 1 2 3]

  • 키-값의 집합
  • map[키 자료형]값 자료형 : map 선언
  • map[키 자료형]값 자료형{키:값} : map 선언과 동시에 초기화
//map 선언 
var suMap = map[int]string{1: "one", 2: "two", 3: "three"}

//map 값이 있으면 해당값, 없으면 ok에 false가 들어옴
var asdf4, ok = suMap[5]
fmt.Println(asdf4, ok) // false

//map 5라는 키값에 five라는 값 초기화
myMap[5] = "five"
asdf4, ok = suMap[5] 
fmt.Println(asdf4, ok) // five

//맵의 5라는 키값에 저장되어 있는 값 삭제
delete(suMap, 5)
asdf4, ok = suMap[5] // false
fmt.Println(asdf4, ok)

구조체

  • 여러 자료형의 집합
  • 구조체나 필드를 다른 패키지에서 접근하려면 구조체 명과 필드의 첫글자가 대문자여야 함.
//구조체 선언
type member struct{
	id int
	name string
	hobby []string
}

//구조체 초기화 방법1
var s = member{
	id : 1,
	name : "hamletshu",
	hobby : []string{"축구","농구"},
}

//구조체 초기화 방법2
var s = member{1,"hamletshu",[]string{"축구","농구"}}

//구조체 초기화 방법3
var s = member{}
s.id = 1
s.name = "hamletshu"
s.hobby = []string{"축구","농구"}

메서드

  • 타입에 연결하는 함수
  • getter 와 setter 처럼 연결해서 사용할 수 있음
//member 패키지 (외부 패키지라서 구조체명과 필드 첫문자가 대문자임)
type Member struct {
	Id    int
	Name  string
	Hobby string
}

func (m Member) GetId() int {
	return m.Id
}

func (m Member) GetName() string {
	return m.Name
}

func (m Member) GetHobby() string {
	return m.Hobby
}

func (m *Member) SetId(id int) {
	m.Id = id
}

func (m *Member) SetName(name string) {
	m.Name = name
}

func (m *Member) SetHobby(hobby string) {
	m.Hobby = hobby
}

//main 패키지
import "member"
var m = member.Member{
	Id:    1,
	Name:  "hamletshu",
	Hobby: "축구",
}

fmt.Println(m.GetId())//1
m.SetId(2)
fmt.Println(m.GetId())//2

//임베딩 : 상속받은 메서드와 필드에 바로 접근 가능
//vip 패키지
package vip
import "member"

type Vip struct {
	member.Member
	vipId int
}

//main 패키지
import "vip"
var v = vip.Vip{}
fmt.Println(v.GetId()) // 0

인터페이스

  • 메서드의 집합
  • 상속 받으려면 메서드만 구현을 하면 됨 (확장성)
  • 타입 단언 : 인터페이스 타입의 값을 특정 타입으로 변환
type MemberInterface interface{
	GetId()int
	GetName() string
	GetHobby() string
}

var memberInterface MemberInterface
var m = Member{}
m.Id = 1
m.Name = "hamletshu"
m.Hobby = "축구"

//인터페이스 사용 방법1
memberInterface = m
fmt.Println(memberInterface.GetId())
fmt.Println(memberInterface.GetName())
fmt.Println(memberInterface.GetHobby())

//인터페이스 사용 방법2
func printLnGetId(i memberInterface){
	fmt.Println(i.GetId())
}
func printLnGetName(i memberInterface){
	fmt.Println(i.GetName())
}
func printLnGetName(i memberInterface){
	fmt.Println(i.GetName())
}

//타입 단언
member, ok := memberinterface.(Member)

조건문

if문

  • 소괄호 없는 것과 변수 초기화 하는 것이 있다는 것 빼고는 java랑 비슷해보임
  • if , else if , else 가 있음
var math = 100
if math == 100 {
	fmt.Println("수학 점수가 만점입니다.")
}else if math > 90 {
	fmt.Println("수학 점수가 우수한 학생입니다")
}else{
	fmt.Println("수학 점수가 애매한 학생입니다")
}

//변수 초기화하고 조건 주기
if math := 100; math == 100 {
	fmt.Println("수학 점수가 만점입니다.")
}else if math > 90 {
	fmt.Println("수학 점수가 우수한 학생입니다")
}else{
	fmt.Println("수학 점수가 애매한 학생입니다")
}

switch문

  • go는 break가 따로 없다 (자동으로 멈춤)
  • break 하지 않으려면 fallthrough 사용
switch math {
	case 100:
		fmt.Println("수학 점수가 만점입니다.")
	default:
		fmt.Println("수학 점수가 만점이 아닙니다.")
}

//초기화와 동시에 조건 주기
switch math := 90; math {
	case 100:
		fmt.Println("수학 점수가 만점입니다.")
	default:
		fmt.Println("수학 점수가 만점이 아닙니다.")
}

//if문 처럼 사용
switch math := 80; {
	case 100 == math:
		fmt.Println("수학 점수가 만점입니다.")
	case math >= 90:
		fmt.Println("수학 점수가 우수합니다.")
	case math >= 80:
		fmt.Println("수학 점수가 평범합니다.")
	case math >= 70:
		fmt.Println("수학 점수가 그닥입니다.")
	default:
		fmt.Println("수학 점수를 모르는게 낫습니다")
}

반복문

  • while문 없으며 for로 모든 반복문 사용
  • 슬라이스나 배열의 경우 for range 문 사용
//1부터 10까지 출력하는 반복문
for i:=1; i<=10; i++{
	fmt.Println(i)
}

//슬라이스나 배열 반복문 
for i, list := range sli {
	fmt.Println(i, "번째 값:", list)
}

//인덱스가 필요 없을 때 _처리
for _, list := range mySlice {
	fmt.Println(list)
}

//인덱스만 필요할 때
for i := range mySlice {
	fmt.Println(i)
}

panic, recover, defer

panic

  • 프로그램이 종료될 때까지 작업을 중단하는 함수
package main

import (
	"fmt"
)

func panicStart(p bool){
	if p {
		panic("panic!!!")
	}
}

func main() {
	panic(true)
	fmt.Println("Hello, playground")
}

//작업을 중지하면서 아래와 같은 에러가 발생함
./prog.go:15:2: unreachable code
Go vet exited.

panic: true

goroutine 1 [running]:
main.main()
	/tmp/sandbox353976549/prog.go:14 +0x39

recover, defer

  • recover : 함수가 종료되기 시작했을 때 정상 작동하도록 만드는 함수
  • defer : LIFO 방식으로 실행한 함수가 끝날 경우 순서대로 실행하게 해주는 예약어
package main

import (
	"fmt"
)

func testPanic() {
    defer func() {
        r := recover()
        if r != nil {
            fmt.Println("Recovered in test_panic : ", r)
        }
    }()
 
    fmt.Println("Hello")
		//panic 함수로 인해 함수가 종료되기 시작했을 때 defer 함수가 실행됨
    panic("Panic Error :D")
    fmt.Println("World!")
}
 
func main() {
    testPanic()
}

//결과 값
./prog.go:17:5: unreachable code
Go vet exited.

Hello
Recovered in test_panic :  Panic Error :D

고루틴

  • 경량 스레드
  • 함수 앞에 go 만 붙여주면 됨
package main

import (
	"fmt"
	"time"
)

func sumLoop() {
	for i:=0; i<10; i++{
		fmt.Println(i)
	}
}
 
func main() {
	go sumLoop()
	// sumLoop가 작업을 끝낼 수 있도록 1초 기다려 줌.
	time.Sleep(1 * time.Second)
	fmt.Println("main end")
}

Go 채널

  • 고루틴 간의 연결 (고루틴이 다른 고루틴으로 값을 보내기 위한 통신 메커니즘)

일반 채널

  • 일반 채널 생성 : ch := make(chan int)
  • 채널을 통해 전달 받은 값 : su := <-ch
  • 채널에 값을 보낼 때 : ch <- 10
package main

import (
	"fmt"
	"time"
)

func send(ch chan int) {
	for i:=0; i<10; i++{
		ch <- i
	}
	close(ch)
}

func get(ch chan int){
	for{
		//채널이 닫혔는지 확인
		i, ok := <- ch
		if !ok{
			break
		}
		fmt.Println("send에서 전달 받은 값:", i)
	}
}
 
func main() {
	ch := make(chan int)
	go send(ch)
	go get(ch)
	//1초 대기
	time.Sleep(1 * time.Second)
	fmt.Println("main end")
}

//결과 값
send에서 전달 받은 : 0
send에서 전달 받은 : 1
send에서 전달 받은 : 2
send에서 전달 받은 : 3
send에서 전달 받은 : 4
send에서 전달 받은 : 5
send에서 전달 받은 : 6
send에서 전달 받은 : 7
send에서 전달 받은 : 8
send에서 전달 받은 : 9
main end

버퍼 채널

  • 버퍼 채널 생성 : ch := make(chan int, 10)
  • 채널을 통해 전달 받은 값 (일반 채널과 동일) : su := <-ch
  • 채널에 값을 보낼 때(일반 채널과 동일) : ch <- 10
  • 여러 값들을 버퍼에 저장 가능
package main
 
import "fmt"
 
func main() {
    c := make(chan int, 2)
    c <- 1
    c <- 2
    c <- 3
    fmt.Println(<-c)
    fmt.Println(<-c)
    fmt.Println(<-c)
}

//출력 값 (버퍼가 가득찼는데도 메시지를 계속 전송해서 에러 발생)
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox297177109/prog.go:9 +0x9f

Program exited: status 2.

select

  • 여러 채널을 동시에 제어 가능
  • default도 사용 가능
package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string)
    c2 := make(chan string)
    
    go func() {
        time.Sleep(time.Second * 1)
        c1 <- "first GoRoutine"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- "second GoRoutine"
    }()
    
    for i:=0; i < 2; i++ {
        select {
        case msg1 := <-c1:
            fmt.Println("Received: ", msg1)
        case msg2 := <-c2:
            fmt.Println("Received: ", msg2)
        }
    }
}

Sync 패키지

  • 잠금이 필요할 경우 사용
  • mutex : 여러 스레드의 접근을 제어하는 객체
  • WaitGroup : 실행 중인 고루틴을 기다렸다가 다름 코드를 실행
//일반 mutex
var mutex = &sync.Mutex{}

mutex.Lock()
//logic ...
mutex.Unlock()

//읽기/쓰기 mutex
var rwMutex = &sync.RWMutex{}

//읽기일 경우
rwMutex.RLock()
//읽기 로직...
rwMutex.RUnLock()

//쓰기일 경우
rwMutex.Lock()
//쓰기 로직...
rwMutex.Unlock()
//WaitGroup
package main

import (
  "fmt"
	"sync"
)

func send(ch chan int) {
  //내부 카운터 1 감소
	defer wg.Done()
	for i:=0; i<10; i++{
		ch <- i
	}
	close(ch)
}

func get(ch chan int){
  //내부 카운터 1 감소
	defer wg.Done()
	for{
		//채널이 닫혔는지 확인
		i, ok := <- ch
		if !ok{
			break
		}
		fmt.Println("send에서 전달 받은 값:", i)
	}
}

var wg = &sync.WaitGroup{}

func main() {
	ch := make(chan int)
  //내부 카운터 2로 설정
	wg.Add(2)
	go send(ch)
	go get(ch)
	wg.Wait()
	
}

//결과 값
send에서 전달 받은 : 0
send에서 전달 받은 : 1
send에서 전달 받은 : 2
send에서 전달 받은 : 3
send에서 전달 받은 : 4
send에서 전달 받은 : 5
send에서 전달 받은 : 6
send에서 전달 받은 : 7
send에서 전달 받은 : 8
send에서 전달 받은 : 9