ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 문법 및 사용법 예시
    Language/Golang 2023. 2. 7. 21:23

    코드의 기본 형식

    1. 세미콜론(;) 이 존재하지 않습니다.
    2. 키워드 사용시 조건에 대한 소괄호가 존재하지 않습니다.
    3. 함수정의시 중괄호는 파라미터의 소괄호 옆에 있어야 합니다.
    4. 각 구문별로 들여쓰기가 알맞게 삽입되어야 합니다.
    5. 소스코드파일은 유니코드(utf-8) 이어야 합니다.
    6. 주석은 C/C++ 혹은 Java 와 동일하지만 CGO를 사용할 경우 다른 사용법 또한 존재합니다.
    7. 시작 프로그램 함수는 package 가 main 이어야 하며, 함수명 또한 main 이어야 합니다.
    8. 변수를 할당 혹은 선언 후 사용하지 않을 경우 "_" 로 처리하거나 코드상에서 삭제되어야 합니다.

    이와 같은 기본 형식이 지켜지지 않았을 경우 빌드에 실패하며 기본 사용 예시는 다음과 같습니다.

    package main
    import "fmt"
    
    func main() {
        //Test Code
        /*
        Test Code
        */
        var i int = 10
    
        if i == 0 {
            fmt.Println(i)
        }
    
        for i := 0; i < 5; i++ {
            fmt.Println(i)
        }
    }

     

    변수 선언

    변수 선언의 기본적인 형식은 다음과 같습니다.

    var 라는 키워드 뒤에 변수명과 변수타입이 붙습니다.

    var [변수명] [변수타입]
    
    var nTest int

     

    변수 선언시 초기값을 설정할 경우 다음과 같이 사용할 수 있습니다.

    var nTest int = 10 
    var nTest = 10

     

    함수 내부에서라면 초기값을 지정함에 따라서 아래와 같이 더욱 축약하여 활용할 수 있습니다.

    nTest := 10

    위와 같은 경우는 암묵적으로 nTest를 int형이라 지정하며, ":=" Short Assignment Statement 라 불리는 방식을 통해 선언과 동시에 할당합니다.

     

    다수개의 변수를 일시에 선언할때는 다음과 같습니다.

    var n1,n2 int = 1,2
    n3,n4 := 10, 20

     

    함수 정의

    Go 의 함수의 기본형식은 다음과 같습니다.

    함수 정의시에도 변수 선언과 동일하게 파라미터는 변수명 뒤에 변수타입이 붙습니다.

    리턴 타입을 입력하지 않는 경우 기본 void 형식으로 처리됩니다.

    func 함수명(파라미터) 리턴타입 {
    
    }
    
    func Test(a int, b string, c bool) bool {
    
    }

     

    Go 에서 함수정의의 특이점은 다수개의 리턴값을 일시에 받을 수 있다는 점입니다.

    func Test() (bool,string,int) {
        return (false, "Test", 0)
    }
    
    func main() {
        a,b,c := Test()
    }

     

    Go 에서는 함수내에서 새로운 함수를 정의하여 사용할 수 있습니다. 해당 기능은 익명함수라 명칭합니다.

    package main
    
    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)
    }

     

    함수정의에서 파라미터로 함수를 받는 경우 다음과 같이 활용가능합니다.

    package main
    
    func main() {
        //변수 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)
    
    }
    
    func calc(f func(int, int) int, a int, b int) int {
        result := f(a, b)
        return result
    }

     

    또한 리턴값을 함수를 활용해서 사용가능합니다.

    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
    }

     

    type 을 통한 함수의 원형을 정의할 수 있습니다.

    함수원형 정의는 구조체나 인터페이스 등에 활용하기 위해 사용됩니다.

    // 원형 정의
    type calculator func(int, int) int
    
    // calculator 원형 사용
    func calc(f calculator, a int, b int) int {
        result := f(a, b)
        return result
    }

     

    Go 에서는 하나의 함수에서 다수의 리턴값을 받을 수 있습니다.

    package main
    import "fmt"
    
    func Test() (int,int,int) {
        return 1,2,3
    }
    
    func main() {
        a,b,c := Test()
        fmt.Println(a,b,c)
    }
    1 2 3

     

    Type 변환 ( 형변환 )

    Go 에서 형변환은 자동으로 바꿀수 없으며 기본적으로 사용되는 방법은 다음과 같습니다.

    n := 15
    n2 := float64(n)
    n3 := int64(n)
    s := strconv.Itoa(n)

    전역변수

    전역변수는 다른 패키지에서 해당 값을 참조할 수 있습니다.

    다만 변수명의 첫글자가 소문자의 경우 Private 로 지정되며, 다른 패키지에서 참조할 수 없습니다.

    main.go
    package main
    
    import "fmt"
    
    import "test"
    
    var nInt = 10
    
    func main() {
        fmt.Println(nInt)
        fmt.Println(test.INum)
        fmt.Println(test.iStr) // undefined: test.iStr
    }
    
    test.go
    package test
    
    var INum = 10
    var iStr = "a"

     

    배열

    배열은 연속적인 메모리 공간에 동일한 타입의 데이타를 순서적으로 저장하는 자료구조이며, C/C++ 과 동일하게 0번부터 시작합니다.

    배열의 기본 형식은 다음과 같습니다.

    package main
    
    import "fmt"
    
    func main() {
        var a [3]int  //정수형 3개 요소를 갖는 배열 a 선언
        a[0] = 1
        a[1] = 2
        a[2] = 3
        fmt.Println(a[1]) // 2 출력
    }

     

    배열에 콜론을 활용한 데이터 접근 또한 가능합니다. 이를 슬라이스라고 합니다.

    package main
    
    import(
        "fmt"
    )
    
    func main(){
        var nInt = []int{1,2,3,4}
    
        fmt.Println(nInt[:1]) // [1]
        fmt.Println(nInt[1:]) // [2 3 4]
        fmt.Println(nInt[1:3]) // [2 3]
    }

     

    "string" 변수의 경우에도 배열로써 활용가능하며, 특정한 길이만큼 잘라낼 경우 다음과 같이 활용 할 수 있습니다.

    끝에 "/" 문자열이 붙은 경우 제거합니다.

    func SuffixSlashRemove(url string) string {
        if strings.HasSuffix(url, "/") {
            url = url[:len(url)-1]
        }
    
        return url
    }

     

    기본 배열만을 할당한 채, 차후 필요한 값들을 채워넣는 vector 와 같은 사용도 가능합니다.

    package main
    
    import "fmt"
    
    func main() {
        var nList []string
    
        nList = append(nList, "abc")
        nList = append(nList, "def")
        nList = append(nList, "ghi")
    
        fmt.Println(nList) // [abc def ghi]
    }

    조건문

    if

    if 문은 해당 조건이 맞으면 { } 블럭안의 내용을 실행하며 Go의 if 조건문은 아래 예제에서 보듯이 조건식을 괄호( )로 둘러 싸지 않아도 됩니다.

    반드시 조건 블럭 시작 브레이스({)를 if문과 같은 라인에 두어야 합니다.

    if k == 1 {
        println("One")
    } else if k == 2 {  //같은 라인
        println("Two")
    } else {   //같은 라인
        println("Other")
    }

     

    if 문에서 조건식을 사용하기 이전에 간단한 문장(Optional Statement)을 함께 실행할 수 있습니다. 즉, 아래 예제처럼 val := i*2 라는 문장을 조건식 이전에 실행할 수 있는데, 여기서 주의할 점은 이때 정의된 변수 val는 if문 블럭 (혹은 if-else 블럭 scope) 안에서만 사용할 수 있다는 것입니다.

    이러한 Optional Statement 표현은 아래의 switch문, for문 등 Go의 여러 문법에서 사용할 수 있습니다.

    if val := i * 2; val < max {
        println(val)
    }
    
    val++ // 아래 처럼 사용하면 Scope 벗어나 에러

     

    switch

    다른 언어들과 비슷하게 switch 문 뒤에 하나의 변수(혹은 Expression)를 지정하고, case 문에 해당 변수가 가질 수 있는 값들을 지정하여, 각 경우에 다른 문장 블럭들을 실행할 수 있습니다.

    복수개의 case 값들이 있을 경우는 아래 예제에서 보듯이 case 3,4 처럼 콤마를 써서 나열할 수 있습니다.

    package main
    
    func main() {
        var name string
        var category = 1
    
        switch category {
        case 1:
            name = "Paper Book"
        case 2:
            name = "eBook"
        case 3, 4:
            name = "Blog"
        default:
            name = "Other"
        }
        println(name)
    
        // Expression을 사용한 경우
        switch x := category << 2; x - 1 {
            //...
        }   
    }

     

    Go의 switch 문에서 한가지 특징적인 용법은 switch 뒤에 조건변수 혹은 Expression을 적지 않는 용법이며 아래와 같이 사용합니다.

    func grade(score int) {
        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 에서 fallthrough 를 활용하면 해당 조건 이후 하위 코드를 실행합니다.

    package main
    
    import "fmt"
    
    func main() {
        check(2)
    }
    
    func check(val int) {
        switch val {
        case 1:
            fmt.Println("1 이하")
            fallthrough
        case 2:
            fmt.Println("2 이하")
            fallthrough
        case 3:
            fmt.Println("3 이하")
            fallthrough
        default:
            fmt.Println("default 도달")
        }
    }
    2 이하
    3 이하
    default 도달

     

    반복문

    Go 에서는 while 문이 존재하지 않으며 for 문만을 사용할 수 있습니다.

    C/C++ 혹은 Java 와 같은 형식으로 활용가능하며 키워드 조건에 괄호는 존재하지 않습니다.

    break 및 continue 사용은 다른 언어와 동일합니다.

    package main
    
    import "fmt"
    
    func main() {
        sum := 0
        for i := 1; i <= 100; i++ {
            sum += i
        }
        fmt.Println(sum)
    }

     

    조건만을 활용하여 while 과 같이 루프문을 사용할 수 있습니다.

    package main
    
    import "fmt"
    
    func main() {
        n := 1
        for n < 100 {
            n *= 2      
            //if n > 90 {
            //   continue 
            //}     
        }
        fmt.Println(n)
    }

     

    무한 루프의 경우 조건 없이 for문을 구성함으로써 활용할 수 있습니다.

    package main
    
    import "fmt"
    
    func main() {
        for {
            fmt.Println("Infinite loop")     
            //break   
        }
    }

     

    range 키워드를 활용하여 map 이나 배열에 대하여 값을 얻어오는 반복문을 구성할 수 있습니다.

    다만 map 의 경우 입력된값이 순차적으로 나오는것이 아닌, 임의대로 출력됩니다. 따라서 map을 통해 순차적인 루프를 할 경우 키 정렬을 통한 루프가 필요합니다.

    아래 예제의 map 의 루프 결과 순서는 실행시 마다 변경될 수 있습니다.

    map 은 초기값을 지정하지 않는 경우 make 명령을 통한 메모리생성 및 초기화를 시켜야 사용할 수 있습니다.

    range 의 결과로 생성한 변수는 반복문 내에서만 사용할 수 있습니다.

    package main
    import "fmt"
    
    func main() {
        names := []string{"홍길동", "이순신", "강감찬"}
    
        for index, name := range names {
            fmt.Println(index, name)
        }
    
        tickers := map[string]string{
            "GOOG": "Google Inc",
            "MSFT": "Microsoft",
            "FB":   "FaceBook",
        }
    
        for key, value := range tickers {
            fmt.Println(key,value)
        }
    
        maps := make(map[string]string)
        maps["a"] = "b"
        maps["c"] = "d"
        maps["e"] = "f"
    
        val, exist := maps["x"]
    
        if exist {
            fmt.Println(val)
        }
    
        fmt.Println("Not Exist x in maps")
    
        for key, value := range maps {
            fmt.Println(key,value)
        }
    }

     

    panic

    Go 내장함수인 panic()함수는 현재 함수를 즉시 멈추고 현재 함수에 defer 함수들을 모두 실행한 후 즉시 리턴합니다.

    panic 모드 실행 방식은 다시 상위함수에도 똑같이 적용되고, 계속 콜스택을 타고 올라가며 적용됩니다.

    마지막에는 프로그램이 에러를 내고 종료하게 됩니다.

    package main
    
    import "os"
    
    func main() {
        // 잘못된 파일명을 넣음
        openFile("Invalid.txt")
    
        // openFile() 안에서 panic이 실행되면
        // 아래 println 문장은 실행 안됨
        println("Done") 
    }
    
    func openFile(fn string) {
        f, err := os.Open(fn)
        if err != nil {
            panic(err) // panic: open Invalid.txt: The system cannot find the file specified.
        }
    
        defer f.Close()
    }
    panic: open Invalid.txt: The system cannot find the file specified.
    
    goroutine 1 [running]:
    main.openFile(0x47be61, 0xb)
        C:/Users/bridgetec/AppData/Local/liteide/goplay.go:17 +0x96
    main.main()
        C:/Users/bridgetec/AppData/Local/liteide/goplay.go:7 +0x2b
    exit status 2

     

    별도의 panic 함수를 호출하지 않더라도 프로그램 실행 중 오류가 발생되는 경우 실행됩니다.

    package main
    
    import "fmt"
    
    func main() {
        var a interface{} = 15
        fmt.Println(a.(string))
    }
    panic: interface conversion: interface {} is int, not string
    
    goroutine 1 [running]:
    main.main()
        C:/Users/bridgetec/AppData/Local/liteide/goplay.go:7 +0x37
    exit status 2

     

    panic의 발생 시 정상상태로 되돌리기 위해서는 recover 를 사용합니다.

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main() {
        // 잘못된 파일명을 넣음
        openFile("Invalid.txt")
    
        // recover에 의한 실행
        println("Done") 
    }
    
    func openFile(fn string) {
        // defer 함수. panic 호출시 실행됨
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("OPEN ERROR", r)
            }
        }()
    
        f, err := os.Open(fn)
        if err != nil {
            panic(err)
        }
    
        defer f.Close()
    }
    OPEN ERROR open Invalid.txt: The system cannot find the file specified.
    Done

     

    Goroutine

    고루틴(goroutine)은 Go 런타임이 관리하는 쓰레드입니다.

    Go에서 "go" 키워드를 사용하여 함수를 호출하면, 런타임시 새로운 goroutine을 실행됩니다.

    goroutine은 비동기적으로(asynchronously) 함수루틴을 실행하므로, 여러 코드를 동시에(Concurrently) 실행하는데 사용됩니다.

    goroutine은 OS 쓰레드보다 훨씬 가볍게 비동기 Concurrent 처리를 구현하기 위하여 만든 것으로, 기본적으로 Go 런타임이 자체 관리합니다.

    Go 런타임 상에서 관리되는 작업단위인 여러 goroutine들은 종종 하나의 OS 쓰레드 1개로도 실행되곤 한다. 즉, Go루틴들은 OS 쓰레드와 1 대 1로 대응되지 않고, Multiplexing으로 훨씬 적은 OS 쓰레드를 사용합니다.

    메모리 측면에서도 OS 쓰레드가 1 메가바이트의 스택을 갖는 반면, goroutine은 이보다 훨씬 작은 몇 킬로바이트의 스택을 갖습니다(필요시 동적으로 증가).

    아래 예시는 결과가 실행시마다 달라질 수 있습니다.

    package main
    
    import (
        "fmt"
        "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")
    
        // 3초 대기
        time.Sleep(time.Second * 3)
    }

     

    익명함수를 고루틴에 활용할 수 있습니다.

    여기서 sync.WaitGroup을 사용하고 있는데, 이는 기본적으로 여러 Go루틴들이 끝날 때까지 기다리는 역활(waitformultipleobjects와 비슷한)을 합니다.

    WaitGroup을 사용하기 위해서는 먼저 Add() 메소드에 몇 개의 Go루틴을 기다릴 것인지 지정하고, 각 Go루틴에서 Done() 메서드를 호출하며, Wait() 메서드를 호출하여, Go루틴들이 모두 끝나기를 기다립니다.

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        // WaitGroup 생성. 2개의 Go루틴을 기다림.
        var wait sync.WaitGroup
        wait.Add(2)
    
        // 익명함수를 사용한 goroutine
        go func() {
            defer wait.Done() //끝나면 .Done() 호출
            fmt.Println("Hello")
        }()
    
        // 익명함수에 파라미터 전달
        go func(msg string) {
            defer wait.Done() //끝나면 .Done() 호출
            fmt.Println(msg)
        }("Hi")
    
        wait.Wait() //Go루틴 모두 끝날 때까지 대기
    }

     

    고루틴에서 점유하는 CPU수량을 지정할 수 있으며, 해당 여러 고루틴을 생성한 경우 시분할 하여 처리됩니다.

    0 으로 지정할 경우 지정에 대한 동작은 하지 않으며 리턴값으로 현재 지정된 CPU 수량에 대하여 반환합니다.

    package main
    
    import (
        "runtime"  
    )
    
    func main() {
        // 4개의 CPU 사용
        runtime.GOMAXPROCS(4)
    }

     

    Channel

    Go 채널은 그 채널을 통하여 데이타를 주고 받는 통로라 볼 수 있는데, 채널은 make() 함수를 통해 미리 생성되어야 하며, 채널 연산자 <- 을 통해 데이타를 보내고 받습니다.

    채널은 흔히 goroutine들 사이 데이타를 주고 받는데 사용되는데, 상대편이 준비될 때까지 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이타를 동기화하는데 사용됩니다.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func readword(ch chan string) {
        var word string = "Type a word, then hit Enter."
        ch <- word // ch에 word 입력
    }
    
    func timeout(t chan bool) {
        time.Sleep(5 * time.Second)
        t <- true // t에 true 입력
    }
    
    func main() {
        t := make(chan bool) // bool 값을 가지는 채널 생성
        go timeout(t) // 고루틴 실행
    
        ch := make(chan string) // string 값을 가지는 채널 생성
        go readword(ch) // 고루틴 실행
    
        select {
        case word := <-ch: // ch 에 입력값이 생길때 까지 대기
            fmt.Println("Received", word)
        case <-t: // t의 입력값이 생길때까지 대기
            fmt.Println("Timeout.")
        }
    }
    Type a word, then hit Enter.
    Received 

     

    채널은 추가 버퍼를 가지고 수신자가 없더라도 입력값을 먼저 삽입 할 수 있습니다. 버퍼를 설정하지 않는 경우 버퍼는 존재하지 않으며 수신자가 생겨야 발신처리가 종료됩니다.

    package main
    
    import "fmt"
    
    func main() {
        ch := make(chan int, 1)
    
        //수신자가 없더라도 보낼 수 있다.
        ch <- 101
    
        fmt.Println(<-ch)
    }

     

    채널을 함수의 파라미터도 전달할 때, 일반적으로 송수신을 모두 하는 채널을 전달하지만, 특별히 해당 채널로 송신만 할 것인지 혹은 수신만할 것인지를 지정할 수도 있습니다.

    송신 파라미터는 (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)
    }

     

    채널을 오픈한 후 데이타를 송신한 후, close()함수를 사용하여 채널을 닫을 수 있습니다.

    채널을 닫게 되면, 해당 채널로는 더이상 송신을 할 수 없지만, 채널이 닫힌 이후에도 계속 수신은 가능합니다.

    채널 수신에 사용되는 <- ch 은 두개의 리턴값을 갖는데, 첫째는 채널 메시지이고, 두번째는 수신이 제대로 되었는가를 나타내며, 만약 채널이 닫혔다면 두번째 리턴값은 false를 리턴합니다.

    package main
    
    func main() {
        ch := make(chan int, 2)
    
        // 채널에 송신
        ch <- 1
        ch <- 2
    
        // 채널을 닫는다
        close(ch)
    
        // 채널 수신
        println(<-ch)
        println(<-ch)
    
        if _, success := <-ch; !success {
            println("더이상 데이타 없음.")
        }
    }

     

    채널에서 지속적으로 데이터를 수신하는 경우 for 문 뿐만 아니라 range 를 사용해서도 활용할 수 있습니다.

    package main
    
    func main() {
        ch := make(chan int, 2)
    
        // 채널에 송신
        ch <- 1
        ch <- 2
    
        // 채널을 닫는다
        close(ch)
    
        // 방법1
        // 채널이 닫힌 것을 감지할 때까지 계속 수신
        /*
        for {
            if i, success := <-ch; success {
                println(i)
            } else {
                break
            }
        }
        */
    
        // 방법2
        // 위 표현과 동일한 채널 range 문
        for i := range ch {
            println(i)
        }
    }

     

    Pointer

    Go에는 C/C++ 과 같이 포인터를 활용하는 기능이 존재합니다. 다만 차이점은 포인터를 사용할때 C/C++ 은 다채로운 방식으로 활용이 가능하지만, Go 에서는 포인터를 이용한 주소값 감가산과 같은 기능은 할 수 없으며 기본적인 적재주소에 대한 엑세스 정도로 볼 수 있습니다.

    package main
    
    import "fmt"
    
    func NonPointer(x int) {
        x = 0
    }
    
    func Pointer(xPtr *int) {
        *xPtr = 0
    }
    
    func main() {
        x := 5
        NonPointer(x)
        fmt.Println(x) // x=5
        Pointer(&x)
        fmt.Println(x) // x=0
    }

     

    Go 에서 포인터를 사용할때, 위의 예제와 같이 값을 할당하는게 아닌 명시적으로 사용할때는 new 를 사용한 할당을 할 수 있습니다.

    package main
    import "fmt"
    
    func one(xPtr *int) {
       *xPtr = 1
    }
    func main() {
        xPtr := new(int)
        one(xPtr)
        fmt.Println(*xPtr) // x는 1
    }

     

    Go에서 구조체에 포인터를 활용하여 사용할 경우 구조체 필드에 대한 접근은 일반 접근방식과 동일한 "." 을 사용하여 접근하게 됩니다.

    package main
    import "fmt"
    
    type Example struct {
        a int
        b string
    }
    
    func main() {
        e := new(Example)
        e.a = 10
    
        fmt.Println(e)
    }

     

    Struct

    구조체의 기본형식은 타언어와 유사합니다. 다만 Go 에서는 구조체를 Print를 통해 출력해볼 수 있으며 구조체 내에 함수를 추가할 수는 없습니다.

    package main
    
    import "fmt"
    
    type Example struct {
        a int
        b string
    }
    
    func main() {
        e := Example{}
    
        e.a = 1
        e.b = "2"
    
        fmt.Println(e)
    }

     

    구조체와 연관하여 함수를 구현할 경우, 아래와 같이 사용할 수 있습니다.

    package main
    
    import "fmt"
    
    type Example struct {
        a int
        b string
    }
    
    func (e *Example) SetInt(a int) {
        e.a = a
    }
    
    func (e Example) SetString(b string) Example {
        e.b = b
    
        return e
    }
    
    func main() {
        e := Example{}
        e.SetInt(10)
        e = e.SetString("20")
    
        fmt.Println(e)
    }

     

    가변 인자

    Go 에서 가변 인자는 "...type" 을 통하여 활용할 수 있습니다.

    package main
    
    import "fmt"
    
    func variadic(strs ...string) {
         // strs is a slice of string
         for i, str := range strs {
             fmt.Printf("%d: %s\n", i, str)
         }
    }
    
    func main() {
         variadic("Hello", "Goodbye")
         variadic("Str1", "Str2", "Str3")
    }

     

    가변인자에 대한 표현법은 Go 내장함수인 append 에서 배열간 연결시에도 사용할 수 있습니다.

    package main
    
    import "fmt"
    
    func main() {
        var Num0 = []int{1,2,3,4}
        var Num1 = []int{1,2,3,4}
        var Num2 = []int{5,6,7,8}
    
        // case1
        Num0 = append(Num0, Num2[0], Num2[1], Num2[2], Num2[3])
        fmt.Println(Num0) // [1 2 3 4 5 6 7 8]
    
        // case2
        Num1 = append(Num1, Num2...)
    
        fmt.Println(Num1) // [1 2 3 4 5 6 7 8]
    }

     

    에러처리

    타언어들의 Exception 객체와 유사한 역활을 하는 error 인터페이스가 존재합니다.

    많은 함수들이 리턴시 (결과값, 에러) 를 리턴하도록 구성합니다.

    별도의 try~catch 가 없기에 각 기능호출별로 if 를 추가하여 체크해야 합니다.

    type error interface {
        Error() string
    }

     

    Interface

    Interface 는 함수의 집합이 될 수도 있고 임의의 변수가 될 수도 있습니다.

    아래 예시는 함수의 집합에 Interface 를 활용하는 코드입니다.

    인터페이스와 같은 함수를 구현하여, 파라미터가 인터페이스인 하나의 함수를 통하여 값을 도출합니다.

    package main
    
    import "math"
    import "fmt"
    
    type Shape interface {
        area() float64
        perimeter() float64
    }
    
    //Rect 정의
    type Rect struct {
        width, height float64
    }
    
    //Circle 정의
    type Circle struct {
        radius float64
    }
    
    //Rect 타입에 대한 Shape 인터페이스 구현 
    func (r Rect) area() float64 { return r.width * r.height }
    func (r Rect) perimeter() float64 {
         return 2 * (r.width + r.height)
    }
    
    //Circle 타입에 대한 Shape 인터페이스 구현 
    func (c Circle) area() float64 { 
        return math.Pi * c.radius * c.radius
    }
    func (c Circle) perimeter() float64 { 
        return 2 * math.Pi * c.radius
    }
    
    func main() {
        r := Rect{10., 20.}
        c := Circle{10}
    
        showArea(r)
        showArea(c)
    }
    
    func showArea(shape Shape) {
        a := shape.area() //인터페이스 메서드 호출
        fmt.Println(a)
    }

     

    인터페이스를 변수로써 활용할 시에는 다음과 같습니다.

    인터페이스 타입 x는 정수 1을 입력받고 다시 문자열 Tom을 받으며, 각 입력값을 출력합니다.

    package main
    
    import "fmt"
    
    func main() {
        var x interface{}
        x = 1 
    
        printIt(x)
    
        x = "Tom"
    
        printIt(x)
    }
    
    func printIt(v interface{}) {
        fmt.Println(v) //Tom
    }

     

    Go Interface 변수의 특이점이라 하면, Type변환과 같은 기능을 하는것으로 보이지만 조금 다른 Type Assertion 이 존재합니다.

    Type Assertion 은 인터페이스가 가지고 있는 실제 값(concrete value)에 접근할 수 있게 해줍니다.

    다음은 Type Assertion 에 대한 예시입니다.

    var n interface{} = 15
    v := n.(int)

     

    만약 interface 에 입력된 값과 Type Assertion 하려는 변수타입이 옳바르지 않는다면 Panic 을 발생시키게 됩니다. 따라서 코드상 데이터 타입이 확실한 경우가 아닌 경우 안정적인 처리를 하기 위해서는 다음과 같은 방법을 사용합니다.

    다음은 실패 체크를 하는 방법입니다.

    var n interface{} = 15
    v, ok := n.(string)
    if ok {
        fmt.Println(n)
    }
    
    fmt.Println("fail")

     

    다음은 데이터 타입에 따른 처리는 다음과 같습니다.

    var x interface{} = 2.3
    switch v := x.(type) {
    case int:
        fmt.Println("int:", v)
    case float64:
        fmt.Println("float64:", v)
    case string:
        fmt.Println("string:", v)
    default:
        fmt.Println("unknown")
    }

     

    reflect 패키지를 이용한 방법 또한 있습니다.

    var x interface{} = []int{1, 2, 3}
    xType := reflect.TypeOf(x)
    xValue := reflect.ValueOf(x)
    fmt.Println(xType, xValue) // "[]int [1 2 3]"

     

    Interface 는 일반 변수뿐만이 아닌 map이나 배열등의 자료형도 될 수 있습니다. 이는 Json 형식을 만들거나 사용할 때에도 활용됩니다.

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        data := make(map[string]interface{})
        data["a"] = 1
        data["b"] = "c"
    
        data2 := make(map[int]string)
        data["c"] = data2
    
        fmt.Println(data)
    }
    map[a:1 b:c c:map[]]

     

    'Language > Golang' 카테고리의 다른 글

    Golang에서 Json 사용예시  (0) 2023.02.07
    CGO ( Golang With C++ ) 케이스별 설명 및 예시  (0) 2023.02.07
    자료형 및 키워드  (0) 2023.02.07
    Golang 설치 및 IDE  (0) 2023.02.07
    What is Golang?  (0) 2023.02.06
Designed by Tistory.