go基础之数据类型

Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。

基础类型,包括:数字字符串布尔型。复合数据类型:数组结构体,是 通过组合简单类型,来表达更加复杂的数据结构。引用类型包括指针切片字典函数通道

1. 基本数据类型

Go语言的数值类型包括几种不同大小的整形数、浮点数和复数。每种数值类型都决定了对应 的大小范围和是否支持正负符号。

1.1 整型

Go语言同时提供了有符号和无符号类型的整数运算。

有符号:int8、int16、int32和int64

无符号:uint8、uint16、uint32和uint64

还有,对应特定CPU平台机器字大小的有符号和无符号整数int和uint,都是同样的大小。

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

最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。

Go语言的取模运算符%只适用在整型。,%取模运算 符的符号和被取模数的符号总是一致的,因此 -5%3 和 -5%-3 结果都是-2。

1.2 浮点数

Go语言提供了两种精度的浮点数,float32和float64。

常量math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应math.MaxFloat64常量大约是1.8e308。

一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进 制数的精度;通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散, 并且float32能精确表示的正整数并不是很大(译注:因为float32的有效bit位只有23个,其它 的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现误差):

var f float32 = 1 << 24
fmt.Println(f == f+1) // "true"

1.3 复数

Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两 种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实 部和虚部:

1.4 布尔型

布尔值并不会隐式转换为数字值0或1

1.5 字符串

一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通 常是用来包含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode码点 (rune)序列。

字符串是不可变的,不可修改内部数据。

切片操作也是共享源字符串的底层数据。

内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目),索引操作s[i]返回第i 个字节的字节值,i必须满足0 ≤ i< len(s)条件约束。

s := "hello world"
fmt.Println(len(s)) // "11"
fmt.Println(s[0]) // "104"

第i个字节并不一定是字符串的第i个字符,因为对于非ASCII字符的UTF8编码会要两个或多个 字节。

字符串的比较

字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然 编码的顺序。

Unicode

用Unicode( http://unicode.org ),它收集了这个世界上所有的符号系统,包括 重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯 一的Unicode码点,Unicode码点对应Go语言中的rune整数类型

1.5.1 字符串和数字的转换

整数装字符串
// 1 用fmt.Sprintf返回一个格式化的字符串
x := 123
y = fmt.Sprintf("%d", x)

// 2 使用strconv.Itoa("整数到ASCII")
x := 123
y := strconv.Itoa(x)

1.6 常量

常量表达式的值在编译期计算,常量的值不可更改。常量可以批量声明。

const (
	e = 2.71828182845904523536028747135266249775724709369995957496696763
	pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

1.6.1 iota常量生成器

常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不 用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行, iota将会被置为0,然后在每一个有常量声明的行加一

type Weekday int

const (
	Sunday Weekday = iota // 0
    Monday // 1
    Tuesday //2
    Wednesday //3
    Thursday
    Friday
    Saturday //6
)

也可以在复杂的常量表达式中使用iota

type Flags int

const (
	FlagUp Flags = 1 << iota
    FlagBroadcast
    FlagLoopback
    FlagPointToPoint
    FlagMylticast
)

随着iota的递增,每个常量对应表达式1 << iota,是连续的2的幂,分别对应一个bit位置。

2. 复合数据类型

复合数据类型有:数组、slice、map和结构体

slice和map属于指针

数组和结构体是聚合类型;他们由许多元素或成员字段组成。

数组和结构体都是有固定大小的数据结构。slice和map是动态的数据街头,它们将根据需要动态增长。

2.1 数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组中的每个元素可以通过索引下标来访问。内置函数len()可以返回数组中元素的个数。

var a[3] int
fmt.Println(a[0]) //0
fmt.Println(a[len(a)-1]) //0

默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型就是0。也可以用数组字面值语法用一组值来初始化数组。

var q[3]int = [3]int{1,2,3} // 1,2,3
var p[3]int = [3]int{1,2}	// 1,2,0

在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始 化值的个数来计算。

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长 度必须是常量表达式,因为数组的长度需要在编译阶段确定。

q := [3]int{1,2,3}
q = [4]int{1,2,3,4} //这是错误的,因为数组的长度是数组类型的一个组成部分
r := [...]int{99: -1} //定义了一个含有100个元素的数组r,最后一个初始化为-1,其他元素初始化为0

如果一个数组的元素类型是可以相互比较的,那么数组类型也是可以相互比较的,这时候我们可以直接通过==比较运算符来比较两个数组,只有当两个数组的所有元素都是相等的时候数组才是相等的。

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 会编译失败,数组的元素类型不同则不能进行比较

2.2 Slice

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作 []T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。

一个slice由三个部分 构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的 是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不 能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分 别返回slice的长度和容量。

slice不能用比较运算==

唯一合法的比较操作就是和nil比较

s := []int{} //声明一个nil的切片, 没有底层的数组
if s == nil{
    fmt.Println("s是nil切片")
}

创建切片

// month := []int{1,2,3}

// 使用make函数
month = make([]T,len, cap) //cap可以省略, 创建一个类型为T,长度为len,容量为cap的切片,在底层,make创建了一个匿名的数组变量,然后返回一个slice

2.2.1 切片操作

2.2.2 append函数

用于向slice追加元素

var r []rune
for _, item := range "hello, world"{
    r = append(r, item)
}
fmt.Printf("%q\n", r)  // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

可以追加多个元素

var l []int
l = append(l, 1,2,3)
fmt.Printf("%d", l) "[1,2,3]"

更新slice变量不仅对调用append函数是必要的,实际上对应任何可能导致长度、容量或底层 数组变化的操作都是必要的。要正确地使用slice,需要记住尽管底层数组的元素是间接访问 的,但是slice对应结构体本身的指针、长度和容量部分是直接访问的。要更新这些信息需要 像上面例子那样一个显式的赋值操作。

3. map

在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别 对应key和value。

map的零值为nil。既没有键,也不能添加键。

3.1 创建map

// 1 使用make函数创建一个map
age := make(map[string]int)  // 等价于 age := map[string]int{} 创建一个空的map

// 2 使用map字面值语法
ages :=  map[string]int{
    "alice": 31,
    "charlie":  34,
}
相当于
ages := make(map[string]int )
ages["alice"] = 31
ages["charlie"] = 34

// nil map
demo := map[string]int

删除

// 使用delete的内置函数
delete(ages, "alice")

查找

直接访问map中的该元素,将其赋值给两个变量,第二个变量就是元素是否存在的修饰变量。

value, exist := ages["tt"]
if !exist {
    fmt.Println("key tt 不存在")
}

如果一个查找失败将返回 value类型对应的零值。

ages["bob"] = ages["bob"] + 1 // ages["bob"] 等于2

是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:

ida := &ages["bob"] // 编译出错

禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而 可能导致之前的地址无效。

遍历map

map中的迭代顺序是不确定的,可以借助range实现。

for key, value := range ages {
    fmt.Println("%s\t%d\n", key, value)
}

map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它 们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常.

4. 结构体

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结 构体的成员。

4.1 结构体比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体 将可以使用==或!=运算符进行比较。

type Point struct{ X, Y int }
p := Point{1, 2}
q := Point{2, 1}
fmt.Println(p.X == q.X && p.Y == q.Y) // "false"
fmt.Println(p == q) // "false"

4.2 结构体嵌入

type Point struct {
    X, Y int
}

type Cirle struct {
    X, Y, Radius int
}

利用结构体嵌入我们可以这样定义Cirle

type Cirle struct{
    Center Point
    Radius int
}

这样改动后结构体类型变得清晰,但是会导致访问成员变得繁琐:

var c Circle
c.Point.X = 1
c.Point.Y = 2
C.Radius = 3

Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就 叫匿名成员。

4.3 匿名成员

匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针。

type Circle struct {
    Point 
    Radius int
}

通过匿名嵌入的特性,我们可以直接访问Point的成员。

var c Circle
c.X = 1  // 等价于c.Point.X = 1
c.Y = 2
c.Radius = 3

但是,结构体字面值没有表示匿名成员的语法

var c Circle
c = Circle{1,2,3} //编译出错

因为匿名成员也有一个隐式的名字,因此不能同时包含两个类型相同的匿名成员,这会导致 名字冲突。

posted @ 2020-11-26 12:36  You__You  阅读(198)  评论(0编辑  收藏  举报