GO Language インタビュー エッセンス - 型変換とアサーションの違いは何ですか?

Go 言語では暗黙的な型変換が許可されていないことはわかっています。つまり、=異なる型の変数を両側に出現させることは許可されていません。

类型转换类型断言本質は、あるタイプを別のタイプに変換することです。違いは、型アサーションがインターフェイス変数で動作することです。

型変換

の場合类型转换、変換前と変換後の 2 つの型に互換性がある必要があります。型変換の構文は次のとおりです。

<結果のタイプ> := <ターゲットのタイプ> ( <式> )

package main

import "fmt"

func main() {
	var i int = 9

	var f float64
	f = float64(i)
	fmt.Printf("%T, %v\n", f, f)

	f = 10.8
	a := int(f)
	fmt.Printf("%T, %v\n", a, a)

	// s := []int(i)
}

int上記のコードでは、 type とtype の変数を定義しfloat64、その前に相互に変換しようとしましたが、結果は成功し、inttype と type はfloat64相互に互換性があります。

コードの最後の行のコメントを解除すると、コンパイラは型の非互換性エラーを報告します。

cannot convert i (type int) to type []int

断言

前に述べたように、空のインターフェイスではinterface{}関数が定義されていないため、Go のすべての型は空のインターフェイスを実装します。関数の仮パラメータが の場合interface{}、関数内でその真の型を取得するには、仮パラメータに対してアサーションを行う必要があります。

アサーションの構文は次のとおりです。

<対象の型の値>, <ブール型パラメータ> := <式>.(対象の型) // 安全な型のアサーション <対象の型
の値> := <式>.(対象の型) // 安全でない型のアサーション

型変換は型アサーションに似ていますが、異なる点は、型アサーションがインターフェイス上で動作することです。

簡単な例を見てみましょう:

package main

import "fmt"

type Student struct {
	Name string
	Age int
}

func main() {
	var i interface{} = new(Student)
	s := i.(Student)
	
	fmt.Println(s)
}

それを実行します:

panic: interface conversion: interface {
    
    } is *main.Student, not main.Student

直接的にはpanic、これは型ではなく型でiあるため、アサーションが失敗します。これは直接発生したものです。オンライン コードはこれには適していない可能性があります。「安全性アサーション」構文を使用できます。*StudentStudentpanic

func main() {
	var i interface{} = new(Student)
	s, ok := i.(Student)
	if ok {
		fmt.Println(s)
	}
}

こうすることで、たとえアサーションが失敗したとしても、失敗しませんpanic

switch実際には、ステートメントを使用してインターフェイスのタイプを決定するために使用されるアサーションの別の形式があります。それぞれをcase順番に検討していきます。a がヒットすると、のステートメントがcase実行されます。複数の一致が存在する可能性があるため、ステートメントの順序は非常に重要ですcasecasecase

コード例は次のとおりです。

func main() {
	//var i interface{} = new(Student)
	//var i interface{} = (*Student)(nil)
	var i interface{}

	fmt.Printf("%p %v\n", &i, i)

	judge(i)
}

func judge(v interface{}) {
	fmt.Printf("%p %v\n", &v, v)

	switch v := v.(type) {
	case nil:
		fmt.Printf("%p %v\n", &v, v)
		fmt.Printf("nil type[%T] %v\n", v, v)

	case Student:
		fmt.Printf("%p %v\n", &v, v)
		fmt.Printf("Student type[%T] %v\n", v, v)

	case *Student:
		fmt.Printf("%p %v\n", &v, v)
		fmt.Printf("*Student type[%T] %v\n", v, v)

	default:
		fmt.Printf("%p %v\n", &v, v)
		fmt.Printf("unknow\n")
	}
}

type Student struct {
	Name string
	Age int
}

main関数には 3 つの異なる宣言行があります。毎回 1 行を実行し、他の 2 行をコメント化して、3 セットの実行結果を取得します。

// --- var i interface{
    
    } = new(Student)
0xc4200701b0 [Name: ], [Age: 0]
0xc4200701d0 [Name: ], [Age: 0]
0xc420080020 [Name: ], [Age: 0]
*Student type[*main.Student] [Name: ], [Age: 0]

// --- var i interface{
    
    } = (*Student)(nil)
0xc42000e1d0 <nil>
0xc42000e1f0 <nil>
0xc42000c030 <nil>
*Student type[*main.Student] <nil>

// --- var i interface{
    
    }
0xc42000e1d0 <nil>
0xc42000e1e0 <nil>
0xc42000e1f0 <nil>
nil type[<nil>] <nil>

ステートメントの最初の行については、次のようになります。

var i interface{} = new(Student)

iこれは*Student3 番目のケースに一致する型で、印刷された 3 つのアドレスから判断すると、この 3 か所の変数は実際には異なります。main関数 にはローカル変数がありますi関数を呼び出すと、パラメータのコピーが実際にコピーされます。そのため、関数 には、vそのiコピーである別の変数があります。アサーションの後に、新しいコピーが生成されます。したがって、最終的に出力される 3 つの変数のアドレスは異なります。

ステートメントの 2 行目については、次のようになります。

var i interface{} = (*Student)(nil)

ここで説明したいのは、iここでの動的型は であり(*Student)、データはnilであり、その型は ではなくnil、それとnil比較すると得られる結果も であるということですfalse

ステートメントの最後の行:

var i interface{}

今回はそのタイプiです。nil

【拡張1】
fmt.Println関数のパラメータは ですinterface組み込み型の場合、関数は徹底的な方法を使用してその真の型を見つけ出し、それを印刷用の文字列に変換します。カスタム型の場合は、まずその型がString()メソッドを実装しているかどうかを確認します。実装している場合は、メソッドの結果が直接出力されますString()。そうでない場合は、オブジェクトのメンバーがリフレクションを介して走査され、出力されます。

もう一度短い例を見てみましょう。比較的単純なので、緊張しないでください。

package main

import "fmt"

type Student struct {
	Name string
	Age int
}

func main() {
	var s = Student{
		Name: "qcrao",
		Age: 18,
	}

	fmt.Println(s)
}

Student構造体はString()メソッドを実装していないため、fmt.Printlnリフレクションを使用してメンバー変数を 1 つずつ出力します。

{
    
    qcrao 18}

String()メソッドの実装を追加します。

func (s Student) String() string {
	return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}

結果を出力します。

[Name: qcrao], [Age: 18]

弊社がカスタマイズした方法で印刷します。

【拡張2】
上記の例について、以下のように変更すると、

func (s *Student) String() string {
	return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}

2 つの関数のレシーバーの型が異なることに注意してください。現在、Student構造体にはレシーバーの型を持つ関数が 1 つだけあります。結果を出力します指针类型String()

{
    
    qcrao 18}

なぜ?

型にはTレシーバが および であるTメソッドのみがあり、型に*TはレシーバがTおよび であるメソッドがあります*T構文的には、T直接調整できるメソッドは、 の糖衣構文に*Tすぎません。Go

したがって、Student構造体がString()レシーバーの型が値型であるメソッドを定義する場合、

fmt.Println(s)
fmt.Println(&s)

すべてカスタマイズされた形式で印刷できます。

Student構造体でString()レシーバーの型がポインター型であるメソッドが定義されている場合、そのメソッドは渡すことしかできません。

fmt.Println(&s)

カスタム形式で印刷します。

おすすめ

転載: blog.csdn.net/zy_dreamer/article/details/132795760