1-3. 기본문법

1. package 및 package 관리

    아래는 golang의 기본 폴더 구조이다. golang에서는 패키지 관리를 위해 내부적으로 지원하는 명령어가 있다. 명령어는 go get gitURL (예 : go get github.com/mattn/go-sqlite3)를 이용하여 사용할 수 있다.

    go get을 사용하기 위해서는 인터넷이 되는 환경이여야 하며 외부 패키지를 다운받기 위해서는 해당 패키지가 github, bitbucket 또는 직접 구성한 git서버가 있어야 한다.

    이 말인즉슨 go에서 패키지 프로그램은 프로젝트 저장소 단위로 관리되어진다는 말이다. 

    ├── pkg     //패키지의 소스코드를 빌드해서 만들어진 라이브러리 파일(.a - ar archive 파일)이 위치
    │   
    ├── src     //패키지의 소스코드가 위치
    |      
    └── bin     //패키지가 main 함수를 포함할 경우 실행 파일이 만들어 지는데, 이들 실행파일이 복사됨 

https://github.com/coma333ryu/utils 해당 링크에 샘플용 라이브러리 파일을 올려두었다. 해당 내용을 go 패키지관리 시스템으로 다운로드 하는 방법은 아래와 같다.

1. go get github.com/coma333ryu/utils 명령어로 라이브러리를 로컬로 당겨온다.
2. GOPATH 아래에 /pkg/darwin_amd64/github.com/coma333ryu/utils.a 가 다운로드되었는지 확인한다.
3. 사용하고자 하는 라이브러리를 소스에서 import한 후 해당 라이브러리의 함수를 사용해 본다.

package main

import (
	"github.com/coma333ryu/utils"
)

func main() {
	println(utils.AddNum(1, 2, 3, 4, 5))
}

https://github.com/coma333ryu/exampleGolang/blob/master/basic0/basic0.go

  • golang 패키지 import시 표준 패키지는 GOROOT/pkg 에서 그리고 사용자 패키지나 3rd Party 패키지의 경우 GOPATH/pkg 에서 패키지를 찾게 된다.
  • init()함수 사용가능하고, init 함수는 패키지가 로드되면서 실행되는 함수로 별도의 호출 없이 자동으로 호출된다.
  • import에 alias를 사용가능함.
package main

import (
	mongo "other/mongo/db"
	mysql "other/mysql/db"
	_ "other/xlib" //xlib패키지 내부에 init함수가 있을시 init함수만 사용되어지고 나머지는 무시하겠다는 의미
)

func main() {
	mondb := mongo.Get()
	mydb := mysql.Get()
	//...
}

2. 기본골격

   package main
        import "fmt"
       func main() {
fmt.Println("Hell World")
}

    

  •   package  

    모든 go파일은 package선언으로 부터 시작되어진다. go 프로그램은 실행 프로그램과 라이브러리 두가지 종류로 분류된다.

    실행 프로그램으로 만들기 위해서는 반드시 package main으로 선언되어야 한다.

   

  •   import

    외부 package를 사용하기위해 쓴다. 

    라이브러리 타입의 go코드를 사용하기위해 사용된다.

    여러 라이브러리를 import할때는 아래와 같이 사용한다.

    import (
"testing"
"fmt"
    )

    

  •   func

    함수선언을 위해 사용된다.

    func ==> 함수선언

    main ==> 함수명

    () ==> 함수 인자값

    리턴값 ==> 인자값 옆에 공백을 주고 리턴값을 선언할 수 있음.

   

3. go 프로그램 실행

    go run 실행할 파일명.go 로 파일을 실행한다. (go run helloworld.go)


4. 변수, 상수

  • 변수
package main

import "fmt"

func main() {
	var a string = "initial"
	fmt.Println(a)

	var b, c int = 1, 2
	fmt.Println(b, c)

	var d = true        //타입추론
	fmt.Println(d)

	var e int
	fmt.Println(e)
        
        //Short Assignment Statement ( := ) 라고 부르며 var를 생략하고 변수생성시 사용
	//함수 내부에서만 사용가능
	f := "short"
	fmt.Println(f)

}
  • 상수
package main

import "fmt"

const c int = 10
const s string = "Hi"

// const c = 10
// const s = "Hi"

const (
	Visa   = "Visa"
	Master = "MasterCard"
	Amex   = "American Express"
)

//상수값을 0부터 순차적으로 부여하기 위해 iota 라는 identifier를 사용할 수 있다. 
const (
	Apple  = iota // 0
	Grape         // 1
	Orange        // 2
)

func main() {
	fmt.Println("Apple", Apple)
	fmt.Println("Grape", Grape)
}


5. 데이터 타입

boolean 타입
bool

String 타입
string: string은 한번 생성되면 수정될 수 없는 Immutable 타입임

정수형 타입
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

Float 및 복소수 타입
float32 float64 complex64 complex128

기타 타입
byte: uint8과 동일하며 바이트 코드에 사용
rune: int32과 동일하며 유니코드 코드포인트에 사용한다

  • 문자열 (``)와 ("")

    Back Quote (` `)로 둘러 싸인 문자열은 Raw String Literal이라 부른다. Raw String 그대로의 값을 갖는다.

    Double Quote (" ")로 둘러 싸인 문자열은 Interpreted String Literal이라 부른다. 복수 라인에 걸쳐 쓸 수 없으며, 인용부호 안의 Escape 문자열들은 특별한 의미로 해석된다.

package main

import "fmt"

func main() {
	rawLiteral := `aaaaa\n 
    bbbb\n 
    cccc`

	interLiteral := "dddd\neeee"

	fmt.Println("rawLiteral", rawLiteral)
	fmt.Println("interLiteral", interLiteral)
}
  • 타입변환
var iVal int = 100
var uintVal uint = uint(iVal)
var floatVal float32 = float32(iVal)
println(floatVal, uintVal)

str := "ABC"
bytes := []byte(str)
str2 := string(bytes)
println(bytes, str2)

https://github.com/coma333ryu/exampleGolang/blob/master/basic1/basic1.go


6. 조건문, 반복문

  • 조건문 (if)
package main

import "fmt"

func main() {
	//()가 없어도 된다.
	if 7%2 == 0 {
		fmt.Println("7 is even")
	} else {
		fmt.Println("7 is odd")
	}

	//if문 내부에서 간단한 실행문을 사용할 수 있음.
	//num값의 scope는 해당 if문 내에서만 사용가능.
	if num := 9; num < 0 {
		fmt.Println(num, "is negative")
	} else if num < 10 {
		fmt.Println(num, "has 1 digit")
	} else {
		fmt.Println(num, "has multiple digits")
	}

	// println("num======>", num)
}
  • 조건문(switch)
package main

import (
	"fmt"
	"time"
)

func main() {
	//case문에 break문이 필요없음. Go 컴파일러가 자동으로 break 문을 각 case문 블럭 마지막에 추가하시 때문임.
	//fallthrough문을 작성하여 아래의 casse문들을 실행 할 수 있음.
	i := 2
	fmt.Print("Write ", i, " as ")
	switch i {
	case 1:
		fmt.Println("one")
		fallthrough
	case 2:
		fmt.Println("two")
		fallthrough
	case 3:
		fmt.Println("three")
	}

	//case문에 여러개의 표현식을 사용할 수 있음.
	switch time.Now().Weekday() {
	case time.Saturday, time.Sunday:
		fmt.Println("It's the weekend")
	default:
		fmt.Println("It's a weekday")
	}

	//case문에 표현식을 작성 할 수 있음.
	score := 100
	switch {
	case score >= 90:
		println("A")
	case score >= 80:
		println("B")
	case score >= 70:
		println("C")
	case score >= 60:
		println("D")
	default:
		println("No Hope")
	}

	//switch문에 표현식이 없을 수도 있음.
	//이러한 경우 switch가 true라 생각하고 첫번째 case문을 실행함.
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("It's before noon")
	default:
		fmt.Println("It's after noon")
	}

	//다른 언어의 switch는 일반적으로 변수의 값을 기준으로 case로 분기하지만, Go는 그 변수의 Type에 따라 case로 분기할 수 있다.
	whatAmI := func(i interface{}) {
		switch t := i.(type) {
		case bool:
			fmt.Println("I'm a bool")
		case int:
			fmt.Println("I'm an int")
		default:
			fmt.Printf("Don't know type %T\n", t)
		}
	}
	whatAmI(true)
	whatAmI(1)
	whatAmI("hey")
}
  • 반복문(for)
package main

import (
	"fmt"
	"time"
)

func main() {
	//golang에서의 반복문은 for만 존재함.
	//()는 사용하지 않음. 사용시 컴파일 에러발생함.
	sum := 0
	for i := 1; i <= 100; i++ {
		sum += i
	}
	println("sum =========>", sum)

	//조건식만 사용하여도 가능함. (타 언어의 while문과 같이 사용하기 위해)
	num := 1
	for num <= 3 {
		fmt.Println(num)
		num = num + 1
	}

	//무한루프
	// for {
	//     println("Infinite loop")
	// }

	//for range문 (foreach와 비슷)
	names := []string{"AAA", "BBB", "CCC"}

	for index, name := range names {
		println(index, name)
	}
	//index가 필요 없을시 _문을 사용하여 무시할 수 있음.
	for _, name := range names {
		println(name)
	}

	//break : for문을 빠져나올 때 사용
	//continue : for 루프의 중간에서 나머지 문장들을 실행하지 않고 for 루프 시작부분으로 가려할 때 사용.
	//goto : 기타 임의의 문장으로 이동하기 위해 사용.
	var a = 1
	for a < 15 {
		if a == 5 {
			a += a
			continue // for루프 시작으로
		}
		a++
		if a > 10 {
			break //루프 빠져나옴
		}
	}
	if a == 11 {
		goto END //goto 사용예
	}
	println(a)

END:
	println("End")

    //break문이 레이블과 같이 사용되어 특정 레이블로 이동이 가능.
	idx := 0
L1:
	for {
		if idx == 0 {
			break L1
		}
	}

	println("OK")
}

7. 함수

  • 함수
package main

//func : 함수 키워드
//Pass By Value 파라메터 전달방식
func say(msg string) {
	println(msg)
}

//Pass By Reference
//*string 과 같이 파라미터가 포인터임을 표시
//say2함수 내부의 msg는 문자열이 아니라 문자열을 갖는 메모리 영역의 주소값
func say2(msg *string) {
	println(*msg)
	*msg = "World" //메시지 변경 (Dereferencing)
}

// Variadic Function (가변인자함수)
func say3(msg ...string) {
	for _, s := range msg {
		println(s)
	}
}

//함수에 리턴값이 있을 경우 리턴 타임을 파라메터() 다음에 기술한다.
//함수의 마지막에 return문과 함께 리턴값을 기술한다.
func sum(nums ...int) int {
	sum := 0
	for _, num := range nums {
		sum += num
	}
	return sum
}

func sum2(nums ...int) (int, int) {
	s := 0
	count := 0
	for _, n := range nums {
		s += n
		count++
	}
	return count, s
}

//Named Return Parameter : 리턴값들을 할당하여 리턴
//count와 total에 함수내부에서 직접 값을 할당함.
//return 문에는 아무 값들을 리턴하지 않지만, 그래도 리턴되는 값이 있을 경우에는 빈 return 문을 반드시 써 주어야 한다 (이를 생략하면 에러 발생).
func sum3(nums ...int) (count int, total int) {
	for _, n := range nums {
		total += n
	}
	count = len(nums)
	return
}

//재귀함수
func fact(n int) int {
	if n == 0 {
		return 1
	}
	return n * fact(n-1)
}

func main() {
	msg := "Hello"
	say(msg)
	//변수앞에 & 부호를 붙이면 msg 변수의 주소를 표시
	//msg 변수의 주소를 전달
	say2(&msg)

	println("======", msg) //변경된 메시지 출력

	say3("This", "is", "a", "book")
	say3("Hi")

	total := sum(1, 7, 3, 5, 9)
	println("total ========>", total)

	count, total := sum2(1, 7, 3, 5, 9)
	println("count and total ======> ", count, total)

	count1, total1 := sum3(1, 7, 3, 5, 9)
	println("count1 and total1 ======> ", count1, total1)

	println("fact(7)", fact(7))
}

  • 익명함수
package main

func calc(f func(int, int) int, a int, b int) int {
	result := f(a, b)
	return result
}

func main() {
	//익명함수 : 함수 이름이 없는 함수
	sum := func(n ...int) int {
		s := 0
		for _, i := range n {
			s += i
		}
		return s
	}

	result := sum(1, 2, 3, 4, 5)
	println(result)

	//변수 add 에 익명함수 할당
	add := func(i int, j int) int {
		return i + j
	}

	//add 함수 전달
	r1 := calc(add, 10, 20)
	println(r1)

	//직접 첫번째 파라미터에 익명함수를 정의함
	r2 := calc(func(x int, y int) int { return x - y }, 10, 20)
	println(r2)
}

  • 클로저
  • 함수 바깥에 있는 변수를 참조하는 함수값(function value)를 일컫는데, 이때의 함수는 바깥의 변수를 마치 함수 안으로 끌어들인 듯이 그 변수를 읽거나 쓸 수 있게 된다.
package main

func nextValue() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}

func main() {
	next := nextValue()

	println(next()) // 1
	println(next()) // 2
	println(next()) // 3

	anotherNext := nextValue()
	println(anotherNext()) // 1 다시 시작
	println(anotherNext()) // 2
}

8. 배열, 슬라이스

  • 배열
package main

func main() {
	var a [3]int
	a[0] = 1
	a[1] = 2
	a[2] = 3
	println(a[0])

	var a1 = [3]int{1, 2, 3}
	var a3 = [...]int{1, 2, 3} //배열크기 자동으로

	println(a1[1])
	println(a3[2])

	var multiArr = [2][3]int{
		{1, 2, 3},
		{4, 5, 6}, //끝에 콤마 추가
	}
	println("multiArr", multiArr[1][2])
}
  • 슬라이스
  • 배열을 기본으로 만들어진 좀더 기능이 많은 배열.
  • 고정된 크기를 미리 지정하지 않을 수 있고, 차후 그 크기를 동적으로 변경할 수도 있고, 또한 부분 배열을 발췌할 수도 있다.
  • 연속된 메모리공간을 활용하는 것이라 용량에 제한이 있다.
package main

import "fmt"

func main() {
	var slice1 []int        //슬라이스 변수 선언
	slice1 = []int{1, 2, 3} //슬라이스에 리터럴값 지정
	slice1[1] = 10
	fmt.Println(slice1)
	println(len(slice1), cap(slice1))

	//내장함수 make를 이용하여 슬라이스 생성
	//make 인자 ==> make(슬라이스 타입, 슬라이스 길이, 슬라이스 내부 배열의 최대 길이)
	slice2 := make([]int, 5, 10)
	println(len(slice2), cap(slice2)) // len 5, cap 10

	//슬라이스에 별도의 길이와 용량을 지정하지 않으면, 기본적으로 길이와 용량이 0 인 슬라이스를 만드는데, 이를 Nil Slice 라 하고, nil 과 비교하면 참을 리턴한다.
	var nilSlice []int

	if nilSlice == nil {
		println("Nil Slice")
	}
	println(len(nilSlice), cap(nilSlice))

	num := []int{1, 2, 3, 4, 5}
	fmt.Println("num =======> ", num)
	fmt.Println("len =======> ", len(num))
	fmt.Println("cap =======> ", cap(num))

	//append() ==> 슬라이스에 새로운 요소 추가.
	//append(슬라이스,추가할 값)
	//배열은 고정된 크기로 그 크기 이상의 데이타를 임의로 추가할 수 없지만, 슬라이스는 자유롭게 새로운 요소를 추가할 수 있다.
	//슬라이스 용량(capacity)이 아직 남아 있는 경우는 그 용량 내에서 슬라이스의 길이(length)를 변경하여 데이타를 추가하고,
	//용량(capacity)을 초과하는 경우 현재 용량의 2배에 해당하는 새로운 Underlying array 을 생성하고
	//기존 배열 값들을 모두 새 배열에 복제한 후 다시 슬라이스를 할당한다.
	num = append(num, 6)
	fmt.Println("new num =======> ", num)
	fmt.Println("new len =======> ", len(num))
	fmt.Println("new cap =======> ", cap(num))

	//슬라이스에 다른 슬라이스 추가하기.
	sliceA := []int{1, 2, 3}
	sliceB := []int{4, 5, 6}

	sliceA = append(sliceA, sliceB...)
	//sliceA = append(sliceA, 4, 5, 6)

	fmt.Println("sliceA ======> ", sliceA)

	//슬라이스 복사
	sliceC := make([]int, len(sliceA))
	copy(sliceC, sliceA)
	fmt.Println("copy sliceC ====> ", sliceC)

	//부분 슬라이스(Sub-slice)
	//슬라이스[처음인덱스:마지막인덱스], 마지막인덱스는 원하는 인덱스+1
        //num[:3] ==> 첫 인덱스(0번째)부터 3번째까지 : 1,2,3 (마지막인덱스는 원하는 인덱스+1 을 사용)
	//처음 인덱스가 생략되면 0 이, 마지막 인덱스가 생략되면 그 슬라이스의 마지막 인덱스가 자동 대입된다.
	sliced1 := num[:3]
	fmt.Println("sliced1 =======> ", sliced1)
	fmt.Println("sliced1 len =======> ", len(sliced1))
	fmt.Println("sliced1 cap =======> ", cap(sliced1))

	sliced3 := sliced1[:4]
	fmt.Println("sliced3 =======> ", sliced3)
	fmt.Println("sliced3 len =======> ", len(sliced3))
	fmt.Println("sliced3 cap =======> ", cap(sliced3))
}


9. map

  • 해시테이블(Hash table)을 구현한 자료구조
  • map[Key타입]Value타입으로 선언
package main

import (
	"fmt"
)

func main() {
	//map를 생성하기 위해서는 make()함수를 사용해야 한다.
	var idMap = make(map[int]string)

	idMap[0] = "Zero"
	idMap[1] = "One"
	idMap[2] = "Two"

	fmt.Println("idMap", idMap)

	//리터럴(literal)을 사용한 초기화
	idMap2 := map[int]string{
		0: "Zero Val",
		1: "One Val",
		2: "Two Val",
	}
	fmt.Println("idMap2", idMap2)

	//map 데이터 읽기
	fmt.Println("idMap[0]", idMap[0])
	fmt.Println("idMap2[1]", idMap2[1])

	//map 삭제
	//delete(삭제할 map,삭제할 key value)
	delete(idMap, 1)

	fmt.Println("after delete idMap", idMap)

	//map key의 존재유무
	// val : 해당 map의 value, exists : 해당 key의 존재유무(true,false)
	// val, exists := idMap2[3]
	_, exists := idMap2[3]
	if !exists {
		println("No idMap2[3] key")
	}

	//for range를 이용한 map데이터 읽기
	for key, val := range idMap2 {
		fmt.Println("idMap2 range", key, val)
	}
}


9. struct

  • 필드들로 이루어진 타입을 갖는 컬렉션이다.
  • 메서드가 없다.
  • 메서드는 없지만 receiver를 이용하여 메서드를 구현할 수 있다.
  • 타언어에서의 class와 유사한 역활을 한다.
package main

import (
    "fmt"
)

type person struct {
    name string
    age  int
}

type rect struct {
    width, height int
}

//person 생성자
func ㅜnewPerson(newName string, newAge int) *person {
    return &person{name: newName, age: newAge}
}

//golang용 메서드 value receiver
//rect가 복사되어 전달된다.
func (r rect) area() int {
    r.width = 20
    return r.width * r.height
}

//golang용 메서드 포인터 receiver
//reat의 포인터가 전달된다.
func (r *rect) area2() int {
    r.width++
    return r.width * r.height
}

//struct는 mutable이고, 다른 함수의 파라메터로 전달시 Pass by Value에 따라 객체를 복사해서 전달하게 된다.
func changePersonVal(personStruct person) person {
    personStruct.name = "New Person"
    personStruct.age = 15
    return personStruct
}

//그래서 Pass by Reference로 struct를 전달하고자 한다면, struct의 포인터를 전달해야 한다.
func changePersonVal2(personStruct *person) *person {
    personStruct.name = "New Person"
    personStruct.age = 15
    return personStruct
}

func main() {
    //struct 생성
    firstPerson := person{}
    secondPerson := person{"Second Person"11}
    //내장함수 new를 이용한 struct 생성
    //new를 이용하여 struct생성하면 반환되는 struct는 포인터값(*person)이 된다.
    thirdPerson := new(person)
    forthPerson := person{"Forth Person"14}
    newPerson := ㅜnewPerson("New Person"40)

    //person에 필드값 할당
    firstPerson.name = "First Person"
    firstPerson.age = 10

    thirdPerson.name = "Third Person"
    thirdPerson.age = 12

    fmt.Println("firstPerson", firstPerson)
    fmt.Println("secondPerson", secondPerson)
    fmt.Println("thirdPerson", *thirdPerson)
    fmt.Println("newPerson", newPerson)

    newPerson1 := changePersonVal(forthPerson)
    fmt.Println("forthPerson 111====>", forthPerson)
    fmt.Println("newPerson1", newPerson1)

    newPerson2 := changePersonVal2(&forthPerson)
    fmt.Println("newPerson2", newPerson2)
    fmt.Println("forthPerson 222====>", forthPerson)

    println("========================= start method =========================")
    //reat struct가 복사되어 전달되기 때문에 원천 reat의 값에 영향을 주지 않는다.
    rect := rect{1020}
    area := rect.area()
    fmt.Println("rect width and area", rect.width, area)

    //reat 포인터가 전달되기 때문에 원천 reat가 변경되어진다.
    area2 := rect.area2()
    fmt.Println("rect width and area new ", rect.width, area2)
}


10. interface

  • 메서드들의 집합
  • 메서드의 구현은 반듯이 struct를 이용해 구현된다.
  • 다른 interface에 상속이 가능하다.
  • interface는 하나의 타입으로 변수와 매개변수로 사용될 수 있다.
  • 빈 인터페이스(interface{})를 사용가능하며, 빈 인터페이스 안에 어떠한 타입이라도 담아서 사용가능하다.
package main

import "math"
import "fmt"

//Shape Interface 구현
//type명령어를 사용하고 interface라고 작성하면 됨.
// 메서드들의 집합
type Shape interface {
	area() float64
	perimeter() float64
}

//Rect 정의
type Rect struct {
	width, height float64
}

//Circle 정의
type Circle struct {
	radius float64
}

func (r Rect) area() float64 {
	return r.width * r.height
}

func (r Rect) perimeter() float64 {
	return 2 * (r.width + r.height)
}

func (c Circle) area() float64 {
	return math.Pi * c.radius * c.radius
}
func (c Circle) perimeter() float64 {
	return 2 * math.Pi * c.radius
}

//Interface를 함수의 파라메터로 받을 수 있다.
func useShapeInterface(shape Shape) {
	fmt.Println("Single Shape", shape)
	fmt.Println("Single area method", shape.area())
	fmt.Println("Single perimeter method", shape.perimeter())
}

//멀티 파라메터도 가능함.
func useMultiShapeInterface(shapes ...Shape) {
	for _, shape := range shapes {
		areaMethod := shape.area()
		perimeterMethod := shape.perimeter()
		fmt.Println("Multi Shape", shape)
		fmt.Println("Multi area method", areaMethod)
		fmt.Println("Multi perimeter method", perimeterMethod)
	}
}

func printEmptyInterface(v interface{}) {
	fmt.Println(v)
}

func main() {
	rect := Rect{width: 3, height: 5}
	circle := Circle{radius: 4}

	useShapeInterface(rect)
	useShapeInterface(circle)

	useMultiShapeInterface(rect, circle)

	var x interface{}
	x = 1
	x = "Tom"

	printEmptyInterface(x)
}


11. error

  • 내장 타입으로 error 라는 interface 타입을 갖는다.
package main

import "errors"
import "fmt"

//사용자 정의용 Error struct
type paramError struct {
	param int
	msg   string
}

//Error method 구현
func (e *paramError) Error() string {
	return fmt.Sprintf("%d - %s", e.param, e.msg)
}

//errors package를 이용한 error처리
func checkFunction1(param int) (int, error) {
	if param == 42 {
		return -1, errors.New("It is 42")
	}

	return param + 3, nil
}

//사용자 정의 paramError struct를 이용한 error처리
func checkFunction2(param int) (int, error) {
	if param == 42 {
		return -1, &paramError{param, "This param is error."}
	}

	return param + 3, nil
}

func main() {
	for _, i := range []int{7, 42} {
		if r, e := checkFunction1(i); e != nil {
			fmt.Println("checkFunction1 failed:", e)
		} else {
			fmt.Println("checkFunction1 worked:", r)
		}
	}

	for _, i := range []int{7, 42} {
		if r, e := checkFunction2(i); e != nil {
			fmt.Println("checkFunction2 failed:", e)
		} else {
			fmt.Println("checkFunction2 worked:", r)
		}
	}

	_, e := checkFunction2(42)
	if ae, ok := e.(*paramError); ok {
		fmt.Println(ae.param)
		fmt.Println(ae.msg)
	}
}

12. defer && panic && recover

  • defer는 특정 문장 혹은 함수를 나중에 (defer를 호출하는 함수가 리턴하기 직전에) 실행하게 한다.
  • panic은 현재 함수를 즉시 멈추고 현재 함수에 defer 함수들을 모두 실행한 후 즉시 리턴한다. 이러한 panic 모드 실행 방식은 상위함수에도 똑같이 적용되고, 계속 콜스택을 타고 올라가며 적용된다. 그리고 마지막에는 프로그램이 에러를 내고 종료하게 된다.
  • recover는 panic 함수에 의한 패닉상태를 다시 정상상태로 되돌리는 함수이다.
package main

import (
	"fmt"
	"os"
)

func firstStart() {
	fmt.Println("First Start Function")
}

func secondStart() {
	fmt.Println("Second Start Function")
}

func thirdStart() {
	defer fmt.Println("Third End Function")
	fmt.Println("Third Start Function")
}

func openFile(fn string) {
	//panic 함수에 의한 패닉상태를 다시 정상상태로 되돌리는 함수
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("OPEN ERROR", r)
		}
	}()

	f, err := os.Open(fn)
	//현재 함수를 즉시 멈추고 현재 함수에 defer 함수들을 모두 실행한 후 즉시 리턴
	if err != nil {
		panic(err)
	}

	defer f.Close()

}

func main() {
	thirdStart()
	//특정 문장 혹은 함수를 나중에 (defer를 호출하는 함수가 리턴하기 직전에) 실행
	defer secondStart()
	firstStart()

	openFile("Invalid.txt")
	fmt.Println("Done")
}

13. goroutine

  • Go 런타임이 관리하는 경량의 실행 스레드이다.
  • "go" 키워드를 사용하여 함수를 호출하면, 런타임시 새로운 goroutine을 실행한다.
  • goroutine은 비동기적으로 함수루틴을 실행하므로, 여러 코드를 동시에 실행하는데 사용된다.
  • 익명함수에 대해 사용할 수도 있다.
package main

import (
	"fmt"
	"sync"
	"time"
)

func say(s string) {
	for i := 0; i < 10; i++ {
		fmt.Println(s, "***", i)
	}
}

func main() {
	say("Sync")

	go say("Async1")
	go say("Async2")
	go say("Async3")

	time.Sleep(time.Second * 3)

	var wait sync.WaitGroup
	wait.Add(2)

	go func() {
		defer wait.Done()
		fmt.Println("Hello")
	}()

	go func(msg string) {
		defer wait.Done()
		fmt.Println(msg)
	}("Hi")

	wait.Wait()
}

14. channel

  • goroutine들 사이 데이타를 주고 받는데 사용된다.
  • 한 채널이 준비될 때까지 다른 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이타를 동기화하는데 사용된다.
  • 기본채널은 동기적 수행.
  • close()함수를 사용하여 채널을 닫을 수 있다. 채널을 닫게 되면, 해당 채널로는 더이상 송신을 할 수 없지만, 채널이 닫힌 이후에도 계속 수신은 가능하다.
package main

import "fmt"

func main() {
	// c := make(chan int)
	//수신루틴이 없으므로 데드락
	// c <- 1
	// fmt.Println(<-c)

	//버퍼채널 생성
	//make(chan type, N) : N는 생성할 버퍼의 갯수입력.
	ch := make(chan int, 1)

	//수신자가 없더라도 보낼 수 있다.
	ch <- 101

	fmt.Println(<-ch)

	ch2 := make(chan int, 2)

	// 채널에 송신
	ch2 <- 1
	ch2 <- 2

	// 채널을 닫는다
	close(ch2)

	// 채널 수신
	println(<-ch2)
	println(<-ch2)
        
        //채널 수신시 return값은 채널 메세지와, 닫힘여부(true/false)이다.
	_, success := <-ch2

	if !success {
		println("더이상 데이타 없음.")
	}

	//채널값을 가져올 때 range문 사용가능.
	// for channelMsg := range ch2 {
	// 	println(channelMsg)
	// }

}


14-1. Unbuffered Channel, Buffered Channel

  • Unbuffered Channel : 하나의 수신자가 데이타를 받을 때까지 송신자가 데이타를 보내는 채널에 묶여 있게 된다.
  • Buffered Channel : 수신자가 받을 준비가 되어 있지 않을 지라도 지정된 버퍼만큼 데이타를 보내고 계속 다른 일을 수행할 수 있다.
  • 버퍼채널은 비동기적 수행.
package main

import "fmt"

func main() {
	// c := make(chan int)
	//수신루틴이 없으므로 데드락
	// c <- 1
	// fmt.Println(<-c)

	//버퍼채널 생성
	//make(chan type, N) : N는 생성할 버퍼의 갯수입력.
	ch := make(chan int, 1)

	//수신자가 없더라도 보낼 수 있다.
	ch <- 101

	fmt.Println(<-ch)
}

14-2. 채널 송수신 방향

  • 채널을 함수의 파라미터도 전달할 때, 일반적으로 송수신을 모두 하는 채널을 전달하지만, 특별히 해당 채널로 송신만 할 것인지 혹은 수신만할 것인지를 지정할 수도 있다.
  • 송신 파라미터는 (p chan<- int)와 같이 chan<- 을 사용하고, 수신 파라미터는 (p <-chan int)와 같이 <-chan 을 사용한다.
  • 만약 송신 채널 파라미터에서 수신을 한다거나, 수신 채널에 송신을 하게되면, 에러가 발생한다.
package main

import "fmt"

func main() {
	ch := make(chan string, 1)
	sendChan(ch)
	receiveChan(ch)
}

//송신채널 파라메터
func sendChan(ch chan<- string) {
	ch <- "Data"
	// x := <-ch // 에러발생
}

//수신채널 파라메터
func receiveChan(ch <-chan string) {
	data := <-ch
	fmt.Println(data)
}

14-3. select

  • 복수 채널들을 기다리면서 준비된 (데이타를 보내온) 채널을 실행하는 기능을 제공한다.
  • 여러 개의 case문에서 각각 다른 채널을 기다리다가 준비가 된 채널 case를 실행하는 것이다.
  • case 채널들이 준비되지 않으면 계속 대기하게 되고, 가장 먼저 도착한 채널의 case를 실행한다.
  • 만약 복수 채널에 신호가 오면, Go 런타임이 랜덤하게 그 중 한 개를 선택한다.
  • 하지만, select문에 default 문이 있으면, case문 채널이 준비되지 않더라도 계속 대기하지 않고 바로 default문을 실행한다.
package main

import (
	"fmt"
	"time"
)

func main() {
	/*
		    c1 := make(chan string)
			c2 := make(chan string)

			go func() {
				time.Sleep(time.Second * 1)
				c1 <- "one"
			}()
			go func() {
				time.Sleep(time.Second * 2)
				c2 <- "two"
			}()

			for i := 0; i < 2; i++ {
				select {
				case msg1 := <-c1:
					fmt.Println("received", msg1)
				case msg2 := <-c2:
					fmt.Println("received", msg2)
				}
			}
	*/

	c1 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c1 <- "result 1"
	}()

	select {
	case res := <-c1:
		fmt.Println(res)
	case <-time.After(time.Second * 1):
		fmt.Println("timeout 1")
	}

	c2 := make(chan string, 1)
	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "result 2"
	}()
	select {
	case res := <-c2:
		fmt.Println(res)
	case <-time.After(time.Second * 3):
		fmt.Println("timeout 2")
	}
}