目次
とある日
Goの勉強中に、標準入力から値を受け取りたいのでScanを使用したいと思った。
いろいろな参考記事を見てると、fmt.Scan(&n)
を使用していた。
それに習って、自分も使用した。
そこで疑問に思った。
&ってなに?
普通に、変数だけを引数に指定するのでは?
そこで、今回は&と標準入力について調べていく。
&の使用用途
Goの日本語情報サイト1から&の意味を調べてみる。
アドレス演算子
T
型であるx
をオペランドとするアドレス演算&x
は、*T
型であり、かつx
を示すポインタを生成します。このオペランドはアドレス指定可能でなければなりません。すなわち、変数、ポインタの間接参照、スライスのインデックス操作、アドレス指定可能な構造体をオペランドとするフィールドセレクタ、アドレス指定可能な配列のインデックス操作です。このうち「アドレス指定可能」であることを条件としていても、x
が複合リテラルであれば例外として認められます。
*T
型(ポインタ)であるx
をオペランドとするポインタの間接参照*x
は、x
によって指されるT
型の値を表します。x
がnil
のとき、*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」だけだ。
次はこのあたりを詳しく調べる。
標準入力
サクッと調べると、色々と出てきた。
使いそうなやつをピックアップして書いてみる。
日本語のドキュメントは見づらいので、英語版のドキュメントで調べた。
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の勉強をしようかなと。