今日はなにの日。

気になったこと勉強になったことのメモ。

今日は、Goで標準入力のScanの&を調べたの日。

目次

とある日

Goの勉強中に、標準入力から値を受け取りたいのでScanを使用したいと思った。

いろいろな参考記事を見てると、fmt.Scan(&n)を使用していた。

それに習って、自分も使用した。

そこで疑問に思った。

&ってなに?

普通に、変数だけを引数に指定するのでは?

そこで、今回はと標準入力について調べていく。

&の使用用途

Goの日本語情報サイト1から&の意味を調べてみる。

アドレス演算子

T型であるxオペランドとするアドレス演算&xは、*T型であり、かつxを示すポインタを生成します。このオペランドはアドレス指定可能でなければなりません。すなわち、変数、ポインタの間接参照、スライスのインデックス操作、アドレス指定可能な構造体をオペランドとするフィールドセレクタ、アドレス指定可能な配列のインデックス操作です。このうち「アドレス指定可能」であることを条件としていても、x複合リテラルであれば例外として認められます。

*T型(ポインタ)であるxオペランドとするポインタの間接参照*xは、xによって指されるT型の値を表します。xnilのとき、*xを評価しようとするとランタイムパニックが発生します。

go &x &a[f(2)] *p *pf(x)

アドレス演算子は初めて聞いた。

正直何を言ってるのかわからない。

気になったキーワードが、「ポインタを生成します。」の部分。

ポインタとは

一応、先程のサイトでポインタを調べる。

ポインタ型

ポインタ型は所定の型(ベース型)の変数へのすべてのポインタの集合を表します。初期化されていないポインタ型の値はnilです。

go PointerType = "*" BaseType . BaseType = Type . *Point *[4]int

Goで学ぶポインタとアドレスこのサイトを参考にしつつ、実践しながらポインタを試してみる。

ポインタはとは。

  • ポインタってのはメモリのアドレス情報のことだよ
  • ポインタってのはアドレス情報を格納するための変数のことだよ

Pythonでも同じアドレス情報の2つの変数を扱うと、どちらとも値が変更されるといった事があった。

Go言語だと、それすらもうまく扱う様になる必要があるみたい。

試してみる

なんとなく、基礎情報は理解したので、実際に手を動かして理解する。

適当な変数作成し値を代入。

そのポインタを代入して、出力してみる。

package main
 
import (
    "fmt"
)
 
func main() {
    test := "test"
    test2 := &test
    fmt.Println(test)
    fmt.Println(*test2)
}

// >> test
// >> test

11行目の「*」をのけて出力してみる。

package main
 
import (
    "fmt"
)
 
func main() {
    test := "test"
    test2 := &test
    fmt.Println(test)
    fmt.Println(test2)
}

// >> test
// >> 0xc0000561e0

ただの変数のtestの出力は問題ないが、test2はアドレス情報が出力された。

じゃあ、test2の値を変えてみる。

package main
 
import (
    "fmt"
)
 
func main() {
    test := "test"
    test2 := &test
    fmt.Println(test)
    fmt.Println(*test2)
    test2 = "test2"
    fmt.Println(test)
    fmt.Println(*test2)
}

// >> ./main.go:12:10: cannot use "test2" (type string) as type *string in assignment
// >> 代入で "test2" (文字列型) を *string 型として使用することはできません。

エラーが出力された。

翻訳した。

型違いで、代入はできないらしい。

でも、アドレス情報格納してるのなら、testを変更すると代わるのでは。

ということでやってみる。

package main
 
import (
    "fmt"
)
 
func main() {
    test := "test"
    test2 := &test
    fmt.Println(test)
    fmt.Println(*test2)
    test = "test2"
    fmt.Println(test)
    fmt.Println(*test2)
}
// >>test
// >>test
// >>test2
// >>test2

想定通りの挙動をした。

最初より、ポインタについては理解できた。

Scan関数

&の説明で出てきた、ポインタについては理解できた。(ある程度)

そもそも、なににこのアドレス演算子を使うものが、Scan関数

(*Scanner) Scan関数

go func (S *Scanner) Scan() (token.Pos, token.Token, []byte)

Scanは、次のトークンをスキャンし、トークンの位置pos、トークンtok、トークンが表すテキストlitを返します。ソースの終りに達したことはtoken.EOFによって検知できます。

返されたトークンがtoken.SEMICOLONのとき、それに対応するリテラルの値は、ソース内にセミコロンが現れたときは「;」、改行またはEOFによってセミコロンが挿入されたときは「\n」です。

パースの際に、なるべくエラーとしないように、構文エラーがあったとしても、可能な限り有効なトークンを戻り値として返します。そのためクライアントは、たとえ返された一連のトークン内に不正なトークンが含まれていなかったとしても、エラーが発生しなかったと仮定すべきではありません。代わりに、ScannerのErrorCountをチェックするか、エラーハンドラが登録されていれば、そのハンドラが呼び出された回数をチェックしてください。

Scanは、Initでファイルセットに追加したファイルに対し、行情報を加えます。トークンの位置は、ファイルからの相対であり、ゆえにファイルセットからの相対でもあります。

引数に、「*」が使用されているので、ポインタを宣言した変数を使用する必要がある。

なので、標準入力のScan関数はfmt.Scan(&n)アドレス演算子が使用されていた。

Scan関数使用例

package main
 
import (
    "fmt"
)
 
func main() {
    var N string
    fmt.Scan(&N)
    fmt.Println(N)
}
// input 5 0
// >> 5

標準入力からは、5 0を入力した。

だが、出力されたのは「5」だけだ。

次はこのあたりを詳しく調べる。

標準入力

サクッと調べると、色々と出てきた。

使いそうなやつをピックアップして書いてみる。

日本語のドキュメントは見づらいので、英語版のドキュメントで調べた。

Package bufio

Scan関数

先程紹介したScan関数。

これが一般的な標準入力ということらしい。

var a int
fmt.Scan(&a)

この方法は、どうやら遅いらしい。

標準入力の速さなど気にしたことがない。

bufioパッケージ

package main

import (
    "bufio"
    "fmt"
    "os"
)

var sc = bufio.NewScanner(os.Stdin)

func main() {
    var s string
    if sc.Scan() {
        s = sc.Text()
    }
    fmt.Println(s)
}

// input 5 0
// >> 5 0

一行まとめて入力を受け取りたい場合は、これが良さそう。

他にも、色々詳しく書いている記事があたったので貼っておく。

Go 言語で標準入力から読み込む競技プログラミングのアレ --- 改訂第二版

今後競技プログラミングで、標準入力使用してやっていきGoの勉強をしようかなと。

Pythonは、標準入力がすごく感嘆だったが、GoはJavaと似ていて少しめんどくさい。