はじめに
こちらの記事は以下Udemy講座で学んだ内容をベースに作成したものです。
https://www.udemy.com/course/golang-webgosql/
より詳しく学びたい方は実際に受講いただくのがいいかと思います。
既に受講された方は復習などでお使いください。
データをコンソールに表示
標準パッケージのfmt
を使用する。
Println
は最後に改行が入る。似た関数ではPrint
やPrintf
がある。
package main
import "fmt"
func main() {
fmt.Println("Hello World") // コンソールにHello Worldと表示される
}
Goの実行方法
go run
簡単な実行方法。
ファイルを指定するだけでプログラムを実行できる。
go run ファイル名
go run main.go // 例
go build
コンパイルしたファイルを作成する。その後作成されたファイルを実行することができる。
go build -o コンパイル後のファイル名 goファイル名
go build -o main main.go // 例
変数
明示的な定義
// 形式
var 変数名 型 = 初期値
// 例
var price int = 1000
var name string = "Golang”
初期値を省略することも可能で、この場合は型の初期値が入る
var price int // int型は0
var name string // string型は空白
暗黙的な定義
初期値から型推論されるため以下のように型指定も不要となる
// 形式
変数名 := 初期値
// 例
price := 1000
name := "Golang"
注意点としては暗黙的な定義は関数内でしか使えない
以下のような関数外ではエラーになる
:price = 1000 // エラー
func main() {
:name = "Golang" // OK
}
複数の変数をまとめて定義するとき
var (
price int = 1000
name string = "Golang"
)
// or
var price, name = 1000, "Golang"
// or
price, name := 1000, "Golang"
変数の代入
var price int = 1000
price = 2000 // 代入
fmt.Println(price) // 2000
定数
変数と異なり再代入ができないのが定数
const pageCount = 3
const fileName = "filename"
pageCount = 10 // エラー
定数も変数と同様にまとめて定義可能
const (
pageCount = 3
fileName = "filename"
)
型の種類
整数・浮動小数点・論理値・文字列型
// 整数型(int)
var i int = 1000
// 浮動小数点型(float)
// 64と32の違いは格納できる値の大きさ
var fl64 float64 = 3.14
var fl32 float32 = 3.14
// 論理値型(bool)
var t bool = true
var f bool = false
// 文字列型(string)
var s string = "Golang"
byte型
// byte型(uint8型)
var b = []byte{97, 98, 99}
fmt.Println(string(b)) // abc
byte型については以下記事がわかりやすいです
https://qiita.com/s_zaq/items/a6d1df0d1bf79f5bcc2a
配列型
複数の値を格納できる型
// 配列型の形式
[要素数]型{値, 値, 値, ...}
var arr1 = [3]int{1, 2, 3}
fmt.Println(arr1) // [1, 2, 3]
// 要素数を...と記載すると初期値の個数になる
var arr2 = [...]int{1, 2, 3} // arr2の型は[3]int
特徴は要素数を変えることができない、
そして要素数が違うと異なる型と判断されること
var arr1 = [3]int
var arr2 = [4]int
arri = arr2 // 要素数が異なり型が違うためエラー
スライス型
簡単に言うと、配列の要素数を変えれるバージョン
jsやphpといった他のプログラミング言語の配列と同じような型
// スライスの形式
[]型{値, 値, 値, ...} // 配列と違い要素数を指定しない
var s1 = []int{1, 2, 3}
s1 = append(s1, 4) // appendは要素を追加するメソッド
fmt.Println(s1) // [1, 2, 3, 4]
map型
// 形式
map[キーの型]値の型{"キー名": 値, ...}
var m = map[string]int{"apple": 100, "banana": 200}
// キーを指定して値を取得
m["apple"] // 100
interface型
あらゆる値に対応した型
var x interface{} // {}までが型名
// どの型の値でも代入できる
x = 1
x = 3.14
x = "string"
注意点としては計算などができない
var x interface{} = 2
sum := x + 2 // エラー
型変換
// float → int
var fl64 float64 = 3.14
int(fl64)
// int → float
var i int = 1
float64(i)
// string → int
var s string = "100"
i, _ = strconv.Atoi(s) // iの型はint
// int → string
var i int = 200
s := strconv.Itoa(i2) // sの型はstring
// string → byte[]
b :=[]byte("abc")
fmt.Println(b) // [97, 98, 99]
// byte[] → string
s := string([]byte{97, 98, 99})
fmt.Println(s) // "abc"
関数
// 形式
func 関数名(引数 型, ...) 返り値の型 {
// 処理
}
// 例
func Add(x int, y int) int {
return x + y
}
func main() {
i := Add(1, 2)
fmt.Println(i) // 3
}
無名関数
簡易的に、そして関数内でも記述できる
関数の引数に関数を渡すときにも使用する
add := func (x int, y int) int {
return x + y
}
add(1, 2) // 3
最後にカッコを記載して無名間数をそのまま実行することも可能
func (x int, y int) int {
return x + y
}(1, 2) // 3
for文
繰り返し処理を行うのに使用する
// 形式
for 初期化; 条件; 繰り返し式 {
// 処理
}
// 例
for i := 0; i < 10; i++ {
fmt.Print(i) // 0123456789
}
インデックスと値、キーと値を取得する方法としてrange
がある
nums := []int{1, 2, 3}
for index, value := range nums {
fmt.Printf("index: %d, value: %d\n", index, value)
// 出力結果
// index: 0, value: 1
// index: 1, value: 2
// index: 2, value: 3
}
// map
var m = map[string]int{"apple": 100, "banana": 200}
for key, value := range m {
fmt.Printf("key: %s, value: %d\n", key, value)
// 出力結果
// key: apple, value: 100
// key: banana, value: 200
}
defer
文の前にdeferをつけることで関数が終わったときの処理を登録できる
以下ではdefer文が最初に記述されているが、実際は最後に実行されていることがわかる
func main() {
defer fmt.Println("end")
fmt.Println("start")
// 出力結果
// start
// end
}
使用するケースとしてはファイルを開いたあとの、閉じ忘れを防ぐのがある。
閉じる処理がないとメモリリークに繋がるため。
file, _ := os.Create("./test.txt")
// ファイルを開いあとに、defer文を使って閉じる処理をすぐに記述することで、閉じ忘れを防ぐ
defer file.Close()
file.Write([]byte("Test\n"))
並行処理
goではgoルーチンを使用することで非同期処理を簡単に記述することが可能
以下の場合subメソッド内の処理が無限ループしているため、 subメソッド呼び出し後のMain loopが実行されない
func sub() {
for {
fmt.Println("Sub loop")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
sub()
for {
fmt.Println("Main loop")
time.Sleep(200 * time.Millisecond)
}
}
// 出力結果
// Sub loop
// Sub loop
// Sub loop
// ...
一方でsubメソッドの呼び出しに対してgoを付与しsub loopを非同期処理にすることで、両方のloopを並行して実行することができるようになる
func sub() {
for {
fmt.Println("Sub loop")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sub() <- ここにgoを追加するだけ
for {
fmt.Println("Main loop")
time.Sleep(200 * time.Millisecond)
}
}
// 出力結果
// Main loop
// Sub loop
// Sub loop
// Main loop
// ...
ポインタ
メモリ上で変数がどこにあるかを示すアドレス情報のこと
これにより変数の値を他の変数と共有することができる
いわゆる参照渡しをしている状態
i := 100
fmt.Println(i) // 100
// 値型のアドレス(&をつける)
fmt.Println(&i) // 0x140000a4008
// ポインタ型(型に*をつけて型指定する)
var p *int = &i // iのアドレスを代入
// ポインタ型のアドレス
fmt.Println(p) // 0x140000a4008
// ポインタ型の実体(*をつける)
fmt.Println(*p) // 100
// iのアドレスをpのポインタ型に代入したため、iとpは同じアドレスを参照している
// このため、iの値を変更するとpの値も変更される。逆も同様
i = 200
fmt.Println(*p) // pも200になる
*p = 300
fmt.Println(i) // iも300になる
基本的に関数の引数としてポインタ型が使われる
理由としては以下のように引数で渡した値を関数内で書き換えるため
func Double(x int) {
x = x * 2
}
func DoublePoint(x *int) {
*x = *x * 2
}
func main() {
var i int = 100
// iと引数として渡されたi(関数内ではx)は別物のとして扱われる
// このためiの値は変更されない
Double(i)
fmt.Println(i) // 100
// ポインタ型を引数に渡すことで、値を共有するためiの値を変更することができる
DoublePoint(&i)
fmt.Println(i) // 200
}
構造体(struct)
他言語でいうclassのようなもの
// 構造体
type User struct {
Name string
Age int
}
func main() {
// 構造体の定義
var user User = User{Name: "Taro", Age: 20}
fmt.Println(user) // {Taro 20}
// 書き換え
user.Name = "Jiro"
fmt.Println(user) // {Jiro 20}
}
structメソッド
他言語でいうclassメソッドのようなもの
type User struct {
Name string
Age int
}
// 定義はfuncの後に以下のように(変数名 ストラクト名)を記載すること
func (u *User) UpdateName(name string) {
u.Name = name
}
func main() {
var user User = User{Name: "Taro", Age: 20}
fmt.Println(user) // {Taro 20}
// userから繋げて関数を実行できる
// UpdateName関数内のuはこのuser変数の値になっている
user.UpdateName("Saburo")
fmt.Println(user) // {Saburo 20}
}
interfaceで異なる型を共通化する
interfaceの一般的な使い方
下記のUserとAdminは異なる型のため、ひとまとめにして同じ処理を実行するといったことが通常ではできない
しかしinterfaceを用いることで以下のように可能となる
// interfaceの定義
// getName() stringを持つ型はHasName型として扱うことができる
type HasName interface {
getName() string
}
type User struct {
Name string
Age int
}
func (u *User) getName() string {
return u.Name
}
type Admin struct {
Name string
Role int
}
func (u *Admin) getName() string {
return u.Name
}
func main() {
// UserとAdminは本来異なる型だが、両方ともgetName() stringメソッドを持っている
// このためHasNameインターフェイスを用いることで同じ型として扱うことができる
vs := []HasName{
&User{Name: "Taro", Age: 20},
&Admin{Name: "Hanako", Role: 1},
}
for _, v := range vs {
fmt.Println(v.getName())
}
}
変数・定数・メソッドを他のファイルから使用する
名前の先頭を大文字にすることで他ファイルから使用することができる
逆に小文字にすることでprivateにすることが可能
var PublicValue int // 他のファイルから使える
var privateValue int
func PublicMethod() { // 他のファイルから使える
}
func privateMethod() {
}
参考資料