10분 안에 Go 배우기
Go(또는 Golang)는 Google에서 설계한 정적 타입의 컴파일 프로그래밍 언어입니다. 단순함, 효율성, 그리고 뛰어난 동시성 지원으로 유명합니다. 이 튜토리얼은 Go 프로그래밍을 빠르게 배울 수 있도록 도와줍니다.
1. 첫 번째 Go 프로그램 작성하기
간단한 프로그램부터 시작해보겠습니다. hello.go
파일을 생성하고 다음 코드를 입력하세요:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
파일을 저장하고 터미널에서 다음 명령어를 실행하세요:
go run hello.go
출력 결과:
Hello, World!
이 간단한 프로그램은 Go의 기본 구조를 보여줍니다:
package main
은 패키지 이름을 선언합니다import "fmt"
는 I/O 작업을 위한 포맷 패키지를 가져옵니다func main()
은 프로그램의 진입점입니다fmt.Println()
은 텍스트를 콘솔에 출력합니다
2. 기본 문법
Go는 깔끔하고 간단한 문법을 가지고 있습니다. Python과 달리 Go는 중괄호 {}
를 사용하여 코드 블록을 정의하고 문장 끝에 세미콜론이 필요합니다(보통 자동으로 삽입됩니다).
// 이것은 한 줄 주석입니다
fmt.Println("Hello, World!")
/*
이것은 여러 줄에 걸친
여러 줄 주석입니다
*/
Go의 기본 문법 규칙:
- 코드 블록: 중괄호
{}
로 정의됩니다 - 주석: 한 줄 주석은
//
로 시작하고, 여러 줄 주석은/* */
로 시작합니다 - 문장: 세미콜론으로 끝납니다(자동 삽입)
- 패키지 선언: 모든 파일은 패키지 선언으로 시작합니다
3. 변수와 데이터 타입
Go는 정적 타입 언어로, 변수 타입을 선언해야 합니다. 그러나 Go는 :=
연산자를 사용한 타입 추론을 지원합니다.
변수 선언 방법:
// 명시적 타입 선언
var name string = "John"
var age int = 25
// 타입 추론
name := "John"
age := 25
// 여러 변수 선언
var x, y int = 10, 20
x, y := 10, 20
Go의 주요 기본 데이터 타입:
- 정수 타입:
int
,int8
,int16
,int32
,int64
,uint
,uint8
,uint16
,uint32
,uint64
,uintptr
- 실수 타입:
float32
,float64
- 문자열:
string
- 불리언:
bool
- 복소수 타입:
complex64
,complex128
3.1 숫자 타입
Go는 다양한 사용 사례에 맞는 여러 숫자 타입을 제공합니다:
// 정수 타입
var age int = 25
var smallNumber int8 = 127
var largeNumber int64 = 9223372036854775807
// 실수 타입
var temperature float32 = 36.5
var pi float64 = 3.14159265359
// 복소수
var complexNum complex64 = 3 + 4i
3.2 문자열 타입
Go의 문자열은 바이트 시퀀스이며 불변입니다:
// 문자열 선언
var greeting string = "Hello, Go!"
name := "Alice"
// 문자열 연산
fmt.Println(len(greeting)) // 문자열 길이
fmt.Println(greeting[0]) // 첫 번째 문자 접근(바이트)
fmt.Println(greeting[0:5]) // 문자열 슬라이싱
fmt.Println(strings.ToUpper(name)) // 대문자로 변환
3.3 불리언 타입
불리언 타입은 true
와 false
두 가지 값을 가집니다:
var isActive bool = true
var isComplete bool = false
// 불리언 연산
result1 := true && false // false
result2 := true || false // true
result3 := !true // false
4. 상수
상수는 const
키워드를 사용하여 선언되며 변경할 수 없습니다:
const Pi = 3.14159
const MaxUsers = 1000
// 여러 상수
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// 타입이 지정된 상수
const Version string = "1.0.0"
5. 데이터 구조
Go는 데이터 저장과 조작을 위한 여러 내장 데이터 구조를 제공합니다.
5.1 배열
배열은 같은 타입의 요소로 이루어진 고정 크기 시퀀스입니다:
// 배열 선언
var numbers [5]int = [5]int{1, 2, 3, 4, 5}
names := [3]string{"Alice", "Bob", "Charlie"}
// 요소 접근
fmt.Println(numbers[0]) // 1
numbers[0] = 10 // 요소 수정
// 배열 길이
fmt.Println(len(numbers)) // 5
5.2 슬라이스
슬라이스는 크기가 동적으로 증가하고 감소할 수 있는 배열입니다:
// 슬라이스 선언
numbers := []int{1, 2, 3, 4, 5}
var emptySlice []int
// 배열에서 슬라이스 생성
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
// 슬라이스 연산
numbers = append(numbers, 6) // 요소 추가
numbers = append(numbers, 7, 8, 9) // 여러 요소 추가
// 슬라이스 용량과 길이
fmt.Println(len(numbers)) // 길이: 9
fmt.Println(cap(numbers)) // 용량: 10 (변동 가능)
5.3 맵
맵은 키-값 쌍의 정렬되지 않은 컬렉션입니다:
// 맵 선언
student := map[string]interface{}{
"name": "John",
"age": 20,
"major": "Computer Science",
}
// 대체 선언
var scores map[string]int = make(map[string]int)
scores["math"] = 95
scores["science"] = 88
// 접근 및 수정
fmt.Println(student["name"])
student["age"] = 21
student["gpa"] = 3.8
// 안전한 접근
if phone, exists := student["phone"]; exists {
fmt.Println(phone)
} else {
fmt.Println("전화번호가 제공되지 않았습니다")
}
// 맵 순회
for key, value := range student {
fmt.Printf("%s: %v\n", key, value)
}
5.4 구조체
구조체는 서로 다른 타입을 가질 수 있는 필드들의 모음입니다:
// 구조체 정의
type Person struct {
Name string
Age int
City string
}
// 구조체 인스턴스 생성
person1 := Person{"Alice", 25, "New York"}
person2 := Person{
Name: "Bob",
Age: 30,
City: "London",
}
// 필드 접근
fmt.Println(person1.Name)
person1.Age = 26
6. 연산과 연산자
Go는 다양한 계산과 비교를 위한 풍부한 연산자 집합을 제공합니다.
- 산술 연산자:
+
,-
,*
,/
,%
(나머지) - 비교 연산자:
==
,!=
,>
,<
,>=
,<=
- 논리 연산자:
&&
,||
,!
- 비트 연산자:
&
,|
,^
,<<
,>>
- 할당 연산자:
=
,+=
,-=
,*=
,/=
6.1 산술 연산자
a, b := 10, 3
fmt.Printf("덧셈: %d\n", a + b) // 13
fmt.Printf("뺄셈: %d\n", a - b) // 7
fmt.Printf("곱셈: %d\n", a * b) // 30
fmt.Printf("나눗셈: %d\n", a / b) // 3
fmt.Printf("나머지: %d\n", a % b) // 1
6.2 비교 연산자
x, y := 5, 10
fmt.Printf("같음: %t\n", x == y) // false
fmt.Printf("다름: %t\n", x != y) // true
fmt.Printf("크다: %t\n", x > y) // false
fmt.Printf("작다: %t\n", x < y) // true
6.3 논리 연산자
a, b := true, false
fmt.Printf("AND 연산: %t\n", a && b) // false
fmt.Printf("OR 연산: %t\n", a || b) // true
fmt.Printf("NOT 연산: %t\n", !a) // false
7. 제어 흐름
Go는 프로그램 실행을 관리하기 위한 여러 제어 흐름 문을 제공합니다.
7.1 if 문
age := 20
if age >= 18 {
fmt.Println("성인")
} else if age >= 13 {
fmt.Println("청소년")
} else {
fmt.Println("어린이")
}
// 짧은 문장과 함께하는 if
if score := 85; score >= 90 {
fmt.Println("성적: A")
} else if score >= 80 {
fmt.Println("성적: B")
} else {
fmt.Println("성적: C")
}
7.2 for 반복문
Go에는 단 하나의 반복 구조인 for
만 있습니다:
// 기본 for 반복문
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// While 스타일 반복문
count := 0
for count < 5 {
fmt.Println(count)
count++
}
// 무한 반복문
for {
fmt.Println("이것은 영원히 실행됩니다")
break // 탈출에 break 사용
}
// Range 반복문 (슬라이스, 배열, 맵용)
fruits := []string{"apple", "banana", "cherry"}
for index, fruit := range fruits {
fmt.Printf("%d: %s\n", index, fruit)
}
7.3 switch 문
day := "Monday"
switch day {
case "Monday":
fmt.Println("주 시작")
case "Friday":
fmt.Println("주말이 가까워요")
case "Saturday", "Sunday":
fmt.Println("주말!")
default:
fmt.Println("평일")
}
// 표현식 없는 switch
score := 85
switch {
case score >= 90:
fmt.Println("성적: A")
case score >= 80:
fmt.Println("성적: B")
case score >= 70:
fmt.Println("성적: C")
default:
fmt.Println("성적: F")
}
8. 함수
Go의 함수는 일급 시민이며 여러 반환 값을 지원합니다.
8.1 기본 함수
func greet(name string) string {
return "Hello, " + name + "!"
}
// 함수 호출
message := greet("John")
fmt.Println(message)
8.2 여러 반환 값
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("0으로 나눌 수 없습니다")
}
return a / b, nil
}
// 여러 반환 값 사용
result, err := divide(10, 2)
if err != nil {
fmt.Println("오류:", err)
} else {
fmt.Println("결과:", result)
}
8.3 명명된 반환 값
func calculateRectangle(width, height float64) (area float64, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // naked return
}
area, perimeter := calculateRectangle(5, 3)
fmt.Printf("면적: %.2f, 둘레: %.2f\n", area, perimeter)
8.4 가변 인자 함수
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
fmt.Println(sum(1, 2, 3, 4)) // 10
fmt.Println(sum(5, 10, 15)) // 30
9. 포인터
Go는 C/C++보다 더 간단한 문법으로 포인터를 가지고 있습니다:
func modifyValue(x *int) {
*x = *x * 2
}
func main() {
value := 10
fmt.Println("이전:", value) // 10
modifyValue(&value)
fmt.Println("이후:", value) // 20
}
10. 메서드
메서드는 리시버 인자를 가진 함수입니다:
type Rectangle struct {
Width float64
Height float64
}
// 값 리시버를 가진 메서드
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 포인터 리시버를 가진 메서드
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
rect := Rectangle{Width: 5, Height: 3}
fmt.Println("면적:", rect.Area()) // 15
rect.Scale(2)
fmt.Println("확장된 면적:", rect.Area()) // 60
11. 인터페이스
인터페이스는 타입이 구현할 수 있는 메서드 시그니처를 정의합니다:
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func printShapeInfo(s Shape) {
fmt.Printf("면적: %.2f, 둘레: %.2f\n", s.Area(), s.Perimeter())
}
circle := Circle{Radius: 5}
printShapeInfo(circle)
12. 오류 처리
Go는 예외 대신 명시적 오류 처리를 사용합니다:
func readFile(filename string) (string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("파일 %s 읽기 실패: %w", filename, err)
}
return string(data), nil
}
content, err := readFile("example.txt")
if err != nil {
fmt.Println("오류:", err)
return
}
fmt.Println("내용:", content)
13. 고루틴을 이용한 동시성
고루틴은 Go 런타임이 관리하는 경량 스레드입니다:
func worker(id int) {
for i := 0; i < 3; i++ {
fmt.Printf("작업자 %d: %d\n", id, i)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
// 여러 고루틴 시작
for i := 1; i <= 3; i++ {
go worker(i)
}
// 고루틴 완료 대기
time.Sleep(time.Second)
fmt.Println("모든 작업자 완료")
}
14. 채널
채널은 고루틴 간 통신에 사용됩니다:
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i // 채널에 값 전송
time.Sleep(time.Millisecond * 100)
}
close(ch) // 완료 시 채널 닫기
}
func consumer(ch <-chan int) {
for value := range ch {
fmt.Println("수신:", value)
}
}
func main() {
ch := make(chan int, 3) // 버퍼 채널
go producer(ch)
consumer(ch)
fmt.Println("채널 통신 완료")
}
15. 파일 작업
Go는 파일 읽기와 쓰기를 위한 간단한 메서드를 제공합니다:
// 파일 읽기
data, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("파일 읽기 오류:", err)
return
}
fmt.Println("파일 내용:", string(data))
// 파일 쓰기
content := "Hello, Go!\n"
err = os.WriteFile("output.txt", []byte(content), 0644)
if err != nil {
fmt.Println("파일 쓰기 오류:", err)
return
}
fmt.Println("파일 쓰기 성공")
16. 패키지와 모듈
Go 모듈은 의존성과 패키지 버전을 관리합니다:
// 표준 라이브러리 패키지 가져오기
import (
"fmt"
"math"
"strings"
)
func main() {
fmt.Println(math.Sqrt(16)) // 4
fmt.Println(strings.ToUpper("go")) // GO
}
자신만의 패키지를 생성하려면 패키지 이름으로 디렉토리를 만들고 함수 이름을 대문자로 시작하여 내보내세요.
17. 테스트
Go는 내장 테스트 지원을 가지고 있습니다:
// math_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; want %d", result, expected)
}
}
func add(a, b int) int {
return a + b
}
테스트 실행: go test
18. 모범 사례
gofmt
를 사용하여 코드를 포맷팅하세요- Go 명명 규칙을 따르세요(변수는 camelCase, 내보내기는 PascalCase)
- 오류를 명시적으로 처리하세요
- 추상화를 위해 인터페이스를 사용하세요
- 상속보다 구성을 선호하세요
- 포괄적인 테스트를 작성하세요
- 가능하면 표준 라이브러리를 사용하세요
이 튜토리얼은 Go 프로그래밍의 필수 기능을 다루고 있습니다. 연습을 통해 Go의 강력한 기능을 사용하여 효율적이고 동시적인 애플리케이션을 구축할 수 있을 것입니다.