Updated:

10 minute read

개요

형식 동사(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


베스트 프랙티스

  1. 타입에 맞는 동사 사용: 각 타입에 최적화된 동사 선택
  2. %v는 디버깅용: 프로덕션 코드에서는 명시적 동사 사용
  3. 정밀도 지정: 실수는 항상 정밀도 지정 (.2f, .4f)
  4. 테이블 출력: 너비와 정렬 활용하여 깔끔한 출력
  5. 일관성: 동일한 데이터는 동일한 형식 사용
  6. 가독성: 복잡한 포맷은 상수나 함수로 분리
  7. %+v 디버깅: 구조체 디버깅 시 필드명 포함
  8. %#v 복사: 값을 Go 코드로 복사할 때 유용
  9. 에러 메시지: %q로 문자열을 감싸 명확하게 표시
  10. 성능: 불필요한 포맷팅 피하기 (fmt.Sprint vs 직접 문자열)


참고 자료