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)

相关推荐
煎鱼eddycjy5 小时前
新提案:由迭代器启发的 Go 错误函数处理
go
煎鱼eddycjy5 小时前
Go 语言十五周年!权力交接、回顾与展望
go
不爱说话郭德纲1 天前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
秋落风声3 天前
【滑动窗口入门篇】
java·算法·leetcode·go·哈希表
0x派大星5 天前
【Golang】——Gin 框架中的模板渲染详解
开发语言·后端·golang·go·gin
0x派大星5 天前
【Golang】——Gin 框架中的表单处理与数据绑定
开发语言·后端·golang·go·gin
三里清风_6 天前
如何使用Casbin设计后台权限管理系统
golang·go·casbin