共计 7465 个字符,预计需要花费 19 分钟才能阅读完成。
值类型和援用类型区别
值类型:应用变量指向 内存的值
,内存调配通常在栈中,产生赋值和参数传递时是把这个数据的值(可能多个数据,如数组包含数据域和长度)一起拷贝一份。
援用类型:援用类型数据是应用变量的 内存地址
,或内存地址中第一个字所在的地位,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中。
为什么要把数组、切片、字符串三种类型一起讲呢?因为它们的数据结构具备紧密联系,在底层他们的内存构造是一样的,只是他们在下层因为语法的起因体现不一样。数组和字符串是值类型,切片是援用类型。
数组
数组是由 同一种数据
元素组成 固定
长度的序列,一个数组由零个或多个元素组成。数组是值类型数据,长度不可扭转,内容可变,数组自身赋值和传递参数都是整体复制。因为数组是固定长度,往往应用比拟少更多应用的是切片。
定义
var a [4]int // 定义长度 4,元素类型为 int, 值为 0 0 0 0
var b = [5]int{1, 2, 3, 4} // 定义长度 5,元素类型为 int, 值为 1 2 3 4 0
var c = [...]int{1, 2, 3, 4} // 定义长度 4,元素类型为 int, 值为 1 2 3 4
var d = [4]int{1: 2, 3} // 定义长度 4,元素类型为 int, 值为 0 2 3 0
var e [4]int = [4]int{1: 2, 3} // 定义长度 4,元素类型为 int, 值为 0 2 3 0
var f = [2]string{"hello","world"} // 定义长度 2,元素类型为 string, 值为 "hello","world"
内存构造,上面是数组 b 的内存构造,比较简单:
var b = [5]int{1, 2, 3, 4}
基本操作
// 数组下标是从 0 开始的。如果下标在数组非法范畴之外,则触发拜访越界,会 panic
var arr = [5]int{1, 2, 3, 4}
fmt.Println(arr) // 打印全副 [1 2 3 4 0]
fmt.Println(arr[1]) // 打印下标为 1(第二个元素) 2
fmt.Println(arr[5]) //invalid array index 5 (out of bounds for 5-element array)
for
循环来迭代数组
var arr = [5]int{1, 2, 3, 4}
for range arr {fmt.Println("hello world")
}
//1、hello hello hello hello hello
for k := range arr {fmt.Println(k)
}
//2、打印下标://0 1 2 3 4
for k, v := range arr {fmt.Println(k, v)
}
//3、key=0,val=1 key=1,val=2 key=2,val=3 key=3,val=4 key=4,val=0
// 不想打印 k,能够用 "_" 代替 k, 示意疏忽 k
for i := 0; i < len(arr); i++ {fmt.Println(i, arr[i])
}
//4、key=0,val=1 key=1,val=2 key=2,val=3 key=3,val=4 key=4,val=0
两个数组之间的操作
数组是值类型数据,变量指向内存所有数据,包含长度。即数组长度也是数组一部分,故不同长度的数组不能间接赋值,大小也不能比拟,雷同长度没问题。
// 数组长度雷同时:var a = [5]int{1, 2, 3, 4}
var b = [5]int{}
b = a
fmt.Println(b) //[1 2 3 4 0]
fmt.Println(b != a) //false
// 数组长度不雷同时:var a = [5]int{1, 2, 3, 4}
var b = [6]int{}
b = a //cannot use a (type [5]int) as type [6]int in assignment
fmt.Println(b != a) //invalid operation: b != a (mismatched types [6]int and [5]int)
// 想把 a 的值给 b 只能通过下标一个个改
for i := 0; i < len(a); i++ {b[i] = a[i]
}
fmt.Println(b) //[1 2 3 4 0 0]
多维数组
多维数组是数组的数组,可把外面的数组成一个咱们常应用的元素类型, 就好了解了。
申明
二维数组申明表达式 : var v_name [行][列]v_type
var a = [3][2]int{{}, {}, {}} //[[0 0] [0 0] [0 0]]
var b = [3][2]int{[2]int{}, [2]int{}, [2]int{}} //[[0 0] [0 0] [0 0]]
var c = [3][2]int{{1}, {2}, {3}} //[[1 0] [2 0] [3 0]]
var d = [3][2]int{{1, 2}, {2, 2}, {3, 2}} //[[1 2] [2 2] [3 2]]
操作
赋值
var arr [3][2]int
fmt.Println(arr) //[[0 0] [0 0] [0 0]]
arr[0][1] = 2
arr[2][0] = 3
fmt.Println(arr) //[[0 2] [0 0] [3 0]]
对应二维数组来说,把每行看成一个元素后就是一个一维数组了,基本操作和一维数组区别不大,其余多维数组也相似,就不一一说了。
字符串
字符串 (string) 是 UTF- 8 的字符一个序列(字符为 ASCII 表里长度 1 字节,其余 2~4 个字节,如中文 3 个字节),它是个不可更改的,只读序列,能够蕴含任意字符。即创立后就不可更改,本质上就是一个定长的字节数组。
Go 字符串在底层 reflect.StringHeader
构造:
type StringHeader struct {
Data uintptr
Len int
}
字符串组成包含两局部:一部分是指向底层字节数组的指针,一部分是字节长度。字符串在拷贝其实是复制一份 reflect.StringHeader
构造体, 并不会复制底层的字节数组。
上面是字符串 hello world
内存构造:
定义
Go 应用双引号""
或反引号 `
包起来的示意是字符串内容,留神不能用单引号'
, 单引号示意的是字节(byte),即 UTF- 8 对应的编码
var str string
var str1 = "hello"
var str2 string = "world"
str3 := "hello world"
fmt.Println(str) //
fmt.Println(str1) //hello
fmt.Println(str2)//world
fmt.Println(str3)//hello world
初始值
Go 字符串的初始值为空字符串""
, 留神" "
不是空字符串,这个示意空格字符串,占一个字节,对应 ASCLL 码的 00100000B(32H)。
特殊字符
有些字符用 \ 加常见的字符示意非凡含意的字符,如换行
- \n:换行符
- \r:回车符
- \t:tab 键
- \u 或 U:Unicode 字符
- \\:反斜杠本身
遍历
因为底层是数组,可用 for range
来遍历, 须要留神的是每个字符占的大小不肯定一样,key 不肯定是间断的,如:
str := "中国 abc"
for k, v := range str {fmt.Println(k, v)
}
//0 20013
//3 22269
//6 97
//7 98
//8 99
不可变性
这个不可变性很容易让人认为这个字符串像常量那样,一旦申明整个生命周期都不能变。这个不能变是通过键下标批改:
str := "abcdef"
fmt.Println(str[1]) // 通过下标拜访(读):98
str[1] = "h" // 通过下标改(写):cannot assign to str[1]
可能会纳闷明明能够扭转字符串:
str := "abcdef"
fmt.Println(str) //abcdef
str = "hello world"
fmt.Println(str) //hello world
这种扭转是把底层指向数组的指针扭转的,是从新开拓一个内存地址保留新数据,摈弃掉原指针数组。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
str := "hello"
fmt.Println(stringAddr(str)) //4999457
str = "world"
fmt.Println(stringAddr(str)) //4999111
}
func stringAddr(s string) uintptr {return (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
}
拼接字符串
-
+
号拼接,会产生开销str1 := "hello" str2 := "world" str := str1 + str2 fmt.Println(str) //hello world
-
fmt 包下的打印函数, 效率不高
str1 := "hello" str2 := "world" str := fmt.Sprintf("%s%s", str1, str2) fmt.Println(str) //hello world
-
strings.Join
示意以什么字符串拼接字符串数组,在已有一个数组的状况下,这种效率会很高,如果没有的话效率也不高。str1 := "hello" str2 := "world" str3 := []string{str1, str2} str := strings.Join(str3, "") fmt.Println(str) //hello world
-
字节缓冲
bytes.Buffer
这种办法的性能就要大大优于下面的了str1 := "hello" str2 := "world" var by bytes.Buffer by.WriteString(str1) by.WriteString(str2) fmt.Println(by.String())
-
strings.Builder
性能和bytes.Buffer
一样str1 := "hello" str2 := "world" var by strings.Builder by.WriteString(str1) by.WriteString(str2) fmt.Println(by.String())
尽管 bytes.Buffer
和strings.Builder
性能最好,官网也倡议,但 +
更简略不便,往往应用得最多。
长度
尽管底层构造体有长度,但字符串不包含长度,要获取字符串长度和数组、切片一样用内置函数 len()
获取。字符串的范畴是依据长度决定的,而非特殊字符 \0。
切片
切片能够看成 动静数组
, 长度不定
,所以长度不是类型组成部分。切片(slice)是对数组的一个间断片段的援用,所以切片是一个援用类型(因而更相似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段能够是整个数组,也能够是由起始和终止索引标识的一些项的子集,须要留神的是,终止索引标识的项不包含在切片内。
Go 字符串在底层reflect.SliceHeader
构造:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
reflect.SliceHeader
构造体组成有三局部:一部分是切片数据指针,另一部分是切片长度,最初一部分是切片容量。
内存构造
定义
var arr1 []int // nil 切片
var arr2 []int = []int{1, 2}// 初始值为[1 2]
var arr3 = []int{1, 2, 3}// 初始值为[1 2 3]
var arr4 = arr3[1:]//arr3 的局部 初始值为[2 3]
var arr5 = make([]int, 3) // 有 3 个元素的切片, len 和 cap 都为 3
var arr6 = make([]int, 2, 3)// 有 2 个元素的切片, len 为 2, cap 为 3
初始值
初始值为
nil
, 不能等价于空字符串""
、布尔值false
和数值上的0
, 这些都是不同类型数据,不能相互之间转换。
基本操作
切片是动静数组,数组的操作,切片都能。
获取长度、容量
长度是切片的理论长度,容量是切片的最大容量。0 <= len(s) <= cap(s)
。当向切片增加数据超过切片容量时,会丢掉原切片数据局部,从新开一个空间来存储新数据,开拓空间大小是上一个切片最大容量的 2 倍。
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {var arr1 = []int{1, 2, 3}
var arr2 = make([]int, 2, 3)
fmt.Println(len(arr2), cap(arr2)) //2 3
fmt.Println(sliceAddr(arr1)) //824634252976
fmt.Println(len(arr1), cap(arr1)) //3 3
arr1 = append(arr1, 4)
fmt.Println(len(arr1), cap(arr1)) //4 6
fmt.Println(sliceAddr(arr1)) //824634253360
}
// 获取切片数据地址
func sliceAddr(s []int) uintptr {return (*reflect.SliceHeader)(unsafe.Pointer(&s)).Data
}
单个元素操作
package main
import ("fmt")
func main() {var arr = []int{1, 2, 3}
// 打印下标为 1 的元素:fmt.Println(arr[1]) // 2
// 获取全副元素:fmt.Println(arr) //[1 2 3]
// 更改下标为 2 的元素
arr[2] = 5
fmt.Println(arr[2]) //5
// 不能越界拜访
fmt.Println(arr[3]) //panic: runtime error: index out of range
}
增加操作
内置函数 append()
示意向切片开端增加元素的意思:
package main
import ("fmt")
func main() {var arr = []int{1, 2, 3}
fmt.Println(arr) //[1 2 3]
// 新增一个元素
arr = append(arr, 4)
fmt.Println(arr) //[1 2 3 4]
// 新增 2 个元素
arr = append(arr, 5, 6)
fmt.Println(arr) //[1 2 3 4 5 6]
// 新增多个个元素,语法糖(arr1...) 示意把 arr1 从头到尾一样一一赋值
var arr1 = []int{20, 30}
arr = append(arr, arr1...)
fmt.Println(arr) //[1 2 3 4 5 6 20 30]
}
拷贝
浅拷贝:拷贝内存地址,批改数据对原数据有影响
深拷贝:拷贝这个值,内存地址不一样,批改数据对原数据不影响
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {var arr = []int{1, 2, 3, 4, 5, 6, 20, 30}
fmt.Println(arr) //[1 2 3 4 5 6 20 30]
------------------------- 浅拷贝 ----------------------------
// 赋值
arr1 := arr
fmt.Println(arr1) //[1 2 3 4 5 6 20 30]
// 截取从下标 2 及前面的局部
arr1 = arr[2:]
fmt.Println(arr1) //[3 4 5 6 20 30]
// 截取从下标 5 后面的局部
arr1 = arr[:5]
fmt.Println(arr1) //[1 2 3 4 5]
// 截取从下标 2 - 5 的局部
arr1 = arr[2:5]
fmt.Println(arr1) //[3 4 5]
// 切片是援用型数据,这样截取数据本质上是数据指针指向内存地址的扭转
// 故其它在这内存地址上的切片扭转,原切片的数据会扭转,如下:arr1[1] = 88
fmt.Println(arr) //[1 2 3 88 5 6 20 30]
------------------------- 深拷贝 ----------------------------
// 如果切片原本是援用另一个切片,但元素减少超过切片容量后,会从新开拓空间,对原数据不影响(指向数据的指针不同)var arr2 = []int{1, 2, 3}
fmt.Println(arr2) //[1 2 3]
fmt.Println(sliceAddr(arr2)) // 原切片数据指针 824633771136
arr3 := arr2
fmt.Println(arr3) //[1 2 3]
fmt.Println(sliceAddr(arr3)) // 新切片数据指针 824633771136
arr3 = append(arr3, 4, 5, 6) // 新增元素超过容量,从新开拓空间
fmt.Println(arr2) //[1 2 3]
fmt.Println(arr3) //[1 2 3 4 5 6]
fmt.Println(sliceAddr(arr2)) // 原切片数据指针 824633771136
fmt.Println(sliceAddr(arr3)) // 超出容量后,新开拓空间切片数据指针 824633787232
// 应用内置函数 copy(), 拷贝一份数据,这种批改数据也对原数据没影响
// copy()和切片扩容后,都是对原数据切片整个复制,不存在对原数据内存地址的援用,这种叫深拷贝,批改对原数据影响没半毛钱关系
var arr4 = make([]int, len(arr))
copy(arr4, arr)
arr4[1] = 100
fmt.Println(arr) //[1 2 3 88 5 6 20 30]
fmt.Println(arr4) //[1 100 3 88 5 6 20 30]
}
func sliceAddr(s []int) uintptr {return (*reflect.SliceHeader)(unsafe.Pointer(&s)).Data
}
遍历
package main
import ("fmt")
func main() {var arr = []int{1, 2, 3, 4, 5, 6, 20, 30}
for range arr {fmt.Printf("%s", "a") // a a a a a a a a
}
fmt.Println("")
for k := range arr {fmt.Printf("%d", arr[k]) //1 2 3 4 5 6 20 30
}
fmt.Println("")
for k, v := range arr {fmt.Printf("%d=>%d", k, v) //0=>1 1=>2 2=>3 3=>4 4=>5 5=>6 6=>20 7=>30
}
fmt.Println("")
for i := 0; i < len(arr); i++ {fmt.Printf("%d", arr[i]) //1 2 3 4 5 6 20 30
}
}