Go

Go 언어 fmt.Printf 완벽 가이드

드리프트2 2024. 9. 19. 22:12

Go 언어 fmt.Printf 완벽 가이드: 이제 두렵지않은 printf 사용법 알아볼까요?

Go 언어를 사용하다 보면 fmt 패키지의 printf 계열 함수들을 자주 만나게 한데요.

 

이번에는 Fprintf, Printf, Sprintf 함수들의 포맷 지정 방법에 대해 자세히 알아보겠습니다.


모든 타입에 사용할 수 있는 verb

%v

값을 기본 형식으로 출력합니다.

기본 타입의 경우

타입 verb
논리값 (bool) %t
부호 있는 정수 (int, int8 등) %d
부호 없는 정수 (uint, uint8 등) %d
부동소수점수 (float64 등) %g
복소수 (complex128 등) %g
문자열 (string) %s
채널 (chan) %p
포인터 (pointer) %p
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%v\n", true)
    fmt.Printf("%v\n", 42)
    fmt.Printf("%v\n", uint(42))
    fmt.Printf("%v\n", 12.345)
    fmt.Printf("%v\n", 1-2i)
    fmt.Printf("%v\n", "초밥🍣맥주🍺")
    fmt.Printf("%v\n", make(chan bool))
    fmt.Printf("%v\n", new(int))
}
true
42
42
12.345
(1-2i)
초밥🍣맥주🍺
0x434080
0x416028

합성(composite) 타입의 경우

각 요소에 대해 재귀적으로 %v 포맷을 적용한 결과를 출력합니다.

타입 포맷
구조체 (struct) {필드1 필드2 ...}
구조체 포인터 &{필드1 필드2 ...}
배열・슬라이스 (array, slice) [요소1 요소2 ...]
배열・슬라이스 포인터 &[요소1 요소2 ...]
맵 (map) map[키1:값1 키2:값2 ...]
맵 포인터 &map[키1:값1 키2:값2 ...]
package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%v\n", http.Client{})
    fmt.Printf("%v\n", &http.Client{})
    fmt.Printf("%v\n", [...]int{1, 2, 3})
    fmt.Printf("%v\n", &[...]int{1, 2, 3})
    fmt.Printf("%v\n", []int{1, 2, 3})
    fmt.Printf("%v\n", &[]int{1, 2, 3})
    fmt.Printf("%v\n", map[string]int{"초밥": 1000, "맥주": 500})
    fmt.Printf("%v\n", &map[string]int{"초밥": 1000, "맥주": 500})
}
{<nil> <nil> <nil> 0s}
&{<nil> <nil> <nil> 0s}
[1 2 3]
&[1 2 3]
[1 2 3]
&[1 2 3]
map[맥주:500 초밥:1000]
&map[맥주:500 초밥:1000]

%+v

%v와 동일하지만, 구조체의 경우 필드 이름을 함께 출력합니다.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%v\n", http.Client{})
    fmt.Printf("%+v\n", http.Client{})
}
{<nil> <nil> <nil> 0s}
{Transport:<nil> CheckRedirect:<nil> Jar:<nil> Timeout:0s}

%#v

값을 Go 언어의 문법 형태로 출력합니다.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%#v\n", true)
    fmt.Printf("%#v\n", 42)
    fmt.Printf("%#v\n", uint(42))
    fmt.Printf("%#v\n", 12.345)
    fmt.Printf("%#v\n", 1-2i)
    fmt.Printf("%#v\n", "초밥🍣맥주🍺")
    fmt.Printf("%#v\n", make(chan bool))
    fmt.Printf("%#v\n", new(int))
    fmt.Printf("\n")
    fmt.Printf("%#v\n", http.Client{})
    fmt.Printf("%#v\n", &http.Client{})
    fmt.Printf("%#v\n", [...]int{1, 2, 3})
    fmt.Printf("%#v\n", &[...]int{1, 2, 3})
    fmt.Printf("%#v\n", []int{1, 2, 3})
    fmt.Printf("%#v\n", &[]int{1, 2, 3})
    fmt.Printf("%#v\n", map[string]int{"초밥": 1000, "맥주": 500})
    fmt.Printf("%#v\n", &map[string]int{"초밥": 1000, "맥주": 500})
}
true
42
0x2a
12.345
(1-2i)
"초밥🍣맥주🍺"
(chan bool)(0x834100)
(*int)(0x816260)

http.Client{Transport:http.RoundTripper(nil), CheckRedirect:(func(*http.Request, []*http.Request) error)(nil), Jar:http.CookieJar(nil), Timeout:0}
&http.Client{Transport:http.RoundTripper(nil), CheckRedirect:(func(*http.Request, []*http.Request) error)(nil), Jar:http.CookieJar(nil), Timeout:0}
[3]int{1, 2, 3}
&[3]int{1, 2, 3}
[]int{1, 2, 3}
&[]int{1, 2, 3}
map[string]int{"맥주":500, "초밥":1000}
&map[string]int{"맥주":500, "초밥":1000}

%T

값의 타입을 Go 언어의 문법 형태로 출력합니다.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("%T\n", true)
    fmt.Printf("%T\n", 42)
    fmt.Printf("%T\n", uint(42))
    fmt.Printf("%T\n", 12.345)
    fmt.Printf("%T\n", 1-2i)
    fmt.Printf("%T\n", "초밥🍣맥주🍺")
    fmt.Printf("%T\n", make(chan bool))
    fmt.Printf("%T\n", new(int))
    fmt.Printf("\n")
    fmt.Printf("%T\n", http.Client{})
    fmt.Printf("%T\n", &http.Client{})
    fmt.Printf("%T\n", [...]int{1, 2, 3})
    fmt.Printf("%T\n", &[...]int{1, 2, 3})
    fmt.Printf("%T\n", []int{1, 2, 3})
    fmt.Printf("%T\n", &[]int{1, 2, 3})
    fmt.Printf("%T\n", map[string]int{"초밥": 1000, "맥주": 500})
    fmt.Printf("%T\n", &map[string]int{"초밥": 1000, "맥주": 500})
}
bool
int
uint
float64
complex128
string
chan bool
*int

http.Client
*http.Client
[3]int
*[3]int
[]int
*[]int
map[string]int
*map[string]int

%%

% 자체를 출력할 때 사용합니다.


논리값에 사용할 수 있는 verb

%t

true 또는 false를 출력합니다.

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%t\n", true)
    fmt.Printf("%t\n", false)
}
true
false

정수에 사용할 수 있는 verb

%d

10진수로 표현합니다.

%b

2진수로 표현합니다.

%o

8진수로 표현합니다.

%x

16진수로 표현합니다 (a-f 소문자).

%X

16진수로 표현합니다 (A-F 대문자).

%c

유니코드 코드 포인트에 해당하는 문자로 표현합니다.

%q

해당 문자를 싱글 쿼트 '로 감싼 문자로 표현합니다.

package main

import (
    "fmt"
)

func main() {
    answer := 42
    fmt.Printf("%b\n", answer)
    fmt.Printf("%c\n", answer)
    fmt.Printf("%d\n", answer)
    fmt.Printf("%o\n", answer)
    fmt.Printf("%q\n", answer)
    fmt.Printf("%x\n", answer)
    fmt.Printf("%X\n", answer)
    fmt.Printf("%U\n", answer)
}
101010
*
42
52
'*'
2a
2A
U+002A

부동소수점수・복소수에 사용할 수 있는 verb

%b

소수점 없는 지수 표기 (지수는 2의 거듭제곱)

%e

지수 표기

%E

%e와 동일하지만 eE로 표기됨

%f, %F

지수 표기 없이 출력

%g

지수가 크면 %e, 아니면 %f 형태로 출력

%G

지수가 크면 %E, 아니면 %F 형태로 출력

package main

import (
    "fmt"
)

func main() {
    f := 12.345
    fmt.Printf("%b\n", f)
    fmt.Printf("%e\n", f)
    fmt.Printf("%E\n", f)
    fmt.Printf("%f\n", f)
    fmt.Printf("%F\n", f)
    fmt.Printf("%g\n", f)
    fmt.Printf("%G\n", f)
    fmt.Printf("%g\n", 12345678.9)
    fmt.Printf("%G\n", 12345678.9)
}
6949617174986097p-49
1.234500e+01
1.234500E+01
12.345000
12.345000
12.345
12.345
1.23456789e+07
1.23456789E+07

문자열 ([]byte도 동일)에 사용할 수 있는 verb

%s

그대로 출력합니다.

%q

Go 언어의 문법으로 이스케이프한 문자열을 출력합니다.

%x

한 바이트당 두 자리의 16진수로 출력합니다 (a-f 소문자).

%X

%x와 동일하지만 A-F 대문자로 출력합니다.

package main

import (
    "fmt"
)

func main() {
    s := "초밥🍣맥주🍺"
    fmt.Printf("%s\n", s)
    fmt.Printf("%q\n", s)
    fmt.Printf("%x\n", s)
    fmt.Printf("%X\n", s)
}
초밥🍣맥주🍺
"초밥🍣맥주🍺"
xec94a0xebb094f09f8da3eba7b5ec9dbceb8dba
EC9490EBB094F09F8DA3EBA7B5EC9DBCEB8DBA

width와 precision

verb 바로 앞에 정수를 지정하여 width를 설정할 수 있습니다.

width는 출력할 문자열의 최소 길이(rune 단위)를 의미합니다. 지정하지 않으면 필요한 만큼의 길이가 됩니다.

 

precisionwidth 바로 뒤에 .을 쓰고 정수를 지정하여 설정할 수 있습니다.

.만 쓰고 숫자를 지정하지 않으면 precision은 0이 됩니다.

 

widthprecision*를 사용하면 다음 인자의 값으로 지정할 수 있습니다.

 

이때 그 인자는 정수여야 합니다.

문자열의 경우

precision은 출력할 문자열의 최대 길이(rune 단위)입니다. 문자열이 너무 길면 잘립니다.

%x 또는 %X의 경우 바이트 단위로 처리됩니다.

부동소수점수의 경우

  • width: 출력될 문자열의 최소 길이
  • precision:
    • %e, %f의 경우 소수점 이하 자릿수
    • %g, %G의 경우 유효 자릿수의 최대값
  • 기본값:
    • %e, %f, %#g: 6
    • %g: 필요한 만큼의 자릿수

복소수의 경우 widthprecision이 실수부와 허수부 각각에 적용되며, 결과는 ()로 감싸집니다.

package main

import (
    "fmt"
)

func main() {
    f := 12.345
    fmt.Printf("%f\n", f)
    fmt.Printf("%12f\n", f)
    fmt.Printf("%12.2f\n", f)
    fmt.Printf("%.2f\n", f)
    fmt.Printf("%12.f\n", f)
    fmt.Printf("%e\n", f)
    fmt.Printf("%#g\n", f)
    fmt.Printf("%g\n", f)

    fmt.Printf("%f", 1-2i)
}
12.345000
   12.345000
       12.35
12.35
        12
1.234500e+01
12.3450
12.345
(1.000000-2.000000i)

플래그 (flags)

widthprecision 외에도 verb 앞에 플래그를 사용하여 포맷을 변경할 수 있습니다.

+

  • 숫자: 양수에도 부호(+)를 표시합니다.
  • %q: ASCII 문자만 출력합니다.
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%d\n", 42)
    fmt.Printf("%+d\n", 42)
    fmt.Printf("%q\n", 945)
    fmt.Printf("%+q\n", 945)
    fmt.Printf("%q\n", "초밥🍣맥주🍺")
    fmt.Printf("%+q\n", "초밥🍣맥주🍺")
}
42
+42
'α'
'\u03b1'
"초밥🍣맥주🍺"
"\ucd08\ubc15\U0001f363\ub9db\uc8fc\U0001f37a"

-

왼쪽 정렬을 합니다.

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%5d\n", 42)
    fmt.Printf("%-5d\n", 42)
    fmt.Printf("%10s\n", "초밥🍣맥주🍺")
    fmt.Printf("%-10s\n", "초밥🍣맥주🍺")
}
   42
42   
    초밥🍣맥주🍺
초밥🍣맥주🍺    

#

기본 포맷과는 다른 출력을 합니다.

  • 8진수 (%#o): 앞에 0을 붙입니다.
  • 16진수 (%#x): 앞에 0x를 붙입니다.
  • 16진수 대문자 (%#X): 앞에 0X를 붙입니다.
  • 포인터 (%#p): 앞의 0x를 생략합니다.
  • %q: strconv.CanBackquotetrue를 반환하면 raw 문자열로 출력합니다.
  • %e, %E, %f, %F, %g, %G: 항상 소수점을 표시합니다.
  • %g, %G: 끝의 0을 생략하지 않습니다.
  • %U: U+0078 'x' 형태로 출력합니다.
package main

import (
    "fmt"
)

func main() {
    answer := 42
    fmt.Printf("%o\n", answer)
    fmt.Printf("%#o\n", answer)
    fmt.Printf("%x\n", answer)
    fmt.Printf("%#x\n", answer)
    fmt.Printf("%X\n", answer)
    fmt.Printf("%#X\n", answer)
    fmt.Printf("%p\n", &answer)
    fmt.Printf("%#p\n", &answer)
    fmt.Printf("%q\n", "go")
    fmt.Printf("%#q\n", "go")
    fmt.Printf("%q\n", "`go`")
    fmt.Printf("%#q\n", "`go`")
    fmt.Printf("%.f\n", 12.345)
    fmt.Printf("%#.f\n", 12.345)
    fmt.Printf("%g\n", 12.345)
    fmt.Printf("%#g\n", 12.345)
    fmt.Printf("%U\n", answer)
    fmt.Printf("%#U\n", answer)
}
52
052
2a
0x2a
2A
0X2A
0x416020
416020
"go"
`go`
"`go`"
"`go`"
12
12.
12.345
12.3450
U+002A
U+002A '*'

(공백)

  • 숫자: 부호를 위한 공간을 한 칸 확보합니다.
  • 바이트 단위 문자열 표현: 각 바이트 사이에 공백을 넣습니다.
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%d\n", 42)
    fmt.Printf("% d\n", 42)
    fmt.Printf("%x\n", "초밥🍣맥주🍺")
    fmt.Printf("% x\n", "초밥🍣맥주🍺")
    fmt.Printf("%X\n", "초밥🍣맥주🍺")
    fmt.Printf("% X\n", "초밥🍣맥주🍺")
}
42
 42
xec94a0eb b094f09f8da3 eb a7 b5 ec 9d bc eb 8d ba
ec 94 a0 eb b0 94 f0 9f 8d a3 eb a7 b5 ec 9d bc eb 8d ba
EC9490EB B094F09F8DA3 EB A7 B5 EC 9D BC EB 8D BA
EC 94 90 EB B0 94 F0 9F 8D A3 EB A7 B5 EC 9D BC EB 8D BA

0

공백 대신 0으로 채웁니다.

  • 숫자: 0으로 채우는 위치는 부호 다음입니다.
package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%10s\n", "초밥🍣맥주🍺")
    fmt.Printf("%010s\n", "초밥🍣맥주🍺")
    fmt.Printf("%10.3f\n", -12.345)
    fmt.Printf("%010.3f\n", -12.345)
}
    초밥🍣맥주🍺
00초밥🍣맥주🍺
    -12.345
-00012.345

인덱스를 사용한 인자 지정 [n]

verb 앞에 [n]을 사용하여 포맷할 인자의 인덱스를 지정할 수 있습니다. *에도 적용 가능합니다.

[n]을 지정하면 이후에는 n+1, n+2 번째 인자가 순서대로 사용됩니다.

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("%[2]s %[1]s\n", "초밥🍣", "맥주🍺")
    fmt.Printf("%[3]*.[2]*[1]f\n", 12.345, 2, 8)
    fmt.Printf("%*.*f\n", 8, 2, 12.345)
    fmt.Printf("%s %s %[1]q %q", "초밥🍣", "맥주🍺")
}
맥주🍺 초밥🍣
   12.35
   12.35
초밥🍣 맥주🍺 "초밥🍣" "맥주🍺"