이미 핫해진 GO 언어를 배워보자! 무작정 언어만 공부하기 보다는 웹사이트를 만들어 보기로한다.
우리가 함께하는 첫 공부가 재밌었으면 좋겠다.

2019.08.31 토요일 오후 4시 운동하고 와서
바람이 살랑 살랑 부는 LAPINE 카페에서 같이 공부를 시작한다.
(잠시 모카는 ... 후기를 작성중...)

요즘에는 node.js에서 GO로 웹프로그래밍이 많이 넘어가고 있고,
이미 구글에서 핫하게 밀고 있는 언어이고
배우기 쉽고, 빠르고 등등.. 그냥 장점만 들어서 일단 해보기로!

GO 시작하기

구글에서 제공하는 A Tour of GO를 통해서 한번
예제를 따라해보기로 한다. 영어를 좋아한다면 그대로 하지만 우리는 한글이 좋으니 한글 (A Tour of GO) 를 살펴보자~

다른 언어처럼 학습하는게 뭐 packages, variables, flow, types, method에 대해서
한번 살펴보고, Go에서 concurrency features에 대해서 살펴보면 위 사이트에서 얻을것은 끝!

다운로드는 여기서 맞춰서 받으면 된다.
다운로드를 받으면 아래 go가 실행 가능해진다.

$ go

일단 온라인으로 A Tour of GO를 수행해보고 오길 (offline으로도 가능한데 그냥 온라인으로 고고)

Packages, Import

packages를 추가하기 위해서는 디렉토리 경로의 마지막 이름을 사용하는게 규칙 이다.
예를 들어 "path/filepath" 라면 filepath가 패키지 명이다.

import를 하는 방법은 아래 두가지! 주의해야할 점은 여러개를 나열해도 ,를 입력하지 않는다.

import "fmt"
import "math"

또는

import (
  "fmt"
  "mat"
)

함수

함수는 int의 타입 매개변수를 두개 받고, 반환할 타입(int)를 지정하면 된다.
java, c와 다르게 뒤에 매개변수의 타입을 지정한다는게 다른점! 코드를 왼쪽에서 오른쪽으로 읽을때 자연스럽게 읽으라고 이렇게 했다는데...
그냥 따르지 왜 바꾼거니?

동일한 매개변수는 하나만 명시하고 나머지는 생략이 가능하다는점
named results의 경우에는 반환 값에 이름을 부여하여 변수처럼 사용이 가능하다.
즉 반환하는 변수에 매개변수의 타입 말고도 변수명을 함께 넘길 수 있음

func add(x int, y int) int {
    return x + y
}

// 또는

func add(x, y int) int {
    return x + y
}

// 여러개의 결과를 반환할때

func add(x, y int) (int, int) {
    return x, y
}

// Named results
func add(x, y int) (x, y int) {
    return x, y
}

함수 클로져 (Function closures) 아래 adder의 함수는 클로져를 반환한다.
각각의 클로져는 자신만의 sum 변수를 가진다.
neg, pos를 각각 sum에 대해서 업데이트가 필요하기 때문에 함수 클로져를 사용.
피보나치를 구현할때 사용한다.

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

// fibonacci

// fibonacci is a function that returns 
// a function that returns an int.
func fibonacci() func() int {
    first, second := 0, 1
    return func() int {
        ret := first
        first, second = second, first+second
        return ret
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

Vairbales

변수는 var를 사용하고 매개변수는 타입의 문장 끝에 명시한다.
특이하게 변수를 설정한다.

변수를 초기화할때는 변수 타입을 생략할 수 있다.
GO에서 알아서 초기화 하고 자하는 값에 따라 타입이 결정된다.

var x, y, z int
var c, python, java bool

var x, y, z int = 1, 2, 3
var c, python, java = true, false, "no"

함수 내에서는 :=을 사용하면 var과 명시적인 타입 (e.g int, bool)을 생략 할 수 있음
(그러나 함수 밖에서는 사용할 수 없음)

func main() {
    c, python, java := true, false, "no!"
}

상수 (Constants)는 문자, 문자열, 부울, 숫자 타입 중의 하나가 될 수 있다.
숫자형 상수 (Numeric Constnats)는 정밀한 값을 표현할 수 있음
타입을 지정하지 않고 문맥에 따라 타입을 가지게 된다.

const World = "안녕"
const True = true / false

반복문

Go 언어는 반복문이 for 밖에 없다. 사용할때 소괄호 없이 중괄호만 사용

sum := 0
for i := 0; i < 10; i++ {
  sum += i
}

// while 처럼 쓰기
sum :=1
for sum < 1000 {
    sum += sum
}

// 무한 루프
for {}

조건문

iffor와 동일하게 소괄호를 사용하지 않는다.

if x < 0 {
    return x
} else {
    return y
}

자료형

기본 자료형은 아래와 같다.

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8의 다른 이름(alias)

rune // int32의 다른 이름(alias)
     // 유니코드 코드 포인트 값을 표현합니다. 

float32 float64

complex64 complex128

구조체 (Structs)는 필드들의 조합이고,
속한 필드는 dot(.)으로 접근한다.

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex {1, 2}
    v.X = 4
} 

간단하고 쉽다며... C때 나왔던 포인터가 있다.
구조체 변수는 구조체 포인터를 이용해 접근할 수 있다.
포인터답게 수정하면 실제 구조체에도 영향을 미치겠지요?

func main() {
    p := Vertex{1,2}
    q := & p
    q.X = 1e9
}

구조체 리터럴은 필드와 값을 나열해서 구조체를 새로 할당하는 방법
원하는 필드를 {Name: value}구문을 통해서 할당 할 수 있음
필드 순서는 상관 없음!

r = Vertex{X: 1}

new(T)는 모든 필드가 0(zero value)이 할당된 T 타입의 포인터를 반환

var t *T = new(T)
t := new(T)
func main() {
    v := new(Vertex)
}

Slicing slices

여기서 슬라이스는 우리가 흔하게 알고 있는 리스트를 말한다.
슬라이스는 재분할 할 수도 있고, 같은 배열을 가리키는(point) 새로운 슬라이드를 만들 수도 있음

s[lo:hi] 위 표현은 lo에서 hi-1의 요소를 포함하는 슬라이스

p := []int {2,3,6,8,1}
p[1:4] # 3,6

슬라이스를 만들기 위해서는 make함수를 이용해서 만들수 있다.

a := make([]int, 5) // len(a) = 5

make 함수의 세번재 매개변수로 용량(capacity)를 제한 할 수 있음

b := make([]int, 0, 5)

var z []int를 통해 빈 슬라이스를 생성할 수 있는데,
zero value는 nill 이다. (언어마다 왜 다르게 하는거야...)

Range

for 반복문에서 range를 사용하면 슬라이스나 맵을 순회할 수 있다.
아래와 같이 하면 i에는 index (0,1,2,3,4,5,6,7), v에는 (1,2,4,8,16,32,64,128)

_을 사용하면 index 또는 value를 무시할 수 있다.

var pow = []int {1,2,3,4,5}
for i, v := range pow {
    fmt.Printf("2**%d = %d\n", i, v)
}

for _, v := range pow {
    fmt.Printf("%d\n", v)
}

Maps

맵은 키와 값을 넣는데, 맵은 반드시 사용하기 전에 make를 명시해야 한다.
뭔가 명시하고 넣는게 복잡해...

m = make(map[string]Vertex)
m["bell Labs"] = Vertex {40.78, -23}

맵 리터럴은 구조체와 비슷하지만 반드시 key를 지증해서 넣어야 한다.
리터럴에서 타입명(Vertex)를 생략해도 된다.

var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

map의 요소를 삽입하거나 수정하는 방법은

m[key] = value
value = m[key]
delete(m, key)
value, ok = m[key]

위의 ok값이 true이면 존재, 아니면 false

Switch

다른 언어에도 switch가 있지만, 다른 언어와 다른 점은 case의 코드 실행을 마치면 알아서 break
fallthrough로 끝나는 case는 스스로 break하지 않는다.

    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }

Methods

여기까지 50번째를 하고 있는데 대략 1시간 정도 소요 했다.
Go 에는 클래스가 없다. 하지만 메소드를 구조체에 붙일 수 있다.
메소드 리시버(method receiver)는 func 키워드와 메소드의 이름 사이에 인자로 들어간다.
Go 언어에서 처음보는 방식이다.

Vertex라는 구조체가 있고, 구조체에 함수(Abs)를 붙이고
그 붙인 함수를 v.Abs()를 통해 접근이 가능하다.

딱 한번에 봤을때 직관적이지 않지만, 클래스를 대체할 수 있을것 같다.

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}

구조체가 아닌 모든 변수에 붙일 수 있다.
MyFloat의 type을 정의하고, 그 타입에 Abs의 함수를 붙이면
f.Abs()로 접근이 가능하다.

package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}

포인터 리시버를 사용하는 이유는 두가지

  • 메소드가 호출될 때 마다 값이 복사되는 것 (큰 구조체 타입인 경우 값이 복사되는 것은 비효율적)을 방지하기 위해서
  • 메소드에서 리시버 포인터가 가르키는 값을 수정하기 위함

아래 예제에서 v를 Vertex 타입으로 받으면 Scale 메소드가 더 이상 동작하지 않는다.
Scale의 함수내에서는 v의 값을 변경하지만, v가 포인터가 아니기 때문에 원래의 값은 변경되지 않음.
그렇기 때문에 &Vertex의 포인터로 넘겨줘 값을 복사하지 않고, 메소드 리시버 포인터가 가르키는 값을 수정해야 한다.

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    v.Scale(5)
    fmt.Println(v, v.Abs())
}

너무 기네... 53부터는 다음에 이어서 하도록 하겠다. 53은 인터페이스! 이어서 하기

+ Recent posts