MadApp

(GolangPractice Day3) OJT 2021-12-13 KJH

느리지만 꾸준하게 2022. 3. 13. 23:35
  • 대략적인 정리(NomadCoder - Golang 입문)

https://kjh950601.tistory.com/317?category=902684

https://kjh950601.tistory.com/318?category=902684 

https://kjh950601.tistory.com/319?category=902684 

https://kjh950601.tistory.com/320?category=902684 

https://kjh950601.tistory.com/321?category=902684 

https://kjh950601.tistory.com/322?category=902684 

https://kjh950601.tistory.com/323?category=902684 

 

Maps & Structs

map의 key값과 value값을 string으로 지정을 해보자. package main import "fmt" func main() { jay := map[string]string{"name": "jay", "age":"27"} fmt.Println(jay) } // map[age:27 name:jay] value만 원한..

kjh950601.tistory.com

  • 구조체(메서드, 임베딩, 캡슐화)
  • 인터페이스와 타입 단언
  • Go : 에러와 패닉
  • 고루틴과 채널
  • 변수 스코프와 블록

 

구조체

Golang에는 class 개념 대신에 struct개념이 있다.

struct의 public & private

public과 private은 Golang에서 대문자 소문자로 나눠지는데

대문자로 시작한다 그러면 외부에서 가져다 사용할 수 있고,

소문자로 시작한다 그러면 내부에서만 사용이 가능하다.

 

stack 과 관련된 struct

스택에 구조체를 선언하는 것이다. 스택에 할당되어 {hwan 10}으로 출력이 된다.

package main

import "fmt"

type A struct {
	name string
	num int
}

func main() {
	a := A{}
	
	a.name = "hwan"
	a.num = 10

	fmt.Println(a)
}

// {hwan 10}

선언과 동시에 초기화도 가능하다.

 

 

package main

import "fmt"

type A struct {
	name string
	num int
}

func main() {
	a := A{"hwan", 10}

	fmt.Println(a)
}

// {hwan 10}

 

 

new 키워드를 통해서 struct 선언(Heap allocation)

new 키워드를 사용해서 구조체를 포인터로 받는다? 그러면 선언과 동시에 초기화가 불가능함.

Golang의 구조체는 일반적인 OOP언어에서 볼 수 있는 생성은 따로 없다.

 

아래 코드를 보자. {hwan 10}이 출력되는 것을 볼 수 있다.

힙에 할당된 자료가 참조되고 있다는 뜻으로 &이 출력이 된다.

( 그리고 생성자는 사용자가 직접 만들어줘야 한다.)

package main

import "fmt"

type A struct {
	name string
	num int
}

func main() {
	a := new(A)

	a.name = "hwan"
	a.num = 10

	fmt.Println(a)
}

// &{hwan 10}

 

 

struct 생성자

Golang은 주소값 전달받으면 주소값의 자료들은 모두 stack에서 heap으로 이동

package main

import "fmt"

type A struct {
	name string
	num int
}

func newA() *A {
	a := A{}
	a.name = "hwan"
	a.num = 10

	return &a
}

func main() {
	a := newA()
	fmt.Println(a)
}

// &{hwan 10}

 

 

함수와 같이 사용

go에서 struct에 멤버함수를 정의하는 기능이 없다.

밖에서 함수 정의한 다음에 struct를 참조하는 식으로 함수를 생성해야 한다.

아래 코드를 보자. print() 함수를 보면 ( a * A)라고 struct구조체를 참조하는 형태를 가진다.

package main

import "fmt"

type A struct {
	name string
	num int
}

func newA() *A {
	a := A{}
	a.name = "hwan"
	a.num = 10

	return &a
}

func (a *A) print() {
	fmt.Println(a)
}

func main() {
	a := newA()

	a.print() 
}

//output : &{hwan 10}

 

Golang의 interface

Interface의 선언도 Struct처럼 Type 선언이 가능.

interface를 제공함으로써 추상화를 돕고, 객체들을 서로 연결시켜주는 역할 제공

 

선언방법은 아래와 같다.

type Calc interface {...}

 

interface를 정의하는 방법을 나타내는 아래 코드를 보면

리시버 받는 구간은 plus() 함수를 정의한 부분이고 r Parameter 부분을 볼 수가 있다.

해당 struct는 인터페이스와 연결된 상태가 되면서,

showCalc() 부분을 보면 인자값을 Calculator로 받는걸 볼 수 있다.

package main

import "fmt"

type Calculator interface {
	plus() int
	minus() int
}

type Parameter struct {
	n1, n2 int
}

func (r Parameter) plus() int {
	return r.n1 + r.n2
}

func (r Parameter) minus() int {
	return r.n1 - r.n2
}

func showCalc(calc Calculator) {
	fmt.Println(calc.plus())
	fmt.Println(calc.minus())
}

func main() {
	r := Parameter{30, 20}
	showCalc(r)
}
// 50
// 10
 
 

리시버는 포인터로도 받는것이 가능하다.

package main

import "fmt"

type Calculator interface {
	plus() int
	minus() int
}

type Parameter struct {
	n1, n2 int
}

func (r *Parameter) plus() int {
	return r.n1 + r.n2
}

func (r *Parameter) minus() int {
	return r.n1 - r.n2
}

func showCalc(calc Calculator) {
	fmt.Println(calc.plus())
	fmt.Println(calc.minus())
}

func main() {
	r := &Parameter{30, 20}
	showCalc(r)
}
 

C++이나 C의 void*같은 역할을 하는 타입이 있는데

인터페이스에는 타입이 있다. 변수에도 선언이 가능하다.

package main

import (
	"fmt"
	"reflect"
)

func typeCheck(x interface{}) {
	fmt.Println(reflect.TypeOf(x))
}

func main() {
	var x interface{}
	x = 1

	fmt.Println(x)
	typeCheck(x)

	x = "Tom"
	fmt.Println(x)
	typeCheck(x)
}

// 1
// int
// Tom
// string
 

변수 선언한 후에 값을 입력하면, 해당 값에 대한 타입이 정의되어 아래와 같이 출력

package main

import "fmt"

func main() {
	var x interface{} = 1

	i := x
	j := x.(int)
	z := x.(string)

	fmt.Println(i) 
	println(i)     
	println(j)     
	
}

// 1
// (0x1c44a0,0x1f5e58)
// 1
// panic: interface conversion: interface {} is int, not string

 

 

z 변수의 경우 x는 string에 대한 값이 아니라서 error발생. error값을 boolean값으로 return 받게 해보자.

flag에는 bool자료형의 false가 저장된다.

package main

func main() {
	var x interface{} = 1
	
	z, flag := x.(string)

	println(z, flag)
}

// false

 

Type Switch문

switch문을 이용해서 type별로 case를 만들어 처리가능하다.

package main

import (
	"fmt"
	"reflect"
)

func typeSwitch(n interface{}) {
	switch n.(type) {
	case string:
		fmt.Println("string")
	case int:
		fmt.Println("int")
	default:
		fmt.Println(reflect.TypeOf(n))
	}
}

func main() {
	var x interface{} = 1

	i := x
	j := x.(int)
	z, flag := x.(string)

	typeSwitch(i)
	typeSwitch(j)
	typeSwitch(z)
	typeSwitch(flag)
}

// int
// int
// string
// bool

 

Embedding Interfaces

아래코드 처럼 Mixed라는 interface로 묶어서 실행을 해보자.

package main

import "fmt"

type ABC interface {
	abc()
}

type ZXC interface {
	zxc()
}

type Mixed interface {
	ABC
	ZXC
}

type Edge struct {
	x int
}

func (e Edge) abc() {
	fmt.Println(e.x + 100)
}

func (e Edge) zxc() {
	fmt.Println(e.x - 100)
}

func main() {
	edge := Edge{10}
	var mixed Mixed = edge

	mixed.abc()
	mixed.zxc()
}

// 110
// -90

 

 

 

Type assertion 이해

Type-Switch 즉 interface type이 가지고 value의 Type을 확인하는 것이다.

interface type의 x와 타입 T를 x.(T)로 표현했을때, x가 nil이 아니며, x는 T 타입에 속한다는 것을 확인하는 것을 Type assertion이라고 한다.

 

아래 코드를 보자. value a를 담은 i를 출력했을 때 값의 주소가 출력이되고 j를 출력했을 때는 값이 출력되는 것을 볼 수 있다.

package main

func main() {
	var a interface{} = 1

	i := a
	j := a.(int)

	println(i)
	println(j)
}

// (0xf7f120,0xf9c618)
// 1

 

 

위와 같은 방법에서 어떤 인터페이스의 값이 특정 타입인지를 확인하다가 에러를 방지하기 위해서

ok값을 받을 수 있다. assert한 타입이 맞으면 ok는 true, 아니면 false가 될 것이고 ok일때는, v에 concrete value가 할당된다.

v, ok := n.(int)

 

 

 

아래코드에서 i는 string type으로 정해놨기 때문에 float64가 아니므로 마지막에 ERROR가 발생

package main

import "fmt"

func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)

    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok)

    f = i.(float64) // panic
    fmt.Println(f)
}
// hello
// hello true
// 0 false
// panic: interface conversion: interface {} is string, not float64

 

 

 

에러와 패닉

golang에서 사용자가 error를 정의할려면

error 변수를 생성 => error변수 생성할려면 error 패키지 생성해야한다.

 

순서를 생각하고 아래 코드를 보자.

숫자가 다를때는 End normally가 출력이 되고 숫자가 같을 때는 error 변수를 생성하면서

입력한 문구가 출력된다.

package main

import (
	"errors"
	"fmt"
)

func returnError(a, b int) error{
	if a == b {
		err := errors.New("Error in returnError() functions")
		return err
	} else {
		return nil
	}
}

func main() {
		err := returnError(1, 2)
	if err == nil{
		fmt.Println("End normally")
	} else {
		fmt.Println(err)
	}

	err = returnError(10, 10)
	if err == nil {
		fmt.Println("End normally")
	} else {
		fmt.Println(err)
	}
}

// End normally
// Error in returnError() functions

 

 

errors.New

errors 패키지의 New 매서드는 파라미터로 문자열을 받아서 대응되는 에러 오브젝트를 만들어 반환한다.

해당 에러 오브젝트에 대해 Error() 메서드를 호출하면 생성할 때 넘긴 문자열이 반환됨.

package main

import (
    "errors"
    "fmt"
)

func div(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("0으로 나누기 ㄴㄴ")
    }
    return a / b, nil
}

func main() {
    var (
        a = 10
        b = 0
    )

    v, e := div(a, b)
    if e != nil {
        fmt.Println(e.Error())
    } else {
        fmt.Printf("%v / %v = %v\n", a, b, v)
    }
}

// 0으로 나누기 ㄴㄴ

 

Panic

panic은 겉으로 보이게는 아무런 문제가 없지만 실행하니까 에러가 발생해 프로그램이 종료되는 기능을 뜻한다.

아래 panic이 발생한 후 프로그램이 종료되기 전에 defer 구문이 실행되는 예시 코드를 보자.

  • 반복문 안에서 선언한 배열의 개수보다 큰 인덱스 값을 접근해서 panic이 발생하고 프로그램이 종료됨
  • “panic done“ 구문이 panic 에러가 발생하는 코드 전에 선언되어서 프로그램이 종료되기 전에
  • “Panic done“ 이 출력되고 종료가 된다.
  • main() 함수에 있는 fmt.Println(“Hello, world!”)는 실행되지 않음
package main

import "fmt"

func panicTest() {
	var a = [4]int{1,2,3,4}
	
	defer fmt.Println("Panic done")
	
	for i := 0; i < 10; i++ {
		fmt.Println(a[i])
	}		
}

func main() {
	panicTest()

	fmt.Println("Hello, world!")
}

// 1
// 2
// 3
// 4
// Panic done

recover() 함수를 Golang에서 사용하는데 Java에서 try~catch 구문을 사용해 예외 처리를 하는 기능이라고 보면된다. 즉 panic 상황이 생겼을 때 프로그램을 종료하지 않고 예외 처리를 하는 것이라고 보면된다.

index out of range에러를 발생하는 코드를 recover() 함수를 사용해서 예외 처리를 한 예시 코드가 아래에 있다. 코드에서 panic이 발생하는 코드 전에 defer 구문을 사용한 익명 함수로 recover() 함수를 선언해 놓았다.

 

 

 

  • for 문에서  index out of range panic이 발생. 프로그램이 종료 되기 전에 지연 처리한 defer 함수가 호출됨
  • 함수 내에 recover() 함수가 호출되어 panic을 복구함.
  • 즉 main()함수에서 선언한 fmt.Println('Hello, world!”)가 호출됨.
  • panicTest() 다음 코드인 fmt.Println(“Hello, world!”)을 실행
package main

import "fmt"

func panicTest() {
	defer func() {
		r := recover()
		fmt.Println(r)
	}()

	var a = [4]int{1,2,3,4}

	for i := 0; i < 10; i++ {
		fmt.Println(a[i])
	}
}

func main() {
	panicTest()

	fmt.Println("Hello, world!")
}

// 1
// 2
// 3
// 4
// runtime error: index out of range [4] with length 4
// Hello, world!

recover()함수 정리

  • panic이 발생해서 프로그램 종료되는 걸 막음
  • 프로그램 종료 전에 실행되어야 해서 defer가 선언된 함수 안에서 쓰임
  • 에러 메시지를 반환, 변수에 초기화해서 에러 메시지를 출력할 수 있음.
  • 변수에 초기화하지 않으면 따로 에러 메시지를 출력하지 않음.

 

 

 

 

goroutine

  • go routine은 함수 및 메소드를 다른 함수 및 메소드와 동시에 사용할 수 있게 해줌
  • 가벼운 thread이고 thread와 비교하면 만들고 사용하는데 적은 비용이 듬.
  • 기존 thread는 stack사이즈가 고정되어 있는데 반해 goroutine은 thread에 비해 application의 요청에 따라 stack의 용량을 늘렸다 줄일 수 있음.
  • goroutine들 사이에 channel을 통해 의사소통이 가능. channel은 의도적으로 goroutine을 사용해 공유 메모리에 접근할 때 경쟁상태 예방

일반적인 goroutine의 경우 순차적으로 코드가 실행된다.

package main

import (
	"fmt"
)

func main() {
	human1 := [2]string{"jay", "kevin"}
	human2 := [2]string{"thompson", "son"}

	humanName(human1)
	humanName(human2)
}

func humanName(names [2]string) {
	for _, name := range names {
		fmt.Println(name)
	}
}

// jay
// kevin
// thompson
// son

 

 

아래 코드를 보면

  • 여기서 time.Sleep()가 있는데 사용한 이유는 go keyword로 지정한 코드가 실행될 충분한 시간을 주는 것
  • 추가로 go로 지정된 함수 및 저기서 main이 종료되게 되면 go keyword가 있어도 바로 종료되는데 만약 go만 적힌 함수만 실행되면 아무 출력없이 바로 실행됨.
package main

import (
	"fmt"
	"time"
)

func main() {
	human1 := [2]string{"jay", "son"}
	human2 := [2]string{"park", "kevin"}

	go humanName(human1)
	humanName(human2)
}

func humanName(names [2]string) {
	for _, name := range names {
		fmt.Println(name)
		time.Sleep(time.Second)
	}
}

// park
// jay
// son
// kevin

 

Channel

  • channel은 goroutine들 끼리 소통할 수 있게 해주는 매개체
  • channel을 통해 값 보내면 받는 쪽에서 값이 올때까지 blocking이 걸린다.
  • time.Sleep 같은 것이 없어도 channel을 통해 값을 받을 수 있음

 

아래 예제코드를 보자.

  • c라는 channel을 만들어준 후 c ← 부분을 만나면 c가 값을 받을 때까지
  • blocking되는데 일반적인, goroutine과 달리 바로 종료 x
  • 값을 넣을 때 c ← true와 같이 넣어줌
package main

import (
	"fmt"
	"reflect"
)

func isNumbers(numbers int, c chan bool) {
	if reflect.TypeOf(numbers).String() == "int" {
		c <- true
	} else {
		c <- false
	}
}

func main() {
	c := make(chan bool)
	numbers := [4]int{1, 1, 3, 2}
	for _, number := range numbers {
		go isNumbers(number, c)
		fmt.Println(<-c)
	}
}

// true
// true
// true
// true

 

 

 

send 전용 channel도 만들 수 있는데 하나의 변수를 추가하여 성공적으로 receive했나 확인이 가능하다.

package main

import "fmt"

func sendData(sendch chan<- int) {
	sendch <- 10
	fmt.Println(<- sendch)
}

// .\goroutine2.go:7:14: invalid operation: <-sendch (receive from send-only type chan<- int)

func main() {
	chnl := make(chan int)
	go sendData(chnl)
	fmt.Println(<-chnl)
}

//10
  • 더 이상 받을 값이 없는 경우 close를 receiver에게 알릴 수 있다.
  • close를 통해 value의 상태 값들도 받을 수 있음.

 

 

 

close and range

더 이상 받을 값이 없으면 close를 receiver에게 알릴 수 있음,

close를 통해서 value의 상태 값들도 받을 수 있음.

v, ok := <- ch

 

아래 코드를 보면 ok가 true면 성공적으로 값을 받은 것이고, false면 실패한 것.

range를 사용해 받은 받은 값들을 반복문을 돌려 값을 이용할 수 있다.

package main

import "fmt"

func producer(chnl chan int) {
	for i := 0; i <10; i++ {
		chnl <- i
	}
	close(chnl)
}

func main() {
	ch := make(chan int)
	go producer(ch)
	for v := range ch {
		fmt.Println("Received", v)
	}
}

// result
Received 0
Received 1
Received 2
Received 3
Received 4
Received 5
Received 6
Received 7
Received 8
Received 9

 

 

 

참고: https://velog.io/@kykevin/Go%EC%9D%98-Interface,  

 

2020 TIL no. 8 - Go의 Interface

스터디 자료 출처 https://medium.com/rungo/interfaces-in-go-ab1601159b3a Golang의 Interface란 무엇인가 Golang에서 Struct는 다양한 타입의 필드들로 이루어진 구조체들을 의미하며, Method를 선언하는

velog.io

https://hwan-shell.tistory.com/336

 

Golang 구조체 설명

Golang에는 class 개념 대신 struct개념이 있습니다. 쉽게말해 C나 C++에 있는 struct입니다. 하지만 Golang에는 다른 OOP와는 다르게, class의 역할중 생성자, 맴버변수 선언을 하지 못합니다. 또한 private, pub

hwan-shell.tistory.com

https://edu.goorm.io/learn/lecture/2010/%ED%95%9C-%EB%88%88%EC%97%90-%EB%81%9D%EB%82%B4%EB%8A%94-%EA%B3%A0%EB%9E%AD-%EA%B8%B0%EC%B4%88/lesson/322396/%EC%A2%85%EB%A3%8C%ED%95%98%EB%8A%94-panic-%EB%B3%B5%EA%B5%AC%ED%95%98%EB%8A%94-recover

 

구름EDU - 모두를 위한 맞춤형 IT교육

구름EDU는 모두를 위한 맞춤형 IT교육 플랫폼입니다. 개인/학교/기업 및 기관 별 최적화된 IT교육 솔루션을 경험해보세요. 기초부터 실무 프로그래밍 교육, 전국 초중고/대학교 온라인 강의, 기업/

edu.goorm.io

https://hoony-gunputer.tistory.com/entry/goroutine-and-channel

 

goroutine and channel

goroutine이란 go routine은 함수 및 메소드를 다른 함수 및 메소드와 동시에 사용할 수 있게 해준다. go routine은 상당히 가벼운 thread이다. thread와 비교하면 만들고 사용하는데 적은 비용이든다. 그러

hoony-gunputer.tistory.com

 

'MadApp' 카테고리의 다른 글

(GolangPractice Day2) OJT 2021-12-10 KJH  (0) 2022.03.13
OJT 2022-03-04  (0) 2022.03.13
OJT 2022-02-21  (0) 2022.03.13
OJT 2021-12-29  (0) 2022.03.13
(GolangPractice Day1) OJT 2021-12-09 KJH  (0) 2022.03.13