..

Golang 문법 시작하기

변수


GoLang의 변수는 크게 2가지 const(상수), var(변수)로 이루어진다.

  • const
    • const name string = “Sanbal”
  • var
    • var name string = “Sanbal”
    • name := “String”
      • 위와 같이 작성하면, Go가 타입추론을 통해서 타입이 지정된다.
      • := 을 쓸때 타입이 지정되기 때문에 나중에 다른 type을 넣을 수 없다.

Function


// 기본적인 function의 형태
func len(name string) int {
	return len(name)
}

// 입력을 여러개 받는 경우
func words(words ...string) {
	// 입력받은 값을 list 형태로 나타낸다.
	fmt.Println(words)
}

// 반환을 여러개하는 경우, python처럼 가능하다.
func lenAndUpper(name string) (int, string) {
	return len(name), strings.ToUpper(name)
}
// -> namelen, up := lenAndUpper('sanbal')

// naked func
// 미리 반환할 변수를 지정해 return될 때 해당 변수를 반환한다.
func naked(name string) (lenght int, uppercase string) {
	// 위에서 이미 변수가 할당됬으니까 :=와 같은 식은 쓰지 않도록 하자!
	lenght = len(name)
	uppercase = strings.ToUpper(name)
	return
	// return lenght, uppercase 해도 상관은 없음!
}

defer

함수가 종료되면 실행되는 명령어

  • return이 발생하고 난 뒤에 실행된다.
  • 비즈니스 로직에서 꽤 유용하게 쓰일 듯하다!
func naked(name string) (lenght int, uppercase string) {
	defer fmt.Println("I'm done!")

	lenght = len(name)
	uppercase = strings.ToUpper(name)
	return
}

반복문 For


Go에서 반복문은 for 하나뿐이다. map, forEach와 같은 기능은 없고 딱 for만(심플해서 오히려 섹시해) for문을 위해서 range가 있는데 python에서 enumerate() 같은 느낌으로 작동한다.

func superAdd(numbers ...int) int {
	total := 0
	// range를 이용한 방법
	for i, number := range numbers {
		total += number
	}

	// 위와같이 쓰면 i에 빨간줄이 생기는데, 사용하지 않는 변수가 있으면 오류가 난다;;;;;
	// 그래서 _를 이용해서 무시해버릴 수 있음! (python에서는 _를 변수로 쓰는 명시적인 느낌이였는데, 얘는 진짜로 못쓴다;;;)
	for _, number := range numbers {
		total += number
	}
	
	// 물론 전통적인 방식도 지원한다.
	for i := 0; i< len(numbers); i++ {
		fmt.Println(i, numbers[i])
	}

	return total
}

조건문 if else


if else

if else는 뭐…심플하다

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

	// 근데 위와같이 쓰는 것보단 이게 더 쿨, 펀, 섹시하기도 하고 vscode에서 extension으로 추천해준다.
	if age < 18 {
		return false
	} 
	return true
}

Switch

python엔 없고, C에서 써본 이후로 정말 오랜만인데 가끔 필요한 일이 생긴다.

func canIDrink(age int) bool {
	// 조건이 걸리는 변수를 지정하거나
	switch age {
	case 10:
		return false
	case 18:
		return true
	}

	// 조건을 명시하거나
	switch {
	case age < 18:
		return false
	case age == 18:
		return true
	case age > 50:
		return false
	}

	return false
}

variable extension

go에만 있는 기능인데, 조건문 안에서 사용할 변수를 조건문 앞에서 사용하는…? 그런 문법이다.

// if else
if koreanAge := age + 2; koreanAge< 18 {
	return false
}
return true

// switch
switch koreanAge := age + 2; koreanAge {
case 10:
	return false
case 18:
	return true
}

Pointer


C언어 이후로 정말 오랜만에 보는 기능이다! C 배울때 여기서 많이들 포기했었지… 기본적 개념은 같고, 사용법도 비슷하다.

  • & : address(주소)
  • * : address의 값
  • := 이 아니라 선언만 하고 싶다면, var potiner *int를 사용하자
func main() {
	a := 2
	b := &a
	fmt.Println(b, *b)
	// -> 0x1400001c070 2

	a = 10
	fmt.Println(b, *b)
	// -> 0x1400001c070 10

	*b = 20
	fmt.Println(a)
	// 20
}

Array & Slice


배열에는 2가지 종류가 있다! 라고 하기엔 조금 애매하긴한데, lenght를 지정해주거나 안해주거나로 2가지로 나뉜다. array는 lenght가 고정되고, slice는 고정되지 않는다.

// array
func main() {
	names := [5]string {"sanbal", "hello", "world"}
	fmt.Println(names)
	names[3] = "hi"
	fmt.Println(names)
}
// slice
func main() {
	names := []string {"sanbal", "hello", "world"}
	fmt.Println(names)
	names = append(names, "hi", "happy")
	fmt.Println(names)
}
  • array는 전통적인 방식으로 사용! 당연히 lenght를 벗어나는 index에는 값을 넣을 수 없다. vscode가 알려주던데?
  • slice는 python에서 사용하듯이 사용할 수 있지만. append는 재할당하는 방식으로 사용해야한다. 솔직히 python 어떤거는 함수는 자기자신 바꾸고, 어떤거는 할당해야하고 그래서 조금 헷갈렸었다…

Map


map도 심플하다. 심플하다 못해 기능이 없다. 없으면 만들어 써야지 뭐…

func main() {
	// key와 value의 자료형은 딱 정해서!
	ksanbal := map[string]string {
		"name": "ksanbal",
		"age": "SuperSecret",
	}
	fmt.Println(ksanbal)

	// 추가는 이렇게
	ksanbal["blog"] = "devksanbal.site"
	fmt.Println(ksanbal)

	// for range가 가능!!
	for key, value := range ksanbal {
		fmt.Println(key, value)
	}
}

비어있는 map을 만들려면 어떻게 해야할까?

// 잘못된 방법
var result map[string]string 
result['hello'] = 'world' // 그럴 듯 해보이지만, panic을 발생한다. result가 nil로 처리되게 떄문

// 올바른 방법
result := map[string]string{} // 끝에 {}로 초기화해준다.
var result = make(map[string]string) // make() 함수를 이용해 초기화한다.
result['hello'] = 'world'

Struct


map 형태에 value를 다양한 자료형으로 하고싶을 때가 있다. 보통 그렇게 관리하기도 하고. go에는 class가 없는 대신 struct가 있다.

type persion struct {
	name string
	age int
	anything []string
}

func main() {
	ksanbal := persion{"ksanbal", 18, []string{"hello", "world"}}
	// 또는
	ksanbal = person{
		name: "ksanbal",
		age: 18,
		anything: []string{"hello", "world"},
	}
	// keyword 방식과 positioned 방식을 섞어 사용할 수 없다.
}

class 대신 struct를 쓰니까 그리운 부분이 생기는데 바로 private와 construct이다. go에서는 어떻게 구현할까 일단 account라는 폴더를 만들고, 그 안에 account.go 파일을 생성하자.

Go에서는 재밌게도 변수, struct, func의 이름 앞글자를 소문자로하면 private, 대문자로하면 public이다. 아래와 같이 account를 생성하면, 중간에 account에 .으로 접근하여 값을 변경할 수 없다.

Constructor

// account.go
package account

// Account struct
type Account struct {
	owner string
	balance int
}

// NewAccount creates Account
// instance를 만들어 복제하는 건 메모리 낭비니까, pointer를 이용해 값을 전달하자.
func NewAccount(owner string) *Account {
	account := Account{
		owner: owner,
		balance: 0,
	}
	return &account
}
// main.go
package main

import (
	"fmt"

	"github.com/devKsanbal/account"
)

func main() {
	account := account.NewAccount("ksanbal")
	fmt.Println(account)
}

Method

이번엔 struct의 method들을 생성해보자. 여기서 Deposit method에서 *에 유의하자! go는 함수를 사용할때 값을 복사해서 사용하기 때문에, pointer를 사용하지 않으면 함수 내에서만 값이 변경된다.

// GetBalance 현재 금액 출력
func (a Account) GetBalance() int {
	return a.balance
}

// Deposit 입금
func (a *Account) Deposit(amount int) {
	a.balance += amount
}

// String fmt.Println(account) 했을때 나올 String 함수 override
func(a Account) String() string {
	return fmt.Sprint(a.GetOwner(), "'s account\nHas : ", a.GetBalance())
}

Error 처리


go에서는 에러가 뜨면 프로그램을 멈추고 그 내용을 뱉어주지 않는다. 프로그래머가 직접 에러를 기록하고 발생시켜야한다. log.Fatalln()은 에러 내용을 출력하고 프로그램을 종료해준다.

// account.go
func (a *Account) Withdraw(amount int) error {
	if a.balance < amount {
		var errNoMoney = errors.New("Can't withdraw, you are poor")
		return errNoMoney
	}
	a.balance -= amount
	return nil // 뭐든 return 해야하기 때문에, nil(null,None)으로 반환
}

// main.go
func main() {
	account := account.NewAccount("ksanbal")
	account.Deposit(10)
	err := account.Withdraw(20)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(account)
}

Dictionary


이번엔 struct가 아닌 dictinary에 method를 추가해보자. 전에 map에 기능이 없다는 얘기를 했었는데 그걸 대신 하는 느낌으로 만들면 될 것 같다.

// mydict.go
var (
	errNotFound = errors.New("Not Found")
	errCantUpdate = errors.New("Can't update non-existing word")
	errCantDelete = errors.New("Can't delete non-existing word")
	errAlreadyExist = errors.New("Already Exit")
)

// Dictionary type
type Dictionary map[string]string

// Search for word
func (d Dictionary) Search(word string) (string, error) {
	value, exists := d[word] // dictonary[value]는 기본적으로 값과, 존재여부를 반환해준다. 그 점을 이용하자
	if exists {
		return value, nil
	}
	return "", errNotFound
}

// Add Add a word
func (d Dictionary) Add(word, def string) error {
	_, err := d.Search(word)
	switch err {
	case errNotFound:
		d[word] = def
	case nil:
		return errAlreadyExist
	}
	return nil
}

// Update update a word
func (d Dictionary) Update(word, def string) error {
	_, err := d.Search(word)
	switch err {
	case nil:
		d[word] = def
	case errNotFound:
		return errCantUpdate
	}
	return nil
}

// Delete delete word
func (d Dictionary) Delete(word string) error{
	_, err := d.Search(word)
	switch err {
	case nil:
		delete(d, word)
	case errNotFound:
		return errCantDelete
	}
	return nil
}
// main.go
func main() {
	// Search
	dictionary := mydict.Dictionary{"first": "First Word"}
	definition, err := dictionary.Search("second")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(definition)
	}

	definition, err = dictionary.Search("first")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(definition)
	}

	// Add
	dictionary = mydict.Dictionary{}
	err = dictionary.Add("hello", "Greeting")
	if err != nil {
		fmt.Println(err)
	}
	
	definition, _ = dictionary.Search("hello")
	fmt.Println(definition)

	err2 := dictionary.Add("hello", "Greeting")
	if err2 != nil {
		fmt.Println(err2)
	}

	// Update
	dictionary = mydict.Dictionary{}
	dictionary.Add("hello", "First")
	// err := dictionary.Update("hello", "Second")
	err = dictionary.Update("test", "Second")
	if err != nil {
		fmt.Println(err)
	} else {
		word, _ := dictionary.Search("hello")
		fmt.Println(word)
	}

	// Delete
	dictionary = mydict.Dictionary{}
	dictionary.Add("hello", "First")
	dictionary.Add("world", "Second")
	fmt.Println(dictionary)
	err = dictionary.Delete("hello")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(dictionary)
	}
}

GoRoutines


go에서 가장 섹시한 점은 바로 Goroutines라는 동시실행이다. 사용법도 쉽다.

func main() {
	people := [4]string {"ksanbal", "hyunkyun", "hello", "world"}

	for _, person := range people {
		go isSexy(person)
	}
}

func isSexy(person string) {
	time.Sleep(time.Second * 5)
	fmt.PrintLn(person, " is sexy")
}

근데 그냥 이렇게 쓰면 아무 결과도 못보고 끝난다. 왜일까? 그 이유는 main()함수는 GoRoutines을 기다리지 않기 때문이다. 그렇기 때문에, 기다릴 수 있고, 서로 데이터를 주고받을 수 있어야한다. 그걸 위해 존재하는 것이 Channel이다.

Channels

Channel은 GoRoutines이 반환하는 값을 받아주는 역활은 한다. callback 함수 같은 느낌이지만 아닌 것 같다. 사용법은 쉽다. <-로 데이터를 전달하고 받는다.

func main() {
	people := [4]string {"ksanbal", "hyunkyun", "hello", "world"}

	c := make(chan string) // string을 반환받는 chan인 c를 생성한다.

	for _, person := range people {
		go isSexy(person, c)
	}
	
	// 실행이 종료되는대로 넘어온 값을 출려해준다.
	result1 := <- c
	result2 := <- c
	fmt.Println(<-c)
	fmt.Println(<-c)
	fmt.Println(<-c) // 더이상 전달받을 값이 없기 때문에 deadlock 오류가 발생한다.
}

func isSexy(person string, c chan string) {
	time.Sleep(time.Second * 5)
	c <- person + " is sexy"
}

그런데 여기서 문제가 있다. <-은 blocking operation이다. 즉 값이 전달될때까지 프로그램이 멈춰서 기다린다. 그리고 <- 이 전달된 것보다 더 많으면 deadlock 에러를 발생한다. 그러면 어떻게 해야할까?

func main() {
	people := [4]string {"ksanbal", "hyunkyun", "hello", "world"}

	c := make(chan string) // string을 반환받는 chan인 c를 생성한다.

	for _, person := range people {
		go isSexy(person, c)
	}
	
	for i:0; i<len(people); i++ {
		fmt.Println(<-c)
	}
}

func isSexy(person string, c chan string) {
	time.Sleep(time.Second * 5)
	c <- person + " is sexy"
}

사실 별건 없고 for문으로 GoRoutines을 생성한만큼 돌려주면 된다. <-c를 기다리는 거니까, for문 첫줄에 넣고 그 뒤에 callback 함수처럼 작성하면 로직을 수행할 수 있을 것 같다.

Send only & Recevie only

그리고 channel은 send only와 receive only 옵션을 적용할 수 있다. 프로그래밍적으로 미리 기능을 차단해서, 코드 작성 중 오류가 발생하지 않도록 하는 것이다. <-의 위치에 따라 적용되며 send only에서 값을 받으려하면, vscode에서 미리 경고를 내준다.

// send only
func hitURL(url string, c chan<- requestResult) {
	...
}

// receive only
func hitURL(url string, c <-chan requestResult) {
	...
}

자잘한 것

  1. 배열은 배열명 뒤에 ...을 이용해서 모든 요소를 꺼낼 수 있다.
    1. append(jobs, []*string* {"police", "doctor", "teacher"}...)