Golang Ch 2 プログラム構造
名前
- 関数内で宣言されたものはその関数に対して local
- 関数の名前の最初の文字が大文字であれば、公開されており (exported であり) 、パッケージの外からも見えるしアクセス可能
例えば fmt パッケージの Printf を参照したような感じ - camel case が基本的に使われる
変数
- var 宣言は
var name type = expressionが一般形式 = expressionが省略された場合は、初期値はその型に対するゼロ値となる- 数値: 0
- boolean: false
- 文字列: ""
- interface と参照型(スライス、ポインタ、マップ、チャネル、関数): nil
- これにより常に変数は値を持ち、未初期化の変数というものはない
- パッケージレベルの変数は main が始まる前に初期化される
省略変数宣言
省略変数宣言は、必ずしも左辺のすべての変数を宣言するわけではない点に注意。
左辺の変数のどれかがすでに同じレキシカルブロック内で宣言されている場合、省略変数宣言はそれらの変数に対する代入のように働く。
in, err := os.Open(infile)
...
f, err := os.Create(outfile)
のような例だと、 2 つ目の err は既存の err 変数へ値を代入している。
ただ、省略変数宣言は少なくとも 1 つは新しい変数を宣言しないといけないので、以下のようなものはコンパイルエラーになる。
f, err := os.Open(infile)
...
f, err := os.Create(outfile)
対応策としては、 2 つめの文をただの代入とする。
ポインタ
var x int と宣言されていれば、 &x (x のアドレス) は整数変数へのポインタすなわち *int 型 (int へのポインタ) の値である。
その値が p とすると「p は x を指している」「p は x へのアドレスを持っている」と表現できる。
p が指している変数は *p と書かれ、 *p は int であるその変数の値を持つが、 *p は変数なので代入の左辺に現れても問題なく、代入はその変数を更新する。
x := 1
p := &x // p は *int 型で、 x を指す
fmt.Println(*p) // "1"
*p = 2 // x = 2 と同義
fmt.Println(x) // "2"
flag パッケージ
プログラム全体に分散しているある種の変数の値を設定するために、プログラムのコマンドライン引数を使う。
echo4 より抜粋
var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")
func main() {
flag.Parse()
fmt.Print(strings.Join(flag.Args(), *sep))
if !*n {
fmt.Println()
}
}
flag.Bool
bool 型の新たなフラグを生成する。
- 第 1 引数: フラグの名前
- 第 2 引数: 変数のデフォルト値
- 第 3 引数: メッセージ
flag.String
string 型の変数を生成する。
引数は同様。
- 第 1 引数: フラグの名前
- 第 2 引数: 変数のデフォルト値
- 第 3 引数: メッセージ
flag.Parse
フラグ変数をデフォルト値から更新するために呼び出す必要がある。 flag.Parse を呼んだあと、 flag.Args() で、非フラグの引数を受け取れる。
Goでflagを使ってコマンドライン引数を扱う | Qiita https://qiita.com/Yaruki00/items/7edc04720a24e71abfa2
new 関数
new(T) は T 型の unnamed variable を作成し、 T のゼロ値へ初期化、 *T 型の値であるそのアドレスを返す。
(名前を与えて宣言するのと何も変わらない)
p := new(int) // *int 型の p は int 変数を指している
fmt.Println(*p) // "0"
*p = 2 // 無名の int に 2 を設定する
fmt.Println(*p) // "2"
また、 new の呼び出しごとに返り値 (アドレス) は変わる。が、例外もある。
struct{} や [0]int などの何も情報を含まず大きさが 0 のものは、同じアドレスを持つ可能性がある。
lifetime
- パッケージレベルの変数の lifetime はプログラムの実行全体
- ローカル変数は動的な lifetime を持つ
- 変数は unreachable になるまで生存し続ける
ガベージコレクタはどうやって変数のメモリ領域が回収可能であることを知るのか。
簡単に言うと、どこからも参照されていないことが確認されれば (経路が到達不可能になれば) 回収可能。
コンパイラは、ローカル変数をヒープまたはスタックどちらに生成するかを選択できる。
これは、 var がつかわれているか new が使われているか、で決まるものではない。
例
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y:= new(int)
*y = 1
}
x はヒープから割り当てないといけない。ローカル変数として宣言されているが、 f が戻ったあとでも global 変数から到達可能なため。
(このとき、 x は f から escape している、と言う)
y は、 g が戻るときには到達不可能になるので、たとえ new で割り当てられていてもスタック上に割り当てるのが安全。
宣言
type 宣言
type 宣言は、既存の型と同じ基底型 (underlying type) を持つ新たな named type を定義する。
named type はこれと underlying type を区別して、場合によっては互換性のない使い方をするための手段を提供する。
例
package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
温度の 2 つの単位に対して
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
温度の 2 つの単位に対して Celsius と Fahrenheit の 2 つの型を定義している。両方とも同じ Underlying type である float64 を持っているが、同じ型ではない。そのため、両者は比較できないし、算術式で組み合わせることもできない。
float64 から変換するためには、 Celsius(t) や Fahrenheit(t) などの明示的な型変換を利用する。これらは変換であり、関数ではない。
すべての型 T に対して、値 x を T 型へ変換するような対応をする変換演算 T(x) がある。別の型への変換は、両方の型が同じ Underlying type を持つ場合か、両方の型が同じ Underlying type の変数を指す無名関数のポインタ型の場合には許される。
