1. 数字

1.1. 整形

1.1.1. 常用的整形数据类型

Go的整形分为两大类:有符号和无符号两类,每一类分别由8、16、32、64位整形组成,一般无特殊情况,开发中直接使用int较多,因为不用涉及类型转换。

类型 描述 范围
uint8 无符号的8位整形 [0,2^8-1] —> [0,255]
uint16 无符号的16位整形 [0,2^16-1] —> [0,65535]
uint32 无符号的32位整形 [0,2^32-1] —-> [0,4294967295‬]
uint64 无符号的64位整形 [0,2^64-1] —-> [0,18446744073709551615‬]
int8 有符号的8位整形 [-2^7,2^7-1] —-> [-128,127]
int16 有符号的16位整形 [-2^15,2^15-1] —> [-32768,32767]
int32 有符号的32位整形 [-2^31,2^31-1] —-> [-2147483648,2147483647]
int64 有符号的64位整形 [-2^63,2^63-1] —-> [-9223372036854775808,9223372036854775807]
类型 描述
uint 无符号的整形,32位操作系统最大值位2^32,64位操作系统位2^64,不建议跨平台使用
int 有符号的整形,32位操作系统位int32,64位操作系统位int64。默认整数类型为int
uinptr 无符号整形,用于存放指针

1.1.2. 进制转换

自从Go 1.13之后,引入了数字字面量语法,这样方便直接定义不同进制的数字:

进制 定义方式 对应十进制数字 对应 fmt.Printf() 中符号
二进制 0b110 0100 100 %b
八进制 0o144 100 %o
十进制 100 100 %d
十六进制 0x64 100 %x

1.1.3. 案例

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 十进制转换
  5. n1 := 100
  6. fmt.Printf("bin:%b; oct:%o; hex:%x\n", n1, n1, n1)
  7. // 二进制转换
  8. var n2 int8
  9. n2 = 0b1100100
  10. fmt.Printf("oct:%o; dec:%d; hex:%x\n", n2, n2, n2)
  11. // 八进制转换
  12. var n3 uint8 = 0o144
  13. fmt.Printf("bin:%b; dec:%d; hex:%x\n", n3, n3, n3)
  14. // 十六进制
  15. var n4 = 0x64
  16. fmt.Printf("bin:%b; oct:%o; dec:%d\n", n4, n4, n4)
  17. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\02-num>go run main.go
  2. bin:1100100; oct:144; hex:64
  3. oct:144; dec:100; hex:64
  4. bin:1100100; dec:100; hex:64
  5. bin:1100100; oct:144; dec:100

1.2. 浮点数

Go语言支持两种类型的浮点数,float32和float64,没有float类型!其范围如下:

浮点数类型 大小 最大值(常量)
float32 约为3.4x10^38,即3.4e38 math.MaxFloat32
float64 约为1.8x10^308,即1.8e308。默认float类型 math.MaxFloat64
  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. func main() {
  7. f1,f2 := 1001.0,1e3
  8. fmt.Println("f1-f2 =", f1-f2)
  9. fmt.Printf("%.2f\n", math.Pi)
  10. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\02-num>go run main.go
  2. f1-f2 = 1
  3. 3.14

2. 布尔值

Go中的bool与Python不一样,Python中True的值1,False的值为0,而Go中true和false为单独的数据类型,不能与整形进行转换和运算!默认的布尔值为false.


3. 字符串

3.1. Go中字符串定义

3.1.1. 自定义字符串

Go语言中字符串必须用双引号 " " 或者反引号 `` 引用起来,**不能使用单引号**,单引号为字符!<br />Go语言内部使用的是UTF-8编码,因此可以直接定义中文字符串而不需要声明他的类型为UTF-8。Go语言的字符串中涉及特殊字符,如换行符、制表符、单引号、双引号、反斜线等,转义方式和其它语言一致。 `` 中的所有字符都以文本格式表示,不涉及任何转义。

3.1.2. 字符串案例

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 打印windowns中路径
  5. s1 := "e:\\OneDrive\\Projects\\Go\\src\\gitee.com\\studygo\\day01\\03-strings"
  6. s2 := `e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings`
  7. fmt.Println("path:", s1)
  8. fmt.Println("path:", s2)
  9. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
  2. path: e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings
  3. path: e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings

3.1.3. 字符串的数据结构

一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。
每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。字符串的底层是一个结构体!因此不同长度的和内容的字符串变量可以相互赋值,因为赋值的是结构体。也因为如此,在传值或者赋值时,不需要使用指针替代字符串本身。

  1. type StringHeader struct {
  2. Data uintptr // 指向底层字节数组的指针
  3. Len int // 字符串的字节长度
  4. }

3.2. 字符

3.2.1. 字符和字符串

Go中的字符本质是字符切片组成的,字符可以是一个字母、符号或者汉字。而根据字符的编码方式,即ASCII和Unicode,将字符类型分为两类,在实际使用中,如果涉及到中文就不能用byte方式处理,否则会因为字节切割异常导致乱码。
查询UTF-8编码网页: http://www.mytju.com/classcode/tools/encode_utf8.asp

字符类型 描述
byte ASCII码表中数字,实际上是uint8类型的别名
rune UTF-8码表中数字,实际是int32类型的别名

3.2.2. 字符串拆分成字节串

  1. package main
  2. import "fmt"
  3. func main() {
  4. var c0 byte = 'a' // 指定byte类型的字符,即 Ascii 格式编码,底层为uint8类型
  5. var c1 rune = 'a' // 指定rune类型的字符,即 UTF-8 格式编码,底层为int32类型
  6. var c2 = 'a' // 不指定类型则为 rune 类型的字符
  7. fmt.Printf("%T %v %c\n", c0, c0, c0)
  8. fmt.Printf("%T %v %c\n", c1, c1, c1)
  9. fmt.Printf("%T %v %c\n", c2, c2, c2)
  10. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
  2. uint8 97 a
  3. int32 97 a
  4. int32 97 a

3.2.3. 字符串的本质

string的底层是一个字节数组,因此可以 string,[]byte,[]rune 可以进行转换,字符串也因此支持切片操作。

  1. package main
  2. import "fmt"
  3. func main() {
  4. str0 := "Hello world!"
  5. str1 := "你好,世界!"
  6. fmt.Printf("str0 --> []byte:%v, str0 --> []rune:%v\n", []byte(str0), []rune(str0))
  7. fmt.Printf("str1 --> []byte:%v, str1 --> []rune:%v\n", []byte(str1), []rune(str1))
  8. // []byte和[]rune转字符串
  9. s0 := []byte{72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33}
  10. fmt.Println("s0=", string(s0))
  11. s1 := []rune{20320, 22909, 65292, 19990, 30028, 65281}
  12. fmt.Println("s1=", string(s1))
  13. // 修改字符串中的字符: "Hello world!" --> "Hello World!"
  14. tmpByte := []byte(str0)
  15. tmpByte[6] = 'W'
  16. fmt.Println(string(tmpByte))
  17. }
  1. [root@heyingsheng 10-slice]# go run str_byte.go
  2. str0 --> []byte:[72 101 108 108 111 32 119 111 114 108 100 33], str0 --> []rune:[72 101 108 108 111 32 119 111 114 108 100 33]
  3. str1 --> []bye:[228 189 160 229 165 189 239 188 140 228 184 150 231 149 140 239 188 129], str1 --> []rune:[20320 22909 65292 19990 30028 65281]
  4. s0= Hello world!
  5. s1= 你好,世界!
  6. Hello World!

3.3. 常见的字符串操作方式

字符类型 描述
len(s) 求长度,返回的类型为 int。针对字符串而言,取得是字节数
utf8.RuneCountInString 按UTF-8编码格式计算字符串长度
+或fmt.Sprintf 拼接字符串,其中fmt.Sprintf是返回拼接后的字符串
strings.Split 分割字符串
strings.Contains 判断字符串中是否包含某一段字符串
strings.HasPrefix 判断字符串开头
strings.HasSuffix 判断字符串结尾
strings.Index 从前向后匹配,取第一次匹配到的字符串开头索引,-1表示无法匹配子串
strings.LastIndex 从后向前匹配,取最后一次匹配到的字符串开头索引,-1表示无法匹配子串
strings.Join 以特定的字符拼接切片

3.4. 案例

3.4.1. 求字符串长度

在Go语言中,字符串是以UTF-8编码方式,而在UTF-8编码下,不同类型的字符对应的长度又不一样,导致中文和英文字符串的长度与预期有差异:

  1. package main
  2. import (
  3. "fmt"
  4. "unicode/utf8"
  5. )
  6. func main() {
  7. s0, s1 := "hello world", "你好,世界"
  8. fmt.Printf("s0:%d\ts1:%d\n", len(s0), len(s1)) // len()对string类型求的长度为字节数量
  9. fmt.Printf("s0:%d\ts1:%d\n", utf8.RuneCountInString(s0), utf8.RuneCountInString(s1)) // 正确计算utf-8字符串长度方式
  10. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
  2. s0:11 s1:15
  3. s0:11 s1:5

3.4.2. 截取字符串

在对字符串操作时,字符串截取很常用,但是在Go中,对字符截取比较繁琐,因为索引是按照字节来计算的,而不是字符数量。

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func main() {
  7. var s0 string = "中国加油,武汉加油,黄冈加油!"
  8. // 截取 s0 中逗号之后的内容
  9. start := strings.Index(s0, ",") + len(",")
  10. fmt.Println(s0[start:], start)
  11. // 截取最后一个"加油"字符串到末尾的值
  12. start = strings.LastIndex(s0, "加油")
  13. fmt.Println(s0[start:], start)
  14. // 截取第二个"加油"字符串,并打印索引
  15. indexOne := strings.Index(s0, "加油") + len("加油") // 第一次"加油"的结束索引
  16. indexTwo := indexOne + strings.Index(s0[indexOne:], "加油") // 第二次"加油"的开始索引
  17. indexThree := indexTwo + len("加油") // 第二次加油的结束索引
  18. fmt.Println(s0[indexTwo:indexThree], indexTwo, indexThree)
  19. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
  2. 武汉加油,黄冈加油! 15
  3. 加油! 36
  4. 加油 21 27

3.4.3. 修改字符串

字符串是不可变数据类型,无法直接修改,一般是构造新的字符串重新赋值给这个变量!

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func main() {
  7. var s1 string = "gzip: compress log content in buffer,then write to disk." // 修改gzip为Gzip
  8. startIndex := strings.Index(s1, "gzip") + len("gzip")
  9. s1 = "Gzip" + s1[startIndex:]
  10. fmt.Println(s1)
  11. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
  2. Gzip: compress log content in buffer,then write to disk.

3.4.4. 字符串反转和回文判断

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func check(str string) {
  7. s1 := strings.Split(str, "")
  8. s2 := make([]string, 0, len(s1))
  9. for i := len(s1) - 1; i >= 0; i-- {
  10. s2 = append(s2, s1[i])
  11. }
  12. reverseStr := strings.Join(s2, "")
  13. if str == reverseStr {
  14. fmt.Printf("%v --> 是回文\n", str)
  15. } else {
  16. fmt.Printf("%v --> 不是回文\n", str)
  17. }
  18. }
  19. func main() {
  20. check("上海自来水来自海上")
  21. check("上海自来水来自海上x")
  22. check("x上海自来水来自海上x")
  23. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\01-homework>go run main.go
  2. 上海自来水来自海上 --> 是回文
  3. 上海自来水来自海上x --> 不是回文
  4. x上海自来水来自海上x --> 是回文

3.4.5. 字符串拼接

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func repeatStrings(s string, c int) {
  7. fmt.Println("strings.Repeat:", strings.Repeat(s, c)) // 重复一个指定的字符串,效率非常高
  8. }
  9. func addStrings(s string, c int) {
  10. var res string
  11. for i:=0;i<c;i++ {
  12. res += s // 使用 + 也能实现,方法简单,但是效率低。
  13. }
  14. fmt.Println("+:", res)
  15. }
  16. func builderStrings(s string, c int) {
  17. var res strings.Builder
  18. for i:=0;i<c;i++ {
  19. res.WriteString(s)
  20. }
  21. fmt.Println("+:", res.String())
  22. }
  23. func main() {
  24. repeatStrings("x", 20)
  25. addStrings("x", 20)
  26. builderStrings("x", 20)
  27. }

4. 指针

指针就是内存地址,是16进制的一个数字,Go语言中的指针不能进行偏移和运算,后期的函数和方法涉及到指针操作非常多,一般涉及三个方面:

  • 定义指针类型的变量
  • 取变量的内存地址: &name
  • 根据内存地址取出对应的值: *ptrName

image.png

4.1. 指针的操作

Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如: *int*int64*string 等。

4.1.1. 通过变量取指针

  1. package main
  2. import "fmt"
  3. func main() {
  4. var var1 string
  5. var var2 uint8 = 255
  6. var var3 = [8]int{7: 1}
  7. p1, p2, p3 := &var1, &var2, &var3
  8. fmt.Printf("p1 --> type:%T\tvalue:%v\n", p1, p1)
  9. fmt.Printf("p2 --> type:%T\tvalue:%v\n", p2, p2)
  10. fmt.Printf("p3 --> type:%T\tvalue:%v\n", p3, p3)
  11. }
  1. e:\OneDrive\Projects\Go\src\gitee.com\studygo\day02\04-mem-pointer>go run main.go
  2. p1 --> type:*string value:0xc0000881e0
  3. p2 --> type:*uint8 value:0xc0000a0068
  4. p3 --> type:*[8]int value:&[0 0 0 0 0 0 0 1]

4.1.2. 通过指针取值

  1. package main
  2. import "fmt"
  3. func main() {
  4. var var0 uint8 = 255
  5. var p0 *uint8 = &var0
  6. fmt.Printf("var0:%v p0:%v *p0:%v\n", var0, p0, *p0) // var0:255 p0:0xc0000100b0 *p0:255
  7. }

4.1.3. 空指针

  1. package main
  2. func main() {
  3. var a *int8 // panic: runtime error: invalid memory address or nil pointer dereference
  4. *a = 100 // 声明了指针变量却没有初始化,所以提示空指针
  5. }

4.2. make vs new

4.2.1. new案例

  1. package main
  2. import "fmt"
  3. func main() {
  4. a1 := new(int) // 初始化类型指针,该指针对应的地址为该类型的默认值,如int:0,bool:false
  5. *a1 = 255
  6. fmt.Printf("type:%T value:%v *a1:%v\n", a1, a1, *a1) // type:*int value:0xc0000100b0 *a1:255
  7. }

4.2.2. make案例

参考:https://www.yuque.com/duduniao/vnmzv5/lz5sgr#Zdim8

4.2.3. make和new对比

  • 二者都是用来做内存分配的。
  • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身,一般用于初始化
  • new用于内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针,一般用于创建指针类型的变量

    4.3. 值类型和引用类型

    4.3.1. 值类型

    值类型是指变量对应的内存空间直接存值。Go中的值类型有: 数字(整形和浮点数)、布尔值、字符串、数组、结构体。这类数据类型通常在栈中分配内存!
    image.png

    4.3.2. 引用类型

    变量存储的是一个地址,这个地址指向的目标内存才是真正的值。Go中的值类型有:指针、切片、映射、管道、interface。这类数据通常存储在堆中!
    image.png

5. 基本数据类型转换

Golang 和java / c 不同,Go 在不同类型的变量之间赋值时需要显式转换。也就是说 Golang 中数据类型不能自动转换。

5.1. 数字之间的转换

数字之间可以用 type() 进行转换,如 int64(100) ,需要注意的是: type() 并不是函数。大范围数据类型转为小范围数据类型时,会按照溢出处理,不会报错,但是结果可能是错误的!

  1. package main
  2. import "fmt"
  3. func main() {
  4. a, b := 1000, 3.141592652
  5. fmt.Printf("type(a):%T\ttype(b):%T\n", a, b) // 默认整数和浮点数类型
  6. // int8 超出长度结果异常
  7. fmt.Printf("a --> int8:%v\tint64:%v\tfloat32:%v\n", int8(a), int64(a), float32(a))
  8. fmt.Printf("b --> int8:%v\tint64:%v\t\tfloat32:%v\n", int8(b), int64(b), float32(b))
  9. }
  1. [root@heyingsheng 2020-03-12]# go run 05.go
  2. type(a):int type(b):float64
  3. a --> int8:-24 int64:1000 float32:1000
  4. b --> int8:3 int64:3 float32:3.1415927

5.2. 其它数据类型转string

5.2.1. 使用fmt.Sprintf()函数

  1. package main
  2. import "fmt"
  3. func main() {
  4. a, b, c := 12345, true, '中'
  5. var str string
  6. str = fmt.Sprintf("%v", a)
  7. fmt.Printf("str: type=%T value=%q\n", str, str)
  8. str = fmt.Sprintf("%v", b)
  9. fmt.Printf("str: type=%T value=%q\n", str, str)
  10. str = fmt.Sprintf("%v", c)
  11. fmt.Printf("str: type=%T value=%q\n", str, str)
  12. }
  1. [root@heyingsheng day01]# go run 03-strings/main.go
  2. str: type=string value="12345"
  3. str: type=string value="true"
  4. str: type=string value="20013"

5.2.2. 使用strconv包

  1. func Itoa(i int) string // 将int转为十进制的string,内部调用的还是 FormatInt()
  2. func FormatInt(i int64, base int) string // 将64位整形转为base进制下的字符串,base支持2-32之间的整数
  3. func FormatUint(i uint64, base int) string // FormatInt的无符号版本
  4. func Itoa(i int) string // 将十进制的int转为string
  5. func FormatBool(b bool) string // 将bool转为字符串
  6. // 将浮点数转为字符串
  7. // f 表示待转换的浮点数;fmt表示格式,一般用'f';prec表示小数位数;bitSize表示目标数据类型精度,支持32和64
  8. func FormatFloat(f float64, fmt byte, prec, bitSize int) string
  1. package main
  2. import (
  3. "fmt"
  4. "strconv"
  5. )
  6. func main() {
  7. a, b, c := 12345, 3.1415926, true
  8. var str string
  9. str = strconv.FormatInt(int64(a), 10)
  10. fmt.Printf("str: type=%T value=%q\n", str, str)
  11. str = strconv.FormatFloat(b, 'f', 2, 32 )
  12. fmt.Printf("str: type=%T value=%q\n", str, str)
  13. str = strconv.FormatBool(c)
  14. fmt.Printf("str: type=%T value=%q\n", str, str)
  15. }
[root@heyingsheng day01]# go run 03-strings/main.go
str: type=string value="12345"
str: type=string value="3.14"
str: type=string value="true"

5.3. string转其它数据类型

# 如果类型转换失败,会返回对应的数据类型的零值!!!
func ParseInt(s string, base int, bitSize int) (i int64, err error)  // 将string转为整形,base位进制,bitSize为数据类型,如64表示int64,0表示int
func ParseUint(s string, base int, bitSize int) (n uint64, err error)  // 将string转为无符号整形
func ParseFloat(s string, bitSize int) (f float64, err error)  // 将string转为浮点数,bitSize表示数据类型,如32表示float32
func ParseBool(str string) (value bool, err error)  // 将string转为bool
package main

import (
    "fmt"
    "strconv"
)

func main()  {
    a, b, c := "123456789", "3.14", "false"
    var (
        aRes int64
        bRes float64
        cRes bool
        dRes bool
    )
    aRes, _ = strconv.ParseInt(a, 10, 64)
    bRes, _ = strconv.ParseFloat(b, 64)
    cRes, _ = strconv.ParseBool(c)
    dRes, _ = strconv.ParseBool("Hello")
    fmt.Printf("str: type=%T value=%q\n", aRes, aRes)
    fmt.Printf("str: type=%T value=%q\n", bRes, bRes)
    fmt.Printf("str: type=%T value=%q\n", cRes, cRes)
    fmt.Printf("str: type=%T value=%q\n", dRes, dRes)  // 转为对应的零值!
}
[root@heyingsheng day01]# go run 03-strings/main.go
str: type=int64 value=%!q(int64=123456789)
str: type=float64 value=%!q(float64=3.14)
str: type=bool value=%!q(bool=false)
str: type=bool value=%!q(bool=false)