[Go] 형식 동사
Updated:
개요
형식 동사(Formatting Verb)는 fmt.Printf, fmt.Sprintf 등의 함수에서 값을 특정 형식으로 출력하기 위해 사용하는 지시자입니다. %로 시작하며, 뒤에 오는 문자에 따라 출력 형식이 결정됩니다.
기본 구조
%[플래그][너비][.정밀도]동사
- 플래그: 출력 형식 조정 (
+,-,#,0, ` `) - 너비: 최소 출력 너비
- 정밀도: 소수점 자리수 또는 문자열 최대 길이
- 동사: 출력 타입 지정자
일반 동사
%v, %+v, %#v (기본 형식)
가장 범용적으로 사용되는 동사입니다.
%v - 기본 형식
fmt.Printf("%v\n", 42) // 42
fmt.Printf("%v\n", "hello") // hello
fmt.Printf("%v\n", true) // true
fmt.Printf("%v\n", 3.14) // 3.14
fmt.Printf("%v\n", []int{1, 2, 3}) // [1 2 3]
fmt.Printf("%v\n", map[string]int{"a": 1}) // map[a:1]
type Person struct {
Name string
Age int
}
fmt.Printf("%v\n", Person{"Alice", 30}) // {Alice 30}
%+v - 필드명 포함 (구조체)
type Person struct {
Name string
Age int
}
p := Person{"Alice", 30}
fmt.Printf("%v\n", p) // {Alice 30}
fmt.Printf("%+v\n", p) // {Name:Alice Age:30}
%#v - Go 문법 형식
fmt.Printf("%#v\n", "hello") // "hello"
fmt.Printf("%#v\n", []int{1, 2, 3}) // []int{1, 2, 3}
fmt.Printf("%#v\n", Person{"Alice", 30}) // main.Person{Name:"Alice", Age:30}
%T - 타입 출력
fmt.Printf("%T\n", 42) // int
fmt.Printf("%T\n", 3.14) // float64
fmt.Printf("%T\n", "hello") // string
fmt.Printf("%T\n", []int{1, 2}) // []int
fmt.Printf("%T\n", Person{}) // main.Person
%% - 리터럴 %
fmt.Printf("완료율: 85%%\n") // 완료율: 85%
fmt.Printf("100%%\n") // 100%
불린 동사
%t - 불린 값
fmt.Printf("%t\n", true) // true
fmt.Printf("%t\n", false) // false
fmt.Printf("%t\n", 1 == 1) // true
fmt.Printf("%t\n", 5 > 10) // false
정수 동사
%d - 10진수 (Decimal)
fmt.Printf("%d\n", 42) // 42
fmt.Printf("%d\n", -15) // -15
fmt.Printf("%d\n", 0) // 0
%b - 2진수 (Binary)
fmt.Printf("%b\n", 8) // 1000
fmt.Printf("%b\n", 15) // 1111
fmt.Printf("%b\n", 255) // 11111111
%o - 8진수 (Octal)
fmt.Printf("%o\n", 8) // 10
fmt.Printf("%o\n", 10) // 12
fmt.Printf("%o\n", 64) // 100
%x, %X - 16진수 (Hexadecimal)
fmt.Printf("%x\n", 26) // 1a (소문자)
fmt.Printf("%X\n", 26) // 1A (대문자)
fmt.Printf("%x\n", 255) // ff
fmt.Printf("%X\n", 255) // FF
%c - 유니코드 문자
fmt.Printf("%c\n", 65) // A
fmt.Printf("%c\n", 97) // a
fmt.Printf("%c\n", '한') // 한
fmt.Printf("%c\n", 0x48) // H
%U - 유니코드 코드 포인트
fmt.Printf("%U\n", 'A') // U+0041
fmt.Printf("%U\n", '한') // U+D55C
fmt.Printf("%U\n", '🔥') // U+1F525
정수 동사 비교 예제
num := 42
fmt.Printf("10진수: %d\n", num) // 10진수: 42
fmt.Printf("2진수: %b\n", num) // 2진수: 101010
fmt.Printf("8진수: %o\n", num) // 8진수: 52
fmt.Printf("16진수: %x\n", num) // 16진수: 2a
fmt.Printf("문자: %c\n", num) // 문자: *
fmt.Printf("유니코드: %U\n", num) // 유니코드: U+002A
실수 동사
%f, %F - 소수점 표기
fmt.Printf("%f\n", 3.14159) // 3.141590 (기본 6자리)
fmt.Printf("%F\n", 3.14159) // 3.141590 (%f와 동일)
fmt.Printf("%.2f\n", 3.14159) // 3.14 (소수점 2자리)
fmt.Printf("%.0f\n", 3.14159) // 3 (정수)
%e, %E - 지수 표기
fmt.Printf("%e\n", 1234.5) // 1.234500e+03 (소문자)
fmt.Printf("%E\n", 1234.5) // 1.234500E+03 (대문자)
fmt.Printf("%.2e\n", 1234.5) // 1.23e+03
%g, %G - 자동 선택 (간결한 표기)
fmt.Printf("%g\n", 1.1) // 1.1
fmt.Printf("%g\n", 0.0000001) // 1e-07
fmt.Printf("%g\n", 1000000.0) // 1e+06
fmt.Printf("%G\n", 1000000.0) // 1E+06
// 작은 수는 %f, 큰 수는 %e 형식 자동 선택
fmt.Printf("%g\n", 123.456) // 123.456
fmt.Printf("%g\n", 123456789.0) // 1.23456789e+08
%b - 실수의 2진 지수 표기
fmt.Printf("%b\n", 8.0) // 4503599627370496p-49
fmt.Printf("%b\n", 2.0) // 4503599627370496p-51
실수 동사 비교 예제
num := 1234.56789
fmt.Printf("기본: %f\n", num) // 기본: 1234.567890
fmt.Printf("소수점 2자리: %.2f\n", num) // 소수점 2자리: 1234.57
fmt.Printf("지수: %e\n", num) // 지수: 1.234568e+03
fmt.Printf("자동: %g\n", num) // 자동: 1234.56789
문자열 동사
%s - 문자열
fmt.Printf("%s\n", "Hello") // Hello
fmt.Printf("%s\n", "안녕하세요") // 안녕하세요
%q - 따옴표로 감싼 문자열 (이스케이프)
fmt.Printf("%q\n", "Hello") // "Hello"
fmt.Printf("%q\n", `"hello"`) // "\"hello\""
fmt.Printf("%q\n", "line1\nline2") // "line1\nline2"
fmt.Printf("%q\n", "tab\there") // "tab\there"
%x, %X - 16진수 인코딩
fmt.Printf("%x\n", "Hello") // 48656c6c6f
fmt.Printf("%X\n", "Hello") // 48656C6C6F
문자열 동사 비교
s := "Hello\tWorld"
fmt.Printf("기본: %s\n", s) // 기본: Hello World
fmt.Printf("따옴표: %q\n", s) // 따옴표: "Hello\tWorld"
fmt.Printf("16진수: %x\n", s) // 16진수: 48656c6c6f09576f726c64
포인터 동사
%p - 포인터 주소
num := 42
ptr := &num
fmt.Printf("%p\n", ptr) // 0xc0000b4010 (예시)
fmt.Printf("%p\n", &num) // 0xc0000b4010 (예시)
s := "hello"
fmt.Printf("%p\n", &s) // 0xc0000a6020 (예시)
너비와 정밀도
너비 (Width)
숫자 너비
// 최소 너비 지정 (오른쪽 정렬)
fmt.Printf("%5d\n", 42) // 42
fmt.Printf("%5d\n", 1) // 1
fmt.Printf("%5d\n", 12345) // 12345
// 문자열 너비
fmt.Printf("%10s\n", "Hello") // Hello
fmt.Printf("%10s\n", "Hi") // Hi
왼쪽 정렬 (-)
fmt.Printf("%-5d|\n", 42) // 42 |
fmt.Printf("%-10s|\n", "Hello") // Hello |
0으로 채우기
fmt.Printf("%05d\n", 42) // 00042
fmt.Printf("%05d\n", 1) // 00001
fmt.Printf("%08d\n", 123) // 00000123
정밀도 (Precision)
실수 정밀도
pi := 3.14159265359
fmt.Printf("%.2f\n", pi) // 3.14
fmt.Printf("%.4f\n", pi) // 3.1416
fmt.Printf("%.8f\n", pi) // 3.14159265
fmt.Printf("%.0f\n", pi) // 3
문자열 최대 길이
s := "Hello, World!"
fmt.Printf("%.5s\n", s) // Hello
fmt.Printf("%.10s\n", s) // Hello, Wor
너비와 정밀도 결합
pi := 3.14159
// %[너비].[정밀도]f
fmt.Printf("%10.2f\n", pi) // 3.14
fmt.Printf("%10.4f\n", pi) // 3.1416
fmt.Printf("%-10.2f|\n", pi) // 3.14 |
정밀도 비교 예제
value := 123.456789
fmt.Printf("%f\n", value) // 123.456789
fmt.Printf("%.2f\n", value) // 123.46
fmt.Printf("%.4f\n", value) // 123.4568
fmt.Printf("%10.2f\n", value) // 123.46
fmt.Printf("%-10.2f\n", value) // 123.46
플래그 (Flags)
+ 플래그 - 부호 항상 표시
fmt.Printf("%d\n", 42) // 42
fmt.Printf("%+d\n", 42) // +42
fmt.Printf("%+d\n", -42) // -42
fmt.Printf("%f\n", 3.14) // 3.140000
fmt.Printf("%+f\n", 3.14) // +3.140000
- 플래그 - 왼쪽 정렬
fmt.Printf("%5d|\n", 42) // 42|
fmt.Printf("%-5d|\n", 42) // 42 |
fmt.Printf("%10s|\n", "Hello") // Hello|
fmt.Printf("%-10s|\n", "Hello") // Hello |
# 플래그 - 대체 형식
8진수/16진수 접두사
fmt.Printf("%o\n", 64) // 100
fmt.Printf("%#o\n", 64) // 0100
fmt.Printf("%x\n", 255) // ff
fmt.Printf("%#x\n", 255) // 0xff
fmt.Printf("%#X\n", 255) // 0XFF
실수 소수점 항상 표시
fmt.Printf("%g\n", 100.0) // 100
fmt.Printf("%#g\n", 100.0) // 100.000
0 플래그 - 0으로 채우기
fmt.Printf("%5d\n", 42) // 42
fmt.Printf("%05d\n", 42) // 00042
fmt.Printf("%8.2f\n", 3.14) // 3.14
fmt.Printf("%08.2f\n", 3.14) // 00003.14
공백 플래그 - 양수에 공백
fmt.Printf("%d\n", 42) // 42
fmt.Printf("% d\n", 42) // 42 (앞에 공백)
fmt.Printf("% d\n", -42) // -42
플래그 조합
// 0과 - 동시 사용: -가 우선
fmt.Printf("%05d\n", 42) // 00042
fmt.Printf("%-05d\n", 42) // 42 (- 우선)
// +와 공백: +가 우선
fmt.Printf("% d\n", 42) // 42
fmt.Printf("%+d\n", 42) // +42
실용 예제
예제 1: 테이블 형식 출력
package main
import "fmt"
func main() {
fmt.Printf("%-10s %5s %8s\n", "이름", "나이", "점수")
fmt.Println("--------------------------------")
fmt.Printf("%-10s %5d %8.2f\n", "Alice", 30, 95.5)
fmt.Printf("%-10s %5d %8.2f\n", "Bob", 25, 87.3)
fmt.Printf("%-10s %5d %8.2f\n", "Charlie", 35, 92.8)
}
// 출력:
// 이름 나이 점수
// --------------------------------
// Alice 30 95.50
// Bob 25 87.30
// Charlie 35 92.80
예제 2: 진행률 표시
package main
import "fmt"
func main() {
total := 100
for i := 0; i <= total; i += 10 {
percent := float64(i) / float64(total) * 100
fmt.Printf("\r진행률: %3.0f%% [%3d/%3d]", percent, i, total)
}
fmt.Println()
}
// 출력:
// 진행률: 100% [100/100]
예제 3: 16진수 덤프
package main
import "fmt"
func hexDump(data []byte) {
for i := 0; i < len(data); i += 16 {
// 주소
fmt.Printf("%08x ", i)
// 16진수
for j := 0; j < 16; j++ {
if i+j < len(data) {
fmt.Printf("%02x ", data[i+j])
} else {
fmt.Print(" ")
}
if j == 7 {
fmt.Print(" ")
}
}
// ASCII
fmt.Print(" |")
for j := 0; j < 16 && i+j < len(data); j++ {
b := data[i+j]
if b >= 32 && b <= 126 {
fmt.Printf("%c", b)
} else {
fmt.Print(".")
}
}
fmt.Println("|")
}
}
func main() {
data := []byte("Hello, World!\nGo is awesome!")
hexDump(data)
}
// 출력:
// 00000000 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 0a 47 6f |Hello, World!.Go|
// 00000010 20 69 73 20 61 77 65 73 6f 6d 65 21 | is awesome!|
예제 4: 통화 형식
package main
import "fmt"
func main() {
prices := []float64{1234.5, 56.78, 9012.345}
for _, price := range prices {
fmt.Printf("₩%,.2f\n", price)
}
// 더 정교한 포맷팅
fmt.Printf("\n금액: %12.2f원\n", 1234567.89)
fmt.Printf("금액: %12.2f원\n", 123.45)
}
// 출력:
// ₩1,234.50
// ₩56.78
// ₩9,012.35
//
// 금액: 1234567.89원
// 금액: 123.45원
예제 5: 디버깅 출력
package main
import "fmt"
type User struct {
ID int
Name string
Email string
IsActive bool
}
func main() {
user := User{
ID: 1001,
Name: "Alice",
Email: "alice@example.com",
IsActive: true,
}
// 다양한 형식으로 출력
fmt.Printf("기본: %v\n", user)
fmt.Printf("필드명 포함: %+v\n", user)
fmt.Printf("Go 구문: %#v\n", user)
fmt.Printf("타입: %T\n", user)
}
// 출력:
// 기본: {1001 Alice alice@example.com true}
// 필드명 포함: {ID:1001 Name:Alice Email:alice@example.com IsActive:true}
// Go 구문: main.User{ID:1001, Name:"Alice", Email:"alice@example.com", IsActive:true}
// 타입: main.User
예제 6: 로그 메시지
package main
import (
"fmt"
"time"
)
func log(level, message string) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
fmt.Printf("[%s] %-5s %s\n", timestamp, level, message)
}
func main() {
log("INFO", "애플리케이션 시작")
log("DEBUG", "설정 파일 로드 완료")
log("WARN", "메모리 사용량 80% 초과")
log("ERROR", "데이터베이스 연결 실패")
}
// 출력:
// [2025-12-31 10:30:45] INFO 애플리케이션 시작
// [2025-12-31 10:30:45] DEBUG 설정 파일 로드 완료
// [2025-12-31 10:30:45] WARN 메모리 사용량 80% 초과
// [2025-12-31 10:30:45] ERROR 데이터베이스 연결 실패
종합 예제
package main
import (
"fmt"
)
type Product struct {
ID int
Name string
Price float64
}
func main() {
// 1. 정수 형식
fmt.Println("=== 정수 형식 ===")
num := 42
fmt.Printf("10진수: %d\n", num)
fmt.Printf("2진수: %b\n", num)
fmt.Printf("8진수: %o\n", num)
fmt.Printf("16진수: %x\n", num)
fmt.Printf("문자: %c\n", num)
fmt.Printf("유니코드: %U\n", num)
// 2. 실수 형식
fmt.Println("\n=== 실수 형식 ===")
pi := 3.14159265
fmt.Printf("기본: %f\n", pi)
fmt.Printf("소수점 2자리: %.2f\n", pi)
fmt.Printf("지수: %e\n", pi)
fmt.Printf("자동: %g\n", pi)
// 3. 문자열 형식
fmt.Println("\n=== 문자열 형식 ===")
str := "Hello\tWorld"
fmt.Printf("기본: %s\n", str)
fmt.Printf("따옴표: %q\n", str)
fmt.Printf("16진수: %x\n", str)
fmt.Printf("최대 5자: %.5s\n", str)
// 4. 너비와 정렬
fmt.Println("\n=== 너비와 정렬 ===")
fmt.Printf("오른쪽 정렬: %10d\n", 42)
fmt.Printf("왼쪽 정렬: %-10d|\n", 42)
fmt.Printf("0으로 채우기: %05d\n", 42)
// 5. 플래그
fmt.Println("\n=== 플래그 ===")
fmt.Printf("부호 표시: %+d\n", 42)
fmt.Printf("16진수 접두사: %#x\n", 255)
fmt.Printf("공백: % d\n", 42)
// 6. 구조체
fmt.Println("\n=== 구조체 ===")
p := Product{1, "노트북", 1500000.0}
fmt.Printf("기본: %v\n", p)
fmt.Printf("필드명: %+v\n", p)
fmt.Printf("Go 구문: %#v\n", p)
fmt.Printf("타입: %T\n", p)
// 7. 포인터
fmt.Println("\n=== 포인터 ===")
x := 100
fmt.Printf("값: %d\n", x)
fmt.Printf("주소: %p\n", &x)
// 8. 테이블 출력
fmt.Println("\n=== 제품 목록 ===")
fmt.Printf("%-5s %-15s %10s\n", "ID", "제품명", "가격")
fmt.Println("-----------------------------------")
products := []Product{
{1, "노트북", 1500000},
{2, "마우스", 25000},
{3, "키보드", 85000},
}
for _, prod := range products {
fmt.Printf("%-5d %-15s %10.0f원\n", prod.ID, prod.Name, prod.Price)
}
}
형식 동사 요약표
일반
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%v |
기본 형식 | %v, 42 |
42 |
%+v |
필드명 포함 (구조체) | %+v, struct |
{field:value} |
%#v |
Go 문법 형식 | %#v, “hi” |
"hi" |
%T |
타입 | %T, 42 |
int |
%% |
리터럴 % | %% |
% |
불린
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%t |
true/false | %t, true |
true |
정수
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%d |
10진수 | %d, 42 |
42 |
%b |
2진수 | %b, 8 |
1000 |
%o |
8진수 | %o, 10 |
12 |
%x |
16진수 (소문자) | %x, 255 |
ff |
%X |
16진수 (대문자) | %X, 255 |
FF |
%c |
유니코드 문자 | %c, 65 |
A |
%U |
유니코드 포인트 | %U, ‘A’ |
U+0041 |
실수
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%f / %F |
소수점 표기 | %.2f, 3.14 |
3.14 |
%e |
지수 표기 (소문자) | %e, 1234.5 |
1.2345e+03 |
%E |
지수 표기 (대문자) | %E, 1234.5 |
1.2345E+03 |
%g |
자동 선택 (소문자) | %g, 1.1 |
1.1 |
%G |
자동 선택 (대문자) | %G, 1e6 |
1E+06 |
문자열
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%s |
문자열 | %s, “hi” |
hi |
%q |
따옴표 문자열 | %q, “hi” |
"hi" |
%x |
16진수 인코딩 | %x, “Hi” |
4869 |
포인터
| 동사 | 설명 | 예제 | 출력 |
|---|---|---|---|
%p |
포인터 주소 | %p, &x |
0xc000... |
플래그
| 플래그 | 설명 | 예제 | 출력 |
|---|---|---|---|
+ |
부호 항상 표시 | %+d, 42 |
+42 |
- |
왼쪽 정렬 | %-5d, 42 |
42 |
# |
대체 형식 | %#x, 255 |
0xff |
0 |
0으로 채우기 | %05d, 42 |
00042 |
| ` ` (공백) | 양수에 공백 | % d, 42 |
` 42` |
일반적인 실수
1. 동사와 타입 불일치
// ❌ 잘못됨
fmt.Printf("%d\n", "hello") // %!d(string=hello)
fmt.Printf("%s\n", 42) // %!s(int=42)
// ✅ 올바름
fmt.Printf("%s\n", "hello") // hello
fmt.Printf("%d\n", 42) // 42
2. 정밀도 없이 실수 출력
// ❌ 불필요하게 긴 출력
fmt.Printf("%f\n", 3.14) // 3.140000
// ✅ 정밀도 지정
fmt.Printf("%.2f\n", 3.14) // 3.14
3. %v 남용
// ❌ 타입별 최적화 없음
fmt.Printf("%v\n", 3.14159) // 3.14159
// ✅ 적절한 동사 사용
fmt.Printf("%.2f\n", 3.14159) // 3.14
4. 인자 개수 불일치
// ❌ 인자 부족
fmt.Printf("%s %d\n", "age") // age %!d(MISSING)
// ❌ 인자 초과
fmt.Printf("%s\n", "name", 30) // name%!(EXTRA int=30)
// ✅ 일치
fmt.Printf("%s %d\n", "age", 30) // age 30
5. 너비 지정 실수
// ❌ 너비보다 긴 값
fmt.Printf("%3d\n", 12345) // 12345 (너비 무시됨)
// ✅ 충분한 너비
fmt.Printf("%6d\n", 12345) // 12345
베스트 프랙티스
- 타입에 맞는 동사 사용: 각 타입에 최적화된 동사 선택
- %v는 디버깅용: 프로덕션 코드에서는 명시적 동사 사용
- 정밀도 지정: 실수는 항상 정밀도 지정 (
.2f,.4f) - 테이블 출력: 너비와 정렬 활용하여 깔끔한 출력
- 일관성: 동일한 데이터는 동일한 형식 사용
- 가독성: 복잡한 포맷은 상수나 함수로 분리
- %+v 디버깅: 구조체 디버깅 시 필드명 포함
- %#v 복사: 값을 Go 코드로 복사할 때 유용
- 에러 메시지: %q로 문자열을 감싸 명확하게 표시
- 성능: 불필요한 포맷팅 피하기 (fmt.Sprint vs 직접 문자열)