The essence of GO language interview - dynamic type and dynamic value of interface?

Dynamic types and dynamic values ​​of interfaces?

You can see from the source code: ifaceIt contains two fields: taban interface table pointer, which points to type information; dataand a data pointer, which points to specific data. They are called respectively 动态类型and 动态值. And interface values ​​include 动态类型and 动态值.

[Extension 1] nilCompare the interface type with

A zero value for an interface value means both 动态类型and . This interface value will be considered only if and only if the values ​​of both parts are .动态值nilnil接口值 == nil

Let’s see an example:

package main

import "fmt"

type Coder interface {
	code()
}

type Gopher struct {
	name string
}

func (g Gopher) code() {
	fmt.Printf("%s is coding\n", g.name)
}

func main() {
	var c Coder
	fmt.Println(c == nil)
	fmt.Printf("c: %T, %v\n", c, c)

	var g *Gopher
	fmt.Println(g == nil)

	c = g
	fmt.Println(c == nil)
	fmt.Printf("c: %T, %v\n", c, c)
}

Output:

true
c: <nil>, <nil>
true
false
c: *main.Gopher, <nil>

At the beginning, cthe dynamic type and dynamic value of are both nil, gand nilwhen is assigned gto c, cthe dynamic type of becomes *main.Gopher, although cthe dynamic value of is still nil, but when compared with , the result cis .nilfalse

[Extension 2]
Let’s take a look at an example and its output:

package main

import "fmt"

type MyError struct {}

func (i MyError) Error() string {
	return "MyError"
}

func main() {
	err := Process()
	fmt.Println(err)

	fmt.Println(err == nil)
}

func Process() error {
	var err *MyError = nil
	return err
}

Function execution result:

<nil>
false

Here we first define a MyErrorstructure, implement Errorthe function, and also implement errorthe interface. ProcessThe function returns an errorinterface, which implies type conversion. So, although its value is nil, actually its type is *MyError, and nilwhen finally compared with , the result is false.

[Extension 3] How to print out the dynamic type and value of the interface?

Look directly at the code:

package main

import (
	"unsafe"
	"fmt"
)

type iface struct {
	itab, data uintptr
}

func main() {
	var a interface{} = nil

	var b interface{} = (*int)(nil)

	x := 5
	var c interface{} = (*int)(&x)
	
	ia := *(*iface)(unsafe.Pointer(&a))
	ib := *(*iface)(unsafe.Pointer(&b))
	ic := *(*iface)(unsafe.Pointer(&c))

	fmt.Println(ia, ib, ic)

	fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}

ifaceA structure is directly defined in the code , using two pointers to describe itaband data, and then the contents of a, b, c in the memory are forced to be interpreted as our own iface. Finally, you can print out the addresses of dynamic types and dynamic values.

The running results are as follows:

{
    
    0 0} {
    
    17426912 0} {
    
    17426912 842350714568}
5

The dynamic type and address of the dynamic value of a are both 0, which is nil; the dynamic type of b is the same as the dynamic type of c; *intfinally, the dynamic value of c is 5.

The compiler automatically detects whether the type implements the interface

I often see some strange usages similar to the following in some open source libraries:

var _ io.Writer = (*myWriter)(nil)

At this time, you will be a little confused and don't know what the author wants to do. In fact, this is the answer to this question. The compiler will check *myWriterwhether the type implements io.Writerthe interface.

Let’s look at an example:

package main

import "io"

type myWriter struct {

}

/*func (w myWriter) Write(p []byte) (n int, err error) {
	return
}*/

func main() {
    // 检查 *myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = (*myWriter)(nil)

    // 检查 myWriter 类型是否实现了 io.Writer 接口
    var _ io.Writer = myWriter{}
}

After commenting out the Write function defined for myWriter, run the program:

src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:
	*myWriter does not implement io.Writer (missing Write method)
src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:
	myWriter does not implement io.Writer (missing Write method)

Error message: *myWriter/myWriter does not implement the io.Writer interface, that is, the Write method is not implemented.

After uncommenting, no error is reported when running the program.

In fact, the above assignment statement will cause implicit type conversion. During the conversion process, the compiler will detect whether the type on the right side of the equal sign implements the function specified by the interface on the left side of the equal sign.

To summarize, you can check whether the type implements the interface by adding code similar to the following:

var _ io.Writer = (*myWriter)(nil)
var _ io.Writer = myWriter{}

How to use interface to achieve polymorphism?

GoThe language is not designed with concepts such as virtual functions, pure virtual functions, inheritance, multiple inheritance, etc., but it supports object-oriented features very elegantly through interfaces.

Polymorphism is a run-time behavior that has the following characteristics:

  1. One type has abilities of multiple types
  2. Allow different objects to react flexibly to the same message
  3. treat each used object in a common way
  4. Non-dynamic languages ​​must be implemented through inheritance and interfaces

Look at a code example that implements polymorphism:

package main

import "fmt"

func main() {
	qcrao := Student{age: 18}
	whatJob(&qcrao)

	growUp(&qcrao)
	fmt.Println(qcrao)

	stefno := Programmer{age: 100}
	whatJob(stefno)

	growUp(stefno)
	fmt.Println(stefno)
}

func whatJob(p Person) {
	p.job()
}

func growUp(p Person) {
	p.growUp()
}

type Person interface {
	job()
	growUp()
}

type Student struct {
	age int
}

func (p Student) job() {
	fmt.Println("I am a student.")
	return
}

func (p *Student) growUp() {
	p.age += 1
	return
}

type Programmer struct {
	age int
}

func (p Programmer) job() {
	fmt.Println("I am a programmer.")
	return
}

func (p Programmer) growUp() {
	// 程序员老得太快 ^_^
	p.age += 10
	return
}

The code first defines an Personinterface, including two functions:

job()
growUp()

Then, two more structures are defined, Studentand , at the same time , Programmerthe type implements the two functions defined by the interface. Note that the type implements the interface, but the type does not.*StudentProgrammerPerson*StudentStudent

After that, I defined Persontwo functions whose function parameters are interfaces:

func whatJob(p Person)
func growUp(p Person)

mainIn the function, the objects of Studentand are first generated Programmer, and then they are passed into the functions whatJoband respectively growUp. In the function, the interface function is called directly. During actual execution, it depends on the entity type that is finally passed in, and the function implemented by the entity type is called. As a result, different objects can have multiple representations of the same message, 多态which is achieved.

To go a little deeper, inside the function whatJob()or growUp(), the interface personis bound to the entity type *Studentor Programmer. According to the source code analyzed earlier , the function saved in ifacewill be directly called here , similar to: , and because the array stores functions implemented by entity types, when the function passes in different entity types, the calls are actually different Function implementation, thereby achieving polymorphism.funs.tab->fun[0]fun

Run the code:

I am a student.
{
    
    19}
I am a programmer.
{
    
    100}

What are the similarities and differences between Go interfaces and C++ interfaces?

An interface defines a specification that describes the behavior and functions of a class without specific implementation.

C++ interfaces are implemented using abstract classes. If at least one function in the class is declared as a pure virtual function, the class is an abstract class. Pure virtual functions are specified by using "= 0" in the declaration. For example:

class Shape
{
   public:
      // 纯虚函数
      virtual double getArea() = 0;
   private:
      string name;      // 名称
};

The purpose of designing an abstract class is to provide an appropriate base class for other classes to inherit from. An abstract class cannot be used to instantiate objects, it can only be used as an interface.

A derived class needs to explicitly declare that it inherits from the base class and needs to implement all pure virtual functions in the base class.

The way C++ defines interfaces is called "intrusive", while Go uses a "non-intrusive" method, which does not require explicit declaration. You only need to implement the functions defined by the interface, and the compiler will automatically recognize it.

The difference in the way C++ and Go define interfaces also leads to differences in underlying implementation. C++ uses virtual function tables to implement base classes calling functions of derived classes; while Go uses fields itabin funto implement interface variables calling functions of entity types. The virtual function table in C++ is generated at compile time; while the fields itabin Go funare dynamically generated during runtime. The reason is that entity types in Go may inadvertently implement N multiple interfaces. Many interfaces are not originally needed, so one cannot be generated for all interfaces implemented by the type. This is also the impact of " itabnon-intrusive"; this is This does not exist in C++ because derivation requires an explicit declaration of which base class it inherits from.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132795705