BuBu

Zwj`Blog

My decentralized blog
github
email

行く

ゴー#

基礎#

コマンドライン引数の解析#

package main

import (
   // ここにコードを追加する必要があります。[1]
   "flag"
   "fmt"
)

var name string

func init() {
   // ここにコードを追加する必要があります。[2]
   flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
   // ここにコードを追加する必要があります。[3]
   flag.Parse()
   fmt.Printf("Hello, %s!\n", name)
}

実行

go run main.go -name="Robert"

出力

Hello, Robert!

変数#

定義#

// 変数の宣言
var a int = 10

短い宣言#

// 短い宣言
b := 20
fmt.Println(a, b)

複数代入#

// 複数代入
c, d, e := 30, 40, 50
fmt.Println(c, d, e)

変数の値を交換#

// 変数の値を交換
f, g := 99, 88
fmt.Println(f, g)//99 88
f, g = g, f
fmt.Println(f, g)//88 99

アンダースコアで無視する値#

// 値/戻り値を無視
h, _ := 10, 20
fmt.Println(h)

フォーマット出力#

// フォーマット出力
fmt.Printf("%d\n", 1)         //%d 1 整数として出力
fmt.Printf("%f\n", 1.1)       //%f 1.100000 浮動小数点型として出力
fmt.Printf("%.2f\n", 1.11111) //%.2f 1.11 浮動小数点型として出力し、小数点以下2桁を保持
fmt.Printf("%t\n", false)     //%t false ブール型データとして出力
fmt.Printf("%s\n", "false")   //%s false 文字列型データとして出力
fmt.Printf("%c\n", 'a')       //%c 'a' 文字型データとして出力
fmt.Printf("%p\n", &a)        //%p 0x1400012c008 ポインタデータとして出力アドレス
fmt.Printf("%T\n", a)         //%T その変数の型を出力

入力を取得#

// ユーザー入力を取得
fmt.Scan(&a)   // 入力88
fmt.Println(a) // 出力88

文字とバイト#

var n byte = 'a' // byteは単一の文字しか格納できない
var m rune = '' // runeは他の言語のcharに相当し、単一のUnicode文字/中国語などを格納する
fmt.Println(n,m)

定数#

定義#

定数は常に変わらないデータを格納します。

// 定数の定義
const a = false

iota 列挙#

定数宣言は iota 定数生成器を使用して初期化できます。これは、類似のルールで初期化された定数のグループを生成するために使用されますが、各行に初期化式を書く必要はありません。注意:const 宣言文の中で、最初の宣言された定数がある行で iota は 0 に設定され、その後、定数宣言のある各行で 1 ずつ加算されます。

const (
   b = iota
   c = iota
   d = iota
)
fmt.Println(b, c, d) //0 1 2

const (
   f = iota
   g
   h
)
fmt.Println(f, g, h) //0 1 2

列挙を定義する際に、同じ行に定数が書かれている場合は、改行すると 1 が加算されます。

const (
   i    = iota
   j, k = iota, iota
)
fmt.Println(i, j, k) //0 1 1

演算子#

算術演算子#

演算子説明
+加算
-減算
*乗算
/除算
%剰余

注意: ++(インクリメント)と–(デクリメント)は Go 言語では独立した文であり、演算子ではありません。

関係演算子#

演算子説明
==2 つの値が等しいかどうかを確認し、等しい場合は True を返し、そうでない場合は False を返します。
!=2 つの値が等しくないかどうかを確認し、等しくない場合は True を返し、そうでない場合は False を返します。
>左側の値が右側の値より大きいかどうかを確認し、そうであれば True を返し、そうでなければ False を返します。
>=左側の値が右側の値以上かどうかを確認し、そうであれば True を返し、そうでなければ False を返します。
<左側の値が右側の値より小さいかどうかを確認し、そうであれば True を返し、そうでなければ False を返します。
<=左側の値が右側の値以下かどうかを確認し、そうであれば True を返し、そうでなければ False を返します。

論理演算子#

演算子説明
&&論理 AND 演算子。両方のオペランドが True の場合は True を返し、そうでない場合は False を返します。
!論理 NOT 演算子。条件が True の場合は False を返し、そうでない場合は True を返します。

ビット演算子#

ビット演算子は整数のメモリ内の 2 進数ビットに対して操作を行います。

演算子説明
&演算に参加する 2 つの数の各ビットを AND します。(両方が 1 のときだけ 1)
^演算に参加する 2 つの数の各ビットを XOR します。対応するビットが異なるとき、結果は 1 になります。(ビットが異なると 1)
<<左に n ビット移動することは 2 の n 乗を掛けることです。“a<<b” は a の各ビットをすべて左に b ビット移動し、高位を捨て、低位を 0 で補います。
>>右に n ビット移動することは 2 の n 乗で割ることです。“a>>b” は a の各ビットをすべて右に b ビット移動します。

代入演算子#

演算子説明
=単純な代入演算子、式の値を左辺に代入します
+=加算後に代入
-=減算後に代入
*=乗算後に代入
/=除算後に代入
%=剰余後に代入
<<=左移動後に代入
>>=右移動後に代入
&=ビット AND 後に代入
=
^=ビット XOR 後に代入

型変換#

// 型変換
c := 3
d := float64(c)
fmt.Println(c, d)

フロー制御#

If 文(Go は三項演算子をサポートしていません)#

• 条件式の括弧を省略できます。
• 初期化文を持ち、コードブロックのローカル変数を定義できます。
• コードブロックの左括弧は条件式の末尾に必ず置く必要があります。

if ブール式 {
  /* ブール式がtrueのときに実行 */
} 

式の中で変数を宣言できます。

x := 0
if n := "abc"; x > 0 { // 初期化文は必ずしも変数の定義である必要はありません。例えばprintln("init")も可能です。
   println(n[2])
} else if x < 0 { // else ifとelseの左括弧の位置に注意。
   println(n[1])
} else {
   println(n[0])
}

Switch 文#

構文#

Go ではデフォルトで Break が付いています。Break が必要ない場合は fallthrough キーワードを使用できます。

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

変数 var1 は任意の型であり、val1 と val2 は同じ型の任意の値です。型は定数や整数に制限されず、同じ型の式の最終結果である必要があります。
複数の条件に一致する可能性のある値を同時にテストすることができ、カンマで区切って使用できます。例えば:case val1, val2, val3。

package main

import "fmt"

func main() {
   /* ローカル変数を定義 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("優秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("合格\n" )      
      case grade == "F":
         fmt.Printf("不合格\n" )
      default:
         fmt.Printf("悪い\n" )
   }
   fmt.Printf("あなたの成績は %s\n", grade )
}    

上記のコードの実行結果は:

優秀!
あなたの成績は A  
型スイッチ#
switch x.(type){
    case type:
       statement(s)      
    case type:
       statement(s)
    /* 任意の数のcaseを定義できます */
    default: /* オプション */
       statement(s)
}  
インスタンス#
package main

import "fmt"

func main() {
    var x interface{}
    // 書き方1:
    switch i := x.(type) { // 初期化文付き
    case nil:
        fmt.Printf(" x の型 :%T\r\n", i)
    case int:
        fmt.Printf("x は int 型")
    case float64:
        fmt.Printf("x は float64 型")
    case func(int) float64:
        fmt.Printf("x は func(int) 型")
    case bool, string:
        fmt.Printf("x は bool または string 型")
    default:
        fmt.Printf("未知型")
    }
    // 書き方2
    var j = 0
    switch j {
    case 0:
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    // 書き方3
    var k = 0
    switch k {
    case 0:
        println("fallthrough")
        fallthrough
        /*
            Goのswitchは非常に柔軟で、式は定数や整数である必要はなく、実行プロセスは上から下に進み、マッチする項目が見つかるまで続きます。
            もしswitchに式がなければ、trueにマッチします。
            Goではswitchはデフォルトで各caseの最後にbreakが付いているようなもので、
            マッチが成功した後は他のcaseを自動的に実行せず、全体のswitchから抜けますが、
            fallthroughを使用すると後続のcaseのコードを強制的に実行できます。
        */
    case 1:
        fmt.Println("1")
    case 2:
        fmt.Println("2")
    default:
        fmt.Println("def")
    }
    // 書き方4
    var n = 0
    switch { // 条件式を省略し、if...else if...elseとして使用できます
    case n > 0 && n < 10:
        fmt.Println("i > 0 and i < 10")
    case n > 10 && n < 20:
        fmt.Println("i > 10 and i < 20")
    default:
        fmt.Println("def")
    }
}   

上記のコードの実行結果は:

    x の型 :<nil>
    fallthrough
    1
    1
    def

For 文#

for ループはループ制御構造であり、指定された回数のループを実行できます。

構文#

Go 言語の For ループには 3 つの形式があり、そのうちの 1 つだけがセミコロンを使用します。

    for init; condition; post { }
    for condition { }
    for { }
    init: 一般的には制御変数に初期値を与える代入式です;
    condition: 関係式または論理式、ループ制御条件です;
    post: 一般的には制御変数の増分または減分を与える代入式です。
    for文の実行プロセスは次のとおりです:
    ①最初に式initに初期値を与えます;
    ②代入式initが指定されたcondition条件を満たすかどうかを判別し、真であればループ条件を満たし、ループ体内の文を実行し、次にpostを実行し、2回目のループに入ります。再度conditionを判別します。そうでなければconditionの値が偽であると判断し、条件を満たさないのでforループを終了し、ループ体の外の文を実行します。   
s := "abc"

for i, n := 0, len(s); i < n; i++ { // よくあるforループ、初期化文をサポートしています。
    println(s[i])
}

n := len(s)
for n > 0 {                // while (n > 0) に置き換えられます {}
    n-- 
    println(s[n])        // for (; n > 0;) に置き換えられます {}
}

for {                    // while (true) に置き換えられます {}
    println(s)            // for (;;) に置き換えられます {}
}  

コンパイラがあなたの考えを理解できるとは期待しないでください。初期化文の中で全ての結果を計算するのは良いアイデアです。

package main

func length(s string) int {
    println("call length.")
    return len(s)
}

func main() {
    s := "abcd"

    for i, n := 0, length(s); i < n; i++ {     // length関数を何度も呼び出すのを避けます。
        println(i, s[i])
    } 
}  

出力:

    call length.
    0 97
    1 98
    2 99
    3 100 
インスタンス#
package main

import "fmt"

func main() {

   var b int = 15
   var a int

   numbers := [6]int{1, 2, 3, 5}

   /* forループ */
   for a := 0; a < 10; a++ {
      fmt.Printf("a の値は: %d\n", a)
   }

   for a < b {
      a++
      fmt.Printf("a の値は: %d\n", a)
      }

   for i,x:= range numbers {
      fmt.Printf("第 %d 位 x の値 = %d\n", i,x)
   }   
} 

上記のインスタンスの実行出力結果は:

    a の値は: 0
    a の値は: 1
    a の値は: 2
    a の値は: 3
    a の値は: 4
    a の値は: 5
    a の値は: 6
    a の値は: 7
    a の値は: 8
    a の値は: 9
    a の値は: 1
    a の値は: 2
    a の値は: 3
    a の値は: 4
    a の値は: 5
    a の値は: 6
    a の値は: 7
    a の値は: 8
    a の値は: 9
    a の値は: 10
    a の値は: 11
    a の値は: 12
    a の値は: 13
    a の値は: 14
    a の値は: 15
    第 0 位 x の値 = 1
    第 1 位 x の値 = 2
    第 2 位 x の値 = 3
    第 3 位 x の値 = 5
    第 4 位 x の値 = 0
    第 5 位 x の値 = 0  
ループのネスト#

for ループの中に 1 つまたは複数の for ループをネストすることができます。

構文

以下は Go 言語のネストループの形式です:

for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s)
   }
   statement(s)
}  
インスタンス

以下のインスタンスでは、ループのネストを使用して 2 から 100 の間の素数を出力します:

package main

import "fmt"

func main() {
   /* ローカル変数を定義 */
   var i, j int

   for i=2; i < 100; i++ {
      for j=2; j <= (i/j); j++ {
         if(i%j==0) {
            break // 因子が見つかった場合、素数ではありません
         }
      }
      if(j > (i/j)) {
         fmt.Printf("%d  は素数です\n", i)
      }
   }  
}  

上記のインスタンスの実行出力結果は:

    2  は素数です
    3  は素数です
    5  は素数です
    7  は素数です
    11  は素数です
    13  は素数です
    17  は素数です
    19  は素数です
    23  は素数です
    29  は素数です
    31  は素数です
    37  は素数です
    41  は素数です
    43  は素数です
    47  は素数です
    53  は素数です
    59  は素数です
    61  は素数です
    67  は素数です
    71  は素数です
    73  は素数です
    79  は素数です
    83  は素数です
    89  は素数です
    97  は素数です  
無限ループ#

ループ内の条件式が常に false でない場合、無限ループが発生します。無限ループを実行するには、for ループ文の中に条件式だけを設定することで無限ループを実行できます:

package main

import "fmt"

func main() {
    for true  {
        fmt.Printf("これは無限ループです。\n");
    }
}  

Range 文#

構文#

Golang の range はイテレータ操作に似ており、(インデックス、値) または (キー、値) を返します。

for ループの range 形式は slice、map、配列、文字列などを反復ループすることができます。形式は次のとおりです:

for key, value := range oldMap {
    newMap[key] = value
}   
1st value2nd value
stringindexs[index]unicode, rune
array/sliceindexs[index]
mapkeym[key]
channelelement

不要な戻り値を無視するか、またはこの特殊な変数 "_" を使用できます。

package main

func main() {
    s := "abc"
    // 2nd valueを無視し、string/array/slice/mapをサポートします。
    for i := range s {
        println(s[i])
    }
    // indexを無視します。
    for _, c := range s {
        println(c)
    }
    // すべての戻り値を無視し、単に反復します。
    for range s {

    }

    m := map[string]int{"a": 1, "b": 2}
    // (key, value)を返します。
    for k, v := range m {
        println(k, v)
    }
}   

出力結果:

    97
    98
    99
    97
    98
    99
    a 1
    b 2  
重要な注意#

*注意、range はオブジェクトをコピーします。

package main

import "fmt"

func main() {
    a := [3]int{0, 1, 2}

    for i, v := range a { // index、valueはコピーから取得されます。

        if i == 0 { // 修正前に、元の配列を修正します。
            a[1], a[2] = 999, 999
            fmt.Println(a) // 修正が有効であることを確認し、出力 [0, 999, 999]。
        }

        a[i] = v + 100 // コピーから取得した値を使用して元の配列を修正します。

    }

    fmt.Println(a) // 出力 [100, 101, 102]。
}   

出力結果:

    [0 999 999]
    [100 101 102]   

len 関数と cap 関数はどちらも配列の長さ(要素数)を返します。

package main

func main() {
    a := [2]int{}
    println(len(a), cap(a)) 
}

出力結果:

2 2
多次元配列の反復
package main

import (
    "fmt"
)

func main() {

    var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

    for k1, v1 := range f {
        for k2, v2 := range v1 {
            fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
        }
        fmt.Println()
    }
}

出力結果:

    (0,0)=1 (0,1)=2 (0,2)=3 
    (1,0)=7 (1,1)=8 (1,2)=9 
配列のコピーと引数#
package main

import "fmt"

func printArr(arr *[5]int) {
    arr[0] = 10
    for i, v := range arr {
        fmt.Println(i, v)
    }
}

func main() {
    var arr1 [5]int
    printArr(&arr1)
    fmt.Println(arr1)
    arr2 := [...]int{2, 4, 6, 8, 10}
    printArr(&arr2)
    fmt.Println(arr2)
}

スライス#

スライスは配列や配列ポインタではありません。内部ポインタと関連属性を使用して配列の断片を参照し、可変長のスキームを実現します。

    1. スライス:スライスは配列の参照であるため、スライスは参照型です。しかし、自身は構造体であり、値コピーで渡されます。
    2. スライスの長さは変更可能であるため、スライスは可変の配列です。
    3. スライスの反復方法は配列と同じで、len()を使用して長さを求めることができます。これは使用可能な要素の数を示し、読み書き操作はこの制限を超えてはなりません。
    4. capはスライスの最大拡張容量を求めることができ、配列の制限を超えてはなりません。0 <= len(slice) <= len(array)、ここでarrayはsliceが参照する配列です。
    5. スライスの定義:var 変数名 []型、例えば var str []string var arr []int。
    6. スライス == nil の場合、len、cap の結果はどちらも0になります。

スライスの作成#

package main

import "fmt"

func main() {
   //1.スライスの宣言
   var s1 []int
   if s1 == nil {
      fmt.Println("空です")
   } else {
      fmt.Println("空ではありません")
   }
   // 2.:=
   s2 := []int{}
   // 3.make()
   var s3 []int = make([]int, 0)
   fmt.Println(s1, s2, s3)
   // 4.初期化代入
   var s4 []int = make([]int, 0, 0)
   fmt.Println(s4)
   s5 := []int{1, 2, 3}
   fmt.Println(s5)
   // 5.配列からスライス
   arr := [5]int{1, 2, 3, 4, 5}
   var s6 []int
   // 前包後不包
   s6 = arr[1:4]
   fmt.Println(s6)
}

スライスの初期化#

//全体:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1] //スライスの最後の要素を削除
//局所:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //スライスの最後の要素を削除

img

コード:

package main

import (
    "fmt"
)

var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6]        //簡略化して var slice []int = arr[:end]
var slice2 []int = arr[5:10]       //簡略化して var slice[]int = arr[start:]
var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
var slice4 = arr[:len(arr)-1]      //スライスの最後の要素を削除
func main() {
    fmt.Printf("全体変数:arr %v\n", arr)
    fmt.Printf("全体変数:slice0 %v\n", slice0)
    fmt.Printf("全体変数:slice1 %v\n", slice1)
    fmt.Printf("全体変数:slice2 %v\n", slice2)
    fmt.Printf("全体変数:slice3 %v\n", slice3)
    fmt.Printf("全体変数:slice4 %v\n", slice4)
    fmt.Printf("-----------------------------------\n")
    arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
    slice5 := arr[2:8]
    slice6 := arr[0:6]         //簡略化して slice := arr[:end]
    slice7 := arr[5:10]        //簡略化して slice := arr[start:]
    slice8 := arr[0:len(arr)]  //slice := arr[:]
    slice9 := arr[:len(arr)-1] //スライスの最後の要素を削除
    fmt.Printf("局所変数: arr2 %v\n", arr2)
    fmt.Printf("局所変数: slice5 %v\n", slice5)
    fmt.Printf("局所変数: slice6 %v\n", slice6)
    fmt.Printf("局所変数: slice7 %v\n", slice7)
    fmt.Printf("局所変数: slice8 %v\n", slice8)
    fmt.Printf("局所変数: slice9 %v\n", slice9)
}

出力結果:

    全体変数:arr [0 1 2 3 4 5 6 7 8 9]
    全体変数:slice0 [2 3 4 5 6 7]
    全体変数:slice1 [0 1 2 3 4 5]
    全体変数:slice2 [5 6 7 8 9]
    全体変数:slice3 [0 1 2 3 4 5 6 7 8 9]
    全体変数:slice4 [0 1 2 3 4 5 6 7 8]
    -----------------------------------
    局所変数: arr2 [9 8 7 6 5 4 3 2 1 0]
    局所変数: slice5 [2 3 4 5 6 7]
    局所変数: slice6 [0 1 2 3 4 5]
    局所変数: slice7 [5 6 7 8 9]
    局所変数: slice8 [0 1 2 3 4 5 6 7 8 9]
    局所変数: slice9 [0 1 2 3 4 5 6 7 8]

スライスの追加#

スライスに値を追加するには append 内蔵関数を使用します。

package main

import (
    "fmt"
)

func main() {

    var a = []int{1, 2, 3}
    fmt.Printf("スライス a : %v\n", a)
    var b = []int{4, 5, 6}
    fmt.Printf("スライス b : %v\n", b)
    c := append(a, b...)
    fmt.Printf("スライス c : %v\n", c)
    d := append(c, 7)
    fmt.Printf("スライス d : %v\n", d)
    e := append(d, 8, 9, 10)
    fmt.Printf("スライス e : %v\n", e)

}

出力結果:

    スライス a : [1 2 3]
    スライス b : [4 5 6]
    スライス c : [1 2 3 4 5 6]
    スライス d : [1 2 3 4 5 6 7]
    スライス e : [1 2 3 4 5 6 7 8 9 10]

append:スライスの末尾にデータを追加し、新しいスライスオブジェクトを返します。

package main

import (
    "fmt"
)

func main() {

    s1 := make([]int, 0, 5)
    fmt.Printf("%p\n", &s1)

    s2 := append(s1, 1)
    fmt.Printf("%p\n", &s2)

    fmt.Println(s1, s2)

}

出力結果:

    0xc42000a060
    0xc42000a080
    [] [1]

スライスの容量が元の容量を超えると、底層の配列が再割り当てされます。#

package main

import (
    "fmt"
)

func main() {
    data := [...]int{0, 1, 2, 3, 4, 5}

    s := data[6:8]
    s[0] += 100
    s[1] += 200

    fmt.Println(s)
    fmt.Println(data)
}

出力:

    [102 203]
    [0 1 102 203 4 5]

スライスの初期化

package main

import "fmt"

func main() {
    s := []int{0, 1, 2, 3}
    p := &s[2] // *int, 底層配列要素のポインタを取得します。
    *p += 100

    fmt.Println(s)
}

出力結果:

    [0 1 102 3]

スライスのメモリレイアウト

null

文字列とスライス#

文字列の底層は byte の配列であるため、スライス操作を行うこともできます。

package main

import (
    "fmt"
)

func main() {
    str := "hello world"
    s1 := str[0:5]
    fmt.Println(s1)

    s2 := str[6:]
    fmt.Println(s2)
}

出力結果:

    hello
    world

文字列は不変であるため、文字列の文字を変更するには次の操作が必要です:
英語の文字列:

package main

import (
    "fmt"
)

func main() {
    str := "Hello world"
    s := []byte(str) //中国語の文字は[]rune(str)を使用する必要があります
    s[6] = 'G'
    s = s[:8]
    s = append(s, '!')
    str = string(s)
    fmt.Println(str)
}

出力結果:

    Hello Go!

スライスの切り取り理解#

golang slice data [:6:8] の 2 つのコロンの理解

通常のスライス、data [6:8]、第 6 位置から第 8 位置まで(6、7 を返す)、長さ len は 2、最大容量 cap は 4(6-9)

別の書き方: data [:6:8] は、スライスの内容は data の 0 から 6 まで、長さ len は 6、最大拡張サイズ cap は 8 に設定されます。

a[x:y] スライスの内容 [x] スライスの長さ: y-x スライスの容量

package main

import (
    "fmt"
)

func main() {
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    d1 := slice[6:8]
    fmt.Println(d1, len(d1), cap(d1))
    d2 := slice[:6:8]
    fmt.Println(d2, len(d2), cap(d2))
}

配列またはスライスを文字列に変換する:

    strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)

構造体#

カスタムタイプ#

Go 言語には基本的なデータ型がいくつかあります。たとえば、string、整数型、浮動小数点型、ブール型などのデータ型があります。Go 言語では type キーワードを使用してカスタムタイプを定義できます。

カスタムタイプは新しい型を定義します。基本的な型を基に定義することも、struct を通じて定義することもできます。たとえば:

//MyIntをint型として定義
type MyInt int 

type キーワードを使用した定義により、MyInt は新しい型であり、int の特性を持っています。

型エイリアス#

型エイリアスは、TypeAlias が Type の別名であることを規定します。基本的に TypeAlias と Type は同じ型です。子供が小さい頃にあだ名や乳名を持っているように、学校に行くと本名があり、英語の先生が英語名を付けることがありますが、これらの名前はすべてその子を指します。

type TypeAlias = Type 

私たちが以前見た rune と byte は、タイプエイリアスです。彼らの定義は次のとおりです:

type byte = uint8
type rune = int32

タイプ定義とタイプエイリアスの違い#

タイプエイリアスとタイプ定義は、表面的には等号の違いしかありません。次のコードを通じて、彼らの違いを理解しましょう。

//タイプ定義
type NewInt int

//タイプエイリアス
type MyInt = int

func main() {
    var a NewInt
    var b MyInt

    fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b) //type of b:int
} 

結果は、a の型は main.NewInt であり、main パッケージ内で定義された NewInt 型を示しています。b の型は int です。MyInt 型はコード内でのみ存在し、コンパイルが完了すると MyInt 型は存在しません。

構造体の定義#

type と struct キーワードを使用して構造体を定義します。具体的なコード形式は次のとおりです:

type タイプ名 struct {
  フィールド名 フィールド型
  フィールド名 フィールド型

} 

ここで:

    1.タイプ名:自定義構造体の名前を示します。同じパッケージ内で重複してはいけません。
    2.フィールド名:構造体のフィールド名を示します。構造体内のフィールド名は一意である必要があります。
    3.フィールド型:構造体フィールドの具体的な型を示します。 
type person struct {
    name string
    city string
    age  int8
} 

type person1 struct {
    name, city string
    age        int8
} 

構造体のインスタンス化#

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p = person{
        "名前",
        "都市",
        18,
    }
    fmt.Println(p)
}

匿名構造体#

一時的なデータ構造などのシーンでは、匿名構造体を使用することもできます。

package main

import (
    "fmt"
)

func main() {
    var user struct{Name string; Age int}
    user.Name = "pprof.cn"
    user.Age = 18
    fmt.Printf("%#v\n", user)
} 

ポインタ型構造体の作成#

new キーワードを使用して構造体をインスタンス化することもでき、得られるのは構造体のアドレスです。形式は次のとおりです:

    var p2 = new(person)
    fmt.Printf("%T\n", p2)     //*main.person
    fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}  

このセクションの最初の例のように、var a *intはポインタ変数 a を宣言するだけで初期化されていません。ポインタは参照型であるため、初期化後に値を設定する必要があります。次のように内蔵の new 関数を使用して a を初期化する必要があります:

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

構造体の初期化#

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p4 person
    fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
} 

キーと値のペアを使用した初期化#

キーと値のペアを使用して構造体を初期化する場合、キーは構造体のフィールドに対応し、値はそのフィールドの初期値に対応します。

p5 := person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}

構造体ポインタに対してもキーと値のペアを使用して初期化できます。例えば:

p6 := &person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18} 

特定のフィールドに初期値がない場合、そのフィールドは省略できます。この場合、初期値が指定されていないフィールドの値はそのフィールドの型のゼロ値になります。

p7 := &person{
    city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

値のリストを使用した初期化#

構造体を初期化する際に、キーを省略して値だけを指定することができます:

p8 := &person{
    "pprof.cn",
    "北京",
    18,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}

この形式で初期化する場合、注意すべき点は次のとおりです:

    1. 構造体のすべてのフィールドを初期化する必要があります。
    2. 初期値の埋め込み順序は、構造体内のフィールドの宣言順序と一致する必要があります。
    3. この方法はキーと値の初期化方法と混用できません。  

構造体のフィールドの可視性#

構造体のフィールドは大文字で始まる場合は公開アクセス可能であり、小文字で始まる場合はプライベート(定義された現在の構造体のパッケージ内でのみアクセス可能)です。

構造体の「継承」#

Go 言語では、構造体を使用して他のプログラミング言語の面向对象の継承を実現できます。

//Animal 動物
type Animal struct {
    name string
}

func (a *Animal) move() {
    fmt.Printf("%sは動く!\n", a.name)
}

//Dog 犬
type Dog struct {
    Feet    int8
    *Animal //匿名構造体を通じて継承を実現
}

func (d *Dog) wang() {
    fmt.Printf("%sはワンワンワンと言う\n", d.name)
}

func main() {
    d1 := &Dog{
        Feet: 4,
        Animal: &Animal{ //注意:構造体ポインタを埋め込む
            name: "レレ",
        },
    }
    d1.wang() //レレはワンワンワンと言う
    d1.move() //レレは動く!
}

構造体のフィールド名の衝突#

構造体内のフィールド名が同じである場合、フィールド名の衝突が発生します。この場合、具体的な埋め込まれた構造体のフィールドを指定する必要があります。

//Address アドレス構造体
type Address struct {
    Province string
    City     string
    CreateTime string
}

//Email メール構造体
type Email struct {
    Account    string
    CreateTime string
}

//User ユーザー構造体
type User struct {
    Name    string
    Gender  string
    Address
    Email
}

func main() {
    var user3 User
    user3.Name = "pprof"
    user3.Gender = "女"
    // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
    user3.Address.CreateTime = "2000" //Address構造体のCreateTimeを指定
    user3.Email.CreateTime = "2000"   //Email構造体のCreateTimeを指定
}  

構造体のタグ(Tag)#

タグは構造体のメタ情報であり、実行時にリフレクションのメカニズムを通じて読み取ることができます。

タグは構造体フィールドの後方に定義され、バッククォートで囲まれています。具体的な形式は次のとおりです:

    `key1:"value1" key2:"value2"`  

構造体タグは 1 つ以上のキーと値のペアで構成されます。キーと値はコロンで区切られ、値は二重引用符で括られます。キーと値のペアは空白で区切られます。注意事項:構造体にタグを書く際は、キーと値のルールを厳守する必要があります。構造体タグの解析コードの耐障害能力は非常に低く、形式が間違っているとコンパイルや実行時にエラーが表示されず、リフレクションを通じても正しく値を取得できません。例えば、key と value の間に空白を追加しないでください。

例えば、Student 構造体の各フィールドに JSON シリアル化時に使用されるタグを定義します:

//Student 学生
type Student struct {
    ID     int
    Gender string
    Name   string
}

//Class クラス
type Class struct {
    Title    string
    Students []*Student
}

func main() {
    c := &Class{
        Title:    "101",
        Students: make([]*Student, 0, 200),
    }
    for i := 0; i < 10; i++ {
        stu := &Student{
            Name:   fmt.Sprintf("stu%02d", i),
            Gender: "男",
            ID:     i,
        }
        c.Students = append(c.Students, stu)
    }
    //JSONシリアル化:構造体-->JSON形式の文字列
    data, err := json.Marshal(c)
    if err != nil {
        fmt.Println("json marshal failed")
        return
    }
    fmt.Printf("json:%s\n", data)
    //JSON逆シリアル化:JSON形式の文字列-->構造体
    str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
    c1 := &Class{}
    err = json.Unmarshal([]byte(str), c1)
    if err != nil {
        fmt.Println("json unmarshal failed!")
        return
    }
    fmt.Printf("%#v\n", c1)
} 

構造体のタグ(Tag)#

タグは構造体のメタ情報であり、実行時にリフレクションのメカニズムを通じて読み取ることができます。

タグは構造体フィールドの後方に定義され、バッククォートで囲まれています。具体的な形式は次のとおりです:

    `key1:"value1" key2:"value2"`  

構造体タグは 1 つ以上のキーと値のペアで構成されます。キーと値はコロンで区切られ、値は二重引用符で括られます。キーと値のペアは空白で区切られます。注意事項:構造体にタグを書く際は、キーと値のルールを厳守する必要があります。構造体タグの解析コードの耐障害能力は非常に低く、形式が間違っているとコンパイルや実行時にエラーが表示されず、リフレクションを通じても正しく値を取得できません。例えば、key と value の間に空白を追加しないでください。

例えば、Student 構造体の各フィールドに JSON シリアル化時に使用されるタグを定義します:

//Student 学生
type Student struct {
    ID     int
    Gender string
    Name   string
}

//Class クラス
type Class struct {
    Title    string
    Students []*Student
}

func main() {
    c := &Class{
        Title:    "101",
        Students: make([]*Student, 0, 200),
    }
    for i := 0; i < 10; i++ {
        stu := &Student{
            Name:   fmt.Sprintf("stu%02d", i),
            Gender: "男",
            ID:     i,
        }
        c.Students = append(c.Students, stu)
    }
    //JSONシリアル化:構造体-->JSON形式の文字列
    data, err := json.Marshal(c)
    if err != nil {
        fmt.Println("json marshal failed")
        return
    }
    fmt.Printf("json:%s\n", data)
    //JSON逆シリアル化:JSON形式の文字列-->構造体
    str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
    c1 := &Class{}
    err = json.Unmarshal([]byte(str), c1)
    if err != nil {
        fmt.Println("json unmarshal failed!")
        return
    }
    fmt.Printf("%#v\n", c1)
} 

インターフェース#

インターフェースタイプ#

Go 言語におけるインターフェース(interface)は、型の一種であり、抽象的な型です。

インターフェースはメソッドの集合であり、ダックタイピングプログラミングの一形態を示しています。インターフェースが行うことは、プロトコル(ルール)を定義することです。ある機械が洗濯や脱水の機能を持っている限り、それを洗濯機と呼ぶことができます。属性(データ)に関心を持たず、動作(メソッド)に関心を持ちます。

インターフェースはメソッドの宣言のみを持ち、実装は持たず、データフィールドは持ちません。インターフェースは匿名で他のインターフェースに埋め込むことができ、構造体に埋め込むこともできます。オブジェクトの割り当ては、インターフェースが nil である場合にのみ行われます。

インターフェースの定義形式は次のとおりです:

    type インターフェース名 interface{
        メソッド名1( パラメータリスト1 ) 戻り値リスト1
        メソッド名2( パラメータリスト2 ) 戻り値リスト2

    } 

ここで:

    1.インターフェース名:インターフェースを自定義の型名として定義します。Go言語のインターフェースは命名時に一般的に単語の後にerを追加します。たとえば、書き込み操作を持つインターフェースはWriterと呼ばれ、文字列機能を持つインターフェースはStringerと呼ばれます。インターフェース名はそのインターフェースの型の意味を強調する必要があります。
    2.メソッド名:メソッド名の最初の文字が大文字であり、このインターフェースの型名の最初の文字も大文字である場合、このメソッドはインターフェースが所在するパッケージ(package)以外のコードからアクセス可能です。
    3.パラメータリスト、戻り値リスト:パラメータリストと戻り値リストのパラメータ変数名は省略可能です。  

例えば、次のように Sayer インターフェースを定義します:

// Sayer インターフェース
type Sayer interface {
    say()
} 

次に、犬と猫の構造体を定義します:

type dog struct {}

type cat struct {} 

Sayer インターフェースには say メソッドが 1 つだけあるため、dog と cat それぞれに say メソッドを実装するだけで Sayer インターフェースを実装できます。

// dogがSayerインターフェースを実装
func (d dog) say() {
    fmt.Println("汪汪汪")
}

// catがSayerインターフェースを実装
func (c cat) say() {
    fmt.Println("喵喵喵")
} 

インターフェースの実装は非常に簡単で、インターフェース内のすべてのメソッドを実装すれば、そのインターフェースを実装したことになります。

インターフェースの型変数#

インターフェースを実装したオブジェクトは、インターフェース型の変数に格納できます。例えば、上記の例では、Sayer 型の変数は dog と cat 型の変数を格納できます。

func main() {
    var x Sayer // Sayer型の変数xを宣言
    a := cat{}  // catをインスタンス化
    b := dog{}  // dogをインスタンス化
    x = a       // catインスタンスを直接xに代入
    x.say()     // 喵喵喵
    x = b       // dogインスタンスを直接xに代入
    x.say()     // 汪汪汪
} 

値受信者とポインタ受信者のインターフェース実装の違い#

値受信者を使用してインターフェースを実装する場合と、ポインタ受信者を使用してインターフェースを実装する場合の違いは何でしょうか?次の例を見てみましょう。

私たちは Mover インターフェースと dog 構造体を持っています。

type Mover interface {
    move()
}

type dog struct {}

値受信者を使用してインターフェースを実装#

func (d dog) move() {
    fmt.Println("犬が動く")
}  

この時、インターフェースを実装しているのは dog 型です:

func main() {
    var x Mover
    var wangcai = dog{} // 旺財はdog型
    x = wangcai         // xはdog型を受け取ることができる
    var fugui = &dog{}  // 富貴は*dog型
    x = fugui           // xは*dog型を受け取ることができる
    x.move()
} 

上記のコードを実行すると、値受信者を使用してインターフェースを実装した場合、dog 構造体と構造体ポインタ*dog型の変数を両方ともインターフェース型の変数に代入できることがわかります。なぜなら、Go 言語にはポインタ型変数の値を求める構文糖があるからです。

ポインタ受信者を使用してインターフェースを実装#

同じコードを使用して、ポインタ受信者を使用してインターフェースを実装する場合の違いを見てみましょう。

func (d *dog) move() {
    fmt.Println("犬が動く")
}
func main() {
    var x Mover
    var wangcai = dog{} // 旺財はdog型
    x = wangcai         // xはdog型を受け取ることができない
    var fugui = &dog{}  // 富貴は*dog型
    x = fugui           // xは*dog型を受け取ることができる
} 

この時、Mover インターフェースを実装しているのは*dog型ですので、dog 型の wangcai を x に渡すことはできません。この場合、x は*dog型の値のみを格納できます。

ここでのコードは良い面接問題です#

次のコードはコンパイルを通過しますか?

通過しません、必ず var peo People = &Student {} にする必要があります。

type People interface {
    Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
    if think == "sb" {
        talk = "あなたは大イケメンです"
    } else {
        talk = "こんにちは"
    }
    return
}

func main() {
    var peo People = Student{}
    think := "bitch"
    fmt.Println(peo.Speak(think))
}

型とインターフェースの関係#

1 つの型が複数のインターフェースを実装#

1 つの型は同時に複数のインターフェースを実装できます。インターフェース間は互いに独立しており、相手の実装を知りません。たとえば、犬は鳴くことができ、車も動くことができます。次のように Sayer インターフェースと Mover インターフェースを定義できます。

// Sayer インターフェース
type Sayer interface {
    say()
}

// Mover インターフェース
type Mover interface {
    move()
} 

dog は Sayer インターフェースを実装し、Mover インターフェースも実装できます。

type dog struct {
    name string
}

// Sayerインターフェースを実装
func (d dog) say() {
    fmt.Printf("%sは汪汪汪と言う\n", d.name)
}

// Moverインターフェースを実装
func (d dog) move() {
    fmt.Printf("%sは動く\n", d.name)
}

func main() {
    var x Sayer
    var a = dog{name: "旺財"}
    x = a
    x.say()
    var y Mover
    y = a
    y.move()
} 
複数の型が同じインターフェースを実装#

Go 言語では異なる型が同じインターフェースを実装できます。まず、Mover インターフェースを定義します。これは move メソッドを持つ必要があります。

// Mover インターフェース
type Mover interface {
    move()
} 

たとえば、犬は動くことができ、車も動くことができるので、次のようにこの関係を実装できます。

type dog struct {
    name string
}

type car struct {
    brand string
}

// dog型がMoverインターフェースを実装
func (d dog) move() {
    fmt.Printf("%sは走る\n", d.name)
}

// car型がMoverインターフェースを実装
func (c car) move() {
    fmt.Printf("%sは70マイルの速度で走る\n", c.brand)
}

この時、コード内で犬と車を「動く物体」として扱うことができます。具体的に何であるかを気にせず、move メソッドを呼び出すだけで済みます。

func main() {
    var x Mover
    var a = dog{name: "旺財"}
    var b = car{brand: "ポルシェ"}
    x = a
    x.move()
    x = b
    x.move()
} 

上記のコードの実行結果は次のとおりです:

    旺財は走る
    ポルシェは70マイルの速度で走る 

さらに、インターフェースのメソッドは必ずしも 1 つの型によって完全に実装される必要はなく、インターフェースのメソッドは他の型や構造体を埋め込むことによって実装できます。

// WashingMachine 洗濯機
type WashingMachine interface {
    wash()
    dry()
}

// 乾燥機
type dryer struct{}

// WashingMachineインターフェースのdry()メソッドを実装
func (d dryer) dry() {
    fmt.Println("脱水中")
}

// Haier洗濯機
type haier struct {
    dryer // 乾燥機を匿名で埋め込む
}

// WashingMachineインターフェースのwash()メソッドを実装
func (h haier) wash() {
    fmt.Println("洗濯中")
}

func main() {
    var x WashingMachine
    x = haier{}
    x.wash()
    x.dry()
}

空インターフェース#

空インターフェースの定義#

空インターフェースは、何のメソッドも定義されていないインターフェースです。したがって、任意の型が空インターフェースを実装します。

空インターフェース型の変数は、任意の型の変数を格納できます。

func main() {
    // 空インターフェースxを定義
    var x interface{}
    s := "pprof.cn"
    x = s
    fmt.Printf("type:%T value:%v\n", x, x)
    i := 100
    x = i
    fmt.Printf("type:%T value:%v\n", x, x)
    b := true
    x = b
    fmt.Printf("type:%T value:%v\n", x, x)
}
空インターフェースの応用#
空インターフェースを関数の引数として使用

空インターフェースを使用して任意の型の関数パラメータを受け取ることができます。

// 空インターフェースを関数パラメータとして使用
func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
} 
空インターフェースを map の値として使用

空インターフェースを使用して任意の値を保存する辞書を作成できます。

// 空インターフェースをmapの値として使用
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo) 
型アサーション

空インターフェースは任意の型の値を格納できるため、格納されている具体的なデータを取得するにはどうすればよいでしょうか?

インターフェースの値(インターフェースの型の値)は、具体的な型と具体的な型の値の 2 つの部分で構成されています。これら 2 つの部分は、インターフェースの動的型と動的値と呼ばれます。

具体的な例を見てみましょう:

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil 

インターフェースの型の値を取得するには、次のように型アサーションを使用します。

    x.(T) 

ここで:

    x:interface{}型の変数を示します
    T:xが可能性のある型を示します。

この構文は 2 つのパラメータを返します。最初のパラメータは x を T 型に変換した変数であり、2 番目の値はブール値で、true の場合はアサーションが成功したことを示し、false の場合はアサーションが失敗したことを示します。

例えば:

func main() {
    var x interface{}
    x = "pprof.cn"
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("型アサーション失敗")
    }
} 

上記の例では、アサーションを複数回行う必要がある場合、if 文を複数書く必要があります。この場合、switch 文を使用して実装できます。

func justifyType(x interface{}) {
    switch v := x.(type) {
    case string:
        fmt.Printf("xは文字列です,値は%vです\n", v)
    case int:
        fmt.Printf("xは整数です,値は%vです\n", v)
    case bool:
        fmt.Printf("xはブールです,値は%vです\n", v)
    default:
        fmt.Println("サポートされていない型です!")
    }
} 

空インターフェースは任意の型の値を格納できるため、空インターフェースは Go 言語で非常に広く使用されています。

インターフェースを使用する必要があるのは、2 つ以上の具体的な型が同じ方法で処理される必要がある場合だけです。インターフェースのためにインターフェースを書くべきではありません。そうすると、不必要な抽象化が増え、実行時の損失が発生します。

異常処理#

Go 言語では、panic を発生させることができ、defer で recover を使用してこの異常をキャッチし、正常に処理することができます。

panic:

    1. 組み込み関数
    2. 関数Fの中にpanic文が書かれている場合、その後の実行コードは終了し、panicが発生した関数F内にdefer関数リストが存在する場合、deferの逆順で実行されます。
    3. 関数Fの呼び出し元Gに戻り、G内の関数F文の後のコードは実行されず、関数G内にdefer関数リストが存在する場合、deferの逆順で実行されます。
    4. goroutine全体が終了し、エラーを
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。