Go语言之数据类型

Go是一种静态类型语言,所谓静态类型语言(强类型语言),是指错误的使用了类型时,编译器将引发错误,所谓动态类型语言(也称为弱类型或者松散类型语言),指的是为了执行程序,运行时会将一种类型转换为另一种类型,可以实现动态转换

静态类型语言的优点:

  • 性能高于动态类型语言
  • Bug通常会被编译器发现
  • 代码编译器可提供代码补全和其他功能
  • 数据完整性更好

动态类型语言的优点:

  • 使用动态类型语言编写的软件速度通常更快
  • 无须为执行代码而等待编译器完成编译
  • 动态类型语言不死板,变更代码容易
  • 学习门槛低

Go 语言中有丰富的数据类型,除了基本的整型、浮点型、布尔型、字符串外,还有切片、结构体、函数、map 、通道( channel )等,切片类型有着指针的便利性,但比指针更为安全, 很多高级语言都配有切片进行安全和高效率的内存操作。 结构体是Go 语言基础的复杂类型之一,函数也是Go 语言的一种数据类型,map 和切片是开发中常见的数据容器类型。

一、整型:

整型分为以下两个大类:

  • 按长度分为: int8 、int16 、int32 、int64
  • 还有对应的无符号整型: uint8 、uint16 、uint32 、uint64 。

区别如下:

有符号整数使用最高位(即最左边的位)来表示符号,0表示正数,1表示负数。其余位表示数值

对于一个n位的有符号整数:

  • 最大值:2n-1 −1 例如:int8的最大值为127
  • 最小值:−2n-1 例如: int8的最小值为-128

无符号整数的所有位都用来表示数值,没有符号位,也就没有负数,都是正数

对于一个n位的无符号整数:

  • 最大值:2n -1 例如:uint8的最大值为255
  • 最小值:0 例如:uint8的最小值0

还有两种类型int和uint,在使用int 和uint 类型时,不能假定它是32 位或64 位的整型,而是考虑int 和uint 可能在不同平台上的差异

实际使用中, 切片或map 的元素数量等都可以用int 来表示。

在二进制传输、读写文件的结构描述时, 为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int 和uint

还有一种无符号整数uintptr,大小并不明确,但是足可以存放指针,uintptr近用于底层编程,例如Go程序与C程序或操作系统接口界面

二、浮点型:

Go 语言支持两种浮点型数: float32 和float64

  • float32 的浮点数的最大范围约为3.4e38 ,可以使用常量定义: math.MaxFloat32
  • float64 的浮点数的最大范围约为l.8e308 ,可以使用一个常量定义: math.MaxF!oat64 。

打印浮点数时,可以使用fmt 包配合动词“ %f”,代码如下:

运行结果如图所示:

在大多数现代计算机中,推荐使用float64

1、下面例子通过第三方工具包decimal实现float四舍五入,如图:

运行结果如下:

2、浮点数精度值,定义浮点数,输出打印,如图:

三、布尔型:

布尔型数据在Go 语言中以bool 类型进行声明,布尔型数据只有true( 真)和false(假)

如果没有给布尔变量赋值,它将默认为false,也可以再次对其进行赋值,如图:

Go 语言中不允许将整型强制转换为布尔型,代码如下:

通过go build编译的时候报错,如图:

布尔型无法参与数值运算,也无法与其他类型进行转换。

四、字符串

在Go 语言中以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32 、float64)一样。

字符串的值为双引号中的内容,可以在Go 语言的源码中直接添加非ASCII 码字符,例如:

str := "hello world"
ch := "中文"

1、字符串转义符:

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如图:

转义符含义
\r回车符(返回行首〉
\n换行符(直接跳到下一行的同列位置)
\t制表符
\’单引号
\”双引号
\\反斜杠

例如:下面使用转义字符的例子如下:

打印后的结果如下:

这段代码中将双引号和反斜杠”\”进行转义。

2、定义多行字符串

在源码中,将字符串的值以双引号书写的方式是字符串的常见表达方式,被称为字符串字面量( string literal )。这种双引号字面量不能跨行。如果需要在源码中嵌入一个多行字符串时,就必须使用”`”字符,代码如下:

打印后的输出结果如下:

注意:”`”称为反引号,就是键盘上l 键左边的键,两个反引号间的字符串将被原样赋值到str变量中。在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

多行字符串一般用于内嵌源码和内嵌数据等

字符串变量可以为空,创建字符串变量后,可以将其与其他数据相加,但是不能修改原来的值,不能对字符串执行数学运算,要想对看起来像数字的字符串执行数学运算,必须先将其转换为数字类型

go语言中的字符串实际上是类型为byte的只读切片。或者说一个字符串就是一堆字节,因此通过range遍历的时候,遍历后显示的都是对应的字节,因此需要通过string()进行转换

五、字符:

字符串中的每一个元素叫做“ 字符” 。在遍历或者单个获取字符串元素时可以获得字符。

Go 语言的字符有以下两种:

  • 一种是uint8 类型, 或者叫byte 型, 用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节
  • 另一种是rune 类型,代表一个UTF-8 字符。当需要处理中文、日文或者其他复合字符时, 则需要用到rune 类型。rune 类型实际是一个int32

使用fmt.Printf 中的“ % T ” 动词可以输出变量的实际类型,使用这个方法可以查看byte和rune 的本来类型,代码如下:

打印结果如下:

可以发现, byte 类型的a 变量,实际类型是uint8 ,其值为’a’,对应的ASCII 编码为97,rune 类型的b 变量的实际类型是int3 2 ,对应的Unicode 码就是20320 。

uint8和byte是一样的,可以理解为它们是一个,byte为uint8的别名

Go 使用了特殊的rune 类型来处理Unicode ,让基于Unicode 的文本处理更为方便,也可以使用byte 型进行默认字符串处理,性能和扩展性都有照顾。

UTF-8 和Unicode 有何区别?

下面例子用来计算字符串长度,如图:

上图中的第一行打印的实际是底层字节的长度,其中,汉字在UTF-8中占用3个字节,在unicode中占用2个字节,go语言默认为utf-8编码规则,因此打印的结果将是12,第二行通过rune,实际获取的是实际字符串的长度,不是底层字节的长度,因此为8,因此要想获取实际字符串的长度而不是底层字节的长度,可以使用rune类型

Unicode 是字符集。ASCII 也是一种字符集。
字符集为每个字符分配一个唯一的ID ,我们使用到的所有字符在U nicode 字符集中都有唯一的一个ID 对应,例如上面例子中的a在Unicode 与ASCII 中的编码都是97 。“你”在Unicode 中的编码为20320 , 但是在不同国家的字符集中,“你”(中文)的ID会不同。而无论
任何情况下, Unicode 中的字符的ID 都是不会变化的。
UTF-8 是编码规则,将Unicode 中字符的ID以某种方式进行编码。UTF-8 的是一种变长编码规则,从1到4个字节不等。编码规则如下:
1、Oxxxxxx 表示文字符号。~ 127 , 兼容ASCII 字符集。
2、从128 到OxlOffff 表示其他字符。
根据这个规则,拉丁文语系的字符编码一般情况下,每个字符依然占用一个字节,而中文每个字符占用3个字节。
广义的Unicode 指一个标准,定义字符集及编码规则,即Unicode 字符集和UTF-8 、UTF-16 编码等。

六:切片

切片是一个拥有相同类型元素的可变长度的序列。切片的声明方式如下:

var name [] T

其中, T 代表切片元素类型,可以是整型、浮点型、布尔型、切片、map、函数等。

切片的元素使用”[]”进行访问,在方括号中提供切片的索引即可访问元素,索引的范围从0开始,且不超过切片的最大容量。代码如下:

a := make([]int , 3)
a[0] = 1
a[1] = 2
a[3] = 5

说明:

  • 第1 行,创建一个容量为3 的整型切片。
  • 第3 ~ 5 行,为切片元素赋值。

切片还可以在其元素集合内连续地选取一段区域作为新的切片, 就像其名字”切片”一样,切出一块区域,形成新的切片。

字符串也可以按切片的方式进行操作,看下面的例子:

str := "hello world"
fmt.Println(str[6:])
输出结果如下:world

七、类型转换

Go标准库提供了良好的类型转换支持,strconv包提供了一整套类型转换方法,可用于转换为字符串或将字符串转换为其他类型

例如:定义布尔变量b,值为true,将其转为字符串类型,如图:

将字符串转换为布尔类型,如图:

1、将字符串转为int:

int,err := strconv.Atoi(string)

八、指针:

Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。Go语言中的指针操作非常简单,只需要记住两个符号: & (取地址)和 * (根据地址取值)。

Go 语言的指针类型变量拥有指针的高效访问,但又不会发生指针偏移,从而避免非法修改关键性数据问题。同时, 垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性, 更为安全。切片发生越界时,运行时会报出岩机,并打出堆栈,而原始指针只会崩溃。

1、认识指针地址和指针类型:

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go 语言中使用“&” 操作符放在变量前面对变量进行“取地址”操作(获取内存地址)。

例如:下面定义变量a,打印变量a的内存地址,如图:

代码执行结果如下:

变量、指针和地址二者的关系是: 每个变量都拥有地址,指针的值就是地址。

2、从指针获取指针指向的值:

对普通变量使用“&”操作符取地址获得这个变量的指针后,可以对指针使用“*”操作,也就是获取指针的实际地址

例如:下面将变量a的内存地址赋值给变量b,并获取内存地址的实际值,如图:

取地址操作符“&”和取值操作符“*”是一对互补操作符, “&”取出地址, “*”根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值(*〉操作,可以获得指针变量指向的原变量的值。

3、使用指针修改值:

通过指针不仅可以取值,也可以修改值。

下面例子使用指针进行数值交换,如图:

代码执行结果如下:

注: “*” 操作符作为右值时(=右侧),意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示获取的是a 指向的变量,也就是x。总结起来,”*” ”操作符的根本意义就是操作指针指向的变量,当操作在右值时,就是取指向变量的值;当操作在左值时,就是将值设置给指向的变量

4、使用指针变量获取命令行的输入信息:

Go 语言的flag 包中,定义的指令以指针类型返回,通过学习flag 包,可以深入了解指针变量在设计上的方便之处。

下面的代码通过提前定义一些命令行指令和对应变量,在运行时,输入对应参数的命令行参数后,经过flag 包的解析后即可通过定义的变量获取命令行的数据,定义文件flagg.go,如图:

说明如下:

通过flag.String, 定义一个mode 变量,这个变量的类型是*string,后面3个参数分别如下:

  • 参数名称: 在给应用输入参数时 ,使用这个名称。
  • 参数值的默认值:与flag 所使用的函数创建变量类型对应, String 对应字符串、Int 对应整型、Bool 对应布尔型等,上图中字符串的默认值为空” “
  • 参数说明:使用-help 时,会出现在说明中,可自定义

通过flag.Parse()解析命令行参数,并将结果写入创建的指令变量中,这个例子中就是mode1变量。

最后打印变量mode1的值

因为运行需要传入参数,如果是命令行直接执行可执行go run flagg.go –mode=gongguan,如果是goland编辑器,那么需要先在run–Edit Configurations–Program arguments中配置,如图:

最后运行,输入参数为gongguan ,因此输出也为gongguan,结果如下:

5、创建指针的另一种方法-new()函数:

先看下面的例子:

从上图可以看到,执行上面的代码会引发panic,因为在Go语言中对于引用类型的变量,我们在使用的时候不仅要声明它,还要为它分配内存空间,否则我们的值就没办法存储,而对于值类型的声明不需要分配内存空间,是因为它们在声明的时候已经默认分配好了内存空间,Go语言中new和make是内建的两个函数,主要用来分配内存

通过new()方法来创建指针变量,格式如下:

代码执行结果如下:

new()函数可以创建一个对应类型的指针,创建过程会分配内存。被创建的指针指向的值为默认值

6、make

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了,如下使用make进行初始化,分配内存,如图:

new和make的区别:

  • 二者都是用来做内存分配的
  • make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  • 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针

7、空指针:

当一个指针被定义后没有分配到任何变量时,它的值为 nil,下面定义空指针并判断,如图:

8、在golang1.22版本之前,for循环中的变量共用一个指针地址,不会创建新地址,如图:

解决此问题的方法就是创建一个新变量即可,如图:

注:golang 1.22版本后不存在此问题

9、指向指针的指针变量

如果第一个指针变量的值是第二个指针变量的内存地址,那么就把第一个指针变量称作指向指针的指针变量。也就是说,定义一个指向指针的指针变量,第一个指针指向第二个指针的内存地址;第二个指针
指向变量的内存地址,语法格式如下:

var ptr **type
  • ptr:指向指针的指针变量的变量名
  • ** : 指向指针的指针变量
  • type:指向指针的指针变量的类型

下面例子,声明并初始化string类型的变量str,再声明string类型的指针变量ptr,以及string类型的指向指针的变量pptr。使用&字符获取变量str的内存地址,让指针ptr指向变量str的内存地址;再使用&字符获取指针ptr的内存地址,让指针pptr指向指针ptr的内存地址,如图:

golang中双引号、单引号、反引号的区别:

  • 双引号里的字符串可以转义,不能换行
  • 反引号里面的内容不能转义,可换行,一般用于SQL语句,html等大段内容,以及正则表达式的使用
  • 单引号,一般只能用来包裹一个字节的ASCII码字符,例如:var asc byte = ‘a’

标签