이 글은 Robert Pike 가 쓴 블로그글을 발로 번역한 글입니다. 


원문은 https://blog.golang.org/strings 이고 이글은 그 중 앞뿐입니다. 


다음 주에 그 다음 부분을 올리겠습니다. 


Go 언어에서 문자열, 바이트열 ,문자 및 특수문자


2013년 8월 23 일 


### 개요 

--------------------


이전 블로그에서, 슬라이스의 숨겨졌던 내부 구현에 대하여 설명하기 위해 그림이 포함된 많은 예를 들었고, 


슬라이스를 Go 언어에서 어떻게 다루어야 하는가에 대한 포스팅을 했다. 


이 글에서는 Go 언어에서 사용되는 문자열이 어떤 것인가에 대하여 다룰 것이다. 


물론, 문자열에 대한 주제가 블로그 포스트 대상으로 간단헤 보일 수는 있을 것이다. 


그러나 문자열을 잘 사용하기 위해서는 문자열이 어떻게 작동하는가를 이해하는 것 뿐만아니라 


바이트, 문자, 특수 문자의 차이점이나 유니코드와 UTF-8 코드의 차이점을 이해해야 하며,


문자열과 문자열의 문자에 대한 것등 여러 가지 미묘한 차이점에 대한 이해가 필요하다. 


이런 주제에 대하여 접근 하는 좋은 방법 중 하나는 자주 묻는 질문에 대한 답을 검토하는 것이다. 


예를 들어 "왜 Go 문자열에 대해서 n번째 위치를 색인했을때 ,n번째 문자을 얻을 수 없을까?" 같은 것이다. 


이 질문은 현대의 프로그램에서 문자를 어떻게 다루고 있는가에 대하여 다양한 시사점을 이끌어 낸다. 


비록 GO를 다루고 있지 않지만 이런 이슈에 대한 탁월한 글로는 


Joel Spolsky가  쓴 


"The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)."


이라는 제목으로 포스팅된 유명한 글이 있다.  


그가 제기한 많은 것들을 여기서도 다룰 것이다. 


### 문자열이란 무었인가?

------------------------


간단한 것부터 시작해 보자. 


Go 에서 문자열은 바이트의 읽기 전용 슬라이스로 생각 할 수 있다.


바이트의 슬라이스가 뭔지 모르거나 어떻게 다루는지를 모른다면 


이전 포스트인 "we'll assume here that you have" 를 먼저 읽기를 바란다. 


먼저 


앞에서 언급했던, 문자열이 임의의 바이트들로 구성된다는 사실을 정확히 이해해야 한다. 


문자열의 내용은 유니코드 텍스트 나 UTF-8 텍스트 또는 사전에 미리 정의된 어떤 특정한 텍스트 형식일 필요는 없다.


아래와 같이 \xNN 표기 형식을 사용해서 문자열의 문자들을 지정할 수 있다. 


아래 표기는 일부러 바이트 값 형식으로 문자열 상수를  표현한 것이다.


(이미 알고 있겠지만 바이트는 16진수 값인 00 부터 FF 까지의 범위 내의 값을 갖는다. )


    const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"


### 문자열 출력 

---------------------


위 예의 샘플문자열의 바이트 값은 표출 가능한 ASCII 코드도 아니고 


유효한 UTF-8도 아니기 때문에 문자열을 직접 출력하면 엉뚱한 출력이 될것이다.


간단한 출력 소스 예인 


fmt.Println(sample)

코드는 다음과 같은 엉뚱한 결과를 만든다. (정확하게는 환경에 따라 다르게 출력된다.)


    ��=� ⌘

문자열이 실제로 어떤 값을 가지고 있는지 알려면, 각 값을 분리하여 살펴 봐야 한다. 


이런 처리를 할 수 있는 방법은 여러가지가 있겠지만,


가장 확실한 방법은 아래와 같은 루프를 동작시켜서 문자열의 내용을 바이트 단위로 출력해 보는 것이다. 


    for i := 0; i < len(sample); i++ {

        fmt.Printf("%x ", sample[i])

    }

위 코드에서 문자열에 대한 인데싱 처리는 개개의 문자에 대한 접근이 아니고 바이트 단위로 접근하는 것이다. 


다시 원래 주제인 문자열 출력에 대해 이제 좀 더 자세히 살표보자.


우선, 바이트 값들을 직접 다루어 보자.


위 코드는 바이트 단위로 루프를 돌며 아래와 같이 출력하게 된다. 


    bd b2 3d bc 20 e2 8c 98

위 결과를 볼 때 문자열을 정의하기 위해 16진수 형식의 이스케이프 시퀀스로 지정되었던 것과 


출력 했을때 각각의 바이트들이 어떻게 서로 연관 되는가를 주의깊게 보자.


복잡하게 구성된 문자열의 내용을 쉽게 출력하는 간단한 방법은 fmt.Printf() 함수에 %x (16진수 형식) 포맷 인자를 사용하는 것이다. 


이 포맷 인자는 바이트당 2개의 16진수 문자열로 바이트들을 순서대로 출력해 보여 준다. 


    fmt.Printf("%x\n", sample)

이 코드의 출력 결과와 앞에서 사용한 코드의 출력을 비교해 보자. 


    bdb23dbc20e28c98


이렇게 붙어 있는 숫자들 사이를 띄고 싶다면 이 포맷에 스페이스 플라그를 사용 하면 되는데 % 와 x 사이에 스페이스 문자를 넣으면 된다.


스페이스 플라그 포맷을 적용한 아래 코드를 사용한 출력결과와 위의 출력 결과를 비교해 보자.


    fmt.Printf("% x\n", sample)


이 코드 예 처럼 아주 간단한 처리로 값 사이에 공백이 자동으로 추가 된다는 것을 알 수 있다. 


    bd b2 3d bc 20 e2 8c 98

부연하면, %q 포맷 인자는 출력할수 없는 바이트 값들을 이스케이프 시컨스 형식으로 출력하여 좀 더 명확하게 출력한다. 


    fmt.Printf("%q\n", sample)

이런 방식은 대 다수의 문자열을 텍스트형태로 알아 볼수 있도록 출력하지만 경우에 따라서 알기 힘든 문자를 출력하기도 한다. 


    "\xbd\xb2=\xbc ⌘"

얼핏보면,

 

한 개의 ASCII 문자인 등호 기호 문자와 그 뒤에 일반적인 공백 문자 


그리고 우리가 잘 알고 있는 스웨덴 풍의 관심있는 장소를 표시하는 심볼 문자들이 포함되어 


결과적으로는 뒤쭉박죽된 알수 없는 문자들 모음 처럼 보인다. 


마지막 심볼 문자는 U+2318 에 해당하는 유니코드로 공백문자( 16진수 값으로 20) 뒤에 e2 8c 98 의 값이 UTF-8로 엔코딩 된 것이다. 


문자열에 이상하 값들로 인해 헷깔리거나 익숙하지 않다면 %q 포맷인자에 플러스 플라그를 사용하는 것이 좋다.


이 플라그는 출력이 되지 않는 시퀀스를 가지는 문자열에 대하여 이스케이프 시퀀스로 표출하는데, 


ASCII 문자열이 아닌것들중 UTF-8 로 해석되는 것을 처리한다. 


결과적으로 아래와 같이 플러스 플라그를 사용하면, 


문자열 안에 있는 ASCII 코드로 표출이 안되는 것들을 적절한 UTF-8 형식으로 표출한다. 


    fmt.Printf("%+q\n", sample)

위 코드에 적용한 형식에 의한 출력 처리는, 스웨덴 풍의 심볼의 유니코드 값을 \u 이스케이프 시퀀스 형식으로 출력한다. 


    "\xbd\xb2=\xbc \u2318"

이런 출력 처리 기법은 문자열의 내용에 대한 디버깅이 용이한 방법이고 


이후 블로그 주제를 다루기에 필요한 소스 코드 작성의 편의를 제공할 것이다. 


이렇게 문자열을 출력하는 방법을 통하여 우리는 문자열이 바이트 슬라이스와 유사하게 동작한다는 것을 알 수 있다. 


아래의 코드는 앞에서 다루었던 출력 옵션에 대해서 시험해 볼 수 있는 정확한 코드리스트이다. 


    package main

    

    import "fmt"

    

    func main() {

        const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98"

    

        fmt.Println("Println:")

        fmt.Println(sample)

    

        fmt.Println("Byte loop:")

        for i := 0; i < len(sample); i++ {

            fmt.Printf("%x ", sample[i])

        }

        fmt.Printf("\n")

    

        fmt.Println("Printf with %x:")

        fmt.Printf("%x\n", sample)

    

        fmt.Println("Printf with % x:")

        fmt.Printf("% x\n", sample)

    

        fmt.Println("Printf with %q:")

        fmt.Printf("%q\n", sample)

    

        fmt.Println("Printf with %+q:")

        fmt.Printf("%+q\n", sample)

    }