Go数据类型
go语言中的数据类型大体上分为 基本数据类型和 派生数据类型,其中基本数据类型主要有以下几种:
- 布尔类型:true/false,与java一样
- 数字类型:包括了整型的int(其中又包括有符号的int8,int16,int32,int64以及无符号的uint8,uint16,uint32,uint64)。还有浮点型的float和complex
- 字符串类型:没错字符串在go中属于基本数据类型
而派生类型主要包括了下面几种:
- 指针类型
- 数组类型
- 切片类型
- 结构化类型
- 函数类型
- Channal类型
- 接口类型
- Map类型
基本数据类型
基本数据类型主要包括以下几种:
- 布尔类型:true/false,与java一样
- 数字类型:包括了整型的int(其中又包括有符号的int8,int16,int32,int64以及无符号的uint8,uint16,uint32,uint64)。还有浮点型的float和complex
- 字符串类型:没错字符串在go中属于基本数据类型
布尔类型
布尔类型占1个字节,它的值只有true或者false,类型关键字为bool。下面简单的来个示例
package main
import "fmt"
var a = true//声明变量a为true,根据值自动判断变量a为bool类型
var b = bool(2==1)//变量b赋值为bool(2==1),表达式2==1为false
func main() {
if a {
fmt.Print(b)
}
}
结果
数字类型
虽然上面说的int、int8、int16,int32,int64等等都是整型的数据类型,只有长度的区别,但是他们都是不同的类型,不能够互相比较,如下图
整数类型int/uint
int8、16、32、64或者unit8、16、32、64都是代表XXX位的类型。int和unit的区别是int为有符号的,可以表示负数;而uint是无符号的,只表示正数。所以同位的int和uint正数范围相差一半。
下面的表格列出了各个类型的存储范围
类型 | 数值 |
---|---|
int8 | -128到127 |
int16 | -32768 到 32767 |
int32 | -2147483648 到 2147483647 |
int64 | -9223372036854775808 到 9223372036854775807 |
uint8 | 0 到 255 |
uint16 | 0 到 65535 |
uint32 | 0 到 4294967295 |
uint64 | 0 到 18446744073709551615 |
还有一个比较特殊的类型就是int和unit。单独的int类型的大小是根据go的版本而变化的,在32位的go版本中int就是32位,等同于int32,;而在64位的go版本中就是64位的。我们也可以使用strconv.IntSize
查看int的位数。或者使用unsafe.Sizeof
查看变量所占字符大小
package main
import (
"fmt"
"strconv"
"unsafe"
)
var a uint = 18446744073709551615
func main() {
fmt.Println(strconv.IntSize)
fmt.Print(unsafe.Sizeof(a))
}
输出结果
因为我的go版本是64位的,所以输出结果为64位,占8字节。uint和int同理。
浮点类型float32/float/64
go中的浮点类型默认为float64,而实际上使用也要尽量使用float64,因为它有着更高的精度。
package main
import (
"fmt"
)
var a float64 = 0.164815484548445458
var b float32 = 0.164815484548445458
func main() {
fmt.Println(a)
fmt.Println(b)
}
结果
go中还有表示复数类型的complex64和complex128,我数学不好就先不研究了。
字符串类型
字符串是由多个字节组成的不可变序列,是使用utf8编码的unicode。如果我们直接输出字符串的某个下标元素,会得到ASCII
package main
import (
"fmt"
)
var a = "abcde"
func main() {
fmt.Println(a[0])
fmt.Println(a[1])
}
结果
97、98对应a和b在ASCII码表中的位置。
派生数据类型
指针类型
指针变量指向了一个值的内存地址
指针类型的声明格式为var var_name *var-type
,使用*+类型表示为某某类型的指针。比如var ptr *string
就表示ptr为指向string类型的指针。指针的赋值为&+变量名称。比如var a = "abcde";var ptr = &a
,同样的,指针类型可以根据值自动判断类型。如果只定义了指针变量,而并没有给他赋值,那么该指针就会指向一个空的内存地址,也就是nil,类似其他语言中的null
package main
import (
"fmt"
)
var a = "abcde"
var ptr *string //声明一个string类型的指针
func main() {
fmt.Println(ptr) //当前指针ptr为空
ptr = &a //让ptr指向a的内存地址
fmt.Println(ptr) //现在ptr指向a的内存地址了
}
结果
数组类型
数组是一组长度固定的统一数据类型的集合,注意:不同长度的数组属于不同的数据类型
数组的声明方式也很简单var a [n]int
,n为数组的长度。后面我们会认识切片slice,其与数组最大的区别就是切片没有固定长度,是对数组的引用,所以在GO中切片比数组要常用许多。
func main() {
a := [3]int{1, 2, 3} // 定义长度为3的数组,并且初始化值为1,2,3
//range循环数组的时候可以循环下标和值
for i, v := range a {
fmt.Printf("%d %d\n", i, v)
}
//也可以只循环值,把下标丢弃
for _, v := range a {
fmt.Printf("%d\n", v)
}
}
输出
切片类型
切片的声明与数组类似,只不过是没有长度,var s []int
,因为切片是对数组的引用,所以只声明而未初始化时切片的值为nil。
func main() {
nums := [5]int{1,2,3,4,5}
s := nums[:]//定义一个切片s,指向数组nums
fmt.Println(nums)
s[0] = 7
fmt.Println(nums)
}
结果
可以看到数组的值改变了,说明切片只是指向数组,他自己并不存储值
func main() {
nums := [5]int{1,2,3,4,5}
s := nums[:]//定义一个切片s,指向数组nums
fmt.Println(nums)
s = append(s, 1)
fmt.Println(s)
fmt.Println(nums)
}
在这里插入代码片
这里我们将切片增加了一个值,会生成一个新的数组并且指向它,然后改变s[0]的值,所以原来的数组并没有被改变。
结构化类型
结构化类型类似于class,使用type和struct关键字声明,结构体中也可以嵌套结构体
type student struct {
name string
age int
class string
addr addr
}
type addr struct {
city string
road string
number int
}
赋值语句也很简单
func main() {
stu := student{
name: "张三",
age: 30,
class: "一年一班",
addr: addr{
city: "xx市",
road: "黄河路",
number: 132,
},
}
}
函数类型
go中的函数也可以是一种类型
type sum func(x, y int) int
func main() {
sum := func(x,y int)int {
return x+y
}
fmt.Print(sum(1,2))
}
channel类型
channel是go语言中协程之间互相通信的重要结构,一般使用make来声明。channel也需要声明一个他自己的类型用来存放对应类型的消息
func main() {
ch := make(chan string) //声明一个string类型的channel
go func() {
ch <- "发送的消息" //箭头的方向表示消息的传递方向
}()
msg := <-ch //从channel中接受消息
fmt.Print(msg)
}
注意如果使用make函数创建channel有两个参数,第二个参数是channel缓冲的大小,如果不写的话默认生成的是一个不带缓冲的channel,那么对应的协程在没有接受到消息或者发送的消息没有被接受之前都是会阻塞的,所以我们上面才会用go开启一个新的协程来发送消息到channel,然后再主协程里接受消息。
func main() {
ch := make(chan string,5) //声明一个string类型的channel
func() {
ch <- "发送的消息" //箭头的方向表示消息的传递方向
}()
msg := <-ch //从channel中接受消息
fmt.Print(msg)
}
这种写法不需要用go关键字开启新协程也能够正常运行,因为消息被放在了缓冲区。
在并发编程领域channel的应用应该还是比较多的,因为GO开发有一句名言“不要用共享来完成通信,而是用通信来完成共享”,说的就是推荐使用channel来完成协程之间的同步和通信等需求,而不是使用加锁,虽然GO也提供了完善的锁机制。
接口类型
GO中的接口并不是强制要求被实现的。如果一个类型实现了接口的所有方法,那么它就自动的实现了这个接口。这里就要在说一下GO中的函数与方法还是有区别的,函数是单纯的实现一些功能的结构,而方法是有类型归属的,比如下面的say就是函数,而run和walk就是方法
type runner interface {
run()
walk()
}
type peopel struct {
name string
age int
}
//函数say
func say() {
fmt.Println("saying")
}
//people的方法run
func (peo peopel)run() {
fmt.Print("running")
}
//people的方法walk
func (peo peopel)walk() {
fmt.Println("walking")
}
func eat(ru runner) {
fmt.Println("eating")
}
func main() {
peo :=peopel{
name: "张三",
age: 20,
}
eat(peo)
peo.run()
}
因为peo有两个方法run和walk,所以它实现了runner接口,也就可以作为参数传到eat方法内。同时方法的调用也类似于java中的调用,是实例.方法。
map类型
GO中的map也与其他语言中的差不多,是对哈希表的引用,所以map也是引用类型
func main() {
m := map[string]int{}//声明一个空的map
m1 := map[string]int{//声明一个有初始值的map
"a":1,
"b":2,
}
m3:=make(map[string]int)//最常用的make方法创建map
m1["c"] = 3//如果没有当前key则添加一个kv对,存在的话就进行修改
delete(m1,"a")//删除key为a的kv对
}