Golang基本类型整理
本文内容纲要:
总是用的用的就模糊了,不知道基本的类型有哪些,看来要反反复复弄几次。###Golang基本类型整理####基本类型以及定义变量需要注意的对于基本类型的介绍,感觉[这个博客](http://my.oschina.net/goal/blog/196891)讲的比较透彻,基本上都是从源码的角度来入手分析的,自己并没有理解到这么深入的程度,这里跟着文章过一下,挑一些主要的部分记录一下。在go语言中,数据类型分为**静态类型**和**底层类型**,感觉底层类型是golang实现的时候所使用的c语言的类型,而静态类型仅仅是针对于go语言本身而言所定义好的类型。具体的信息可以查看$GOROOT/src/runtime/runtime.h可以看到golang中的byte类型,通过`typedefuint8byte`来定义,这样在golang中直接使用反射
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varbbyte='D'
fmt.Println(reflect.TypeOf(b))
}
//output:uint8
这里通过反射得到的是uint8,8个bit表示的无符号整数类型。
string
可以看到string类型的结构体就是
structString
{
byte*str;
intgolen;
}
字符数组存储实际的数据,len存储长度。通过结构体就可以看到,定义好string变量之后是不能动态增加的,因为len已经写入进去了。
strconvgolang中string与基本数据类型之间的转化
fmt.sprintf函数也可以将interface类型转化为string类型
http://blog.csdn.net/siddontang/article/details/23541587
string之间的类型的比较strings.Equal??
slice
slice为了实现动态增长,又多添加了一个元素cap,这个元素表示已经分配的元素,就是相当于总的空间,len表示对于用户使用而言这个slice的实际长度。这个和java中的Arraylist感觉有点类似,就是先分配一个空间,之后看空间不够的话,再动态扩展,但是扩展的过程对使用者来说是不可见的:
structSlice
{
byte*array;
uintgolen;
uintgocap;
}
注意slice的第一个参数是一个byte指针,实际上指向的就是底层数组的对应的位置。
这里补充一点,关于初始化的问题,make函数可以用于对slice、map以及channel对象进行初始化操作,这三个对象的背后使用了必须要初始化的结构,比如对于slice,要是不初始化的话,各个值都是nil显然是没没法使用的。关于make与new的区别可以参考这个博客
下面是一个slice和string转化的例子,可以用来更好地了解slice与string以及底层数组的关系,不同的生成slice的方式,内部的操作是不同的。
packagemain
import(
"fmt"
"unsafe"
)
funcmain(){
//example1
varslice[]int32=make([]int32,5,10)
//p是指向slice类型的一个指针
//这里是模拟一个slice类型然后转化过来
p:=(*struct{
arrayuintptr
lenint
capint
})(unsafe.Pointer(&slice))
//paravwillprintthegotype
//Ifthevalueisastruct,
//the%+vvariantwillincludethestruct’sfieldnames
fmt.Printf("output:%+v\n",p)
//example2
//声明数组的时候没有显式指定数组的长度而是通过特殊的标记...
//告诉编译器让编译器去自动地计算
vararray=[...]int32{1,2,3,4,5}
//数组进行截取转化为slice此时自动给len和cap赋了值
varsliceb=array[2:4]
fmt.Printf("beforemodify:array=%+v,slice=%+v\n",array,sliceb)
//此时slice中用的底层数组和array是同一个
//此时slice[0]的位置指向的实际是底层数组中元素3所在的位置
sliceb[0]=678
fmt.Printf("aftermodify:array=%+v,slice=%+v\n",array,sliceb)
//example3
//注意如果使用的是append方式生成新的slice
//就不会有类似的效果因为采用append方式会分配新的底层数组
array=[...]int32{1,2,3,4,5}
varslicec=array[1:3]
slicec=append(slicec,6,7,8)
//可以对比这次结果发现两次array的输出是一样的并没有因为slice的修改而对array造成影响
fmt.Printf("beforemodify:array=%+v,slice=%+v\n",array,sliceb)
slicec[0]=123
fmt.Printf("aftermodify:array=%+v,slice=%+v\n",array,sliceb)
//注意从slice生成slice的时候原理也是类似的直接用slicea=sliceb[para1:para2]的语法
//使用的是同一个底层的数组要是通过append方式则会重新分配底层数组
}
/*输出:
output:&{array:8725963136len:5cap:10}
beforemodify:array=[12345],slice=[34]
aftermodify:array=[1267845],slice=[6784]
beforemodify:array=[12345],slice=[34]
aftermodify:array=[12345],slice=[34]
*/
map
map类型、slice类型、以及通道类型,都是引用类型,引用类型就是说这个结构总会持有一个指针,保持对底层某个结构的引用。作为对比,数组类型就是值类型。对于引用类型,在使用的时候,具体声明了之后,还要进行初始化,只有这样才能建立起和底层数据结构之间的联系。使用字典的时候,要不就直接声明+初始化,要不就声明之后使用make初始化。
//直接声明加赋值
mb:=map[int]string{1:"hello",2:"go"}
//直接声明并初始化
m:=make(map[string]int)
之后就可以直接使用了。
array
array定义的时候,通过[n]类型进行定义,可以在定义的时候就把值赋进去,比如[3]strng{"a","b","c"}还可以在[]中使用...标记,这样编译器会自动识别数组的长度,并且进行初始化。
interface以及类型转化
接口类型的实现比较复杂,整理出几条关于接口的要点,实际中,关于断言表达式的地方总是用的不太好。这个帖子关于断言说的比较好。这个文章介绍也比较好。
go语言是编译型的,有一次写了这样的代码:
containerpid=fmt.Sprintf("%d",containerpid)
之后是对containerpid进行一些字符串的操作,比如字符串的拼接,在编译的时候就会报如下的错误:
invalidoperation:"teststring"+containerpid(mismatchedtypesstringandinterface{})
主要原因是因为go并不是那种动态执行的语言,因为是要先编译好再执行,在编译器看来,这个contaierid还是没有转化成strig类型(??)除非不使用同名的变量,另外新起一个变量名字,这样编译时才能通过。
(1)普通类型向接口类型的转化是隐式的,接口类型向普通类型转换需要类型断言。由于interface{}包含了0个方法,所以任何类型都实现了interface{}接口,这就是为什么可以将任意类型值赋值给interface{}类型的变量,包括nil,下面是隐式转化的例子:
packagemain
import(
"fmt"
"reflect"
)
funcmain(){
varvalinterface{}="hello"
fmt.Println(reflect.TypeOf(val))//output:string
fmt.Println(val)
val=[]byte{'a','b','c'}
fmt.Println(reflect.TypeOf(val))//output:string
fmt.Println(val)
}
(2)在显式的类型转化中,一种方式是使用comma-ok的断言语法,由于golang中不像java哪有那样采用泛型声明的方式,因此好多时候不能确定这个类型到底实现了哪些接口,于是可以采用断言表达式,注意x.(T)这样的断言类型转化,x必须是一个接口类型的变量,T是一个对应的实际的类型,比如下面这个程序,生成一个interface{}类型的数组,之后通过隐式转化,可以往里面放入任何类型的变量,之后还可以再使用用断言表达式检测对应的类型。
packagemain
import(
"fmt"
)
typeTest[]interface{}
funcmain(){
test:=make(Test,5)
test[0]="a"
test[1]=2
test[2]=true
test[3]=[]byte("test")
forindex,element:=rangetest{
ifvalue,ok:=element.(string);ok{
fmt.Printf("test[%d]istypeofstringthevalueis%v\n",index,value)
}elseifvalue,ok:=element.([]byte);ok{
fmt.Printf("test[%d]istypeofstringthe[]byteis%v\n",index,value)
}
}
}
(3)在使用实例实现interface的时候,还要注意值方法和指针方法的区别。interface这里的使用,多多少少可以看出一些动态语言的设计风格,就是所谓的"鸭子类型",即不是通过显式的指明继承自某个接口或者类,而是由当前类型实现方法的属性集合来决定。理解上就是,感觉比静态的类型逼格高一点,总之我的interface就写在那里,你要是想继承我,使用我的方法,就把这些方法实现了就行,并不要求你显示的声明,继承自我,怎样怎样。有些动态类型的语言在运行期才能进行类型的语法检测,go语言在编译期间就可以检测。比如下面这个例子:
packagemain
import(
"fmt"
"reflect"
"sort"
)
typeSortableinterface{
Len()int
Less(i,jint)bool
Swap(i,jint)
}
typeSortStringA[3]string
typeSortStringB[3]string
func(selfSortStringA)Len()int{
returnlen(self)
}
func(selfSortStringA)Less(i,jint)bool{
returnself[i]<self[j]
}
func(selfSortStringA)Swap(i,jint){
self[i],self[j]=self[j],self[i]
}
func(selfSortStringA)Sort(){
sort.Sort(self)
}
func(self*SortStringB)Len()int{
returnlen(self)
}
func(self*SortStringB)Less(i,jint)bool{
returnself[i]<self[j]
}
func(self*SortStringB)Swap(i,jint){
self[i],self[j]=self[j],self[i]
}
func(self*SortStringB)Sort(){
//调用sort包中的Sort方法传入的参数只要是一个实现了sort.interface的类型的实例即可
sort.Sort(self)
}
funcmain(){
sa:=SortStringA{"2","3","1"}
sb1:=&SortStringB{"2","3","1"}
sb2:=SortStringB{"2","3","1"}
fmt.Println(reflect.TypeOf(sa))
sorta,ok:=interface{}(sa).(Sortable)
fmt.Println(reflect.TypeOf(sorta))
fmt.Println(ok)//output:true
sa.Sort()
fmt.Printf("SortStringAaftersort%v:\n",sa)
sort.Sort(sa)
fmt.Printf("SortStringAaftersort%v:\n",sa)
//在golang的源码包中
fmt.Println(reflect.TypeOf(sb1))
sort.Sort(sb1)
sorted:=sort.IsSorted(sb1)
fmt.Printf("sb1(type:SortStringB)aftersort%v:,issorted:%v\n",*sb1,sorted)
sb2.Sort()
fmt.Printf("sb2(type:SortStringB)aftersort:%v\n",sb2)
}
/*output:
main.SortStringA
main.SortStringA
true
SortStringAaftersort[231]:
SortStringAaftersort[231]:
*main.SortStringB
sb1(type:SortStringB)aftersort[123]:,issorted:true
sb2(type:SortStringB)aftersort:[123]
感觉这一段代码还是能够说明一些问题的:
-
首先定义了一个sortstable的接口这个接口中有三个方法,这三个方法同sort包中的基本类型sort.interface中定义的三个方法是一样的,之后分别用值方法与指针方法对这三个方法进行了实现。
-
可以看到,采用值方法时,因为方法调用的时候,采用的是指传递,原来的变量中sa的数据并没有排序。同时还测试了一下断言表达式,通过comma-ok的表达式,sa类型可以转化为sortable类型,这里体现了其动态性,但是采用反射输出的时候,还显示的是其本身声明时候的类型main.SortStringA,这里又体现了其强静态类型(类型一但确定就不再改变),两者结合起来使Go语言在保持强静态类型的安全和高效的同时,也能灵活安全地在不同相容类型之间转换。(这个文章讲一些interface原理性的内容,比较好)注意里面的典型的interface+switch+断言的使用方式。
-
由于sortable中的三个方法和sort.interface中定义的三个方法一样,因此sasb相当于都实现了sort.interface方法,可以直接调用sort包中的函数。但是要注意,具体实现的时候,前一个是SortStringA本身实现了三个方法,后一个是SortStringB指针实现了三个方法,因此SortStringB指针类型实现了sort.Interface调用的时候传递过去的要是指针才行,否则编译报错。比如直接sort.Sort(sb2)会报错
SortStringBdoesnotimplementsort.Interface(Lenmethodhaspointerreceiver)
参考资源
http://www.infoq.com/cn/articles/go-interface-talk
本文内容总结:
原文链接:https://www.cnblogs.com/Goden/p/4593066.html