新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Go言語の使い方(string・byte・rune)

Go言語の入門書には必ずstring・byte・runeが出てきます。

サラッと書かれていることが多く、そんなものかと分かった気になって軽く流してしまいがちなのですが、いざプログラムを組み始めると、それぞれを相互変換する場面が頻発して混乱してしまいます。

そんな時は、何となく型変換してうまく動いたからOKとしがちなのですが、もう少し意味を分かった上で型変換できるようにしたいので、ここにstring・byte・runeの使い方をまとめようと思います。

byte とは

byteとはuint8の別名です。

プログラムではバイナリーデータを扱う場面が多いのですが、その際、バイナリーデータはuint8の配列に格納されます。

しかし、unit8と書いてしまうとuint16uint32...など、他の型と並列に見えてしまうので、バイナリーデータだと分かりやすくするために、バイナリーデータを扱う際はbyteを使います。

string とは

stringとは、byteの配列の文字列領域と、その領域配列の長さを持つ言語組み込みの特殊な構造体です。

type StringHeader struct {
    Data uintptr
    Len  int
}

特に注目すべきは、一度作られたstringの内容は一生涯固定で後から変更できない点で、それは言語で保証されています。

例えば途中の1文字を変更するといったようなことはできません。下記のような操作はエラーになります。

a := "ABC"

// NG
a[0] = 'a'

stringの演算は必ず新しいstringを生成します。下記例ではaの領域は再利用されず、123ABC123ABCの3つの文字列がメモリに生成されます。

a := "123"
b := "ABC"
a = a + b

スライスのようにapend()で後に追加するようなことはできません。下記のような操作はエラーになります。

a := "123"
b := "ABC"

// NG
a = append(a, b)

このことから分かるように、stringの演算はあまり効率よくありません。

rune とは

stringに格納される文字列の文字コードはUTF-8です。

UTF-8は文字毎に長さが違う可変長コードです。

stringの長さはbyte配列の長さなので、実際の文字の数と必ずしも一致しません。

a := "ABCあいう"
fmt.Println(len(a)) // 12

一方、全ての文字を4byteの固定長で表す、UTF-32という文字コードがあります。

プログラム中において、stringから文字数を求めたり、特定の位置の文字を取り出すといった、文字列から個々の文字を参照する需要はよくあります。

そういった需要に応じるため、Go言語では、stringで個々の文字を参照する時は、一旦UTF-8の文字列をUTF-32の文字列に変換して、UTF-32文字で扱います。

そして、そのUTF-32文字をGo言語ではruneと呼びます。

例えば、stringの文字数は下記の方法で数えることができます。

a := "ABCあいう"
r : = []rune(a)
fmt.Println(len(r)) // 6

UTF-8の文字列をUTF-32の文字列に変換するという作業は、stringをruneのスライスに型変換することによりGo言語が行ってくれます。

変換した結果はruneのスライスに格納されます。

また、rangeでstringを呼び出した時も変換が行われ、runeのスライスが裏で自動で生成されます。

a := "ABCあいう"
for _, r := range a {
    fmt.Printf("%c", r) // ABCあいう
}

上記の例では、rangeにより、stringからruneのスライスが生成され、そこから1文字づつruneを取り出して表示するということを行っています。

また、runeはstringではありません。runeを文字として表示するには%cを使います。

相互型変換

通常、ある型のスライスから別の型のスライスへの型変換は行なえないのですが、stringからbyte・runeのスライスと、byte・runeのスライスからstringへの型変換は言語で特別に定義されています。

stringからbyte

stringから[]byteへの型変換は、stringのデータがそのまま[]byteにコピーされます。

データはコピーなので、stringから[]byteに型変換した場合、[]byteの値を変更しても、元のstringの値はそのままです。

s := "ABC"
b := []byte(s)
b[0] = 'a'
fmt.Printf("%s\n", b) // aBC
fmt.Printf("%s\n", s) // ABC

byteからstring

[]byteからstringへの型変換は、[]byteのデータを元に、新たなstringが生成されます。

ここでも、stringのデータは[]byteからコピーしたものなので、変換後に[]byteの値を変更しても、生成されたstringの値はそのままです。

b := []byte{'A', 'B', 'C'}
s := string(b)
b[0] = 'a'
fmt.Printf("%s\n", b) // aBC
fmt.Printf("%s\n", s) // ABC

[]byteに格納されている文字列を表示するには、一旦stringに型変換するか、%s[]byteを文字列として表示します。

b := []byte{'A', 'B', 'C'}
s := string(b)
fmt.Printf("%s\n", s) // ABC
fmt.Printf("%s\n", b) // ABC

stringからrune

混乱しやすいのですが、stringは文字列、runeは1文字を表します。

なので、stringに対応する概念はruneではなく[]runeになります。

前述の通り、stringを[]runeに型変換すると、stringをUTF-32に変換したruneのスライスが新たに生成されます。

// rune slice
rs := []rune("ABC")
for _, r := range rs {
    fmt.Printf("%c", r) // ABC
}

1文字のruneへの変換は、1文字のUTF-8文字の型変換で行います。

UTF-8の1文字がUTF-32の1文字に変換されてruneになります。

ダブルクォーテーションでくくられた文字列はstringなので1文字のruneへの変換は行なえません。

また、シングルクォーテーションでくくられた文字でも、2文字以上だとstringになるので、1文字のruneへの変換は行なえません。

// OK
r := rune('A')

// NG
r = rune("A")

// NG
r = rune('AB')

runeからstring

[]runeを型変換でstringに変換できます。

UTF-32の文字列がUTF-8の文字列に変換され、そのデータで新たにstringが生成されます。

r := []rune{'A', 'B', 'C'}
s := string(r)
fmt.Println(s) // ABC

1文字のruneをstringに型変換することもできます。その場合、1文字のstringが新たに生成されます。

r := rune('A')
s := string(r)
fmt.Println(s) // A

便利だけとコストがかかる

stringからの型変換および、stringへの型変換は、必ず新しい配列の生成とその配列への値のコピーが行われるのでコストがかかります。

また、stringの演算は、必ず新しいstringが生成されるので、ここでも新しい配列の生成とその配列への値のコピーが行われるのでコストがかかります。

通常そういったコストは考えなくていいのですが、コストが無視できなくなってきた場合は、文字列をstringではなく[]byteで扱うことを考えた始めた方がいいかも知れません。

文字列を[]byteで扱い始めると、今までstringが裏でやってくれていた面倒な操作を自分で行う必要がでてきます。よくある操作は下記のパッケージに関数で用意されていますので、自分でやろうとする前に軽く目を通しておくといいかも知れません。

関連カテゴリー記事

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com