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