こんにちは、いっちゃんです。10ANTZでは、サーバーサイドのソフトウェア開発に携わっています。
本記事では、2023年9月4日に開催された社内勉強会の復習をします。10ANTZのエンジニアの間では不定期で勉強会が開催されており、業務と直接関係あるかどうかに関わらず幅広いテーマが取り上げられています。今回は、Goクイズと題しまして、プログラミング言語Goの振る舞いに関するクイズを出題しました。以下では、ここで出題したクイズの中で、stringの振る舞いに関するものについてピックアップして解説します。本記事は、すでに公開されている記事「Goクイズ(nil編)https://developers.10antz.co.jp/archives/3835 」、「Goクイズ(defer編)https://developers.10antz.co.jp/archives/3837 」の続きとなっています。
目次
クイズの形式
前回、前々回と同様に、クイズは、短いGoのプログラム(main.go)と4つの選択肢からなります。選択肢は以下の4つです。
- コンパイルエラー(go build -o main main.goが失敗)
- 実行時エラー(./main を実行した時,終了ステータスが0以外)
- Xを出力(./main を実行した時,標準出力にXが含まれる)
- Yを出力(./main を実行した時,標準出力にYが含まれる)
解答では、これらの選択肢の中から該当するものを全て選択します。
出題したクイズ
以下に勉強会で出題したクイズとその解答を示します。番号は前回の続きからとなっています。
クイズ8
プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package main import ( "fmt" ) func main() { n := len("株式会社10ANTZ") if n == 10 { fmt.Println("X") } else { fmt.Println("Y") } } |
選択肢
- コンパイルエラー
- 実行時エラー
- Xを出力
- Yを出力
解答
4
関数lenは、string型の引数に対してそのバイト数を返します。stringは文字列をUTF-8として保持するため、lenの返り値は文字数と一致するとは限りません。
https://go.dev/ref/spec#Length_and_capacity
クイズ9
プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import ( "fmt" ) func main() { for i := range "いろは" { if i == 2 { fmt.Println("X") } if i == 6 { fmt.Println("Y") } } } |
選択肢
- コンパイルエラー
- 実行時エラー
- Xを出力
- Yを出力
解答
4
stringに対してrange forはバイトではなく文字をイテレートします。一つ目のパラメータは各文字の開始バイトのインデックスを表します。stringは文字列をUTF-8として保持するため、iの値は飛び飛びの値となることがあります。
https://go.dev/ref/spec#For_range
おまけのクイズ
以下におまけとして、勉強会で出題しなかったクイズとその解答を示します。
クイズおまけA
プログラム
1 2 3 4 5 6 7 8 9 10 11 12 |
package main import "fmt" func main() { s := "いっちゃん" if s[0] == 'い' { fmt.Println("X") } else { fmt.Println("Y") } } |
選択肢
- コンパイルエラー
- 実行時エラー
- Xを出力
- Yを出力
解答
1
以下のようなコンパイルエラーとなります。
‘い’ (untyped rune constant 12356) overflows byte
string型の値に対して添え字アクセスを行うと、byte型の値が返ります。ifの比較において、右辺の文字リテラル’い’の値がbyte型で表現できないため、コンパイルエラーとなります。
クイズおまけB
プログラム
1 2 3 4 5 6 7 8 9 |
package main import "fmt" func main() { s := "XYZ" s[0] = 'Y' fmt.Println(string(s[0])) } |
選択肢
- コンパイルエラー
- 実行時エラー
- Xを出力
- Yを出力
解答
1
以下のようなコンパイルエラーとなります。
cannot assign to s[0] (neither addressable nor a map index expression)
string型の値は変更することができません。そのため、文字列を構成する要素に別の値を代入しようとすると、コンパイルエラーとなります。
https://go.dev/ref/spec#String_types
解説
これらのクイズは全て、Goにおける string に関して私が分かりにくいと感じた振る舞いをもとに作成しました。これらの振る舞いについて分かりにくいと感じる人は他にもいると思います。実際、上のクイズについて、社内勉強会における正答率(回答者のうち、正答した人の割合)は、クイズ8では0.9、クイズ9では0.33となり、正答率の低いクイズが存在します。
これらの振る舞いは、Goにおけるstringは、文字の列(0以上の整数のそれぞれに対して文字が対応した列)ではなく、バイト列のようなものとして考えることで、理解することができると思います。以下ではそのことについて解説します。
stringは、以下に示すような[]byteと同様の性質を持ちます。そのため、文字の列というよりはバイト列のようなものとみなすことができます。
- len関数に渡すとバイト数が返る。
- 添え字演算子によりバイトデータにアクセスできる。
また、stringと[]byteは相互に変換することもできます。しかしながら、以下に示すように[]byteと異なる性質も持ちます。
- stringは、不変である。
- stringは、+演算子や文字列リテラルといった文法を使用できる。
- stringは、stringsパッケージで利用できる。
stringは、文字の列ではなくバイト列のようなものと考える方が良いと述べましたが、文字の列として扱いたいとき(例えば、文字列を構成するそれぞれの文字に対して操作を行う場合など)に便利な型として[]runeがあります。stringは[]runeと相互に変換することができます。[]runeはruneのスライスで、runeは文字を表します。そのため、[]runeは文字の列を表現することができます。
stringは[]byteとも[]runeともそれぞれ相互に変換可能です。以下ではこれらの使い分けの方針の一つについて整理しておきます。
- string:stringsパッケージを利用するときなどは、文字列はstringのまま扱う。
- []byte:文字列をテキストデータではなくバイナリデータとして扱うときは、文字列は[]byteに変換して扱う。
- []rune:文字列を文字の列(0以上の整数のそれぞれに対して文字が対応した列)として扱うときは、文字列は[]runeに変換して扱う。
まとめ
本記事では、社内勉強会で出題したGoの振る舞いに関するクイズのうち、stringに関するものをピックアップして紹介し、解説しました。また、おまけのクイズを2問紹介しました。解説では、stringは[]byteのようなバイト列のようなものとみなすことができ、文字の列として扱いたいときは[]runeに変換することができることついて解説しました。また、string、[]byte、[]runeの使い分けについて整理しました。