본문 바로가기
Others

한 장으로 보는 GO 기초

by ma_ro 2023. 8. 22.

GO

  • 처음 설치하면 /(local)user/go 폴더를 생성한다.
    • go 프로젝트는 무조건 이 디렉터리 안에만 넣을 수 있다.
    • 다운받는 패키지들도 npm 같은 패키지 관리자를 쓰는 것이 아니다. 따라서 다운받은 경로에 따라 go/src 폴더 안에 정리해서 패키지를 관리한다.

Package

  • Main Package
    • 컴파일이 필요한 경우, main package가 필요하다. 컴파일이 필요없는 경우(라이브러리 생성하는 등)에는 Main Package가 필요하지 않을 수 있다.
    • 컴파일러는 main 패키지 먼저 찾아서 컴파일한다.
  • Import
    • 패키지 내에서 대문자로 시작하는 메서드들은 public, 즉 export된 메서드로 볼 수 있다.
    • 이런 메서드들은 다른 패키지에서 불러와서 사용할 수 있다.

Variable and Constant

func main() {
    var name String = "someone"
    name := "someone"
}
  • := 는 go가 type을 알아서 찾아서 지정해준다. 이는 func 안에서만 가능하다.

Function

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

func main() {
    totalLength, upperName := lenAndUpper("user")
    fmt.Println(totalLength, upperName)
} 
  • 함수에서 파라미터와 반환 타입을 지정해줘야 한다.
  • 여러 개의 값을 반환할 수 있다.
  • 변수를 선언만 하고 사용하지 않으면 그것으로도 에러가 발생한다.
func main() {
    totalLength, _ := lenAndUpper("user")
    fmt.Println(totalLength)
}
  • _를 사용해서 ignored value를 지정할 수 있다. 이렇게 하면 여러값을 반환하는 함수를 호출하여도 원하는 값만 받을 수 있다.
func printNames(names ...string) {
    fmt.Println(names)
}

func main() {
    printNames("peter", "jane", "james", "teddy")
}
  • 함수에 가변인자를 넘겨서 여러개의 파라미터를 전달할 수 있다.
func divide(x, y float64) (result float64, err error) {
    if y == 0 {
        err = errors.New("division by zero")
        return
    }
    result = x / y
    return
}
  • naked return : 함수에서 값을 반환할 때 변수명 없이 return 키워드만 사용하는 방식
  • 위 예제와 같이 result, err 값을 생략할 수 있다.
func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    // 파일 내용을 처리하는 코드
    return nil
}
  • defer를 사용하면 함수 내에서 일어난 일들을 추적하거나, 리소스를 정리하거나, 에러 핸들링 등의 작업을 수행할 수 있다. defer로 지정한 코드 블록은 함수가 반환하기 직전에 실행되므로, 함수가 종료되기 전에 반드시 실행되는 것이 보장된다.
  • 위 함수에서는 os.Open() 함수를 호출하여 파일을 열고, 파일 내용을 처리하는 코드를 실행한다. 이때, defer를 사용하여 파일을 닫는 코드를 지정하면, 함수가 반환되기 직전에 파일이 항상 닫히도록 보장할 수 있다. 이렇게 함으로써 파일 핸들링과 관련된 버그를 방지할 수 있다.
func printNumbers() {
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
}
  • defer는 여러 개의 코드 블록을 지정할 수 있으며, 지정한 순서대로 역순으로 실행된다. 예를 들어, 다음과 같은 코드를 실행하면 "3", "2", "1"이 순서대로 출력된다.

Loop

for {
    // 무한 루프로 실행할 코드 블록
}
  • go에서는 for문으로 모든 loof 처리를 한다.
for 인덱스, 값 := range 컬렉션 {
    // 반복해서 실행할 코드 블록
}
  • range를 사용하면 컬렉션의 길이만큼 반복문이 실행되며, 각 반복마다 인덱스와 값이 지정된 변수에 할당된다. 인덱스 값이 필요없을 경우에는 ignored value를 사용하여 제외한다.
m := map[string]int{"a": 1, "b": 2, "c": 3}

for key, val := range m {
    fmt.Printf("키: %s, 값: %d\n", key, val)
}
  • 혹은 map의 요소들을 순회하는데에 사용할 수 있다.

If

if x := 9; x%2 == 0 {
        fmt.Println("짝수입니다.")
    } else {
        fmt.Println("홀수입니다.")
    }
  • 다른 언어와 달리 if문 조건식에 ()를 사용하지 않는다.
  • 변수 표현식을 조건문 안에 넣어서 해당 조건문 안에서만 사용하게 할 수 있다.

Switch

switch {
case x > 0:
    fmt.Println("양수입니다.")
case x < 0:
    fmt.Println("음수입니다.")
default:
    fmt.Println("0입니다.")
}
  • ()를 생략하며 그 외에 다른 언어의 switch 문과 동일하게 사용한다.
  • 조건식을 생략하고 case 문에서 판별하도록 할 수 있다.

Pointer

func main() {
    a := 2
    b := a
    fmt.Println(a, b)    // 2 2
    fmt.Println(&a, &b)  // 0x140000140c0 0x140000140c8
}
  • 값만 복사되고 주소는 동일하지 않다.
func main() {
    a := 2
    b := &a
    fmt.Println(a, b)    // 2 0x140000140c0
    fmt.Println(&a, *b)  // 0x140000140c0 2

    *b = 20
    fmt.Println(a)       // 20
}
  • &를 붙이면 주소값을 가져온다.
  • *를 붙이면 해당 포인터와 연결된 값을 가져온다.
  • 대용량의 데이터를 공유하거나 하는 경우, 객체를 계속 복사하지 않고 주소값만 공유하여 사용할 수 있다. 이를 통해 좀더 빠른 동작을 보장할 수 있다.

Array, Slice

func main() {
    arr := [5]int{1, 2, 3}
    fmt.Println(arr)    // [1 2 3 0 0]

    str := [5]string{"1", "2", "3"}
    fmt.Println(str) // [1 2 3  ]
}
  • Array는 고정된 크기를 가진다.
func main() {
    arr := []int{1, 2, 3}
    fmt.Println(arr)    // [1 2 3]
}
  • Slice는 동적으로 크기가 조정될 수 있다.
slice := make([]int, 5) // 길이가 5인 정수형 슬라이스를 생성합니다.
slice = append(slice, 4) // 슬라이스에 요소를 추가합니다.
  • make 함수를 사용하여 생성할 수 있다.
  • append 함수는 slice 마지막에 요소를 추가한다.

Map

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    fmt.Println(m)    // map[a:1 b:2 c:3]
    n := make(map[string]int)
    fmt.Println(n)    // map[]
}
  • 다른 언어와 달리 key나 value에 Object로 타입 선언하여 여러가지 타입을 다 받을 수 없다.
  • make 함수로 초기화할 수 있다.
func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    delete(m, "a")
    fmt.Println(m) // map[b:2 c:3]
}
  • delete 함수로 초기화 한다.

Struct

type Person struct {
    name string
    age int
}
  • struct는 필드로 구성된 자료구조이며, 각 필드는 이름과 타입을 가지고 있다. struct를 사용하면 여러 필드를 하나의 자료구조로 묶을 수 있다.
p := Person{name: "Alice", age: 30}

var p Person // Person 구조체를 선언합니다.

p.name = "Alice" // name 필드에 값을 할당합니다.
p.age = 30 // age 필드에 값을 할당합니다.
  • 초기화에는 위와 같은 방법을 사용할 수 있다.

Method

func (t 타입) 메서드이름() 반환타입 {
    // 메서드 구현
}
  • method는 특정 타입에 속한 함수이다. method는 해당 타입에 대한 동작을 정의하며, 해당 타입의 값을 조작하는 데 사용한다.
type Person struct {
    name string
    age int
}

func (p *Person) SetName(name string) {
    p.name = name
}
  • method를 정의할 때 포인터를 사용하여 해당 값의 주소를 전달할 수도 있다. 이렇게 하여 함수가 해당 값의 복사본을 만들지 않고, 해당 값에 직접 접근할 수 있다. 이를 통해 메모리 사용량을 줄이고 성능을 향상시킬 수 있다.

String Method

p := Person{name: "Alice", age: 30}
fmt.Println(p)
  • 위 코드에서는 Person 구조체 값을 출력하면서 내부에서 String method를 호출하여 해당 구조체의 값을 문자열로 변환한다.
func (p Person) String() string {
    return "Whatever you want"
}
fmt.Println(p)
  • 또한 String method를 overwrite하여 사용할 수 있다.

Goroutine

func main() {
    // WaitGroup 생성. 2개의 Go루틴을 기다림.
    var wait sync.WaitGroup
    wait.Add(2)

    // 익명함수를 사용한 goroutine
    go func() {
        defer wait.Done() //끝나면 .Done() 호출
        fmt.Println("Hello")
    }()

    // 익명함수에 파라미터 전달
    go func(msg string) {
        defer wait.Done() //끝나면 .Done() 호출
        fmt.Println(msg)
    }("Hi")

    wait.Wait() //Go루틴 모두 끝날 때까지 대기
}
  • goroutine은 Go 프로그래밍 언어에서 제공하는 경량 쓰레드를 말한다. 비동기적으로 함수 루틴을 실행하므로, 여러 코드를 동시에 실행하는 데 사용한다.
  • goroutine은 OS 쓰레드보다 훨씬 가볍게 비동기 Concurrent 처리를 구현하기 위하여 만든 것으로, 기본적으로 Go 런타임이 자체 관리한다. Go 런타임 상에서 관리되는 작업단위인 여러 goroutine들은 종종 하나의 OS 쓰레드 1개로도 실행되곤 한다. 즉, Go루틴들은 OS 쓰레드와 1 대 1로 대응되지 않고, Multiplexing으로 훨씬 적은 OS 쓰레드를 사용한다. 메모리 측면에서도 OS 쓰레드가 1 메가바이트의 스택을 갖는 반면, goroutine은 이보다 훨씬 작은 몇 킬로바이트의 스택을 갖는다(필요시 동적으로 증가). Go 런타임은 Go루틴을 관리하면서 Go 채널을 통해 Go루틴 간의 통신을 쉽게 할 수 있도록 하였다.
  • 위에서 sync.WaitGroup을 사용하고 있는데 이를 통해 Go 루틴들이 끝날 때까지 기다리도록 한다. Add()로 몇 개의 Go루틴을 기다릴 것인지 지정하고, 각 Go 루틴에서 Done()를 호출한다.

Channel

func main() {
    c := make(chan bool)
    people := [2]string{"nico", "flynn"}
    for _, person := range people {
        go isPerson(person, c)
    }
    fmt.Println(<-c)
    fmt.Println(<-c)
}

func isPerson(person string, c chan bool) {
    time.Sleep(time.Second * 5)
    fmt.Println(person)
    c <- true
}
  • 고루틴 간 데이터를 주고 받는 통로로써 상대편이 준비될 때까지 채널에서 대기하여 별도의 lock을 걸지 않고 데이터를 동기화 하는데 사용한다.

http://golang.site/go/article/21-Go-%EB%A3%A8%ED%8B%B4-goroutine

댓글