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)
}
}
학습 팁
- 공식 문서 활용: golang.org에서 최신 문서를 확인하세요
- Go Playground: 온라인에서 코드를 바로 테스트할 수 있습니다
- 실습 프로젝트: CLI 도구, 웹 서버, REST API 등을 직접 만들어보세요
- 표준 라이브러리 학습: Go의 강력한 표준 라이브러리를 익히세요
- 동시성 패턴: 고루틴과 채널을 활용한 다양한 패턴을 연습하세요
Go는 배우기 쉽고 강력한 언어입니다. 위 예제들을 직접 실행해보고 수정해보면서 익숙해지시길 바랍니다!