Go言語面接エッセンス - ifaceとefaceの違いは何ですか?Go言語とダックタイピングにはどのような関係があるのでしょうか?

ifaceと はefaceどちらも Go のインターフェイスを記述する基礎となる構造ですが、ifaceで記述されるインターフェイスにはメソッドが含まれているのに対し、efaceはメソッドを含まない空のインターフェイスであるという違いがありますinterface{}

ソースコードレベルを見てみましょう。

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter  *interfacetype
	_type  *_type
	link   *itab
	hash   uint32 // copy of _type.hash. Used for type switches.
	bad    bool   // type does not implement interface
	inhash bool   // has this itab been added to hash?
	unused [2]byte
	fun    [1]uintptr // variable sized
}

iface2 つのポインタが内部的に維持され、インターフェイスのタイプとこのインターフェイスに割り当てられたエンティティ タイプを表すエンティティtabを指しますitabdataこれは、インターフェイスの特定の値、通常はヒープ メモリへのポインタを指します。

itab構造を詳しく見てみましょう:_typeフィールドはメモリの配置、サイズなどを含むエンティティのタイプを記述し、interフィールドはインターフェイスのタイプを記述します。funフィールドの配置と、インターフェイス メソッドに対応する特定のデータ型のメソッド アドレスは、インターフェイス呼び出しメソッドの動的ディスパッチを実装します。通常、このテーブルは、インターフェイスまたはキャッシュされた itab に値を割り当てるときに変換が発生するたびに更新されます。直接入手することになります。

ここにはエンティティ タイプとインターフェイスに関連するメソッドのみがリストされており、エンティティ タイプの他のメソッドはここには表示されません。C++ を学習している場合は、ここで仮想関数の概念を類推できます。

さらに、なぜfun配列のサイズが 1 なのか疑問に思うかもしれません。インターフェイスで複数のメソッドが定義されている場合はどうなるでしょうか。実際、ここに格納されているのは最初のメソッドの関数ポインタであり、メソッドがさらに存在する場合は、それ以降もメモリ空間に格納され続けます。アセンブリの観点から見ると、これらの関数ポインタはアドレスを追加することで取得でき、影響はありません。なお、これらのメソッドは関数名ごとに辞書順に並べてあります。

interfacetypeインターフェースのタイプを表す typeをもう一度見てみましょう。

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

ご覧のとおり、型をラップしています。_type型は、_type実際には Go 言語でさまざまなデータ型を記述する構造体です。mhdrここには、インターフェースによって定義された関数のリストを表し、pkgpathインターフェースを定義するパッケージの名前を記録するフィールドもあることに気付きました。

iface全体的な構造を示す図は次のとおりです。

ここに画像の説明を挿入します

efaceソースコードを見てみましょう:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

それに比べてifaceefaceそれは比較的単純です。維持されるフィールドは 1 つだけで_type、空のインターフェイスによって伝送される特定のエンティティ タイプを示します。data具体的な値について説明します。

ここに画像の説明を挿入します

例を見てみましょう:

package main

import "fmt"

func main() {
	x := 200
	var any interface{} = x
	fmt.Println(any)

	g := Gopher{"Go"}
	var c coder = g
	fmt.Println(c)
}

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p Gopher) debug() {
	fmt.Printf("I am debuging %s language\n", p.language)
}

コマンドを実行し、アセンブリ言語を出力します。

go tool compile -S ./src/main.go

ご覧のとおり、main 関数で 2 つの関数が呼び出されます。

func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

上記の 2 つの関数のパラメータとiface構造体のフィールドefaceはリンクできます。両方の関数はパラメータを結合して组装最終的なインターフェイスを形成します。

補足として、最後に_type構造を見てみましょう。

type _type struct {
    // 类型大小
	size       uintptr
    ptrdata    uintptr
    // 类型的 hash 值
    hash       uint32
    // 类型的 flag,和反射相关
    tflag      tflag
    // 内存对齐相关
    align      uint8
    fieldalign uint8
    // 类型的编号,有bool, slice, struct 等等等等
	kind       uint8
	alg        *typeAlg
	// gc 相关
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

Go 言語のさまざまなデータ型は、_typeフィールドに基づいていくつかの追加フィールドを追加することで管理されます。

type arraytype struct {
	typ   _type
	elem  *_type
	slice *_type
	len   uintptr
}

type chantype struct {
	typ  _type
	elem *_type
	dir  uintptr
}

type slicetype struct {
	typ  _type
	elem *_type
}

type structtype struct {
	typ     _type
	pkgPath name
	fields  []structfield
}

これらのデータ型の構造定義は、リフレクション実装の基礎となります。

Go言語とダックタイピングの関係

Wikipedia の定義を直接見てみましょう。

アヒルのように見え、アヒルのように泳ぎ、アヒルのように鳴く場合、それはおそらくアヒルです。

翻訳: アヒルのように見え、アヒルのように泳ぎ、アヒルのように鳴く場合、それはアヒルとみなされます。

Duck Typingダック タイピングは動的プログラミング言語のオブジェクト推論戦略であり、オブジェクト自体の型ではなく、オブジェクトがどのように使用されるかに重点を置きます。Go 言語は静的言語として、インターフェースを介したダックタイピングを完全にサポートしています。

たとえば、動的言語 Python では、次のような関数を定義します。

def hello_world(coder):
    coder.say_hello()

say_hello()この関数を呼び出すときは、関数を実装している限り、任意の型を渡すことができます。未実装の場合、動作時にエラーが発生します。

Java や C++ などの静的言語では、このインターフェイスが必要な場所で使用する前に、インターフェイスを実装することを明示的に宣言する必要があります。プログラム内で関数を呼び出し、まったく実装されていない型をhello_world渡しても、コンパイル段階では渡されません。say_hello()これが、静的言語が動的言語より安全である理由です。

動的言語と静的言語の違いがここに反映されています。静的言語は、エラーが報告される前にそのコード行まで実行する必要がある動的言語とは異なり、コンパイル中に型の不一致エラーを検出できます。ちなみに、これもpython私が使いたくない理由の一つです。もちろん、静的言語ではプログラマがコーディング段階で規定に従ってプログラムを書いたり、変数ごとにデータ型を指定したりする必要があり、ある程度の作業負荷とコード量が増加します。動的言語にはこれらの要件がないため、人々はビジネスに集中することができ、コードは短く、より速く書くことができます。Python を書く学生はこのことをよく知っています。

Go 言語は、最新の静的言語としては後発であるという利点があります。動的言語の利便性を導入し、同時に静的言語の型チェックも行うので、書いていてとても楽しいです。Go は妥協的なアプローチを採用しており、型がインターフェイスを実装していることを明示的に宣言する必要はなく、関連するメソッドが実装されている限り、コンパイラはそれを検出できます。

例を見てみましょう:

まず、インターフェイスと、このインターフェイスをパラメータとして使用する関数を定義します。

type IGreeting interface {
	sayHello()
}

func sayHello(i IGreeting) {
	i.sayHello()
}

2 つの構造を定義しましょう。

type Go struct {}
func (g Go) sayHello() {
	fmt.Println("Hi, I am GO!")
}

type PHP struct {}
func (p PHP) sayHello() {
	fmt.Println("Hi, I am PHP!")
}

最後に、main 関数で SayHello() 関数を呼び出します。

func main() {
	golang := Go{}
	php := PHP{}

	sayHello(golang)
	sayHello(php)
}

プログラム出力:

Hi, I am GO!
Hi, I am PHP!

main 関数では、sayHello() 関数を呼び出すときにgolang, phpオブジェクトが渡されます。これらのオブジェクトは、IGreeting 型の実装を明示的に宣言せず、インターフェイスで指定された SayHello() 関数のみを実装します。実際、コンパイラが SayHello() 関数を呼び出すと、golang, phpオブジェクトは暗黙的に IGreeting 型に変換されます。これは、静的言語の型チェック関数でもあります。

ところで、動的言語の特徴について触れておきます。

変数バインディングのタイプは未定義であり、実行時にのみ決定できます。
関数とメソッドは任意のタイプのパラメータを受け取ることができ、呼び出し時にパラメータのタイプはチェックされません。
インターフェイスを実装する必要はありません。

要約すると、ダックタイピングは、オブジェクトの有効なセマンティクスが特定のクラスからの継承や特定のインターフェイスの実装によって決定されるのではなく、その「現在のメソッドとプロパティのセット」によって決定される動的言語のスタイルです。Go は静的言語として、 インターフェイス を通じて実装されており鸭子类型、実際、Go のコンパイラはその中で隠れた変換作業を実行します。

おすすめ

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