본문으로 바로가기

오랜만에 nomad coder 강의를 들으며 Go 언어를 새로 공부해 보았다.
지금부터 기재하는 내용은 모두 nomadcoders 쉽고 빠른 Go 시작하기에서 강의를 무려 무료로 !! 들을 수 있으니 모두 시간이 된다면 공부해보길 바란다.

Go의 기본문법 및 작은 미니프로젝트 까지 다뤄질 강의이다.

java나 javascript나 python이나 등등 프로그래밍을 기본적으로 공부한 이들이 도전하기에 좋은 언어인 듯하다.
매우 빠른 속도를 자랑하기 때문이다. 그 이유는 병렬성 특성이 강해서 멀티프로세싱을 지원하기 때문인데
물론 다른 언어에도 멀티프로세싱을 구현할 수 있으나 Go 만큼 빠른지는 모르겠다. 라고 니코가(강의 쌤이름) 강력 추천

여튼 강의를 듣고 개인적으로 정리를 해보는 내용임


func main 의 선언이 있고 없고의 차이

  1. 있으면 컴파일 하겠다는 의미

    package main
  2. 없으면 라이브러리오픈소스로 공유하겠단 소리

    package 배포할 이름

변수 선언의 차이

# 상수
const 변수명 타입 = 값
#변수
var 변수명 타입 = 값

하지만 변수는 func 안에서만 유일하게 축약이 가능하다

변수명 := 값

위 처럼 선언하면 Go가 알아서 값의 타입을 유추하여 타입을 지정해준다
전역변수로는 축약이 불가능 오로지 func에서만 축약가능하고 상수는 축약이 불가능


func 의 파라미터 선언 방식

인자를 받을 때 타입을 지정해주어야하고 결과값의 타입도 지정해야함

  1. a도 int, b도 int

    func multiply(a int, b int) int {
     return a * b
    }
  2. 둘다 int 인경우 최종적으로 b에만 지정해도 상관없다

    func multiply(a, b int) int {
     return a * b
    }

리턴값은 여러개 지정이 가능하다

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

func main() {
    totalLength, upperName := lenAndUpper("jeffrey") // 리턴 값을 원하는 것만 받고 나머지는 버리고 싶은 경우 _(underscore) 를 선언
    // totalLength, _ := lenAndUpper("jeffrey")
    fmt.Println(totalLength, upperName) // 만약 이 줄이 선언되지 않으면 totalLength, upperName 의 변수 사용이 되지 않아서 않으면 에러가 난다.
}

제한 없는 Arguments 받기

같은 타입의 여러 Arguments를 받을 때는 타입앞에 ... 을 붙여주기만 하면 끝

func repeatMe(words ...string) {
    fmt.Println(words)
}

func main() {
    repeatMe("jeffrey", "oh", "awesome", "developer")
}

다른방법의 리턴

앞서 얘기한 리턴값의 여러 개 지정이 가능한 부분의 소스를 이용하여 재설명하자면
리턴할 변수를 미리 지정하여 해당 변수를 프로그래밍하고 리턴할 수 있다

func lenAndUpper(name string) (lengthName int, upperName string) {
    lengthName = len(name)
    upperName = strings.ToUpper(name)
    return
    # 물론 return lengthName, upperName 해도 되지만 굳이 선언 하지않아도 go가 알아서 지정된 리턴 변수를 찾아 리턴한다
}

defer의 사용법

defer란 func 의 return이 실행되고 난 직후에 실행될 부분을 정의하는 것이다
예를들어 파일을 저장하던가 삭제하던가 닫는다던가 API를 요청한다던가 한 경우의 마지막 처리 부분이라고 보면 된다

func lenAndUpper(name string) (lengthName int, upperName string) {
    defer fmt.Println("I'm done")

    lengthName = len(name)
    upperName = strings.ToUpper(name)
    return
    # 물론 return lengthName, upperName 해도 되지만 굳이 선언 하지않아도 go가 알아서 지정된 리턴 변수를 찾아 리턴한다
}

for를 이용한 loop

Go에서는 반복문을 이용하려면 for만 있으면 된다. 사실 for말곤 없다. 기존에 알고 있던 반복문이란게 없다 오로지 for

배열로 전달된 숫자를 더해서 출력하는 소스를 예제로 작성

func superAdd(numbers ...int) int {
    fmt.Println(numbers)
    return 1 // 없으면 에러나서 임의값 리턴
}

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

결과로 콘솔창에는 다음과 같이 나온다.

[1 2 3 4]

위의 값은 Array로 출력되었다는 소리이다. 그러면 우리는 반복문을 통해 해당 값을 1개씩 출력할 수 있다.


for문을 이용할 땐 range 라는 것과 같이 사용하면 된다. 기존 python에서 사용했던 범위 지정인 듯 하다

func superAdd(numbers ...int) int {
    for number := range numbers {
        fmt.Println(number)
    }
    return 1 // 없으면 에러나서 임의값 리턴
}

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

결과로 콘솔창에는 다음과 같이 나온다.

0
1
2
3

왜 배열안에 있는 1,2,3,4가 안나오고 0,1,2,3 가 나오냐면 기본적으로 반복문에서는 index를 먼저 리턴하고 2번째 인수로 값을 뱉는다. 기존의 forEach 개념과 같다고 보면 된다. (key, value)

따라서 값도 같이 출력하기 위해선 이와 같이 하면된다

func superAdd(numbers ...int) int {
    for index, number := range numbers {
        fmt.Println(index, number)
    }
    return 1 // 없으면 에러나서 임의값 리턴
}

그 결과는 이렇게 나온다

0 1
1 2
2 3
3 4

물론 위 for문의 방식에서 index가 필요 없다면 ignore 처리가 가능한 _(underscore)를 지정하면 된다

func superAdd(numbers ...int) int {
    for _, number := range numbers {
        fmt.Println(_, number)
    }
    return 1 // 없으면 에러나서 임의값 리턴
}

또 다른 방법으로는 아주 원초적인 방법도 가능하다

func superAdd(numbers ...int) int {
    for i:= 0; i < len(numbers); i++ {
        fmt.Println(numbers[i])
    }
    return 1 // 없으면 에러나서 임의값 리턴
}

최종적으로 값의 합산을 리턴해주는 함수 예제를 작성하면 아래와 같다

func superAdd(numbers ...int) int {
    total := 0
    for _, number := range numbers {
        total += number
    }
    return total
}

func main() {
    result := superAdd(1, 2, 3, 4)
    fmt.Println(result)
}

if else 사용법

javascript에서는 조건문을 다음과 같이 사용했다

if () {
    ...
}

python에서는 다음과 같이 사용

if ... :
 ...

하지만 Go에서는 다음과 같이 사용한다

if num > 10 {
    ...
}

다른 점이라고는 javascript 처럼 () 괄호가 조건에 안들어가고 python 처럼 : 콜론 대신 중괄호를 사용한다는 점이다


go만의 variable expression이 있는데 소스로 설명

if addNum := num + 1; addNum > 10 {
    ...
}

다른 언어에서도 못본 문법이다 (글쓴이 기준)
이 방식은 if 문에서 조건에서만 사용하기위해 임시적으로 만들어진 variable 이다. ;을 기준으로 조건에서 바로 사용이 가능하다

기존에는 변수가 필요하면 먼저 선언한 뒤 조건에서 사용했을 것이다

addNum := num + 1
if addNum > 10 {
    ...
}

위와 같이 선언하면 조건문이 아닌 func 안에서 어디에서든 사용할 수 있지만 이전 소스처럼 선언하면 조건이 실행될 때만 사용할 것이라는 의미로 차이점이 있다


switch 사용법

switch도 앞서 다룬 if else와 마찬가지로 조건에서만 사용하기위해 임시적인 변수를 할당할 수 있다
기본적인 switch는 다른 언어와 차이가 별로 없다

switch {
    case num < 10 :
        return false
    case num > 18 :
        return true
}
return false

위가 기본적인 예제이고 응용하면 다음과 같다

switch addNum := num + 2; addNum {
    case addNum < 10 :
        return false
    case addNum > 18 :
        return true
}
return false

pointer

C 언어를 학습한 적이 있다면 pointer를 본 적이 있을 것이다

기본적으로 변수라는 것은 메모리를 할당하여 해당 메모리에 값을 저장한다
그렇기 때문에 변수의 값을 보기위해서는 해당 주소를 통해서도 볼수 있다

주소를 보기위해서는 메모리에 값이 이미 할당된 변수에 &을 앞에 작성해주면 된다

a := 2
b := &a
fmt.Println(b)

결과는 콘솔에 a의 주소값이 나올 것이다 (ex. 0x112312312)

a의 주소값을 이용하여 입력된 값이 무엇인지 알기 위해서는 *를 붙이면 된다

a := 2
b := &a
fmt.Println(*b)

만약 주소값을 이용하여 a의 값을 변경하고자 할 때는 다음처럼 하면 된다

a := 2
b := &a // a의 주소값을 b에 저장
*b = 10 // b에 저장된 것은 a의 주소값이기 때문에 원래의 값을 보기위해선 *를 사용, 그리고 값을 할당하면 a값이 변경됨
fmt.Println(a)

위 소스를 실행해보면 콘솔에는 10이 출력이 될 것이다


Arrays and Slices

배열의 선언에 있어서 우리가 흔히들 사용하는 방법은 변수명 = 타입[크기] 일텐데 go에서는 순서만 다르다

names := [4]string{"jeffrey", "oh", "awesome", "developer"}

크기를 먼저쓰고 타입을 쓴 후 {} 안에 초기화를 하면된다. 빈 방을 만들려면 [크기]string{} 혹은 []string{} 만 작성하면 된다
크기를 지정하지 않은 경우 우리는 배열에 값 추가를 무한대로 할 수있는데 javascript에서는 arrays.push(값) 하면 추가가 되었으나
go에서는 이렇게 쓰지않고 append() 함수를 이용하여 추가한다. 차이점은 다음과 같다

names := []string{"jeffrey", "oh", "awesome", "developer"}
names = append(names, "good")

append() 함수는 값의 추가를 한 후에 새로운 배열을 return 한다. 정말로 그런 것인지 앞서 배운 포인터를 통해 확인해보자.

names := []string{"jeffrey", "oh", "awesome", "developer"}
fmt.Println(&names[0])
names = append(names, "good")
fmt.Println(&names[0])

위의 소스는 namesjeffrey라는 값의 주소를 출력한 후 good이라는 값을 names 끝에 추가하여 namesjeffrey의 주소값을 출력한 것이다. 당연한 소리지만 메모리 값은 다르다. names 자체가 새로 다시 할당된 것이기 때문이다. 기존의 names의 끝에 추가만으로 끝이 나는게 아닌 names의 끝에 추가한 후 새로운 names를 append() 함수가 return 해주었기 때문에 아예 새로운 배열이다


Maps

Map의 형태는 key, value 이다. go에서도 간단하게 map을 만들 수 있다

jeffrey := map[string]string{"name" : "jeffrey", "age" : "20"}
fmt.Println(jeffrey)

출력해보면 다음과 같이 나온다

map[age:20 name:jeffrey]

약간 변형해보면 배열안의 배열은 다음과 같이 사용 가능하다

jeffrey := map[string][]string{
    "name": {
        "jeffrey",
        "oh", // 마지막 부분에 ,를 표시하지 않으면 에러라고 뜬다
    },
    "age": {
        "10",
        "12", // 마지막 부분에 ,를 표시하지 않으면 에러라고 뜬다
    }, // 마지막 부분에 ,를 표시하지 않으면 에러라고 뜬다 (vsc에서의 
}
fmt.Println(jeffrey)

key가 string이면서 value가 배열을 가지고 해당 배열들의 value가 string인 형태이다. 결과는 다음처럼 나온다

map[age:[10] name:[jeffrey]]

Structs

C언어에서 봤던 구조체를 다시 볼 줄은 몰랐다. Go에서 구조체는 상당히 중요하다고 한다. 만드는 방법은 다음과 같다

type person struct {
    name    string
    age     int
    favFood []string
}

func main() {
    favFood := []string{"bibimbap", "kimchi"}
    jeffrey := person{"jeffrey", 20, favFood}
    fmt.Println(jeffrey)
}

타입이 person인 구조체를 만들어서 하나의 struct를 만들어 봤다. java로 치면 class 및 객체 생성 느낌인데 go에서는 class가 없고 생성자도 없다.
근데 느낌이 이미 비슷하다. 구조체를 통해 값을 할당하고 그 결과를 변수로 담아낸 것 자체가 java에서 생성자를 이용하여 할당한 느낌

여튼, 결과는 콘솔창에 다음과 같이 표시된다.

{jeffrey 20 [bibimbap kimchi]}

구조체를 이용하여 값을 할당할 때 jeffrey := person{"jeffrey", 20, favFood} 이렇게 하면 해당 구조체의 구조를 매번 확인해야 한다.
물론 눈으로 봐도 뭔지 알수 있겠지만 이 방식 보다는 다음 방법을 추천하며 그냥 이 방법을 쓰도록 하자

jeffrey := person{name: "jeffrey", age: 20, favFood: favFood}

값의 타입을 미리 지정하여 사용하는 방법이고 맨 앞에 타입을 사용했다면 부분적으로 타입의 생략이 불가능하다

jeffrey := person{name: "jeffrey", 20, favFood} // 맨 앞에 타입을 지정했기 때문에 다른 field의 value의 type 지정이 필수

이 것은 맨 처음에 들어가는 값의 형태에 따라 뒤가 따라온다. 타입을 지정하면 뒤의 field도 타입 지정, 타입미지정인 경우 나머지도 미지정해야한다


Channels, Goroutines

위의 문법은 새로운 페이지에서 작성할 예정이다. 왜냐면 강의에서 미니 프로젝트하면서 설명해준다 함.. ㅎㅎ