Go 문자열 기본

GolangBeginner
지금 연습하기

소개

이전 레슨에서 Go 의 문자는 UTF-8 을 사용하여 인코딩되고 byte 또는 rune 타입으로 저장된다는 것을 배웠습니다. 이제 문자들의 모음인 문자열에 대해 논의해 보겠습니다. 함께 이 주제를 탐구해 봅시다.

지식 포인트:

  • 문자열이란 무엇인가
  • 문자열 생성
  • 문자열 선언
  • 일반적인 문자열 함수
  • 문자열 요소 접근

문자열이란 무엇인가?

Go 에서 처음 배운 프로그램에서 우리는 문자열 "hello, world"를 출력했습니다.

문자열은 Go 의 기본 데이터 타입으로, 문자열 리터럴 (string literal) 이라고도 합니다. 문자들의 모음으로 이해할 수 있으며, 연속된 메모리 블록을 차지합니다. 이 메모리 블록은 문자, 텍스트, 이모지 등 모든 종류의 데이터를 저장할 수 있습니다.

하지만 다른 언어와 달리, Go 의 문자열은 불변 (immutable) 이며 수정할 수 없습니다. 즉, 문자열이 생성되면 개별 문자를 변경할 수 없습니다. 문자열의 수정된 버전이 필요한 경우, 새로운 문자열을 생성해야 합니다.

문자열 생성하기

문자열은 여러 가지 방법으로 선언할 수 있습니다. 첫 번째 방법을 살펴보겠습니다. string.go라는 새 파일을 생성합니다.

touch ~/project/string.go

다음 코드를 작성합니다.

package main

import "fmt"

func main() {
    // var 키워드를 사용하여 문자열 변수 a 를 생성합니다.
    var a string = "labex"
    a = "labex" // 변수 a 에 "labex"를 할당합니다.

    // 변수 b 를 선언하고 값을 할당합니다.
    var b string = "labs"

    // 타입 선언은 생략할 수 있습니다.
    var c = "Monday"

    // :=를 사용하여 빠른 선언 및 할당을 수행합니다.
    d := "Sunday"
    fmt.Println(a, b, c, d)
}

위 코드는 var 키워드와 := 연산자를 사용하여 문자열을 생성하는 방법을 보여줍니다. var로 변수를 생성할 때 값을 할당하면, 변수 c의 생성에서 볼 수 있듯이 타입 선언을 생략할 수 있습니다. := 연산자는 Go 에서 변수를 선언하고 초기화하는 축약형입니다. 할당된 값에서 변수의 타입을 자동으로 추론합니다. 이를 사용하면 코드를 더 간결하게 만들 수 있습니다.

go run string.go

예상 출력은 다음과 같습니다.

labex labs Monday Sunday

문자열 선언하기

대부분의 경우, 우리는 큰따옴표 ""를 사용하여 문자열을 선언합니다. 큰따옴표의 장점은 이스케이프 시퀀스 (escape sequences) 로 사용할 수 있다는 것입니다. 예를 들어, 아래 프로그램에서 \n 이스케이프 시퀀스를 사용하여 새 줄을 만듭니다.

package main

import "fmt"

func main() {
    x := "linux\nlabex"
    fmt.Println(x)
}
go run string.go

예상 출력은 다음과 같습니다.

linux
labex

다음은 몇 가지 일반적인 이스케이프 시퀀스입니다.

기호 설명
\n 새 줄
\r 캐리지 리턴
\t
\b 백스페이스
\\ 백슬래시
\' 작은따옴표
\" 큰따옴표

텍스트의 원래 형식을 유지하거나 여러 줄을 사용해야 하는 경우, 백틱 (backticks) 을 사용하여 표현할 수 있습니다.

package main

import "fmt"

func main() {
    // "labex"의 ASCII 아트를 출력합니다.
    ascii := `
        ##        #####   #########  ########## ##    #
        ##       ##  ##  ##    ## ##       ##  #
        ##      ##    ## #########  #########    ##
        ##      ########## ##    ## ##        ##
        ##      ##    ## ##    ## ##       ##  #
        ########## ##    ## #########  ########## ##    #`
    fmt.Println(ascii)
}

백틱은 프롬프트, HTML 템플릿 및 출력의 원래 형식을 유지해야 하는 다른 경우에 일반적으로 사용됩니다. 백틱 안의 텍스트는 원시 문자열 리터럴 (raw string literal) 로 처리되므로, 이스케이프 시퀀스가 해석되지 않습니다. 이는 여러 줄의 텍스트와 특수 문자를 이스케이프하지 않고 포함하는 데 편리합니다.

문자열 길이 구하기

이전 레슨에서 우리는 영어 문자 및 일반적인 구두점 기호가 1 바이트를 차지한다는 것을 배웠습니다.

따라서 Go 에서는 len() 함수를 사용하여 문자열의 바이트 길이를 얻을 수 있습니다. 여러 바이트를 차지하는 문자가 없는 경우, len() 함수를 사용하여 문자열의 길이를 정확하게 측정할 수 있습니다.

문자열에 여러 바이트를 차지하는 문자가 포함된 경우, utf8.RuneCountInString 함수를 사용하여 문자열의 실제 문자 수를 얻을 수 있습니다.

예제를 살펴보겠습니다. string.go 파일에 다음 코드를 작성합니다.

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    // var 및 :=를 사용하여 두 개의 빈 문자열을 선언합니다.
    var a string
    b := ""

    c := "labex"
    d := "abc"


    // 바이트 길이 출력
    fmt.Printf("a의 값은 %s이고, a의 바이트 길이는: %d\n", a, len(a))
    fmt.Printf("b의 값은 %s이고, b의 바이트 길이는: %d\n", b, len(b))
    fmt.Printf("c의 값은 %s이고, c의 바이트 길이는: %d\n", c, len(c))
 fmt.Printf("d의 값은 %s이고, d의 바이트 길이는: %d\n", d, len(d))


    // 문자열 길이 출력
    fmt.Printf("c 의 길이는: %d\n", utf8.RuneCountInString(c))
 fmt.Printf("d 의 길이는: %d\n", utf8.RuneCountInString(d))
}
go run string.go

예상 출력은 다음과 같습니다.

The value of a is , the byte length of a is: 0
The value of b is , the byte length of b is: 0
The value of c is labex, the byte length of c is: 5
The value of d is abc, the byte length of d is: 3
The length of c is: 5
The length of d is: 3

프로그램에서 먼저 두 개의 빈 문자열과 문자열 labexabc를 선언했습니다. 단일 바이트 문자만 포함하므로 바이트 길이와 실제 길이가 동일하다는 것을 알 수 있습니다.

문자열 요소 접근하기

문자열은 본질적으로 바이트 시퀀스이므로, 인덱스를 사용하여 문자열의 개별 바이트 또는 문자에 접근할 수 있습니다. Go 에서 문자열 인덱싱은 배열과 마찬가지로 0 부터 시작합니다.

그러나 인덱스를 사용하여 문자열 요소에 접근하면 문자 (rune) 가 아닌 byte를 반환한다는 점을 기억하는 것이 중요합니다. 문자열에 멀티 바이트 문자가 포함된 경우, 인덱스를 사용할 때 완전한 문자를 얻지 못할 수 있으므로 주의해야 합니다. 개별 문자 (rune) 를 안전하게 얻으려면, UTF-8 인코딩을 제대로 처리하는 for...range 루프를 사용하여 문자열을 반복 처리할 수 있습니다.

string.go 파일에 다음 코드를 추가해 보겠습니다.

package main

import (
 "fmt"
 "unicode/utf8"
)

func main() {
 str := "Hello, world!"

 // 인덱스로 바이트 접근
 fmt.Printf("인덱스 0 의 바이트: %c\n", str[0]) // 출력: H
 fmt.Printf("인덱스 7 의 바이트: %c\n", str[7]) // 출력: w

    // for...range 를 사용하여 안전하게 rune 얻기
    fmt.Println("룬 반복 처리:")
    for index, char := range str {
        fmt.Printf("인덱스: %d, 문자: %c\n", index, char)
    }

 // 룬 수 얻기
 fmt.Printf("문자열의 룬 (문자) 수: %d\n", utf8.RuneCountInString(str))

}

이제 프로그램을 실행합니다.

go run string.go

출력은 다음과 같습니다.

Byte at index 0: H
Byte at index 7: w
Iterating through runes:
Index: 0, Char: H
Index: 1, Char: e
Index: 2, Char: l
Index: 3, Char: l
Index: 4, Char: o
Index: 5, Char: ,
Index: 6, Char:
Index: 7, Char: w
Index: 8, Char: o
Index: 9, Char: r
Index: 10, Char: l
Index: 11, Char: d
Index: 12, Char: !
Number of runes (characters) in the string: 13

이 출력에서 다음을 확인할 수 있습니다.

  1. 특정 인덱스로 문자열에 접근하면 바이트가 반환됩니다.
  2. for...range 루프를 사용하면 룬을 올바르게 반복 처리할 수 있습니다.

이 예제는 바이트와 룬의 주요 차이점과 Go 문자열에서 문자를 처리할 때 적절한 메서드를 사용하는 것의 중요성을 강조합니다.

문자열과 정수 변환

strconv 패키지의 함수를 사용하여 문자열과 정수 간에 변환할 수 있습니다.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 문자열 a 와 정수 b 선언
    a, b := "233", 223

    // Atoi 를 사용하여 문자열을 정수로 변환
    c, _ := strconv.Atoi(a)

    // Sprintf 및 Itoa 함수를 사용하여
    // 정수를 문자열로 변환
    d1 := fmt.Sprintf("%d", b)
    d2 := strconv.Itoa(b)

    fmt.Printf("a 의 타입: %T\n", a)   // string
    fmt.Printf("b 의 타입: %T\n", b)   // int
    fmt.Printf("c 의 타입: %T\n", c)   // int
    fmt.Printf("d1 의 타입: %T\n", d1) // string
    fmt.Printf("d2 의 타입: %T\n", d2) // string
}
go run string.go

예상 출력은 다음과 같습니다.

The type of a: string
The type of b: int
The type of c: int
The type of d1: string
The type of d2: string

프로그램에서 fmt 패키지의 Sprintf() 함수를 사용하며, 형식은 다음과 같습니다.

func Sprintf(format string, a ...interface{}) string

format은 이스케이프 시퀀스가 있는 문자열이고, a는 이스케이프 시퀀스에 값을 제공하는 상수 또는 변수이며, ...a와 동일한 유형의 변수가 여러 개 있을 수 있음을 의미합니다. 함수 뒤의 문자열은 Sprintf가 문자열을 반환한다는 것을 나타냅니다. 다음은 이 함수를 사용하는 예입니다.

a = fmt.Sprintf("%d+%d=%d", 1, 2, 3)
fmt.Println(a) // 1+2=3

이 코드 조각에서 format은 세 개의 정수 변수 1, 2, 및 3 과 함께 전달됩니다. format%d 정수 이스케이프 문자는 정수 값으로 대체되고, Sprintf 함수는 대체 후 결과인 1+2=3을 반환합니다.

또한 strconv.Atoi()를 사용하여 문자열을 정수로 변환할 때, 함수는 변환된 정수 val과 오류 코드 err의 두 값을 반환한다는 점에 유의하십시오. Go 에서는 변수를 선언하면 사용해야 하므로, 밑줄 _를 사용하여 err 변수를 무시할 수 있습니다.

strconv.Atoi()가 올바르게 변환되면 errnil을 반환합니다. 변환 중에 오류가 발생하면 err는 오류 메시지를 반환하고, val의 값은 0 이 됩니다. 문자열 a의 값을 변경하고 밑줄을 일반 변수로 바꿔서 직접 시도해 볼 수 있습니다. 이는 Go 프로그래밍의 중요한 부분인 오류 처리를 위한 좋은 연습입니다.

문자열 연결 (Concatenation)

두 개 이상의 문자열을 연결하는 가장 간단한 방법은 + 연산자를 사용하는 것입니다. 또한 fmt.Sprintf() 함수를 사용하여 문자열을 연결할 수도 있습니다. 예제를 살펴보겠습니다.

package main

import (
    "fmt"
)

func main() {
    a, b := "lab", "ex"
    // 가장 간단한 방법인 +를 사용하여 연결
    c1 := a + b
    // Sprintf 함수를 사용하여 연결
    c2 := fmt.Sprintf("%s%s", a, b)
    fmt.Println(a, b, c1, c2) // lab ex labex labex
}
go run string.go

예상 출력은 다음과 같습니다.

lab ex labex labex

프로그램에서 문자열을 연결하고 결과를 출력하기 위해 fmt 패키지의 Sprintf() 함수도 사용했습니다. 두 방법 모두 문자열을 연결하는 일반적인 방법이며, 둘 중 어떤 것을 선택할지는 가독성과 개인적인 선호도에 따라 달라지는 경우가 많습니다.

문자열 앞뒤 공백 제거

strings.TrimSpace 함수를 사용하여 문자열의 앞뒤 공백을 제거할 수 있습니다. 이 함수는 문자열을 입력으로 받아 앞뒤 공백이 제거된 문자열을 반환합니다. 형식은 다음과 같습니다.

func TrimSpace(s string) string

다음은 예시입니다.

package main

import (
    "fmt"
    "strings"
)

func main() {
    a := " \t \n  labex \n \t labs"
    fmt.Println(strings.TrimSpace(a))
}
go run string.go

예상 출력은 다음과 같습니다.

labex
         labs

strings.TrimSpace()는 문자열의 시작과 끝 부분의 공백만 제거하며, 문자열 내부의 공백은 그대로 유지된다는 점에 유의하십시오.

요약

이번 레슨에서 배운 내용을 요약하면 다음과 같습니다.

  • 문자열과 문자의 관계
  • 큰따옴표와 백틱을 사용하여 문자열을 선언하는 두 가지 방법.
  • 인덱스 (바이트 접근) 와 for...range(룬 접근) 를 사용하여 문자열의 요소에 접근하기.
  • len() (바이트 길이) 및 utf8.RuneCountInString (문자/룬 길이) 을 사용하여 문자열의 길이 얻기.
  • strconv.Atoi()strconv.Itoa()를 사용하여 문자열과 정수 변환하기.
  • + 연산자와 fmt.Sprintf()를 사용하여 문자열 연결하기.
  • strings.TrimSpace()를 사용하여 문자열의 앞뒤 공백 제거하기.

이번 레슨에서는 일상생활에서 사용하는 문자열에 대해 설명했습니다. 문자열과 문자의 관계에 대해 배우고, 문자열 생성 및 선언을 익혔으며, 일반적인 문자열 함수에 대한 지식을 얻었습니다. 또한, 특히 멀티 바이트 문자를 처리할 때 문자열의 개별 문자에 안전하게 접근하는 방법을 배웠으며, 이제 몇 가지 중요한 문자열 조작 방법을 이해하게 되었습니다. 이는 Go 에서 문자열 데이터를 다루는 강력한 기반을 제공합니다.