LEFログ:学習記録ノート

leflog: 学習の記録をどんどんアップしていきます

A Tour of Goで難しかったところ その1 ―― Readersについて、ポインタ・アドレスの使い時について

A Tour of Goの画面

概要

この3日間ほど、A Tour of Goに取り組んでいたのですが、想像以上に難しかったです。

自分が詰まったところについて、ちょっとだけ記録を残そうと思います。

Readers

Readers | A Tour of Go

この出力結果は次のようになっています。

n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"

該当の出力するコードは次のようになっています。

  fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
  fmt.Printf("b[:n] = %q\n", b[:n])

この部分、最初はどういうことか分からなかったのですが、色々調べて分かりました。

重要なのは、[72 101 108 108 111 44 32 82][101 97 100 101 114 33 32 82]の比較です。

つまり、バイト数が8のときは、全ての値が更新されています。

しかし、バイト数が6のときは、最後の3282は更新されていません(前から6つ分は更新されています)。

つまり、b[:n]で、更新されたバイト数ぶんを受け取って、それを%qで人が読める形に変換しているということです。

bの初期値は[0 0 0 0 0 0 0 0]になっています。for文の前に、

fmt.Println(r)
fmt.Println(b)

このように埋め込むと分かりやすいと思います。

Readers その2

Exercise: Readers | A Tour of Go

まず結論から言うと、このコードの回答は次のようになります。

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

func (mr MyReader) Read(p []byte) (n int, e error) {
    for i := range p {
        p[i] = 'A';
    }
    return len(p), nil
}

func main() {
    reader.Validate(MyReader{})
}

しかし、なぜこれが答えになるのかが今ひとつ理解できませんでした。

まず、reader.Validate関数は次のようになります。

このコードの中で、重要な部分がここです。

    b := make([]byte, 1024, 2048)
    i, o := 0, 0
    for ; i < 1<<20 && o < 1<<20; i++ { // test 1mb
        n, err := r.Read(b)
        for i, v := range b[:n] {
            if v != 'A' {
                fmt.Fprintf(os.Stderr, "got byte %x at offset %v, want 'A'\n", v, o+i)
                return
            }
        }

まず、rというのはfunc Validate(r io.Reader) {から来ていて、これは先程作ったfunc (mr MyReader) Read(p []byte) (n int, e error)を指しています。

そのため、r.Read(b)で呼び出しています。

さて、b := make([]byte, 1024, 2048)とは何でしょうか? これは、バイトスライスを作成しています。つまり、初期値の長さが1024のスライスです。

[0 0 0 0 0 ... 0 0 0]みたいな0が1024入っていると考えると分かりやすいかもしれません。ちなみに、バイト型とは次のことを指します。

【Go言語】byte型ってなんだ - Qiita

ここで「なぜ2048なのか?」という疑問が湧きますが、それについてはあまり気にしなくて良さそうです。というのも、これはスライスの容量であり、おそらく何かしらの拡張性を担保するために付けられているのだと想像します。

Goのスライスについて学ぼう - Qiita

そして、ここからが本題です。

        n, err := r.Read(b)
        for i, v := range b[:n] {
            if v != 'A' {
                fmt.Fprintf(os.Stderr, "got byte %x at offset %v, want 'A'\n", v, o+i)

ここで自分は苦戦しました。

結論から言うと、r.Read(b)が実行されることで、bの値が書き換わっています。

(返り値がないので分かりにくいですが、ここでbの値を変更していると考えると辻褄が合います)

つまり、こういうことです。

先程の[0 0 0 ... 0]が、[65 65 65 ... 65]に変わっているのです。

Hex to ASCII Text String Converter

Aに対応するHexadecimal(16進数)は41です。そのため、%x65が16進数に変換されて、41になります(より正確には0x41)。

先程のコードで、

    for i := range p {
        p[i] = 'B';
    }

と、'A'ではなく'B'を敷き詰めてあげると、良い感じにエラーを返してくれます。

got byte 42 at offset 0, want 'A'

42を10進数にすると、66、つまりASCIIにおける'B' となります。

あとはRangeでループさせて、一つ一つ'A'になっているか……つまり65になっているかを確認している、ということになります。


ちなみに、for ; i < 1<<20 && o < 1<<20; i++は、読み込みが1MBに達するまで、またはo(総読み取りバイト数)が1MBに達するまで、ループを続けているようです。

1<<20はビット演算を使っていて、つまりこのコードによって、0000000000000000000110000000000000000000になります。これを十進数で表すと2 ** 20であり、1,048,576バイト……つまりは1メガバイトになります。

メガバイトとは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

難しすぎるってばよ……!!

ImagesとExercise: Imagesについて

実はよく分かっていません……あとで復習します。

ポインタとアドレスについて

ポインタとアドレスって、結局どういうときに使うの?という疑問が湧いたので調べてみました。

自分が参考になったのはこの3つの記事です。

特に1つ目の記事の、この部分が参考になりました。

ポインタはGoでいつ使うのか

Goでは、引数やレシーバを関数内で書き換える必要がある場合、書き換える対象はポインタで渡す必要があります。

2つ目の記事では、この部分が参考になりました。

これらの追加コストを回避するために、Goでは関数の引数や戻り値としてポインタを渡すことができます。ポインタが渡されると、関数はその値のコピーではなく、元の値の基礎となるメモリ位置にアクセスすることができます。つまり、関数内で値に変更を加えると、元の値にも影響が及びます。

3つ目の記事では、次の部分が参考になりました。

*namePoint には、 &name (stringへのポインタである*string型の値が格納されているからであり、それを *namePoint = "二郎"で書き換えているので、当然 name の値も書き変わるということである。

4つ目の記事では分かりやすい具体例が載っていました。

つまり、「破壊的な変更を加える処理をしたい場合はポインタを使う」という認識で良さそうです。

ポインタを使ってアドレスに変更を加えるため、元の場所の値を書き換えている、というイメージです。

元の場所の値を書き換えるため、メモリの使用量を減らせる(?)というメリットもあるみたいです。

次回

水曜日から金曜日までの3日間では、ConcurrencyのGoroutinesまでしか進みませんでした💧 進捗としては8割だと思うのですが、残りの2割がなかなか進まないです。

なかなかコードを読み解けなかったのと、Exerciseが個人的に難しかったのが進捗の遅い原因でした。既に修了した部分でも、まだ理解が曖昧なところは多いです。

また時間のあるときに、ConcurrencyのChannels以降をトライしてみたいと思います。💪

(もし、この記事の理解で間違っている箇所があれば、ご指摘頂けると嬉しいです!)

追記:A Tour of Goの解説動画と、Go入門資料

ネットで色々調べてみたのですが、こちらのShizuoka.goさんの解説動画がかなり良さそうです。

また、tenntennさんの作成したこちらのGo入門資料がとても良さそうです。

docs.google.com

1. Goに触れる - Google スライド