『プログラミング言語Go』をやっていく会
README
公式情報
写経リポジトリ
実績
- 2021/07/03(土)
- 第14回『プログラミング言語Go』オンライン読書会(第2サイクル) - connpass
- まえがき〜2.1(p.30)まで
- 2021/08/07(土)
- 2.2〜3.2(p.31~p.62)まで
- 第15回『プログラミング言語Go』オンライン読書会(第2サイクル) - connpass
まえがき
2015年の書籍だけど、どうなの? 問題
- 書籍が出たのは2015年なのでね! Go1.6くらい?
- 言語仕様というより、ツールチェインが変化している!
- もちろん、言語仕様も変わっているが、わりと細かいところっぽい
- 1.18から大きく変化する感じ(ジェネリクスとか)
周辺のひとたち
-
golang.org/x/text
の凄さ! - UTF-8つくった人たちだったか! - KenThompson と RobPike が!
- ブライアン・カーニハンとロブ・パイクは、『プログラミング作法』の著者じゃん
p.xvi 「局所性」ってなに?
p.xvii
可変サイズのスタックは最初は十分に小さい
このへん全然わからんちん。
他の言語では、100万個のゴルーチンをつくるみたいなことは、高コストでできないらしい
Goの「簡潔さ」って何?
原著だと、どんな単語を使っている?
「Goは簡潔」とか「Goはスッキリかける」とかいろんな言い回しがあるような気がするけど、何がどうそうなのやら?
「安全性のほとんど」
Goはプログラマに安全性のほとんどを与えてくれますし、複雑な型システムの負担なしに、比較的強力な型システムの実行時性能の恩恵を与えてくれます。(p. xvi)
なんつーか、ちょうどいい具合(めんどくさくない。でも、有用)みたいな?
具象型と抽象型(インタフェース)の関係が暗黙的なところ、おもしろくない?
具象型と抽象型(インタフェース)との関係は暗黙的であるため、具象型の設計者が気づかないうちに、その具象型があるインタフェースを満足しているかもしれません。(p. xviii
普通(というか、他のOOP言語?)だと、明示的だよね。だから、気づくもなにも、気づいている。
「このクラスは、この抽象型を実装していますよー!!!」
って、明示的に書かないといけない!
(Javaとかそういうやつから入ったので、それが普通だと思っていたわけで!)
第1章 チュートリアル
go build
したときに生成されるファイル名
-
go build main.go
→ ファイル名 -
go build
のみ → ディレクトリ名
$ pwd
dir/helloworld
$ ls
main.go
# ファイル名を指定したbuildはファイル名ままのバイナリ生成
$ go build main.go
$ ls
main main.go
# ファイル名を指定しないと、ディレクトリ名になる!
$ go build
$ ls
helloworld main main.go
build失敗例
$ ls
foo.go bar.go
$ cat foo.go
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
$ cat bar.go
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
$ go build
# github.com/mohira/gopl/ch1/1.1/helloworld
./foo.go:5:6: main redeclared in this block
previous declaration at ./bar.go:5:6
だめだよ
import "fmt"
package main
func main() {}
$ go build main.go
main.go:1:1: expected 'package', found 'import'
i++
やi--
は文であって、式ではない!
こういうことはできないよって話
package main
func main() {
i := 0
j := i++
}
$ go run main.go
# command-line-arguments
./main.go:6:9: syntax error: unexpected ++ at end of statement
Cだとできちゃうから、違いに注意ね!
#include "stdio.h"
int main() {
int i, j;
i = 0;
j = i++;
printf("i=%d, j=%d\n", i, j); // i=1, j=0
}
$ gcc main.c; ./a.out
i=1, j=0
Q. 単文と複文?
あとでな。
p.7らへん
forの文法の中で検証
Q. 変数宣言時に型を明示的にするときに違いが出るやつ?
-
var x
var x int
var x float64
の違い -
var
とconst
package main
import "fmt"
func main() {
//var x = 1e+9
//var x int = 1e+9
//var x float64 = 1e+9
const x = 1e+9
//const x int = 1e+9
//const x float64 = 1e+9
var a int
fmt.Println(a + x)
var b float64
fmt.Println(b + x)
}
mapとかいうデータ型
- 定数時間で操作
- キーは、
==
で比較できる型であればなんでもOK - 内部的にはハッシュテーブル
- Javaのように
Equals()
みたいなメソッドを作らなくても、処理系が勝手にハッシュ化してくれるらしい -
make
すると何が起きるのかは、また4章で。 - 順序は保持されない
- 最近のPythonは保持してくれたりするけど、それとは違うね
bufio.Scanner
あたりの挙動理解したい〜〜
気になるやつね。
AtCoderでも使うよね。てきとーに使ってるけど。
ioutil
まわりのAPIがゴリゴリ変わっていく感じっぽい go doc
してこうな!
Go1.5から、Go1.16だから、そりゃあいろいろあるよね
go doc
はWebでみるより楽ちんだ。
$ go doc ioutil.ReadAll
package ioutil // import "io/ioutil"
func ReadAll(r io.Reader) ([]byte, error)
ReadAll reads from r until an error or EOF and returns the data it read. A
successful call returns err == nil, not err == EOF. Because ReadAll is
defined to read from src until EOF, it does not treat an EOF from Read as an
error to be reported.
As of Go 1.16, this function simply calls io.ReadAll.
As of Go 1.16, this function simply calls io.ReadAll.
というわけで、io.ReadAll
もみていくとこんなん。
$ go doc io.ReadAll
package io // import "io"
func ReadAll(r Reader) ([]byte, error)
ReadAll reads from r until an error or EOF and returns the data it read. A
successful call returns err == nil, not err == EOF. Because ReadAll is
defined to read from src until EOF, it does not treat an EOF from Read as an
error to be reported.
Q. 実験してね? コードがほしいぞ! 「文にラベルがつけられる」!?!?」
文にはラベル付けすることができますので、 break と continue はそれらのラベルを参照することがdけいます。それにより、例えば、複数のネストしたループから一度に抜け出したり、最も外側のループの次の繰り返しを開始したりできます。
p.27
ラベル文(Labeld Statements)
A labeled statement may be the target of a goto, break or continue statement.
ほーん?
雰囲気は、こんなんらしい。いまんとこ、見たことがない。
RowLoop:
for y, row := range rows {
for x, data := range row {
if data == endOfRow {
continue RowLoop
}
row[x] = data + bias(x, y)
}
}
実験した!
-
gofmt
でも内側にインデントされない! - あんまり使わなさそう
package main
import "fmt"
// 無限ループになるので注意!
func main() {
// gofmtでも内側にインデントされない!
Hello:
for i := 0; i < 5; i++ {
fmt.Println(i)
if i == 1 {
continue Hello
}
}
GoodBye:
for j := 1; j < 10; j++ {
if j %2 == 0 {
goto Hello // あえてgoto使ってみる
}
if j == 9 {
break GoodBye
}
}
}
ラベル文
package main
import "fmt"
func main() {
Name:
i:=0
fmt.Println(i)
}
$ go build main.go
./main.go:7:1: label Hello defined and not used
p.19 1.6が並行処理の威力とGoでの並行処理の書きやすさの例としてGreatなのでは!?
-
fetch()
は、成功しても失敗しても、ch
にデータ送信するようになっている! -
main()
では、<-ch
でブロックしている
$ go run ch1/1.6/fetchall/main.go https://example.com http://foobarhogefuga.com https://golang.org https://go.pkg.dev https://blog.golang.jp/
Get "http://foobarhogefuga.com": dial tcp: lookup foobarhogefuga.com: no such host
0.42s 9951 https://golang.org
0.52s 86520 https://blog.golang.jp/
0.58s 1256 https://example.com
1.14s 634134 https://go.pkg.dev
1.14s elapsed
package main
import (
"fmt"
"io"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch)
}
for range os.Args[1:] {
fmt.Println(<-ch)
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
nbytes, err := io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
第2章 プログラム構造
Q. 予約語(keyword) と 事前宣言(predeclared) の違い
- 予約語
- ex:
break
,func
,go
, など - 識別子としては使えない
- ex:
- 事前宣言された名前
- ex:
true
,int
,make
など - 識別子として使える
- ex:
ex: keywordは識別子としては使えない
package main
func main() {
break := 123 // syntax error: unexpected := at end of statement
}
true
は事前宣言された名前に過ぎないので、識別子として使える
ex: package main
import "fmt"
func main() {
true := 123
fmt.Println(true) // 123
}
int
も事前宣言された名前に過ぎないので、識別子として使える。で、int
を識別子として使うと、型としてのint
は使えなくなる
ex: package main
import "fmt"
func main() {
int := 123
fmt.Println(int) // 123
var x int = 99 // int is not a type
fmt.Println(x)
}
ちなみに、GoLandだと、この色になるやつ
Goには未初期化の変数はない
p.32
ゼロ値の仕組みにより、変数はいつでもその方の明確に定義された値をもつことが保証されます。
つまり、Goには未初期化の変数というものはありません。
eratta: 現状(1.16)の言語仕様には参照型(Referenced Type)という記述はない!
でも、本文では、「参照型」というフレーズが今後も出てくるので注意な!
昔はあったらしい。いつまで?
p.32
インターフェースと参照型(スライス、ポインタ、マップ、チャネル、関数)ならnilです
未解決:「ヒープにとるか、スタックにとるか」はコンパイラが決める?
このへんしりたいな。
変数の管理ってどうなってるんだ?
package main
import "fmt"
func f() *int {
v := 1
return &v
}
var p = f()
// p.35 下段
func main() {
fmt.Println(p == p) // true
fmt.Println(p == f()) // false
fmt.Println(f() == f()) // false
}
Q. new関数をつかうときってどんなとき?
eratta: named type -> defined type
p.43
type alias が導入されたタイミングで、named type から defined type になったらしい
索引の秘密 p.121にRobPikeの記述がないとみせかけて...
Rob Pikeさんのアーチェリーネタww
『プログラミング言語Go』の豆知識だ。
索引にあるのにp.121にRobPikeの記述がないと思いきや、あるというww
doc.go
ないものも多い
$ find /usr/local/go/src -name 'doc.go'
/usr/local/go/src/cmd/vet/doc.go
/usr/local/go/src/cmd/asm/doc.go
/usr/local/go/src/cmd/go/internal/doc/doc.go
/usr/local/go/src/cmd/trace/doc.go
/usr/local/go/src/cmd/pprof/doc.go
/usr/local/go/src/cmd/dist/doc.go
/usr/local/go/src/cmd/internal/obj/ppc64/doc.go
/usr/local/go/src/cmd/internal/obj/arm64/doc.go
/usr/local/go/src/cmd/pack/doc.go
/usr/local/go/src/cmd/compile/doc.go
/usr/local/go/src/cmd/link/doc.go
/usr/local/go/src/cmd/cgo/doc.go
/usr/local/go/src/cmd/gofmt/doc.go
/usr/local/go/src/cmd/nm/doc.go
/usr/local/go/src/cmd/fix/doc.go
/usr/local/go/src/cmd/buildid/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/tools/go/analysis/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/xerrors/doc.go
/usr/local/go/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/doc.go
/usr/local/go/src/cmd/cover/doc.go
/usr/local/go/src/strconv/doc.go
/usr/local/go/src/net/http/doc.go
/usr/local/go/src/go/doc/doc.go
/usr/local/go/src/go/build/doc.go
/usr/local/go/src/regexp/syntax/doc.go
/usr/local/go/src/fmt/doc.go
/usr/local/go/src/runtime/metrics/doc.go
/usr/local/go/src/runtime/race/doc.go
/usr/local/go/src/internal/race/doc.go
/usr/local/go/src/encoding/gob/doc.go
/usr/local/go/src/html/template/doc.go
/usr/local/go/src/math/big/doc.go
/usr/local/go/src/log/syslog/doc.go
/usr/local/go/src/os/signal/doc.go
/usr/local/go/src/text/template/doc.go
/usr/local/go/src/sync/atomic/doc.go
p.50 練習問題2-3,2-4,2-5は、最適化されない状態でのベンチマークをとらないと嘘ベンチマークになる
コンパイラ「計算した結果を使ってないから、いらないか。消しとこ。」
みたいなことがある。
すると、
- ただループを回す速度を測っただけ
- コンパイル時に計算が完了しているので実際には関数は動いていない
みたいなことになる。
で、どう直すかは、math/bits/bits_test.go
の実装を参照するとよい。
グローバルな変数を用意して、それに結果を代入したり、測定したい関数にわたす値をリテラルじゃなくて変数にしたりする感じ。へ〜。
// Exported (global) variable serving as input for some
// of the benchmarks to ensure side-effect free calls
// are not optimized away.
var Input uint64 = DeBruijn64
// Exported (global) variable to store function results
// during benchmarking to ensure side-effect free calls
// are not optimized away.
var Output int
PopulationsCountのアルゴリズム "Hacker's Delight" という本がよく引用されるらしい
$ go doc math/bits.OnesCount
package bits // import "math/bits"
func OnesCount(x uint) int
OnesCount returns the number of one bits ("population count") in x.
// --- OnesCount ---
const m0 = 0x5555555555555555 // 01010101 ...
const m1 = 0x3333333333333333 // 00110011 ...
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
const m3 = 0x00ff00ff00ff00ff // etc.
const m4 = 0x0000ffff0000ffff
// OnesCount returns the number of one bits ("population count") in x.
func OnesCount(x uint) int {
if UintSize == 32 {
return OnesCount32(uint32(x))
}
return OnesCount64(uint64(x))
}
// OnesCount8 returns the number of one bits ("population count") in x.
func OnesCount8(x uint8) int {
return int(pop8tab[x])
}
// OnesCount16 returns the number of one bits ("population count") in x.
func OnesCount16(x uint16) int {
return int(pop8tab[x>>8] + pop8tab[x&0xff])
}
// OnesCount32 returns the number of one bits ("population count") in x.
func OnesCount32(x uint32) int {
return int(pop8tab[x>>24] + pop8tab[x>>16&0xff] + pop8tab[x>>8&0xff] + pop8tab[x&0xff])
}
// OnesCount64 returns the number of one bits ("population count") in x.
func OnesCount64(x uint64) int {
// Implementation: Parallel summing of adjacent bits.
// See "Hacker's Delight", Chap. 5: Counting Bits.
// The following pattern shows the general approach:
//
// x = x>>1&(m0&m) + x&(m0&m)
// x = x>>2&(m1&m) + x&(m1&m)
// x = x>>4&(m2&m) + x&(m2&m)
// x = x>>8&(m3&m) + x&(m3&m)
// x = x>>16&(m4&m) + x&(m4&m)
// x = x>>32&(m5&m) + x&(m5&m)
// return int(x)
//
// Masking (& operations) can be left away when there's no
// danger that a field's sum will carry over into the next
// field: Since the result cannot be > 64, 8 bits is enough
// and we can ignore the masks for the shifts by 8 and up.
// Per "Hacker's Delight", the first line can be simplified
// more, but it saves at best one instruction, so we leave
// it alone for clarity.
const m = 1<<64 - 1
x = x>>1&(m0&m) + x&(m0&m)
x = x>>2&(m1&m) + x&(m1&m)
x = (x>>4 + x) & (m2 & m)
x += x >> 8
x += x >> 16
x += x >> 32
return int(x) & (1<<7 - 1)
}
型なしの値
package main
import "fmt"
func main() {
const x = 1
var y int
var z float64
y = x
z = x
fmt.Printf("%T\n", x) // int
fmt.Printf("%T\n", y) // int
fmt.Printf("%T\n", z) // float64
}
2.3.1 省略変数宣言
:=
は宣言、=
は代入
宣言(Declarations)と代入(Assignment)は違う! - 省略変数宣言(
:=
)は、少なくとも1つの新たな変数を宣言しないといけない。 - https://golang.org/ref/spec#Short_variable_declarations
コード例: 宣言してないのでコンパイルエラー
os.Create
の行は、既存の変数に代入しているだけであり、宣言はしていない、という雰囲気だと思う。
Goの話ではなく、言葉的なニュアンスとして「宣言」には、「新しいものを定義する」的な感覚があると思う。そういう意味では納得感のある仕様かもしれない。
package main
import "os"
// p.34中段: 宣言 と 代入 は違う!
func main() {
f, err := os.Open("foo")
if err != nil {
panic(err)
}
f, err := os.Create("bar") // no new variables on left side of :=
if err != nil {
panic(err)
}
}
もっと短いコード例
package main
import "fmt"
func main() {
a := 1
a := 2 // no new variables on left side of :=
fmt.Println(a)
}
再宣言(Redeclaration)というのもある
Goの話ではなく、言葉的なニュアンスとして「宣言」には、「新しいものを定義する」的な感覚があると思う。そういう意味では納得感のある仕様かもしれない。
自分でこう書いたけど、同じ名前で別の値を再び宣言するってこともある。
コード例
- 他の言語で言う「最代入」と同じ雰囲気。
package main
import "fmt"
// ただ多値を返すことを表現するだけの関数
func tuple() (int, int) {
return 1, 2
}
func main() {
a, x := tuple()
a, y := tuple() // a は再宣言、yは新しい識別子なので、コンパイルは通る
fmt.Println(a, x, y)
}
var
使った宣言)だと、同じブロックで再宣言することはできない
reaular variable declaration(
package main
import "fmt"
func main() {
// ./main.go:7:6: a redeclared in this block
// previous declaration at ./main.go:6:6
var a = 1
var a = 2
fmt.Println(a)
}
var
がいけるが、シャドーイングになっちゃう...
ブロックを変えれば、同じ識別子で再度- これは危険
- 他のファイルからimportするときに困るやつね
package main
import "fmt"
var a = 1
func main() {
var a = 2
fmt.Println(a) // 2 <- 1じゃない!
}
2.3.2 ポインタ
ポインタは比較可能
package main
import "fmt"
// p.35 中段
func main() {
var x, y int
fmt.Println(&x == &x, &x == &y, &x == nil) // true false false
}
毎回違うアドレスを返す
package main
import "fmt"
func f() *int {
v := 1
return &v
}
var p = f()
// p.35 下段
func main() {
fmt.Println(p == p) // true
fmt.Println(*p == *f()) // true; 値は同じ
fmt.Println(p == f()) // false; アドレスは違う
fmt.Println(f() == f()) // false
}
アドレスを利用して実体をインクリメント
-
*
は pointer indirectionというらしい -
&
はアドレス演算子(Address operator)
package main
import "fmt"
func incr(p *int) int {
*p++ // 実体に加算
return *p
}
func main() {
v := 1
fmt.Println(v) // 1
incr(&v)
fmt.Println(v) // 2
fmt.Println(incr(&v)) // 3
}
flag.Parse
を通じてポインタを学ぶ
package main
import (
"flag"
"fmt"
"strings"
)
// それぞれポインタ型を返す
var n *bool = flag.Bool("n", false, "omit trailing newline(改行の省略)")
var sep *string = flag.String("s", " ", "separator")
// p.36
func main() {
flag.Parse()
fmt.Printf("%T\n", n) // *bool
fmt.Printf("%T\n", sep) // *string
fmt.Println(flag.Args())
fmt.Print(strings.Join(flag.Args(), *sep))
if !*n {
fmt.Println()
}
}
2.3.3 new関数
new
式
new(T)
はT型の**無名変数(unnamed variable)**を作成し、それをTのゼロ値へ初期化し、*T型の値であるそのアドレスを返す。
$ go doc builtin.new
package builtin // import "builtin"
func new(Type) *Type
The new built-in function allocates memory. The first argument is a type,
not a value, and the value returned is a pointer to a newly allocated zero
value of that type.
コード例
package main
import (
"fmt"
)
func main() {
p := new(int)
fmt.Printf("%T\n", p) // *int
fmt.Println(p) // 0xc0000140c8
fmt.Println(*p) // 0
*p = 2
fmt.Println(*p) // 2
}
new関数はめったに使わない話(p.38)
newは比較的まれにしか使われません
newは構文上の利便性であるだけで基本的な概念ではありません。
newは事前に宣言された関数であり、予約語ではありませんので
2.3.4 変数の生存期間(lifetime)
- このへん全然わからん
- ヒープ領域とスタック領域の話を知りたい
- ガベージコレクションも重要な話題
-
コンパイラはローカル変数をヒープ上あるいはスタック上のどちらかに生成するかを選択できます。
- エスケープ(escape)
-
コンパイラが*yをスタック上に割り当てるのは安全です(?)
メモリ管理に参考になりそうな記事
2.4.2 代入可能性(Assignability)
- こまかいのは言語仕様で → https://golang.org/ref/spec#Assignability
関数呼び出しは、引数の値を対応するパラメータ変数に暗黙的に代入します。
return
文はreturn
のオペランドを対応する結果変数へ暗黙的に代入します。
へ〜。代入なのか。
2.6.2 パッケージ初期化
init
関数は何個でもいける!
package main
import (
"fmt"
)
func init() {
fmt.Println("init2")
}
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init3")
}
func main() {
fmt.Println("main done")
}
$ go run main.go
init2
init1
init3
main done
パッケージレベルの変数初期化において依存関係はうまいこと解決している
package main
import "fmt"
// p.49
// c, a, b の順で評価される == 依存関係をうまいこと解決している
var a = valueA()
var b = valueB()
var c = 1
func valueA() int {
fmt.Println("call valueA()")
return b + c
}
func valueB() int {
fmt.Println("call valueB()")
return c + 1
}
func main() {
fmt.Println("main start")
fmt.Println("main done")
}
$ go run main.go
call valueB()
call valueA()
main start
main done
未検証: コンパイラがパッケージに含まれるファイルを初期化する順序
パッケージが複数の
.go
ファイルから構成されるのであれば、ファイルがコンパイラへ渡された順序で初期化されます。
goツールはコンパイラを呼び出す前に.go
をソートします。
2.7 スコープ
スコープと生存期間を混同しないように!
宣言のスコープはプログラムテキストの範囲です。すなわちコンパイル時の特性です。
変数の生存期間はその変数がプログラムの他の部分から参照できる実行時の時間の範囲です。すなわち実行時の特性です。
シャドーイング
package main
import "fmt"
// シャドーイング
var x = 1
// [Big Sky :: Go 言語で変数のシャドウイングを避けたいなら shadow を使おう。](https://mattn.kaoriya.net/software/lang/go/20200227102218.htm)
func main() {
x := 2
fmt.Println(x)
}
ループごとに宣言するパターン
-
:=
は代入じゃなくて宣言ってことを意識すると理解できる感じがある
package main
import "fmt"
// p.52: 3つのレキシカルブロック
// Goroutineの i:=i も同じっぽい
// 他の言語とくらべると混乱するやつ
func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
fmt.Printf("i=%d\n", i)
fmt.Printf("\tfor block: x=%v\n", x) // ループごとに x を「宣言」している!
x := x[i] // 2回めのループでのxはどうなるんだ?
if x != '!' {
x := x + 'A' - 'a' // 英小文字 を 英大文字 に変換
fmt.Printf("\t if block: x=%c\n", x)
}
}
}
$ go run main.go
i=0
for block: x=hello!
if block: x=H
i=1
for block: x=hello!
if block: x=E
i=2
for block: x=hello!
if block: x=L
i=3
for block: x=hello!
if block: x=L
i=4
for block: x=hello!
if block: x=O
i=5
for block: x=hello!
for文もレキシカルブロックを生み出す
package main
import "fmt"
// p.52 {}がレキシカルブロックってわけじゃない!
// for文の暗黙のレキシカルブロックを作っている
func main() {
for i := 1; i <= 5; i++ {
fmt.Printf("outer: i=%d", i)
i := i*i
fmt.Printf(" inner: i=%d\n", i)
}
}
$ go run main.go
outer: i=1 inner: i=1
outer: i=2 inner: i=4
outer: i=3 inner: i=9
outer: i=4 inner: i=16
outer: i=5 inner: i=25
ifもレキシカルブロックを作る
package main
import "fmt"
func f() int {
return 0
}
func g(n int) int {
return 0
}
// p.53
func main() {
if x := f(); x == 0 {
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Printf(x, y) // undefined x, y
}
package main
import (
"fmt"
"os"
)
func main() {
if _, err := os.Open("foo"); err != nil {
panic(err)
}
fmt.Println(err) // undefined: err
}
else句もブロックに含まれるよ
package main
import (
"log"
"os"
)
func main() {
if f, err := os.Open("tmp"); err != nil {
log.Println(err)
} else {
// else句もブロックに含まれるのでセーフ
f.Stat()
}
}
レキシカルブロックを理解しておかないと、他のファイルからimportする値を使うときにハマる
package foo
import (
"log"
"os"
)
var Cwd string // 実は使われてない!!! GoLandは警告出してくれるね
func init() {
Cwd, err := os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v\n", err)
}
// ここの Cwd は init()内のレキシカルスコープで「宣言」された Cwd
// なので、パッケージレベルの変数cwdはゼロ値のまま!
// なので、他のファイルから Cwd を 利用しようとすると...
log.Printf("Working directory = %s\n", Cwd)
}
package main
import (
"fmt"
"github.com/mohira/gopl/ch2/2.7/7/foo"
)
func main() {
fmt.Println("main")
fmt.Println(foo.Cwd=="") // true; つまり foo.Cwdが文字列のゼロ値のまま!
}
fmt.Printf
で、1つの変数を一気に展開するやつ便利
package main
import "fmt"
func main() {
x := 11
fmt.Printf("0b%b 0x%[1]x 0o%[1]o %[1]d\n", x)
}