go语言基础笔记

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 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。

  1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。

  2. 切片的长度可以改变,因此,切片是一个可变的数组。

  3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。

  4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。

  5. 切片的定义:var 变量名 []类型,比如 var str []string var arr []int。

  6. 如果 slice == nil,那么 len、cap 结果都等于 0。

4.1 创建切片的各种方式

package main

import "fmt"

func main() {

//1.声明切片

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.name = "pprof.cn"

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.Name = "pprof.cn"

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

Email

}

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特性:

  1. 关键字 defer 用于注册延迟调用。

  2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。

  3. 多个defer语句,按先进后出的方式执行。

  4. defer语句中的变量,在defer声明时就决定了。

defer用途:

  1. 关闭文件句柄

  2. 锁资源释放

  3. 数据库连接释放

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)

相关推荐
童先生2 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*4 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
架构师那点事儿9 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
于顾而言1 天前
【笔记】Go Coding In Go Way
后端·go
qq_172805591 天前
GIN 反向代理功能
后端·golang·go
follycat1 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter1 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈
探索云原生1 天前
GPU 环境搭建指南:如何在裸机、Docker、K8s 等环境中使用 GPU
ai·云原生·kubernetes·go·gpu
OT.Ter2 天前
【力扣打卡系列】移动零(双指针)
算法·leetcode·职场和发展·go
码财小子2 天前
k8s 集群中 Golang pprof 工具的使用
后端·kubernetes·go