Theory of Go

Alpaca·2021년 7월 21일
0

GO

목록 보기
2/2

github에서 publishing할 때 branch의 이름이 gh-pages여야 하는 것 처럼
Go에서도 compile할 메인파일은 꼭 main.go로 해야한다

그리고 string은 double quotes("")로 감싸줘야한다

package main

import "fmt"

func main() {
  fmt.Println("Hello world!")
}

import and export

위에 Println()함수를 보면 알겠지만 첫 글자가 대문자이다
Goimport하기 굉장히 편리하게 export하고 싶은 함수는 첫글자를 대문자로 쓰게하고 있다

something.go라는 파일을 하나 만들고

package something

import "fmt"

func SayHello() {
  fmt.Println("Hello")
}

라고 작성하고 main.go에서 이를 import할 때는

package main

func main() {
  something.SayHello()
}

하면 된다

결국 fmt도 내장된 package이고 그 안에 수 많은 함수중에 Println()을 불러서 쓴 것일 뿐이다

constants and variables

const num int = 50
var name string = "alpaca"

GoC와 매우 비슷하기 때문에 type을 지정해줘야 한다
근데 너무 길고 귀찮으니 함수 안에서 처리할 때에는

package main

func main() {
  name := "alpaca"
  age := 20
}

과 같이 :=를 사용하면 알아서 type을 지정해준다
📌 대신 이렇게하면 type은 임의로 변경할 수 없다는 점에 유의하도록 하자

functions

Gofunction에서 return하는 것들도 모두 type을 정해줘야한다
(TS, C, Java 등을 쓴 경험이 있다면 이해하는게 어렵지 않을 것 같다)
이러한 Statically typed(정적 타입)의 특징은 compile시 속도가 빠르다는 것이다

자세한 건 Why we switched from Python to Go을 참고하도록 하자

그래서 함수를 만들때도

package main

import "fmt"

func lenAndUpper(name string) (int, string) {
  return len(name), strings.ToUpper(name)
}

func main() {
  totalLength, upperName := lenAndUpper("alpaca")
  fmt.Println(totalLength, upperName)
}

// 6 ALPACA

와 같이 type을 지정해 주어야 한다
lenAndUpper()에서 한가지만 return받고 싶을 때에는

totalLength, _ := lenAndUpper("alpaca")

와 같이 underscore( _ )를 사용해주면 된다

그리고 2개이상의 arguments들을 받고 싶을 때에는

package main

import "fmt"

func repeatName(names ...string) {
  fmt.Println(names)
}

func main() {
  repeatName("alpaca", "cat", "dog", "pepe")
}

// [alpaca cat dog pepe]

...type을 사용하여 받을 수 있다

개인적으로 Go의 함수에서 가장 마음에 드는 것은 naked return이다

package main

import "fmt"

func lenAndUpper(name string) (length int, uppercase string) {
  length = len(name)
  uppercase = strings.ToUpper(name)
  return
}

func main() {
  totalLength, upperCase := lenAndUpper("alpaca")
  fmt.Println(lenAndUpper(totalLength, upperCase)
}

// 6 ALPACA

위와 같이 return할 변수들을 굳이 꼭 명시하지 않아도 된다
왜냐하면 (length int, uppercase string)이 부분에서 이미 명시했기 때문이다

또 하나의 마음에 드는 것은 defer

package main

import "fmt"

func lenAndUpper(name string) (length int, uppercase string) {
  defer fmt.Println("I'm done.")
  length = len(name)
  uppercase = strings.ToUpper(name)
  return
}

func main() {
  totalLength, upperCase := lenAndUpper("alpaca")
  fmt.Println(lenAndUpper(totalLength, upperCase)
}

// I'm done.
// 6 ALPACA

defer는 말 그대로 미룬다는 말인데 함수의 실행이 다 끝나고 나서 실행이 된다

main()함수 안에 있는 totalLength, upperCase := lenAndUpper("alpaca")에서
lenAndUpper()함수의 실행이 끝나서 먼저 I'm done.이라는 문장이 출력되는 것이다

for, range, ...args

Go에서는 오직 for에서 loop를 사용한다
js처럼 forEach, map()같은게 없다는 말이다

package main

import "fmt"

func superAdd(numbers ...int) int {
  for index, number := range numbers {
    fmt.Println(index, number)
  }
  return 0
}

func main () {
  superAdd(1, 2, 3, 4, 5, 6)
}

// 0 1
// 1 2
// 2 3
// 3 4
// 4 5
// 5 6

range는 자동적으로 index를 만들어준다

func superAdd(numbers ...int) int {
  for i := 0; i < len(numbers); i++ {
    fmt.Println(numbers[i])
  }
  return 0
}

func main () {
  superAdd(1, 2, 3, 4, 5, 6)
}

고전적인 방법으로는 위와 같이도 짤 수 있겠지만 나는 조금이라도 짧은 코드를 선호한다

if

package main

import "fmt"

func canIDrink(age int) bool {
  if age < 18 {
    return false
  } else {
    return true
  }
}

func main () {
  fmt.Println(canIDrink(16))
}

// false

위와 같이 기본 if문을 쓸 수 있다
근데 재밌는 건 if문을 쓰는 순간 variable expression이 가능하다는 거다
if문의 조건문에서 변수를 만들 수 있다는 거다

package main

import "fmt"

func canIDrink(age int) bool {
  if koreanAge := age + 2; koreanAge < 18 {
    return false
  } else {
    return true
  }
}

func main () {
  fmt.Println(canIDrink(16))
}

// true

switch

package main

import "fmt"

func canIDrink(age int) bool {
  switch {
    case age < 18:
      return false
    case age >= 18:
      return true
	}
  return false
}

func main () {
  fmt.Println(canIDrink(16))
}

// false

위와 같이 if-else를 남용하지 않을 때 사용 할 수 있다
switch도 마찬가지로 variable expression을 할 수 있으므로 이를 활용하면
좀 더 가독성 좋은 코드를 작성할 수 있다

pointer

pointerC를 공부할 때도 진짜 이해하기 힘들었던 부분이다
Low level programming을 내가 혐오하는 이유다

package main

import "fmt"

func main()  {
  a := 2
  b := a
  fmt.Println(a, b)
}

// 2 2

위 상황에서 b에 a의 값을 assignment했기 때문에 같은 것이라고 생각 할 수 있는데

package main

import "fmt"

func main()  {
  a := 2
  b := a
  fmt.Println(&a, &b)
}

// 0xc0000140c0 0xc0000140c8

각각의 메모리의 주소를 보면 다른 것을 알 수 있다
그렇다면 b := &a로 메모리의 주소값을 저장한 후 이를 출력한다면 둘이 같은 값을 같는 것이 된다

package main

import "fmt"

func main()  {
  a := 2
  b := a
  fmt.Println(a, *b)
  
// 2 2
}

위와 같이 각 변수가 완전히 같은 값을 바라보고 있게 된다

a := 2
b := a
a = 10

fmt.Println(a, b)

// 10 2

만약 그냥 값을 assignment해줬다면 위와 같이 a = 10으로 변경해도 b에는 영향이 없다

그냥 폴더를 복사 붙여넣기를 한 것 같은 느낌이다

a := 2
b := &a
a = 10

fmt.Println(a, b)

// 10 10

위와 같이 `&`으로 주소 값을 저장해주고 `*`으로 그 값을 불러온다면 `a = 10`이 b에게도 영향을 주게 된다
> 폴더를 복사 붙여넣기를 한 것이 아니고 a라는 폴더와 a라는 폴더를 갈 수 있는 b라는
> 바로가기 파일이 있다고 생각하면 된다
![](https://velog.velcdn.com/images%2Fzxcvbnm5288%2Fpost%2Fe6eba101-c3ff-485e-9c99-ab2689ed5d88%2Fimage.png)

이는 굉장히 큰 데이터를 다룰 때 유용할텐데 엄청 용량이 큰 폴더를 복사할 때는 엄청나게 큰 시간이 걸리고
이를 수정하면 다시 assignment해줘야 하는데 경로를 넣어준 것이므로 따로 변경할 일이 없다

그리고 바로가기를 통해 들어간 폴더의 내용을 수정할 수 있듯이 `*b = 20`으로 a의 값을 변경 할 수도 있다

## array and slices

`Go`는 배열의 길이를 명시해주고 타입도 명시해줘야한다
```go
names := [3]string{"alpaca", "pepe"}

근데 배열의 길이를 정하기 싫을 때(혹은 정할 수 없을 때)는 어떻게 해야할까?
이를 위해 있는게 slice

names := []string{"alpaca", "pepe"}

굉장히 간단하면서 의미도 잘 전달한다
하지만 배열이였다면 남은공간에 assignment해주면 될텐데 (ex. names[2] = "cat")
slice에서는 에러가 발생한다

slice에 추가를 할 때에는 append()라는 함수를 사용해야 한다(ex. append(names, "cat"))
근데 이 함수는 수정된 slice를 return해 준다
names는 바뀌지 않았다는 거다
그래서 사용을 할 때에는 names = append(names, "cat")이런식으로 다시 assignment까지 해줘야 한다

maps

map은 데이터 구조로써 js의 object와 약간 비슷하다

alpaca := map[string]string{"name": "alpaca", "age": "30"}

map은 key: value 구조로 되어있고 정해진 type만 저장할 수 있다

for _, value := range alpaca {
  fmt.Println(value)
}

위와 같이 map에서도 for loop를 사용할 수 있다

structs

struct는 map보다 좀 더 유연하게 사용할 수 있다
map에서의 value는 정해진 type으로만 저장할 수 있었다면 struct에서는 아니다

type person struct {
  name string
  age int
}

alpaca := person{"alpaca", 30}

하지만 위의 경우는 struct의 구조도 살펴봐야 하기 때문에

alpaca := person {name: "alpaca", age: 30}

이런 식으로 key와 value를 한 눈에 볼 수 있게 짜는 것이 더 좋다고 생각한다






reference

  1. Go package documentation
  2. Nomadcoders
profile
2020년 10월 15일 퇴사하고 개발자의 길에 도전합니다.

0개의 댓글