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