go语言指针被拆分成两个核心概念:指针类型和切片,他们分别对应变量和数组的引用。
指针类型:指向变量在底层的内存,但是go语言的指针不允许偏移和运算。切片:是对数组一个连续片段的引用,切片底层数据结构都是数组,切片内部结构包括:地址、大小和容量。切片一般用于快速操作一块数据集合。 变量
参考前一篇文章:go语言变量与常量
指针 变量由内存地址,数据类型和值组成,指针即变量内存地址的别名,通过这个别名同样可以访问和修改变量的内存。
go语言中指针类型只能操作单个变量(不像c语言中可以通过指针偏移操作一大片内存。感觉更像c++的引用类型)。
不能偏移的指针更方便垃圾回收。
var name *T
指针变量赋值 有两种方式:
1、 " & "操作符取出变量的地址,然后赋值给指针,所以说指针实际上就是地址。
2、 使用new(Type)直接在内存中开辟一个空间并把地址赋值给指针。
对指针使用" * "操作符来取指针指向的值。
需要注意的是,指针变量也是一个变量,同样可以使用 " & "取其地址。
下面的例子中:将ptr和ptr1都指向a,然后对ptr指向的内存的值做" ++ "操作,打印其值。
var a int = 10var ptr *int = &a(*ptr)++fmt.Printf("Typeof ptr:%T, Address of ptr:%v,ptr=%v, *ptr=%d, a=%d n", ptr, &ptr, ptr, *ptr, a)ptr1 := new(string)*ptr1 = "hello go"fmt.Printf("Type of ptr1: %T, ptr1= %v, *ptr1=", ptr1, ptr1, *ptr1)// 输出:// Typeof ptr:*int, Address of ptr:0xc0000ac018,ptr=0xc0000b2008,*ptr=11,a=11 // Type of ptr1: *string, ptr1= 0xc000096210, *ptr1=%!(EXTRA string=hello go)
一个小例子new(string)引发的思考
由于c++ new的惯性思维,认为new(string)的时候就要申请好容纳string值的内存,但是从上面的例子里面看到,new(string)的时候并没有给字符串长度之类的参数。搜索了一下发现go的string定义时这样的:type stringStruct struct {str unsafe.Pointer //str首地址len int //str长度}
包含了一个底层指针和一个长度,这两个类型的长度都是明确的,所以new的时候实际上是new了一个指针变量和一个int变量。
而容纳string内容的内存开辟出现在赋值的时候,即*ptr1 = "hello go",先在只读区存入"hello go"字符串,然后将地址和长度赋值给ptr1指向的stringStruct的str和len。
flag包的使用,flag是go语言提供的用来解析命令行参数的工具包
//定义命令行参数var key1 = flag.String("key1", "", "key1 xxxxxx,value set to 1/2")var key2 = flag.String("key2", "", "key2 xxxxxx,value set to 3/4")//解析命令行参数flag.Parse()fmt.Println("key1 = ", *key1, "key2=", *key2)
数组数组是0个或多个相同数据类型组成的一个数据结构,数组的长度是固定的,数组声明就自动完成初始化。
数组的声明var name [count]Type //其中count必须在编译期就确定
初始化和访问数组元素使用" [index] "访问index索引所指向的元素
var nums = [3]int{1,2,3}fmt.Println(nums[0], nums[1], nums[2]) //输出 1 2 3var nums1 = [3]int{0: 2, 1: 3} //给前两个位置赋值2/3fmt.Println("array nums1:", nums1[0], nums1[1], nums1[2]) //输出 2 3 0
for…range遍历使用range遍历返回索引和值,可以使用匿名变量" _ "来忽略其中一个返回值。
var arr [3]intarr[2] = 10for index, value := range arr {fmt.Printf("value of index %d is %dn", index, value)}//输出value of index 0 is 0value of index 1 is 0value of index 2 is 10
自动推到数组大小
//自动推到数组大小var arr1 = [...]int{1, 2, 3, 4} //数组大小推到为4for _, value := range arr1 { //忽略索引fmt.Print(value, " ")}
数组相等判断 可以使用 " == " " != "符号判断两个数组是否相等。
只有数组类型相同才能进行比较,其中类型包括数组长度,比如[2]int和[3]int是两种不同的类型,不能进行比较。
只有两个数组所有元素一一相等两个数组才相等,否则不相等。
var nums = [3]int{1, 2, 3}var nums1 = [3]int{0: 2, 1: 3} fmt.Printf("nums == nums1 : %vn", nums == nums1) //输出 false
多维数组//多维数组//声明一个二维数组,初始化为0var arr2 [2][3]intfmt.Println(arr2) //输出: [[0 0 0] [0 0 0]]//赋值arr2 = [2][3]int{{1, 2, 3}, {4, 5, 6}} fmt.Println(arr2) //输出: [[1 2 3] [4 5 6]]//指定赋值的索引arr2 = [2][3]int{1: {7, 8, 9}}fmt.Println(arr2) //输出: [[0 0 0] [7 8 9]]//通过索引取值和赋值num := arr2[0][0]fmt.Println(num) //输出: 0arr2[1][1] = 10fmt.Println(arr2)//输出:[[0 0 0] [7 10 9]]//for...range遍历for index, value := range arr2 {fmt.Printf("value in index %d of arr2:%v n", index, value)}//数组赋值,只能赋值长度一样的数组var tmp [3]int = arr2[1]fmt.Println(tmp) //输出:[7 10 9]
切片切片是在数组基础上的引用,所以也只能操作数组范围内的内存,不会产生非法修改内存等错误,因此切片比指针类型更安全,当越界时,会报宕机并打出堆栈,指针越界时直接崩溃。
从数组生成切片切片有三种生成方式:从数组生成,自定义,使用make函数生成
使用len和cap函数取切片的长度和容量
切片可以从数组中直接生成,切片也可以生成新的切片。
//从数组生成切片var srcArr = [4]int{1, 2, 3, 4}//start:end 对应目标数组需要引用部分的起始和结束索引,左闭右开fmt.Println(srcArr[1:3]) //输出[2 3]//从切片生成新的切片fmt.Println(srcArr[1:3][0:1]) //输出 [2]//缺省起始索引表示从0开始fmt.Println(srcArr[:3]) //[1 2 3]//缺省结束索引表示到数组末尾fmt.Println(srcArr[1:]) //[2 3 4]//缺省起始和结束索引,与原数组等效fmt.Println(srcArr[:]) //[1 2 3 4]//起始和结束都为0,为空fmt.Println(srcArr[0:0]) //[]//切片长度和容量fmt.Println("len:", len(srcArr[0:2]), ",cap", cap(srcArr[0:2])) //输出 len: 1 ,cap 3//切片越界//fmt.Println(srcArr[1:100]) //编译时即报错
主动声明新切片 除了可以从原有数组生成外,还可以主动声明一个新的切片,这种方式类似于c++的动态数组std::vector
这种方式没有初始容量,所以每次append都会发生扩容操作。
//申明切片var slice []string //注意与数组声明的区别:数组必须给定长度fmt.Println(slice, "is nil:", slice == nil) //[] true//切片是动态类型,只能与nil进行比较,不能相互比较var slice1 = []string{}fmt.Println(slice1, "is nil:", slice1 == nil) //[] false ——注意,一旦赋值就不是nil//使用append向切片中添加元素slice = append(slice, "aaa")slice = append(slice, "bbb")fmt.Println(slice) //[aaa bbb]
使用make创建切片使用make创建的切片会在make的时候申请cap大小的内存空间,在cap范围内append元素不会发生扩容,提高了性能。
var name = make([]Type,size,cap) //size表示初始len,表示初始已经有的元素个数,cap表示容量var slice2 = make([]string, 2, 10)fmt.Println(slice2, " len:", len(slice2), " cap:", cap(slice2)) //[ ] len: 2 cap: 10//可以缺省cap参数,此时cap和size是一样大的var slice3 = make([]string, 2)fmt.Println(slice3, " len:", len(slice3), " cap:", cap(slice3)) //[ ] len: 2 cap: 2
切片复制使用copy函数将src切片元素复制到dst中,返回值为实际复制的元素个数,copy只会复制min(len(src),len(dst))个元素。
copy(dst,src,[]Type) intvar arr3 = [6]int{1, 2, 3, 4, 5, 6}copy(arr3[:2], arr3[3:6])fmt.Print(arr3) //[4 5 3 4 5 6]