複雑なデータ型 / 派生データ型【ポインタ、配列、構造体、パイプ、関数、スライス、インターフェース、マップ】#
ポインタ#
ポインタとは#
package main
import "fmt"
func main() {
var age int = 18
//*intはptrに対応するポインタ型を示す
//&ageはage変数のメモリ内のアドレスを示す
var ptr *int = &age
fmt.Println(ptr)
//&ptrはptrポインタ変数が存在するメモリ空間のアドレスを示す
fmt.Println(&ptr)
//*ptrはptrポインタが指し示すデータを示す
fmt.Println(*ptr)
}
まとめ:
&記号はメモリアドレスを取得することを示す
*記号はアドレスに基づいて値を取得することを示す
ポインタの 4 つの詳細#
1、ポインタを通じて指し示す値を変更できる
2、ポインタ変数が受け取るのは必ずアドレス値である
3、ポインタ変数のアドレスは不一致であってはならない
4、基本データ型にはそれぞれ対応するポインタ型がある
関数#
関数の導入#
関数の定義:特定の機能を達成するためのプログラム命令の集合を関数と呼ぶ。
役割:コードの再利用を促進し、コードの冗長性を減少させ、コードの保守性を向上させる。
基本構文#
Func 関数名 (形参リスト) (戻り値型リスト) {
実行文...
Return + 戻り値リスト
}
package main
import "fmt"
// 自定義cal関数、機能:2つの数を加算する
func cal(sum1 int, num2 int) int { //戻り値型リストが1つの場合は括弧を省略できる
var sum int = 0
sum += sum1
sum += num2
return sum
}
func main() {
var n int = 10
var m int = 20
s := cal(m, n)
fmt.Println(s)
}
詳細#
-
関数名
- 識別子命名規則に従う
- 最初の文字は数字であってはならない
- 最初の文字が大文字の場合は本パッケージと他のパッケージから呼び出せる(public に似ている)
- 最初の文字が小文字の場合は本パッケージからのみ呼び出せる(private に似ている)
-
形参リスト
- 個数:パラメータは 0 個、1 個、または n 個であることができる
- 役割:外部データを受け取る
-
戻り値型リスト
-
戻り値型リストが 0 個の場合はこの項目を省略する
-
戻り値型リストが 1 つの場合は括弧を省略できる
-
戻り値が受け取られたくない場合は_を使って無視できる
-
-
オーバーロードはサポートされていない
-
可変引数をサポートする
package main
import "fmt"
func test(args ...int) {
for j := 0; j < len(args); j++ {
fmt.Println(args[j])
}
}
func main() {
test()
fmt.Println("________________________")
test(3)
fmt.Println("________________________")
test(2, 5, 6, 9)
}
実行結果:
________________________
3
________________________
2
5
6
9
- 基本データ型と配列はデフォルトで値渡しであり、関数内で変更しても元の値には影響しない。
package main
import "fmt"
func ex(num int) {
num = 30
}
func main() {
var num int = 10
ex(num)
fmt.Println(num)
}
実行結果:
10
- 値渡しのデータ型で、関数内の変数が関数外の変数を変更したい場合は、& を使って変数のアドレスを渡し、関数内でポインタ方式で変数を操作する。
package main
import "fmt"
func ex(num *int) {
*num = 30
}
func main() {
var num int = 10
ex(&num)
fmt.Println(num)
}
実行結果:
30
- 関数もデータ型の一種であり、変数に代入でき、その変数を通じて関数を呼び出すことができ、関数も形参として呼び出すことができる。
package main
import "fmt"
// 関数を定義
func re(num int) {
fmt.Println(num)
}
// 別の関数を定義し、別の関数を形参として受け取る
func te(num1 int, num2 float32, testFunc func(int)) {
fmt.Println("*************")
}
func main() {
a := re
fmt.Printf("aの型は:%T,re関数の型は:%T", a, re) //aの型は:func(int),re関数の型は:func(int)
a(10) //test(10)と等価
te(1, 2.5, a) //te(1, 2.5, re)と等価
}
- Go はカスタムデータ型をサポートしている(既存のデータ型に別名を付けることに相当)
基本構文:type カスタムデータ型名データ型
例:type myInt int 【この時の myInt は int と等価】
例:type mySum func (int, int) int 【この時の mySum は func (int, int) int と等価】 - 関数の戻り値に名前を付けることをサポートする
package main
func test1(num1 int, num2 int) (int, int) {
result1 := num1 + num2
result2 := num1 - num2
return result1, result2
}
// 関数の戻り値に名前を付けることができる
func test2(num1 int, num2 int) (sum int, sub int) {
sum = num1 + num2
sub = num1 - num2
return
}
パッケージの導入#
パッケージを使用する理由#
- 関数を分類し、異なる元ファイルに配置できる
- 同名問題を解決する、異なるパッケージ内の関数は同名にでき、パッケージで区別する
詳細#
- 宣言するパッケージは所在ファイルのフォルダ名と同じにすることを推奨
- Main パッケージはプログラムのエントリーパッケージであり、一般的に main 関数はこのパッケージに存在する
- パッケージ構文:package パッケージ名
- パッケージをインポートする構文:import "パッケージのパス",複数のパッケージを一度にインポートする場合は {} を使う
- 異なるパッケージの関数を呼び出す際は、まず所在パッケージを特定する必要がある
- 関数名、変数名の最初の文字が大文字でなければ他のパッケージからアクセスできない
- 同じディレクトリ内に重複する関数があってはならない
- パッケージ名とフォルダ名は異なってもよい
- 同じディレクトリ内の同レベルのファイルは同じパッケージに属する
- パッケージに別名を付けることができ、一度別名を付けると元のパッケージ名は使用できない
Init 関数#
初期化関数で、いくつかの初期化操作を行うことができる。
匿名関数#
特定の関数を一度だけ使用したい場合は、匿名関数を考慮することができる
匿名関数の使用方法#
- 匿名関数を定義する際に直接呼び出す。この方法では一度だけ呼び出すことができる
package main
import "fmt"
func main() {
//匿名関数を定義:定義と同時に呼び出す
sum := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
fmt.Println(sum)
}
- 匿名関数を変数(すなわち関数変数)に代入し、その変数を通じて呼び出す
package main
import "fmt"
func main() {
//匿名関数を定義し、変数に代入
sub := func(num1 int, num2 int) int {
return num1 - num2
}
result := sub(20, 10)
fmt.Println(result)
}
クロージャ#
クロージャとは#
クロージャは、関数とそれに関連する参照環境の組み合わせである。
package main
import "fmt"
// fucSum関数を定義し、引数は空
// fucSum関数は関数を返し、その関数の引数はint型、戻り値もint型
func fucSum() func(int) int {
var sum int = 0
return func(p int) int {
sum += p
return sum
}
}
// クロージャはここでfucSum関数が返す関数と変数sumの組み合わせのようなものである
func main() {
f := fucSum()
fmt.Println(f(1)) //1
fmt.Println(f(2)) //3
fmt.Println(f(3)) //6
fmt.Println(f(4)) //10
}
> 匿名関数が参照する変数はメモリに保持され、常に使用できるため、メモリ消費が大きくなるので、乱用してはならない
クロージャの本質#
クロージャ = 匿名関数 + 参照された変数 / パラメータ
Defer キーワード#
関数が完了した後にリソースを即座に解放するためのもの
package main
import "fmt"
func add(m int, n int) int {
//golangでは、deferキーワードに遭遇すると、deferの後の文は即座に実行されず、deferの後の文がスタックにプッシュされ、関数の後の文を続けて実行する
//スタックの特徴:先入れ後出し
//関数が完了した後、スタックから文を取り出して実行し、先入れ後出しのルールに従って文を実行する
defer fmt.Println(m)
defer fmt.Println(n)
fmt.Println(m + n)
return m + n
}
func main() {
fmt.Println("結果は:", add(10, 20))
}
実行結果:
30
20
10
結果は: 30
defer の適用シーン#
defer は遅延実行メカニズム(関数が完了した後に defer でスタックにプッシュされた文を実行する)を持っているため、リソースを閉じる必要がある場合は、defer を使うと便利である。
システム関数#
文字列関連関数#
文字列の長さをカウントする
len(str)
文字列を走査する
r := []rune(str)
文字列を整数に変換する
n, err := strconv.Atoi("66")
整数を文字列に変換する
str := strconv.Itoa(88)
文字列に別の文字列が含まれているかを判断する
strings.Contains("javaandgolang", "go")
特定の文字列が別の文字列に出現する回数をカウントする
strings.Count("javaandgolang", "a")
大文字小文字を区別せずに 2 つの文字列が一致するかを判断する
fmt.Println(strings.EqualFold("go", "Go"))
文字列が別の文字列の中で最初に出現するインデックス値を返す
strings.Index("javaandgolang", "a")
存在しない場合は - 1 を返す
文字列の置換
strings.Replace("goandjavagogo", "go", "golang", n)
N は置換する数を指定し、n が - 1 の場合はすべてを置換する
指定した文字列で別の文字列を分割して文字列配列にする
strings.Split("go-java-python", "-")
文字列の大文字小文字を置換する
strings.ToLower("GO")
strings.ToUpper("go")
文字列の左右の空白を取り除く
strings.TrimSpace(" go and java ")
文字列の左右の指定した文字を取り除く
strings.Trim("~go~", "~")
文字列の左側の指定した文字を取り除く
strings.TrimLeft("~go", "~")
文字列の右側の指定した文字を取り除く
strings.TrimRight("go~", "~")
文字列が指定した文字列で始まるかを判断する
strings.HasPrefix("https://libilibi.eu.org", "https")
文字列が指定した文字列で終わるかを判断する
strings.HasSuffix("https://libilibi.eu.org", "org")
日付と時間関連関数#
package main
import (
"fmt"
"time"
)
func main() {
//現在のローカル時間を取得するNow()、返り値は構造体で、型はtime.Time
now := time.Now()
fmt.Println(now) //2023-06-27 23:11:41.8027164 +0800 CST m=+0.001038201
fmt.Printf("%T \n", now) //time.Time
//構造体のメソッドを呼び出す:
fmt.Printf("年:%v \n", now.Year()) //年:2023
fmt.Printf("月:%v \n", now.Month()) //月:June
fmt.Printf("月:%v \n", int(now.Month())) //月:6
fmt.Printf("日:%v \n", now.Day()) //日:27
fmt.Printf("時:%v \n", now.Hour()) //時:23
fmt.Printf("分:%v \n", now.Minute()) //分:20
fmt.Printf("秒:%v \n", now.Second()) //秒:56
fmt.Println("_____________________________________________")
//Printfは文字列を直接出力する
fmt.Printf("現在年月日:%d-%d-%d 時分秒:%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
//Sprintfは文字列を取得し、呼び出すことができる
dateStr := fmt.Sprintf("現在年月日:%d-%d-%d 時分秒:%d:%d:%d \n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
fmt.Println(dateStr) //現在年月日:2023-6-27 時分秒:23:25:37
fmt.Println("_____________________________________________")
dateStr2 := now.Format("2006/01/02 15/04/05") //各位置の数字は固定でなければならず、省略はできるが変更はできない
fmt.Println(dateStr2) //2023/06/27 23/38/24
}
組み込み関数#
組み込み関数は builtin パッケージにあり、パッケージをインポートせずに直接使用できる
一般的な組み込み関数:
- Len 関数:バイト単位で文字列の長さをカウントする
- New 関数:メモリを割り当て、主に値型を割り当てる
package main
import "fmt"
func main() {
//newはメモリを割り当て、new関数の引数は数値ではなく型であり、new関数の返り値は対応する型のポインタ
num := new(int)
fmt.Printf("numの型:%T,numの値は:%v,numのアドレスは:%v,numポインタが指し示す値は:%v", num, num, &num, *num) //numの型:*int,numの値は:0xc0000a6058,numのアドレスは:0xc0000ca018,numポインタが指し示す値は:0
}
- Make 関数:メモリを割り当て、主に参照型を割り当てる
配列#
配列の宣言#
var 変数名 [インデックス値] データ型
package main
import (
"fmt"
)
func main() {
//変数名をscores、インデックス値を5(つまり5つの変数を格納できる)とするint型の配列を宣言
var scores [5]int
scores[0] = 89
scores[1] = 79
scores[2] = 92
scores[3] = 95
scores[4] = 88
var sum int
//合計を求める
for i := 0; i < len(scores); i++ {
sum += scores[i]
}
//平均を求める
avg := sum / len(scores)
fmt.Println(sum) //443
fmt.Println(avg) //88
}
復習 for rang
package main
import "fmt"
func main() {
var scores [5]int
//配列内の各変数の値を手動で入力する
for i := 0; i < len(scores); i++ {
fmt.Printf("第%dの値を入力してください------", i+1)
fmt.Scanln(&scores[i])
}
for m, n := range scores {
fmt.Printf("第%dの値は:%d\n", m+1, n)
}
}
配列の初期化#
package main
import "fmt"
func main() {
//第一の方法
var a [3]int = [3]int{1, 2, 3}
fmt.Println(a)
//第二の方法
var b = [3]float32{2.5, 3.6, 5.5}
fmt.Println(b)
//第三の方法
var c = [...]byte{4, 5, 6}
fmt.Println(c)
//第四の方法
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}
fmt.Println(d)
}
実行結果:
[1 2 3]
[2.5 3.6 5.5]
[4 5 6]
[wang jia ming]
配列の注意事項#
- 配列の長さは配列型の一部である
package main
import "fmt"
func main() {
//第一の方法
var a [3]int = [3]int{1, 2, 3}
fmt.Printf("配列の型は:%T\n", a)
//第二の方法
var b = [2]float32{2.5, 3.6}
fmt.Printf("配列の型は:%T\n", b)
//第三の方法
var c = [...]byte{4, 5, 6, 7}
fmt.Printf("配列の型は:%T\n", c)
//第四の方法
var d = [...]string{1: "jia", 2: "ming", 0: "wang"}
fmt.Printf("配列の型は:%T\n", d)
}
実行結果:
配列の型は:[3]int
配列の型は:[2]float32
配列の型は:[4]uint8
配列の型は:[3]string
- Go では配列は値型であり、デフォルトでは値渡しが行われるため、値のコピーが行われる。そのため、他の関数で配列を変更したい場合は、参照渡し(すなわちポインタ方式)を使用する必要がある。
package main
import "fmt"
func main() {
var arr = [3]int{7, 8, 9}
test1(arr)
fmt.Println(arr) //[7,8,9] test関数でarr[0]に値を代入しても配列は変わらない
test2(&arr) //配列のアドレスを渡す
fmt.Println(arr) //[5,8,9] この時配列は変わった
}
func test1(arr [3]int) { //配列の値を取り出して代入する
arr[0] = 5
}
func test2(arr *[3]int) { //配列のアドレスを取り出して代入する
arr[0] = 5
}
二次元配列の宣言#
var 変数名 [インデックス値][インデックス値] データ型
package main
import "fmt"
func main() {
var a [2][3]int
fmt.Println(a) //[[0 0 0] [0 0 0]] この配列は2つの長さ3の一次元配列から構成されている
}
二次元配列の初期化#
package main
import "fmt"
func main() {
var arr = [2][3]string{{"a", "b", "c"}, {"a", "b", "c"}}
fmt.Println(arr)
}
二次元配列の走査#
package main
import "fmt"
func main() {
var arr = [3][3]string{{"a", "b", "c"}, {"d", "e", "f"}, {"g", "h", "i"}}
//二重forループ
for i := 0; i < len(arr); i++ {
for j := 0; j < 3; j++ {
fmt.Println(arr[i][j])
}
}
println("______________________________________________________")
//二重for rang インデックスが必要ない場合はアンダースコアで無視できる
for _, m := range arr {
for _, n := range m {
fmt.Println(n)
}
}
}
実行結果:
a
b
c
d
e
f
g
h
i
______________________________________________________
a
b
c
d
e
f
g
h
i
スライス#
スライスとは#
スライスは構造体で、len は基底配列の長さ、cap は予め割り当てられたメモリ空間の総長さ(すなわち基底配列と予め割り当てられたメモリの長さの合計)を示し、予め割り当てられたメモリの長さは可変であり、一度超えると、予め割り当てられた総メモリ長さを単位にして再度増加する。
スライスの宣言と初期化#
var 変数名 [] データ型
package main
import "fmt"
func main() {
var arr []int
arr = make([]int, 3, 5)
arr[0], arr[1], arr[2] = 7, 8, 9
//基底配列の長さと予め割り当てられたメモリ空間の長さを表示
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 5
//スライスを表示
fmt.Println(arr) //[7 8 9]
}
スライスの詳細#
package main
import "fmt"
func main() {
var arr = make([]int, 3, 4)
arr[0], arr[1], arr[2] = 7, 8, 9
//基底配列の長さと予め割り当てられたメモリ空間の長さを表示
fmt.Printf("%d %d\n", len(arr), cap(arr)) //3 4
//スライスを表示
fmt.Println(arr) //[7 8 9]
//スライスarrをスライスbrrに代入
brr := arr
fmt.Printf("%p %p\n", arr, brr) //0xc00000c330 0xc00000c330 これら2つのスライスのメモリアドレスは同じで、同じメモリアドレスを共有している
//arrスライスの基礎の上に10を追加し、予め割り当てられたメモリに格納してbrrスライスに代入
brr = append(arr, 10)
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //4 4 [7 8 9 10] この時基底配列が変更され、予め割り当てられたメモリが満杯になった
//brrスライスの元の基礎の上に11を追加すると、予め割り当てられたメモリが不足し、以前のメモリの総長さ4に新たに長さ4のメモリを割り当てる
brr = append(brr, 11)
fmt.Printf("%d %d %d\n", len(brr), cap(brr), brr) //5 8 [7 8 9 10 11] この時基底配列が変更され、割り当てられたメモリの総長さも変更された
fmt.Printf("%p %p\n", arr, brr) //0xc000012180 0xc00000e3c0 この時メモリアドレスも変更され、共有されなくなった
}
スライスの走査#
package main
import "fmt"
func main() {
var arr = make([]int, 3, 4)
arr[0], arr[1], arr[2] = 7, 8, 9
for i, value := range arr {
fmt.Printf("%p %p %d %d\n", &value, &arr[i], value, arr[i])
}
}
実行結果:
0xc0000a6058 0xc0000d2020 7 7
0xc0000a6058 0xc0000d2028 8 8
0xc0000a6058 0xc0000d2030 9 9
走査過程で value のメモリアドレスは変わらない
マップ#
マップの宣言と初期化#
var 変数名 map [キーのデータ型] 値のデータ型
package main
import "fmt"
func main() {
var m map[string]int
fmt.Println(m)
}
マップの詳細#
package main
import "fmt"
func main() {
var m map[string]int
m = map[string]int{"a": 7, "b": 8, "c": 9}
m["a"] = 6
fmt.Println(m["a"])
//mからaにマッピングされた値を削除する、削除したがデフォルト値0が返る
delete(m, "a")
fmt.Println(m["a"])
//mのaにマッピングされた値が本当に削除されたかを判断する
if v, exists := m["a"]; exists { //vとexistsを初期化し、m["a"]に等しく、existsが存在するかを判断する
fmt.Println(v)
} else {
fmt.Println("m[\"a\"]は削除されました")
}
}
実行結果:
6
0
m["a"]は削除されました
マップの走査#
package main
import (
"fmt"
)
func main() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Println(key, value)
}
}
実行結果:
a 1
b 2
c 3
構造体#
構造体の定義と運用#
package main
import "fmt"
type Teacher struct {
Name string
Age int
School string
}
type User struct {
Name string
Age int
School string
}
func main() {
t := Teacher{"張三", 18, "清華"}
fmt.Println(t) //{張三 18 清華}
u := *new(User)
u.Name = "李四"
u.Age = 19
u.School = "北大"
fmt.Println(u) //{李四 19 北大}
}
インターフェース#
インターフェースの定義と運用#
インターフェースは一連の行動規範の集合である
package main
import "fmt"
//Mathインターフェースは規則を定義できる
type Math interface {
Formula(int, int) int
}
func test(math Math) {
m := math.Formula(6, 3)
fmt.Println(m)
}
func main() {
//AddとSubはMathインターフェースの具体的な実装である
m := Add{}
n := Sub{}
test(m) //9
test(n) //3
}
type Add struct {
}
//構造体にメソッドを追加
func (Add) Formula(a int, b int) int {
return a + b
}
type Sub struct {
}
//構造体にメソッドを追加
func (Sub) Formula(a int, b int) int {
return a - b
}
空インターフェース#
空インターフェースは直接 interface {} と書くことができ、あらゆる行動規範を受け入れ(すなわちすべてを包容し)、自由に代入できる
package main
import "fmt"
func main() {
var inter interface{}
var str = "golang"
inter = str
fmt.Println(inter) //golang
var a = 0
inter = a
fmt.Println(inter) //0
var f = true
inter = f
fmt.Println(inter) //true
}