"구글이 만들고, 갈가마구가 배우는 Golang"의 두 판 사이의 차이

589번째 줄: 589번째 줄:
:func 바로 뒤에 함수명을 생략 하는점을 제외하고 함수의 정의 내용을 동일하다.  
:func 바로 뒤에 함수명을 생략 하는점을 제외하고 함수의 정의 내용을 동일하다.  
:일단 익명함수가 변수에 할당된 이후에는 변수명이 함수명과 같이 취급되며 "변수명(파라미터들)" 형식으로 함수를 호출할 수 있다.
:일단 익명함수가 변수에 할당된 이후에는 변수명이 함수명과 같이 취급되며 "변수명(파라미터들)" 형식으로 함수를 호출할 수 있다.
<source lang='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)
}
</source>


==같이 보기==
==같이 보기==

2020년 5월 26일 (화) 12:18 판

위키 사용법도 잘 모르고, GO도 처음인 방랑자가 혼자 끄적이기 위한 공간입니다.

다음 사이트를 정리 하면서, 참고를 하고 있어요...

예제로 배우는 Go 프로그래밍


1 Go 설치

리눅스 민트 19.2 시나몬 배포판에 Go를 설치한다.
  • Go 공식 웹사이트인 http://golang.org/dl 에서 Linux용 압축파일을 다운로드 받는다.
  • /usr/local 에 압축을 푼다
$ tar -C /usr/local -xzf go1.14.2.linux-amd64.tar.gz

/usr/local/go 폴더 아래에 설치가 된다.
"Go build error
  • no non-test Go files in <dir>"
       이런 에러가 나온다면...아마도 "apt-get install golang" 으로 낮은 버전을 설치 했을 것이다.
       내 경운 그랬다....최신버전으로 설치를 하면 되더라..
  • 환경변수 설정하기
$ export PATH=$PATH:/usr/local/go/bin 
//go 컴파일러의 위치 /usr/local/go/bin를 PATH에 추가.

$ export GOPATH=$HOME/go
//작업 폴더의 위치를 알려 준다.

$ export GOBIN=$HOME/go/bin
//실행 파일이 저장될 위치.

$HOME/.profile 추가,  재로그인 하면 수정이 적용된다.
   로그아웃 없이 .profile 적용하기, 둘중 하나 실행.
$ source .profile
$ . ./.profile
   GOROOT="/usr/local/go"
       GOROOT는 자동으로 설정 된다. 
  • 작업 폴더 만들기
$ go get golang.org/x/tools/cmd/...
       $HOME/go 아래에 bin, pkg, src
       3개의 폴더가 생기고 이런 저런 파일 들이 만들어 진다.
       이게 뭘하는 건지는 아직 모르겠다.

2 Go 편집기, Atom 설치

  • Atom 설치
      http://atom.io 에서 "Download.deb"를 클릭해서 설치한다.
      어렵지 않다 시키는 대로 하면 된다.
  • Git 설치
      https://git-scm.com/downloads 에서 "Downloads for Linux"를 클릭해서 설치 한다.
      역시 어렵지 않았다, 뭘 하는건지를 몰라서 그렇지. Atom을 설치 할때 Git이 필요하다고 한다.
      Git에서 Atom을 만들었다고 하니까...
      뭐 거창 한건줄,  
$ apt-get install git
한줄 입력하면 되더라, 다른 버전 설치를 원하면 어쩌구 하는 설명이 몇줄 더 있고...
  • Atom 필요한 패키지 설치
      Atom 설치 후 Go에 필요한 패키지를 설치 한다.
      Help -> Welcome Guide,  
      Install Package 에서 "go-plus" 검색 -> Install 클릭.
      동일하게 "platformio-ide-terminal" 검색하여 설치한다.

3 Go 프로그래밍, 변수 와 상수

  • 변수
   3.1 var 키워드 사용 하는 방법
       var [변수명] [변수타입] = [초기값]
var a int,  a = 1
    정수(int) 변수를 선언하고, 나중에 초기화.
var f float32 = 11
    실수(float) 변수 선언과 초기화.
var a b c int = 1, 2, 3
    여러개의 변수 선언  초기화.
    " = " 빼먹고 개고생함...어이가 없네.
   3.2 변수 타입을 생략 하고,  초기 값에 따라 변수 타입을 추론 하는 방법;
var i = 1 var s = "Hi"
i  정수형이구나, s  문자형이겠구나. 이런 방법이 편할지는 모르겠지만, 나중에 귀찮아 질것 같다...
뭐든 기본이 중요하니까.. 별로 쓰고 싶진 않다.
   3.3 := (Short Assignment Statement) 를 사용하는 방법
            함수(func)내에서만 사용가능하고, 함수 밖에서는 var를 사용해야 한다.
                - 이건 불편하다 안쓸란다.
   ** 변수를 선언하면서 초기값을 지정하지 않으면, 
           Zero Value를 기본적으로 할당한다.    
  • 상수
   3.4 const [변수명] [변수타입] = [초기값]
       키워드 const 를 사용 하는것 이외에는 변수 선언과 거의 비슷하다.
        const c int = 10
        const s string = "Hi"
   3.5 역시나 값을 보고 변수형을 추정하는 방식도 있다.
        const c = 10
        const s = "Hi"
   3.6 한번에 상수를 여러개 묶어서 만들 수 있다.
         const (
                 Visa = "Visa"
                 Master = "MasterCard"
                 Amex = "American Express"
          )
   3.7 상수의 값을 0부터 차례대로 할당 할때는 iota 를 사용한다.
        const (
                  Apple = iota // 0
                  Grape        // 1
                  Orange       // 2
          )
   ## 당연한 거겠지만, GO에서 사용하는 키워드는 변수명에 사용할 수 없다. 
       25개가 있다는데 아직 뭘 하는건지 모르겠다..거의다...

4 Go 프로그래밍, 데이터 타입

  • Go 데이터 타입
Go 프로그래밍 언어는 다음과 같은 기본적인 데이타 타입들을 갖고 있다.
데이터 타입 설명
문자열 string은 수정 될 수 없는 immutable 타입
부울린 bool
정수형 int int8 int16 int32 int64
unit unit8 unit16 unit32 unit64 unitptr
실수형 float32 float64
복소수형 complex64 complex128
바이트 코드 byte: unit8 과 동일, 바이트 코드에 사용
유니 코드 rune: int32 와 동일, 바이트 코드에 사용
  • 문자열

문자열 리터럴은 Back Quote(` `) 혹은 이중인용부호(" ")를 사용하여 표현할 수 있다.

   Raw String Literal - Back Quote (` `)로 둘러 싸인 문자열
        안에 있는 문자열은 별도로 해석되지 않고 Raw String 그대로의 값을 갖는다. 
        예를 들어, 문자열 안에 \n 이 있을 경우 이는 NewLine으로 해석되지 않는다. 
        또한, Back Quote은 복수 라인의 문자열을 표현할 때 자주 사용된다. 
   Interpreted String Literal - 이중인용부호(" ")로 둘러 싸인 문자열
        복수 라인에 걸쳐 쓸 수 없으며, 인용부호 안의 Escape 문자열들은 특별한 의미로 해석된다.
        예를 들어, 문자열 안에 \n 이 있을 경우 이는 NewLine으로 해석된다. 
        이중인용부호를 이용해 문자열을 여러 라인에 걸쳐 쓰기 위해서는 + 연산자를 이용해 결합하여 사용한다. 
    interLiteral := "아리랑아리랑\n" + 
    "아리리요"
  • 데이터 타입 변환
   하나의 데이터 타입에서 다른 타입으로 변환을 하는것을 말한다.
   Go에서 타입간 변환은 명시적으로 지정해 주어야 한다.
   암묵적 변환은 이뤄지지 않고, 런타임시 에러가 생긴다.
     var i int = 100
     var u unit = unit(i)    // 변수 = 변환될 타입(변환될 값)

5 Go 프로그래밍, Go 연산자

연산자 설명은 마이 부족하다. 일단 이런게 있구나 하고 넘어가자 실재로 사용할때 한번씩 더 정리를 해서 추가 해야 할듯하다.

  • 산술 연산자
   사칙연산자(+, -, *, /, % (Modulus))와 증감연산자(++, --)
    c = (a + b) / 5
    i++
  • 관계연산자
   서로의 크기를 비교하거나 동일함을 체크 한다.
    a == b
    a != b
    a <= b
  • 논리연산자
   AND, OR, NOT, XOR 을 표현하는데 사용된다.
    A && B
    A || !(C && B)
    1111 ^ 1010
  • Bitwise 연산자
   비트단위 연산을 위해 사용되는데, 바이너리 AND, OR, XOR와 바이너리 쉬프트 연산자가 있다.
    0011 & 0101
    0011 ^ 0101
       Bitwise operators  비트 연산자 요기에 아주 잘 설명되어 있다.
  • 할당연산자
   값을 할당하는 = 연산자 외에 사칙연산, 비트연산을 축약한 +=, &=, <<= 같은 연산자들도 있다.
    a = 100
    a *= 10
    a >>= 2
  • 포인터연산자
   C++와 같이 & 혹은 * 을 사용하여 해당 변수의 주소를 얻어내거나 이를 반대로 Dereference 할 때 사용한다.
    var k int = 10
    var p = &k  //k의 주소를 할당
    println(*p) //p가 가리키는 주소에 있는 실제 내용을 출력
    // 포인터연산자를 제공하지만 포인터 산술 즉 포인터에 더하고 빼는 기능은 제공하지 않는다.
       포인터의 역참조    역참조 (Dereference) 에 대한 설명은 아무래도 C를 참고 하는게 좋을듯.

6 Go 프로그래밍, Go 조건문

  • if문
   if 문은 해당 조건이 맞으면 { } 블럭안의 내용을 실행한다.
   조건식을 괄호( )로 둘러 싸지 않아도 되며,  반드시 Boolean 식으로 표현되어야 한다.
   그러나 반드시 조건 블럭 시작 브레이스({)를 if문과 같은 라인에 두어야 한다. 아니면 에러가 발생한다.
    if k == 1 {              // if문과 { 는 같은 라인에 
        println("one")
    }
   else 문은 이전의 if 문들이 모두 거짓일 때 실행된다.
   else if 문은 if 조건문이 거짓일 때 다시 다른 if 조건식을 검사하는 데 사용된다.
   (복잡하긴 맞냐? 아니면 또 맞냐? 인거지.)
    if k  == 1 {
       println("one")
    } else if k == 2 {
       println("two")
    } else {
       println("other")
    }
   if 문에서 조건식을 사용하기 이전에 간단한 문장(Optional Statement)을 함께 실행할 수 있다.
  정의된 변수 val는 if문 블럭 (혹은 if-else 블럭 scope) 안에서만 사용할 수 있다. 벗어나서 사용하면 에러발생
    if val := i * 2; val < max {
        println(val)
    }
  • switch문
   여러 값을 비교해야 하는 경우 혹은 다수의 조건식을 체크해야 하는 경우 switch 문을 사용한다.
   switch 문 뒤에 하나의 변수(혹은 Expression)를 지정하고
   case 문에 해당 변수가 가질 수 있는 값들을 지정하여
   값이 일치할 경우에 다른 문장 블럭들을 실행할 수 있다. 
   # 복수의 값을 하나의 문장블럭으로 실행할 경우 콤마를 써서 나열 하면된다.
   # default: case 문에 일치하는 값이 없을 경우에 실행 된다.
        package main
        
        func main() {
            var name string 
            var catagory int = 1
            
            switch catagory {
            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 뒤에 expression이 없을 수 있음 다른 언어는 switch 키워드 뒤에 변수나 expression 반드시 두지만, Go는 이를 쓰지 않아도 된다.

이 경우 Go는 switch expression을 true로 생각하고 첫번째 case문으로 이동하여 검사한다.
//그냥 넘겨도 case문에서 조건을 지정할 수 있기에 가능하다.

case문에 expression을 쓸 수 있음 다른 언어의 case문은 일반적으로 리터럴 값만을 갖지만,

Go는 case문에 복잡한 expression을 가질 수 있다

No default fall through 다른 언어의 case문은 break를 쓰지 않는 한 다음 case로 이동하지만,

Go는 다음 case로 가지 않는다

Type switch 다른 언어의 switch는 일반적으로 변수의 값을 기준으로 case로 분기하지만,

Go는 그 변수의 Type에 따라 case로 분기할 수 있다

   Go의 switch 문에서 한가지 특징적인 용법은 switch 뒤에 조건변수 혹은 Expression을 적지 않는 용법이다. 
   case 조건문들을 순서대로 검사하여 조건에 맞는 경우 해당 case 블럭을 실행하고 switch문을 빠져나온다.
   복잡한 if...else if...else if... 문장을 단순화하는데 유용하다. 
    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")
            }
    }
   Go의 또 다른 용법은 switch 변수의 타입을 검사하는 타입 switch가 있다. 
   아래 예제는 변수 v의 타입이 int 인지, bool, string 인지를 체크한 후 해당 case 블럭을 실행하는 예이다. 
    switch v.(type) {
        case int:
            println("int")
        case bool:
            println("bool")
        case string:
            println("string")
        default:
            println("unknown")
    }
   # 이대로 실행을 하면 에러가 주루룩 나온다.  func를 만들고 인자를 지정할때 interface{} 로 해야 제대로 실행이 된다.
   이건 함수를 공부 할때 비로소 알 수 있을 것 같다. 함수의 인자 type이 바뀌는 함수를 만들때 필요한게 interface{} 인것 같다...짐작이다.
    func do( i interface{} ) {...}
   Go는 case문 마지막에 break 문을 적든 break 문을 생략하든, 항상 break 하여 switch 문을 빠져나온다.
   Go 컴파일러가 자동으로 break 문을 각 case문 블럭 마지막에 추가하기 때문이다. case문을 빠저 나오지 않고 다음 case문으로 진행 하려면
   fallthrough 문을 사용하면 된다.
    package main
 
        import "fmt"
 
        func main() {
            check(2)
        }
 
        func check(val int) {
            switch val {
            case 1:
                fmt.Println("1 이하")
                fallthrough
            case 2:                                //ckeck(2)로 호출, 따하서 case 2: 에서 default: 문까지 실행된다.         
                fmt.Println("2 이하") 
                fallthrough
            case 3:
                fmt.Println("3 이하")
                fallthrough
            default:
                fmt.Println("default 도달")
            }
     }

7 Go 프로그래밍, Go 반목문

  • for 문
   Go는 반복문에 for 하나 밖에 없다.
   "for 초기값; 조건식; 증감 { ... }" 의 형식을 따른다. 초기값, 조건식, 증감은 경우에 따라 생략이 가능하지만,
   초기값; 조건식; 증감"을 둘러싸는 괄호 ( )를 생략하는데, 괄호를 쓰면 에러가 난다.
    func main() {
            sum := 0
            for i := 1; i <= 100; i++ { // 초기값; 조건식;증감 구분 세미콜론(;)
            sum += i
            }
            println(sum)
    }
  • for 문 - 조건식만 쓰는 for 루프
   Go에서 for 루프는 초기값과 증감식을 생략하고 조건식만을 사용할 수 있다.
   # 다른 언어의 while문과 비슷하다는데, 제일 많이 안쓸듯
    func main() {
            n := 1
            for n < 100 {
                n *= 2      
            }
            println(n)
    }
  • for 문 - 조건식만 쓰는 for 루프
   무한루프를 만들려면 "초기값; 조건식; 증감" 모두를 생략하면 된다.
   Ctrl + C 로 빠져 나오면 된다.
    package main
 
        func main() {
            for {
                println("Infinite loop")        
            }
    }
  • for range 문
    "for 인덱스, 요소값 := range 컬렉션" 같이 for 루프를 구성 하고,
    range 키워드 다음의 컬렉션으로부터 하나씩 요소를 리턴해서 그 요소의 위치 인덱스와 값을 for 키워드 다음의 2개의 변수에 각각 할당한다. 
    # 뭔소린가 하니 배열의 인덱스와 값을 for문 다음에 오는 2개의 변수에 순서대로 하나씩 끝날때 까지 가져 온다. 그런 말임.
   names := []string{"홍길동", "이순신", "강감찬"}
        for index, name := range names {  // index = 0, 1, 2 name = "홍길동", "이순신", "감감찬" 순서대로 값이 입력됨
            println(index, name)
    }
  • break, continue, goto 문
   break 
       for 루프내에서 즉시 빠져나올 필요가 있는데, 이때 break 문을 사용한다.
       for문 이외에 select, switch문엣서 사용가능하다.
       "break 레이블" goto문 처럼 레이블을 사용할 수 도 있는데, for 문 바로 전에 레이블을 만들고 break 레이블, 형식으로 사용하는데 이 경우
       for문의 다음으로 이동해서 진동하게 된다는데...이건 뭐 명시적이지도 이해 하기가 쉽지도 안는 불편한 기능이다. 있으나 마나....
   continue
       for 루프의 중간에서 나머지 문장들을 실행하지 않고 for 루프 시작부분으로 바로 가려면 continue문을 사용한다.
       for 루프에서만 사용가능하다.
   goto 
       임의의 문장으로 이동하기 위해 goto 문을 사용할 수 있다. goto문은 for 루프와 관련없이 사용될 수 있다.
   # goto, break, continue 이게 쓸일이 있을지...쓴다 해도 나중에 어쩔라고...
    package main
        func main() {
            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")
    }

8 Go 프로그래밍, Go 함수

  • 함수
   함수는 여러 문장을 묶어서 실행하는 코드 블럭의 단위이다. 
   Go에서 함수는 func 키워드를 사용하여 정의한다. 
   func 뒤에 함수명을 적고 괄호 ( ) 안에 그 함수에 전달하는 파라미터들을 적게 된다. 
   함수 파라미터는 0개 이상 사용할 수 있는데, 각 파라미터는 파라미터명 뒤에 int, string 등의 파라미터 타입을 적어서 정의한다. 
   함수의 리턴 타입은 파라미터 괄호 ( ) 뒤에 적게 되는데, 이는 C와 같은 다른 언어에서 리턴 타입을 함수명 앞에 쓰는 것과 대조적이다. 
   #함수의 파라미터의 타입도 변수 뒤에 둔다. 이게 변하다고 생각 한걸까?
   함수는 패키지 안에 정의되며 호출되는 함수가 호출하는 함수의 반드시 앞에 위치해야 할 필요는 없다.
       package main
        func main() {
            msg := "Hello"
            say(msg)
        }
 
        func say(msg string) {   //함수가 정의되는 위치는 어디든 상관 없다.
            println(msg)
        }
정수형을 반환하는 함수
        func say(msg string) '''int''' {
                println(msg)
                '''return 1'''
        }
  • Pass By Reference
   Go에서 파라미터를 전달하는 방식은 크게 Pass By Value와 Pass By Reference로 나뉜다. 
   1. Pass By Value
       msg의 값 "Hello" 문자열이 복사되어 함수 say()에 전달된다. (위의 예제 참조)
       따라서 msg의 값이 say() 함수 내에서 변경 된다 하더라도 호출 함수 main() 에서 의 msg 변수는 변함이 없다. 
   2. Pass By Reference
        msg 변수의 주소를 전달하게 된다.
        msg 변수앞에 & 부호를 붙여 변수의 포인터(주소)를 전할하는 방식이다.
       이경우 함수에서 msg의 값을 바꾸면, 변수의 값 자체가 바뀌게 된다.
   # 파라미터의 값을 바꾸고 싶으면 포인터를 이용한 Reference 방식을 바꾸기 싫다면 복사를 하는 Value 방식을 이용하면 된다.
        package main
        func main() {
            msg := "Hello"
            say('''&msg''')     //msg의 주소를 파라미터로 넘겨준다. &msg
            println(msg) //변경된 메시지 출력
        }
 
        func say(msg '''*string''') {
            println('''*msg''')
            *msg = "Changed" //메시지 변경
        }
  • Variadic Function (가변인자함수)
   함수에 고정된 수의 파라미터들을 전달하지 않고 다양한 숫자의 파라미터를 전달하고자 할 때 가변 파라미터를 나타내는 ... (3개의 마침표)을 사용한다
   즉 문자열 가변 파라미터를 나타내기 위해서 ...string 과 같이 표현한다. 
    n개의 동일타입 파라미터를 전달할 수 있다. 
   이렇게 가변 파라미터를 받아들이는 함수를 Variadic Function (가변인자함수)라고 부른다.
        package main
        func main() {   
            say("This", "is", "a", "book")
            say("Hi")
        }
 
        func say('''msg ...string''') {
            '''for _, s := range msg''' {     //for 문의 range를 사용할때 인덱스를 생략 할때는 "for  _ , " 이렇게 한다. 생략하면 말도 안되는 에러가 나더라.
                println(s)
            }
        }
  • 함수 리턴값
   func 문의 (파라미터 괄호 다음) 마지막에 리턴값의 타입을 정의해 준다. 값을 리턴하기 위해 함수내에서 return 키워드를 사용한다.
   Go 프로그래밍 언어에서 함수는
   1. 리턴값이 없을 수도,
           지금가지 연습했던 함수들이 리턴값이 없는 함수다.
   2. 리턴값이 하나 일 수도,
            package main
            func main() {
                total := sum(1, 7, 3, 5, 9)
                println(total) //total 함수가 리턴하는 하나의 값을 출력한다.
            }
 
            func sum(nums ...int) '''int''' {    //리턴값의 타입을 저의: 정수형 하나를 리턴 한다.
                s := 0
                for _, n := range nums {
                    s += n
                }
                '''return s'''    //정수형 값을 리턴한다.
            }
   3. 또는 리턴값이 복수 개일 수도 있다.
       Go에서 복수 개의 값을 리턴하기 위해서는 해당 리턴 타입들을 괄호 ( ) 안에 적어 준다. 
       예를 들어, 처음 리턴값이 int이고 두번째 리턴값이 string 인 경우 (int, string) 과 같이 적어 준다.
            package main
            func main() {
                count, total := sum(1, 7, 3, 5, 9)
                println(count, total)   
            }
 
            func sum(nums ...int) '''(int, int)''' {    // 리턴값은 (int, int) 2개가 될거라고 선언하는 거다. 
                s := 0      // 합계
                count := 0  // 요소 갯수
                for _, n := range nums {
                    s += n
                    count++
                }
                '''return count, s'''
            }
   4. Named Return Parameter
       리턴값에 이름을 지정해 준다. 이렇게 하면, 리턴되는 값들이 여러 개일 때, 코드 가독성을 높이는 장점이 있다.
       func 라인의 마지막 리턴타입 정의 부분을 보면, (int, int) 가 아니라 (count int, total int) 처럼 정의되어 있음을 볼 수 있다.
       즉, 리턴 파라미터명과 그 타입을 함께 정의한 것이다. 그리고 함수 내에서는 이 count, total에 결과값을 직접 할당하고 있음을 볼 수 있다. 
        func sum(nums ...int) '''(count int, total int)''' {
            for _, n := range nums {
                  total += n
            }
            count = len(nums)
            return   // return count, total 지정을 해줘도 되더라, 리턴값의 이름을 생략해도 된다, 그래도 return 자체를 생략해서는 안된다. 
          }

9 Go 프로그래밍, Go 익명함수

  • 익명함수
함수명을 갖지 않는 함수를 익명함수(Anonymous Function)이라 부른다.
일반적으로 익명함수는 그 함수 전체를 변수에 할당하거나 다른 함수의 파라미터에 직접 정의되어 사용되곤 한다.
func 바로 뒤에 함수명을 생략 하는점을 제외하고 함수의 정의 내용을 동일하다.
일단 익명함수가 변수에 할당된 이후에는 변수명이 함수명과 같이 취급되며 "변수명(파라미터들)" 형식으로 함수를 호출할 수 있다.
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)
}

10 같이 보기

문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}