图片说明

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)
}