初期化
表面的にはCやC++での初期化とあまり変わらないように見えますが、Goでの初期化はより強力です。複雑な構造を初期化中に構築することができ、異なるパッケージ間でも初期化されたオブジェクト間の順序問題が正しく処理されます。
定数
Goの定数は正にその通り、定数です。それらは、関数内でローカルとして定義されても、コンパイル時に作成され、数値、文字(ルーン)、文字列、ブール値のみが可能です。コンパイル時制限のため、それらを定義する式は、コンパイラによって評価可能な定数式でなければなりません。たとえば、1<<3
は定数式ですが、math.Sin(math.Pi/4)
は実行時にmath.Sin
への関数呼び出しが発生する必要があるため、そうではありません。
Goでは、列挙定数はiota
列挙子を使用して作成されます。iota
は式の一部となることができ、式は暗黙的に繰り返されるため、複雑な値のセットを簡単に構築できます。
type ByteSize float64
const (
_ = iota // ブランク識別子への代入により最初の値を無視
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
PB
EB
ZB
YB
)
ユーザー定義型にString
などのメソッドを添付する能力により、任意の値が印刷時に自動的に自分をフォーマットできます。構造体に最もよく適用されるのを見るでしょうが、この技術はByteSize
のような浮動小数点型などのスカラー型にも有用です。
func (b ByteSize) String() string {
switch {
case b >= YB:
return fmt.Sprintf("%.2fYB", b/YB)
case b >= ZB:
return fmt.Sprintf("%.2fZB", b/ZB)
case b >= EB:
return fmt.Sprintf("%.2fEB", b/EB)
case b >= PB:
return fmt.Sprintf("%.2fPB", b/PB)
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%.2fB", b)
}
式YB
は1.00YB
として印刷され、ByteSize(1e13)
は9.09TB
として印刷されます。
ここでByteSize
のString
メソッドを実装するためにSprintf
を使用することは、変換のためではなく、%f
でSprintf
を呼び出すため、安全です(無限再帰を回避)。これは文字列フォーマットではありません:Sprintf
は文字列が欲しいときのみString
メソッドを呼び出し、%f
は浮動小数点値を要求します。
変数
変数は定数のように初期化できますが、初期化子は実行時に計算される一般的な式にすることができます。
var (
home = os.Getenv("HOME")
user = os.Getenv("USER")
gopath = os.Getenv("GOPATH")
)
init関数
最後に、各ソースファイルは、必要な状態を設定するために独自の無引数init
関数を定義できます。(実際、各ファイルは複数のinit
関数を持つことができます。)そして最後に、それは最後を意味します:init
は、パッケージ内のすべての変数宣言がその初期化子を評価した後に呼び出され、これらはすべてのインポートされたパッケージが初期化された後にのみ評価されます。
宣言として表現できない初期化に加えて、init
関数の一般的な用途は、実際の実行が開始される前にプログラム状態の正しさを検証または修復することです。
func init() {
if user == "" {
log.Fatal("$USER not set")
}
if home == "" {
home = "/home/" + user
}
if gopath == "" {
gopath = home + "/go"
}
// gopath はコマンドラインの --gopath フラグによって上書きされる可能性があります
flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}