📚 GO 기본문법 - 변수와 함수
📋 목차
1. 변수 선언
💡 변수 선언의 4가지 방법
1. 명시적 선언 (var 키워드)
package main
import "fmt"
func main() {
// 타입과 함께 선언
var name string = "Go"
var age int = 13
var isActive bool = true
// 기본값으로 초기화 (zero value)
var count int // 0
var message string // ""
var flag bool // false
fmt.Printf("Name: %s, Age: %d, Active: %t\n", name, age, isActive)
fmt.Printf("Count: %d, Message: '%s', Flag: %t\n", count, message, flag)
}2. 타입 추론 (var 키워드 + 추론)
func variableInference() {
// Go가 타입을 자동으로 추론
var language = "Golang" // string으로 추론
var version = 1.21 // float64로 추론
var downloads = 1000000 // int로 추론
fmt.Printf("Language: %s (Type: %T)\n", language, language)
fmt.Printf("Version: %.1f (Type: %T)\n", version, version)
fmt.Printf("Downloads: %d (Type: %T)\n", downloads, downloads)
}3. 짧은 선언 (Short Declaration)
func shortDeclaration() {
// 함수 내부에서만 사용 가능!
nickname := "Gopher"
year := 2025
pi := 3.14159
// 한 줄에 여러 변수 선언
x, y := 10, 20
firstName, lastName := "John", "Doe"
fmt.Printf("Nickname: %s, Year: %d, Pi: %.2f\n", nickname, year, pi)
fmt.Printf("Coordinates: (%d, %d)\n", x, y)
fmt.Printf("Full Name: %s %s\n", firstName, lastName)
}4. 그룹 선언
var (
// 패키지 레벨 변수들
appName = "MyGoApp"
version = "1.0.0"
maxUsers = 1000
isDebug = false
)
func groupDeclaration() {
// 함수 내부 그룹 선언
var (
width = 1920
height = 1080
fps = 60
)
fmt.Printf("App: %s v%s, Max Users: %d\n", appName, version, maxUsers)
fmt.Printf("Resolution: %dx%d@%dfps\n", width, height, fps)
}🔄 변수 재할당
func reassignment() {
// 변수 값 변경
var score int = 0
score = 100 // 재할당
score += 25 // score = score + 25
// 짧은 선언으로 새 변수 추가
name := "Alice"
name, age := "Bob", 30 // name은 재할당, age는 새 선언
fmt.Printf("Score: %d\n", score)
fmt.Printf("Name: %s, Age: %d\n", name, age)
}📊 기본 데이터 타입
func dataTypes() {
// 정수형
var smallInt int8 = 127 // -128 ~ 127
var mediumInt int16 = 32767 // -32768 ~ 32767
var normalInt int32 = 2147483647 // -2^31 ~ 2^31-1
var bigInt int64 = 9223372036854775807 // -2^63 ~ 2^63-1
var autoInt int = 42 // 플랫폼에 따라 32bit 또는 64bit
// 부호 없는 정수형
var uSmall uint8 = 255 // 0 ~ 255 (byte와 같음)
var uMedium uint16 = 65535 // 0 ~ 65535
var uNormal uint32 = 4294967295 // 0 ~ 2^32-1
var uBig uint64 = 18446744073709551615 // 0 ~ 2^64-1
// 실수형
var precision float32 = 3.14159 // 32bit 부동소수점
var doublePrecision float64 = 3.141592653589793 // 64bit 부동소수점
// 문자열과 문자
var text string = "Hello, Go!" // UTF-8 문자열
var char rune = 'G' // Unicode 코드 포인트 (int32 별칭)
var byteVal byte = 65 // uint8 별칭 (ASCII 'A')
// 불린
var isReady bool = true // true 또는 false
// 복소수 (잘 안 씀)
var complexNum complex64 = 1 + 2i
var bigComplexNum complex128 = 1 + 2i
fmt.Printf("Integer types: %d, %d, %d, %d, %d\n",
smallInt, mediumInt, normalInt, bigInt, autoInt)
fmt.Printf("Float types: %.2f, %.6f\n", precision, doublePrecision)
fmt.Printf("String: %s, Rune: %c, Byte: %c\n", text, char, byteVal)
fmt.Printf("Boolean: %t\n", isReady)
}2. 함수 정의
🔧 기본 함수 문법
// 기본 함수 형태
func functionName(parameter1 type1, parameter2 type2) returnType {
// 함수 본문
return value
}💫 다양한 함수 예제
1. 매개변수와 반환값이 있는 함수
// 두 수를 더하는 함수
func add(a int, b int) int {
return a + b
}
// 같은 타입 매개변수는 축약 가능
func multiply(a, b int) int {
return a * b
}
// 문자열 조작 함수
func greet(name string) string {
return "Hello, " + name + "!"
}
func basicFunctions() {
result1 := add(10, 20)
result2 := multiply(5, 6)
message := greet("Gopher")
fmt.Printf("10 + 20 = %d\n", result1)
fmt.Printf("5 × 6 = %d\n", result2)
fmt.Printf("%s\n", message)
}2. 다중 반환값 (Go의 특징!)
// 나눗셈과 나머지를 동시에 반환
func divideAndMod(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
// 에러와 함께 반환 (일반적인 패턴)
func divide(a, b int) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return float64(a) / float64(b), nil
}
// 이름있는 반환값 (named return values)
func getCircleStats(radius float64) (area, circumference float64) {
area = 3.14159 * radius * radius
circumference = 2 * 3.14159 * radius
return // 자동으로 area, circumference 반환
}
func multipleReturns() {
// 다중 반환값 받기
quotient, remainder := divideAndMod(17, 5)
fmt.Printf("17 ÷ 5 = %d remainder %d\n", quotient, remainder)
// 에러 처리 패턴
result, err := divide(10, 2)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("10 ÷ 2 = %.2f\n", result)
}
// 이름있는 반환값
area, circum := getCircleStats(5.0)
fmt.Printf("Circle (r=5): Area=%.2f, Circumference=%.2f\n", area, circum)
}3. 가변 인자 함수 (Variadic Functions)
// 가변 개수의 정수를 받아서 합계 계산
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 가변 인자 + 다른 매개변수
func formatMessage(prefix string, messages ...string) string {
result := prefix + ": "
for i, msg := range messages {
if i > 0 {
result += ", "
}
result += msg
}
return result
}
func variadicFunctions() {
// 다양한 개수의 인자 전달
fmt.Printf("Sum of 1,2,3: %d\n", sum(1, 2, 3))
fmt.Printf("Sum of 1,2,3,4,5: %d\n", sum(1, 2, 3, 4, 5))
// 슬라이스를 가변 인자로 전달
numbers := []int{10, 20, 30}
fmt.Printf("Sum of slice: %d\n", sum(numbers...))
// 메시지 포맷팅
message := formatMessage("Alert", "Server down", "Database error", "High CPU")
fmt.Println(message)
}4. 함수도 값이다! (First-class functions)
// 함수 타입 정의
type Operation func(int, int) int
// 함수를 매개변수로 받는 함수
func calculate(a, b int, op Operation) int {
return op(a, b)
}
// 함수를 반환하는 함수
func getOperation(operator string) Operation {
switch operator {
case "+":
return add
case "*":
return multiply
default:
return func(a, b int) int { return 0 }
}
}
func functionsAsValues() {
// 함수를 변수에 할당
var myFunc Operation = add
result := myFunc(10, 20)
fmt.Printf("Function variable: %d\n", result)
// 함수를 매개변수로 전달
result2 := calculate(15, 3, multiply)
fmt.Printf("Function parameter: %d\n", result2)
// 익명 함수 사용
subtract := func(a, b int) int {
return a - b
}
result3 := calculate(20, 8, subtract)
fmt.Printf("Anonymous function: %d\n", result3)
// 함수를 반환받아 사용
addFunc := getOperation("+")
result4 := addFunc(100, 200)
fmt.Printf("Returned function: %d\n", result4)
}3. 에러 처리
🚨 Go의 에러 처리 철학
Go의 에러 처리
Go는 예외(exception) 대신 명시적인 에러 반환을 사용합니다. 에러는 값이며, 반드시 확인해야 합니다.
import (
"errors"
"fmt"
"strconv"
)
// 기본 에러 처리 패턴
func parseAge(s string) (int, error) {
age, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("invalid age format: %s", s)
}
if age < 0 || age > 150 {
return 0, errors.New("age must be between 0 and 150")
}
return age, nil
}
// 커스텀 에러 타입
type ValidationError struct {
Field string
Value string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("validation error in field '%s': %s (value: %s)",
e.Field, e.Message, e.Value)
}
// 커스텀 에러를 반환하는 함수
func validateEmail(email string) error {
if email == "" {
return ValidationError{
Field: "email",
Value: email,
Message: "email cannot be empty",
}
}
if !strings.Contains(email, "@") {
return ValidationError{
Field: "email",
Value: email,
Message: "email must contain @ symbol",
}
}
return nil
}
func errorHandling() {
// 기본 에러 처리
age1, err := parseAge("25")
if err != nil {
fmt.Printf("Error parsing age: %v\n", err)
} else {
fmt.Printf("Valid age: %d\n", age1)
}
// 잘못된 입력 테스트
age2, err := parseAge("abc")
if err != nil {
fmt.Printf("Error parsing age: %v\n", err)
}
// 커스텀 에러 처리
if err := validateEmail(""); err != nil {
fmt.Printf("Email validation error: %v\n", err)
// 에러 타입 확인
if validErr, ok := err.(ValidationError); ok {
fmt.Printf("Field: %s, Value: %s\n", validErr.Field, validErr.Value)
}
}
if err := validateEmail("test@example.com"); err != nil {
fmt.Printf("Email validation error: %v\n", err)
} else {
fmt.Println("Email is valid!")
}
}🛡️ 에러 처리 베스트 프랙티스
// 1. 에러 체크 생략하지 말기
func badExample() {
data, _ := ioutil.ReadFile("config.json") // 나쁜 예: 에러 무시
// 파일이 없어도 프로그램이 계속 실행됨
}
func goodExample() {
data, err := ioutil.ReadFile("config.json")
if err != nil {
log.Fatalf("Failed to read config file: %v", err)
return
}
// 안전하게 data 사용
}
// 2. 에러 래핑 (Go 1.13+)
func processFile(filename string) error {
data, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", filename, err)
}
if err := validateData(data); err != nil {
return fmt.Errorf("validation failed for file %s: %w", filename, err)
}
return nil
}
// 3. 에러 체인 확인
func checkErrorChain() {
err := processFile("nonexistent.txt")
if err != nil {
fmt.Printf("Error: %v\n", err)
// 원본 에러 확인
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
}
}4. 포인터 기초
🎯 포인터란?
포인터
포인터는 메모리 주소를 저장하는 변수입니다. Go의 포인터는 C보다 안전하며, 포인터 산술 연산은 지원하지 않습니다.
func pointerBasics() {
// 기본 변수
x := 42
// 포인터 선언 및 초기화
var p *int // int 타입을 가리키는 포인터
p = &x // x의 주소를 p에 저장
fmt.Printf("x의 값: %d\n", x)
fmt.Printf("x의 주소: %p\n", &x)
fmt.Printf("p의 값 (주소): %p\n", p)
fmt.Printf("p가 가리키는 값: %d\n", *p) // 역참조
// 포인터를 통한 값 변경
*p = 100
fmt.Printf("포인터로 변경 후 x의 값: %d\n", x)
}🔄 함수와 포인터
// 값에 의한 호출 (Call by Value)
func doubleValue(x int) int {
x = x * 2
return x // 원본은 변경되지 않음
}
// 포인터에 의한 호출 (Call by Pointer)
func doublePointer(x *int) {
*x = *x * 2 // 원본 값이 변경됨
}
// 구조체와 포인터
type Person struct {
Name string
Age int
}
func (p Person) growUpValue() {
p.Age++ // 복사본의 나이만 증가
}
func (p *Person) growUpPointer() {
p.Age++ // 원본의 나이가 증가
}
func pointerFunctions() {
// 값 전달 vs 포인터 전달
num := 10
doubled := doubleValue(num)
fmt.Printf("After doubleValue - num: %d, returned: %d\n", num, doubled)
doublePointer(&num)
fmt.Printf("After doublePointer - num: %d\n", num)
// 구조체와 포인터
person := Person{Name: "Alice", Age: 25}
person.growUpValue()
fmt.Printf("After growUpValue: %+v\n", person)
person.growUpPointer()
fmt.Printf("After growUpPointer: %+v\n", person)
}🆕 새로운 메모리 할당
func memoryAllocation() {
// new() 함수: 영점으로 초기화된 메모리 할당
p1 := new(int) // *int 타입, 0으로 초기화
*p1 = 42
fmt.Printf("new()로 할당: %d\n", *p1)
// &를 사용한 주소 획득
x := 100
p2 := &x
fmt.Printf("&로 주소 획득: %d\n", *p2)
// make()는 슬라이스, 맵, 채널용
slice := make([]int, 5) // 길이 5인 int 슬라이스
slice[0] = 10
fmt.Printf("make()로 슬라이스: %v\n", slice)
}🎯 실습 예제
📝 연습 문제 1: 계산기 함수
package main
import (
"fmt"
"errors"
)
// 사칙연산 계산기
func calculator(a, b float64, operator string) (float64, error) {
switch operator {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
default:
return 0, fmt.Errorf("unknown operator: %s", operator)
}
}
func calculatorTest() {
operations := []struct {
a, b float64
operator string
}{
{10, 5, "+"},
{10, 5, "-"},
{10, 5, "*"},
{10, 5, "/"},
{10, 0, "/"}, // 에러 케이스
{10, 5, "%"}, // 에러 케이스
}
for _, op := range operations {
result, err := calculator(op.a, op.b, op.operator)
if err != nil {
fmt.Printf("%.1f %s %.1f = Error: %v\n",
op.a, op.operator, op.b, err)
} else {
fmt.Printf("%.1f %s %.1f = %.2f\n",
op.a, op.operator, op.b, result)
}
}
}📝 연습 문제 2: 문자열 처리 함수
import "strings"
// 문자열 통계 정보
type StringStats struct {
Length int
Words int
Vowels int
Uppercase int
Lowercase int
}
func analyzeString(text string) StringStats {
stats := StringStats{
Length: len(text),
Words: len(strings.Fields(text)),
}
vowels := "aeiouAEIOU"
for _, char := range text {
if strings.ContainsRune(vowels, char) {
stats.Vowels++
}
if char >= 'A' && char <= 'Z' {
stats.Uppercase++
} else if char >= 'a' && char <= 'z' {
stats.Lowercase++
}
}
return stats
}
func stringAnalysisTest() {
texts := []string{
"Hello, Go!",
"The Quick Brown Fox Jumps Over The Lazy Dog",
"golang is AWESOME",
}
for _, text := range texts {
stats := analyzeString(text)
fmt.Printf("\nText: %s\n", text)
fmt.Printf(" Length: %d characters\n", stats.Length)
fmt.Printf(" Words: %d\n", stats.Words)
fmt.Printf(" Vowels: %d\n", stats.Vowels)
fmt.Printf(" Uppercase: %d\n", stats.Uppercase)
fmt.Printf(" Lowercase: %d\n", stats.Lowercase)
}
}📝 연습 문제 3: 포인터를 사용한 스왑 함수
// 두 값을 교환하는 함수
func swap(a, b *int) {
temp := *a
*a = *b
*b = temp
}
// 문자열 스왑
func swapStrings(s1, s2 *string) {
*s1, *s2 = *s2, *s1 // Go의 다중 할당 사용
}
func swapTest() {
// 정수 스왑
x, y := 10, 20
fmt.Printf("Before swap: x=%d, y=%d\n", x, y)
swap(&x, &y)
fmt.Printf("After swap: x=%d, y=%d\n", x, y)
// 문자열 스왑
name1, name2 := "Alice", "Bob"
fmt.Printf("Before swap: name1=%s, name2=%s\n", name1, name2)
swapStrings(&name1, &name2)
fmt.Printf("After swap: name1=%s, name2=%s\n", name1, name2)
}✅ 체크리스트
변수와 타입
- 4가지 변수 선언 방법 이해
- 기본 데이터 타입 파악
- 타입 추론 원리 이해
- Zero values 개념 파악
함수
- 기본 함수 문법 이해
- 다중 반환값 활용법
- 가변 인자 함수 사용법
- 함수를 값으로 다루기
에러 처리
- Go의 에러 처리 철학 이해
- 기본 에러 처리 패턴
- 커스텀 에러 타입 생성
- 에러 래핑과 체인
포인터
- 포인터 기본 개념
- 값 전달 vs 포인터 전달
- 메모리 할당 방법
- 포인터 안전성 이해
다음 단계
기본 문법을 익혔다면 GO 구조체와 인터페이스로 넘어가세요!