Golang学习笔记(02-3数据类型-基础数据类型)

220 阅读14分钟

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 0100100%b
八进制0o144100%o
十进制100100%d
十六进制0x64100%x

1.1.3. 案例

package main

import "fmt"

func main() {
	// 十进制转换
	n1 := 100
	fmt.Printf("bin:%b; oct:%o; hex:%x\n", n1, n1, n1)
	// 二进制转换
	var n2 int8
	n2 = 0b1100100
	fmt.Printf("oct:%o; dec:%d; hex:%x\n", n2, n2, n2)
	// 八进制转换
	var n3 uint8 = 0o144
	fmt.Printf("bin:%b; dec:%d; hex:%x\n", n3, n3, n3)
	// 十六进制
	var n4 = 0x64
	fmt.Printf("bin:%b; oct:%o; dec:%d\n", n4, n4, n4)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\02-num>go run main.go
bin:1100100; oct:144; hex:64
oct:144; dec:100; hex:64
bin:1100100; dec:100; hex:64
bin:1100100; oct:144; dec:100

1.2. 浮点数

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

浮点数类型大小最大值(常量)
float32约为3.4x10^38,即3.4e38math.MaxFloat32
float64约为1.8x10^308,即1.8e308。默认float类型math.MaxFloat64
package main

import (
	"fmt"
	"math"
)

func main() {
	f1,f2 := 1001.0,1e3
	fmt.Println("f1-f2 =", f1-f2)
	fmt.Printf("%.2f\n", math.Pi)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\02-num>go run main.go
f1-f2 = 1
3.14

2. 布尔值

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


3. 字符串

3.1. Go中字符串定义

3.1.1. 自定义字符串

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

3.1.2. 字符串案例

package main

import "fmt"

func main() {
	// 打印windowns中路径
	s1 := "e:\\OneDrive\\Projects\\Go\\src\\gitee.com\\studygo\\day01\\03-strings"
	s2 := `e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings`
	fmt.Println("path:", s1)
	fmt.Println("path:", s2)
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings>go run main.go
path: e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings
path: e:\OneDrive\Projects\Go\src\gitee.com\studygo\day01\03-strings

3.1.3. 字符串的数据结构

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

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

3.2. 字符

3.2.1. 字符和字符串

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

字符类型描述
byteASCII码表中数字,实际上是uint8类型的别名
runeUTF-8码表中数字,实际是int32类型的别名

3.2.2. 字符串拆分成字节串

package main

import "fmt"

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

3.2.3. 字符串的本质

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

package main

import "fmt"

func main() {
	str0 := "Hello world!"
	str1 := "你好,世界!"
	fmt.Printf("str0 --> []byte:%v, str0 --> []rune:%v\n", []byte(str0), []rune(str0))
	fmt.Printf("str1 --> []byte:%v, str1 --> []rune:%v\n", []byte(str1), []rune(str1))
	// []byte和[]rune转字符串
	s0 := []byte{72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33}
	fmt.Println("s0=", string(s0))
	s1 := []rune{20320, 22909, 65292, 19990, 30028, 65281}
	fmt.Println("s1=", string(s1))
	// 修改字符串中的字符: "Hello world!" --> "Hello World!"
	tmpByte := []byte(str0)
	tmpByte[6] = 'W'
	fmt.Println(string(tmpByte))
}
[root@heyingsheng 10-slice]# go run str_byte.go
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]
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]
s0= Hello world!
s1= 你好,世界!
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编码下,不同类型的字符对应的长度又不一样,导致中文和英文字符串的长度与预期有差异:

package main

import (
	"fmt"
	"unicode/utf8"
)

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

3.4.2. 截取字符串

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

package main

import (
	"fmt"
	"strings"
)

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

3.4.3. 修改字符串

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

package main

import (
	"fmt"
	"strings"
)

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

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

package main

import (
	"fmt"
	"strings"
)

func check(str string) {
	s1 := strings.Split(str, "")
	s2 := make([]string, 0, len(s1))
	for i := len(s1) - 1; i >= 0; i-- {
		s2 = append(s2, s1[i])
	}
	reverseStr := strings.Join(s2, "")
	if str == reverseStr {
		fmt.Printf("%v --> 是回文\n", str)
	} else {
		fmt.Printf("%v --> 不是回文\n", str)
	}
}

func main() {
	check("上海自来水来自海上")
	check("上海自来水来自海上x")
	check("x上海自来水来自海上x")
}
e:\OneDrive\Projects\Go\src\gitee.com\studygo\day03\01-homework>go run main.go
上海自来水来自海上 --> 是回文
上海自来水来自海上x --> 不是回文
x上海自来水来自海上x --> 是回文

3.4.5. 字符串拼接

package main

import (
	"fmt"
	"strings"
)

func repeatStrings(s string, c int) {
	fmt.Println("strings.Repeat:", strings.Repeat(s, c))  // 重复一个指定的字符串,效率非常高
}

func addStrings(s string, c int)  {
	var res string
	for i:=0;i<c;i++ {
		res += s  // 使用 + 也能实现,方法简单,但是效率低。
	}
	fmt.Println("+:", res)
}

func builderStrings(s string, c int) {
	var res strings.Builder
	for i:=0;i<c;i++ {
		res.WriteString(s)
	}
	fmt.Println("+:", res.String())
}

func main()  {
	repeatStrings("x", 20)
	addStrings("x", 20)
	builderStrings("x", 20)
}

4. 指针

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

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

image.png

4.1. 指针的操作

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

4.1.1. 通过变量取指针

package main

import "fmt"

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

4.1.2. 通过指针取值

package main

import "fmt"

func main() {
	var var0 uint8 = 255
	var p0 *uint8 = &var0
	fmt.Printf("var0:%v p0:%v *p0:%v\n", var0, p0, *p0) // var0:255 p0:0xc0000100b0 *p0:255
}

4.1.3. 空指针

package main

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

4.2. make vs new

4.2.1. new案例

package main

import "fmt"

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

4.2.2. make案例

参考:www.yuque.com/duduniao/vn…

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() 并不是函数。大范围数据类型转为小范围数据类型时,会按照溢出处理,不会报错,但是结果可能是错误的!

package main

import "fmt"

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

5.2. 其它数据类型转string

5.2.1. 使用fmt.Sprintf()函数

package main

import "fmt"

func main() {
	a, b, c := 12345, true, '中'
	var str string
	str = fmt.Sprintf("%v", a)
	fmt.Printf("str: type=%T value=%q\n", str, str)
	str = fmt.Sprintf("%v", b)
	fmt.Printf("str: type=%T value=%q\n", str, str)
	str = fmt.Sprintf("%v", c)
	fmt.Printf("str: type=%T value=%q\n", str, str)
}
[root@heyingsheng day01]# go run 03-strings/main.go
str: type=string value="12345"
str: type=string value="true"
str: type=string value="20013"

5.2.2. 使用strconv包

func Itoa(i int) string                     // 将int转为十进制的string,内部调用的还是 FormatInt()
func FormatInt(i int64, base int) string    // 将64位整形转为base进制下的字符串,base支持2-32之间的整数
func FormatUint(i uint64, base int) string  // FormatInt的无符号版本
func Itoa(i int) string // 将十进制的int转为string

func FormatBool(b bool) string // 将bool转为字符串

// 将浮点数转为字符串
// f 表示待转换的浮点数;fmt表示格式,一般用'f';prec表示小数位数;bitSize表示目标数据类型精度,支持32和64
func FormatFloat(f float64, fmt byte, prec, bitSize int) string 
package main

import (
	"fmt"
	"strconv"
)

func main()  {
	a, b, c := 12345, 3.1415926, true
	var str string
	str = strconv.FormatInt(int64(a), 10)
	fmt.Printf("str: type=%T value=%q\n", str, str)
	str = strconv.FormatFloat(b, 'f', 2, 32 )
	fmt.Printf("str: type=%T value=%q\n", str, str)
	str = strconv.FormatBool(c)
	fmt.Printf("str: type=%T value=%q\n", str, str)
}
[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)