丸善出版の『プログラミング言語Go』の読書履歴です。
出てきた内容の気になったポイントのまとめや練習問題の解答(自分なりの)を記載していきます。
(ここに記載されている内容で問題が生じても責任は負えませんのであしからず…。)
この章では主に変数やパッケージ、スコープについての説明になります。
練習問題があまりないので、各節の気になったポイントを書いていこうと思います。
もくじ
2.1 名前
2.2 宣言
2.3 変数
2.4 代入
2.5 型宣言
2.6 パッケージとファイル
2.7 スコープ
2.1 名前
Go言語では大文字小文字は区別されるので、heapSort と Heapsort は異なる名前になります。
(大抵の言語はそうですけど。)
最初の文字が大文字が小文字かでパッケージ外から見えるかどうかが変わって、大文字だとパッケージ外からアクセス可能になります。
他の言語だと関数名の先頭は小文字っていうのが多いので fmt.Println()
とか大文字で始まるのが気持ち悪いなぁと思っていたのですが、なるほどこういうことなのですね。
なので他言語でよく見られる public や private と言ったキーワードがないようです。
関数内で宣言されたローカルなので関数内でしか使えませんが、関数外で宣言された変数はそのパッケージ内からは使うことができます。
2.2 宣言
宣言において重要なものが4種類あって、それは var、const、type、funcで、それぞれ変数、定数、型、関数ですね。
2.3 変数
変数はvarを使って定義して、初期値を設定します。基本構文は以下になります。
1 |
var name type = expression |
具体的に書くこんな感じ。
1 |
var s string = "hello!" |
type か expression の部分を省略することができます。
type が省略されたときは、初期値によって型が決まります。(型推論ですね)
expression が省略された場合はその方に対するゼロ値になります。
数値なら0、真偽値ならfalse、文字列なら””、インタフェースと参照型はnilと言った具合です。
単一宣言で複数の変数を宣言することができて、初期値を宣言時に設定する場合はデータ型が異なっていてもOKです。
1 2 |
var i, j, k int var b, f, s = true, 2.3, "hoge" |
2.3.1 省略変数宣言
ローカル変数を宣言して初期化するときに省略変数宣言を使うことができます。
1 |
name := expression |
チュートリアルで sep := " "
とか出てきてなんだろうと思っていたのですが、ようやく納得ですw
宣言時に初期値を設定しない場合や明示的な型の宣言が必要な場合は var を使います。
諸略変数宣言ですが、複数の変数でもOKで、同じように :=
を使います。
1 |
i, j := 0, 1 |
便利だからと言って多用し過ぎると可読性が落ちてしまうので、ローカル内でそれほど使われないというような変数のみにしたほうがよさそうですね。
2.3.2 ポインタ
おぉ。ポインタ。C/C++以外ではポインタって用語を使う言語が少ないので、ちょっと身構えてしまいますね。
(もともとC/C++プログラマなんですけど、私w)
C/C++のポインタとほぼ同じような考え方です。
ポインタを指す場合は &
を使って、ポインタの中身を取り出すときは *
を使います。
1 2 3 |
x := 1 p := &x *p = 2 |
このようなソースの場合は p は x のポインタ(変数のアドレス)が格納されているので、*p とするとポインタの中身を書き換えるので、x も書き換わります。
うん、C/C++と同じですね。
2.3.3 new関数
変数を作成する別の方法として new 関数があります。
変数名を付ける必要がないときなどに便利そうです。
すごく単純な例ですけど、こういう使い方ができるようです。
1 2 3 |
func newInt() *int { return new(int) } |
2.3.4 変数の保存期間
変数は到達不可能になるまで存在し続け、その後はそのメモリ領域は再利用されるかもしれない、というお話ですね。
ただ、ガベージコレクションが基本的にやってくれるので、そこまで神経質に考える必要はないけれど少し頭に入れておくのはいいとは思います。
2.4 代入
代入するには =
を使えばOKです。これまでにも出てきていますが。
+=
、*=
などや++
、--
も使えます。
2.4.1 タプル代入
タプル代入では複数の変数に一度に代入ができます。
以下のような2つの変数の値を交換するという使い方もできます。
1 |
x, y = y, x |
今までにも出てきたような関数から複数の値を受け取るようなものもタプル代入ですね。
1 |
f, err = os.Open("hoge.txt") |
Go言語は使われない変数を定義することができないので、複数値を返す関数の1つの値しか使わない、という場合には ブランク識別子 _
(アンダースコア) を指定することで安全に破棄してくれるようになります。
例えば、io.Copy()
はコピーしたバイト数とエラー値を返しますが、バイト数がいらない場合はブランクにしてあげればOKです。
1 |
_, err = io.Copy(dst, src) |
2.4.2 代入可能性
暗黙的に発生する代入があり、例えば、関数は引数をパラメータ変数に暗黙的に代入するというようなお話ですね。
あとはスライスの初期値の設定なんかもそういうことらしいです。
型によっても変わってくるので、あとの章で説明があるようです。
2.5 型宣言
もともと用意されている型はいろいろありますが、type宣言をすると既存の方と同じ基底型を持つ新しい型を作ることができます。
構文はこのようになります
1 |
type name underlying-type |
たとえば、長さの単位毎に型を作って見るとすると以下のようになります。
1 2 |
type Meter float64 type Feet float64 |
どちらもflota64を基底型としていますが、まったく別の型として使うことができます。
typeも名前の先頭を大文字で書くとパッケージ外からも使うことができるようになります。
2.6 パッケージとファイル
今までもなんとなく書いてきたpackageについてです。
他の言語と同じように名前空間として機能があって、同じ関数名がバッティングしないようにするために使います。
構文は次のようになります。
1 |
package name |
同じパッケージに属しているファイルであれば、パッケージ名を使うことなく関数や変数を使うことができます。
練習問題2.1
さて、この章では初めても練習問題です。
書籍の本文に記載されている摂氏、華氏の変換モジュールに絶対温度との変換も追加しなさい、というようなものですね。
とりあえずこんな感じでいいかな、と…
exportedな関数にドックコメントをつけていないとwarningが出てしまいますが、とりあえず面倒なので入れていません(汗)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<div class="filename">tempconv.go</div> package tempconv import "fmt" type Celsius float64 type Fahrenheit float64 type Kelvin float64 const ( AbsoluteZeroC Celsius = -273.15 FreezingC Celsius = 0 BoilingC Celsius = 100 ) func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) } func (k Kelvin) String() string { return fmt.Sprintf("%g°K", k) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="filename">conv.go</div> package commonconv func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } func CToK(c Celsius) Kelvin { return Kelvin(c + 273.15) } func FToK(f Fahrenheit) Kelvin { return Kelvin(5/9*(f-32) + 273.15) } func KToC(k Kelvin) Celsius { return Celsius(k - 273.15) } func KToF(k Kelvin) Fahrenheit { return Fahrenheit(5/9*(k-32) + 273.15) } func FtToM(ft Feet) Meter { return Meter(ft / 3.2808) } func MToFt(m Meter) Feet { return Feet(m * 3.2808) } func KgToP(kg Kilogram) Pound { return Pound(kg * 0.454) } func PToKg(p Pound) Kilogram { return Kilogram(p / 0.454) } |
2.6.1 インポート
パッケージを作ったら、使う場合はインポートをしてあげなければいけません。
今までも fmtやstringsなどなどいろいろとインポートしていたのも同様ですね。
パッケージ名とフォルダ名は一致させておいたほうがいいようです。
なぜならば、インポートで指定するのはフォルダ名なんですよね。
というわけで、私はGOPATHで指定したパスのsrcフォルダのなかに、lessons というフォルダを作っていその中に書籍のソースや練習問題を入れています。
なので、前節のソースコードは $GOPATH/lessons/ch2/tempconv
というフォルダに入れてあります。
このtempconvをインポートする場合は下記のように指定してあげればOKです。
$GOPATH/src 以下のパスを指定しないと、標準パッケージを見に行こうとするので、独自パッケージの場合はパスの指定が必要なようです。
1 |
import "lessons/ch2/tempconv" |
こうすると、標準パッケージと同じように tempconv.FToC
というように書けるわけです。
練習問題2.2
さてさて、練習問題ですね。
main.goでコマンドライン引数がない場合は、オプションと値を入力してもらうようにしています。
それから、コマンドライン引数の場合は flag を使ってオプションと値を取るようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
<div class="filename">main.go</div> package main import ( "bufio" "flag" "fmt" "lessons/ex2/2_2/commonconv" "os" "strconv" ) func main() { if len(os.Args) <= 1 { input := bufio.NewScanner(os.Stdin) fmt.Println("種類を入力してください(温度=t, 長さ=h, 重さ=w)") option := "" value := 0.0 for input.Scan() { option = input.Text() break } fmt.Println("数値を入力してください") for input.Scan() { value, _ = strconv.ParseFloat(input.Text(), 64) break } printConv(option, value) } else { t := flag.Float64("t", -0.0, "temperature") h := flag.Float64("h", -0.0, "height") w := flag.Float64("w", -0.0, "weight") flag.Parse() if *t != -0.0 { printConv("t", *t) } if *h != -0.0 { printConv("h", *h) } if *w != -0.0 { printConv("w", *w) } } } func printConv(option string, value float64) { switch option { case "t": f := commonconv.Fahrenheit(value) c := commonconv.Celsius(value) fmt.Printf("%s = %s, %s = %s\n", f, commonconv.FToC(f), c, commonconv.CToF(c)) case "w": p := commonconv.Pound(value) kg := commonconv.Kilogram(value) fmt.Printf("%s = %s, %s = %s\n", p, commonconv.PToKg(p), kg, commonconv.KgToP(kg)) case "h": ft := commonconv.Feet(value) m := commonconv.Meter(value) fmt.Printf("%s = %s, %s = %s\n", ft, commonconv.FtToM(ft), m, commonconv.MToFt(m)) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<div class="filename">commonconv/common_conv.go</div> package commonconv import "fmt" type Celsius float64 type Fahrenheit float64 type Kelvin float64 type Feet float64 type Meter float64 type Kilogram float64 type Pound float64 const ( AbsoluteZeroC Celsius = -273.15 FreezingC Celsius = 0 BoilingC Celsius = 100 ) func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) } func (k Kelvin) String() string { return fmt.Sprintf("%g°K", k) } func (ft Feet) String() string { return fmt.Sprintf("%gft", ft) } func (m Meter) String() string { return fmt.Sprintf("%gm", m) } func (kg Kilogram) String() string { return fmt.Sprintf("%gkg", kg) } func (p Pound) String() string { return fmt.Sprintf("%glbs", p) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="filename">commonconv/conv.go</div> package commonconv func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } func CToK(c Celsius) Kelvin { return Kelvin(c + 273.15) } func FToK(f Fahrenheit) Kelvin { return Kelvin(5/9*(f-32) + 273.15) } func KToC(k Kelvin) Celsius { return Celsius(k - 273.15) } func KToF(k Kelvin) Fahrenheit { return Fahrenheit(5/9*(k-32) + 273.15) } func FtToM(ft Feet) Meter { return Meter(ft / 3.2808) } func MToFt(m Meter) Feet { return Feet(m * 3.2808) } func KgToP(kg Kilogram) Pound { return Pound(kg * 0.454) } func PToKg(p Pound) Kilogram { return Kilogram(p / 0.454) } |
2.6.2 パッケージ初期化
パッケージの初期化処理は func init() {...}
という特殊な関数を使うことができます。
変数の初期化程度なら、パッケージレベルで行えばいいのですが、処理が複雑になってくるとそれも難しいので、そういうときに init() を使うといいようです。
他の言語のクラスのコンストラクタのようなものですね。最初に必ず実行されます。
練習問題2.3
単一式ではなくループってことなので、こんなかんじ?
性能比較はやっていません…。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package popcount var pc [256]byte func init() { for i := range pc { pc[i] = pc[i/2] + byte(i&1) } } func PopCount(x uint64) int { count := 0 for i := 0; i < 8; i++ { count += int(pc[byte(x>>(uint(i)*8))]) } return count } |
練習問題2.4
ビットシフトしながらということなので、こんな感じでしょうか…。
PopCount()のみ書き出しています。他の部分は練習問題2.3と同じです。
1 2 3 4 5 6 7 |
func PopCount(x uint64) int { count := 0 for i := 0; i < 64; i++ { count += int(x>>uint(i)) & 1 } return count } |
練習問題2.5
式x&(x-1)
は最下位ビットをクリアする、という事実を使ってビット数を求める、ということで自身が無いですが、こんな感じで…
とりあえずは動いてます。
1 2 3 4 5 6 7 8 9 10 |
func PopCount(x uint64) int { count := 0 for x > 0 { if x != x&(x-1) { count++ x = x & (x - 1) } } return count } |
2.7 スコープ
宣言された変数などを参照できる範囲についての説明です。
ブロックは関数やループを囲んでいる {}
で囲われた一連の文のことになります。
ブロック内で宣言された変数などはブロック外からは見えません。
具体的な例を出しながら説明されているので、実際に動かしながら見てみるとわかりやすいと思います。
ここでは練習問題はなし!わーいw
2章から結構ボリュームあるなぁ、読み応えあるなぁ、という印象です。
3章は基本データ型について。ここも結構ボリューミィな感じなので、がんばります。