2023년 12월 15일 금요일

Golang_min_ebook_kth

Golang_min_ebook_kth

Go Language

enter image description here

ⓒ 2021. Sugoi Group All rights reserved.
ⓒ 2021. provided by DX-NINJA, DX-JAPAN.

앱 개발 및 기타문의 : : cg99132@gmail.com
일본에서 프로그래머 취업하기 : (카카오톡 오픈 토크)

E-BOOK PDF 버젼 보기

Golang 이란

Go ?

2008년 구글의 사내 프로젝트로 개발한 Go 언어는 C언어형식을 기반으로 JAVA, C++같은 OOP프로그램의 클래스,객체상속 같은 복잡하고 불필요한 기능을 모두 배제하고 인터프리터언어와 콤파일러언어의 장점만을 적용하여 프로그래머가 좀더 간결하게 개발할수 있도록 개발자 친화적으로 만들어진 언어이다.

10여년 역사동안 큰 두각을 나타내고 있지 않았지만, 최근에는 도커(Docker), 곡스(Gogs), 쿠버네티스(kubernates), 몽고DB, 트위치 등의 규모가 큰 서비스에서 사용하고 있다.

Go 언어의 주요 특징

Golang은 기본적으로 Complie을 통해서 실행화일을 만들수 있는 컴파일어 언어이다. 컴파일언어라고 하면 C, C++같이 컴파일과정이 매우 느리고 지루한 언어라고 생각할수도 있는데 Golang언어는 컴파일속도가 매우 빨라서 마치 Python, Php 같은 인터프리터언어처럼 실행결과가 빠르게 나오기 때문에, 개발시에 컴파일에 의한 지루함은 많이 줄어들 것이기에 안심해도 좋다.

또한 Golang은 멀티 플랫폼용으로 컴파일이 가능해서 하나의 소스코드로 WIndow, Mac, Linux용 실행파일을 만들수 있다.

최근의 Golang은 서버쪽 백엔드 뿐 아니라, 프론트엔드, 심지어 모바일어플 개발까지 가능하도록 영역을 넓혀가고 있는중이다. (하지만 코틀린이 좀더 개발자에게 친근해보인다)

Go 언어의 주요 특징은 다음과 같다.

  • 불필요한 중복코드를 쓰지 않도록 간결하고 유연한 문법
  • 가비지 컬렉션 제공
  • 컴파일 언어
  • 멀티플랫폼에 실행가능한 프로그램 개발가능
  • “메모리 주소참조”용 포인터가 있어서 메모리에 최적화된 개발가능
  • 자료형이 정해 있는 정적타입
  • 동시성 프로그램이 가능하도록 GoRoutine 을 제공
  • 모던프로그램 개발에 필요한 다양한 라이브러리를 기본 제공
  • 복잡하고 중복코딩발생의 원인이 되었던 클래스, 상속, 제너릭등의 기능 배제
  • 함수형 프로그램

Go 언어가 기존 언어와 다른점

Golang은 간결하고 중복되지 않는 문법을 추구한다. 따라서 기존의 불필요한 문법들은 과감히 통합했고, 포인터를 사용하여 변수생성의 남발을 막고
메모리를 절약하면서 자료에 빠르게 접근할수 있도록 했다.

최근 일부 언어에서는 반복문도 for, foreach, for in , while , do while 처럼 여러가지가 있어서 같은 개발자가 비슷한 로직을 개발할때도 여러개의 반복문을 혼합해서 사용하는 경우가 있다.

golang에서는 반복문은 무조건 for, 조건문은 if, 선택문은 switch 를 사용하도록 했고, 고정크기 배열은 array, 가변크기 배열은 slice, key-vaue 를 map 을 사용하도록 했다.

따라서 개발자는 어떤 문법을 쓸까에 고민할 필요없어, 항상 같은 문법과 패턴으로 일관성 있게 로직을 짤수 있다. 혹시 없거나 추가적으로 필요한 기능은 라이브러리 형태로 제작하도록 유도하기 때문에 개발을 할수록 재사용 라이브러리와 축적되는 장점도 생긴다.

Golang에서는 또한 OOP의 핵심이라고 할수 있는 상속과 제너릭을 과감히 배제했다. 상속과 제너릭은 제대로만 사용한다면 프로그램을 좀더 구조화시켜 주지만, 추상화를 제대로 하지 못한다면 그냥 거대한 클래스 덩어리로 되는 경향이 종종있기 때문에 Golang에서는 이를 배제하고 구조체(struct) 와 인터페이스만으로 함수의 기능을 확장하고 데이터를 구조화 시킬수 있도록 했다.

또한 C에서 사용하던 포인터를 도입했는데, 메모리 연산, 캐스팅등 복잡한 기능들은 배제하고 단순히 메모리에 최적화된 프로그램을 개발하기 위한 메모리참조용 포인터기능만을 도입했다. (하지만 C언어 보다 가볍고 빠르지는 않다)

이정도까지는 그냥 기존언어에서 장점만을 가지고 새로운 언어를 만들었다고 생각할수도 있겠다.

사실, Goglang이 주목받는 가장 큰이유는 동시성프로그램에서 뛰어난 성능을 내기 때문이다.

동시성프로그램을 개발할때 JAVA, C 에서는 쓰레드를 생성하는데 이때의 쓰레드는 OS에서 쓰레드의 실행스케쥴을 관리한다. 운영체제는 하나의 프로그램만 관리하는게 아니기 때문에 다른일을 하다가 우리가 개발한 프로그램의 쓰레드들을 보러 와서 로직만큼 실행하고 가야하는데, 이 과정에서 오버헤드가 발생한다.

Golang에서는 스레드를 OS에 맡기는게 아니라, Golang의 실행루틴에서 자체적으로 쓰레드 관리를 한다. 즉, OS차원에서는 Golang의 메인쓰레드만 관리하고 프로그램내에서의 하위쓰레드는 go 루틴이 실행해주기 때문에 아무리 많은 쓰레드를 생성해도 OS에 부담이 덜 가게된다.

기존의 방식은 쓰레드가 필요할때 별도의 도로를 새로 깔고 프로그램을 달리게 하는것이라면, 고루틴은 이미 깔려져있는 도로에 한가할때 프로그램을 실은 루틴을 만들어서 달리도록 하는것과 같다. 물론 루틴이 많아지면 알아서 도로를 새로 깔기도 한다.

이밖의 특징으로는

  • 웹관련 패키지들을 기본제공해서 편하다.
  • OOP개념에서 상속, 클래스,제너릭 개념을 빼고 순수OOP개념만 남겼다.즉 구조체와 인터페이스로 상속개념이아님 합성개념으로 프로그램을 짤수 있게 해준다.
  • 컴파일언어니까빠르다. 결과물은 각 OS에 맞게 나온다.
  • 가독성이 좋다. 라고 하지만 기존 OOP 개발자나 script기반개발자들에겐 struct과 method, point 조합에서 혀를 내둘루게 된다.
  • 애초에 언어를 go 라는 이름으로 지은 작명센스가 고자다.<-격하게 공감한다. 구글의 변태개발자가 지가 무슨 신이나 된것처럼 작명한거같다. 덕분에 구글링이 매우 힘들다.
  • 배열과 배열같지 않은 배열인 슬라이스 라는 걸 따로 제공한다.
  • 확장함수같은 개념으로 구조체에 확장 함수를 갖다 붙이는 개념을 메소드(이것도 작명센스가 콱)라는 걸 만들었다. 역시 기존소스 분석이 어렵게 된다.
  • 멀티쓰레드를 기본적으로 간단히 제공하기 때문에 동시성 프로그램제작시에 매우 빠르다고 한다. 그래서 쿠버네티스등 시스템쪽 솔류션에 더 적합할수도있다.

일단 개발만 하라는 Golang

Golang 은 기본적으로 개발자가 많이 자주 쓰이는 라이브러리 (git 연동)같은 기능들을 기본탑재했다. 그래서 git 에 올라가있는 라이브러리는 외부커멘드로 다운로드 받는게 아니라, golang프로그램에서 git에 올라가있는 라이브러리를 바로 import 할수 있다.

또한, testing, documentation, formatting 등의 기본툴들을 내장하고 있어서, 바로 테스트코드를 작성,실행해 볼수 있고, 도큐먼트를 만들고, golang소스문서의 포맷을 재정렬할수 있다.

생각해보면 개발시에 git에서 라이브러리 받아서 우리 프로젝트에 포함하고, unit test 라이브러리를 연동하고, 도큐먼트와 소스포맷터로 lint 걸어서 체크하는게 얼마나 귀찮고 따분한 작업이었는가?

golang 에서는 이러한 것들을 “일단" 기본기능에서 제공하기 때문에, os나 환경에 영향을 받는 라이브러리를 따로 설치안해도 모든 os 환경에서 개발해도 동일한 결과물을 낼수 있다.

이러한 철학은 golang 의 아키텍쳐에서도 볼수 있는데, 불필요한 class 와 상속, while 등을 과감히 배제하고 최대한 심플한 형태로 간결하게 코딩을 할수 있도록 여러가지 문법에 변화를 주었다.

Golang 설치와 프로젝트 폴더 구조

설치는 각자 OS에 맞게 Go 공식 홈페이지(https://golang.org/doc/install)에서 설치하면된다.

Golang에 꼭 필요한 환경변수는


GOBIN : go install 명령을 할때 제작한 실행파일이 들어갈 경로이다.

GOROOT : 하위폴더(bin, doc, pkg,src) Golang관련 컴파일러,도움말,예제등이 있다.(=JAVA_HOME)

GOPATH : 작업 공간Workspace를 지정한다. 기본적으로 src 를 하위 폴더로 생성해서 go 파일을 생성한다. go get 등으로 외부라이브러리를 가져오면 GOPATH 하위에 가져오게 된다. 즉, golang파일 실행시 사용한 라이브러리를 검색하는 경로이기도 하다.

export GOROOT="/usr/local/Cellar/go/1.15.2/libexec"

export GOPATH="$HOME/study_golang"

export GOBIN="$GOPATH/bin"

export PATH=“PATH:PATH:GOROOT/bin:$GOBIN”

처음 go 언어를 설치후 프로젝트 폴더에서 main.go를 만들고, 자체라이브러이를 만들어서 go run main.go 해서 실행하려 할때 다음과 같은 메시지가 나올때가 있다.

cannot find package ……

…….(from $GOPATH)

이럴때는 go env해보면 GOPATH가 현재 작업중인 프로젝트폴더가 아니라 다른곳으로 지정되어 있어서 src하위의 라이브러리 폴더를 읽어 오지 못한것인 원인이다.

GOPATH를 현재 작업중인폴더로 지정하면된다.

export GOPATH=/Users/mymy/workspace/golang/sample1/

go install 해서 실행파일을 만들때 다음과 같은 에러메시지가 나올때가 있다.

go install: no install location for .go files listed on command line (GOBIN not set)

go install 을 하여 생성된 실행파일을 놓을 공간 GOBIN이 설정안되어 있는 것이 원인이다.

GOBIN을 현재 작업중인폴더하위의 bin으로 지정한다.

export GOBIN=/Users/mymy/workspace/golang/sample1/bin

또는

export GOBIN=$GOPATH/bin

Golang 개발용 IDE

go 언어로 프로그램을 좀더 편하게 개발하기 위한 IDE는 ATOM( 플러그인 ), Goland(유료), VSCode(플러그인)등이 있다.

기호에 따라 선택할수 있지만, 무료이면서도 사용성이 좋은 VSCode + Go plugin 을 이용하는것을 적극 권장한다.

Golang Workspace

golang은 다른 프로그램언어와 달리 하나의 workspace 에서 작업하되 각각의 프로젝트별 소스는 src폴더에서 각각폴더를 생성해서 구분짓고, 관련패키지는 pkg 폴더에서 일괄적으로 관리하는 구조이다.

이런 구조가 처음에는 조금 당황스러울수 있는데 java 나 c, node같은 프로젝트에서 같은 외부라이브러리를 각 프로젝트마다 임포트하여 버젼관리가 제각각이었던 점을 생각해보면 golang처럼 한군데서 라이브러리와 소스를 관리한다는 것도 조금은 수긍이 간다.

어쩄든, golang 프로젝트는 각 프로젝트는 src하위폴더에서 각 프로젝트 이름으로 생성하고, pkg는 여러프로젝트에서 쓸수있도록 pkg에 저장이된다

pkg폴더에는 go get “외부패키지" 명령으로 가져온 라이브러리가 위치하는데, 자신이 만든 라이브러리도 go install “go파일" 하면 GOPATH 하위에 자동으로 pkg/OS플랫폼(예:darwin_amd64)/모듈파일.a 형식으로 라이브러리가 만들어진다.

pkg폴더에 만들어진 라이브러리는 다른 프로젝트에서도 import 해서 재사용할수 있다.

정리하자면, GOPATH로 지정된 프로젝트들의 상위폴더에

src : 각각의 프로젝트를 폴더로 구분하여 개발

pkg : 외부또는 생성한 라이브러리들이 위치하여 프로젝트에서 사용할수 있게함

bin : 프로젝트를 컴파일하여 실행파일을 놓아두는곳 (GOBIN 경로로 지정해야한다)

와 같은 폴더 구조를 기반으로 개발한다.

Golang Tools

golang는 소스코드의 포맷, 외부라이브러리 import, 소스코드 스타일체크, doc 제작, 함수리턴값 재정렬 등 소스코드 품질을 높일수 있는 몇가지 기본적인 도구를 제공해준다.

이런 툴(실행파일)들은 go가 설치된 폴더에 있으며, GOROOT로 환경변수가 지정되었다면 아무 터미널에서도 사용할수 있다.

  • gofmt : 코드 포맷터로서 들여쓰기, 간격등 자동으로 조절해 준다.
  • goimports : 라이브러리의 import 문을 자동으로 삽입해주거나, 삭제해준다.
  • golint : 잘못된 코딩스타일에 대해 경고를 해준다.
  • goreturn : import의 삽입/삭제, 함수의 리턴값에 맞는 return 문을 수정해준다.
  • godoc : 라이브러리 혹은 소스코드에 작성한 주석을 코드와 함께 보기좋게 출력해준다. godoc자체에 서버기능이 있어서 포트만 지정하면 브라우져를 통해서 프로젝트 전체의 주석을 볼수도 있다.

main 프로그램 엔트리 포인트(프로그램 진입점)

golang의 각 프로젝트는 GOPATH 에 지정된 폴더의 하위 src 의 밑에 프로젝트명 으로 생성한다.

생성된 모든 프로젝에는 main 이라는 패키지에 main() 함수가 있어야 golang 에서 프로그램의 시작점으로 인식한다.그렇다 c언어에서처럼 무조건 main() 이다.

파일이름은 상관이 없지만 package main 과 function main() 이 존재하지않는다면 프로그램은 절대로 실행되지 않을것이다.

(예.

package main

func main() {

} 

golang 프로그램 실행

golang소스를 실행하는 방법에는 두가지가 있다. 인터프리터 언어처럼 go 실행파일을 이용해서 바로 실행하는 것과 빌드(컴파일)과정을 거쳐 자체 실행화일로 만드는 방법이다.

바로 실행하려면 .go 파일이 있는 곳에서 go run 명령어로 실행하면 된다.

go run hello.go

자체적으로 실행가능한 실행파일을 만들려면 go build 명령어로 빌드(컴파일)하면 된다.

go build hello.go  

하면

hello 파일이 생성됨 (window라면 hello.exe)

go build xxx.go 하면 현재폴더에 실행파일이 생성되기 때문에, go install xxx.go라고 하여 처음에 지정했던 GOBIN 폴더에 실행파일이 생성된다.

  go install hello.go

실행파일 어디갔나~? GOBIN으로 설정한 곳에 가 있다.

go install: no install location for directory 가 뜬다면

export GOBIN=$HOME/workspace/golang/xxx/bin

등과 같이 bin 이라는 이름의 폴더(없으면 만들어라)에 들어가 있다.

Go .go 파일의 기본구성

.go파일은 크게 package선언 , import 절, 본문소스코드 로 이루어 진다.

package main

import (
  "fmt"
  "math"
)

const username = "kim"

func main() {
  fmt.Println(username)

}

Package : main 패키지와 그외 라이브러리 패키지

main package 는 프로그램의 시작점임을 알리기 위한 특별한 패키지 선언이다.

main 패키지 이외에는 전부 별도의 패키지로 구분되며 킴퍼일러가 라이브러리 형태로 파일을 생성하여 프로그램내에서 import를 통해 사용할 수있도록 해준다.

package main

별도의 패키지를 어떨때 만드는가?

급하게 프로그램하다보면 main 패키지 내에서 모든것을 한번에 처리하려고 하는 경향이 있다. 규모가 크지않은 작은 프로젝트에서는 문제가 없을수도 있지만, 프로젝트 규모가 커지고 지속적인 수정사항이 요구된다면 기능별, 화면별로 별도의 패키지로 떼어내서 모듈화 시키는 과정이 필요하게된다.

기능별로 별도의 패키지로 만들어 놓았다면, 다른 프로젝트 진행시에 해당 패키지를 import 하여 재활용할수 있다는 장점도 있다.

소스코드내에서는 필요한 패키지를 여러개 불러낼수도 있는데, golang에서는 불러만내고 사용하지 않는 패키지에 대해서는 컴파일에러를 내뿜는다. 따라서 무시문자 _ 를 사용하여 import가 가능하다.

서로 다른 소스로부터 import한 패키지를 구분하기 위해서 별칭을 제공한다.

모듈 패키지 생성

$GOPATH/src/utils/strutils.go

package strutils

import (
 "strings"
)

func ToUpperCase(s string) string {
 return strings.ToUpper(s)
}

메인패키지에서 모듈 패키지를 import ( strutils 로 alias 함)

package main

import (
 "fmt"
 strutils "utils"
)

func main() {
 fname := "hello world!"
 fmt.Println(strutils.ToUpperCase(fname)) 
}
go run main.go
HELLO WORLD!

변수

go언어는 기본적으로 var(variable:변수) 를 이용해서 선언한다. 변수라고 선언하기위해서 예전부터 내려오던 관습(?) 이기 때문에 이부분도 golang에서도 그대로 사용한다.

golang에서 변수 선언방식이 기존의 선언 방식과 조금 다른것이 있는데 “변수명 다음에 변수타입" 이라는 것이다. golang 에서는 개발자가 읽기 편한 방식으로 하기위해서 “이 변수는 이런 타입이야" 라고 이야기 하고 싶었다고 한다.

(신입이야 그런줄 알겠지만, 경력자는 이미 String myText 와 같은 형식에 익숙해져 있다)

var myText string
myText = "wtf"

단순히 변수선언만 보면 뭐가 모던이냐 할지도 모르겠다. 그래서 쓸데없이 var, var, var 하게되어 코드가 반복되고 지루해 보이는것같아서 golang 에서는 := 라는 약식표현을 통해서 선언을 할수 있도록 해준다.

  myText := "wtf"

간단하지 않는가? 거의 모든 소스들이 이처럼 변수선언을 하고 있다.

대신 선언과 동시에 값을 넣어야 golang에서 타입추론을 할수 있으며 전역변수에는 사용할수 없다.

변수는 한번에 여러개를 선언할수도 있다.

var myv func(float64, float64) float64
myv = func(p, i float64) float64 {
   return p / (i * i)
} 

//var () 안에 여러개를 묶어서 선언할수도 있다.

var (
   nums = 10
   ids []string = []string{"test1", "test2", "test3"}
   vector = []float64{35.4, 13, 344, 78, 2.5}
)

변수명은 일부 예약어와 숫자로 시작하는 문자를 제외하고는 변수명으로 사용할수 있다.

golang에서는 선언후 사용하지 않는 변수가 있다면 에러를 내뿜는다. 독한것…

빈식별자 (사용하지 않을 변수)

웹에서 golang 의 예제 소스들을 보면 변수이름이 나올 위치에 _ (밑출) 표시가 나오는 경우가 있다.

이는 굳이 변수명이 필요없는 무시가능한 변수의 경우에 사용한다.

알다시피 golang에서는 어떤 스코프에서던 사용하지 않는 변수명이 있다면 에러를 내뿜는다. _ 는 그럴때 유용하다.

예를 들어 for 문, if문, 함수의 리턴값중 특정순서의 리턴값을 무시하고 싶을때 사용한다.

myVar,_ := getData()
if myVar,error; error !=nil {
 getData()
}

상수

다른 언어에서처럼 const키워드를 사용하여 상수를 선언한다.

단 ,상수로 선언될수 있는 값은 bool, int,string 이다.

golang에서는 상수를 사용하여 열거형(Enumeric) 형식으로 선언할수 있다. ( Enum 형식은 golang에서는 없기때문이다)

사실 Enum선언때 개발자가 가장많이 하던게 이름과 숫자값을 지정하던 거였으니, golang에서는 그냥 닥치고 상수로 통일해서 쓰라는 거같다.

const (
  Saturday = 5      // 5
  Sunday = iota // 0
  Monday        // 1
  Tuesday       // 2
  Thursday      // 3
  Friday        // 4
)

토요일처럼 특정 상수값을 지정할수도있고, iota라는 특별한 키워드를 지정하면 0 부터 다시 자동증가되는 상수를 만들수도 있다.

for : 반복문은 오로지

다른 프로그램에서는 for, foreach, while 등이 있지만, golang에서는 for 문 밖에 없다.

for 문은

  • 아무런 조건이 없는 for ( 다른 언어에서는 while 이라고 부른다 )
for {
    fmt.Printf("무한반복.\n")
}
  • 단순 조건만 있는 for
n := 1
for n < 100 {
    fmt.Printf("count %d\n", n)
    n = n + 2
}
  • foreach처럼 배열의 요소를 참조하는 for
var actors [4]string = [4]string{"A", "B", "C", "D"}
for index, value := range { }
  • 초기값, 조건, 증감값이 있는 전형적인 for
for i := 0; i < COUNT; i++ {
}

range 를 이용하면

for range []int{1,1,1,1} {

       fmt.Println("Looping")

}

for i, v := range []V{1,2,3} {

}

또는

for k, v := range

map[K]V {

...

}

이렇게 map, string, 배열, 채널 까지 자동으로 해담범위를 지정하여 준다.

물론 continue, break는 있다.

for {
    i = i + 1if i < 0 {
        break
    }
    if i%2 == 0 {
        continue
    }
}

중첩 for문일경우 부모for문의 break,continue 를 제어할수 있다.

HEREME:
     for i &lt; 100 {
       for k  &lt;10 {
         if k == 5 {
           continue HEREME
         } 
         if k == 7 {
           break HEREME
         } 
       }
    }

추가로 goto 를 지원하는데, for 문안에서 특정 위치의 밖으로 빠져나간다든지 할 수 있다. 그러나 사용하는 순간 유지보수는 포기하자.

if : 조건식

조건식에 소괄호가 없다는 것만 뺴곤 보통의 if와 같다.

추가로 if 문의 조건식 앞부분에 if 블럭안에서 사용할 변수초기화를 할수 있다.

if …else if 로 여러 조건을 사용할수 있지만, 소스가 지저분해 지기 때문에, switch … case문을 사용하는게 좋다.

err := json.NewDecoder(r.Body).Decode(user)  
if err != nil {  } 
//아래 처럼 한줄에 쓸수도 있다.
if err := json.NewDecoder(r.Body).Decode(user); err != nil {  } 

switch , case : 다중조건식

switch 의 사용법이 다른 언어와 조금 다르게, case 문에 여러개의 조건을 다양하게 걸수있고, break 문이 없다는 것이다.

break문이 없다라기 보다는 case문의 마지막에 break문이 항상 숨어있다라고 하는게 맞을것이다.

case 비교값을 여러개 사용할 수 있기 때문이다.

여러개의 case문을 통해야 하는 경우가 있을경우 각 case문의 마지막줄에

fallthrough

라고 적으면 현재 case 문이 끝나지 않고 다음 case 문을 실행하도록 한다. ( 말그대로 통과다)

fallthrough 가 필요한 상황을 많드는것 보다 case 문 여거값을 두는게 좋다.

type 형식도 비교 가능하다.

func testSwitch(data interface{}) {
  switch  data.(type) {
  case int:
     fmt.Println("number")
  case string:
     fmt.Println("string")
  default:
     fmt.Println("idontknow")
  }

}

추가로 switch 조건식에 인터페이스를 지정할수도 있기때문에, 인터페이스의 구현체만 바꿈으로서 좀더 재사용이 가능한 프로그램이 가능하다. ( 테스트 코드 작성할때 매우 유용하겠다 )

array : 고정배열

golang 에서의 array 는 초기에 고정한 크기를 변경할수 없다.

따라서 중간에 전체 개수가 변하지 않을 배열을 만든다면 array를 사용한다.

만일 크기변경이 가능한 배열을 만들고 싶을 때는 slice라는 것을 사용한다.

선언방식이 조금 특이한데

var myArray [5]string

처럼 한다. “선언하는데 myArray를 5개로 string형식이야" 라고 말하는것과 같다(? 억지다)

slice : 변경가능한 배열

slice는 java 에서의 ArrayList 처럼 배열의 길이를 조절할 수 있다.

golang에서는 더 나아가서 배열에서 일부 데이터(혹은 데이터범위)를 가져오는 것을 편하게 잘라가져올수 있도록 하는 기능을 제공한다.

slice는 call by reference즉 , 선언시에 메모리에 공간을 만들고 나서 값을 채우는 방식으로 사용한다. 따라서 선언 방식도 일단 메모리에 공간을 만들라는 명령어([] 빈공간 또는 make 함수)를 이용해서 선언한다.

slice는 메모리참조형식이기 때문에 append 등을 이용하여 요소를 추가할때 미리 확보했던 메모리 공간보다 클경우 새로운 reference 즉 메모리 주소를 확보하여 돌려준다.

그래서 slice 에는 len 함수와 cap 함수가 있는데, len 는 현재 선언된 slice배열에 값이 몇개나 들어있나를 알려주고, cap(capavility) 는 이용가능한 전체 배열의 크기이다.

golang은 다양한 기기나 단말에 이용할수 있도록 설계되어 있기 때문에, 메모리에 대한 규칙도 다소 엄격하다.

프로그램 설계를 할때 slice배열의 크기를 너무 작게 해버리면 append에 의해 메모리복사, 가비지 가 자주 발생하게 되어 실행속도가 좋지 않게 될수도있기 때문에, 적절한 cap 를 가진 slice배열을 미리 지정하여 사용하는게 좋다.

( 예, 원격지에서 받아오는 게시물 데이터가 100개 이하라면 아예 make 로 100개의 slice공간을 가진 배열을 생성해놓는게 좋다.)

  • 가변인자에 Slice 값 전달
    func Sum(nums …int) 와 같은 가변인자가 있을때, []int{1,2,3,4} 와 같은 Slice값을 넣고 싶을때는 Sum(num…) 라고 뒤쪽에 … 전개연산자를 넣음

map : 키와 값으로 이루어진 컬렉션

map은 key 를 이용하여 value값을 간단하게 입력하거나 꺼내올수 있도록 도와주는 컬렉션이다 .

key는 int, string,포인터 등 참조타입이 아닌 모든 형태가 올수 있으며,value는 모든 형식이 올수 있다.

선언은

map[string]int{"apple": 150, "banana": 300, "lemon": 300}

처럼 map[키형식]값형식 으로 한다.

특정 키에 값을 대입하려면

m["apple"] = 150

처럼 하면 되고, 불러낼때도 키를 이용해서 값을 불러내면 된다.

golang에서는 map 의 key가 있는지 없는지를 boolean 값으로 편하게 알수 있도록 해준다.

**myVal, isAlready := m[“myKey”] **

myKey가 있다면 myVal에 값이 들어가고, isAlready 에는 true가 들어간다. 만일 없다면 isAlready에는 false가 들어간다.

Collection : container/heap

우선순위큐형태의 minHeap(트리)를 import 해서 사용할수 있다. pop으로 가져오는 값은 항상 트리에서 가장작은값이 된다.
공식사이트에서 설명과 예제를 참고하자.
https://golang.org/pkg/container/heap/

// This example demonstrates a priority queue built using the heap interface.
package main

import (
	"container/heap"
	"fmt"
)

// An Item is something we manage in a priority queue.
type Item struct {
	value    string // The value of the item; arbitrary.
	priority int    // The priority of the item in the queue.
	// The index is needed by update and is maintained by the heap.Interface methods.
	index int // The index of the item in the heap.
}

// A PriorityQueue implements heap.Interface and holds Items.
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }

func (pq PriorityQueue) Less(i, j int) bool {
	// We want Pop to give us the highest, not lowest, priority so we use greater than here.
	return pq[i].priority > pq[j].priority
}

func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}

func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	old[n-1] = nil  // avoid memory leak
	item.index = -1 // for safety
	*pq = old[0 : n-1]
	return item
}

// update modifies the priority and value of an Item in the queue.
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
	item.value = value
	item.priority = priority
	heap.Fix(pq, item.index)
}

// This example creates a PriorityQueue with some items, adds and manipulates an item,
// and then removes the items in priority order.
func main() {
	// Some items and their priorities.
	items := map[string]int{
		"banana": 3, "apple": 2, "pear": 4,
	}

	// Create a priority queue, put the items in it, and
	// establish the priority queue (heap) invariants.
	pq := make(PriorityQueue, len(items))
	i := 0
	for value, priority := range items {
		pq[i] = &Item{
			value:    value,
			priority: priority,
			index:    i,
		}
		i++
	}
	heap.Init(&pq)

	// Insert a new item and then modify its priority.
	item := &Item{
		value:    "orange",
		priority: 1,
	}
	heap.Push(&pq, item)
	pq.update(item, item.value, 5)

	// Take the items out; they arrive in decreasing priority order.
	for pq.Len() > 0 {
		item := heap.Pop(&pq).(*Item)
		fmt.Printf("%.2d:%s ", item.priority, item.value)
	}
}

Collection : container/list

Doubly Linked List 를 import 해서 사용할수 있다.
공식페이지 참고.https://golang.org/pkg/container/list/

mylist := list.New()
//링크드 리스트니까 다음꺼를 찾을수 있다.
mylist.PushBack("A")
mylist.PushFront("A")
for e := mylist.Front(); e != nil; e = e.Next() {
	// do something with e.Value
}
//링크드 리스트니까 이전꺼도 찾을수 있다.
for e := mylist.Back(); e != nil; e = e.Prev() {
	// do something with e.Value
 }

  
type newsPublisher struct {  
   subscribers *list.List  
}
newsCenter := newsPublisher{  
   subscribers: new(list.List),  
}
// list 요소 순회
func (n *newsPublisher) update(text string) {  
   for e := n.subscribers.Front(); e != nil; e = e.Next() {  
      s := e.Value.(subscriber)  //value가 사용자타입으로 변환
      s.onUpdate(text)  //사용자 인터페이스에 정의한 함수 실행
   }  
}
// list 에추가
func (n *newsPublisher) add(s subscriber) {  
   n.subscribers.PushBack(s)  
}  
//리스트에서 삭제  
func (n *newsPublisher) delete(s subscriber) {  
   var removeItem subscriber  
  for e := n.subscribers.Front(); e != nil; e = e.Next() {  
      removeItem =  e.Value.(subscriber)  //value가 사용자타입으로 변환
      if s == removeItem {  
         n.subscribers.Remove(e)  //element로 삭제해야함.
         break;  
      }  
   }  
}

Collection : container/ring

Circular Linked List를 만들어준다. 맨끝노드는 첫노드를 참고한다.
공식페이지 참고.https://golang.org/pkg/container/ring/

package main

import (
	"container/ring"
	"fmt"
)

func main() {
	// Create a new ring of size 6
	r := ring.New(6)

	// Get the length of the ring
	n := r.Len()

	// Initialize the ring with some integer values
	for i := 0; i < n; i++ {
		r.Value = i
		r = r.Next()
	}

	// Unlink three elements from r, starting from r.Next()
	r.Unlink(3)

	// Iterate through the remaining ring and print its contents
	r.Do(func(p interface{}) {
		fmt.Println(p.(int))
	})

}

range : 배열이나 map 의 요소의 범위를 돌려줌

배열(slice포함) 이나 map 의 요소를 반복하고자 할때, range 를 사용하면 포함된 요소들을 for 문을 사용해서 모두 확인할 수있다.

kvs라는 맵의 요소를 반복할경우

for k := range kvs {
        fmt.Println("key:", k)
}

num 이라는 배열(slice) 요소를 반복할경우

for i, num := range nums {
    if num == 3 {
     fmt.Println("index:", i)
    }
}

한다. 굳이 이런말을 하는이유는 golang에서는 함수와 메소드가 명확히 구분되어 있어서이다.

함수는 기본적으로 받을 인자(고정 혹은 가변인자), 리턴값 으로 구성된다.

가변인자와 리턴값은 포인터를 가리킬수 있는데 java, php 등의 언어를 하던 사람들이 조금 어려울수 있다.

java,php 등의 언어에서는 call by value 즉, 함수에 인자로 값을 넣어서 그 함수에서는 해당값에 대한 새로운 복사본을 만들어서 처리된 새로운 값을 돌려 주는 방식으로 실행된다. 반면 c 언어에서는 call by reference라고 해서 해당 값이 가지고 있는 메모리 주소를 넘겨주어 함수내에서도 해당 값을 직접 고치는게 가능하다.

golang에서는 c언에서처럼 두개다 지원, 특히 call by reference 를 지원해서 실행시 메모리 복제떄문에 일어나는 퍼포먼스 저하를 줄이고 , 쓸데없이 리턴값을 돌려줘야하는 수고를 없앴다. 다만 복잡도는 늘었다.

함수의 인자로 몇개가 들어올지 모르는 경우를 대비해 가변인자를 지원하는데, … (점세개) 지시어를 함수의 인자명 앞에 넣어주면된다.

struct : 구조체

데이터의 형식만을 정의하는 struct은 c 의 struct와 기능적으로 같다.
type 스트럭명 struct { 필드들 }
식으로 정의한다.

struct은 단지 구조체쳉체이기 때문에 객체를 생성하여 값을 넣어주어야 사용할수 있다.
객체생성의 방식은 다음과 같다.

type car struct {
    maker string
    year int
    //factories []int
}
//변수를 선언후 값을 나중에 할당1
var car1 car
car1 = car{"hyundai", 1998}

//변수를 선언후 값을 나중에 할당2
car2 := car{}
car2.maker = "kia"
car2.year = 1998

//생성과 동시에 값을 할당.
car2 := car{"hyundai", 1998}

//golang의 new 내장함수를 사용하여 필드값을 zero Value로 초기화하여 생성
car3 := new(car) //new에 의해 car3는 포인터변수가 된다.
car3.name = "kia"

//struct선언과 초기화를 동시에 진행
bestCar :=[]struct {
    maker string
    model string
    power int
}{
{maker: "bmw", model: "5678"},
{maker: "audi", model: "1234", power: 1000},

}

type alias : 데이터 형식에 대한 알리아스를 정의

struct을 정의할때 type이라는 키워드를 앞에 붙였는데, type 키워드는 지정된 이름을 그뒤의 나오는 형식의 대신으로 사용한다는 의미이다.
즉 type fooJsonHandler Struct{} 라고 한것은 fooJsonHandler 가 참조되면 빈 struct{}의 주소를 바라보겠다는의미이다.

//http예제에서 사용한 type aliase
type fooJsonHandler struct{}
func (f *fooJsonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
//server.go의 Handle함수의 두번째 인자에 지정된   Handler 인터페이스를 fooJsonHandler 구조체의 확장함수 ServerHttp에서 구현하여 서버가 실행된다 
mux.Handle("/api/myjson", &fooJsonHandler{})

//함수를 alias한 type
//아래는 type floatToIntF 와 func(float64) int 로 나누어 보면된다. 
type floatToIntF func(float64) int  
  
byteSizeToIntFunc := map[int]floatToIntF{  
   16: float64ToInt,  
   32: float64ToInt,  
}  
  
myByteInt := byteSizeToIntFunc[16](12.0)
  
func float64ToInt(float float64) int {  
   return int(float)  
}

method : 함수 또는 메소드의 리턴값

최근 다시 함수형 프로그램이 유행이 되면서 기존 class 에서의 extends 키워드 처럼 기능을 확장하는 하는것에 대한 여러가지 방법들이 나오고 있다. 일부 프로그램에서는 extention 라는 키워드로 기존 함수에 추가할수 잇도록 하거나, 필터 등 각자의 언어별로 함수의 기능을 확장하는 방법이 있다.

golang 에서는 구조체에 대해서 추가로 함수를 확장 하는 개념으로 접근했는데, method 라는 형식으로 사용한다.

즉, 구조체는 데이터만 가지고 있는 것이므로 , 데이터를 가지고 처리를 하기위해서는 method(처리방법) 이라는 개념을 붙여서 기능을 확장할수 있는것이다.

method 는 특정 구조체에 얽메이지 않고, 해당 구조체가 가진 값을 가지고 처리를 하는 역활만 한다.

이를 위해서 golang에서는 함수선언시에 리시버라는 특별한 인자를 추가하여 선언하면 해당 함수가 method 역활을 할수 있도록 설계했다.

리시버는 사용자가 만든 구조체가 들어올수 있고, 해당 구조체의 요소를 가지고 메소드(함수) 내 에서 적절한 처리를 할 수있다.

 type rect struct {
    width, height float64
}

func (r rect) area() float64 {
    return r.width * r.height
}

func (r *rect) area() float64 {
    return r.width * r.height
}

메소드의 리시버는 call by value 와 call by reference로 나뉜다.

call by value는 (r rect) 식으로 되어 값이 직접전달되어 메소드 내에서 해당값이 복제된다. 따라서 전달 받은 리시버에 값을 변경해도 원래의 리시버객체는 값이 변경이 안된다.

call by reference는 (r *rect)식으로 되어 참조주소가 전달되어 메소드내에서 전달받은 리시버의 메모리주소를 참조하여 값을 쓰거나 읽을수 있게된다.

interface : 코드의 인터페이스

인터페이스는 다양한 프로그램적 요구에 맞게 공통된 규격을 제시함으로서 코드의 유지보수와 확장을 용이하게 해준다.
실세계에서나 프로그램에서나 인터페이스가 없으면 불필요한 것들을 매번 재생산하는 불편이 생길것이다.

golang에서도 인터페이스를 type형태로 선언하여 사용할 할수 있는데, 함수의 인자로서 사용한다.
golang에서는 interface 의 구현체를 만들고 연결하는 방식이 조금 특이한데, extends 와 implements 라는 개념이 golang에서는 없기 때문이다.
따라서 인터페이스를 정의하고 동일한 인자와 리턴값으로 메소드를 선언하여 인터페이스를 구현했다고 인식한다.

type buz struct {say string }  
type iBla interface {  blabla(name string)  }  
func (b *buz)  blabla(sayWhat string)  {  
  b.say = sayWhat  
}  
func blabla2(sayWhat string) iBla {  
  return &buz{}  // buz가 인터페이스를 구현하고 있다
}  
func makeBlabla(bla iBla)  {  
  bla.blabla("hehe")  
}

v := &buz{}  
makeBlabla(v)  
makeBlabla(blabla2("ff"))

//buz 구조체에 blabla(name string) 메소드를 iBla인터페이스와 동일한 형태로 하면 구현체가 된다.
//일반 함수도 리턴값이 인터페이스이면 된다.

익명인터페이스도 지원하는데, 함수의 인자부분에 인터페이스 정의를 하면 된다.

  
type iBla interface {
   blabla(name string) 
}
 
type buz struct {
   say string
}

type aChild struct {
   buz
}  
func (b *buz) blabla(sayWhat string) {
   b.say = sayWhat
}

func canBla() iBla { 
   return &aChild {
      buz: buz{
      say: "hellow"
   }
}
canBla().blabla("hungry")

func : private vs public 지정

golang에서는 정말 어이없게도, 패키지내에서 함수를 선언할때 첫문자를 소문자로 지정하면 private 함수이고 대문자로 지정하면 public 함수의 속성을 가진다. **대형사고 여기저기서 터질것같다.

다른 언어에서는 함수나 메소드의 첫글자를 대문자로 지정하는 관습이 있는데, golang에서는 잘 구분해서 정의해야 한다.

private 는 같은 패키지 내에서는 호출할수 있고,

public 은 외부에서도 함수를 호출할수 있다.

func : 인자

인자는 func를 포함한 모든 형식이 지원된다.
개수가 정해져있지않은 여러개의 인자는 … 으로 받는다.
함수형 프로그램처럼 함수를 바로 인자로 사용할수도있다.

func myFunc(f func(sum int) (int, int)) {  
   a,b := f(20)  
   fmt.Print(a,b)  
} //처럼 선언하고

fn := func(sum int) (int, int) {
	return 0,1
}
myFunc(fn);

func : 함수 또는 메소드의 리턴값

  • golang에서 함수이름의 첫문자를 대문자로 쓰면 외부공개 용 이라는 표식이된다.

  • 일반 프로그램언어에서는 기본적으로 리턴값을 한개만 가진다. 두개이상의 리턴값을 할수가 없기 때문에 보통 struct 이나 Entity object를 만들어서 거기에 담아서 해당 객체를 리턴한다.

golang 에서는 두개이상의 리턴값이 가능하다. 바로 위에서 말한대로 객체만들어서 거기에 담아서 리턴한다는게 얼마나 귀찮고 부질없는 짓인지 우리는 알고 있다.

그래서 golang에서는 리턴값을 여러개 할수 있도록 해서 불필요한 것들을 만들지 않고 리턴값을 호출하는 쪽에서 바로 처리할수 있도록 해준다.

package main

import (
  "fmt"
)

func MyNames() (first string, second string) {
  first = "First"
  second = "Second"
  return
}

func main() {
  fmt.Print( MyNames())

  name1, name2 := MyNames()
  fmt.Print( name1, name2)
}

추가로, 리턴되는 변수이름에 값을 바로 함수내에서 이용할수 있다. 위의 소스를 보면 리턴하는 두개의 변수명이 있는데, 이를 함수 내에서 선언도 없이 바로 대입했다. 보통의 프로그램이라면 함수내에서 리턴용 변수를 선언한다음 그것을 리턴하는 것이 일반적인 방식인데, 그러한 복잡성을 없애고 간결하게 할수 있도록 했다.

또한 return 구문에서도 굳이 리턴할 변수를 입력하지 않아도 알아서 리턴하도록 했다. 몰론 직접 return first, second 처럼 변수명을 명시해도 좋지만, 굳이 변수이름을 여기저기 남발하며 쓸필요가 없다.

(소스 분석때는 자기가 짰던 소스도 분석이 불가능할지도 모르겠다. 그래서 되도록 한파일에 코드를 짧게 쓰는 습관이 필요하다.)

리턴된 값은 순서대로 여러변수에 한번에 대입할수 있다. name1, name2 를 보면 MyNames에서 first, second 문자열을 바로 두군데에 대입한 것을 볼수 있다.

자바스크립에서

const { first, second } = {first:“First”, second:“Second”}

라고 했던 것과 비슷하다.

func : defer함수내에서 특정블럭을 지연시키기 defer

defer(지연시키기) 기능은 함수내의 특정 불럭또는 다른 함수 호출을 나중으로 미룰수 있는 기능이다.

defer로 예약된 블럭이나 다른 함수 호출부분은 함수실행의 맨 마지막 단계에 실행되는 것으로 예약되어 있기 때문에, 함수의 마지막에 해야 될 처리들 , 즉 file함수의 close 나 데이터베이스 disconnect , go channel닫기 등의 작업을 할때 유용하다.

java의 finally와 비슷하지만 약간 다른 점은 defer는 여러개를 지정할수 있다는 점이다.

이때 주의할 점은 쌓이는 순서는 stack 형태로서 맨마지막에 지정한 defer 함수가 맨처음 실행된다.

package main
   import "fmt"
   func do(steps ...string) {
   	  //3
      defer fmt.Println("All done!")
      for _, s := range steps {
      		//2
            defer fmt.Println(s)
      }
      //1
      fmt.Println("Starting")
}

func main() {
   do(
"step1",
"step2",
"step3",
"step4",
) }

출력 결과를 보면 1->2->3 인데, 원래 2는 step1—>4까지 이지만 defer특성상 stack형식이므로 위에서 부터 꺼내쓰니까 4–>1이된다.

Clouser : 함수를 일급객체로 하여 변수와 리턴값에 할당

함수기반의 프로그램언어의 경우 함수를 일급객체로 취급하여 특정변수와 인자, 리턴값에 함수를 지정할수 있다.

마치 c++ 에서 함수포인터와 거의 같은 개념으로서, 지정한 변수가 함수를 가르키도록 하여 변수를 호출하면 해당 함수가 실행되도록 하는 것이다.

클로저는 이러한 개념을 이용한 방식으로서, 내부에서 별도의 함수를 실행한 결과를 바깥쪽 함수에 돌려주는 방식이다.

예를 들어, 회사의 어떤 부서에서 물건에 대한 세금포함, 미포함, 특별세추가 에 대한 검증을 하고자 할때, 클로저의 바깥쪽 함수에서는 세금비율을 지정하고, 내부함수에서는 해당 세금비율을 계산한 최종물건값을 계산하고 싶다고 하자.

일반적인 방법으로 설계하자면,

func getFinalPrice( tax int, productPrice int ) (finalPrice int){
	finalPrice = productPrice + tax;
	return 

}

라고 정의할 수 있다.그런데 이 함수는 인자를 두개씩 받고 있어서 함수가 실행스택에 쌓일때 마다 두개의 메모리 변수를 필요하게 된다. 이런 방법은 클린 하지 못한 개발방법이다.

이를 클로저를 이용하면 좀더 깔끔하게 할수 있다.

func getFinalPrice( tax int ) func(finalPrice int){
	return func(finalPrice int) int {
              return productPrice + tax;
    }
}

finalPriceWithTax := getFinalPrice( MY_CONST_TAX_10P )
fmt.println(finalPriceWithTax(PRODUCT_SNACK1):

go에서는 함수가 일급객체이기 때문에 .

var (
      mul = func(op0, op1 int) int {
            return op0 * op1
     }

      sqr = func(val int) int {
            return mul(val, val)
     } 
 )

func main() {
      fmt.Printf("mul(25,7) = %d\n", mul(25, 7))
      fmt.Printf("sqr(13) = %d\n", sqr(13))
}
   

이렇게도 가능하다

Pointer : 메모리 주소를 직접참조 하기

C++이외의 거의 모든 프로그램언어들은 포인터라는 개념을 없앴다. 포인터라는 것은 메모리의 물리적인 주소값으 나타내는 것이다.

우리가 프로그래밍 할때 변수나 함수, 클래스들은 어차피 물리적인 메모리에 저장이 되기 때문에, 메모리주소를 직접 참고해서 값을 쓰거나, 읽을수 있는 것은 당연하다. 그치만 메모리주소라는게 사람이 알아보기 어려운 형태(0x1234) 와 같은 형식이라 프로그램언어에서는 사람이 알기쉽도록 변수 라는 것을 사용하여 해당 변수가 어떤 메모리를 참고하고 있는지 따위는 신경안쓰고 개발할수있도록 해준다.

이렇게 메모리주소를 신경안쓰고 개발할때는 개발하기가 편한반면, 변수에 값을 할당하거나 재설정 할때 불필요하게 메모리를 복제하거나 재생성하여 메모리를 낭비할수 있게 되어버린다.

포인트를 사용하여 메모리주소를 직접참조하면 불필요하게 새로운 메모리를 생성안하고도 같은 메모리 주소를 사용할수 있게되어, 프로그램의 전반적인 속도와 메모리 효용성이 높아지게 된다.

c++프로그램이 java 나 다른 프로그램보다 빠른 처리가 가능한이유가 , 포인터를 통해 메모리를 직접제어할수 있기 떄문이지만 프로그램에서 포인터를 다루는 것은 생각보다 간단하지 않다.

golang 에서는 속도와 메모리를 효율적으로 높이기 위해서 c언어의 포인터를 “장점만” 차용했다.

“장점만" 이라고 표현한 이유는 포인터가 가지는 최대의 장점인 메모리 주소 참조만을 차용했기때문이다.

포인터 연산이나 캐스팅은 없앴다.

변수를 포인터로 선언하면 해당 변수는 메모리의 물리적인 주소값(0x012345) 와 같은 값을 가져야한다. 포인터 변수는 * 마크를 붙인다.

자 그럼 , 우리는 이 포인터변수가 가리키는 메모리주소에 어떤 값이 있는지 알아내야 하는데, 변수호출시 & 마크를 붙이면 포인터변수가 담고있는 데이터를 내놔라고 하는 것과 같다.

포인터변수를 사용하게 되면 , 다른 여러변수들이 포인터변수 하나만 바라보도록 설계할수도 있어서, 같은 데이터를 여기저기 여러변수들이 들고 있을 필요가 없어 메모리공간을 절약할수 있다.

꽤나 무거운 구조체등을 포인터변수가 가리키고 있다면, 프로그램에서 해당 포인터변수를 가르키는 여러변수들은 단지 메모리주소(int) 값만 들고 있으면 되기 때문에, 각각의 변수들이 무거운 구조체를 일일히 들고 있을 필요가 없어서 메모리 공간이 절약된다.

포인트가 프로그램하기에는 다소 까다로운 부분이 있지만, 프로그램입장에서는 메모리를 최소화하고 처리속도를 증가시킬수 있기때문에 golang에서는 차용한 것같다.

포인터의 선언은

   var p *int 

처럼 * 을 붙이고 메모리를 확보활 크기형식(int,string 나 구조체도 된다)

처럼 하는 방식과

new()를 사용하여 생성하는 방식이 있다.

  p := new(int)

포인터는 메소드의 receiver와 interface 에서 주로 사용한다.

// Poooint가 바라보는 주소값을 참조하게된다.
// type struct Poooint {}  하고 
// haha := Poooint{}  하면 
// haha.my()  함수있다. Poooint의 확장함수처럼 되었다. 
func (a *Poooint) my() {}

//인터페이스를 통해 주소값을 돌려받을수 있다.
type iMyInterface interface {}
func (a *Poooint) my() iMyInterface {
    return &Poooint{}
}

//줄때는 *, 받을떄는 & 를 사용하면 메모리주소를 참고할수있다.

커멘드에서 인자 받기

멀티os기반의 프로그램이 가능하기 때문에 os.Args로 여러개의 인자를 받을수 있다.

func main() {

  fmt.Print("gogogo")
  who :=  "world"
  if len(os.Args) >1 {
     who = strings.Join(os.Args[1:], " ")

  }
  fmt.Println("hello", who)
}

panic :

golang에서 실행상의 에러가 발생할때는 panic 함수가 실행되며 프로그램이 멈추게된다. 패닉상태는 단지 프로그램의 로직상의 문제로 인해 에러가 된 상황이기때문이다.

panic은 상위호출함수로 계속 전달되기 떄문에 프로그램이 중지된다.

recover :

golang에서 실행상의 에러가 발생할때는 panic 함수가 실행되며 프로그램이 멈추게된다. 이를 막기위해서 함수 내의 defer를 사용해서 호출한 함수가 멈추지 않도록 recover를 체크하여 프로그램이 중단되지 않고, 문제가 있는 함수만 panic상태로 두게 제어할수 있다.

error : golang에서의 에러처리

golang에서는 다른 언어에서처럼 try{}catch{}finally{}, throw 라는 것이 없다.

대신 error가 발생할때는 모든 함수의 실행마지막에 에러값을 돌려주도록 되어있어, 함수의 반환값을 체크해서 에러객체에 에러값이 있나 없나를 확인하는 방식이다.

error패키지는 golang의 내부패키지로서 단순히 import 만 하면된다.

goroutines : 동시성 프로그램

golang 런타인에서 관리하는 경량쓰레드(몇kb수준)이다.

goroutines는 go의 런타임에서 관리하기 때문에 프로그램이 종료되면 모든 goroutine스레드가 종료된다.

만일 os thread라면 프로그램이 종료되어도 메모리상에 해제되지않는 쓰레드가 계속 가동되게 될것이다.

따라서 golang에서는 프로그램이 종료되기 저넹 기동중인 goroutine 이 모두 종료 될때 까지 기다리는 async.waitGroup 이라는 것을 사용하여 지정된 개수의 goroutine 이 모두 끝나서 카운트가 0이 될 때 메인프로그램을 끝낼수 있도록 해준다.

var wait sync.waitGroup

wait.Add(2)

go func() {
	defer wait.Done()
	fmt.Println(“hello”)
}()

go func2(s string) {
	defer wait.Done()
	fmt.Println(s)
}(“f”)

wait.Wait();

goroutine은 기본적으로 1개의 CPU를 가지고 시분할(매마다 다음 goroutine을 돌아가며 처리하는것) 방식으로 동시에 여러 루틴을 처리한다.

최근에는 4,8,16 코어의 CPU가 있는데, 여러 CPU에서 동시다발적(pararell)로 처리하고자 할때는 runtime.GOMAXPROCS(CPU 개수) 라고 지정하면 된다.

참고로 말하자면, concurrent (동시성) 은 여러대의 기차를 한명의 기관사가 왔다갔다 하면서 움직이도록 하는거고, parellelism (멀티성) 은 여러대의 기차를 여러명의 기관사가 같이 움직이도록 하는것이다.

선언은 참쉽다.

func myRoutine() {}

go myRoutine()

또는 다른 루틴과 통신이 필요할때는

myChannel chan int 

func myRoutine(myChannerl chan string) {}

myChannel := make (chan string) 스트링 타입의 채널 메모리를 할당했다.

go myRoutine( myChannel)

channel : goroutine들이 데이터를 전달 하는 방법

goroutine은 기본적으로 각각의 goroutine들이 메시지를 통해서 데이터를 전달받는다.

메시지 라는 것은 신호라는 의미이기도 한데, 달리고 있는 goroutine 에 특정 메시지가 담긴 신호를 주어서 해당 메시지의 내용을 처리하는 것이다.

이를 golang 에서는 channel 이라는 키워드르 통해서 할수 있도록 했다.

channel 에는 어떤 데이터형식도 지정할수 있으며, 채널을 전달받은 goroutine은 채널의 형식과 내용을 보고 해당 루틴에서 적절한 처리를 할수 있다.

선언은

myChannel chan int 

라고 선언하다.

선언후에는 goroutine에서 메시지를 주고 받을 메모리를 할당 해야 하는데 아래와 같이 한다.

myChannel := make (chan string)

채널을 생성하고 메모리 공간을 마련했으면 goroutine 함수에서 채널에 값을 전달받거나 전달할수 있다.

func myRoutine(myChannerl chan string) {
	myChannel &lt;- “hehe”  채널(메모리공간) 에 지정된 형식의 값을 넣었다.
}

func myRoutine2(myChannerl chan string) {
	sayWhat := &lt;- myChannel
	fmt.Println( sayWhat)  채널(메모리공간) 에  담겨진 값을 꺼낸다.
}

go myRoutine( myChannel)
go myRoutine2( myChannel)

채널은 기본적으로 양방향 (보내기 받기) 가 가능해서 받은 채널에 다시 새로운 값을 던져넣을 수도 있다.

경우에 따라서는 받기만 또는 주기만 하는 채널이 필요한데, <- 키워드를 이용해서 가능하다.

사용 예 )

func myRoutineOnlySend(myChannerl &lt;- chan string) {
    myChannel &lt;- “hehe”  
    // sayWhat := &lt;- myChannel   채널의 값을 꺼내는건 절대 안됨.
}

func myRoutineOnlyRecieve( &lt;- myChannerl  chan string) {
    myChannel &lt;- “hehe”   // 채널에 값을 보내는건 절대 안됨
    sayWhat := &lt;- myChannel  
}

buffered channel : 채널에 일정한 크기를 제한함

goroutine 은 기본적으로 채널에 값이 올때까지 대기하게된다. 이를 unbeffred Channel 이라고 하는데, 버퍼링을 하지 않기 때문에, 하나의 메시지를 누군가 보내기 전까지는 대기 상태로 있게된다.

buffeted channel은 지정된 숫자만큼 채널에 버퍼를 두어 , 한번에 여러개의 채널을 비동기 형태로 보낼수 있다. 채널에 메시지를 보내는 입장에서는 일단 여러개의 채널메시지를 던져놓고, 제한된 개수만큼 던져놓았으면 , 수신받는 쪽이 메시지를 모두 받을때까지 대기한다. 수신자가 모두 받았다면 다시 메시지를 보내는 방식으로 처리된다.

channel close : 채널 닫기

close(채널변수) 로 채널을 닫아서 송신을 할수 없게 한다.

channel select : 여러채널 값을 감시해서 적절한 처리가능

swich 문과 비슷하게 생긴 채널 전용의 select 문이 있는데 여러 channel로 부터 데이터가 들어왔을때 어떤 채널인지 감지하여 cae문에서 적절한 코드를 실행할수 있도록 해준다.

select문은 자체적으로 반복하는 기능이 없기때문에, 지속적으로 채널을 감시하려면 for문, 또는 range 등에 넣어서 사용하면된다.

swith문처럼 default를 둘수 있다.