1. 整型与浮点型
1.1 整型
整型还可以继续分为10个类型
int 和 uint 的区别就在于一个 u,有 u 说明是无符号,没有 u 代表有符号
出于习惯,在初始化数据类型为整型的变量时,我们会使用10进制的表示法,但是同时也可以使用其他进制来表示一个整数
tips: fmt包的格式化功能
1.2 浮点型
浮点数类型的值一般是由整数部分、小数点‘.’和小数部分组成
其中,整数部分和小数部分均由10进制表示法表示。不过还有另一种表示方法。那就是在其中加入指数部分。指数部分由“E”或“e”以及一个带正负号的10进制数组成。比如,3.7E-2表示浮点数0.037。又比如,3.7E+1表示浮点数37,还可以简化:37.0简化为37, 0.037简化为。037
有一点需要注意,在Go语言里,浮点数的相关部分只能由10进制表示法表示,而不能由8进制表示法或16进制表示法表示
float32 和 float64
精度主要取决于尾数部分的位数。
对于 float32(单精度)来说,表示尾数的为23位,除去全部为0的情况以外,最小为2^-23,约等于1.19*10^-7,所以float小数部分只能精确到后面6位,加上小数点前的一位,即有效数字为7位。
同理 float64(单精度)的尾数部分为 52位,最小为2^-52,约为2.22*10^-16,所以精确到小数点后15位,加上小数点前的一位,有效位数为16位。
总结如下:
一、float32 和 float64 可以表示的数值很多
浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到:
·常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
·常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
·常量 math.MinFloat32 表示 float32 能取到的最大数值,大约是 1.4e-45;
·常量 math.MinFloat64 表示 float64 能取到的最大数值,大约是 4.9e-324;
二、数值很大但精度有限
·float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
·float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
精度:
比如 10000018这个数,用 float32 的类型来表示的话,由于其有效位是7位,将10000018 表示成科学计数法,就是 1.0000018 * 10^7,能精确到小数点后面6位。
此时用科学计数法表示后,小数点后有7位,刚刚满足我们的精度要求,意思是什么呢?此时你对这个数进行+1或者-1等数***算,都能保证计算结果是精确的
换成 100000187,同样使用 float32类型,表示成科学计数法,由于精度有限,表示的时候小数点后面7位是准确的,但若是对其进行数***算,由于第八位无法表示,所以运算后第七位的值,就会变得不精确。
由于精度的问题,就会出现这种很怪异的现象,myfloat == myfloat +1 会返回 true,因为计算机会自动截取到准确精度的那一位
2. byte、rune和字符串类型
2.1 byte
byte,占用1个节字,就 8 个比特位,所以它和 uint8 类型本质上没有区别,它表示的是 ACSII 表中的一个字符
import "fmt" func main() { var a byte = 65 // 8进制写法: var c byte = '\101' 其中 \ 是固定前缀 // 16进制写法: var c byte = '\x41' 其中 \x 是固定前缀 var b uint8 = 66 fmt.Printf("a 的值: %c \nb 的值: %c", a, b) // 或者使用 string 函数 // fmt.Println("a 的值: ", string(a)," \nb 的值: ", string(b)) }
在 ASCII 表中,由于字母 A 的ASCII 的编号为 65 ,字母 B 的ASCII 编号为 66,所以上面的代码也可以写成这样
import "fmt" func main() { var a byte = 'A' var b uint8 = 'B' fmt.Printf("a 的值: %c \nb 的值: %c", a, b) }
上面两种写法的输出结果都是一样的
a 的值: A b 的值: B
2.2 rune类型
占用4个字节,共32位比特位,所以它和 uint32 本质上也没有区别。它表示的是一个 Unicode字符(Unicode是一个可以表示世界范围内的绝大部分字符的编码规范)
由于 byte 类型能表示的值是有限,只有 2^8=256 个。所以如果你想表示中文的话,你只能使用 rune 类型。
var name rune = '中'
在go语言中,单引号和双引号并不是等价的,单引号用来表示字符,双引号用来表示字符串,如果你前面声明了是字符变量,但是却使用双引号例如“a”,那么golang会认为这是一个字符串,和声明不一致,就会报错
2.3 字符串类型
var mystr string = "hello"
run干戈字符放在一起就组成了一个字符串,所以string的本质是一个byte数组
import ( "fmt" ) func main() { var mystr01 string = "hello" var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111} fmt.Printf("mystr01: %s\n", mystr01) fmt.Printf("mystr02: %s", mystr02) }
输出如下:
mystr01: hello mystr02: hello
除了双引号之外 ,还可以使用反引号``来表示字符串
大多情况下,二者并没有区别,但如果你的字符串中有转义字符\ ,这里就要注意了,它们是有区别的。
使用反引号号包裹的字符串,相当于 Python 中的 raw 字符串,会忽略里面的转义。
二者的打印结果都是一样的
如果你仍然想使用解释型的字符串,但是各种转义实在太麻烦了。你可以使用 fmt 的 %q 来还原一下。
import ( "fmt" ) func main() { var mystr01 string = `\r\n` fmt.Println(`\r\n`) fmt.Printf("的解释型字符串是: %q", mystr01) } 输出如下: \r\n 的解释型字符串是: "\\r\\n"
同时反引号可以不写换行符来表示一个多行的字符串
func main() { var mystr01 string = `你好呀! 我的名字是小小` fmt.Println(mystr01) } //output 你好呀! 我的名字是小小
golang中也有数字类型转换的函数:
基本格式:
type_name(expression) //type_name是类型,expression是表达式 func main() { var sum int = 17 var count int = 5 var mean float32 mean = float32(sum)/float32(count) fmt.Printf("mean 的值为: %f\n",mean) }
但是golang不支持隐式转换类型,例如:
package main import "fmt" func main() { var a int64 = 3 var b int32 b = a fmt.Printf("b 为 : %d", b) }
此时会报错
cannot use a (type int64) as type int32 in assignment cannot use b (type int32) as type string in argument to fmt.Printf
但是如果改成 b = int32(a) 就不会报错了:
package main import "fmt" func main() { var a int64 = 3 var b int32 b = int32(a) fmt.Printf("b 为 : %d", b) }
String()函数与strconv.Itoa()函数的区别:
Itoa函数:
strconv.Itoa()函数的参数是一个整型数字,它可以将数字转换成对应的字符串类型的数字
num := 97 res := strconv.Itoa(num) //"97"
String()函数:
string函数的参数如果是一个整型数字,那么它会将该整型数字转换成ASCII码值等于该整型数字的字符
3.数组和切片
3.1 数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
声明数组:
var arr [3]int var arr [3]int = [3]int{1,2,3} arr := [3]int{1,2,3}
上面的 3 表示数组的元素个数 ,万一你哪天想往该数组中增加元素,你得对应修改这个数字,为了避免这种硬编码,你可以这样写,使用 ... 让Go语言自己根据实际情况来分配空间。
arr := [...]int{1,2,3}
[3]int 和 [4]int不是同一种类型,在使用的时候一定要注意
import ( "fmt" ) func main() { arr01 := [...]int{1, 2, 3} arr02 := [...]int{1, 2, 3, 4} fmt.Printf("%d 的类型是: %T\n", arr01, arr01) fmt.Printf("%d 的类型是: %T", arr02, arr02) } //output [1 2 3] 的类型是: [3]int [1 2 3 4] 的类型是: [4]int
使用 type 关键字可以定义一个类型字面量
func main() { type arr3 [3]int myarr := arr3{1,2,3} fmt.Printf("%d 的类型是: %T", myarr, myarr) } //output [1 2 3] 的类型是: main.arr3
定义数组的时候还可以指定数组内元素的值
arr:=[4]int{3:1} [0 0 0 1]
4表示数组有4个元素,3 表示前面三个元素是 0,后面1表示最后一个元素是1。
arr:=[4]int{1:1} [0 1 0 0]
第一个1表示前一个元素是0,第二个1表示后一个元素为1,那么剩下的元素自动为0
3.2 切片
切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度,比如切片arr={1,2,3,4},判断类型输出的是[]int,所以根据类型无法判断它的长度
每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。
切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(意思是这是个左闭右开的区间)
切片的构造,有四种方式:
1.对数组进行片段截取(区间左闭右开),当你使用这种方式生成切片对象时,切片的容量会从截取的起始索引到原数组的终止索引。
如下这段代码所示,切片从索引值 1 开始,到原数组终止索引值5,中间还可以容纳4个元素,所以容量为 4,但是由于我们切片的时候只要求取到索引值2 (3-1),所以当我们对这个切片进行打印时,并不会打印索引值3,4,5 对应的元素值
package main import "fmt" func main(){ myarr := [5]int{1,2,3,4,5} fmt.Printf("myarr 的长度为:%d,容量为:%d\n", len(myarr), cap(myarr)) mysli := myarr[1:3] fmt.Printf("mysli 的长度为:%d,容量为:%d\n", len(mysli), cap(mysli)) fmt.Println(mysli) } //output myarr 的长度为:5,容量为:5 mysli 的长度为:2,容量为:4 [2,3]
2.从头声明赋值
// 声明字符串切片 var strList []string // 声明整型切片 var numList []int // 声明一个空切片 var numListEmpty = []int{}
3.使用make函数:make( []Type, size, cap )
type是类型,size是长度,cap是容量
使用make构造切片,cap不是必须要指明值,size<=cap,例如,cap表示一个公司里最多可以容纳的员工数量,size表示公司里当前所拥有的员工数量
4.使用数组一样的方法
import ( "fmt" ) func main() { a := []int{4:2} fmt.Println(a) fmt.Println(len(a), cap(a)) } //output [0 0 0 0 2] 5 5
由于切片是引用类型,所以你不对它进行赋值的话,它的零值(默认值)是 nil
数组 与 切片 有相同点,它们都是可以容纳若干类型相同的元素的容器,但数组的容器大小固定,而切片本身是引用类型 ,我们可以对它 append 进行元素的添加。
import ( "fmt" ) func main() { myarr := []int{1} // 追加一个元素 myarr = append(myarr, 2) // 追加多个元素 myarr = append(myarr, 3, 4) // 追加一个切片, ... 表示解包,不能省略 myarr = append(myarr, []int{7, 8}...) // 在第一个位置插入元素 myarr = append([]int{0}, myarr...) // 在中间插入一个切片(两个元素) myarr = append(myarr[:5], append([]int{5,6}, myarr[5:]...)...) fmt.Println(myarr) } //output [0 1 2 3 4 5 6 7 8]
切片引用数组,共用同一个内存空间,更改值会相互影响
4. 字典和布尔类型
4.1字典
字典(Map类型)是由若干个key:value键值对组成的数据结构
它是哈希表的一个实现,这就要求它的每个映射里的key,都是唯一的,可以使用 == 和 != 来进行判等操作,换句话说就是key必须是可哈希的(一个不可变对象,都可以用一个哈希值来唯一表示,也就是说map里面的key值不可以是切片、字典和函数类型)
在声明字典时,需要指定好key和value的类型,然后使用map关键字声明
map[KEY_TYPE]VALUE_TYPE
声明并且初始化字典的方法:
// 第一种方法 var scores map[string]int = map[string]int{"english": 80, "chinese": 85} func main() { // 声明一个名为 score 的字典 var scores map[string]int // 未初始化的 score 的零值为nil,无法直接进行赋值 if scores == nil { // 需要使用 make 函数先对其初始化 scores = make(map[string]int) } // 经过初始化后,就可以直接赋值 scores["chinese"] = 90 fmt.Println(scores) } // 第二种方法 scores := map[string]int{"english": 80, "chinese": 85} // 第三种方法 scores := make(map[string]int) scores["english"] = 80 scores["chinese"] = 85
字典的相关操作:
添加元素
scores["math"] = 90
更新元素,如果key已经存在的话,直接更新value
scores["math"] = 100
删除元素,使用 delete 函数,如果 key 不存在,delete 函数会静默处理,不会报错。
delete(scores, "math")
读取元素,直接使用 map[key] 即可 ,如果 key 不存在,也不报错,会返回其value-type 的零值。
判断 key 是否存在
当key不存在,会返回value-type的零值 ,所以你不能通过返回的结果是否是零值来判断对应的 key 是否存在,因为 key 对应的 value 值可能恰好就是零值。
其实字典的下标读取可以返回两个值,使用第二个返回值都表示对应的 key 是否存在,若存在ok为true,若不存在,则ok为false
func main() { scores := map[string]int{"english": 80, "chinese": 85} math, ok := scores["math"] if ok { fmt.Printf("math 的值是: %d", math) } else { fmt.Println("math 不存在") } }
如何对字典进行循环
1、获取 key 和 value
func main() { scores := map[string]int{"english": 80, "chinese": 85} for key, value := range scores { fmt.Printf("key: %s, value: %d\n", subject, score) } }
2、只获取key,可以不使用占位符
func main() { scores := map[string]int{"english": 80, "chinese": 85} for key := range scores { fmt.Printf("key: %s\n", subject) } }
3、只获取value
func main() { scores := map[string]int{"english": 80, "chinese": 85} for _,value := range scores { fmt.Printf("key: %s\n", value) } }
4.2 bool类型
在有些语言中,真值用true表示,并且和1相等,但是在Go 中,真值用 true 表示,不但不与 1 相等,并且更加严格,不同类型无法进行比较,而假值用 false 表示,同样与 0 无法比较。
bool转int
func bool2int(b bool) int { if b { return 1 } return 0 }
int转bool
func int2bool(b int) bool { return b != 0 }
在golang中使用!对逻辑值进行取反,使用 && 表示且,用 || 表示或,并且有短路行为(即左边表达式已经可以确认整个表达式的值,那么右边将不会再被求值)
5. 指针
5.1 什么是指针
当我们定义一个变量 name
var name string = "Go编程时光"
此时,name 是变量名,它只是编程语言中方便程序员编写和理解代码的一个标签。
当我们访问这个标签时,计算机会返回给我们它指向的内存地址里存储的值:Go编程时光。
出于某些需要,我们会将这个内存地址赋值给另一个变量名,通常叫做 ptr(pointer的简写),而这个变量,我们称之为指针变量。
换句话说,指针变量(一个标签)的值是指针,也就是内存地址。
根据变量指向的值,是否是内存地址,我把变量分为两种:
·普通变量:存数据值本身
·指针变量:存值的内存地址
5.2 指针的创建方法
1、先定义对应的变量,再通过变量取得内存地址,创建指针
// 定义普通变量 aint := 1 // 定义指针变量 ptr := &aint
2、先创建指针,分配好内存后,再给指针指向的内存地址写入对应的值。
// 创建指针 astr := new(string) // 给指针赋值 *astr = "Go编程时光"
3、先声明一个指针变量,再从其他变量取得内存地址赋值给它
aint := 1 var bint *int // 声明一个指针 bint = &aint // 初始化
上面的三段代码中,指针的操作都离不开这两个符号:
&:从一个普通变量中取得内存地址
:当在赋值操作值的右边,是从一个指针变量中取得变量值,当在赋值操作值的左边,是指该指针指向的变量
*要想打印指针指向的内存地址,方法有两种**
// 第一种 fmt.Printf("%p", ptr) // 第二种 fmt.Println(ptr)
5.3 指针的类型
func main() { astr := "hello" aint := 1 abool := false arune := 'a' afloat := 1.2 fmt.Printf("astr 指针类型是:%T\n", &astr) fmt.Printf("aint 指针类型是:%T\n", &aint) fmt.Printf("abool 指针类型是:%T\n", &abool) fmt.Printf("arune 指针类型是:%T\n", &arune) fmt.Printf("afloat 指针类型是:%T\n", &afloat) } //output astr 指针类型是:*string aint 指针类型是:*int abool 指针类型是:*bool arune 指针类型是:*int32 afloat 指针类型是:*float64
5.4 指针零值
当指针声明后,没有进行初始化,其零值是 nil。
func main() { a := 25 var b *int // 声明一个指针 if b == nil { fmt.Println(b) b = &a // 初始化:将a的内存地址给b fmt.Println(b) } } //output <nil> 0xc0000100a0
5.5 指针与切片
切片与指针一样,都是引用类型。
如果我们想通过一个函数改变一个数组的值,有两种方法:
1、将这个数组的切片做为参数传给函数
2、将这个数组的指针做为参数传给函数
尽管二者都可以实现我们的目的,但是按照 Go 语言的使用习惯,建议使用第一种方法,因为第一种方法,写出来的代码会更加简洁,易读。具体你可以参数下面两种方法的代码实现
使用切片
func modify(sls []int) { sls[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(a[:]) fmt.Println(a) }
使用指针
func modify(arr *[3]int) { (*arr)[0] = 90 } func main() { a := [3]int{89, 90, 91} modify(&a) fmt.Println(a) }