go 学习 ---数据类型

25个关键字

  程序声明:import, package

  程序实体声明和定义:chan, const, func, interface, map, struct, type, var

  程序流程控制:go, select, break, case, continue, default, defer, else, fallthrough, for, goto, if, range, return

 

类型

  18个基本类型:bool, string, rune, byte, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, complex64, complex128

  7个复合类型:array, struct, function, interface, slice, map, channel

  其中,切片、字典、通道类型都是引用类型

  类型的声明一般以 type 关键字开始,然后是自定义的标识符名称,然后是基本类型的名称或复合类型的定义。

  Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样byte也是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。

  最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。 

  一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大

    rune 官方的解释如下:rune  is an alias for int32 and is equivalent to int32 in all ways. It is  used, by convention, to distinguish character values from integer values. (rune 是int32的别名,几乎在所有方面等同于int32  它用来区分字符值和整数值)

 type rune = int32

我们通过一个简单的例子来看下rune的作用。先来看下下面这块代码执行结果是什么?

package main

import "fmt"

func main() {

    var str = "hello 你好"
    fmt.Println("len(str):", len(str))

}

  我们猜测结果应该是:8:5个字符1个空格2个汉字。那么正确答案是多少呢?

len(str): 12

  结果居然是12,这是为什么呢!?

golang中string底层是通过byte数组实现的。中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8。

那么?如果我们预期想得到一个字符串的长度,而不是字符串底层占得字节长度,该怎么办呢???

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {

    var str = "hello 你好"

    //golang中string底层是通过byte数组实现的,直接求len 实际是在按字节长度计算  所以一个汉字占3个字节算了3个长度
    fmt.Println("len(str):", len(str))
    
    //以下两种都可以得到str的字符串长度
    
    //golang中的unicode/utf8包提供了用utf-8获取长度的方法
    fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))

    //通过rune类型处理unicode字符
    fmt.Println("rune:", len([]rune(str)))
}

 运行结果:

len(str): 12
RuneCountInString: 8
rune: 8

 

golang中还有一个byte数据类型与rune相似,它们都是用来表示字符类型的变量类型。它们的不同在于:

  • byte 等同于int8,常用来处理ascii字符
  • rune 等同于int32,常用来处理unicode或utf-8字符

 

操作符

  列举一些特殊的操作符,注意下面的位操作符

&      位运算 AND
|      位运算 OR
^      位运算 XOR
&^     位清空 (AND NOT)
<<     左移
>>     右移

  可以通过 Printf 函数的 %b 参数来输出二进制格式的数字。

 

特殊的空标识符

  下划线 _ 作为一个特殊的标识符,可以用于 import 语句中,仅执行导入包中的 init 方法。也可以作为赋值语句的左边,表示该变量并不关心且不使用。

  此外,标识符首字母的大小写,在GO语言中被用来控制变量或函数的访问权限,类似于其它语言的 public\private。

 

类型断言

  比较特殊的表达式有类型断言,如果判断一个表达式 element 的类型是 T 的话,表达式为 element.(T),意思是 element 不为 nil 且存储在其中的值是T类型。这里有两种情况,如果 T 不是一个接口类型,则 element 必须要为接口类型的值,比如判断 100 是 int 型,不能使用 100.(int),而要用 interface{}(100).(int) ; 如果T 是一个接口类型,表示断言 element 实现了 T 这个接口。如果函数的参数是一个空接口,则必须断言传入的接口实现类型,才能使用其对应的方法。

 

可变参函数

  最后一个参数为 ...T 的形式的函数即为可变参函数,意味着可变参数都是 T 类型(或实现了T的类型)如:func CallFunction(first string, t ...string),GO语言会在调用可变参函数时,创建一个切片,然后将这些可变参数放入切片中,但如果传入的可变参部分就是一个元素类型为T的切片的话,则直接把传入切片赋值给创建的切片,且在调用写法上也有区别,为: CallFunction("hello", []string{"x","y"}...) 

 

数组类型

  array: 声明一个长度为 n 、元素类型为 T 的数组为: [n]T, 元素类型可以为基本类型也可以为复合类型,也可以不指定 n ,由推导得出,如: [...]string{"a","b"} , 数组长度 n = len([...]string{"a","b"}),另外如果指定了数组长度,但定义的数组长度小于声明的长度,则以声明长度为准,不足的元素补默认值。同一元素类型,但数组长度不同,则视为不同类型。

 

切片类型

  slice: 切片类型的声明为 []T数组,切片类型里没有关于长度的规定,其它跟数组一样,切片类型的零值是 nil。切片总是对应于一个数组,其对应的数组称为底级数组。切片和其底层数组的关系是引用关系,如果有修改都会影响到对方。

  切片的数据结构包含了指向其底层数组的指针、切片长度和切片容量。切片的长度很容易理解,切片的容量是什么呢,它是切片第一个元素到底层数组最后一个元素的长度。

 

字典类型

  map: 定义一个哈希表的格式为 map[K]V,其中 K 表示键的类型,V表示值的类型,如: map[string]bool{"IsOK":true, "IsError":false}

 

接口类型

  定义了一组方法声明,接口中可以包含接口

  GO语言对接口类型的实现是非侵入式的(备注:侵入式是指用户代码与框架代码有依赖,而非侵入式则没有依赖,或者说耦合),只要一个类型定义了某个接口中声明的所有方法,就认为它实现了该接口。

  一个特殊的接口: interface{} 是一个空接口,不包含任何方法声明,所以GO语言所有的类型都可以看成是它的实现类型,我们就可以使用它实现类似其它语言中的公共基类的功能。比如声明一个字典,键是字符串,值是不确定类型,就可以使用 map[string]interface{} 

  判断一个类型是否实现了一个接口,可以通过类型断言来确定: _, ok := interface{}(MyType{}).(MyInterface)

 

函数与方法

  GO语言中,函数跟方法是有区别的,函数就是我们通常理解的函数,而方法是附属于某个自定义数据类型的函数,即存在某个接收者。

  func (self MyType) Len() int {}     这里的 (self MyInterface) 是表示方法接收者。

  值方法和指针方法,值方法是指接收者是一个对象,而指针方法是指接收者是一个对象指针。两者的区别是,值方法中对接收者的改变是副本级别的,而指针方法中对接收者的改变是地址级别的。所以其实一般都推荐使用指针方法,因为大多数情况下我们在方法内部修改接收者,都是为了真实的改变它,而不是改变一个副本。但是,对于引用类型的接收者来说,两者并无区别。

  匿名函数由函数字面量表示,函数是作为值存在的,可以赋给函数类型的变量。如:

    var myfunc func(int, int) int
    myfunc = func(x, y int) (result int) {
        result = x + y
        return
    }
    log.Println(myfunc(3, 4))

  一个方法的类型是一个函数类型,即把接收者放到函数的第一个参数位置即可。

  非常遗憾,GO语言不支持函数和方法重载。

 

结构体

  可以包含字段,也可以包含匿名字段,一般匿名字段是接口类型,这样就相当于该结构体包含了该接口中的方法,另外也可以在结构里重写隐藏接口中的方法。

  

指针

  有一个专门用于存储内存地址的类型 unitptr,它与 int/unit 一样属于数值类型,存储32/64位无符号整数。

  可以在一个类型前面使用*号,来表示对应的指针类型,也可以在可寻址的变量前面使用&,来获取其地址。

 

常量

  定义常量和多个常量如下:

复制代码
    const PP = iota        //0
    const QQ = iota          //0

    const (
        A = 1
        B = 2
        C
        D = iota
        E
        F 
    )
    log.Print(A, B, C, D, E, F)
//输出是: 1 2 2 3 4 5
复制代码

  注意,iota 只能在 const 里使用,它是 const 的行数索引器,其值是所在const组中的索引值,单常量定义时 iota 就等于 0。另外,const组中某一项如果不赋值,则默认和上一个值一样,但如果前面是 iota ,则为上一个值+1。使用 iota 可以实现其它语言中的枚举。

 

变量

  变量的声明语句以 var 开始,声明多个变量时,和声明多个const的方法相同。

var x string = "df"
var x = "df"
x := "df"        //此为简写形式。

 

数据初始化

  GO语言的数据初始化有两种方法,一是使用 new ,一是使用 make ,其中 new 是为某个类型分配内存,并设置零值后返回地址,如 new(T) 返回的就是指向T类型值指针值,即*T。如 new([3]int) 其实相当于 [3]int{0,0,0},所以它是一种很干净的内存分配策略。

  make 只用于创建切片类型、字典类型和通道类型(注意这三个类型的特点,都是引用类型),它对这些类型创建之后,还会进行必要的初始化,与 new 不同,它返回的就是指T类型的值,而不是指针值。

定义常量的方式是使用 const ,如 const PI = 3.14,如果定义多个常量可以使用 

编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定的。 如果一个函数里声明一个局部变量,但是将其指针赋给一个全局变量,那么则不能将此局部变量放在栈中,而只能放在堆中,我们可以称之为该局部变量逃逸了,所以关于变量是分配在栈上还是堆上,是由编译器根据情况来选择的。

 

内建函数

  close 只接受通道类型的值

  len函数,可以应用于字符串、切片、数组、字典、通道类型

  cap函数,可以应用于切片、数组、通道类型

  new函数和make函数

  append函数和copy函数,应用于切片

  delete函数,根据字典的键删除某一项

  complex\real\imag函数,复数相关

  panic 函数,异常相关,它的参数一般是某个error接口的某个实现类型;recover 函数,不接受任何参数,返回 interface{} 类型,也就是意味着,它可以返回任意类型。recover返回的内容是与panic相关的,就是panic的参数内容。

  print\println 函数,这两个函数支持基本类型参数,且是可变参数。但输出格式是固定的,且GO语言不保证以后会保留这两个函数,所以知道就好,不推荐使用。可以使用 fmt.Print 和 fmt.Println 来代替,效果更佳。

 

合并书写:

和 var/const 类似,多个 type 定义可合并成组,如下:

复制代码
type (
    Person struct {
        Name string
        Age  int32
    }   

    myfunc func(string) bool
)
复制代码

 尤其是在函数内部定义一堆临时类型时,可集中书写,可读性更好。

 

 

自增/自减运算符不同于其它语言,不能用作表达式,只能当独立的语句使用,且不能前置,只有后置形式,以尽可能避免该运算符带的复杂性问题。

 

 

unsafe.Pointer 与 uintptr 的区别:

前者类似 C 语言中的 void* 万能指针,能安全持有对象,但后者不行,后者只是一种特殊整型,并不引用目标对象,无法阻止垃圾回收器回收对象内存。

 

posted on 2018-11-04 20:53  清明-心若淡定  阅读(291)  评论(0编辑  收藏  举报