まえがき
Go言語はhttpサーバに使われることがおおいー>これは、現代のシステムがマイクロアーキテクチャに依存していることを示している。一つ一つのコンポーネントは単純な入出力に対応しているだけである。そのため、シンプルなgo言語が好まれるのだろう。
さらに、goの人気を上げているのが、並行処理の扱いやすさにある。 一般的な言語では、スレッドを立てて、スレッド間でなにか、スレッドセーフなコンテナを準備してやり取りをしないといけなかった。 しかし、goでは、goroutineとchannelを使って、スレッド間通信のようなものがかんたんにできるのがかなりいい。
以上の2つの要因によって、goではデータの流れがだいぶ見やすくなっていると思う。川上から川下へ流れていくがごとく、データの流れがとにかくつかみやすい。
goで特徴的なもう一つの機能は、インターフェースだろう。 これについては、後ほど説明する。
第一章 環境構築
https://go.dev/dl/から最新版をダウロード。tarで解凍。/usr/local/いかにgo/をおいて、そこにパスを通す。
echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile
source .profile
ここで疑問なのだが、どうやら、go/はホームディレクトリいかにもおいておかないとだめみたいなんだ。 というのも、ダウンロードしたモジュールやパッケージはホームディレクトリ以下のgo/に置かれるかららしい。 まあ、よくわからんよね。じゃあ、/usr/local/いかにおいたgoは何だったんだとね。
実行
go run hello.goでも実行できるし、 go buildで、コンパイルもできる。go自体はコンパイル言語。go runは直接実行しているように見えるが、内部的にはgo buildで一回ビルドしている。 ちなみに、go buildで出力される実行ファイルは.go前の部分。だから、hello.goをコンパイルすると、helloという実行ファイルが生成される。これを変えたい場合は、-oで実行ファイル名を変更することもできる。
モジュール
まあ、c++やcの分割コンパイルみたいなことができるって話で、モジュールを作ってみましょう、ということ。
mkdir hello-world
cd hello-world
go mod init hello-world
これで新たにgo.modというファイルが出来上がる。
go install
goはgo installコマンドでgoのバイナリをインストールできる便利なコマンドである。
ためしに、heyという、httpサーバのロードテストを行えるツールをインストールしてみましょう。
go install github.com/rakyll/hey@latest
デフォルトではホームの下の~/go/binにインストールされます。なので、ここにもパスを通す必要がありますね。 が、変えたかったら、$GOPATHを変えましょうって話ですね。まあ、変えることはあまりおすすめしませんが。 で、goで作ったファイルはこんな感じでかんたんに公開できるわけですね。
goのコードのフォーマット
無意味な宗教戦争を産まないために、かなりフォーマットに制限があるという話。こういうのは結構好き。例えば、
main(){
}
と
main ()
{
}
みたいな。goでは後者はコンパイルエラーになる。
開発ツールにgo fmtやgoimportsというものがあり、これはプログラマーが書いたソースをきれいにしてくれるフォーマッターである。 他にもstaticcheckという名のりんたーがある。 これは、まあ、コードを解析してくれるツールです。きれいなコードを生み出してくれるということで、ありがたい。
第二章
変数の定義について色々教えてくれています。これは実際に書いていくなかで勉強していくほうがいいのではないかと思っているのです。 ただ、変数の命名規則は結構勉強になった。 go言語ではスネークケースはあまり使わないと。これは俺が好んで使うやつだね。 comp_calcみたいな感じ。でもこれはあまり使われないらしい。で、キャメルケースのほうがいいって話。うん。たしかにそっちのほうが主流な気もしてきたな。 あと、goでいいなって思ったのは、定義した変数を全部使わないとコンパイルエラーが出るところ。これでいらない変数を全部排除できるって話です。
三章
配列、スライス
配列は、
var array [10]int
みたいな感じで宣言します。 二次元配列ももちろん作れて、
var x [2][3]int
みたいな感じで作れます。ただね、配列が使われることは殆どない。というのは、goでは、長さの違う配列は別の方として扱われるから。関数間で配列の受け渡しとかができないんですわ。代わりに使われるのが、スライスっていう、c++で言うところのvectorみたいな感じのやつよね。こっちのほうが使える。
var array []int
ってだけで、スライスができます。配列との違いは、大きさを指定していないところですね。これで動的配列ができます。っていうのがいいね。 lenって関数で配列やスライスの大きさを取得できます。
len array
map
totalwinds j:= map[string]int{}
読み書き可能なmapはこのように作らないとだめです。 で、読み書きは普通にこんな感じでやります。
totalwinds["god"] = 1;
みたいな感じです。で、マップからの削除ですが、
delete(m,"hello")
って感じで、キーを指定して削除します。
ちなみに、マップの中にスライスを入れることも可能で、こんな感じになります。
sliceMap := map[string][]int みたいな感じです。
構造体
type person struct {
name string
age int
pet string
}
4章
シャドーウィング。
まあ、go言語にも変数のスコープっていうのがあってね、あるブロックの外側で宣言された変数はそのブロックの内側でも利用可能であると。で、もし名前がかぶってしまったら、外側のやつは内側のやつで隠蔽されると。それをシャドーウィングっていうだけです。かんたんだね。
if文について
こんな感じで書きます。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(10)
if n == 0 {
fmt.Println("sukositiisasugimasu")
} else if n > 5 {
fmt.Println("ookisugimasu")
} else {
fmt.Println("iikannjidesu")
}
}
for文
全部で4つの形式のforがあるって話ですね。
- 標準的なfor
for i = 0; i < 10 ; i++ {
}
- whileみたいなもの
i := 1
for i < 10 {
fmt.Printf(i)
i = i * 2;
}
- rangeを使うもの。まあ、pythonとかでもよくあるやつだよね。
// patern3 for-range型
evenVals := []int{2, 4, 6, 8, 10, 12}
for i, v := range evenVals {
fmt.Println(i, v)
}
name2height := map[string]int{}
name2height["ray"] = 184
name2height["makoto"] = 177
name2height["ayu"] = 170
for k, v := range name2height {
fmt.Println(k, v)
}
これは、mapとか、スライスをイテレーションするときによく使うと思う。 一応マップでも書いてみるかね。
type Person struct {
name string
height float64
sex string
}
// patern3 map version
m := map[string]Person{}
m["makiko"] = Person{
name: "makiko",
height: 188,
sex: "femail",
}
m["makoto"] = Person{
name: "makoto",
height: 199,
sex: "male",
}
for k, v := range m {
fmt.Println(k, v)
}
5章、関数
まあ、今までの関数と同じですね。
func add(a int, b int) int {
c := a + b
return c
}
複数の返り値を返すことができるのが結構新しいかな。しかも静的片付けで。
func addAndMult(a int, b int) (int,int) {
add := a + b
mul := a * b
return add,mul
}
エラーも返せます。
で、一つだけ。goでは定義した変数は全部使わないといけないんだけど、例えば、上の関数でmulがいらないときはどうするかって話だけど、
a,m := addAndMult(4,5)
上のやつを
a, _ := addAndMult(4,5)
にする。つまり、_がその変数を無視するのに使われるやつ。
無名関数
まあ、その場で作れるラムダ関数みたいなものです。というか同じです。
func main() {
for i := 0; i < 10; i++ {
func(i int) {
fmt.Println(i)
}(i)
}
}
こんなかんじです。
5.3クロージャ (関数引数)
まあ、こんな感じで、関数に引数を渡すことはよくあるよね。node.jsでも関数にコールバック関数を渡すし、 c言語でもソートするときに、比較関数を渡すことがあるよね。それと同じよ。難しくはない。
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
people := []Person{
{"Pat", "Patterson", 37},
{"Tracy", "Bobbert", 23},
{"kami", "sama", 2023},
}
fmt.Println("initial data:", people)
sort.Slice(people, func(i int, j int) bool {
return people[i].LastName < people[j].LastName
})
fmt.Println("after sort:", people)
sort.Slice(people, func(i int, j int) bool {
return people[i].Age > people[j].Age
})
fmt.Println("after age:", people)
}
defer ここを見ているときに完全にgoに惚れてしまいました。
まあ、外部リソースはね、ちゃんと後処理をしないといけないんだけど、そのときにdeferを使うんです。そうすると後始末されます。
package main
import (
"io"
"log"
"os"
)
// easy cat function
func main() {
if len(os.Args) < 2 {
log.Fatal("file is not specified")
}
f, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
data := make([]byte, 2048)
for { // inf loop
count, err := f.Read(data)
os.Stdout.Write(data[:count])
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
}
os.Exit(0)
}
ただ、気をつけてほしいのが、deferが実行されるのは、その関数が終わりに差し掛かったときね。 まあ、遅延実行するのがdeferってことね。で、いつ実行されるかというと、最後に差し掛かったところというね。
で、この章を読んでいて、かなり気になったのが、goの標準ライブラリがデータベースをサポートしているってところ。これはやばい。まじで何なんだ??って感じ。いや、まじで実用的すぎて怖い。どのパッケージ化って言うと、
database/sql
っていうパッケージだね。
chap6 ポインタ
ここは、cとかをやってきた人ならすぐに理解できる部分だね。ただ、オブジェクトの生成に関して、普通ではないので、注意が必要。 どういうことかというと、c++では、shared_ptrとか、Newとかを使わないと、オブジェクトの生成とそのポインタへのオブジェクトを取得できなかったよね。しかし、goではshared_ptrがなくてもそれができると。どういうことか。ちょっとしたを見てほしい。
#include<iostream>
#include<memory>
typedef struct person {
int height;
int age;
} Person;
using namespace std;
int main(void) {
shared_ptr<Person> p = make_shared<Person>();
p->height = 100;
p->age = 25;
cout << p->height << " ," << p->age << std::endl;
cout << p << std::endl;
return 0;
}
typedef struct person {
int height;
int age;
} Person;
int main(void) {
Person *p = malloc(sizeof(Person)); // Allocate memory for a Person struct
if (p == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
p->height = 100; // Set the values for height and age
p->age = 20;
printf("%d,%d\n",p->height,p->age);
printf("%p\n",p);
// Use the data in p
// Don't forget to free the allocated memory when you're done with it
free(p);
return 0;
}
package main
import (
"fmt"
)
type human struct {
height int
age int
}
func main() {
h := &human{
height: 100,
age: 25,
}
fmt.Println(h)
h.height = 1000
h.age = 345
fmt.Println(h)
fmt.Println(h.height, h.age)
}
上の3つは全部同じことをやっているんだよね。どういうことかって言うと、 実態をオブジェクトとして生成し、あくまでポインタを持っている、というそういう話。 ちなみに、なぜオブジェクトをポインタとして持ちたいかというと、例えば複数のスレッドで共有したいからとか、そういう理由がありますね。
で、見てもらうと、go言語は、かなりかんたんにシェアードポインターを作れているのがわかる。しかもガーベッジコレクターがあるから、難しい管理も必要ないって、そういう話なわけよ。
chap7 メソッドとインファーフェース
chap8 エラー処理
chap9 モジュールとかパッケージとかについてだね
goでは、基本的に作ったソースコードはgithub上で公開することが前提だってことを覚えておいてくれ。 でだ。c++みたいにモジュール作りたいよね?モジュールというか、ライブラリだね。c++ではだいぶ面倒くさかったよね。.cppファイルと.hppファイルがあってさ、どっちもコンパイルして最後にリンクするっていう作業があるわけで。それが面倒臭すぎるからcmakeとかMakefileができたって話で。さて、作り方です。
まずはライブラリを作りたいディレクトリを作りましょう今回はqueueを作りたかったので、
mkdir queue
でいきました。つぎに、そのモジュールの名前を決めます。githubに上げることが前提で作られているので、こんな感じで行きましょう
go mod init martin-ray/queue
で、あとは実際にqueue.goを書いて、package名をqueueにしてって感じです。
で、呼び出し側なんですが、
import (
martin-ray/queue
)
ってやると、github.com/martin-ray/queueを探しに行ってしまうんですね。まだアップロードしていないんで、こっちは見られませーん。だから、変えないといけないんですね。 (githubへのアップロード方法は後ほど教えます)
go mod edit -replace=martin-ray/queue=/home/ray/path/to/module/
ってやって、go.modを変更してから、実際にモジュールをロードしてくる
go get martin-ray/queue
をやることでロードが完了します。完璧です。 ちょっともう一個やってみたいことがあって。データ構造って一つにまとめたいじゃないですか。やっぱり。だから、 /dtst/queueとか、/dtst/stackとかを作ってそれをインポートしたいんですよね。
chap10
chap15 ジェネリクス
これがc++のテンプレートですー。なかなかいいですよ。えー。これ、goのv1.18から追加されたらしいのですが、だいぶいいですよ。ええ。とりあえず、queueとか、stackとかを作っておきました。したのを見て作り方を学んでください。