Go语言内置如下这些基础类型:golang
此外,Go语言也支持如下这些复合类型:数组
golang中,nil只能赋值给指针、channel、func、interface、map或slice类型的变量.缓存
Go 也有基于架构的类型,例如:int、uint 和 uintptr。这些类型的长度都是根据运行程序所在的操做系统类型所决定的:安全
Go 语言中没有 float 类型。数据结构
与操做系统架构无关的类型都有固定的大小,并在类型的名称中就能够看出来:架构
整数:并发
-128 -> 127
)-32768 -> 32767
)-2,147,483,648 -> 2,147,483,647
)-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807
)无符号整数:app
0 -> 255
)0 -> 65,535
)0 -> 4,294,967,295
)0 -> 18,446,744,073,709,551,615
)浮点型(IEEE-754 标准):框架
+- 1e-45 -> +- 3.4 * 1e38
)+- 5 * 1e-324 -> 107 * 1e308
)int 型是计算最快的一种类型。函数
float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。因为精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当很是当心.
你应该尽量地使用 float64,由于 math 包中全部有关数学运算的函数都会要求接收这个类型。
各种型的最大值最小值在math
包中定义。
类型声明可在builtin
包中查看。
true、false
var v1 bool v1 = true v2 := (1 == 2) // v2也会被推导为bool类型
布尔类型不能接受其余类型的赋值,不支持自动或强制的类型转换。如下的示例是一些错误的用法,会致使编译错误:
var b bool b = 1 // 编译错误 b = bool(1) // 编译错误
如下的用法才是正确的:
var b bool b = (1!=0) // 编译正确 fmt.Println("Result:", b) // 打印结果为Result: true
【注意1】int和int32在Go语言里被认为是两种不一样的类型,编译器也不会帮你自动作类型转换,好比如下的例子会有编译错误:
var value2 int32 value1 := 64 // value1将会被自动推导为int类型 value2 = value1 // 编译错误
编译错误相似于:
cannot use value1 (type int) as type int32 in assignment。
使用强制类型转换能够解决这个编译错误:
value2 = int32(value1) // 编译经过
固然,开发者在作强制类型转换时,须要注意数据长度被截短而发生的数据精度损失(好比将浮点数强制转为整数)和值溢出(值超过转换的目标类型的值范围时)问题
【注意2】两个不一样类型的整型数不能直接比较,好比int8类型的数和int类型的数不能直接比较,但各类类型的整型变量均可以直接与字面常量(literal)进行比较,好比:
var i int32 var j int64 i, j = 1, 2 if i == j { // 编译错误 fmt.Println("i and j are equal.") } if i == 1 || j == 2 { // 编译经过 fmt.Println("i and j are equal.") }
进制
fmt.Println(0x10) // 十六进制 16 fmt.Println(010) // 八进制 8
e
2e3表示2*10^3=2000
前70个已经被缓存起来,可在math.Pow()中查看。
2e3或者2.0e3默认都是float64类型:
var i = 2e3 reflect.TypeOf(i) == float64 var i int = 2e3 reflect.TypeOf(i) == int
int64 和 uint64 的最大位赋值
var big int64 = 1 << 63 fmt.Printf("%b\n", big)
报错:constant 9223372036854775808 overflows int64
var big uint64 = 1 << 63 fmt.Printf("%b\n", big)
输出: 1000000000000000000000000000000000000000000000000000000000000000
这多是由于 int64 中符号位也要占一个 bit。
int占多少位二进制
在 atoi.go 文件中:
const intSize = 32 << (^uint(0) >> 63)
x << y
左移x >> y
右移x ^ y
异或x & y
与x | y
或^x
取反位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。
%b
是用于表示位的格式化标识符。
【&^】
位清除 &^:将指定位置上的值设置为 0
n1, n2 := 7, 3 fmt.Printf("%b &^ %b = %b\n", n1, n2, n1&^n2) // 111 &^ 11 = 100 末尾1,2位置零 n1, n2 := 7, 2 fmt.Printf("%b &^ %b = %b\n", n1, n2, n1&^n2) // 111 &^ 10 = 101 末尾2位置零
【^x】
单独使用取反,结合使用异或。
该运算符与异或运算符一同使用,即 m^x,对于无符号 x 使用“所有位设置为 1”,对于有符号 x 时使用 m=-1.
var n uint8 = 2 fmt.Printf("^%b = %b\n", n, ^n) // ^10 = 11111101 即 253 var nn uint8 = 0xFF fmt.Printf("%b ^ %b = %b\n", nn, n, nn^n) // 11111111 ^ 10 = 11111101 var n2 int8 = 2 fmt.Printf("^%b = %b\n", n2, ^n2) // ^10 = -11 即 -3 var nn2 int8 = -1 fmt.Printf("%b ^ %b = %b\n", nn2, n2, nn2^n2) // -1 ^ 10 = -11
对于无符号整型,取反至关于与 0xFF 异或
对于有符号整型,取反至关于与 -1 异或。
上面的操做都是以补码的形式进行的,正数的补码仍是自身。
取反至关于补码和 11111111 异或,而后求补码。
【<<】
3<<2 => 11 << 2 = 1100 -3<<1 => -11 << 1 = -6(不求补码好像也能够)
【>>】
var n1 uint8 = 7 var c uint8 = 2 fmt.Printf("%b >> %d = %b\n", n1, c, n1>>c) // 111 >> 2 = 1 var n2 int8 = 7 fmt.Printf("%b >> %d = %b\n", n2, c, n2>>c) // 111 >> 2 = 1 var n3 int8 = -7 fmt.Printf("%b >> %d = %b\n", n3, c, n3>>c) // -111 >> 2 = -2
对于正数,右移时左边补0.
对于负数,以-7(-0111)为例,求补码(1 1001,第一位为1表示负数,其他位取反+1,或者从右开始遇到第一个1,这个1以前的取反),将补码右移2位,左边补1,变成1 1110,再求补码变成1 0010,即-2。
另外须要注意的是:-1 >> 1 == -1
, 避免陷入死循环。
【位左移常见实现存储单位的用例】
使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
type ByteSize float64 const ( _ = iota // 经过赋值给空白标识符来忽略值 KB ByteSize = 1<<(10*iota) MB GB TB PB EB ZB YB )
【在通信中使用位左移表示标识的用例】
type BitFlag int const ( Active BitFlag = 1 << iota // 1 << 0 == 1 Send // 1 << 1 == 2 Receive // 1 << 2 == 4 ) flag := Active | Send // == 3
标志位操做
a := 0 a |= 1 << 2 // 0000100: 在bit2设置标志位 a |= 1 << 6 // 1000100: 在bit6设置标志位 a = a &^ (1 << 6) // 0000100: 清除bit6标志位
移位操做右边的数不能是有符号数
bit := 2 a := 3 << bit fmt.Printf("%b\n", a)
报错:invalid operation: 3 << bit (shift count type int, must be unsigned integer)
这样使用:
var bit uint = 2 a := 3 << bit fmt.Printf("%b\n", a) // 1100
Go语言定义了两个类型float32和float64,其中float32等价于C语言的float类型,float64等价于C语言的double类型
var fvalue1 float32 fvalue1 = 12 fvalue2 := 12.0 // 若是不加小数点,fvalue2会被推导为整型而不是浮点型
注意!fvalue2默认被推导为float64
var val float32 val = 3.0 val2 := 12.4 // val2被自动推导为float64 val = val2 // 错误,不能将float64赋给float32 val = float32(val2) // 使用强制转换
浮点数不是一种精确的表达方式,因此像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会致使不稳定的结果。
下面是一种推荐的替代方案:
import "math" // p为用户自定义的比较精度,好比0.00001 func IsEqual(f1, f2, p float64) bool{ return math.Fdim(f1, f2) < p }
一般应该优先使用float64类型,由于float32类型的累计计算偏差很容易扩散,而且float32能精确表示的正整数并非很大(译注:由于float32的有效bit位只有23个,其它的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现偏差)
var f float32 = 16777216 // 1 << 24 fmt.Println(f == f+1) // "true"!
小数点前面或后面的数字均可能被省略(例如.707或1.)。 很小或很大的数最好用科学计数法书写,经过e或E来指定指数部分:
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数 const Planck = 6.62606957e-34 // 普朗克常数
for _, n := range []float64{1.4, 1.5, 1.6, 2.0} { fmt.Println(n, "=>", int(n+0.5)) } // 1 // 2 // 2 // 2
复数类型有complex64和complex128
var value1 complex64 // 由2个float32构成的复数类型 value1 = 3.2 + 12i value2 := 3.2 + 12i // value2是complex128类型 value3 := complex(3.2, 12) // value3结果同value2 var value4 complex128 = 3.2 + 12i
real(value1), imag(value1)
复数的一些运算可在math/cmplx包中找到。
使用==或!=进行比较时注意精度。
var str string // 声明一个字符串变量 str = "Hello world" // 字符串赋值 ch := str[0] // 取字符串的第一个字符 fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)
输出结果为:
The length of "Hello world" is 11 The first character of "Hello world" is H.
【字符串嵌套】
var s = "ni 'hao'. " var s = `ni "hao" .`
【字符串修改】
字符串初始化后不能被改变
str := "Hello world" // 字符串也支持声明时进行初始化的作法 str[0] = 'X' // 编译错误
能够先将其转换成[]rune或[]byte,完成后再转换成string,不管哪一种方式,都会从新分配内存,并复制字节数组。
s := "abc" bs := []byte(s) bs[1] = 'B' println(s, string(bs)) u := "电脑" us := []rune(u) us[1] = '话' println(u, string(us))
输出:
abc aBc 电脑 电话
【字符串链接】
s := "hello" + " world" s := "hello" + " world " + 3 // 错误 s := "hello" + " world " + string(31) // 正确,但不会输出31
+的并非最高效的作法,使用strings.Join()和bytes.Buffer更好些。
【字符串遍历】
方式一:
str := "hello,世界" for i, n := 0, len(str); i < n; i++ { var ch2 uint8 = str[i] // 类型是byte,即uint8 fmt.Printf("%d = '%c' , ", i, ch2) }
输出:
0 = 'h' , 1 = 'e' , 2 = 'l' , 3 = 'l' , 4 = 'o' , 5 = ',' , 6 = 'ä' , 7 = '¸' , 8 = '' , 9 = 'ç' , 10 = '' , 11 = '' ,
长度为12,每一个中文字符在UTF-8中占3个字节。
方式二:
str := "hello,世界" for i, ch := range str { fmt.Print(i, "= ") fmt.Print(ch, " ,") // ch的类型是rune fmt.Printf("'%c' .", ch) }
输出:
0= 104 ,'h' .1= 101 ,'e' .2= 108 ,'l' .3= 108 ,'l' .4= 111 ,'o' .5= 44 ,',' .6= 19990 ,'世' .9= 30028 ,'界' .
【中文字符截取】
strEn := "abcdef" strCn := "中文测试" fmt.Println(strEn[0:3]) // abc fmt.Println(strCn[0:3]) // 中 fmt.Println(string([]rune(strCn)[0:3])) // 中文测 func SubString(str string, begin, length int) (substr string) { // 将字符串的转换成[]rune rs := []rune(str) lth := len(rs) // 简单的越界判断 if begin < 0 { begin = 0 } if begin >= lth { begin = lth } end := begin + length if end > lth { end = lth } // 返回子串 return string(rs[begin:end]) } fmt.Println(SubString("中文测试", 1, 3)) // 文测试 fmt.Println(SubString("abcd", 1, 3)) // bcd
【中文字符串定位】
// 思路:首先经过strings库中的Index函数得到子串的字节位置,再经过这个位置得到子串以前的字节数组pre, // 再将pre转换成[]rune,得到[]rune的长度,即是子串以前字符串的长度,也就是子串在字符串中的字符位置 // 这里用的是string.Index函数,相似的,也能够写中文字符串的相似strings中的IndexAny,LastIndex等函数 func UnicodeIndex(str, substr string) int { // 子串在字符串的字节位置 result := strings.Index(str, substr) if result >= 0 { // 得到子串以前的字符串并转换成[]byte prefix := []byte(str)[0:result] // 将子串以前的字符串转换成[]rune rs := []rune(string(prefix)) // 得到子串以前的字符串的长度,即是子串在字符串的字符位置 result = len(rs) } return result } fmt.Println(UnicodeIndex("中文测试", "文")) // 1 fmt.Println(UnicodeIndex("abcd", "c")) // 2
【使用"`"定义不作转义处理的原始字符串,支持跨行】
s := `a b\r\n\x00 c` println(s)
输出
a b\r\n\x00 c
注意上面第二行是顶行写的,否则缩进也是会如实反映的。
链接跨行字符串时,"+" 必须在上一行末尾,不然致使编译错误
s := "hello, " + "world!" s2 := "hello, " +"World." // invalid operation: + untyped string
【字符串和其余类型的转换】
strconv包。
runtime.h
struct String { byte* str; intgo len; };
中文字符串长度
unicode/utf8
包中有一个RuneCountInString()
var buffer bytes.Buffer
或者 new 出一个指针:var r *bytes.Buffer = new(bytes.Buffer)
或者经过函数:func NewBuffer(buf []byte) *Buffer
,这就建立了一个 Buffer 对象而且用 buf 初始化好了;NewBuffer 最好用在从 buf 读取的时候使用。var buffer bytes.Buffer for { if s, ok := getNextString(); ok { //method getNextString() not shown here buffer.WriteString(s) } else { break } } fmt.Print(buffer.String(), "\n")
这种实现方式比使用 += 要更节省内存和 CPU,尤为是要串联的字符串数目特别多的时候。
byte和rune
byte(其实是uint8的别名),表明UTF-8字符串的单个字节的值
rune(int32),表明单个Unicode字符
var abc byte abc = 'a' fmt.Print(abc) // 97
若是abc = ‘你’,会提示超出byte的范围
var abc rune abc = '你' fmt.Print(abc) // 20320
字符数组
string => []byte
[]byte("hello")
[]byte => string
string([]byte)
可以使用的库:bytes:
bytes.NewBuffer()
rune可作变量名
rune := rune('a') fmt.Println(rune) // 97
十六进制
var ch byte = 65 或 var ch byte = '\x41'
(\x 老是紧跟着长度为 2 的 16 进制数)
另一种可能的写法是 \ 后面紧跟着长度为 3 的十进制数,例如:\377
通常使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,而且是 int32 的别名。
在书写 Unicode 字符时,须要在 16 进制数以前加上前缀 \u 或者 \U。
由于 Unicode 至少占用 2 个字节,因此咱们使用 int16 或者 int 类型来表示。若是须要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则老是紧跟着长度为 4 的 16 进制数,前缀 \U 紧跟着长度为 8 的 16 进制数。
var ch int = '\u0041' var ch2 int = '\u03B2' var ch3 int = '\U00101234' fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
输出:
65 - 946 - 1053236 A - β - r 41 - 3B2 - 101234 U+0041 - U+03B2 - U+101234
一些常见的函数见unicode包
[32]byte // 长度为32的数组,每一个元素为一个字节 [2*N] struct{ x, y int32} // 复杂类型数组 [1000]*float64 // 指针数组 [3][5]int // 二维数组 [2][2][2]float64 // 等同于[2]([2]([2]float64))
数组长度在定义后就不可更改,在声明时长度能够为一个常量或者一个常量表达式(常量表达式是指在编译期便可计算结果的表达式)。
【数组长度】
var arr [10]byte len(arr) // 10 var arr [10][23]int fmt.Println(len(arr)) // 10
【初始化】
arr := [5]int{1, 2, 3, 4, 5} fmt.Println(arr[4]) // 5
b := [10]int{1, 2, 3}
声明了一个长度为10的int数组,其中前三个元素初始化为一、二、3,其它默认为0
若是不想写[5],也可使用[...]代替:
arr := [...]int{1, 2, 3, 4, 5}
也能够省略,什么都不写(这是数组仍是切片?这是切片)
arr := []int{1, 2, 3}
指定索引来初始化
var names = []string{ 1: "a", 2: "b", 4: "d", }
names[0]和names[3]都是""
数组指针
arr1 := new([3]int) fmt.Printf("arr1 type:%T\n", arr1) // arr1 type:*[3]int arr2 := [3]int{} fmt.Printf("arr2 type:%T\n", arr2) // arr2 type:[3]int fmt.Println(arr1, arr2) // &[0 0 0] [0 0 0] arr1[1] = 4 (*arr1)[2] = 1 arr2[1] = 5 fmt.Println(arr1, arr2) // &[0 4 1] [0 5 0]
【遍历】
方式一:
arr := [5]int{1, 3, 5, 7, 9} for i, n := 0, len(arr); i < n; i++ { fmt.Println(i, "=>", arr[i]) }
方式二:
arr := [5]int{1, 3, 5, 7, 9} for i, v := range arr { fmt.Println(i, "=>", v) }
方式三:(range匿名变量)
for i, v := range [5]int{1, 2, 3, 4} { fmt.Println(i, "=>", v) }
【数组是值类型】
数组是一个值类型(value type)。全部的值类型变量在赋值和做为参数传递时都将产生一次复制动做。若是将数组做为函数的参数类型,则在函数调用时该参数将发生数据复制。所以,在函数体中没法修改传入的数组的内容,由于函数内操做的只是所传入数组的一个副本。
func main() { arr := [5]int{1, 2, 3, 4, 5} modify(arr) fmt.Println("in main() arr is: ", arr) } func modify(arr [5]int) { arr[0] = 10 fmt.Println("in modify() arr is: ", arr) }
输出结果:
in modify() arr is: [10 2 3 4 5] in main() arr is: [1 2 3 4 5]
固然,也能够给函数传递一个数组的指针:
func sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } arr := [...]float64{7.0, 5.4, 9.2} fmt.Println(sum(&arr))
不过,这种风格并不符合Go的语言习惯。相反的,应该使用切片。
count[x] = count[x] * scale 可替换成 count[x] *= scale 这样能够省去对变量表达式的重复计算
// 声明了一个二维数组,该数组以两个数组做为元素,其中每一个数组中又有4个int类型的元素 doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} // 上面的声明能够简化,直接忽略内部的类型 easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} arr := [3][2]int{ {1, 2}, {3, 4}, {5, 6}} // 若是最后的}放到下一行了,则须要使用,({5,6},) fmt.Println(arr[2]) // [5 6] towDimen := [][]string{ []string{"a", "b", "c"}, []string{"d", "e"}, }
数组切片的数据结构能够抽象为如下3个变量:
切片持有对底层数组的引用,若是你将一个切片赋值给另外一个,两者都将引用同一个数组。若是函数接受一个切片参数,那么其对切片的元素所作的改动,对于调用者是可见的,比如是传递了一个底层数组的指针。所以,Read函数能够接受一个切片参数,而不是一个指针和一个计数;切片中的长度已经设定了要读取的数据的上限.
注意 绝对不要用指针指向 slice。slice 自己已是一个引用类型,因此它自己就是一个指针!!
【建立切片】
方式一:在已有数组的基础上建立
arr := [5]int{1, 2, 3, 4, 5} slice := arr[:4] // 前4个元素 0-3 fmt.Println(slice) // [1 2 3 4] slice := arr[1:] // 从1开始到结尾 [2 3 4 5] slice := arr[1:4] // 1-3 [2 3 4] slice := arr[:] // 所有
切片的改变可影响原数组
arr := [3]int{1, 2, 3} slice := arr[:] slice[0] = 5 fmt.Printf("arr=%v, slice=%v", arr, slice) // arr=[5 2 3], slice=[5 2 3]
方式二:直接建立
建立一个初始元素个数为5的数组切片,元素初始值为0,cap为5:
mySlice1 := make([]int, 5)
建立一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
mySlice2 := make([]int, 5, 10)
直接建立并初始化包含5个元素的数组切片(len和cap都为5):
mySlice3 := []int{1, 2, 3, 4, 5}
固然,事实上还会有一个匿名数组被建立出来,只是不须要咱们来操心而已
如下这两种方式可建立相同的slice:
s1 := make([]int, 5, 10) s2 := new([10]int)[:5] fmt.Printf("s1 type:%T, s2 type:%T\n", s1, s2) // s1 type:[]int, s2 type:[]int fmt.Printf("s1 len:%d,cap:%d, s2 len:%d,cap:%d\n", len(s1), cap(s1), len(s2), cap(s2)) // s1 len:5,cap:10, s2 len:5,cap:10
方法三:基于切片建立切片
oldSlice := []int{1, 2, 3, 4, 5} newSlice := oldSlice[:3] // 基于oldSlice的前3个元素构建新数组切片 fmt.Println(newSlice)
有意思的是,选择的oldSlicef元素范围甚至能够超过所包含的元素个数,好比newSlice能够基于oldSlice的前6个元素建立,虽然oldSlice只包含5个元素。只要这个选择的范围不超过oldSlice存储能力(即cap()返回的值),那么这个建立程序就是合法的。newSlice中超出oldSlice元素的部分都会填上0。
然而在这里 oldSlice 的容量就是5,而 newSlice 的容量也是5,由于这俩用得是同一个数组。
这个和从新分片是同样的。
由于字符串是纯粹不可变的字节数组,它们也能够被切分红 slice
s := "hello,世界" s2 := s[6:9] fmt.Println(s2) // 世
【切片的第3个参数】
slice := [...]int{0, 1, 2, 3, 4, 5, 6} fmt.Println(slice) s := slice[1:2:4] fmt.Println(len(s), cap(s)) // 1 3 s1 := slice[1:2] fmt.Println(len(s1), cap(s1)) // 1 6
第3个参数表示容量最大界索引,第三个参数减去第一个参数的差值就是容量。
【查看长度和容量】
slice := []int{1, 2, 3, 4, 5} fmt.Println(len(slice), cap(slice)) // 5 5 slice2 := make([]int, 5, 10) fmt.Println(len(slice2), cap(slice2)) // 5 10
一个 slice s 能够这样扩展到它的大小上限:s = s[:cap(s)]
s := make([]int, 5, 10) s = s[:cap(s)] fmt.Println(len(s), cap(s)) // 10,10
【从新分片】
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} s := arr[:5] fmt.Println(s) // [1 2 3 4 5] s = arr[5:10] fmt.Println(s) // [6 7 8 9 0]
【新增元素】
slice := []int{1, 2, 3, 4, 5} slice2 := append(slice, 6, 7, 8) fmt.Println(slice) // [1 2 3 4 5] fmt.Println(slice2) // [1 2 3 4 5 6 7 8]
append()是产生了一个新的切片
append()的第二个参数实际上是一个不定参数,咱们能够按本身需求添加若干个元素,甚至直接将一个数组切片追加到另外一个数组切片的末尾:
slice := []int{1, 2, 3, 4, 5} slice2 := []int{8, 9, 10} slice = append(slice, slice2...) fmt.Println(slice)
第二个参数slice2后面加了三个点,即一个省略号,若是没有这个省略号的话,会有编译错误,由于按append()的语义,从第二个参数起的全部参数都是待附加的元素。由于slice中的元素类型为int,因此直接传递slice2是行不通的。加上省略号至关于把slice2包含的全部元素打散后传入。 上述调用等同于:
slice = append(slice, 8, 9, 10)
数组切片会自动处理存储空间不足的问题。若是追加的内容长度超过当前已分配的存储空间(即cap()调用返回的信息),数组切片会自动分配一块足够大的内存,而后返回指向新数组的切片。
append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响
append interface{}时的坑
var slice []interface{} = make([]interface{}, 0) slice = append(slice, "hello") slice = append(slice, 12) var arr []interface{} = []interface{}{"one", "two"} slice = append(slice, arr...) var arr2 []string = []string{"three", "four"} slice = append(slice, arr2...) // cannot use arr2 (type []string) as type []interface {} in append fmt.Println(slice)
这里在append arr2时是想让go作一个隐式转换,把[]string转化成[]interface{},但显然go如今还不支持。
var arr []string = nil arr = append(arr, "hello")
并不会报空指针异常,可能仍是由于类型和值都为nil时才为nil,很明显,这里的类型不为nil。
【内容复制】
数组切片支持Go语言的另外一个内置函数copy(),用于将内容从一个数组切片复制到另外一个数组切片。若是加入的两个数组切片不同大,就会按其中较小的那个数组切片的元素个数进行复制
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中 fmt.Println(slice2) // [1 2 3] slice2[0], slice2[1], slice2[2] = 7, 8, 9 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置 fmt.Println(slice1) // [7 8 9 4 5]
【删除】
func (m *MusicManager) Remove(index int) *MusicEntry { if index < 0 || index >= len(m.musics) { return nil } removedMusic := m.musics[index] // 这里不能加&,不然删除的时候会被后面的覆盖,因此须要把元素复制出来而不是只取地址 // 从切片中删除 len := len(m.musics) if len == 0 || len == 1 { // 删除仅有的一个 m.musics = m.musics[0:0] } else if index == 0 { // 以后的长度至少为2 // 删除开头的 m.musics = m.musics[1:] } else if index == len { //最后一个 m.musics = m.musics[:len-1] } else { // 中间的,长度至少为3 m.musics = append(m.musics[:index], m.musics[index+1:]...) } return &removedMusic }
[start : end]操做并无改变底层的数组,仅仅是改变了开始索引和长度,append操做会覆盖掉中间的元素,但底层数组仍是没有改变,只是改变了索引位置和长度。
通常性的代码:
func DeleteString(slice []string, index int) []string { len := len(slice) if index < 0 || index >= len { return slice } if len == 0 { return slice } if len == 1 { return slice[0:0] } if index == 0 { return slice[1:] } if index == len-1 { return slice[:len-1] } return append(slice[:index], slice[index+1:]...) }
上面代码有些啰嗦了,下面是简洁版的:
func DeleteString2(slice []string, index int) []string { if index < 0 || index >= len(slice) { return slice } return append(slice[:index], slice[index+1:]...) }
Go的数组和切片都是一维的。要建立等价的二维数组或者切片,须要定义一个数组的数组或者切片的切片,相似这样:
type Transform [3][3]float64 // A 3x3 array, really an array of arrays. type LinesOfText [][]byte // A slice of byte slices.
由于切片是可变长度的,因此能够将每一个内部的切片具备不一样的长度。这种状况很常见,正如咱们的LinesOfText例子中:每一行都有一个独立的长度。
text := LinesOfText{ []byte("Now is the time"), []byte("for all good gophers"), []byte("to bring some fun to the party."), }
有时候是须要分配一个二维切片的,例如这种状况可见于当扫描像素行的时候。有两种方式能够实现。一种是独立的分配每个切片;另外一种是分配单个数组,为其 指定单独的切片们。使用哪种方式取决于你的应用。若是切片们可能会增大或者缩小,则它们应该被单独的分配以免覆写了下一行;若是不会,则构建单个分配 会更加有效。做为参考,这里有两种方式的框架。首先是一次一行:
// Allocate the top-level slice. picture := make([][]uint8, YSize) // One row per unit of y. // Loop over the rows, allocating the slice for each row. for i := range picture { picture[i] = make([]uint8, XSize) }
而后是分配一次,被切片成多行:
// Allocate the top-level slice, the same as before. picture := make([][]uint8, YSize) // One row per unit of y. // Allocate one large slice to hold all the pixels. pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8. // Loop over the rows, slicing each row from the front of the remaining pixels slice. for i := range picture { picture[i], pixels = pixels[:XSize], pixels[XSize:] }
key能够为任何定义了等于操做符的类型,例如整数,浮点和复数,字符串,指针,接口(只要其动态类型支持等于操做),结构体和数组。切片不能 做为map的key,由于它们没有定义等于操做。和切片相似,map持有对底层数据结构的引用。若是将map传递给函数,其对map的内容作了改变,则这 些改变对于调用者是可见的。
key能够是任意数据类型,只要该类型可以用==来进行比较
map和其余基本型别不一样,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
【变量声明】
var person map[string] string
[]内是键的类型,后面是值类型
【建立】
person = make(map[string]string)
声明加+建立:
var person map[string]string = make(map[string]string) person := make(map[string]string)
指定该map的初始存储能力:
person = make(map[string]string, 100)
建立并初始化map:
var person map[string]string person = map[string]string{ "a": "haha", "b": "ni", // 最后的逗号是必须的 }
【元素赋值/添加元素】
var person map[string]string = make(map[string]string) person["1"] = "abc"
m := make(map[string]int) m["a"]++
不用担忧map在没有当前的key时就对其进行++操做会有什么问题,由于go语言在碰到这种状况时,会自动将其初始化为0,而后再进行操做。
【元素删除】
delete(person, "1")
若是传入的map是nil,则报错,若是键不存在,则什么都不发生
【元素查找】
value, ok := person["2"] if ok { fmt.Println(value) } else { fmt.Println("does not find!") }
或者:
if value, ok := person["2"]; ok { fmt.Println(value) } else { fmt.Println("does not find!") }
即使是nil也是能够查找的:
var m map[string]int = nil if v, ok := m["a"]; ok { fmt.Println(v) } else { fmt.Println("!ok") } // 输出!ok
map中的元素不会出现nil的现象(很神奇):
func main() { m := make(map[string]entry) fmt.Println(m["a"].name == "") // true } type entry struct { name string }
m[“a”]返回的并非nil,而是{},若是map的元素是指针,则是nil
m := make(map[string]*entry) fmt.Println(m["a"] == nil) // true
【遍历】
for k, v := range person { fmt.Println("key=", k, "value=", v) }
map也是一种引用类型,若是两个map同时指向一个底层,那么一个改变,另外一个也相应的改变:
m := make(map[string]string) m["Hello"] = "Bonjour" m1 := m m1["Hello"] = "Salut" // 如今m["hello"]的值已是Salut了
【清空map】
for k, _ := range m { delete(m, k) }
或者从新赋值:
m = make(map[string]string)
【线程安全的map】
【map中的对象是个拷贝问题】
直接对map对象使用[]操做符得到的对象不能直接修改状态:
type Person struct { age int } m := map[string]Person{"c": {10}} m["c"].age = 100 // 编译错误:cannot assign to m["c"].age
经过查询map得到的对象是个拷贝,对此对象的修改不影响原有对象的状态:
type Person struct { age int } m := map[string]Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 fmt.Println(m["c"].age) // 10
解决方法
type Person struct { age int } m := map[string]*Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 fmt.Println(m["c"].age) // 20
type Person struct { age int } m := map[string]Person{"c": {10}} p := m["c"] p.age = 20 fmt.Println(p.age) // 20 m["c"] = p fmt.Println(m["c"].age) // 20
【分拆map】
分拆map,提升并发能力
下面这张图来源于Russ Cox Blog中一篇介绍Go数据结构的文章,你们能够看到这些基础类型底层都是分配了一块内存,而后存储了相应的值。
var p *int = new(int) *p = 5 fmt.Println(*p, p) // 5 0xc0820001d0 q := 6 var pq *int = &q fmt.Println(*pq, pq) // 6 0xc082000200
const i = 5 ptr := &i //error: cannot take the address of i ptr2 := &10 //error: cannot take the address of 10
package main func main() { var p *int = nil *p = 0 } // in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/> // runtime error: invalid memory address or nil pointer dereference
x := 0x12345678 p := unsafe.Pointer(&x) // *int -> Pointer n := (*[4]byte)(p) // Pointer -> *[4]byte, 3,5也能够 for i := 0; i < len(n); i++ { fmt.Printf("%X ", n[i]) }
输出:
78 56 34 12
func test() *int { x := 100 return &x // 在堆上分配x内存,但在内联时,也能够直接分配在目标栈 }
d := struct { s string x int }{"abc", 100} p := uintptr(unsafe.Pointer(&d)) // *struct -> Pointer -> uintptr p += unsafe.Offsetof(d.x) // uintptr + offset p2 := unsafe.Pointer(p) // uintptr -> Pointer px := (*int)(p2) // Pointer -> *int *px = 200 // d.x = 200 fmt.Printf("%#v\n", d)
输出:
struct { s string; x int }{s:"abc", x:200}
注意:GC 把 uintptr 当成普通整数对象,它没法阻止"关联"对象被回收。