배열과 슬라이스
슬라이스는 Go 언어에서 주로 동적 배열 용도로 많이 사용됩니다. 즉, 크기가 계속 늘어날 때 슬라이스를 사용합니다. 하지만 슬라이스 본래 정의는 배열 일부분을 가리키는 타입으로 배열 포인터라고 볼 수 있습니다. 그래서 슬라이스는 항상 다른 배열의 일부분을 가리키고 있다는 점을 명심해야 합니다.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
슬라이스의 구조체는 위와 같은 필드를 갖고 있습니다.
var array [10]int
var slice []int = array[1:3] // [1, 2, 3]
var slice []int = array[1:3:5] // [1, 2, 3] + 1자리 => Cap(4)
슬라이싱의 첫 번째 요소는 시작 인덱스, 두 번째 요소는 개수, 세 번째 인덱스는 끝 인덱스입니다.
for range
for range는 여러 요소를 가진 타입의 각 요소를 순회하는 구문입니다. 어떤 타입이냐에 따라서 각 요소의 반환값이 달라집니다.
문자열
문자열 타입이 주어지면 각 문자를 순회하며, 첫 번째 반환값은 인덱스, 두 번째 반환값은 문자를 나타내는 rune 입니다.
var str := "hello 월드"
for i, c := range str {
// i는 인덱스, c는 문자값
}
슬라이스
슬라이스 타입일 때는 문자열과 마찬가지로 첫 번째 반환값으로 인덱스가, 두 번째 반환값으로 각 요소가 옵니다.
var slice := []int{ 1, 2, 3, 4, 5 }
for i, v := range slice {
// i는 인덱스, v는 요소값
]
맵
맵 타입일 때는 첫 번째 반환값으로 키가, 두 번째 반환값으로 해당 키의 값이 옵니다.
var m := map[string]int{ "aaa":1, "bbb":2, "ccc":3 }
for k, v := range m {
// k는 키, v는 키에 해당하는 값
}
채널
채널 타입일 때는 채널에서 값이 들어올 때까지 계속 대기합니다. 값이 들어오면 들어온 값을 채널에서 빼내서 반환하고 다시 대기합니다.
채널이 close()로 닫힐 때까지 계속 반복됩니다.
var ch := make(chan int)
for v := range ch {
// 들어온 값을 꺼내어 반환
}
입출력 처리
io 패키지
Go 언어는 io 패키지의 Reader, Writer 인터페이스를 사용해서 모든 입출력을 처리합니다.
type Reader interface {
Read(p []byte) (n int, err error)
}
Reader는 단 하나의 메서드만 정의된 인터페이스로, Read() 메서드는 기본적으로 읽은 데이터를 저장할 수 있는 byte 슬라이스를 생성해서 넣어주면 p 슬라이스 크기만큼 데이터를 읽어서 채워줍니다. 만약 p 크기만큼 데이터를 읽지 못하면 읽을 수 있는 만큼만 읽어서 채웁니다. 총 읽은 크기를 n으로 반환하고, 에러가 발생하면 err을 반환합니다.
type Writer interface {
Write(p []byte) (n int, err error)
}
Writer 또한 단 하나의 메서드만 정의된 인터페이스로, Write() 메서드는 쓰고자 하는 데이터를 p로 넣어주면 쓸 수 있는 만큼 쓰고 쓴 바이트 수를 n으로 반환합니다.
파일 핸들러인 os.File 객체나 네트워크 연결을 다루는 net.Conn 객체 모두 io.Reader, io.Writer 인터페이스를 구현하고 있습니다.
bufio 패키지
io.Reader, io.Writer 인터페이스는 가장 기본적인 메서드만 제공하기 때문에 바로 사용하기에 불편합니다. 그래서 내부에 메모리 버퍼를 가진 bufio의 Reader, Writer, Scanner를 사용해야 편리하게 이용할 수 있습니다.
type Reader
func NewReader(rd io.Reader) *Reader
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
func (b *Reader) ReadRune() (r rune, size int, err error)
func (b *Reader) ReadString(delim byte) (string, error)
bufio.Reader 객체는 내부에 메모리 버퍼를 갖고 있고, NewReader() 함수의 인수로 io.Reader를 넣어서 생성할 수 있습니다.
Reader 객체는 한 줄을 읽는 ReadLine() 메서드, 하나의 문자를 읽는 ReadRune(), 특정 구분자가 나올 때까지 문자열을 읽는 ReadString() 등의 메서드를 제공합니다.
type Scanner
func NewScanner(r io.Reader) *Scanner
func (s *Scanner) Scan() bool
func (s *Scanner) Err() error
func (s *Scanner) Split(split SplitFunc)
func (s *Scanner) Text() string
bufio.Scanner 객체는 일정한 패턴으로 io.Reader 인스턴스에서 값을 읽어올 때 사용합니다.
역시 NewScanner() 함수에 io.Reader를 인수로 사용해서 만들 수 있고, Scan() 메서드로 토큰을 읽고, Text()로 읽어온 토큰을 반환합니다. 토큰이란 패턴에 해당하는 만큼 읽어온 문자열을 의미하는데, 단어 단위로 읽는 경우에는 한 단어를 말하고 줄 단위로 읽을 때는 한 줄을 의미합니다.
Scan()에서 읽기에 실패한 경우 false를 반환하며, 일반적으로 더 읽을 수 없을 때 false를 반환합니다.
type Writer
func NewWriter(w io.Writer) *Writer
func (b *Writer) WriteString(s string) (int, error)
bufio.Writer 객체는 io.Writer 인스턴스에 문자열을 쓸 때 유용하며, 보통 WriterString() 메서드로 문자열을 쓸 때 이용합니다.
func ReadAll(r Reader) ([]byte, error)
ReadAll() 함수는 io.Reader 인스턴스인 r에서 모든 데이터를 읽어서 []byte 타입으로 반환합니다.
fmt.Fprint()
fmt.Fprint() 함수는 io.Writer 인스턴스에 원하는 형태의 문자열을 쓸 때 사용합니다.
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err errr)
Fprint() 시리즈는 기본적으로 Print() 시리즈와 동작이 같습니다. 다른 점은 Print() 시리즈는 표준 출력인 os.Stdout에 쓰는 반면, Fprint() 시리즈는 프로그래머가 어떤 io.Writer에 쓸지 정할 수 있다는 점입니다.
알아두면 유용한 go 명령어
Go 언어에서 제공하는 명령어는 go 뒤에 붙여서 사용하면 됩니다.
cgo
cgo는 Go 언어에서 C 언어 함수 호출을 지원하는 기능입니다. C 언어는 메모리 관리를 프로그래머가 직접해야 하고 모던 언어에서 제공하는 편리한 기능이 부족하기 때문에 새로운 대규모 프로젝트에서 메인 언어로 사용되긴 힘들지만 역사가 깊고 속도가 빠르기 때문에 라이브러리나 임베디드 프로그램에서 여전히 사용되고 있습니다.
cgo를 사용하면 내부적으로 C 코드를 컴파일하고 Go 코드와 연결하기 위해서 gcc와 같은 C 컴파일러가 필요합니다.
go doc
go doc은 패키지 문서를 출력해주는 명령어입니다. Go 에서 제공해주는 표준 패키지 외에도 사용자가 직접 만든 패키지도 가능합니다.
작성 규칙으로는 각 요소(상수, 함수, 구조체 등) 위에 그 이름으로 시작하는 주석을 추가하면 됩니다.
// CharSize 문자의 크기를 의미합니다.
const CharSize = 3
go doc 명령으로 패키지 문서를 볼 수 있지만 텍스트 형태로 출력되다 보니 사용하기 불편한데 Go 언어에서 공식으로 제공하는 godoc 툴을 이용하면 웹 페이지 형태로 문서를 볼 수 있습니다.
Embed
Go 1.16 버전에서 추가된 기능으로 특정 파일들을 실행 파일 바이너리 안에 포함시켜서 파일 읽기 성능을 향상 시키는 기능입니다. 주로 웹 서버에서 파일을 읽을 때 성능을 향상시키는 용도로 사용합니다.
파일들을 실행 파일 내에 포함시키면 프로그램이 실행될 때 포함된 파일 데이터 역시 모두 메모리에 로드되기 때문에 파일 내용을 매우 빠르게 읽어올 수 있지만, 반면에 실행 파일이 포함된 파일의 크기만큼 커져서 더 많은 메로리를 차지하게 됩니다. 그리고 파일 내용일 변경될 때마다 다시 빌드를 해줘야하는 문제점도 있습니다.
'Go' 카테고리의 다른 글
Go (9) - 더 생각해보기 (0) | 2025.04.25 |
---|---|
Go (7) - 테스트와 벤치마크, 웹 서버 (1) | 2025.04.17 |
Go (6) - 고루틴과 동시성 프로그래밍, 채널과 컨텍스트, Generic (0) | 2025.04.17 |
Go (5) - 함수 고급, 자료구조, 에러 핸들링 (0) | 2025.04.13 |
Go (4) - 슬라이스, 메서드, 인터페이스 (0) | 2025.04.13 |