7 minute read

개요

Go는 정적 타입 언어로, 컴파일 시점에 모든 변수의 타입이 결정됩니다. Go의 기본 자료형은 크게 숫자형, 문자열, 불린, 함수로 구분됩니다.


숫자형 (Numeric Types)

정수형 (Integer)

부호 있는 정수 (Signed Integer)

타입 크기 범위 설명
int8 1 byte -128 ~ 127 8비트 정수
int16 2 bytes -32,768 ~ 32,767 16비트 정수
int32 4 bytes -2,147,483,648 ~ 2,147,483,647 32비트 정수
int64 8 bytes -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 64비트 정수
int 플랫폼 의존 32bit 시스템: int32, 64bit 시스템: int64 기본 정수형

부호 없는 정수 (Unsigned Integer)

타입 크기 범위 설명
uint8 1 byte 0 ~ 255 8비트 부호 없는 정수
uint16 2 bytes 0 ~ 65,535 16비트 부호 없는 정수
uint32 4 bytes 0 ~ 4,294,967,295 32비트 부호 없는 정수
uint64 8 bytes 0 ~ 18,446,744,073,709,551,615 64비트 부호 없는 정수
uint 플랫폼 의존 32bit 시스템: uint32, 64bit 시스템: uint64 부호 없는 기본 정수형
uintptr 플랫폼 의존 포인터 크기와 동일 포인터 값 저장용

별칭 타입

타입 실제 타입 용도
byte uint8 바이트 데이터 (ASCII, 바이너리)
rune int32 유니코드 코드 포인트 (문자)

정수형 예제

package main

import "fmt"

func main() {
	// 기본 정수 타입
	var a int = 10
	var b int8 = 127
	var c uint = 100
	
	fmt.Printf("int: %d, int8: %d, uint: %d\n", a, b, c)
	
	// 타입 추론
	x := 42  // int로 추론
	fmt.Printf("x의 타입: %T, 값: %d\n", x, x)
	
	// 16진수, 8진수, 2진수 표현
	hex := 0xFF      // 16진수 (255)
	oct := 0o77      // 8진수 (63)
	bin := 0b1010    // 2진수 (10)
	fmt.Printf("hex: %d, oct: %d, bin: %d\n", hex, oct, bin)
}

부동소수점형 (Floating Point)

타입 크기 정밀도 설명
float32 4 bytes 약 6-7 자리 단정밀도 부동소수점
float64 8 bytes 약 15-16 자리 배정밀도 부동소수점 (기본)
package main

import "fmt"

func main() {
	var f32 float32 = 3.14159
	var f64 float64 = 3.141592653589793
	
	fmt.Printf("float32: %.5f\n", f32)
	fmt.Printf("float64: %.15f\n", f64)
	
	// 타입 추론 (float64가 기본)
	pi := 3.14
	fmt.Printf("pi의 타입: %T\n", pi)  // float64
	
	// 과학적 표기법
	scientific := 1.23e-4  // 0.000123
	fmt.Printf("scientific: %f\n", scientific)
}

복소수형 (Complex)

타입 크기 구성 설명
complex64 8 bytes float32 실수부 + float32 허수부 단정밀도 복소수
complex128 16 bytes float64 실수부 + float64 허수부 배정밀도 복소수 (기본)
package main

import "fmt"

func main() {
	var c1 complex64 = 1 + 2i
	var c2 complex128 = complex(3, 4)  // 3 + 4i
	
	fmt.Printf("c1: %v\n", c1)
	fmt.Printf("c2: %v\n", c2)
	
	// 실수부와 허수부 추출
	fmt.Printf("실수부: %f, 허수부: %f\n", real(c2), imag(c2))
	
	// 연산
	sum := c1 + complex64(c2)
	fmt.Printf("합: %v\n", sum)
}


문자열형 (String)

string

  • 불변(immutable) 타입
  • UTF-8 인코딩 바이트 시퀀스
  • 큰따옴표(")로 정의
  • 백틱(`)으로 raw string 정의 (이스케이프 무시)
package main

import "fmt"

func main() {
	// 기본 문자열
	str1 := "Hello, 世界"
	fmt.Println(str1)
	
	// Raw string (이스케이프 시퀀스 무시)
	str2 := `첫 번째 줄
두 번째 줄
세 번째 줄`
	fmt.Println(str2)
	
	// 문자열 연산
	concatenated := "Hello" + " " + "World"
	fmt.Println(concatenated)
	
	// 길이 (바이트 수)
	fmt.Printf("바이트 길이: %d\n", len(str1))  // 13 (영문 6 + 한자 6 + 쉼표/공백 2)
	
	// 룬(문자) 개수
	fmt.Printf("문자 개수: %d\n", len([]rune(str1)))  // 9
	
	// 인덱싱 (바이트 단위)
	fmt.Printf("첫 바이트: %c\n", str1[0])  // H
}

문자열 vs 바이트 슬라이스

package main

import "fmt"

func main() {
	str := "Hello"
	
	// 문자열 → 바이트 슬라이스
	bytes := []byte(str)
	fmt.Printf("바이트: %v\n", bytes)  // [72 101 108 108 111]
	
	// 바이트 슬라이스 → 문자열
	newStr := string(bytes)
	fmt.Printf("문자열: %s\n", newStr)  // Hello
}

rune (문자)

  • int32의 별칭
  • 유니코드 코드 포인트 표현
  • 작은따옴표(')로 정의
  • 단일 유니코드 문자 저장
package main

import "fmt"

func main() {
	var r1 rune = 'A'
	var r2 rune = '가'
	var r3 rune = '世'
	
	fmt.Printf("r1: %c (코드: %d)\n", r1, r1)  // A (코드: 65)
	fmt.Printf("r2: %c (코드: %d)\n", r2, r2)  // 가 (코드: 44032)
	fmt.Printf("r3: %c (코드: %d)\n", r3, r3)  // 世 (코드: 19990)
	
	// 문자열 순회 (룬 단위)
	str := "Hello, 世界"
	for i, r := range str {
		fmt.Printf("인덱스 %d: %c (코드: %d)\n", i, r, r)
	}
}

byte

  • uint8의 별칭
  • ASCII 문자 또는 바이너리 데이터 표현
  • 1바이트 데이터
package main

import "fmt"

func main() {
	var b1 byte = 'A'  // ASCII 65
	var b2 byte = 72   // 'H'
	
	fmt.Printf("b1: %c (%d)\n", b1, b1)  // A (65)
	fmt.Printf("b2: %c (%d)\n", b2, b2)  // H (72)
	
	// 바이트 슬라이스
	data := []byte{72, 101, 108, 108, 111}
	fmt.Printf("문자열: %s\n", string(data))  // Hello
}


불린형 (Boolean)

타입 크기 설명
bool 1 byte true 또는 false 논리값
package main

import "fmt"

func main() {
	var isTrue bool = true
	var isFalse bool = false
	
	fmt.Printf("isTrue: %t, isFalse: %t\n", isTrue, isFalse)
	
	// 비교 연산 결과
	result := 10 > 5
	fmt.Printf("10 > 5: %t\n", result)
	
	// 논리 연산
	and := true && false   // false
	or := true || false    // true
	not := !true           // false
	
	fmt.Printf("AND: %t, OR: %t, NOT: %t\n", and, or, not)
}

주의: Go에서는 0이나 빈 문자열이 자동으로 false로 변환되지 않습니다.

// ❌ 컴파일 에러
if 1 {  // non-bool 1 used as if condition
	fmt.Println("Error")
}

// ✅ 명시적 비교 필요
if 1 != 0 {
	fmt.Println("OK")
}


함수형 (Function)

함수도 Go의 일급 객체(first-class citizen)로 타입입니다.

package main

import "fmt"

func main() {
	// 함수를 변수에 할당
	var add func(int, int) int = func(a, b int) int {
		return a + b
	}
	
	result := add(3, 5)
	fmt.Printf("3 + 5 = %d\n", result)
	
	// 함수를 매개변수로 전달
	calculate := func(a, b int, op func(int, int) int) int {
		return op(a, b)
	}
	
	multiply := func(a, b int) int { return a * b }
	fmt.Printf("4 * 5 = %d\n", calculate(4, 5, multiply))
}


제로값 (Zero Value)

Go는 선언만 하고 초기화하지 않은 변수에 제로값(zero value)을 자동으로 할당합니다.

타입 제로값
정수형 (int, int8, …) 0
부동소수점 (float32, float64) 0.0
복소수 (complex64, complex128) 0+0i
bool false
string "" (빈 문자열)
포인터, 함수, 인터페이스, 슬라이스, 채널, 맵 nil
package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	
	fmt.Printf("int: %d\n", i)        // 0
	fmt.Printf("float64: %f\n", f)    // 0.000000
	fmt.Printf("bool: %t\n", b)       // false
	fmt.Printf("string: '%s'\n", s)   // ''
}


타입 추론과 리터럴

타입 추론

:= 연산자를 사용하면 Go가 자동으로 타입을 추론합니다.

package main

import "fmt"

func main() {
	// 타입 추론
	a := 42          // int
	b := 3.14        // float64
	c := "hello"     // string
	d := true        // bool
	e := 'A'         // rune (int32)
	
	fmt.Printf("%T, %T, %T, %T, %T\n", a, b, c, d, e)
	// int, float64, string, bool, int32
}

리터럴 접미사

특정 타입을 명시하려면 타입 변환이 필요합니다.

package main

import "fmt"

func main() {
	// float32로 명시
	var f32 float32 = 3.14
	
	// 타입 변환 필요
	x := float32(3.14)
	
	fmt.Printf("%T, %T\n", f32, x)  // float32, float32
}


실전 예제

package main

import "fmt"

func main() {
	// 1. 정수형
	var age int = 25
	var maxUsers uint = 1000
	fmt.Printf("나이: %d, 최대 사용자: %d\n", age, maxUsers)
	
	// 2. 부동소수점
	var price float64 = 19.99
	var discount float32 = 0.15
	finalPrice := price * (1 - float64(discount))
	fmt.Printf("최종 가격: $%.2f\n", finalPrice)
	
	// 3. 문자열
	name := "홍길동"
	greeting := fmt.Sprintf("안녕하세요, %s님!", name)
	fmt.Println(greeting)
	
	// 4. 룬과 문자열 처리
	text := "Hello, 世界"
	runeCount := len([]rune(text))
	byteCount := len(text)
	fmt.Printf("문자 수: %d, 바이트 수: %d\n", runeCount, byteCount)
	
	// 5. 불린
	isActive := true
	isPremium := false
	
	if isActive && !isPremium {
		fmt.Println("무료 회원이 활성화됨")
	}
	
	// 6. 복소수 (과학/공학 계산)
	impedance := complex(3, 4)  // 3 + 4i
	magnitude := real(impedance)*real(impedance) + imag(impedance)*imag(impedance)
	fmt.Printf("임피던스 크기의 제곱: %.0f\n", magnitude)
}


타입 변환

Go는 명시적 타입 변환만 허용합니다 (암시적 변환 없음).

package main

import "fmt"

func main() {
	var i int = 42
	var f float64 = float64(i)  // int → float64
	var u uint = uint(f)        // float64 → uint
	
	fmt.Printf("int: %d, float64: %f, uint: %d\n", i, f, u)
	
	// ❌ 암시적 변환 불가
	// var x float64 = i  // cannot use i (type int) as type float64
	
	// ✅ 명시적 변환 필요
	var x float64 = float64(i)
	fmt.Printf("x: %f\n", x)
}


타입 확인

%T 포맷 지정자나 reflect 패키지로 타입을 확인할 수 있습니다.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x = 42
	
	// %T 사용
	fmt.Printf("타입: %T\n", x)  // int
	
	// reflect 패키지 사용
	fmt.Println(reflect.TypeOf(x))  // int
}


베스트 프랙티스

  1. 기본 타입 사용: 특별한 이유가 없다면 int, float64 사용
  2. 명시적 변환: 타입 변환은 항상 명시적으로
  3. rune vs byte: 유니코드 문자는 rune, ASCII/바이너리는 byte
  4. 문자열 불변성: 문자열은 변경 불가, 새로운 문자열 생성 필요
  5. 제로값 활용: 제로값이 유효한 초기값인지 확인
  6. 오버플로우 주의: 정수 연산 시 범위 초과 주의


참고 자료