整型
int8和uint8,8代表8个bit,能代表的数值个数有2^8 = 256
uint8没符号,额能代表的是正数0-258
int8可正可负,区间-128~127
int8、int16、int32、int64带数值,表示的数值个数是固定的,
int没有指定位数,他是可以变化的
当你在32位系统下,int和uint都占用4个字节,也就是32位,当你在64位系统下,int和uint都占8个字节,也就是64位
在二进制传输、读写文件的结构,为了保存文件的结构不受不同的编译目标平台字节长度影响,避免使用int和uint,而是使用更加精确的int32和int64
不同进制的表示方法
package main
import "fmt"
func main(){
var num01 int = 0b1100
var num02 int = 0o14
var num03 int = 0xC
fmt.Printf("2进制数 %b 表示的是: %d \n", num01, num01)
fmt.Printf("8进制数 %o 表示的是: %d \n", num02, num02)
fmt.Printf("16进制数 %X 表示的是: %d \n", num03, num03)
}
输出
2进制数 1100 表示的是: 12
8进制数 14 表示的是: 12
16进制数 C 表示的是: 12
%b 表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"
%E 用科学计数法表示
%f 用浮点数表示
浮点型
浮点数类型的值由整数部分、小数点"."和小数部分组成
整数部分和小数部分由10进制表示法表示。
另外一种表示方法,就是在其中加入指数。指数部分由"E"或"e"以及一个带正负号的十进制数组成。
比如3.7E-2表示浮点数0.037,3.7E+1表示浮点数37
float32,也即我们常说的单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
float64,也即我们熟悉的双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数
那么精度是什么意思?有效位有多少位?
精度主要取决于尾数部分的位数。
对于 float32(单精度)来说,表示尾数的为23位,除去全部为0的情况以外,最小为2-23,约等于1.19*10-7,所以float小数部分只能精确到后面6位,加上小数点前的一位,即有效数字为7位。
同理 float64(单精度)的尾数部分为 52位,最小为2-52,约为2.22*10-16,所以精确到小数点后15位,加上小数点前的一位,有效位数为16位。
总结
1、float32和float64可以表示的数值很多
浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到:
常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
2、 数值很大但精度有限
float32的精度只能提供大约6个十进制数(表示后科学计数法后,小数点后6位)的精度
float64的精度能提供大约15个十进制数(表示后科学计数法后,小数点后15位)的精度
精度是什么?
比如10000018这个数,用float32的类型来表示的话,由于有效位是7位,科学计数法就是1.0000018 * 10^7,精确到小数点后面7位
package main
import "fmt"
func main(){
var num1 float32 = 10000018
var num2 float32 = 100000182
var num3 float32 = 100000187
fmt.Println("num1 :",num)
fmt.Println("num1+1 :",num+1)
fmt.Println("num2 :",num2)
fmt.Println("num2+5 :",num+5)
fmt.Println(num1 == num2+5)
}
输出
num1: 1.0000018e+07
num1+1: 1.0000019e+07
num2: 1.00000184e+08
num2+5: 1.0000019e+08
false
byte、rune与字符串
byte占一个字节,就是8bit表示范围0~255,和uint8类型本质上没有区别,表示的是ACSII表中的一个字符
import "fmt"
func main() {
var a byte = 65
// 8进制写法: var a byte = '\101' 其中 \ 是固定前缀
// 16进制写法: var a byte = '\x41' 其中 \x 是固定前缀
var b uint8 = 66
fmt.Printf("a 的值: %c \nb 的值: %c", a, b)
// 或者使用 string 函数
// fmt.Println("a 的值: ", string(a)," \nb 的值: ", string(b))
}
输出
a 的值: A
b 的值: B
rune占4个字节,32bit,跟int32本质上没区别。表示的是Unicode字符
import (
"fmt"
"unsafe"
)
func main() {
var a byte = 'A'
var b rune = 'B'
fmt.Printf("a 占用 %d 个字节数\nb 占用 %d 个字节数", unsafe.Sizeof(a), unsafe.Sizeof(b))
}
输出
a 占用 1 个字节数
b 占用 4 个字节数
上面的例子都是使用单引号,在GO中单引号不等价与双引号
单引号是用来表示字符
双引号是用来表示字符串
byte和uint8、rune和uint32有没有区别?
因为uint8和uint32,直观上让人以为是一个数值,但实际上,也可以表示一个字符,所以为了消除直观错觉,就有了byte和rune
字符串
var str string = "hello world!"
byte和rune都是字符类型,多个字符放一起,就组成了字符串string类型
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
占用字节题
”hello 世界“占了多少个字节?
Go语言的string是用utf-8编码,英文字母占一个字节,汉字占三个字节,空格、逗号这些也是占一个字节,所以总共占用5+1+(3*2)个字节
数组与切片
数组是固定长度的特定类型元素组成的序列,因为长度固定,所以很少使用
//声明一个长度为3的数组
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
//声明并直接初始化
var arr [3]int = [3]int{1,2,3}
arr := [3]int{1,2,3}
//...让Go自己根据实际情况来分配空间
arr := [...]int{1,2,3}
//偷懒定义数组
arr := [4]int{2:3} //[0,0,3,0]
切片与数组一样,也是可以容纳若干类型相同的元素容器。与数组不同的是无法通过切片类型来确定其值的长度。每个切片的值都会讲数组作为底层数据结果。我们吧这样的数组称为切片的底层数组
切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意,终止索引标识的项不包括在切片内
1、对数组进行片段截取
myarr := [5]int{1,2,3,4,5}
// 【第一种】
// 1 表示从索引1开始,直到到索引为 2 (3-1)的元素
mysli1 := myarr[1:3]
// 【第二种】
// 1 表示从索引1开始,直到到索引为 2 (3-1)的元素
mysli2 := myarr[1:3:4]
打印出来他们是一样的,第二种的4是终止索引
在切片的时候,若不指定第三个数,那么切片终止索引会一直到原数组的最后一个数。如果指定第三个数,那么切片终止索引只会到原数组的该索引值,切片的第三个数影响的只是切片的容量,而不会影响长度
package main
import "fmt"
func main(){
myarr := [5]int{1,2,3,4,5}
fmt.Printf("myarr 的长度为:%d,容量为:%d\n", len(myarr), cap(myarr))
mysli1 := myarr[1:3]
fmt.Printf("mysli1 的长度为:%d,容量为:%d\n", len(mysli1), cap(mysli1))
fmt.Println(mysli1)
mysli2 := myarr[1:3:4]
fmt.Printf("mysli2 的长度为:%d,容量为:%d\n", len(mysli2), cap(mysli2))
fmt.Println(mysli2)
}
输出
myarr 的长度为:5,容量为:5
mysli1 的长度为:2,容量为:4
[2 3]
mysli2 的长度为:2,容量为:3
[2 3]
2、从头声明赋值
//声明字符串切片
var strList []string
//声明整形切片
var numList []int
//声明一个空切片
var emptyList []int{}
3、使用make构造
make([]Type, size, cap)
import "fmt"
func main(){
a := make([]int,2)
b := make([]int,2,10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))
}
输出
[0 0] [0 0]
2 2
2 10
4、偷懒方式
import (
"fmt"
)
func main() {
a := []int{4:2}
fmt.Println(a)
fmt.Println(len(a), cap(a))
}
输出
[0 0 0 0 2]
5 5
切片是引用类型,不对它赋值,它的零值(默认值)是nil
切片可以随时增加长度
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)
}
[0 1 2 3 4 5 6 7 8]
Map类型
map类型是由若干个key:value这样的键值对映射组合在一起的数据结构
他是哈希表的一个实现,也就是要求它的每个映射里的key都是唯一的,可以用==和!=来进行判等操作,也就是锁key必须是可哈希的
什么是哈希?简单来说,一个不可变对象,都可以用一个哈希值来唯一表示,这样的不可变对象,比如字符串类型的对象(可以说除了切片、map,函数之外的其他内建类型都算)
map[KEY_TYPE]VALUE_TYPE
声明初始化map
//第一种方法
var score map[string]int = map[string]int{"a":1,"b":2}
//第二种
score := map[string]int = map[string]int{"a":1,"b":2}
//第三种
score := make(map[string]int)
score["a"] = 1
score["b"] = 2
常用第三种,第一种未初始化make(map[string]int)零值为nil,无法进行赋值
//添加元素,若key已存在,直接更新value
score["a"] = 1
//读取元素
fmt.Println(score["a"])
//删除元素是
delete(socre["a"])
//循环读取
for key, value := range score{
fmt.Printf("key: %s, value: %d\n", key, value)
}
for key := range score{
fmt.Printf("key: %s\n", key)
}
for _, value := range score{
fmt.Printf("value: %d\n", value)
}
布尔类型
在Go中,真值用true,不与1相等,假值用false不与0相等
指针
var name string = "王小明"
当我们访问这个变量时,计算机会返回给我们它指向的内存地址里面存储的值:王小明
将这个内存地址赋值给另外一个变量名,叫做pointer,而这个变量,我们称之为指针变量
普通变量:存数据本身
指针变量:存值的内存地址
创建指针
//方法一
aint := 1
ptr := &aint
//方法二
//创建指针
astr := new(string)
//指针赋值
*astr = "hello world"
//第三种
aint := 1
var bint *int//声明一个指针
bint = &aint//初始化
&:从一个普通变量中取得内存地址
星号*:当星号*在赋值操作符(=)的右边,是从一个指针变量总取得变量值,当在左边是指该指针指向的变量
package main
import "fmt"
func main() {
aint := 1 // 定义普通变量
ptr := &aint // 定义指针变量
fmt.Println("普通变量存储的是:", aint)
fmt.Println("普通变量存储的是:", *ptr)
fmt.Println("指针变量存储的是:", &aint)
fmt.Println("指针变量存储的是:", ptr)
}
输出
普通变量存储的是: 1
普通变量存储的是: 1
指针变量存储的是: 0xc0000100a0
指针变量存储的是: 0xc0000100a0
指针类型
package main
import "fmt"
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)
}
输出
astr 指针类型是:*string
aint 指针类型是:*int
abool 指针类型是:*bool
arune 指针类型是:*int32
afloat 指针类型是:*float64
若我们定义一个只接收指针类型的参数的函数,可以这么写
func mytest(ptr *int){
fmt.Println(*ptr)
}
指针零值
//当指针声明后,没有进行初始化,其零值是nil
func main(){
a := 25
var b *int
if b == nil{
fmt.Println(b)
b = &a//初始化,将a的内存地址给b
fmt.Println(b)
}
}
输出
<nil>
0xc0000100a0
切片与指针一样都是引用类型
如果我们想通过一个函数改变一个数组的值,有两种方法
1、将这个数组的切片作为参数传递给函数
2、将这个数组的指针作为参数传递给函数
使用切片
func main(){
arr := [3]int{1,2,3}
modify(arr[:])
fmt.Println(arr)
}
func modify(arr []int){
arr[1] = 4
}
使用指针
func main(){
arr := [3]int{1,2,3}
modify(&arr)
fmt.Println(arr)
}
func modify(arr *[3]int){
(*arr)[1] = 4
}
尽管两者都可以实现我们的目的,但是按照Go语言的使用习惯,建议用第一种,因为写出来代码更加简洁易读