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) {
...
}
자잘한 것
- 배열은 배열명 뒤에
...
을 이용해서 모든 요소를 꺼낼 수 있다.append(jobs, []*string* {"police", "doctor", "teacher"}...)