[Go] strconv
개요
Go의 strconv 패키지는 기본 타입과 문자열 간의 변환을 제공합니다.
주요 특징:
- 정수 변환: Atoi, Itoa로 간편한 정수 변환
- Format/Parse: 모든 기본 타입 양방향 변환
- 진법 지원: 2진법부터 36진법까지
- 에러 처리: Parse 함수는 에러 반환
- Quote/Unquote: 문자열 이스케이프 처리
- AppendXxx: 버퍼에 직접 추가로 성능 향상
- 정밀도 제어: 실수 포맷팅 옵션
정수 변환
1. Atoi/Itoa - 간단한 변환
import (
"fmt"
"strconv"
)
func main() {
// Int to ASCII (정수 → 문자열)
s := strconv.Itoa(123)
fmt.Printf("%s (type: %T)\n", s, s) // 123 (type: string)
// ASCII to Int (문자열 → 정수)
n, err := strconv.Atoi("456")
if err != nil {
fmt.Println("에러:", err)
}
fmt.Printf("%d (type: %T)\n", n, n) // 456 (type: int)
// 음수
s = strconv.Itoa(-789)
fmt.Println(s) // -789
n, _ = strconv.Atoi("-999")
fmt.Println(n) // -999
}
2. ParseInt - 상세한 정수 파싱
func main() {
// ParseInt(s string, base int, bitSize int) (int64, error)
// 10진수, 64비트
n, err := strconv.ParseInt("12345", 10, 64)
if err != nil {
fmt.Println(err)
}
fmt.Println(n) // 12345
// 10진수, 32비트 (범위 초과 시 에러)
n, err = strconv.ParseInt("2147483648", 10, 32)
if err != nil {
fmt.Println(err) // strconv.ParseInt: parsing "2147483648": value out of range
}
// 음수
n, _ = strconv.ParseInt("-999", 10, 64)
fmt.Println(n) // -999
// base 0 = 자동 감지 (0x, 0o, 0b 접두사)
n, _ = strconv.ParseInt("0x1F", 0, 64)
fmt.Println(n) // 31
}
3. FormatInt - 정수 포맷팅
func main() {
// FormatInt(i int64, base int) string
num := int64(255)
// 10진수
s := strconv.FormatInt(num, 10)
fmt.Println(s) // 255
// 2진수
s = strconv.FormatInt(num, 2)
fmt.Println(s) // 11111111
// 16진수
s = strconv.FormatInt(num, 16)
fmt.Println(s) // ff
// 음수
s = strconv.FormatInt(-123, 10)
fmt.Println(s) // -123
}
4. ParseUint/FormatUint - 부호 없는 정수
func main() {
// 부호 없는 정수 파싱
n, err := strconv.ParseUint("12345", 10, 64)
if err != nil {
fmt.Println(err)
}
fmt.Println(n) // 12345
// 음수는 에러
_, err = strconv.ParseUint("-123", 10, 64)
if err != nil {
fmt.Println(err) // strconv.ParseUint: parsing "-123": invalid syntax
}
// 포맷팅
s := strconv.FormatUint(255, 16)
fmt.Println(s) // ff
}
진법 변환
1. 2진법
func main() {
num := 42
// 10진수 → 2진수
binary := strconv.FormatInt(int64(num), 2)
fmt.Printf("%d = %s (binary)\n", num, binary)
// 42 = 101010 (binary)
// 2진수 → 10진수
n, _ := strconv.ParseInt("101010", 2, 64)
fmt.Println(n) // 42
}
2. 8진법
func main() {
num := 64
// 10진수 → 8진수
octal := strconv.FormatInt(int64(num), 8)
fmt.Printf("%d = %s (octal)\n", num, octal)
// 64 = 100 (octal)
// 8진수 → 10진수
n, _ := strconv.ParseInt("100", 8, 64)
fmt.Println(n) // 64
// 0o 접두사 인식
n, _ = strconv.ParseInt("0o100", 0, 64)
fmt.Println(n) // 64
}
3. 16진법
func main() {
num := 255
// 10진수 → 16진수
hex := strconv.FormatInt(int64(num), 16)
fmt.Printf("%d = %s (hex)\n", num, hex)
// 255 = ff (hex)
// 16진수 → 10진수
n, _ := strconv.ParseInt("ff", 16, 64)
fmt.Println(n) // 255
// 대소문자 모두 인식
n, _ = strconv.ParseInt("FF", 16, 64)
fmt.Println(n) // 255
// 0x 접두사 인식
n, _ = strconv.ParseInt("0xFF", 0, 64)
fmt.Println(n) // 255
}
4. 36진법
func main() {
num := 1234
// 10진수 → 36진법 (0-9, a-z)
base36 := strconv.FormatInt(int64(num), 36)
fmt.Printf("%d = %s (base36)\n", num, base36)
// 1234 = ya (base36)
// 36진법 → 10진수
n, _ := strconv.ParseInt("ya", 36, 64)
fmt.Println(n) // 1234
}
실수 변환
1. ParseFloat - 실수 파싱
func main() {
// ParseFloat(s string, bitSize int) (float64, error)
// 기본 실수
f, err := strconv.ParseFloat("3.14", 64)
if err != nil {
fmt.Println(err)
}
fmt.Println(f) // 3.14
// 과학적 표기법
f, _ = strconv.ParseFloat("1.23e-4", 64)
fmt.Println(f) // 0.000123
// 음수
f, _ = strconv.ParseFloat("-2.71", 64)
fmt.Println(f) // -2.71
// 특수 값
f, _ = strconv.ParseFloat("Inf", 64)
fmt.Println(f) // +Inf
f, _ = strconv.ParseFloat("NaN", 64)
fmt.Println(f) // NaN
}
2. FormatFloat - 실수 포맷팅
func main() {
// FormatFloat(f float64, fmt byte, prec int, bitSize int) string
num := 3.14159265359
// 'f': 고정 소수점
s := strconv.FormatFloat(num, 'f', 2, 64)
fmt.Println(s) // 3.14
s = strconv.FormatFloat(num, 'f', -1, 64)
fmt.Println(s) // 3.14159265359
// 'e': 과학적 표기법
s = strconv.FormatFloat(num, 'e', 2, 64)
fmt.Println(s) // 3.14e+00
// 'g': 자동 선택 (간결한 표현)
s = strconv.FormatFloat(num, 'g', 5, 64)
fmt.Println(s) // 3.1416
// 'E': 대문자 E
s = strconv.FormatFloat(num, 'E', 2, 64)
fmt.Println(s) // 3.14E+00
}
3. 포맷 옵션 비교
func main() {
num := 1234.56789
fmt.Println("'f':", strconv.FormatFloat(num, 'f', 2, 64)) // 1234.57
fmt.Println("'e':", strconv.FormatFloat(num, 'e', 2, 64)) // 1.23e+03
fmt.Println("'E':", strconv.FormatFloat(num, 'E', 2, 64)) // 1.23E+03
fmt.Println("'g':", strconv.FormatFloat(num, 'g', 5, 64)) // 1234.6
fmt.Println("'G':", strconv.FormatFloat(num, 'G', 5, 64)) // 1234.6
smallNum := 0.00012345
fmt.Println("'f':", strconv.FormatFloat(smallNum, 'f', 5, 64)) // 0.00012
fmt.Println("'e':", strconv.FormatFloat(smallNum, 'e', 2, 64)) // 1.23e-04
fmt.Println("'g':", strconv.FormatFloat(smallNum, 'g', 3, 64)) // 0.000123
}
Bool 변환
1. ParseBool/FormatBool
func main() {
// FormatBool
s := strconv.FormatBool(true)
fmt.Println(s) // true
s = strconv.FormatBool(false)
fmt.Println(s) // false
// ParseBool - 다양한 형식 지원
b, _ := strconv.ParseBool("true")
fmt.Println(b) // true
b, _ = strconv.ParseBool("1")
fmt.Println(b) // true
b, _ = strconv.ParseBool("t")
fmt.Println(b) // true
b, _ = strconv.ParseBool("T")
fmt.Println(b) // true
b, _ = strconv.ParseBool("TRUE")
fmt.Println(b) // true
b, _ = strconv.ParseBool("false")
fmt.Println(b) // false
b, _ = strconv.ParseBool("0")
fmt.Println(b) // false
b, _ = strconv.ParseBool("f")
fmt.Println(b) // false
// 잘못된 값
_, err := strconv.ParseBool("yes")
if err != nil {
fmt.Println(err) // strconv.ParseBool: parsing "yes": invalid syntax
}
}
Quote/Unquote
1. Quote - 문자열 이스케이프
func main() {
// 기본 문자열
s := strconv.Quote("hello")
fmt.Println(s) // "hello"
// 특수 문자 포함
s = strconv.Quote("hello\nworld")
fmt.Println(s) // "hello\nworld"
// 탭, 따옴표
s = strconv.Quote("tab\there\t\"quoted\"")
fmt.Println(s) // "tab\there\t\"quoted\""
// 유니코드
s = strconv.Quote("안녕")
fmt.Println(s) // "안녕"
}
2. QuoteToASCII - ASCII만 사용
func main() {
// 비ASCII 문자를 \uXXXX로 변환
s := strconv.QuoteToASCII("안녕")
fmt.Println(s) // "\uc548\ub155"
s = strconv.QuoteToASCII("Hello 세계")
fmt.Println(s) // "Hello \uc138\uacc4"
}
3. Unquote - 이스케이프 해제
func main() {
// Quote된 문자열 원래대로
s, err := strconv.Unquote(`"hello\nworld"`)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%q\n", s) // "hello\nworld"
fmt.Println(s)
// hello
// world
// 유니코드
s, _ = strconv.Unquote(`"\uc548\ub155"`)
fmt.Println(s) // 안녕
// 잘못된 형식
_, err = strconv.Unquote("hello")
if err != nil {
fmt.Println(err) // invalid syntax
}
}
4. QuoteRune/QuoteRuneToASCII
func main() {
// 룬(문자) Quote
s := strconv.QuoteRune('A')
fmt.Println(s) // 'A'
s = strconv.QuoteRune('\n')
fmt.Println(s) // '\n'
// 한글
s = strconv.QuoteRune('가')
fmt.Println(s) // '가'
// ASCII로
s = strconv.QuoteRuneToASCII('가')
fmt.Println(s) // '\uac00'
}
Append 함수
1. AppendInt - 버퍼에 직접 추가
func main() {
// 기존 버퍼에 추가 (메모리 할당 최소화)
buf := make([]byte, 0, 100)
buf = strconv.AppendInt(buf, 123, 10)
fmt.Println(string(buf)) // 123
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, 456, 10)
fmt.Println(string(buf)) // 123 456
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, 255, 16)
fmt.Println(string(buf)) // 123 456 ff
}
2. AppendFloat - 실수 추가
func main() {
buf := make([]byte, 0, 100)
buf = strconv.AppendFloat(buf, 3.14, 'f', 2, 64)
fmt.Println(string(buf)) // 3.14
buf = append(buf, ' ')
buf = strconv.AppendFloat(buf, 2.71, 'e', 2, 64)
fmt.Println(string(buf)) // 3.14 2.71e+00
}
3. AppendBool/AppendQuote
func main() {
buf := make([]byte, 0, 100)
buf = strconv.AppendBool(buf, true)
buf = append(buf, ' ')
buf = strconv.AppendBool(buf, false)
fmt.Println(string(buf)) // true false
buf = buf[:0]
buf = strconv.AppendQuote(buf, "hello")
fmt.Println(string(buf)) // "hello"
}
실전 예제
1. 설정 파일 파싱
import (
"bufio"
"os"
"strings"
)
type Config struct {
Port int
EnableSSL bool
Timeout float64
MaxClients uint64
}
func ParseConfig(filename string) (*Config, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
cfg := &Config{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "port":
if port, err := strconv.Atoi(value); err == nil {
cfg.Port = port
}
case "enable_ssl":
if ssl, err := strconv.ParseBool(value); err == nil {
cfg.EnableSSL = ssl
}
case "timeout":
if timeout, err := strconv.ParseFloat(value, 64); err == nil {
cfg.Timeout = timeout
}
case "max_clients":
if max, err := strconv.ParseUint(value, 10, 64); err == nil {
cfg.MaxClients = max
}
}
}
return cfg, scanner.Err()
}
func main() {
// config.txt:
// port=8080
// enable_ssl=true
// timeout=30.5
// max_clients=1000
cfg, err := ParseConfig("config.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Port: %d\n", cfg.Port)
fmt.Printf("SSL: %v\n", cfg.EnableSSL)
fmt.Printf("Timeout: %.1f\n", cfg.Timeout)
fmt.Printf("Max Clients: %d\n", cfg.MaxClients)
}
2. CSV 파서
import (
"encoding/csv"
"io"
)
type Person struct {
Name string
Age int
GPA float64
}
func ParseCSV(r io.Reader) ([]Person, error) {
csvReader := csv.NewReader(r)
// 헤더 스킵
if _, err := csvReader.Read(); err != nil {
return nil, err
}
var people []Person
for {
record, err := csvReader.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if len(record) < 3 {
continue
}
age, err := strconv.Atoi(record[1])
if err != nil {
continue
}
gpa, err := strconv.ParseFloat(record[2], 64)
if err != nil {
continue
}
people = append(people, Person{
Name: record[0],
Age: age,
GPA: gpa,
})
}
return people, nil
}
func main() {
data := `Name,Age,GPA
Alice,25,3.8
Bob,22,3.5
Charlie,24,3.9`
people, err := ParseCSV(strings.NewReader(data))
if err != nil {
fmt.Println(err)
return
}
for _, p := range people {
fmt.Printf("%s: %d세, GPA %.1f\n", p.Name, p.Age, p.GPA)
}
}
3. 진법 변환기
type BaseConverter struct{}
func (bc BaseConverter) Convert(num string, fromBase, toBase int) (string, error) {
// 문자열을 10진수로 변환
decimal, err := strconv.ParseInt(num, fromBase, 64)
if err != nil {
return "", fmt.Errorf("변환 실패: %w", err)
}
// 10진수를 목표 진법으로 변환
result := strconv.FormatInt(decimal, toBase)
return result, nil
}
func main() {
bc := BaseConverter{}
// 2진수 → 16진수
result, _ := bc.Convert("11111111", 2, 16)
fmt.Printf("11111111 (2진수) = %s (16진수)\n", result) // ff
// 16진수 → 10진수
result, _ = bc.Convert("ff", 16, 10)
fmt.Printf("ff (16진수) = %s (10진수)\n", result) // 255
// 10진수 → 2진수
result, _ = bc.Convert("42", 10, 2)
fmt.Printf("42 (10진수) = %s (2진수)\n", result) // 101010
}
4. 파일 크기 포맷터
func FormatFileSize(bytes int64) string {
const unit = 1024
if bytes < unit {
return strconv.FormatInt(bytes, 10) + " B"
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
units := []string{"KB", "MB", "GB", "TB", "PB"}
value := float64(bytes) / float64(div)
return strconv.FormatFloat(value, 'f', 1, 64) + " " + units[exp]
}
func main() {
fmt.Println(FormatFileSize(500)) // 500 B
fmt.Println(FormatFileSize(1024)) // 1.0 KB
fmt.Println(FormatFileSize(1536)) // 1.5 KB
fmt.Println(FormatFileSize(1048576)) // 1.0 MB
fmt.Println(FormatFileSize(1073741824)) // 1.0 GB
fmt.Println(FormatFileSize(5368709120)) // 5.0 GB
}
5. URL 쿼리 파라미터 파싱
import "net/url"
type QueryParams struct {
Page int
PageSize int
SortBy string
Desc bool
}
func ParseQuery(query string) (*QueryParams, error) {
values, err := url.ParseQuery(query)
if err != nil {
return nil, err
}
params := &QueryParams{
Page: 1,
PageSize: 10,
SortBy: "id",
Desc: false,
}
if page := values.Get("page"); page != "" {
if p, err := strconv.Atoi(page); err == nil && p > 0 {
params.Page = p
}
}
if size := values.Get("page_size"); size != "" {
if s, err := strconv.Atoi(size); err == nil && s > 0 {
params.PageSize = s
}
}
if sort := values.Get("sort_by"); sort != "" {
params.SortBy = sort
}
if desc := values.Get("desc"); desc != "" {
if d, err := strconv.ParseBool(desc); err == nil {
params.Desc = d
}
}
return params, nil
}
func main() {
query := "page=2&page_size=20&sort_by=name&desc=true"
params, err := ParseQuery(query)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Page: %d\n", params.Page)
fmt.Printf("PageSize: %d\n", params.PageSize)
fmt.Printf("SortBy: %s\n", params.SortBy)
fmt.Printf("Desc: %v\n", params.Desc)
}
6. JSON 빌더 (간단 버전)
import "bytes"
type JSONBuilder struct {
buf bytes.Buffer
}
func (jb *JSONBuilder) StartObject() {
jb.buf.WriteByte('{')
}
func (jb *JSONBuilder) EndObject() {
if jb.buf.Len() > 0 && jb.buf.Bytes()[jb.buf.Len()-1] == ',' {
jb.buf.Truncate(jb.buf.Len() - 1)
}
jb.buf.WriteByte('}')
}
func (jb *JSONBuilder) AddString(key, value string) {
jb.buf.WriteString(strconv.Quote(key))
jb.buf.WriteByte(':')
jb.buf.WriteString(strconv.Quote(value))
jb.buf.WriteByte(',')
}
func (jb *JSONBuilder) AddInt(key string, value int) {
jb.buf.WriteString(strconv.Quote(key))
jb.buf.WriteByte(':')
jb.buf.WriteString(strconv.Itoa(value))
jb.buf.WriteByte(',')
}
func (jb *JSONBuilder) AddFloat(key string, value float64) {
jb.buf.WriteString(strconv.Quote(key))
jb.buf.WriteByte(':')
jb.buf.WriteString(strconv.FormatFloat(value, 'f', 2, 64))
jb.buf.WriteByte(',')
}
func (jb *JSONBuilder) AddBool(key string, value bool) {
jb.buf.WriteString(strconv.Quote(key))
jb.buf.WriteByte(':')
jb.buf.WriteString(strconv.FormatBool(value))
jb.buf.WriteByte(',')
}
func (jb *JSONBuilder) String() string {
return jb.buf.String()
}
func main() {
jb := &JSONBuilder{}
jb.StartObject()
jb.AddString("name", "Alice")
jb.AddInt("age", 30)
jb.AddFloat("score", 95.5)
jb.AddBool("active", true)
jb.EndObject()
fmt.Println(jb.String())
// {"name":"Alice","age":30,"score":95.50,"active":true}
}
7. 로그 레벨 파서
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
)
func (l LogLevel) String() string {
switch l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
default:
return "UNKNOWN"
}
}
func ParseLogLevel(s string) (LogLevel, error) {
switch strings.ToUpper(s) {
case "DEBUG", "0":
return DEBUG, nil
case "INFO", "1":
return INFO, nil
case "WARN", "2":
return WARN, nil
case "ERROR", "3":
return ERROR, nil
default:
// 숫자로 시도
if level, err := strconv.Atoi(s); err == nil {
if level >= 0 && level <= 3 {
return LogLevel(level), nil
}
}
return 0, fmt.Errorf("invalid log level: %s", s)
}
}
func main() {
levels := []string{"DEBUG", "1", "warn", "3", "unknown"}
for _, s := range levels {
if level, err := ParseLogLevel(s); err == nil {
fmt.Printf("%s → %s\n", s, level)
} else {
fmt.Printf("%s → %v\n", s, err)
}
}
// DEBUG → DEBUG
// 1 → INFO
// warn → WARN
// 3 → ERROR
// unknown → invalid log level: unknown
}
8. 성능 최적화 버퍼
import (
"bytes"
"time"
)
type LogBuffer struct {
buf bytes.Buffer
}
func (lb *LogBuffer) Log(level string, msg string) {
// Append 함수로 메모리 할당 최소화
lb.buf.WriteByte('[')
lb.buf = *bytes.NewBuffer(
strconv.AppendInt(lb.buf.Bytes(), time.Now().Unix(), 10),
)
lb.buf.WriteString("] [")
lb.buf.WriteString(level)
lb.buf.WriteString("] ")
lb.buf.WriteString(msg)
lb.buf.WriteByte('\n')
}
func (lb *LogBuffer) String() string {
return lb.buf.String()
}
func (lb *LogBuffer) Clear() {
lb.buf.Reset()
}
func main() {
logger := &LogBuffer{}
logger.Log("INFO", "Application started")
logger.Log("DEBUG", "Database connected")
logger.Log("ERROR", "Failed to load config")
fmt.Print(logger.String())
// [1234567890] [INFO] Application started
// [1234567890] [DEBUG] Database connected
// [1234567890] [ERROR] Failed to load config
}
일반적인 실수
1. 에러 무시
// ❌ 나쁜 예 (에러 무시)
func main() {
n, _ := strconv.Atoi("abc")
fmt.Println(n + 10) // 0 + 10 = 10 (잘못된 결과)
}
// ✅ 좋은 예 (에러 처리)
func main() {
n, err := strconv.Atoi("abc")
if err != nil {
fmt.Println("변환 실패:", err)
return
}
fmt.Println(n + 10)
}
2. 범위 초과 무시
// ❌ 나쁜 예 (범위 체크 안 함)
func main() {
s := "2147483648" // int32 범위 초과
n, _ := strconv.ParseInt(s, 10, 32)
fmt.Println(n) // 에러 무시
}
// ✅ 좋은 예 (범위 확인)
func main() {
s := "2147483648"
n, err := strconv.ParseInt(s, 10, 32)
if err != nil {
fmt.Println("범위 초과:", err)
return
}
fmt.Println(n)
}
3. 진법 혼동
// ❌ 나쁜 예 (진법 지정 안 함)
func main() {
// "010"을 10진수로 해석
n, _ := strconv.Atoi("010")
fmt.Println(n) // 10 (8진수 아님!)
}
// ✅ 좋은 예 (진법 명시)
func main() {
// 8진수 파싱
n, _ := strconv.ParseInt("010", 8, 64)
fmt.Println(n) // 8
// 자동 감지
n, _ = strconv.ParseInt("0o10", 0, 64)
fmt.Println(n) // 8
}
4. FormatFloat 정밀도 오해
// ❌ 나쁜 예 (정밀도 의미 오해)
func main() {
f := 3.14159
// prec는 전체 자릿수가 아님
s := strconv.FormatFloat(f, 'f', 10, 64)
fmt.Println(s) // 3.1415900000 (소수점 이하 10자리)
}
// ✅ 좋은 예 (정확한 이해)
func main() {
f := 3.14159
// 'f': prec는 소수점 이하 자릿수
s := strconv.FormatFloat(f, 'f', 2, 64)
fmt.Println(s) // 3.14
// 'g': prec는 유효 숫자
s = strconv.FormatFloat(f, 'g', 3, 64)
fmt.Println(s) // 3.14
}
5. Quote/Unquote 짝 안 맞음
// ❌ 나쁜 예 (Quote 없이 Unquote)
func main() {
s := "hello\nworld"
unquoted, err := strconv.Unquote(s)
if err != nil {
fmt.Println(err) // invalid syntax
}
}
// ✅ 좋은 예 (Quote 후 Unquote)
func main() {
original := "hello\nworld"
quoted := strconv.Quote(original)
unquoted, err := strconv.Unquote(quoted)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(original == unquoted) // true
}
6. bitSize 혼동
// ❌ 나쁜 예 (bitSize 의미 오해)
func main() {
// bitSize는 결과 타입 크기 (32비트 int로 변환하려고)
n, _ := strconv.ParseInt("100", 10, 32)
// 하지만 결과는 항상 int64
var i32 int32 = int32(n) // 명시적 변환 필요
}
// ✅ 좋은 예 (올바른 사용)
func main() {
// bitSize는 범위 검증용
n, err := strconv.ParseInt("2147483648", 10, 32)
if err != nil {
fmt.Println("32비트 범위 초과:", err)
return
}
i32 := int32(n) // 안전하게 변환
fmt.Println(i32)
}
7. Append 함수 반환값 무시
// ❌ 나쁜 예 (반환값 무시)
func main() {
buf := make([]byte, 0, 10)
strconv.AppendInt(buf, 123, 10) // 반환값 사용 안 함
fmt.Println(string(buf)) // "" (빈 문자열)
}
// ✅ 좋은 예 (반환값 사용)
func main() {
buf := make([]byte, 0, 10)
buf = strconv.AppendInt(buf, 123, 10) // 반환값 할당
fmt.Println(string(buf)) // "123"
}
베스트 프랙티스
1. 에러 처리
// ✅ 명확한 에러 메시지
func ParsePort(s string) (int, error) {
port, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("포트 번호 변환 실패: %w", err)
}
if port < 1 || port > 65535 {
return 0, fmt.Errorf("포트 번호 범위 오류: %d", port)
}
return port, nil
}
2. 기본값 제공
// ✅ 변환 실패 시 기본값
func ParseIntWithDefault(s string, defaultValue int) int {
if n, err := strconv.Atoi(s); err == nil {
return n
}
return defaultValue
}
func main() {
port := ParseIntWithDefault(os.Getenv("PORT"), 8080)
fmt.Println("Port:", port)
}
3. 타입별 함수 사용
// ✅ 적절한 함수 선택
func main() {
// 간단한 int 변환: Atoi
n, _ := strconv.Atoi("123")
// 진법 지정 필요: ParseInt
hex, _ := strconv.ParseInt("FF", 16, 64)
// 부호 없는 정수: ParseUint
unsigned, _ := strconv.ParseUint("255", 10, 64)
// 실수: ParseFloat
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(n, hex, unsigned, f)
}
4. Append 함수로 성능 최적화
// ✅ 여러 값 연결 시 Append 사용
func FormatRecord(id int, name string, score float64) string {
buf := make([]byte, 0, 100)
buf = strconv.AppendInt(buf, int64(id), 10)
buf = append(buf, ',')
buf = strconv.AppendQuote(buf, name)
buf = append(buf, ',')
buf = strconv.AppendFloat(buf, score, 'f', 2, 64)
return string(buf)
}
func main() {
fmt.Println(FormatRecord(1, "Alice", 95.5))
// 1,"Alice",95.50
}
5. 검증 함수
// ✅ 변환 전 검증
func IsValidInt(s string) bool {
_, err := strconv.Atoi(s)
return err == nil
}
func IsValidFloat(s string) bool {
_, err := strconv.ParseFloat(s, 64)
return err == nil
}
func main() {
inputs := []string{"123", "abc", "3.14"}
for _, input := range inputs {
fmt.Printf("%s: int=%v, float=%v\n",
input,
IsValidInt(input),
IsValidFloat(input))
}
}
6. 안전한 타입 변환
// ✅ 범위 체크 포함
func SafeIntToInt32(n int64) (int32, error) {
if n < math.MinInt32 || n > math.MaxInt32 {
return 0, fmt.Errorf("범위 초과: %d", n)
}
return int32(n), nil
}
func ParseToInt32(s string) (int32, error) {
n, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, err
}
return int32(n), nil
}
7. 문서화
// ✅ 명확한 문서화
// ParseDuration converts a string to duration in seconds.
// Supported formats:
// - "30" or "30s" for 30 seconds
// - "5m" for 5 minutes (300 seconds)
// - "2h" for 2 hours (7200 seconds)
// Returns an error if the format is invalid.
func ParseDuration(s string) (int, error) {
s = strings.TrimSpace(s)
if strings.HasSuffix(s, "h") {
hours, err := strconv.Atoi(strings.TrimSuffix(s, "h"))
if err != nil {
return 0, err
}
return hours * 3600, nil
}
if strings.HasSuffix(s, "m") {
minutes, err := strconv.Atoi(strings.TrimSuffix(s, "m"))
if err != nil {
return 0, err
}
return minutes * 60, nil
}
return strconv.Atoi(strings.TrimSuffix(s, "s"))
}
8. 테스트
// ✅ 경계값 테스트
func TestParseInt(t *testing.T) {
tests := []struct {
input string
want int
wantErr bool
}{
{"0", 0, false},
{"123", 123, false},
{"-456", -456, false},
{"abc", 0, true},
{"", 0, true},
{"2147483647", 2147483647, false}, // max int32
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got, err := strconv.Atoi(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("wantErr %v, got %v", tt.wantErr, err)
}
if !tt.wantErr && got != tt.want {
t.Errorf("want %d, got %d", tt.want, got)
}
})
}
}
정리
- 정수: Atoi/Itoa (간편), ParseInt/FormatInt (진법 지정), ParseUint/FormatUint (부호 없음)
- 진법: 2진법(2), 8진법(8), 16진법(16), 최대 36진법, base 0으로 자동 감지
- 실수: ParseFloat/FormatFloat, fmt 옵션(‘f’, ‘e’, ‘g’), prec로 정밀도 제어
- Bool: ParseBool (다양한 형식), FormatBool (true/false)
- Quote: Quote/Unquote (이스케이프), QuoteToASCII (유니코드→ASCII), QuoteRune (문자)
- Append: AppendInt/Float/Bool/Quote로 버퍼에 직접 추가, 메모리 할당 최소화
- 실전: 설정 파싱, CSV 파서, 진법 변환기, 파일 크기 포맷터, URL 쿼리, JSON 빌더, 로그 레벨, 성능 버퍼
- 실수: 에러 무시, 범위 초과, 진법 혼동, 정밀도 오해, Quote/Unquote 짝, bitSize 혼동, Append 반환값
- 베스트: 에러 처리, 기본값, 적절한 함수, Append 활용, 검증, 안전한 변환, 문서화, 테스트