banner
音小董

哩哔哩哔

这世上的热闹出自孤单

第4章:複雑なデータ型

複雑なデータ型 / 派生データ型【ポインタ、配列、構造体、パイプ、関数、スライス、インターフェース、マップ】#

ポインタ#

ポインタとは#

指针.png

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)  
}
詳細#
  1. 関数名

    • 識別子命名規則に従う
    • 最初の文字は数字であってはならない
    • 最初の文字が大文字の場合は本パッケージと他のパッケージから呼び出せる(public に似ている)
    • 最初の文字が小文字の場合は本パッケージからのみ呼び出せる(private に似ている)
  2. 形参リスト

    • 個数:パラメータは 0 個、1 個、または n 個であることができる
    • 役割:外部データを受け取る
  3. 戻り値型リスト

    • 戻り値型リストが 0 個の場合はこの項目を省略する

    • 戻り値型リストが 1 つの場合は括弧を省略できる

    • 戻り値が受け取られたくない場合は_を使って無視できる

  4. オーバーロードはサポートされていない

  5. 可変引数をサポートする

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   
  1. 基本データ型と配列はデフォルトで値渡しであり、関数内で変更しても元の値には影響しない。
package main  
  
import "fmt"  
  
func ex(num int) {  
num = 30  
}  
func main() {  
var num int = 10  
ex(num)  
fmt.Println(num)  
}

実行結果:

10
  1. 値渡しのデータ型で、関数内の変数が関数外の変数を変更したい場合は、& を使って変数のアドレスを渡し、関数内でポインタ方式で変数を操作する。
package main  
  
import "fmt"  
  
func ex(num *int) {  
*num = 30  
}  
func main() {  
var num int = 10  
ex(&num)  
fmt.Println(num)  
}

実行結果:

30
  1. 関数もデータ型の一種であり、変数に代入でき、その変数を通じて関数を呼び出すことができ、関数も形参として呼び出すことができる。
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)と等価  
}
  1. Go はカスタムデータ型をサポートしている(既存のデータ型に別名を付けることに相当)
    基本構文:type カスタムデータ型名データ型
    例:type myInt int 【この時の myInt は int と等価】
    例:type mySum func (int, int) int 【この時の mySum は func (int, int) int と等価】
  2. 関数の戻り値に名前を付けることをサポートする
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  
}

パッケージの導入#

パッケージを使用する理由#
  1. 関数を分類し、異なる元ファイルに配置できる
  2. 同名問題を解決する、異なるパッケージ内の関数は同名にでき、パッケージで区別する
詳細#
  1. 宣言するパッケージは所在ファイルのフォルダ名と同じにすることを推奨
  2. Main パッケージはプログラムのエントリーパッケージであり、一般的に main 関数はこのパッケージに存在する
  3. パッケージ構文:package パッケージ名
  4. パッケージをインポートする構文:import "パッケージのパス",複数のパッケージを一度にインポートする場合は {} を使う
  5. 異なるパッケージの関数を呼び出す際は、まず所在パッケージを特定する必要がある
  6. 関数名、変数名の最初の文字が大文字でなければ他のパッケージからアクセスできない
  7. 同じディレクトリ内に重複する関数があってはならない
  8. パッケージ名とフォルダ名は異なってもよい
  9. 同じディレクトリ内の同レベルのファイルは同じパッケージに属する
  10. パッケージに別名を付けることができ、一度別名を付けると元のパッケージ名は使用できない

Init 関数#

初期化関数で、いくつかの初期化操作を行うことができる。

匿名関数#

特定の関数を一度だけ使用したい場合は、匿名関数を考慮することができる

匿名関数の使用方法#
  1. 匿名関数を定義する際に直接呼び出す。この方法では一度だけ呼び出すことができる
package main  
  
import "fmt"  
  
func main() {  
//匿名関数を定義:定義と同時に呼び出す  
sum := func(num1 int, num2 int) int {  
return num1 + num2  
}(10, 20)   
fmt.Println(sum)  
}
  1. 匿名関数を変数(すなわち関数変数)に代入し、その変数を通じて呼び出す
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 パッケージにあり、パッケージをインポートせずに直接使用できる

一般的な組み込み関数:

  1. Len 関数:バイト単位で文字列の長さをカウントする
  2. 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
}
  1. 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]

配列の注意事項#

  1. 配列の長さは配列型の一部である
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 
  1. 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 は予め割り当てられたメモリ空間の総長さ(すなわち基底配列と予め割り当てられたメモリの長さの合計)を示し、予め割り当てられたメモリの長さは可変であり、一度超えると、予め割り当てられた総メモリ長さを単位にして再度増加する。
image.png
image.png

スライスの宣言と初期化#

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  
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。