1.基本类型
1.1. 基本类型
bool
int: int8, int16, int32(rune), int64
uint: uint8(byte), uint16, uint32, uint64
float32, float64
string
复数:complex64, complex128
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。
array -- 固定长度的数组
int8 range: -128 127
int16 range: -32768 32767
int32 range: -2147483648 2147483647
int64 range: -9223372036854775808 9223372036854775807
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759
// 输出各数值范围
fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
注意:
byte // uint8 的别名
rune // int32 的别名 代表一个 Unicode 码
int 代表 int64
float 代表 float64
Go 语言中不允许将整型强制转换为布尔型.,布尔型无法参与数值运算,也无法与其他类型进行转换。
当一个变量被声明之后,系统自动赋予它该类型的零值: int 为 0,float 为 0.0,bool 为 false,string 为空字符串
切片slice、map、channel、interface、function的默认为 nil
1.2 值类型和引用类型:
值类型:int、float、bool、string、数组array、结构体struct
引用类型:指针、切片slice、map、接口interface、函数func、管道chan
区别:
1)值类型:变量直接存储值,内存通常在栈中分配。
给新的变量赋值时(拷贝时),为拷贝,直接开辟新的内存地址存储值。
2)引用类型:
变量直接存储内存地址,这个地址存储值。内存通常再堆上分配。
给新的变量赋值时(拷贝时),为浅拷贝,新的变量通过指针指向原来的内存地址。可以使用copy关键字实现引用类型的深拷贝。
当如果没有任何一个变量引用这个地址时,这个地址就会被GC垃圾回收。
2.变量
2.1变量的声明
1)var 变量名 类型 = 表达式
如: var a int = 27
如果变量没有初始化默认为对应类型的初始值
2)如果变量没有指定类型 可以通过变量的初始值来判断变量类型
如 var d = true
3)使用 :=
使用格式:名称 :=
也就是说a := 1相等于:
var a int
a =1
2.2多变量声明
可以同时声明多个类型相同的变量(非全局变量),如下图所示:
var x, y int
var c, d int = 1, 2
g, h := 123, "hello"
关于全局变量的声明如下:
var (
a int
b bool
)
2.3匿名变量
匿名变量的特点是一个下画线_,这本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。
使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。
示例代码如下:
func GetData() (int, int) {
return 10, 20
}
func main(){
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b)
}
需要注意的是匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。
2.4 变量作用域
根据定义位置的不同,可以分为一下三个类型:
- 局部变量
- 全局变量以 var 关键字开头外部包中使用首字母必须大写
- 形式参数形式参数会作为函数的局部变量来使用
3.数组
3.1 一维数组:
全局:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
局部:
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
代码:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
func main() {
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
fmt.Println(arr0, arr1, arr2, str)
fmt.Println(a, b, c, d)
}
输出结果:
[1 2 3 0 0] [1 2 3 4 5] [1 2 3 4 5 6] [ hello world tom]
[1 2 0] [1 2 3 4] [0 0 100 0 200] [{user1 10} {user2 20}]
3.2多维数组
全局
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
局部:
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
代码:
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
func main() {
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
fmt.Println(arr0, arr1)
fmt.Println(a, b)
}
输出结果:
[[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]] [[1 2 3] [7 8 9]]
[[1 2 3] [4 5 6]] [[1 1] [2 2] [3 3]]
内置函数 len 和 cap 都返回数组长度 (元素数量)。如:
a := [2]int{}
println(len(a), cap(a)) //得到 2 2
3.3多维数组遍历:
func main() {
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
for k2, v2 := range v1 {
fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
}
fmt.Println()
}
}
输出结果:
(0,0)=1 (0,1)=2 (0,2)=3
(1,0)=7 (1,1)=8 (1,2)=9
3.4. 数组拷贝和传参
package main
import "fmt"
func printArr(arr *[5]int) {
arr[0] = 10
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int
printArr(&arr1)
fmt.Println(arr1)
arr2 := [...]int{2, 4, 6, 8, 10}
printArr(&arr2)
fmt.Println(arr2)
}
4.切片Slice
需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
-
切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
-
切片的长度可以改变,因此,切片是一个可变的数组。
-
切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
-
cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
-
切片的定义:var 变量名 []类型,比如 var str []string var arr []int。
-
如果 slice == nil,那么 len、cap 结果都等于 0。
4.1 创建切片的各种方式
package main
import "fmt"
func main() {
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println(s1, s2, s3)
// 4.初始化赋值
var s4 []int = make([]int, 0, 0)
fmt.Println(s4)
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println(s6)
}
4.2. 切片初始化
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
代码:
package main
import (
"fmt"
)
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6] //可以简写为 var slice []int = arr[:end]
var slice2 []int = arr[5:10] //可以简写为 var slice[]int = arr[start:]
var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素
func main() {
fmt.Printf("全局变量:arr %v\n", arr)
fmt.Printf("全局变量:slice0 %v\n", slice0)
fmt.Printf("全局变量:slice1 %v\n", slice1)
fmt.Printf("全局变量:slice2 %v\n", slice2)
fmt.Printf("全局变量:slice3 %v\n", slice3)
fmt.Printf("全局变量:slice4 %v\n", slice4)
fmt.Printf("-----------------------------------\n")
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[2:8]
slice6 := arr[0:6] //可以简写为 slice := arr[:end]
slice7 := arr[5:10] //可以简写为 slice := arr[start:]
slice8 := arr[0:len(arr)] //slice := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
fmt.Printf("局部变量: arr2 %v\n", arr2)
fmt.Printf("局部变量: slice5 %v\n", slice5)
fmt.Printf("局部变量: slice6 %v\n", slice6)
fmt.Printf("局部变量: slice7 %v\n", slice7)
fmt.Printf("局部变量: slice8 %v\n", slice8)
fmt.Printf("局部变量: slice9 %v\n", slice9)
}
输出结果:
全局变量:arr [0 1 2 3 4 5 6 7 8 9]
全局变量:slice0 [2 3 4 5 6 7]
全局变量:slice1 [0 1 2 3 4 5]
全局变量:slice2 [5 6 7 8 9]
全局变量:slice3 [0 1 2 3 4 5 6 7 8 9]
全局变量:slice4 [0 1 2 3 4 5 6 7 8]
局部变量: arr2 [9 8 7 6 5 4 3 2 1 0]
局部变量: slice5 [2 3 4 5 6 7]
局部变量: slice6 [0 1 2 3 4 5]
局部变量: slice7 [5 6 7 8 9]
局部变量: slice8 [0 1 2 3 4 5 6 7 8 9]
局部变量: slice9 [0 1 2 3 4 5 6 7 8]
4.3. 通过make来创建切片
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)
使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 获取底层数组元素指针。
*p += 100
fmt.Println(s)
}
输出结果:
[0 1 102 3]
至于 [][]T,是指元素类型为 []T 。
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
fmt.Println(data)
输出结果:
[[1 2 3] [100 200] [11 22 33 44]]
4.4. 用append内置函数操作切片(切片追加)
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3}
fmt.Printf("slice a : %v\n", a)
var b = []int{4, 5, 6}
fmt.Printf("slice b : %v\n", b)
c := append(a, b...)
fmt.Printf("slice c : %v\n", c)
d := append(c, 7)
fmt.Printf("slice d : %v\n", d)
e := append(d, 8, 9, 10)
fmt.Printf("slice e : %v\n", e)
}
输出结果:
slice a : [1 2 3]
slice b : [4 5 6]
slice c : [1 2 3 4 5 6]
slice d : [1 2 3 4 5 6 7]
slice e : [1 2 3 4 5 6 7 8 9 10]
append :向 slice 尾部添加数据,返回新的 slice 对象。
package main
import (
"fmt"
)
func main() {
s1 := make([]int, 0, 5)
fmt.Printf("%p\n", &s1) //0xc42000a060
s2 := append(s1, 1)
fmt.Printf("%p\n", &s2)//0xc42000a080
fmt.Println(s1, s2) //[] [1]
}
4.5. 超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。
fmt.Println(s, data) // 重新分配底层数组,与原数组无关。
fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
//得到 [0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
//0xc4200160f0 0xc420070060
}
从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。 通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。
4.6. slice中cap重新分配规律:
package main
import (
"fmt"
)
func main() {
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
}
输出结果:
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
4.7. 切片拷贝
package main
import (
"fmt"
)
func main() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice s1 : %v\n", s1)
s2 := make([]int, 10)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
s3 := []int{1, 2, 3}
fmt.Printf("slice s3 : %v\n", s3)
s3 = append(s3, s2...)
fmt.Printf("appended slice s3 : %v\n", s3)
s3 = append(s3, 4, 5, 6)
fmt.Printf("last slice s3 : %v\n", s3)
}
输出结果:
slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
slice s3 : [1 2 3]
appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("array data : ", data)
s1 := data[8:]
s2 := data[:5]
fmt.Printf("slice s1 : %v\n", s1)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
fmt.Println("last array data : ", data)
}
输出结果:
array data : [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data : [8 9 2 3 4 5 6 7 8 9]
应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。
4.8. slice遍历:
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := data[:]
for index, value := range slice {
fmt.Printf("inde : %v , value : %v\n", index, value)
}
}
输出结果:
inde : 0 , value : 0
inde : 1 , value : 1 等.....
4.9. 切片resize(调整大小)
var a = []int{1, 3, 4, 5}
fmt.Printf(a : %v , len(a) : %v\n", a, len(a))//a : [1 3 4 5] , len(a) : 4
b := a[1:2]
fmt.Printf("b : %v , len(b) : %v\n", b, len(b))// b: [3]
c := b[0:3]
fmt.Printf("c : %v , len(c) : %v\n", c, len(c))// c: [3,4,5]
注意:
c := b[0:3] 的结果为什么是[3,4,5]:上述b的结果是切片[3] 由于bb是切片[3],它只有一个元素。尝试使用索引0到3的范围去索引这个切片会导致一个越界的错误。在Go中,当一个切片被越界索引时,它会返回一个新的切片,该切片与原始切片共享底层数组,但具有指定的长度和容量。
在这种情况下,bb[0:3]返回的切片将是[3 4 5]。这是因为:
·起始索引0对应于原始切片的第一个元素(在这种情况下,这是3)。
·结束索引3对应于原始切片的第四个元素(越界到5)。
因此,c的结果是切片[3 4 5]。
4.10. 数组和切片的内存布局
4.11. 字符串和切片(string and slice)
string底层就是一个byte的数组,因此,也可以进行切片操作。
字符串转换成切片
[]byte(str)用于纯英文转换
[]rune(str)用于含有中文字符的转换
如:
(1) 英文字符串:[]byte
str := "Hello world"
s := []byte(str) //中文字符需要用[]rune(str)
s[6] = 'G'
s = s[:8]
s = append(s, '!')
str = string(s)
fmt.Println(str)//Hello Go!
(2) 中文字符串:[]rune
str := "你好,世界!hello world!"
s := []rune(str)
s[3] = '嘿'
s[4] = '哈'
s[12] = 'g'
s = s[:14]
str = string(s)
fmt.Println(str)// 你好,嘿哈!Hello go
4.12 a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x
golang slice data[:6:8] 两个冒号的理解
常规slice , data[6:8],从第6位到第8位(返回6, 7),长度len为2, 最大可扩充长度cap为4(6-9)
另一种写法: data[:6:8] 每个数字前都有个冒号, slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
d1 := slice[6:8]
fmt.Println(d1, len(d1), cap(d1))//[6 7] 2 4
d2 := slice[:6:8]
fmt.Println(d2, len(d2), cap(d2))//[0 1 2 3 4 5] 6 8
4.13 数组或切片转换成字符串:
str := strings.Replace(strings.Trim(fmt.Sprint(要转换的数组或切片), "[]"), " ", ",", -1)
如:
slice := []int{0, 1, 2, 3}
//数组或切片转换成字符串
str := strings.Replace(strings.Trim(fmt.Sprint(slice), "[]"), " ", ",", -1)
fmt.Printf("%T,%v", str, str) //得到string, 0,1,2,3
package main
import (
"fmt"
)
func main() {
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)//hello
s2 := str[6:]
fmt.Println(s2)//world
}
5. 指针
Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号:
&(取地址) 和 *(根据地址取值)。
5.1 指针声明和初始化
和基础类型数据相同,在使用指针变量之前我们首先需要申明指针,声明格式如下:var var_name *var-type,其中的var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。
代码举例如下:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
指针的初始化就是取出相对应的变量地址对指针进行赋值,具体如下:
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018
5.2指针取值
在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。
func main() {
//指针取值
a := 10
b := &a // 取变量a的地址,将指针保存到b中
fmt.Printf("type of b:%T\n", b)
c := *b // 指针取值(根据指针去内存取值)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
}
得到:
type of b:*int
type of c:int
value of c:10
总结: 取地址操作符&和取值操作符 * 是一对互补操作符, & 取出地址, * 根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:\
1.对变量进行取地址(&)操作,可以获得这个变量的指针变量。
2.指针变量的值是指针地址。
3.对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
指针传值示例:
func modify1(x int) {
x = 100
}
func modify2(x *int) {
*x = 100
}
func main() {
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}
5.3 空指针
当一个指针被定义后没有分配到任何变量 时,它的值为 nil,也称为空指针。
func main() {
var p *string
fmt.Println(p)
fmt.Printf("p的值是%s/n", p)
if p != nil {
fmt.Println("非空")
} else {
fmt.Println("空值")
}
}
5.4. new和make
new它是一个内置的函数,它的函数签名: func new(Type) *Type
其中,
1.Type表示类型,new函数只接受一个参数,这个参数是一个类型
2.*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。举个例子:
func main() {
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
}
如var a *int只是声明了一个指针变量a但是没有初始化,指针作为引用类型需要初始化后才会拥有内存空间,才可以给它赋值。应该按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:
func main() {
var a *int
a = new(int)
*a = 10
fmt.Println(*a)
}
make:
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:
func make(t Type, size ...IntegerType) Type
make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。
var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:
func main() {
var b map[string]int
b = make(map[string]int, 10)
b["测试"] = 100
fmt.Println(b)
}
5.5. new与make的区别
1.二者都是用来做内存分配的。
2.make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
3.而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
6. Map
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
6.1. map定义
Go语言中 map的定义语法如下 map[KeyType]ValueType
其中,KeyType:表示键的类型, ValueType:表示键对应的值的类型。
map类型的变量默认初始值为nil,需要使用make()函数来分配内存。语法为:
make(map[KeyType]ValueType, [cap])
其中cap表示map的容量,该参数虽然不是必须的,但是我们应该在初始化map的时候就为其指定一个合适的容量。
var result = make(map[string]interface{})
6.2. map基本使用
map中的数据都是成对出现的,map的基本使用示例代码如下:
全局:
var result = make(map[string]interface{})
func main() {
//局部
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)
}
输出:
map[小明:100 张三:90]
100
type of a:map[string]int
map也支持在声明的时候填充元素,例如:
func main() {
userInfo := map[string]string{
"username": "pprof.cn",
"password": "123456",
}
fmt.Println(userInfo) //
}
6.3. 判断某个键是否存在
Go语言中有个判断map中键是否存在的特殊写法,格式如下:
value, ok := map[key]
举个例子:
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
v, ok := scoreMap["张三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查无此人")
}
}
6.4. map的遍历
Go语言中使用for range遍历map。
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
for k, v := range scoreMap {
fmt.Println(k, v)
}
}
但我们只想遍历key的时候,可以按下面的写法:
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
for k := range scoreMap {
fmt.Println(k)
}
}
注意: 遍历map时的元素顺序与添加键值对的顺序无关。
6.5. 使用delete()函数删除键值对
使用delete()内建函数从map中删除一组键值对,delete()函数的格式如下:
delete(map, key)
其中,
map:表示要删除键值对的map
key:表示要删除的键值对的键
示例代码如下:
func main(){
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
delete(scoreMap, "小明")//将小明:100从map中删除
for k,v := range scoreMap{
fmt.Println(k, v)
}
}
6.6值为inteface的map
var result = make(map[string]interface{}) //值是inteface表示值可以是任意类型的
var bb = map[string]interface{}{
"name": "1111",
"age": 11,
}
6.7. 元素为map类型的切片
下面的代码演示了切片中的元素为map类型时的操作:
func main() {
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "王五"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "红旗大街"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
}
6.8. 值为切片类型的map
下面的代码演示了map中值为切片类型的操作:
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap)
}
7.结构体
7.1. 自定义类型
在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:
//将MyInt定义为int类型
type MyInt int
通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。
7.2. 结构体
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。
Go语言中通过struct来实现面向对象。
7.2.1. 结构体的定义
使用type和struct关键字来定义结构体,具体代码格式如下:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
...
}
其中:
1.类型名:标识自定义结构体的名称,在同一个包内不能重复。
2.字段名:表示结构体字段名。结构体中的字段名必须唯一。
3.字段类型:表示结构体字段的具体类型。
同样类型的字段也可以写在一行,
type person1 struct {
name, city string
age int8
}
7.2.2. 结构体实例化
只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。
结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
var 结构体实例 结构体类型
7.2.3. 基本实例化
type person struct {
name string
city string
age int8
}
func main() {
var p1 person
p1.city = "北京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //p1={pprof.cn 北京 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"pprof.cn", city:"北京", age:18}
}
我们通过.来访问结构体的字段(成员变量),例如p1.name和p1.age等。
7.2.3. 匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package main
import (
"fmt"
)
func main() {
var user struct{Name string; Age int}
user.Age = 18
fmt.Printf("%#v\n", user)
}
7.2.1. 创建指针类型结构体
我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:
var p2 = new(person)
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
从打印的结果中我们可以看出p2是一个结构体指针。
需要注意的是在Go语言中支持对结构体指针直接使用.来访问结构体的成员。
var p2 = new(person)
p2.name = "测试"
p2.age = 18
p2.city = "北京"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"测试", city:"北京", age:18}
7.2.2. 取结构体的地址实例化
使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。
p3 := &person{}
fmt.Printf("%T\n", p3) //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "博客"
p3.age = 30
p3.city = "成都"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}
p3.name = "博客"其实在底层是(*p3).name = "博客",这是Go语言帮我们实现的语法糖。
7.2.3. 结构体初始化
type person struct {
name string
city string
age int8
}
func main() {
var p4 person
fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}
7.2.4. 使用键值对初始化
使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。
p5 := person{
name: "pprof.cn",
city: "北京",
age: 18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}
也可以对结构体指针进行键值对初始化,例如:
p6 := &person{
name: "pprof.cn",
city: "北京",
age: 18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18}
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。
p7 := &person{
city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}
7.2.5. 使用值的列表初始化
初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
p8 := &person{
"pprof.cn",
"北京",
18,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}
使用这种格式初始化时,需要注意:
1.必须初始化结构体的所有字段。
2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。
3.该方式不能和键值初始化方式混用。
7.2.6. 结构体内存布局
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
输出:
n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063
7.2.7. 构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
func newPerson(name, city string, age int8) *person {
return &person{
name: name,
city: city,
age: age,
}
}
调用构造函数
p9 := newPerson("pprof.cn", "测试", 90)
fmt.Printf("%#v\n", p9)
7.2.8. 方法和接收者
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。
方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
其中,
1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
2.接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
3.方法名、参数列表、返回参数:具体格式与函数定义相同。
举个例子:
//Person 结构体
type Person struct {
name string
age int8
}
//NewPerson 构造函数
func NewPerson(name string, age int8) *Person {
return &Person{
name: name,
age: age,
}
}
//Dream Person做梦的方法
func (p Person) Dream() {
fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}
func main() {
p1 := NewPerson("测试", 25)
p1.Dream()
}
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
7.2.9. 指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
调用该方法:
func main() {
p1 := NewPerson("测试", 25)
fmt.Println(p1.age) // 25
p1.SetAge(30)
fmt.Println(p1.age) // 30
}
7.2.10. 值类型的接收者
当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("测试", 25)
p1.Dream()
fmt.Println(p1.age) // 25
p1.SetAge2(30) // (*p1).SetAge2(30)
fmt.Println(p1.age) // 25
}
7.2.11. 什么时候应该使用指针类型接收者
1.需要修改接收者中的值
2.接收者是拷贝代价比较大的大对象
3.保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
7.2.12. 任意类型添加方法
在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。
//MyInt 将int定义为自定义MyInt类型
type MyInt int
//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
fmt.Println("Hello, 我是一个int。")
}
func main() {
var m1 MyInt
m1.SayHello() //Hello, 我是一个int。
m1 = 100
fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt
}
注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。
7.2.13. 结构体的匿名字段
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
//Person 结构体Person类型
type Person struct {
string
int
}
func main() {
p1 := Person{
"pprof.cn",
18,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"pprof.cn", int:18}
fmt.Println(p1.string, p1.int) //pprof.cn 18
}
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。
7.2.14. 嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针。
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address Address
}
func main() {
user1 := User{
Name: "pprof",
Gender: "女",
Address: Address{
Province: "黑龙江",
City: "哈尔滨",
},
}
fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
7.2.15. 嵌套匿名结构体
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address //匿名结构体
}
func main() {
var user2 User
user2.Name = "pprof"
user2.Gender = "女"
user2.Address.Province = "黑龙江" //通过匿名结构体.字段名访问
user2.City = "哈尔滨" //直接访问匿名结构体的字段名
fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
7.2.16. 嵌套结构体的字段名冲突
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
//Address 地址结构体
type Address struct {
Province string
City string
CreateTime string
}
//Email 邮箱结构体
type Email struct {
Account string
CreateTime string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address
}
func main() {
var user3 User
user3.Name = "pprof"
user3.Gender = "女"
// user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
user3.Email.CreateTime = "2000" //指定Email结构体中的CreateTime
}
7.2.17. 结构体的"继承"
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
//Animal 动物
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}
func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
7.2.18. 结构体字段的可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
7.2.19. 结构体与JSON序列化
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
//Student 学生
type Student struct {
ID int
Gender string
Name string
}
//Class 班级
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{
Title: "101",
Students: make([]*Student, 0, 200),
}
for i := 0; i < 10; i++ {
stu := &Student{
Name: fmt.Sprintf("stu%02d", i),
Gender: "男",
ID: i,
}
c.Students = append(c.Students, stu)
}
//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
if err != nil {
fmt.Println("json marshal failed")
return
}
fmt.Printf("json:%s\n", data)
//JSON反序列化:JSON格式的字符串-->结构体
str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Printf("%#v\n", c1)
}
7.2.20. 结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义json序列化时使用的Tag:
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
func main() {
s1 := Student{
ID: 1,
Gender: "女",
name: "pprof",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"女"}
}
7.2.21 类型别名
类型别名是Go1.9版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型
我们之前见过的rune和byte就是类型别名,他们的定义如下:
type byte = uint8
type rune = int32
7.3.22 类型定义和类型别名的区别
类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
//类型定义
type NewInt int
//类型别名
type MyInt = int
func main() {
var a NewInt
var b MyInt
fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}
结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。
8.流程控制
8.1.循环语句for
s := "abcd"
for i, n := 0, length(s); i < n; i++ { // 避免多次调用 length 函数。
println(i, s[i])
}
8.2循环语句range
Golang range类似迭代器操作,返回 (索引, 值) 或 (键, 值)。
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
可忽略不想要的返回值, "_" 这个特殊变量。
s := "abc"
// 忽略 index。
for _, c := range s {
println(c)
}
8.3switch 语句
switch默认的条件:bool=true
fallthrough(穿透 )会强制执行后面的case语句,不管下一条表达式的结果是否为true
8.4循环控制Goto、Break、Continue
Goto、Break、Continue:
break 跳出后不会再进入循环
continue 跳出后会在进入循环,但不执行初始化
goto 则是调整执行位置,相当于代码跳到L的位置再次执行
9.函数:
函数是引用传递:
普通函数和高阶函数:
9.1.defer延迟调用:
defer特性:
-
关键字 defer 用于注册延迟调用。
-
这些调用直到 return 前才被执。因此,可以用来做资源清理。
-
多个defer语句,按先进后出的方式执行。
-
defer语句中的变量,在defer声明时就决定了。
defer用途:
-
关闭文件句柄
-
锁资源释放
-
数据库连接释放
9.2. 内置函数
Go 语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap 和 append,或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。
append -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭channel
delete -- 从map中删除key对应的value
panic -- 停止常规的goroutine (panic和recover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
imag -- 返回complex的实部 (complex、real imag:用于创建和操作复数)
real -- 返回complex的虚部
make -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
new -- 用来分配内存,主要用来分配值类型,比如int、struct。返回指向Type的指针
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
copy -- 用于复制和连接slice,返回复制的数目
len -- 来求长度,比如string、array、slice、map、channel ,返回长度
print、println -- 底层打印函数,在部署环境中建议使用 fmt 包
10.数据类型转换:
10.1类型转换是将一种数据类型的变量转为另一种类型的变量
Go强制要求使用显式类型转换。所以语法更能确定语句及表达式的明确含义
转换的时候如果大的转给小的,会有精度损失(数据溢出)比如int64转int8
转换格式:
// 将v转成T类型,但是v本身的数据类型并不会改变,只是把v变量的值类型转成T
表达式 T(v)
如: string(data) int(a)
var a int32 = 1999999 // 小转大一样要显示转换
var b float64 = float64(a) // a转b a本身数据类型并不会改变,只是把a的值(1999999)转成了float64
10.2基本类型转string:
(1)Sprint和Sprintf()
fmt.Sprintf():格式化为字符串
fmt.Sprint():格式化为字符串
Sprintf和printf的区别:printf是将一个格式化的字符串打印到控制台,Sprintf是转换为字符串
格式:
接收变量 = fmt.Sprintf(%格式符,参数列表)
接收变量 = fmt.Sprint(参数)
上述的参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。
如:
var (
num1 int = 9
num2 float64 = 9.99
b bool = false
c byte = 'a'
str string
)
str = fmt.Sprintf("%d", num1)
//str的类型: string 值:9
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
str = fmt.Sprintf("%f", num2)
//str的类型: string 值:9.990000
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
str = fmt.Sprintf("%t", b)
//str的类型: string 值:false
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
str = fmt.Sprintf("%c", c)
//str的类型: string 值:a
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
(2)strconv.Format方式:
strconv.Itoa可以将数字转换成字符串类型的数字
var (
num int = 24
num2 float64 = 1.111
str string
)
// FormatInt参数1:要转的变量 参数2:进制
str = strconv.FormatInt(int64(num), 10)
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
// strconv.FormatInt也可以用来转换进制,比如将10进制转换为2进制,其它进制,换掉后面的数字就可以了
str = strconv.FormatInt(123, 2)
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
// 'f':格式 10:保留10位 64:float64
str = strconv.FormatFloat(num2, 'f', 10, 64)
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
str = strconv.FormatBool(false)
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
str = strconv.Itoa(num)
fmt.Printf("str的类型: %T\t 值:%v\n ", str, str)
输出:
str的类型: string 值:24
str的类型: string 值:1111011
str的类型: string 值:1.1110000000
str的类型: string 值:false
str的类型: string 值:24
10.3string转基本类型:
将string类型转换成基本数据类型时,要确保string类型能够转成有有效的数据
例:可以把"123"转成int类型,但是不可以将"aaa"转成int类型,编译器不会报错,go会把它变成默认值0,因为go会判断这个值能不能转成有效的数据,如果不可以会按照该数据的数据类型的默认值赋值。
strconv.ParseInt:
func main() {
var (
str string = "123"
i int64 // 这里只能用int64
f float64
b bool
)
// str:字符串base:进制bitSize:数据类型
i, _ = strconv.ParseInt(str, 10, 64)
fmt.Printf("i的类型: %T\t 值:%v\n ", i, i)
f, _ = strconv.ParseFloat(str, 64)
fmt.Printf("f的类型: %T\t 值:%v\n ", f, f)
b, _ = strconv.ParseBool(str)
fmt.Printf("b的类型: %T\t 值:%v\n ", b, b)
s, _ := strconv.Atoi("str")
fmt.Printf("s的类型: %T\t 值:%v\n ", s, s)
}
输出:
i的类型: int64 值:123
f的类型: float64 值:123
b的类型: bool 值:false
s的类型: int 值:0
10.4 inteface{}转字符串
使用**.(类型)** 并在括号中传入想要解析的任何类型
格式: 变量名.(要转换的类型)
如name**.(string)** 将interface{}类型的 name转换成string
var daName interface{}
fmt.Println(daName.(string))
注:若不确定interface类型时候,使用变量名.(type)结合switch case来做判断。
switch value.(type) {
case string:
// 将interface转为string字符串类型
op, ok := value.(string)
fmt.Println(op, ok)
case int32:
// 将interface转为int32类型
op, ok := value.(int32)
fmt.Println(op, ok)
case int64:
// 将interface转为int64类型
op, ok := value.(int64)
fmt.Println(op, ok)
case User:
// 将interface转为User struct类型,并使用其Name对象
op, ok := value.(User)
fmt.Println(op.Name, ok)
case []int:
// 将interface转为切片类型
op := make([]int, 0) //[]
op = value.([]int)
fmt.Println(op)
default:
fmt.Println("unknown")
}
10.5字符串转字节切片
func main() {
// 字符串转切片
var b = []byte ("aaaaa")
fmt.Println("b = ", b)
// 切片转字符串
var str = string( []byte{97, 98, 99})
fmt.Println("str = ", str)
}
输出:b = [105 116 122 104 117 122 104 117]
str = abc
byte类型的切片([]byte)与string可以相互转换:
如:
s:="上海"
bslice := []byte(s)
fmt.Printf("bslice的类型是: %T,值是:%v", bslice,bslice) //得到
[]byte转换成string:
10.6 string 与 int 类型之间的转换:
10.6.1 strconv包
1) Itoa():整型转字符串
func Itoa(i int) string
示例代码如下:
func main() {
num := 100
str := strconv.Itoa(num)
fmt.Printf("type:%T value:%#v\n", str, str)
}
运行结果如下所示:
type:string value:"100"
2) Atoi():字符串转整型
func Atoi(s string) (i int, err error)
func main() {
str1 := "110"
str2 := "s100"
num1, err := strconv.Atoi(str1)
if err != nil {
fmt.Printf("%v 转换失败!", str1)
} else {
fmt.Printf("type:%T value:%#v\n", num1, num1)
}
num2, err := strconv.Atoi(str2)
if err != nil {
fmt.Printf("%v 转换失败!", str2)
} else {
fmt.Printf("type:%T value:%#v\n", num2, num2)
}
}
10.6.2 Parse 系列函数
Parse 系列函数用于将字符串转换为指定类型的值,其中包括 ParseBool()、ParseFloat()、ParseInt()、ParseUint()。
1) ParseBool() 函数用于将字符串转换为 bool 类型的值,它只能接受 1、0、t、f、T、F、true、false、True、False、TRUE、FALSE,其它的值均返回错误,函数签名如下。
func ParseBool(str string) (value bool, err error)
2) ParseInt()
ParseInt() 函数用于返回字符串表示的整数值(可以包含正负号),函数签名如下:
func ParseInt(s string, base int, bitSize int) (i int64, err error)
参数说明:
base 指定进制,取值范围是 2 到 36。如果 base 为 0,则会从字符串前置判断,"0x"是 16 进制,"0"是 8 进制,否则是 10 进制。
bitSize 指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64。
返回的 err 是 *NumErr 类型的,如果语法有误,err.Error = ErrSyntax,如果结果超出类型范围 err.Error = ErrRange。
11.fmt常用输出
11.1 P rint、 P rintln 和 P rintf
Print 和 Println :用于将数据输出到标准输出(通常是终端)。
Print 函数输出数据后不换行
Println 函数输出数据后会自动换行。
Printf :用于将格式化的数据输出到标准输出。它使用类似 C 语言的格式化字符串,可以通过占位符指定输出数据的格式和位置。
11.2 Sprint、Sprintln 和 Sprintf
Sprint 、 Sprintln 和 Sprintf 是 Go 语言 fmt 包提供的函数,用于将格式化的数据转换为字符串。
这些函数的命名类似于 Print 、 Println 和 Printf 函数,但是它们不是将数据输出到标准输出,而是将数据格式化为字符串并返回。
Sprint 函数将传入的数据格式化为字符串,并返回该字符串。它接受可变数量的参数,并按照指定的格式进行格式化。
Sprintln 函数与 Sprint 函数类似,但在每个参数之间会添加一个空格,并在最后添加一个换行符( \n )。
Sprintf 函数根据指定的格式将传入的数据格式化为字符串,并返回该字符串。它接受一个格式化字符串作为第一个参数,然后根据该格式化字符串和后续的参数进行格式化 。
如:
var name = "Alice"
var age = 25
var height = 1.65
// 使用 Sprint 格式化为字符串,并赋值给变量
var info1 = fmt.Sprint("Name:", name, ", Age:", age, ", Height:", height)
fmt.Println(info1)
// 使用 Sprintln 格式化为字符串,并赋值给变量
var info2 = fmt.Sprintln("Name:", name, ", Age:", age, ", Height:", height)
fmt.Println(info2)
// 使用 Sprintf 进行格式化,并赋值给变量
var info3 = fmt.Sprintf("Name: %s, Age: %d, Height: %.2f", name, age, height)
fmt.Println(info3)
11.3Fprint、Fprintln 和 Fprintf
Fprint 、 Fprintln 和 Fprintf 是 Go 语言 fmt 包提供的函数,用于将格式化的数据输出到指定的 io.Writer。
这些函数与 Print 、 Println 和 Printf 函数类似,但是它们不是将数据输出到标准输出,而是将数据格式化后输出到指定的 io.Writer,例如文件、网络连接等。
· Fprint 函数将传入的数据格式化为字符串,并将结果输出到指定的 io.Writer。它接受一个 io.Writer 类型的参数作为第一个参数,后面可以跟上可变数量的参数。
· Fprintln 函数与 Fprint 函数类似,但在每个参数之间会添加一个空格,并在最后添加一个换行符( \n )。
· Fprintf 函数根据指定的格式将传入的数据格式化为字符串,并将结果输出到指定的 io.Writer。它接受一个 io.Writer 类型的参数作为第一个参数,后面跟着一个格式化字符串和后续的参数。
fmt.Scan()
fmt.Scanf()
fmt.Scanln()
12.go语言中常见占位符含义:
%d:十进制整数。
%f:浮点数。
%s:字符串。
%t:布尔值。
%T:数据的类型
%p 输出 指针地址 十六进制表示
%v:通用格式,默认格式化为相应值的字符串 。
%+v :获取数据的值,如果结构体,会携带字段名。
%#v:获取数据的值,如果是结构体,会携带结构体名和字段名。
%b 一个二进制整数,将一个整数格式转化为二进制的表达方式
%c 一个Unicode的字符
%d 十进制整数
%s字符串或字节切片。
%o 八进制整数
%x 小写的十六进制数值
%X 大写的十六进制数值
%U 一个Unicode表示法表示的整型码值
%s 输出以原生的UTF8字节表示的字符,如果console不支持utf8编码,则会乱码
%t 以true或者false的方式输出布尔值
%v 使用默认格式输出值,或者如果方法存在,则使用类性值的String()方法输出自定义值
%% 字面上的一个百分号
%b 二进制
%c Unicode 码转字符。
fmt. Printf ( "%c" , 0x82d7 ) // 输出 苗
%d、%5d、%-5d、%05d 十进制整数表示。
fmt. Printf ( "%d,%d,%d" , 10 , 010 , 0x10 ) // 输出 10 , 8 , 16
三个数据: 10 十进制,010 八进制,0x10 十六进制
%q 同 %c 类似,都是Unicode 码转字符,只是结果多了单引号。
fmt. Printf ( "%q" , 0x82d7 ) // 输出 '苗'
汉字对应表: 字体编辑用中日韩汉字Unicode编码表 - 编著:资深中韩翻译金圣镇 金圣镇
%x、%#x 十六进制表示,字母形式为小写 a-f,%#x 输出带 0x 开头。
fmt. Printf ( "%x, %#x" , 13 , 13 ) // 输出 d, 0xd
%X、%#X十六进制表示,字母形式为小写 A-F,%#X 输出带 0X 开头。
fmt. Printf ( "%X, %#X" , 13 , 13 ) // 输出 D, 0XD
%U:转化为 Unicode 格式规范。
fmt. Printf ( "%U" , 0x82d7 ) // 输出 U+ 82 D7
%#U:转化为 Unicode 格式并带上对应的字符。
fmt. Printf ( "%#U" , 0x82d7 ) // 输出 U+ 82 D7 '苗'
%b 浮点数转化为 2 的幂的科学计数法。
fmt. Printf ( "%b" , 0.1 ) // 输出 7205759403792794 p- 56
%f、%.2f 等等
浮点数,%.2f 表示保留 2 位小数,%f 默认保留 6 位,%f 与 %F 等价。
保留的规则我现在还没有搞清楚,有时候符合四舍五入,有时候不符合,容我下来研究下,再告诉大家。
fmt. Printf ( "%f" , 10.2 ) // 输出 10.200000
fmt. Printf ( "%.2f|%.2f" , 10.232 , 10.235 ) // 输出 10.23 | 10.23
%q 有 Go 语言安全转义,双引号包裹。
fmt. Printf ( "%q" , "老苗" ) // 输出 "老苗"
指针%p、%#p :地址,使用十六进制表示,%p 带 0x,%#p 不带。
num := 2 s := [] int { 1 , 2 }fmt. Printf ( "%p|%p" , &num, s)
// 输出 0xc00000a1d0 | 0xc00000a1e0
13.常用
对切片进行排序(string类型的切片): sort.Strings(slice)
//对切片进行排序(string类型的切片)
var slice = []string{"a", "f", "d"}
sort.Strings(slice)
fmt.Println(slice) //[a d f]
判断字符串中是否含有某字符:
判断字符串是否以prefix开头 :strings.HasPrefix(str, "he")
s := "hello world hello world"
//判断字符串s是否以prefix开头
ret := strings.HasPrefix(s, "he")
fmt.Println(ret)