go类型相关常见问题

1.如何判断变量类型

有时候需要根据变量类型动态判断变量类型,可以有如下三种方法:

func getType( i interface{}){
	switch i.(type) {
	case int32:
		fmt.Println("int32 variable")
	case int64:
		fmt.Println("int64 variable")
	case int:
		fmt.Println("int variable")
	case string:
		fmt.Println("string variable")
	}

	if _,ok := i.(int); ok {
		fmt.Println("int variable 2")
	}
}

func getType2(i interface{}){
	fmt.Println(reflect.TypeOf(i))
}

func getType3(i interface{}){
	fmt.Printf("%T", i)
}

方法1,采用.(类型)调用语法,如果指定具体类型,则可以通过返回字段判断是否具体类型;如果传入type,那么返回的就是转换称对应变量的类型

方法2,使用反射调用

方法3,使用Printf动态判断

注意,这里方法1必须在接口上调用,示例采用的是通过引入中间空接口来完成的,如下直接在int变量上调用会失败,方法2/3则无此限制

	var a int
	getType(a)
	getType2(a)
	getType3(a)

	// error
	//if _,ok := a.(int); ok {
	//	fmt.Println("int variable 2")
	//}
	
	fmt.Println(reflect.TypeOf(a))
	fmt.Printf("%T", a)

变量类型判断可以用来实现根据类型调用不同方法,如下testTool传入Hammer类型调用Hit方法,传入Knife类型调用Cut方法。

type Hammer struct {
	Owner string
}

func (h Hammer) Hit(){
	fmt.Println(h.Owner, " Hit!")
}

type Knife struct {
	Owner string
}

func (h Knife) Cut(){
	fmt.Println(h.Owner, " Cut!")
}

func testTool(i interface{}){
	switch v:=i.(type) {
	case Hammer:
		v.Hit()
	case Knife:
		v.Cut()
	}
}

2.方法实现使用值接受者还是指针接受者

go语言中最大幅度隐藏了指针和值的差别,实现结构的方法时,可以以值接受者或指针接受者来实现对应方法。两者都可以在指针或值类型上调用,具体的调用时go会将变量隐式转成对应的接受者类型,大大减少工作量。

那么具体实现时,用值接受者还是指针接受者呢?

这个和具体的方法目的有关,如果方法的目的是生成新的对象,那么使用值接受者;如果方法的目的是为了修改当前变量值,那么使用指针接受者。总之,值是为了新增,指针是为了共享。

3.函数参数使用值还是指针

同样函数参数可以使用值或指针,原则和其他语言一样:

1.对普通类型,int、float、数组或简单struct等,不需要改变值时都使用传值,需要改变或返回值时使用指针

2.如果传递的数组或结构体很大,为了优化内存,也可以使用传指针

3.对于slice、map、接口等引用类型,直接传值

但是注意一点:对于string类型,传值即可。我们知道基础类型赋值都是拷贝,因此自然想到string类型对应值较大时,如果传值而且这里不需要改变值,会不会占用较大内存。实际上go中string的内存结构如下(参考):

这里两个string变量底层都是一样,只是标头各不一样,其实就相当于引用。因此函数调用时,string类型复制的是标头,既然是超大string传值一样可以满足要求。

这里逻辑可以如下简单验证,传入函数一个超大的string变量,观察函数调用前后内存耗用情况,发现函数调用前后,内存耗用变化不大。

func dumpString(s string){
	fmt.Println("in dumpString")
	time.Sleep(time.Duration(20)*time.Second)
}

s := "test stringtest stringtest "
for i:=0; i<10000; i++ {
    s += "test stringtest stringtest "
}

fmt.Println("start")
time.Sleep(time.Duration(20)*time.Second)
dumpString(s)

4.转成接口时传值还是指针

对于如下接口

type People interface{
	SayHello()
	SayGoodbye()
}

如下,分别以值接受者和指针接收者实现方法,如下:

type Student struct {
	Name string
	Age int
}

func (s Student) SayHello(){
	fmt.Println("Hello My name is ", s.Name, " age is ", s.Age)
}

func (s *Student) SayGoodbye(){
	fmt.Println("Goodbye My name is ", s.Name, " age is ", s.Age)
}

分别,用指针和值转成接口调用如下:

func main(){
	s := Student{"wenzhou", 10}

	(&s).SayHello()
	(&s).SayGoodbye()
	s.SayHello()
	s.SayGoodbye()

	InvokeOnHello(&s)
	InvokeOnHello(s)
}

会发现在调用InvokeOnHello(s)时报错如下

Student does not implement People (SayGoodbye method has pointer receiver)

为什么直接调用时使用指针和值都没问题,在使用转成接口调用时使用指针没问题,但是值就有问题呢?

这里涉及到go中变量布局问题,上述问题抽象如下。可以看到每个类型由iTable和对应值构成,以Student为例,值保存在Student值中,iTable包括类型信息和方法集,每个类型包括值类型和指针类型,在声明方法时,值接受者方法关联到值类型和指针类型方法集,指针接受者方法仅关联到指针类型方法集,所以Student类型方法集仅包含SayHello方法,*Student类型方法集包含SayHello和SayGoodbye方法。

在实际调用时,如果是直接调用,会默认转成对应接受者类型,调用对应方法集方法即可。但是对于接口不一样,在接口赋值前,接口是无状态的,如上,接口赋值时后保存两个地址,一个是对应类型的iTable地址,一个是对应值的地址。很容易看到,接口传值时指向值类型方法集,接口传指针时指向指针类型方法集,而值类型方法集没有实现SayGoodbye方法,所以报之前的错误

那么,为什么值类型仅关联值接受者方法集呢,这是因为知道一个指针一定可以知道对应的值,但是知道一个值不一定能获取对应的地址,一个简单的实例如下:

type Test int

func (t *Test) Output() {
	fmt.Println("test ", int(*t))
}

a := Test(10)
a.Output()
Test(10).Output()

可以看到输出

cannot take the address of Test(10)

注意这里的内存布局原因就不会再给接口传值时出现错误。

演示代码下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

猜你喜欢

转载自blog.csdn.net/wenzhou1219/article/details/81780234