Go言語

【Go言語】文字コード、文字列リテラル、ルーンとは?

Go言語にはルーン(rune)という型があります。

Go言語を勉強し始めたとき、ルーンって何って感じで混乱したので、「ルーンとは何なのか?」を中心にGo言語のstring型、rune型について学んだ内容をまとめてみました。

一言で言えばルーンはUnicodeコードポイントのことなのですが、「文字コードとは?」、「Unicodeコードポイントとは?」ってところから整理していきます。

 

文字コード

文字コードとは「文字に割り当てられた数字」

コンピューターは0, 1しか理解できないので、文字を数字として変換して処理できるように文字に数字を割り当てています。

例えば、「あ → 00000001」というように各文字に数字が割り当てられている対応表があって、コンピューターはこの対応表を使って文字を数字に変換して処理しています。

 

そして、この対応表には「符号化文字集合」と「文字符号化方式」の2つがあって、コンピューターはこの2つの対応表を組み合わせて文字を処理しています。

  • 符号化文字集合
    • 「文字」と「文字に割り当てた番号」の対応表
    • 例. Unicode
  • 文字符号化方式
    • 「文字に割り当てた番号」と「コンピュータが扱う数字」の対応表
    • 例. UTF-8, Shift-JIS

 

2つの表を組み合わせて↓な感じの対応表を作り「文字<=>数字」を変換しているイメージです。

文字コード

青色で書いた文字符号化方式の部分が良く聞く「UTF-8」、「UTF-16」、「Shift-JIS」です。

「UTF-8」と「Shift-JIS」では、対応表が変わってくるのでShift-JISのデータをUTF-8で表示しようとすると文字化けしちゃうわけです。

 

ASCIIコード

符号化文字集合。「文字」と「文字に割り当てた番号」の対応表

ASCIIコードは「文字に割り当てた番号」と「コンピュータが扱う数字」が同じなので文字符号化方式を意識しなくて良いです。

7ビットの整数(0~127)で表現され、ASCII文字コード表にあるようにアルファベット、数字、記号、空白文字、制御文字の128文字のみに番号が振られています。

繰り返しになりますが、この対応表を使って文字をコンピューターが理解できる数字に変換しているわけです。

Unicode

符号化文字集合。「文字」と「文字に割り当てた番号」の対応表

ASCIIコードだと英語以外の言語のデータが扱えないので、Unicodeができました。

Unicodeは、世界の全ての書記体系のすべての文字、アクセント、発音区別記号、タブなどの制御コード、絵文字に番号を振っています。

Unicodeが各文字に割り振っている番号のことをUnicodeコードポイントといいます。

そして、このUnicodeコードポイントのことをGo言語ではルーン(rune)と呼びます。

UTF-8

文字符号化方式。「文字に割り当てた番号」と「コンピュータが扱う数字」の対応表

言い換えると「Unicodeが各文字に割り当てた番号」と「コンピュータが扱う数字」の対応表がUTF-8です。

Goのソースファイルは常にUTF-8でエンコードされ、Goの文字列はUTF-8として解釈されます。

Go言語の文字列

文字列は不変なバイト列。Goでは文字列はバイトのスライス。

  • Goのソースファイルは常にUTF-8でエンコードされ、Goの文字列はUTF-8として解釈される

  • len関数は文字列中のバイト数を返す

  • str[i] は文字列str のi番目のバイトを取り出す

  • ASCIIではないコードポイントのUTF-8エンコーディングは2バイト以上になるので、文字列のi番目のバイトは必ずしもi番目の文字ではない

 

試しに、文字列の各バイトの値を見てみると、UnicodeとUTF-8の対応表と一致することを確認できます。

unicode table のUTF-8の項目をみると、hexが「E3 81 82」, dec(bytes)が「227 129 130」となっており、以下のコードの結果も同じになっています。

str := "あ"
fmt.Printf("len: %d\n", len(str)) // => len: 3

for i := 0; i < len(str); i++ {
	fmt.Printf("%d: %d\n", i, str[i])
	// 10進数で表示してみる
	// => 0: 227
	// => 1: 129
	// => 2: 130
}

// 「% x」は16進数の2桁ごとの間にスペースを入れてくれる
fmt.Printf("% x\n", str) // => e3 81 82

文字列リテラル

  • ダブルクォートで囲む
  • エスケープシーケンス(\)を処理したくない場合は、バッククォート(`)で囲む。バッククォートで囲んだ文字列は改行も含めて入力した文字がそのまま処理される。

Usageなど改行を含む文字列を扱うときにバッククォートでの記法を使うとスッキリ書けます。

package main

import (
	"fmt"
)

func main() {
	text := `こんちには、
私のブログを読んで頂きありがとうございます。
	
これからもよろしくおねがいします。`

	fmt.Println(text)
}

 

ルーン

Unicodeコードポイントのことです。ルーン(rune)はint32のsynonym(別名)で、慣習的に値がUnicodeコードポイントであることを示すために使われています。

runeリテラルはシングルクォートで囲み、verbは%cです。

 

rune型はint32型の別名なので、演算もできます。

r1 := 'あ'
r2 := 'a'
fmt.Printf("r1: %d, %c\n", r1, r1) // r1: 12354, あ
fmt.Printf("r2: %d, %c\n", r2, r2) // r2: 97, a
fmt.Println(r1 - r2)               // 12257

ルーン数を知りたい場合は、unicode/utf8パッケージを使います。

str := "Hello, Go言語"
fmt.Printf("len: %d\n", len(str))                           // => len: 15
fmt.Printf("rune count: %d\n", utf8.RuneCountInString(str)) // => rune count: 11

ルーンごと(1文字ずつ)に処理をしたい場合には、rangeループが便利です。rangeが文字列に適用された場合は、暗黙的にUTF-8のデコードをしてくれます。

str := "Hello, Go言語"
for i, r := range str {
    fmt.Printf("%d\t%q\t%d\n", i, r, r)
}

// 出力結果 インデックス、文字、ルーン(unicodeコードポイント)
0       'H'     72
1       'e'     101
2       'l'     108
3       'l'     108
4       'o'     111
5       ','     44
6       ' '     32
7       'G'     71
8       'o'     111
9       '言'    35328
12      '語'    35486  // ASCIIではないルーンの場合インデックスが飛んでいる

rangeを使わないのであれば、次のようなforループを書くことになります。

str := "Hello, Go言語"
for i := 0; i < len(str); {
	r, size := utf8.DecodeRuneInString(str[i:])
	fmt.Printf("%d\t%c\t%d\n", i, r, r)
  // 単純に1ずつインクリメントしては、ルーンごとに処理できないので、バイトサイズを加算していく
  // 
	i += size
}

Unicodeコードポイントの数値で文字を指定する

次の形式のコードポイントをダブルクォートで囲むだけです。

  • 256未満のコードポイント`\xhh`
  • 16ビット値 `\uhhhh`
  • 32ビット値 `\Uhhhhhhhh`

※ hは16進数の1桁

実際にunicode tableのコードポイントを参考にいくつか出力してみます。

package main

import "fmt"

func main() {
	// A
	fmt.Println("\x41")
	fmt.Println("\u0041")
	fmt.Println("\U00000041")
	// あ
	fmt.Println("\u3042")
	fmt.Println("\U00003042")

	// emoji
	fmt.Println("\U0001F600")
}

 

-Go言語

© 2021 フリエン生活 Powered by AFFINGER5