[Go语言系列] 7. Go 语言数据类型

Go语言系列知识快速查看入口

:point_down::point_down::point_down:



本章内容

Go语言数据类型包含基础类型和复合类型两大类。

Tips:上述内容会一一回复在下面。

参考链接:Go语言字符串-golang string-golang 字符串拼接-go语言中文字符串-嗨客网

2赞

7.1 布尔型 bool

Go语言bool类型数据只允许取值true和false。Go中bool类型占1个字节,默认值为false。

Go语言布尔类型适用于逻辑运算,一般用于程序流程控制条件判断

Go语言bool类型的 true 表示条件为真,false 表示条件为假。

案例:

package main
import (
	"fmt"
)
func main() {
	var isDefault bool
	var isTrue = true
	fmt.Println("IsOk = ", isTrue, "IsOnline = ", isDefault)
}

上述使用bool定义了一个布尔变量isDefault,此时没有给该变量赋初值,则默认值为false。

我们使用bool定义了一个布尔变量isTrue,其对应的值为true。

7.2 数字类型

Go语言的数字类型分为整数类型浮点数类型复数类型。每种数字类型都有其对应的大小以及是否支持正负号的定义。


7.2.1 整数类型

① 有符号整数

Go语言有符号整数,既可以支持正数也可以支持负数的形式。

数据类型 字节 取值范围
int CPU32位 - 4字节CPU64位 - 8字节 CPU32位:-2147483648 ~ 2147483647;CPU64位:-9223372036854775808 ~ 9223372036854775807
int8 1字节 -128 ~ 127
int16 2字节 -32768 ~ 32767
int32 4字节 -2147483648 ~ 2147483647
int64 8字节 -9223372036854775808 ~ 9223372036854775807

② 无符号整数

Go语言无符号整数,即只支持正数形式,不支持负数的形式。

数据类型 字节 取值范围
uint CPU32位 - 4字节;CPU64位 - 8字节 CPU32位:0 ~ 4294967295;CPU64位:0 ~ 18446744073709551615
uint8 1字节 0 ~ 255
uint16 2字节 0 ~ 65535
uint32 4字节 0 ~ 4294967295
uint64 8字节 0 ~ 18446744073709551615

③ 案例

package main
import (
	"fmt"
)
func main() {
	var age  uint32 = 18
	var temp int64 = -12
	fmt.Print("Age = ", age, " Temp = ", temp)
}

输出

Age = 18 Temp = -12

7.2.2 浮点数类型

Go语言中的浮点数是用于存放小数的。Go语言浮点类型只支持有符号类型,不支持无符号类型。

数据类型 最大值 常量
float32 3.4e38 math.MaxFloat32
float64 1.8e308 math.MaxFloat64

Tips:

① 一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度。

② 通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。因为 float32 的有效 bit 位只有 23 个,其它的 bit 位用于指数和符号,所以当整数大于 23 bit 能表达的范围时,float32 的表示将出现误差。

③ Go 语言浮点型表示的数值在很小或很大的时候最好用科学计数法书写,通过 e 或 E 来指定指数部分。


案例

package main
import (
   "fmt"
   "math"
)

func main() {
   var score float32 = -1.1
   var temp float64 = 12.2e10
   var float32Max = math.MaxFloat32
   var float64Max = math.MaxFloat64
   fmt.Print("Score = ", score, "\nTemp = ", temp)
   fmt.Print("\nFloat32Max = ", float32Max, "\nFloat64Max = ", float64Max)
}

输出

Score = -1.1
Temp = 1.22e+11
Float32Max = 3.4028234663852886e+38
Float64Max = 1.7976931348623157e+308

7.2.3 复数类型

Go语言提供了两种复数类型,complex64和complex128。

其中,complex64的实数部分和虚数部分都是float32类型,complex128的实数部分和虚数部分都是float64类型。

① 语法

complex(real,image)

② 参数

参数 描述
complex 定义复数类型使用的关键字。
real 1.8e308
image 1.8e308

③ 案例

package main
import (
	"fmt"
)
func main() {
    var score complex64 = complex(1,2)
    var number complex128 = complex(23.23, 11.11)
    fmt.Print("Score = ", score, " Number = ", number, "\n")
    fmt.Print("Real Score = ", real(score), " Image Score = ", imag(score), "\n")
    fmt.Print("Real Number = ", real(number), " Image Number = ", imag(number))
}

输出

Score = (1+2i) Number = (23.23+11.11i)
Real Score = 1 Image Score = 2
Real Number = 23.23 Image Number = 11.11

Tips:使用 real 获取复数的实部,使用 imag 获取复数的虚部。

7.3 字符串类型

字符串是一个不可改变的字节序列。字符串可以包含任意的数据,但是通常是用来包含可读的文本。

Go语言的字符串的字节使用UTF-8编码标识Unicode文本。

Go语言字符串的内容(纯字节)可以通过标准索引法来获取,在中括号[ ]内写入索引,索引从0开始计数。


Go语言字符串形式详解

形式一:双引号

"hello world" 

说明:使用双引号定义的字符串,会识别转移字符

形式二:反引号

`hello world` 

`hello
 "world"
`

说明:使用反引号定义的字符串,以字符串的原生形式输出,包括换行和特殊字符,可是实现防止攻击、输出源代码等效果。


案例

package main
import (
	"fmt"
)
func main() {
	var name string = "Hello \"World\""
	var site = `Hello
        "World"`
	fmt.Println("Name = ", name)
	fmt.Println("Site = ", site)
}

输出

Name =  Hello "World"
Site =  Hello
        "World"

说明
① 我们使用 string 定义一个字符串,字符串中包含双引号,所以我们必须使用转义字符进行转义,否则会报错。
② 下面,我们使用反引号定义一个多行字符串,该字符串里面所有的字符都会原样输出,不需要进行任何转义。

Tips:更多有关字符串的操作,请参考:

7.4 指针类型(Pointer)

Go语言的指针类型,就是一个存放地址的类型。

Go语言指针变量存放的是一个地址,这个地址指向的空间存放的才是值。

Go语言指针不支持指针的移动,这是Go语言指针与其他语言指针的区别。


① 语法:

var varname1 *type = &varname2

② 参数:

参数 描述
var 定义变量所使用的关键字
varname1 指针类型的变量
type 指针变量的类型
varname2 需要保存地址的变量

③ 说明:

1)使用 & 符号可以获取一个变量的地址。
2)使用 * 符号可以获取Go语言指针类型的地址所对应的值。
3)当一个指针被定义后没有分配到任何变量时,它的值为nil,nil指针也称为空指针。

④ 案例:

package main
import (
	"fmt"
)
func main() {
   var score int32 = 18
   // 定义一个指针变量
   var pScore *int32 = &score 
   // 输出变量值及指针值
   fmt.Println("score = ", score, "pScore = ", pScore)  // score =  18 pScore =  0xc000198000
   
   // 获取指针变量对应的地址存放的值
   var scoreVal = *pScore
   // 输出指针变量对应的地址存放的值
   fmt.Println("scoreVal = ", scoreVal)  // scoreVal =  18
   
   // 利用指针pScore修改变量score的值
   *pScore = 20
   // 输出修改后的score变量的值
   fmt.Println("score = ", score)  // score =  20

   // 定义一个空指针
   var ptr *int 
   // 输出空指针值
   fmt.Println("ptr = ", ptr)
}

⑤ 输出:

score =  18 pScore =  0xc000198000
scoreVal =  18
score =  20
ptr =  <nil>

Tips:空指针判断::point_down:

if(ptr != nil)    /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */

更多有关指针的内容会在之后的指针板块中更新

7.5 数组类型

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。

因为数组的长度是固定的,所以在Go语言中很少直接使用数组。

和数组对应的类型是Slice(切片),Slice是可以增长和收缩的动态序列,功能也更灵活。Slice在下一回复中讲。

数组声明

var 数组变量名 [元素数量]Type

实例

package main
import (
	"fmt"
)
func main() {
   var  a [2]int       // 定义2个整数的数组
   a[0] = 1            // 为数组元素赋值
   a[1] = 2            // 为数组元素赋值
   fmt.Println(a[1])   // 打印第2个元素
   fmt.Println(len(a)) // 打印数组长度
}

输出

2
2

更多有关数组的内容会在之后的数组板块中更新

7.6 切片类型

切片(Slice)是对数组的一个连续片段的引用,所以切片是一个引用类型。

生成切片

① 从数组或切片生成新的切片

切片默认指向一段连续的内存区域,可以是数组,也可以是切片本身。

slice[开始位置:结束位置]
参数 说明
slice 表示目标切片对象
开始位置 对应目标切片对象的索引
结束位置 对应目标切片的结束索引(结束索引的项不包括在切片内)

案例

package main
import (
	"fmt"
)
func main() {
   a := []int{1, 2, 3, 4, 5}
   fmt.Println(a[1:3]) // 区间
   fmt.Println(a[2:])  // 中间到尾部的所有元素
   fmt.Println(a[:2])  // 开头到中间指定位置的所有元素
   fmt.Println(a[:])   // 原有的切片
}

输出

[2 3]
[3 4 5]
[1 2]
[1 2 3 4 5]

② 直接声明新的切片

var name []Type

案例

 var strList []string  // 声明一个字符串切片,切片为空,且没有为其分配内存
 var numListEmpty = []int{}  // 声明一个整型切片,切片为空(没有为其填充),但已为其分配内存

切片是动态结构,可以使用append() 函数向切片中添加元素。

③ 使用make()内建函数构造切片

make([]Type, size, cap)
参数 说明
Type 切片的元素类型
size 为这个类型分配多少个元素
cap 预分配的元素数量,这个值设定后不影响size,只是能提前分配空间,降低多次分配空间造成的性能问题。

案例

package main
import (
	"fmt"
)
func main() {
   a := make([]int, 2)
   b := make([]int, 2, 10)

   fmt.Println(a, b)
   fmt.Println(len(a), len(b))
}

输出

[0 0] [0 0]
2 2

说明

(1)其中a和b均是预分配2个元素的切片,只是b的内部存储空间已经分配了10个,但实际使用了2个元素。
(2)容量(cap)不会影响当前的元素个数,因此a和b取len都是2。

更多有关切片的内容会在之后的切片板块中更新

7.7 结构体类型

Go语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。

结构体是有一系列具有相同类型或不同类型的数据构成的数据集合。

定义结构体

type struct_variable_type struct {
    member definition
    member definition
    ...
    member definition
}

结构体变量声明

variable_name := struct_variable_type {value1, value2, value3}
或
variable_name := struct_variable_type {key1: value1, key2: value2, ..., keyn: valuen}

案例

package main
import (
	"fmt"
)
type Person struct{
   name  string
   sex   string
   age   int
}

func main() {
   fmt.Println(Person{"Bob", "男", 18})
   fmt.Println(Person{name: "Alice", sex: "女",  age: 18})
   // 忽略的字段为0或空
   fmt.Println(Person{sex:"女",  age: 20})
}

输出

{Bob 男 18}
{Alice 女 18}
{ 女 20}

更多有关结构体的内容会在之后的结构体板块中更新

7.8 Channel 类型

Screen Shot 2021-12-27 at 2.55.41 PM

1. 通道的介绍

  • goroutine是Go语言程序的并发体,channels是他们之间的通信机制,它可以让一个goroutine通过它给另一个goroutine发送值消息,即一个goroutine是发送者,另外一个goroutine是接收者。
  • channel是一种特殊的类型,在任何时候,同时只有一个goroutine访问通道进行发送和获取数据。
  • channel遵循先入先出原则。
  • 通道的收发操作在两个不同的goroutine之间进行,由于通道的数据在没有接收方处理时,数据发送方会持续阻塞,因此通道的接受必定在另外一个goroutine中进行。
  • 如果接收方接收时,通道中没有发送方发送数据,接收方也会发生阻塞,直到发送方发送数据为止。
  • 通道一次只能接收一个数据元素。

2. 创建通道

通道是引用类型,需要使用make进行创建。

通道实例 := make(chan 数据类型)

案例

ch1 := make(chan int)              // 创建一个整型类型的通道
ch2 := make(chan interface{})      // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch2 := make(chan *Equip)           // 创建Equip指针类型的通道, 可以存放*Equip

3. 使用通道发送数据

通道变量 <- 发送值(与通道变量的元素类型一致)

案例

package main

func main() {
   // 创建一个整型通道
   ch := make(chan int)
   // 尝试将0通过通道发送
   ch <- 0
}

注意:上述我们只是往通道中发送数据,并没有接收数据的实例,因此发送操作将持续阻塞。Go程序运行时能智能地发现一些永远无法发送成功的语句并作出提示,如下:

fatal error: all goroutines are asleep - deadlock!

4. 使用通道接收数据

接收值 <- 通道变量

通道的数据接收一共有以下4中写法:

① 阻塞接收数据
执行该语句时将会阻塞,直到接收到数据并赋值给data变量。

data := <-ch

② 非阻塞接收数据
使用非阻塞方式从通道接收数据时,语句不会发生阻塞。

data, ok := <-ch

data:表示接收到的数据。未接收到数据时,data为通道类型的零值。
ok:表示是否接收到数据。

③ 接收任意数据,忽略接收的数据

<-ch

执行该语句时将会发生阻塞,直到接收到数据,但接收到的数据会被忽略。

④ 循环接收
通道的数据接收可以借用 for range 语句进行多个元素的接收操作

for data := range ch {
}

通道 ch 是可以进行遍历的,遍历的结果就是接收到的数据。数据类型就是通道的数据类型。通过 for 遍历获得的变量只有一个,即上面例子中的 data。

案例

package main
import (
    "fmt"
    "time"
)
func main() {
    // 构建一个通道
    ch := make(chan int)
    // 开启一个并发匿名函数
    go func() {
        // 从3循环到0
        for i := 3; i >= 0; i-- {
            // 发送3到0之间的数值
            ch <- i
            // 每次发送完时等待
            time.Sleep(time.Second)
        }
    }()
    // 遍历接收通道数据
    for data := range ch {
        // 打印通道数据
        fmt.Println(data)
        // 当遇到数据0时, 退出接收循环
        if data == 0 {
                break
        }
    }
}

更多有关channel的内容会在之后的channel板块中更新

7.9 函数类型

有关函数的内容会在之后的函数板块中更新

7.10 接口类型

有关接口的内容会在之后的接口板块中更新

7.11 Map类型

Map是一种无序键值对集合

Map最重要的一点是通过key来快速检索数据,key类似于索引,指向数据的值。

定义Map

方式一:使用map关键字

var map_variable map[key_data_type]value_data_type

方式二:使用内建函数make

map_variable := make(map[key_data_type]value_data_type)

案例

package main

import "fmt"

func main() {
   
    countryCapitalMap := make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "巴黎"
    countryCapitalMap [ "Italy" ] = "罗马"
    countryCapitalMap [ "Japan" ] = "东京"

    /*使用键输出地图值 */
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */

    if (ok) {
        fmt.Println("American 的首都是", capital)
    } else {
        fmt.Println("American 的首都不存在")
    }
}

输出

France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
American 的首都不存在

delete() 函数

delete()函数用于删除集合的元素,参数为map和其对应的key。

package main

import "fmt"

func main() {

   /* 创建map */
   countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}

   fmt.Println("原始地图")

   /* 打印地图 */
   for country := range countryCapitalMap {
            fmt.Println(country, "首都是", countryCapitalMap [ country ])
   }

   /*删除元素*/ delete(countryCapitalMap, "France")
   fmt.Println("法国条目被删除")

   fmt.Println("删除元素后地图")

   /*打印地图*/
   for country := range countryCapitalMap {
            fmt.Println(country, "首都是", countryCapitalMap [ country ])
   }
}

输出

原始地图
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi
法国条目被删除
删除元素后地图
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi

更多有关map的内容会在之后的map板块中更新

7.12 类型转换

必要以及可行的情况下,一种类型的值可以被转换成另一种类型的值。

由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显示的声明。

valueOfTypeB = typeB(valueOfTypeA)

类型B的值 = 类型B(类型A的值)

例如:

a := 5.0
b := int(a)

说明

① 类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(如 int16 转换成 int32)

② 当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或 将float32 转换为 int),会发生精度丢失(截断)的情况。

③ 只有相同底层类型的变量之间可以相互转换(如将 int16 类型 转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 boo l类型转换为 int 类型)