Skip to content

Go 입출력

go
package main

import "fmt"

func main() {
   fmt.Println("Hello 세계!")
}

이 사이트의 첫 입문 사례는 문자열을 출력하는 것이었습니다. 이번 절에서는 Go 에서 입출력을 어떻게 수행하는지 알아보겠습니다.

파일 디스크립터

go
var (
   Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
   Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
   Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

os 패키지 아래에는 세 개의 외부에 공개된 파일 디스크립터가 있으며, 타입은 모두 *os.File입니다. 각각 다음과 같습니다.

  • os.Stdin - 표준 입력
  • os.Stdout - 표준 출력
  • os.Stderr - 표준 오류

Go 에서 입출력은 이들을 벗어나지 않습니다.

출력

Go 에는 여러 가지 출력 방법이 있습니다. 아래는 몇 가지 일반적인 방법입니다.

stdout

표준 출력은 본질적으로 파일이므로 문자열을 표준 출력에 직접 쓸 수 있습니다.

go
package main

import "os"

func main() {
  os.Stdout.WriteString("hello world!")
}

print

Go 에는 두 개의 내장 함수 print, println이 있으며, 이들은 매개변수를 표준 오류로 출력합니다. 디버깅용으로만 사용되며 일반적으로 권장되지 않습니다.

go
package main

func main() {
  print("hello world!\n")
  println("hello world")
}

fmt

가장 일반적인 용도는 fmt 패키지를 사용하는 것입니다. fmt.Println 함수를 제공하며, 이 함수는 기본적으로 매개변수를 표준 출력으로 출력합니다.

go
package main

import "fmt"

func main() {
  fmt.Println("hello world!")
}

이 함수의 매개변수는 임의의 타입을 지원하며, 타입이 String 인터페이스를 구현하면 String 메서드를 호출하여 문자열 표현을 가져옵니다. 따라서 출력 내용의 가독성이 높아 대부분의 상황에 적합합니다. 다만 내부에서 리플렉션을 사용하므로 성능이 중요한 상황에서는 대량 사용을 권장하지 않습니다.

bufio

bufio 는 버퍼링된 출력 방법을 제공합니다. 데이터를 먼저 메모리에 쓴 후 일정 임계값에 도달하면 지정된 Writer 로 출력합니다. 기본 버퍼 크기는 4KB입니다. 파일 IO, 네트워크 IO 시 이 패키지를 사용하는 것을 권장합니다.

go
func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()
  writer.WriteString("hello world!")
}

fmt 패키지와 결합하여 사용할 수도 있습니다.

go
func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()
  fmt.Fprintln(writer, "hello world!")
}

포맷팅

Go 의 포맷팅 출력 기능은 기본적으로 fmt.Printf 함수에서 제공합니다. C 계열 언어를 배운 적이 있다면 익숙할 것입니다. 아래는 간단한 예시입니다.

go
func main() {
  fmt.Printf("hello world, %s!", "jack")
}

아래는 Go 가 현재 지원하는 모든 포맷팅 동사입니다.

번호포맷팅설명수신 타입
1%%퍼센트 기호 % 출력임의
2%sstring/[] byte 값 출력string,[] byte
3%q문자열 포맷팅, 출력 문자열 양쪽에 큰따옴표 ""string,[] byte
4%d10 진수 정수 값 출력정수
5%f부동소수점 출력부동
6%e과학적 표기법 형태 출력, 복소수에도 사용 가능부동
7%E%e 와 동일부동
8%g상황에 따라 %f 또는 %e 출력, 불필요한 0 제거부동
9%b정수의 2 진수 표현 출력숫자
10%#b2 진수 전체 표현 출력숫자
11%o정수의 8 진수 표현 출력정수
12%#o정수의 전체 8 진수 표현 출력정수
13%x정수의 소문자 16 진수 표현 출력숫자
14%#x정수의 전체 소문자 16 진수 표현 출력숫자
15%X정수의 대문자 16 진수 표현 출력숫자
16%#X정수의 전체 대문자 16 진수 표현 출력숫자
17%v값의 원래 형태 출력, 주로 데이터 구조 출력에 사용임의
18%+v구조체 출력 시 필드명 포함임의
19%#v전체 Go 문법 형식의 값 출력임의
20%t불리언 값 출력불리언
21%T값에 해당하는 Go 언어 타입 값 출력임의
22%cUnicode 코드에 해당하는 문자 출력int32
23%U문자에 해당하는 Unicode 코드 출력rune,byte
24%p포인터가 가리키는 주소 출력포인터

fmt.Sprintf 또는 fmt.Printf 를 사용하여 문자열을 포맷팅하거나 포맷팅된 문자열을 출력합니다. 몇 가지 예시를 보겠습니다.

go
fmt.Printf("%%%s\n", "hello world")

fmt.Printf("%s\n", "hello world")
fmt.Printf("%q\n", "hello world")
fmt.Printf("%d\n", 2<<7-1)

fmt.Printf("%f\n", 1e2)
fmt.Printf("%e\n", 1e2)
fmt.Printf("%E\n", 1e2)
fmt.Printf("%g\n", 1e2)

fmt.Printf("%b\n", 2<<7-1)
fmt.Printf("%#b\n", 2<<7-1)
fmt.Printf("%o\n", 2<<7-1)
fmt.Printf("%#o\n", 2<<7-1)
fmt.Printf("%x\n", 2<<7-1)
fmt.Printf("%#x\n", 2<<7-1)
fmt.Printf("%X\n", 2<<7-1)
fmt.Printf("%#X\n", 2<<7-1)

type person struct {
    name    string
    age     int
    address string
}
fmt.Printf("%v\n", person{"lihua", 22, "beijing"})
fmt.Printf("%+v\n", person{"lihua", 22, "beijing"})
fmt.Printf("%#v\n", person{"lihua", 22, "beijing"})
fmt.Printf("%t\n", true)
fmt.Printf("%T\n", person{})
fmt.Printf("%c%c\n", 20050, 20051)
fmt.Printf("%U\n", '')
fmt.Printf("%p\n", &person{})

다른 진법을 사용할 때는 % 와 포맷팅 동사 사이에 공백을 추가하면 구분자 효과를 얻을 수 있습니다. 예를 들어

go
func main() {
  str := "abcdefg"
  fmt.Printf("%x\n", str)
  fmt.Printf("% x\n", str)
}

이 예시의 출력 결과는 다음과 같습니다.

61626364656667
61 62 63 64 65 66 67

숫자를 사용할 때는 자동으로 0 을 채울 수도 있습니다. 예를 들어

go
fmt.Printf("%09d", 1)
// 000000001

2 진수도 마찬가지입니다.

go
fmt.Printf("%09b", 1<<3)
// 000001000

오류 상황

포맷팅 문자 수 < 매개변수 목록 수

go
fmt.Printf("", "") //%!(EXTRA string=)

포맷팅 문자 수 > 매개변수 목록 수

go
fmt.Printf("%s%s", "") //%!s(MISSING)

타입 불일치

go
fmt.Printf("%s", 1) //%!s(int=1)

포맷팅 동사 누락

go
fmt.Printf("%", 1) // %!(NOVERB)%!(EXTRA int=1)

입력

다음은 일반적인 입력 방법을 소개합니다.

read

파일을 읽는 것과 마찬가지로 입력 내용을 읽을 수 있습니다. 다음과 같습니다.

go
func main() {
  var buf [1024]byte
  n, _ := os.Stdin.Read(buf[:])
  os.Stdout.Write(buf[:n])
}

이렇게 사용하기에는 너무 번거로우므로 일반적으로 권장되지 않습니다.

fmt

fmt 패키지에서 제공하는 몇 가지 함수를 사용할 수 있으며, 사용법은 C 와 비슷합니다.

go
// os.Stdin 에서 읽은 텍스트를 스캔, 공백으로 구분, 줄바꿈도 공백으로 간주
func Scan(a ...any) (n int, err error)

// Scan 과 유사하지만 줄바꿈을 만나면 스캔 중지
func Scanln(a ...any) (n int, err error)

// 포맷팅된 문자열에 따라 스캔
func Scanf(format string, a ...any) (n int, err error)

두 개의 숫자 읽기

go
func main() {
  var a, b int
  fmt.Scanln(&a, &b)
  fmt.Printf("%d + %d = %d\n", a, b, a+b)
}

고정 길이 배열 읽기

go
func main() {
  n := 10
  s := make([]int, n)
  for i := range n {
    fmt.Scan(&s[i])
  }
  fmt.Println(s)
}
1 2 3 4 5 6 7 8 9 10
[1 2 3 4 5 6 7 8 9 10]

bufio

대량의 입력을 읽어야 할 때는 bufio.Reader 를 사용하여 내용을 읽는 것을 권장합니다.

go
func main() {
    reader := bufio.NewReader(os.Stdin)
    var a, b int
    fmt.Fscanln(reader, &a, &b)
    fmt.Printf("%d + %d = %d\n", a, b, a+b)
}

scanner

bufio.Scannerbufio.Reader 와 유사하지만, 줄 단위로 읽습니다.

go
func main() {
  scanner := bufio.NewScanner(os.Stdin)
  for scanner.Scan() {
    line := scanner.Text()
    if line == "exit" {
      break
    }
    fmt.Println("scan", line)
  }
}

결과는 다음과 같습니다.

first line
scan first line
second line
scan second line
third line
scan third line
exit

TIP

입출력 연습을 하고 싶다면 洛谷 에서 간단한 ACM 모드 알고리즘 문제를 몇 풀면 익숙해질 수 있습니다.

Golang by www.golangdev.cn edit