1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golang入門記事の没ネタサンプルコード

Last updated at Posted at 2020-01-10

Golang入門中です。

以前、以下の記事を書きました。
Golang入門:AtCoderなどの競技プログラミング問題を解き始めるための基本構文+その他おまけTips

記事に載せようかと思い書いてみたけれど、
趣旨から外れるため除外したサンプルコードをせっかくなのでまとめて公開したいと思います。

入門者によるざっくり試してみたコードの羅列ですが、参考になれば幸いです。

#型宣言

基本の型から独自の型を宣言できます。

newType01.go
package main

import (
	"fmt"
)

// intから新しい型を宣言
type aInt int
type bInt int

func main() {
	var a aInt = 5
	var b bInt = 55
	hoge(a, b)

	// 数値はaInt型,bInt型に代入できるので実行可能
	hoge(5, 55)

	// intだとエラーになる
	// エラー例:
	// # command-line-arguments
	// .\newType01.go:19:6: cannot use i (type int) as type aInt in argument to hoge
	// .\newType01.go:19:6: cannot use j (type int) as type bInt in argument to hoge
	// var i int = 5
	// var j int = 55
	// hoge(i, j)

	// 別の型として扱われるので、引数の順番を逆にするとエラーになる
	// エラー例:
	// # command-line-arguments
	// .\newType01.go:16:6: cannot use b (type bInt) as type aInt in argument to hoge
	// .\newType01.go:16:6: cannot use a (type aInt) as type bInt in argument to hoge
	// hoge(b, a)

	// 型に関連付けられたメソッドを実行
	a.show()

}

func hoge(x aInt, y bInt) {
	fmt.Printf("x :%T, %v\n", x, x)
	fmt.Printf("y :%T, %v\n", y, y)
}

// 新しい型に関数を関連付けられる
// 型の変数をレシーバ、関数をメソッドという
func (a aInt) show() {
	fmt.Println(a)
}
実行結果
$ go run newType01.go
x :main.aInt, 5
y :main.bInt, 55
x :main.aInt, 5
y :main.bInt, 55
5

#構造体
型を組み合わせて構造体が作れます。

typeStruct01.go
package main

import (
	"fmt"
)

type vector struct {
	x, y int
}

func main() {
	var v1 vector
	v1.x = 5
	v1.y = 55
	fmt.Printf("v1: %T, %v\n", v1, v1)

	v2 := vector{1, 2}
	fmt.Printf("v2: %T, %v\n", v2, v2)

	fmt.Printf("subXY(): %v\n", v1.subXY())
	fmt.Printf("addXY(): %v\n", v2.addXY())
}

// 型宣言と同様にメソッドを定義できる
func (v vector) addXY() int {
	return v.x + v.y
}

func (v vector) subXY() int {
	return v.y - v.x
}
実行結果
$ go run typeStruct01.go
v1: main.vector, {5 55}
v2: main.vector, {1 2}
subXY(): 50
addXY(): 3

#遅延実行
deferを使うと関数内で、最後に実行される処理を宣言できます。

defer01.go
package main

import "fmt"

func main() {
	// 関数の最後に実行される
	// 複数宣言した場合、後に宣言したdeferが先に実行される
	defer fmt.Println("a defer")
	defer fmt.Println("b defer")
	defer hoge()
	defer fmt.Println("c defer")
	fmt.Println("main")
}

func hoge() {
	defer fmt.Println("hoge defer")
	fmt.Println("hoge")
}
実行結果
$ go run defer01.go
main
c defer
hoge
hoge defer
b defer
a defer

#例外処理
Golangには例外処理の構文(try ~ catchなど)がありません。
替わりに関数から返されるerrorインターフェースをハンドリングします。

error01.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	e()
}

func e() {
	n, err := strconv.Atoi("aaa")
	// 関数が返す値が判定して例外処理する
	if err != nil {
		fmt.Printf("type: %T\nerr: %v\n", err, err)
		return
	}
	fmt.Println(n)
}
実行結果
$ go run error01.go
type: *strconv.NumError
err: strconv.Atoi: parsing "aaa": invalid syntax

typeはNumErrorですが、NumErrorにはerrorが含まれているっぽいです。

strconv.atoi.go
// 抜粋
type NumError struct {
	Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
	Num  string // the input
	Err  error  // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.)
}

#ポインタ
Golangではポインタがあります。

pointer01.go
package main

import (
	"fmt"
)

func main() {
	// &でアドレスを取得
	var n int = 10
	fmt.Println("nのアドレス: ", &n)
	fmt.Println("nの値: ", n)
	fmt.Println()

	//int型のポインタ変数宣言(アドレスを格納できる)
	var pointer *int

	// nのアドレスを代入
	pointer = &n
	fmt.Println("pointerの値: ", pointer)
	// アドレスから値取得
	fmt.Println("pointerの中身: ", *pointer)
	fmt.Println()

	a, b := 1, 1
	addOneByValue(a)      // 値渡しで加算
	addOneByPointer(&b)   // 参照渡しで加算
	fmt.Println("a: ", a) // 値渡しなので元の変数に影響なし
	fmt.Println("b: ", b) // 参照渡しなので変更される
	fmt.Println()

	s := []int{1, 2, 3}
	fmt.Println("sのアドレス: ", &s) // スライスは&でアドレス確認できない
	fmt.Printf("sのアドレス: %p\n", s)
	slicePointer(s) //スライスは参照渡し

}

// 値渡しの関数
func addOneByValue(a int) {
	a = a + 1
}

// ポインタ渡し(参照渡し)の関数
func addOneByPointer(a *int) {
	*a = *a + 1
}

// スライスを引数にした場合の渡し方の確認
func slicePointer(s []int) {
	fmt.Printf("sのアドレス: %p\n", s)
}
実行結果
$ go run pointer01.go
nのアドレス:  0xc0000140a0
nの値:  10

pointerの値:  0xc0000140a0
pointerの中身:  10

a:  1
b:  2

sのアドレス:  &[1 2 3]
sのアドレス: 0xc000016180
sのアドレス: 0xc000016180

#インターフェース
インターフェースを使用することで、ポリモーフィズムを実装することができます。

interface01.go
package main

import (
	"fmt"
)

type person interface {
	// say()を実装するとインターフェースを実装したことになる
	say()
}

func personSay(p person) {
	p.say()
}

type p1 struct{}

func (a p1) say() {
	fmt.Println("aaaaa")
}

type p2 struct{}

func (b p2) say() {
	fmt.Println("bbbbb")
}

type p3 struct{}

func (c p3) nosay() {
	fmt.Println(". . .")
}

// インターフェースを使うと、どの型も引数に取るれる、返せる関数が作れる
func personShout(someone interface{}) interface{} {
	_, ok := someone.(person)
	if !ok {
		fmt.Print("あああああ")
		return "aaaaa"
	} else {
		fmt.Print("ゴゴゴゴゴゴ")
		return 55555555
	}
}

func main() {
	a := new(p1)
	b := new(p2)
	// interfaceに関連づいたメソッド経由で実行
	personSay(a)
	personSay(b)

	c := new(p3)
	// personSay(c) // sayを実装していないのでエラーになる
	// エラー例
	// # command-line-arguments
	// .\interface01.go:41:11: cannot use c (type *p3) as type person in argument to personSay:
	//         *p3 does not implement person (missing say method)
	c.nosay() // メソッドとしては実行できる

	//
	fmt.Println(personShout(a))
	fmt.Println(personShout(c))
}
実行結果
$ go run interface01.go
aaaaa
bbbbb
. . .
ゴゴゴゴゴゴ55555555
あああああaaaaa

#並行処理
Golangと言えばで必ず上がるであろうGolangの強みです。
比較的簡単に並行処理の記述が可能になっています。
(そもそも並行処理が難しいですが。。)

##goroutine
go <関数>するとgoroutineと呼ばれる軽量なスレッドで<関数>が起動します。

goroutine01.go
package main

import (
	"fmt"
	"time"
)

func main() {
	// 呼び出し側の処理時間内に終わるため最後まで実行される
	go subfunc01()
	// 10~20まで出力する関数だが、呼び出し側の処理が終了すると終了する
	go subfunc02()
	for i := 100; i <= 103; i++ {
		fmt.Println(i)
		time.Sleep(1 * time.Second)
	}
	fmt.Println("end main")
}

func subfunc01() {
	for i := 0; i < 3; i++ {
		fmt.Println(i)
		time.Sleep(1 * time.Second)
	}
	fmt.Println("end subfunc01")
}

func subfunc02() {
	for i := 10; i < 20; i++ {
		fmt.Println(i)
		time.Sleep(1 * time.Second)
	}
	fmt.Println("end subfunc02")
}
実行結果
$ go run goroutine01.go
100
0
10
1
11
101
12
102
2
103
end subfunc01
13
14
end main

##Channel
golangではChannelを使用して、各goroutine間でメッセージの送受信ができます。
サンプルを書いてる最中に、「これ範囲外だな」と思い書ききらなかったのでサンプルがありません。。(´・ω・`)

#外部パッケージの利用
他の言語同様にGolangでも公開されているパッケージの利用ができます。
外部パッケージを使用する場合はgo get <パッケージ>で取得します。

ここではmysqlのドライバをgetしてDBにアクセスしてみます。
使用する外部パッケージ(mysql用のドライバ):https://github.com/go-sql-driver/mysql

※以降はIPアドレス:192.168.10.10が割り当てられた環境で実施しています。

mysqlのドライバ取得
$ go get -u github.com/go-sql-driver/mysql
フォルダ構成
# GOPATHは「go env GOPATH」で確認
<GOPATH>/src/dbsample
 └ db.go
想定するテーブル
create table test_db.goods
(
  goods_id bigint not null auto_increment,
  name varchar( 255 ) not null,
  category varchar( 255 ),
  memo varchar( 255 ),
  index (goods_id),
  primary key (goods_id)
);

insert into test_db.goods (name, category) values ("PC0001", "PC");
insert into test_db.goods (name, category) values ("PC0002", "PC");
insert into test_db.goods (name, category) values ("MB0001", "携帯電話");
insert into test_db.goods (name, category) values ("MB0002", "携帯電話");
db.go
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

type Goods struct {
	goodsId  int    `db:goods_id`
	name     string `db:name`
	category string `db:category`
	memo     string `db:memo`
}

func main() {
	whereCategory := "PC"
	goodsRows := selectQuery(whereCategory)
	// *sql.Rows型からGoodsのスライスに詰め替えている。
	var goodsSlice []Goods
	for goodsRows.Next() {
		var goods Goods
		goodsRows.Scan(&goods.goodsId, &goods.name, &goods.category, &goods.memo)
		goodsSlice = append(goodsSlice, goods)
	}
	fmt.Println(goodsSlice)
}

func selectQuery(whereCategory string) *sql.Rows {
	// DBへの操作は標準パッケージのdatabase/sqlを使用。
	// DB接続(ホスト:192.168.10.10、DB:test_dbの場合)
	db, err := sql.Open("mysql", "master:master@tcp(192.168.10.10:3306)/test_db?charset=utf8mb4")
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	// ステートメント作成
	query := "SELECT * FROM goods;"
	stmt, err := db.Prepare(query)
	if err != nil {
		panic(err.Error())
	}
	defer stmt.Close()

	// クエリの実行
	rows, err := stmt.Query()
	if err != nil {
		panic(err.Error())
	}
	return rows
}
実行結果
$ go run db.go
[{1 PC0001 PC } {2 PC0002 PC }]

#標準パッケージのみでWebサーバー作成
標準パッケージで簡単なWebサーバーを作成してみます。
テンプレート機能もあります。簡単にですがコード例を紹介します。

※以降はIPアドレス:192.168.10.10が割り当てられた環境で実施しています。

フォルダ構成
sampleweb
 └ helloserver.go
 └ tmpl01.html
helloserver.go
package main

import (
	"fmt"
	"html/template"
	"net/http"
)

func main() {
	fmt.Println("Start Sample Server")
	server := http.Server{
		Addr: ":8080",
	}
	http.HandleFunc("/", index)
	server.ListenAndServe()
}

func index(w http.ResponseWriter, r *http.Request) {
	t, err := template.ParseFiles("tmpl01.html")
	if err != nil {
		fmt.Println("template parse error.", err)
	}

	helloworldSlice := []string{"hello", "world"}

	err = t.Execute(w, helloworldSlice)
	if err != nil {
		fmt.Println("template execute error.", err)
	}
}
tmpl01.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <body>
    <ul>
    {{ range . }}
      <li>{{ . }}</li>
    {{ else }}
      データなし
    {{ end}}
    </ul>
  </body>
</html>
実行結果
$ go run helloserver.go
Start Sample Server

(停止する場合はCtrl + C)

別コンソールからcurlを叩いてみます。

実行結果
$ curl 192.168.10.10:8080
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample</title>
</head>
<body>
  <body>
    <ul>

      <li>hello</li>

      <li>world</li>

    </ul>
  </body>
</html>

ブラウザでアクセスすると以下のように表示される。

2020-01-10_10h06_23.png

#おわり
Golangはシンプルで構文も少ない方だと思いますが、まだまだ勉強することが多く奥が深いです。
着実にできることを増やしていきたいです。

今回は以上です。

#参考
The Go programming Language
Go言語.com
The Go Blog 日本語訳
プログラミング経験者がGo言語を本格的に勉強する前に読むための本
はじめてのGo言語
Go の並行処理

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?