GO 문법 정리

Posted by Albert 67Day 8Hour 49Min 7Sec ago [2025-12-01]

Go(또는 Golang)는 Google에서 개발한 현대적인 프로그래밍 언어로, 간결함과 효율성, 동시성 처리에 강점을 가지고 있습니다.

1. 기초 입문

1.1 Hello World

Go 프로그램의 기본 구조입니다.

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
  • package main: 실행 가능한 프로그램의 진입점
  • import: 필요한 패키지 가져오기
  • func main(): 프로그램 시작 함수

1.2 변수와 상수

package main

import "fmt"

func main() {
    // 변수 선언 방법들
    var name string = "홍길동"
    var age int = 25
    
    // 타입 추론
    var city = "서울"
    
    // 짧은 선언 (함수 내부에서만 사용)
    score := 95
    
    // 여러 변수 동시 선언
    var x, y int = 10, 20
    
    // 상수
    const PI = 3.14159
    
    fmt.Println(name, age, city, score, x, y, PI)
}

1.3 기본 데이터 타입

package main

import "fmt"

func main() {
    // 정수형
    var i int = 42
    var i8 int8 = 127
    var ui uint = 100
    
    // 실수형
    var f32 float32 = 3.14
    var f64 float64 = 2.718281828
    
    // 불린
    var isActive bool = true
    
    // 문자열
    var message string = "안녕하세요"
    
    // 복소수
    var c complex64 = 1 + 2i
    
    fmt.Printf("int: %d, float: %.2f, bool: %t, string: %s\n", 
               i, f64, isActive, message)
}

2. 제어 구조

2.1 조건문

package main

import "fmt"

func main() {
    age := 20
    
    // if-else
    if age >= 18 {
        fmt.Println("성인입니다")
    } else {
        fmt.Println("미성년자입니다")
    }
    
    // if with initialization
    if score := 85; score >= 90 {
        fmt.Println("A학점")
    } else if score >= 80 {
        fmt.Println("B학점")
    } else {
        fmt.Println("노력이 필요합니다")
    }
    
    // switch
    day := "월요일"
    switch day {
    case "월요일", "화요일":
        fmt.Println("주초입니다")
    case "수요일":
        fmt.Println("주중입니다")
    default:
        fmt.Println("주말이 가까워요")
    }
    
    // switch without expression
    temperature := 25
    switch {
    case temperature < 10:
        fmt.Println("추워요")
    case temperature < 25:
        fmt.Println("적당해요")
    default:
        fmt.Println("더워요")
    }
}

2.2 반복문

package main

import "fmt"

func main() {
    // 기본 for 루프
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
    
    // while 스타일
    count := 0
    for count < 3 {
        fmt.Println("Count:", count)
        count++
    }
    
    // 무한 루프
    num := 0
    for {
        if num >= 3 {
            break
        }
        fmt.Println("Num:", num)
        num++
    }
    
    // continue 사용
    for i := 0; i < 5; i++ {
        if i == 2 {
            continue
        }
        fmt.Println("Value:", i)
    }
}

3. 자료구조

3.1 배열과 슬라이스

package main

import "fmt"

func main() {
    // 배열 (크기 고정)
    var arr [3]int = [3]int{1, 2, 3}
    fmt.Println("배열:", arr)
    
    // 슬라이스 (동적 크기)
    slice := []int{1, 2, 3, 4, 5}
    fmt.Println("슬라이스:", slice)
    
    // 슬라이스 연산
    slice = append(slice, 6, 7)
    fmt.Println("추가 후:", slice)
    
    // 슬라이싱
    subSlice := slice[1:4]
    fmt.Println("부분 슬라이스:", subSlice)
    
    // make로 슬라이스 생성
    numbers := make([]int, 5, 10) // 길이 5, 용량 10
    fmt.Printf("길이: %d, 용량: %d\n", len(numbers), cap(numbers))
    
    // range로 반복
    for index, value := range slice {
        fmt.Printf("인덱스: %d, 값: %d\n", index, value)
    }
}

3.2 맵 (Map)

package main

import "fmt"

func main() {
    // 맵 생성
    ages := make(map[string]int)
    
    // 값 추가
    ages["홍길동"] = 25
    ages["김철수"] = 30
    ages["이영희"] = 28
    
    // 값 조회
    fmt.Println("홍길동의 나이:", ages["홍길동"])
    
    // 존재 여부 확인
    age, exists := ages["박민수"]
    if exists {
        fmt.Println("박민수의 나이:", age)
    } else {
        fmt.Println("박민수 정보 없음")
    }
    
    // 리터럴로 초기화
    scores := map[string]int{
        "수학": 90,
        "영어": 85,
        "과학": 95,
    }
    
    // 순회
    for subject, score := range scores {
        fmt.Printf("%s: %d점\n", subject, score)
    }
    
    // 삭제
    delete(scores, "영어")
    fmt.Println("삭제 후:", scores)
}

4. 함수

4.1 기본 함수

package main

import "fmt"

// 기본 함수
func add(a int, b int) int {
    return a + b
}

// 같은 타입 매개변수 축약
func multiply(x, y int) int {
    return x * y
}

// 다중 반환값
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("0으로 나눌 수 없습니다")
    }
    return a / b, nil
}

// 명명된 반환값
func rectangle(width, height float64) (area, perimeter float64) {
    area = width * height
    perimeter = 2 * (width + height)
    return // naked return
}

// 가변 인자
func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

func main() {
    fmt.Println("덧셈:", add(5, 3))
    fmt.Println("곱셈:", multiply(4, 7))
    
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("에러:", err)
    } else {
        fmt.Println("나눗셈:", result)
    }
    
    area, perimeter := rectangle(5, 3)
    fmt.Printf("넓이: %.2f, 둘레: %.2f\n", area, perimeter)
    
    fmt.Println("합계:", sum(1, 2, 3, 4, 5))
}

4.2 익명 함수와 클로저

package main

import "fmt"

func main() {
    // 익명 함수
    greeting := func(name string) string {
        return "안녕하세요, " + name + "님!"
    }
    fmt.Println(greeting("홍길동"))
    
    // 즉시 실행 함수
    func(msg string) {
        fmt.Println(msg)
    }("즉시 실행됩니다!")
    
    // 클로저
    counter := makeCounter()
    fmt.Println(counter()) // 1
    fmt.Println(counter()) // 2
    fmt.Println(counter()) // 3
}

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

5. 구조체와 메서드

5.1 구조체

package main

import "fmt"

// 구조체 정의
type Person struct {
    Name string
    Age  int
    City string
}

// 중첩 구조체
type Employee struct {
    Person
    ID       int
    Position string
    Salary   float64
}

func main() {
    // 구조체 생성 방법들
    p1 := Person{Name: "홍길동", Age: 25, City: "서울"}
    p2 := Person{"김철수", 30, "부산"}
    
    var p3 Person
    p3.Name = "이영희"
    p3.Age = 28
    
    fmt.Println(p1)
    fmt.Printf("%s는 %d살이고 %s에 삽니다\n", p1.Name, p1.Age, p1.City)
    
    // 포인터로 구조체 생성
    p4 := &Person{Name: "박민수", Age: 35, City: "대구"}
    fmt.Println(p4.Name) // 자동 역참조
    
    // 중첩 구조체
    emp := Employee{
        Person:   Person{Name: "최지훈", Age: 32, City: "인천"},
        ID:       1001,
        Position: "개발자",
        Salary:   50000.00,
    }
    fmt.Printf("직원: %s, 직급: %s\n", emp.Name, emp.Position)
}

5.2 메서드

package main

import (
    "fmt"
    "math"
)

type Circle struct {
    Radius float64
}

type Rectangle struct {
    Width  float64
    Height float64
}

// 값 리시버
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

// 포인터 리시버 (값 수정 가능)
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

func main() {
    c := Circle{Radius: 5}
    fmt.Printf("원의 넓이: %.2f\n", c.Area())
    
    c.Scale(2)
    fmt.Printf("확대 후 반지름: %.2f\n", c.Radius)
    
    r := Rectangle{Width: 4, Height: 6}
    fmt.Printf("사각형 넓이: %.2f, 둘레: %.2f\n", r.Area(), r.Perimeter())
}

6. 인터페이스

package main

import (
    "fmt"
    "math"
)

// 인터페이스 정의
type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct {
    Radius float64
}

type Rectangle struct {
    Width, Height float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// 인터페이스를 매개변수로 받는 함수
func printShapeInfo(s Shape) {
    fmt.Printf("넓이: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}

// 빈 인터페이스 (모든 타입 수용)
func printAnything(i interface{}) {
    fmt.Println(i)
}

// 타입 단언
func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("정수: %d\n", v)
    case string:
        fmt.Printf("문자열: %s\n", v)
    case Circle:
        fmt.Printf("원, 반지름: %.2f\n", v.Radius)
    default:
        fmt.Printf("알 수 없는 타입: %T\n", v)
    }
}

func main() {
    c := Circle{Radius: 5}
    r := Rectangle{Width: 4, Height: 6}
    
    printShapeInfo(c)
    printShapeInfo(r)
    
    printAnything(42)
    printAnything("Hello")
    printAnything(c)
    
    describe(100)
    describe("Go 언어")
    describe(c)
}

7. 에러 처리

package main

import (
    "errors"
    "fmt"
)

// 사용자 정의 에러
type DivideError struct {
    Dividend float64
    Divisor  float64
}

func (e *DivideError) Error() string {
    return fmt.Sprintf("%.2f를 %.2f로 나눌 수 없습니다", e.Dividend, e.Divisor)
}

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, &DivideError{Dividend: a, Divisor: b}
    }
    return a / b, nil
}

func safeDivide(a, b float64) (result float64) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("패닉 복구:", r)
            result = 0
        }
    }()
    
    if b == 0 {
        panic("0으로 나누기 시도")
    }
    return a / b
}

func processFile(filename string) error {
    if filename == "" {
        return errors.New("파일명이 비어있습니다")
    }
    
    // 파일 처리 로직...
    return nil
}

func main() {
    // 에러 처리
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("에러:", err)
    } else {
        fmt.Println("결과:", result)
    }
    
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("에러:", err)
    }
    
    // panic과 recover
    fmt.Println("안전한 나눗셈:", safeDivide(10, 0))
    
    // 에러 래핑 (Go 1.13+)
    err = processFile("")
    if err != nil {
        wrapped := fmt.Errorf("파일 처리 실패: %w", err)
        fmt.Println(wrapped)
    }
}

8. 동시성 (Goroutines & Channels)

8.1 고루틴

package main

import (
    "fmt"
    "time"
)

func sayHello(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("Hello, %s! (%d)\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func count(id int) {
    for i := 1; i <= 5; i++ {
        fmt.Printf("고루틴 %d: %d\n", id, i)
        time.Sleep(50 * time.Millisecond)
    }
}

func main() {
    // 고루틴 시작
    go sayHello("고루틴")
    
    // 여러 고루틴 실행
    for i := 1; i <= 3; i++ {
        go count(i)
    }
    
    // 메인 함수도 동시 실행
    sayHello("메인")
    
    // 고루틴이 끝날 때까지 대기
    time.Sleep(1 * time.Second)
    fmt.Println("프로그램 종료")
}

8.2 채널

package main

import (
    "fmt"
    "time"
)

func sendData(ch chan int) {
    for i := 1; i <= 5; i++ {
        ch <- i // 채널에 데이터 전송
        fmt.Println("전송:", i)
        time.Sleep(100 * time.Millisecond)
    }
    close(ch) // 채널 닫기
}

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("작업자 %d: 작업 %d 시작\n", id, job)
        time.Sleep(time.Second)
        results <- job * 2
    }
}

func main() {
    // 기본 채널
    ch := make(chan int)
    go sendData(ch)
    
    for num := range ch {
        fmt.Println("수신:", num)
    }
    
    // 버퍼링된 채널
    buffered := make(chan string, 2)
    buffered <- "첫번째"
    buffered <- "두번째"
    fmt.Println(<-buffered)
    fmt.Println(<-buffered)
    
    // 워커 풀 패턴
    jobs := make(chan int, 5)
    results := make(chan int, 5)
    
    // 3개의 작업자 시작
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // 작업 전송
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 결과 수신
    for r := 1; r <= 5; r++ {
        result := <-results
        fmt.Println("결과:", result)
    }
}

8.3 Select문

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "채널 1"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "채널 2"
    }()
    
    // select로 여러 채널 대기
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("수신:", msg1)
        case msg2 := <-ch2:
            fmt.Println("수신:", msg2)
        case <-time.After(3 * time.Second):
            fmt.Println("타임아웃")
        }
    }
    
    // 논블로킹 작업
    select {
    case msg := <-ch1:
        fmt.Println("수신:", msg)
    default:
        fmt.Println("즉시 사용 가능한 데이터 없음")
    }
}

9. 고급 기능

9.1 컨텍스트 (Context)

package main

import (
    "context"
    "fmt"
    "time"
)

func operation(ctx context.Context, duration time.Duration) {
    select {
    case <-time.After(duration):
        fmt.Println("작업 완료")
    case <-ctx.Done():
        fmt.Println("작업 취소:", ctx.Err())
    }
}

func main() {
    // 타임아웃이 있는 컨텍스트
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    go operation(ctx, 3*time.Second)
    
    time.Sleep(3 * time.Second)
    
    // 값을 가진 컨텍스트
    ctx2 := context.WithValue(context.Background(), "userID", 12345)
    
    if userID, ok := ctx2.Value("userID").(int); ok {
        fmt.Println("사용자 ID:", userID)
    }
}

9.2 제네릭 (Go 1.18+)

package main

import "fmt"

// 제네릭 함수
func Min[T int | float64](a, b T) T {
    if a < b {
        return a
    }
    return b
}

// 제네릭 슬라이스 함수
func Map[T any, U any](slice []T, f func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}

// 제네릭 타입
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func main() {
    fmt.Println("최소값(int):", Min(5, 3))
    fmt.Println("최소값(float):", Min(3.14, 2.71))
    
    numbers := []int{1, 2, 3, 4, 5}
    doubled := Map(numbers, func(n int) int { return n * 2 })
    fmt.Println("두 배:", doubled)
    
    // 제네릭 스택
    intStack := &Stack[int]{}
    intStack.Push(10)
    intStack.Push(20)
    if val, ok := intStack.Pop(); ok {
        fmt.Println("팝:", val)
    }
    
    stringStack := &Stack[string]{}
    stringStack.Push("Hello")
    stringStack.Push("World")
    if val, ok := stringStack.Pop(); ok {
        fmt.Println("팝:", val)
    }
}

9.3 파일 입출력

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    // 파일 쓰기
    file, err := os.Create("test.txt")
    if err != nil {
        fmt.Println("파일 생성 에러:", err)
        return
    }
    defer file.Close()
    
    file.WriteString("첫 번째 줄\n")
    file.WriteString("두 번째 줄\n")
    
    // 파일 읽기 (전체)
    content, err := os.ReadFile("test.txt")
    if err != nil {
        fmt.Println("파일 읽기 에러:", err)
        return
    }
    fmt.Println("파일 내용:")
    fmt.Println(string(content))
    
    // 파일 읽기 (라인별)
    file, err = os.Open("test.txt")
    if err != nil {
        fmt.Println("파일 열기 에러:", err)
        return
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    lineNum := 1
    for scanner.Scan() {
        fmt.Printf("줄 %d: %s\n", lineNum, scanner.Text())
        lineNum++
    }
    
    // 버퍼를 이용한 복사
    sourceFile, _ := os.Open("test.txt")
    defer sourceFile.Close()
    
    destFile, _ := os.Create("copy.txt")
    defer destFile.Close()
    
    bytesWritten, _ := io.Copy(destFile, sourceFile)
    fmt.Printf("%d 바이트 복사 완료\n", bytesWritten)
}

10. 테스팅

// math_utils.go
package main

func Add(a, b int) int {
    return a + b
}

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("0으로 나눌 수 없습니다")
    }
    return a / b, nil
}


// math_utils_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    
    if result != expected {
        t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
    }
}

func TestMultiply(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"양수", 2, 3, 6},
        {"0 포함", 5, 0, 0},
        {"음수", -2, 3, -6},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Multiply(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Multiply(%d, %d) = %d; expected %d", 
                         tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

func TestDivide(t *testing.T) {
    // 정상 케이스
    result, err := Divide(10, 2)
    if err != nil {
        t.Errorf("예상치 못한 에러: %v", err)
    }
    if result != 5 {
        t.Errorf("Divide(10, 2) = %f; expected 5", result)
    }
    
    // 에러 케이스
    _, err = Divide(10, 0)
    if err == nil {
        t.Error("0으로 나누기 시 에러가 발생해야 합니다")
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

학습 팁

  1. 공식 문서 활용: golang.org에서 최신 문서를 확인하세요
  2. Go Playground: 온라인에서 코드를 바로 테스트할 수 있습니다
  3. 실습 프로젝트: CLI 도구, 웹 서버, REST API 등을 직접 만들어보세요
  4. 표준 라이브러리 학습: Go의 강력한 표준 라이브러리를 익히세요
  5. 동시성 패턴: 고루틴과 채널을 활용한 다양한 패턴을 연습하세요

Go는 배우기 쉽고 강력한 언어입니다. 위 예제들을 직접 실행해보고 수정해보면서 익숙해지시길 바랍니다!




LIST

Copyright © 2014 visionboy.me All Right Reserved.