Golang複合タイプ


前言
上記のGolang基本タイプではgolang基本タイプの一般的な使い方を紹介しましたが、golangの複合データタイプを紹介します.よく使われる複合データタイプはarray配列、sliceスライス、map辞書、structの4種類です.
はいれつ
配列は、固定長の特定のタイプの要素からなるシーケンスであり、長さが固定されているため、実際のビジネスシーンでは使用が不便であるため、goでは直接使用することは少ない.
配列の長さは配列タイプの構成部分であるため、[3]intと[4]intは2つの異なる配列タイプである.配列の長さは、配列の長さがコンパイルフェーズで決定される必要があるため、定数式でなければなりません.
ほとんどの言語と同様にgoの配列下表も[0]から[len-1]まで
var a [3]int
var q [3]int = [3]int{
     1,2,3}   // 1,2,3
var r [3]int = [3]int{
     1,2}     // 1,2,0

a[0] = 1
a[1] = 2
a[2] = 3

「...」を使用して配列を宣言することもでき、配列の長さ位置に「...」の省略記号が現れると、配列の長さが初期化値の個数に基づいて計算されることを示すため、上記q配列の定義を簡略化することができる.
q := [...]int{
     1,2,3}
fmt.Println(reflect.TypeOf(q))  //    [3]int

大きな配列の場合、1つのインデックスと対応する値リストを指定して初期化することができ、以下のように、100個の要素を含む配列rを定義し、インデックス13の位置値は21であり、最後の要素は-1で初期化され、他の要素は0で初期化される.
r := [...]int{
     13: 21, 99: -1}

Slice
Slice(スライス)は、長いシーケンスを表し、シーケンス内の各要素には同じタイプがあります.
goのsliceの下層は配列によって実現され,1つのsliceはポインタ,長さ,容量の3つの部分から構成されている.sliceを作成するとき、ポインタは最初のslice要素に対応する最下位の配列要素のアドレスを指し、長さはslice中の要素の数に対応し、容量は配列を作成するときに空間を割り当てる初期サイズを表し、長さは容量を超えてはならない.容量は一般的にsliceの開始位置から最下位のデータの終了位置である.
配列とは異なり、slice間では==を使用して互いに比較することはできません.sliceの唯一の合法的な比較操作はnilと比較することですが、通常はlen(s) == 0を使用して判断することをお勧めします.そうしないと、s == nilという形式のslice判断に問題が発生します.以下のコードを参照してください.
var s []int 	// len(s) == 0, s == nil
s = []int(nil)  // len(s) == 0, s == nil
s = []int{
     }     // len(s) == 0, s != nil

slice基本操作
初期化[]int{}を使用してsliceを初期化すると、capパラメータはデフォルトで、デフォルトではcapはlenに等しい
sl_null := []int{
     } 	//    slice
sl0 := make([]int, 0, 3)  //    cap   3    slice []
sl1 := make([]int, 0)  //    []
sl2 := make([]int, 3)  //    [0,0,0]

//          slice
sl4 := []int{
     1,2,3}    //     []int
//      slice         
//         s := [...]{1,2,3},     [3]int

sliceの最後に要素を追加
要素を追加すると、追加が完了するとlenが現在のcapより大きい場合、sliceは拡張され、拡張capのサイズが2倍になります.
sl3 = append(sl3, 4)   //       
sl3 = append(sl3, 5, 6, 7)  //       

sl4 := []int{
     8, 9, 10}
sl3 = append(sl3, sl4...)   //   sl4   ,         sl3   
//    sl3      sl4    

sliceの長さと容量の取得
内蔵されたmake([]Type, len, cap)およびlen関数は、sliceの長さおよび容量をそれぞれ返します.
len_sl3 = len(sl3)   // int
cap_sl3 = cap(sl3)   // int

エレメントがsliceにあるかどうかを確認します
内蔵関数はなく、自分で遍歴する必要があります.slice遍歴時に2つの値を取り、1つ目はsliceの下付き、2つ目は下付きの値です.
for _, num := rang num_slice {
     
	if num == target {
     
		// ...
	}
}

要素の削除
goは要素のライブラリ関数を直接削除せず,スライス付与により実現する
(挿入操作も同様)
//        
a = a[1:] //     1   
a = a[N:] //     N   

//        ,       slice     ,          ...   
a = append(a[:i], a[i+1:]...) //     1   
a = append(a[:i], a[i+N:]...) //     N   

//      
a = a[:len(a)-1] //     1   
a = a[:len(a)-N] //     N   

挿入
a := []int{
     1,2,4,5,6,7,8}
temp := append([]int{
     3}, a[2:]...)
a = append(a[:2], temp...)
// output a: [1 2 3 4 5 6 7 8]

//       :
a = append(a[:2], append([]int{
     3},a[2:]...)...)

スライス
スライスとコピーを紹介する前に、浅いコピーと深いコピーを簡単に紹介する必要があります.
浅いコピー:スタックにメモリを再作成します.コピー前後のオブジェクトの基本データ型は互いに影響しませんが、コピー前後のオブジェクトの参照タイプは同じメモリを共有するため、相互に影響します.
深いコピー:スタックメモリから新しい領域を開いて新しいオブジェクトを保存し、オブジェクトのサブオブジェクトを再帰的にコピーします.コピー前後の2つのオブジェクトは互いに影響しません.
スライス操作は浅いコピーであり、capsli[m:n]からsli[m]の間の要素を取得するが、スライスのデータはコピーされない.元の配列を指す新しいスライス値を作成します.
したがって、再スライスされた要素を修正すると、元のスライスの要素が変更されます.実際の使用ではこれを無視すると,予想外の誤りが起こりやすい.
注意:pythonとは異なり、golangのスライスインデックスは負数にできません.
sli := []int{
     1,2,3,4,5,6}	// [1 2 3 4 5 6]
sli2 := sli[2:5]	// [3 4 5]
sli2[0] = 10	// [10 4 5]
fmt.Println(sli) // [1 2 10 4 5 6]

コピー
コピー操作は深コピーで、copy後、tの内容を変更してもsの内容は変更されません
tのlenがsより小さい場合は、sの内容を遮断してコピーします.
s := []int{
     1,2,3,4,5,6,7,8,9,10}
t := make([]int, len(s), cap(s))
copy(t,s)   //   s     t [1 2 3 4 5 6 7 8 9 10]
t[0] = 10 // [10 2 3 4 5 6 7 8 9 10]
fmt.Println(s) // [1 2 3 4 5 6 7 8 9 10]

反転配列
s := []int{
     1,2,3,4,5,6,7,8,9,10}
for i, j := 0, len(s) - 1; i < j; i, j = i + 1, j - 1 {
     
  s[i], s[j] = s[j], s[i]
}

map
golangのmapはハッシュテーブルであり、無秩序なkey/valueペアの集合であり、すべてのkeyが異なり、与えられたkeyによって定数時間複雑度内に対応するvalueを取得、更新、または削除することができる.
mapのkeyは比較可能なデータ型でなければなりません.valueのタイプは要求されていません(sliceではkeyができません.valueしかできません).
map基本操作
初期化
//     ,   map   nil
var map_variable map[key_data_type]value_data_type   //        map[K]V
map_variable = make(map[key_data_type]value_data_type) //   

//    make   
map_variable := make(map[key_data_type]value_data_type) //   dic := map[string]int

//    
map_variable := map[key_data_type]value_data_type{
     }
//              map, :
age:= map[string]int {
     
  "Tony" : 35,
  "Wallace" : 24,    //          
}

キーが存在するか否かを判断する
//      key n       
//    ,ok   True,    False
if _, ok := map_name[n]; ok {
     
    // ...
}

要素の追加または変更
キーが存在する場合は変更、そうでない場合は追加
map_name[key] = value  //   age["Tony"] = 35

要素の削除
Goの削除操作もアクセス操作も安全で、アクセスまたは削除したキーが存在しなくてもエラーは報告されず、アクセスが存在しないキーは0を返します
delete(age, "Tony")

注意:mapはvalueに対して様々な数値操作を行うことができますが、valueは変数ではありませんので、mapの要素に対してアドレス操作を行うことはできません.map要素のアドレスが禁止されているのは、mapが要素数の増加に伴ってより大きなメモリ領域を再割り当てし、以前のアドレスが無効になる可能性があるためです.
n = &age["bob"]  // Compile Error: cannot take address of map element

map反復
Mapの反復順序はランダムであり,異なるハッシュ関数実装は異なる遍歴順序をもたらす可能性がある.
for key, value := range age {
     
  fmt.Println(key, value)
}

golangには組み込まれた集合機能がなく、mapを使用して集合を実現することができます.keyは唯一です(keyを使用して集合要素を表し、valueはすべて1に設定されています)
Struct
構造体は、集約されたデータ型であり、0つ以上の任意のタイプの値で集約されたエンティティです.各値は、構造体のメンバーと呼ばれます.
定義と宣言
type employee struct {
          //    Employee       
  id 			int
  name 		string
  address string
  title 	string
}

var wallace employee		//      Employee       wallace
wallace.id = 12345    //              

tony := Employee {
     3333, "Tony", "Beijing", "Engineer"}  //     Employee   
cassie := Employee{
     
  id:				3333
  name:			"cassie"
  address:	"shenzhen"
  title:		"engineer"
}

注意:
  • アクセス制限アルファベット大文字小文字による
  • 構造体のすべてのメンバーが比較可能であれば、構造体も比較可能であり、そうすると2つの構造体は==または!=を使用することができる.演算子を比較します.2つの構造体の各メンバーが比較されます.

  • 構造体ポインタ
    再メソッド内部で構造体メンバー変数の値を変更する必要がある場合は、goではすべての関数パラメータが値コピーで入力され、関数パラメータは関数呼び出し時の元の変数ではないため、構造体ポインタを使用して値を伝達する必要があります.
    func AwardAnnualRaise(e *Employee) {
         
    	e.Salary = e.Salary * 105 / 100
    }
    

    さらに、Sという名前の構造体タイプでは、Sタイプのメンバーを含めることはできませんが、Sタイプの構造体にはsli[n - 1]ポインタタイプのメンバーを含めることができます.これにより、チェーンテーブルやツリー構造などの再帰的なデータ構造を作成できます.
    type tree struct {
         
    	value  int
    	left   *tree
    	right  *tree
    }
    

    また、ポインタをパラメータとして使用すると効率が高くなるため、通常、関数のパラメータとパラメータの構造体ではポインタが使用されます.
    構造体値の印刷
    %vではなく%+vを使用することを推奨します.出力には構造体のメンバー名と値が含まれます.
    package main
    
    import "fmt"
    
    type info struct {
         
        name string
        id int
    }
    
    func main()  {
         
        v := info{
         "Nan", 33}
        fmt.Printf("%v
    "
    , v) fmt.Printf("%+v
    "
    , v) fmt.Printf("%#v
    "
    , v) fmt.Printf("%T
    "
    , v) }

    実行結果は次のとおりです.
    {
         Nan 33}
    {
         name:Nan id:33}
    main.info{
         name:Nan id:33}
    main.info
    

    匿名メンバー
    メンテナンスを容易にするために、次のような同じ機能を持つ属性が独立しています.
    type Point struct {
         
        X, Y int
    }
    
    type Circle struct {
         
        Center Point
        Radius int
    }
    
    type Wheel struct {
         
        Circle Circle
        Spokes int
    }
    

    しかし、この変更により、メンバー変数へのアクセスが非常に煩雑になります.
    var w Wheel
    w.Circle.Center.X = 8
    w.Circle.Center.Y = 8
    w.Circle.Radius = 5
    w.Spokes = 20
    

    匿名メンバーを使用してこの問題を解決するには、メンバーの名前を指定せずにメンバーのデータ型のみを宣言します.匿名メンバーのデータ型は、名前付きタイプまたは名前付きタイプへのポインタである必要があります.
    type Point struct {
         
      X, Y int
    }
    
    type Circle struct {
         
      Point
      Radius int
    }
    
    type Wheel struct {
         
      Circle
      Spokes int
    }
    
    //           ,                       
    
    var w Wheel
    w.X = 8            // equivalent to w.Circle.Point.X = 8
    w.Y = 8            // equivalent to w.Circle.Point.Y = 8
    w.Radius = 5       // equivalent to w.Circle.Radius = 5
    w.Spokes = 20
    
    //     ,                    ,               :
    
    w = Wheel{
         8, 8, 5, 20}                       // compile error: unknown fields
    w = Wheel{
         X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
    
    //                ,        
    w = Wheel{
         Circle{
         Point{
         8, 8}, 5}, 20}
    
    w = Wheel{
         
        Circle: Circle{
         
            Point:  Point{
         X: 8, Y: 8},
            Radius: 5,
        },
        Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
    }
    

    その他の複合タイプ
    Json
    JSONは、簡潔性、可読性、流行度などの理由から、構造化情報の標準プロトコルの中で最も広く応用されている.JSONはキー値ペアを使用して構造化情報を表し、カンマで区切られます.最後のメンバーまたは要素の後ろにカンマ区切り記号はありません.
    {
         
        "id" : 1,
        "name": "wallace",
    	"teams":{
         
    		"team_id":1,
    		"team_name":"wallace team"
    	},
    	"skills":["go","mysql","redis"],
        "description": "test"
    }
    

    Marshaling
    1つのGo言語の構造体sliceをJSONに変換するプロセスを符号化(marshaling)と呼び、グループ化は*S関数を呼び出すことで完了し、jsonのkey値を構造体のjson tagが指定した内容に変換する.
    次のような構造体があります.
    type Team struct {
         
    	TeamID   int    `json:"team_id" db:"team_id"`
    	TeamName string `json:"team_name" db:"team_name"`
    }
    
    type Member struct {
         
    	ID          int      `json:"id" db:"id"`
    	Name        string   `json:"name" db:"name"`
    	Teams       *Team    `json:"teams" db:"teams"`
    	Skills      []string `json:"skills" db:"skills"`
    	Description string   `json:"description" db:"description"`
    }
    

    構造体オブジェクトに対してmarshalingを行い、json文字列に符号化する
    wallace := &Member{
         
    	ID:          1,
    	Name:        "wallace",
    	Teams:       &Team{
         
    		TeamID:   1,
    		TeamName: "team 1",
    	},
    	Skills:      []string{
         "go", "mysql", "redis"},
    	Description: "test",
    }
    
    js, err := json.Marshal(wallace)
    if err != nil {
         
    	log.Fatalf("JSON marshaling failed: %s", err)
    }
    
    fmt.Printf("%s
    "
    , js)

    出力:
    {
         "id":1,"name":"wallace","teams":{
         "team_id":1,"team_name":"team 1"},"skills":["go","mysql","redis"],"description":"test"}
    
    json.Marshal関数を使用して、整列したインデント出力を生成することもできます.この関数には、各行の出力の接頭辞と各レベルのインデントを表す2つの追加文字列パラメータがあります.
    data, err := json.MarshalIndent(wallace, "", " ")
    if err != nil {
         
    	log.Fatalf("JSON marshaling failed: %s", err)
    }
    fmt.Printf("%s
    "
    , data)

    出力:
    {
         
        "id": 1,
        "name": "wallace",
        "teams": {
         
            "team_id": 1,
            "team_name": "team 1"
        },
        "skills": [
            "go",
            "mysql",
            "redis"
        ],
        "description": "test"
    }
    

    符号化時には、導出可能な構造体メンバーのみが符号化される.
    Unmarshal
    符号化の逆操作は復号(unmarshaling)であり,json文字列を構造体に変換する.
    member := &Member{
         }
    err = json.Unmarshal(data, member)
    if err != nil {
         
    	log.Fatalf("JSON unmarshaling failed: %s", err)
    }
    fmt.Printf("%+v", *member)
    

    出力:
    {
         ID:1, Name:"wallace", Teams:(*timetest.Team)(0xc42000a3e0), Skills:[]string{
         "go", "mysql", "redis"}, Description:"test"}