Go从入门到精通——数据类型
Go 数据类型
本章介绍如下内容:
- 数据类型是什么?
- 区分静态类型和动态类型
- 使用布尔类型
- 理解数值类型
- 检查变量的类型
- 类型转换
Go 是一种静态类型语言,而静态类型是一个必须理解的概念。如果您没有接触过静态类型语言,这个概念的理解尤为重要。
1.1 数据类型是什么
数据类型让编程语言、编译器、数据库和代码执行环境知道如何操作和处理数据。例如,如果数据类型为数字,通常可对其执行数学运算。编程语言和数据库常常根据数据类型赋予程序员不同的功能和性能。大多数编程语言还提供了用于处理常见数据的标准库,而数据库提供了查询语言,让程序员能够根据底层数据类型来查询数据以及与之交互。无论数据类型是否被显示地声明,它们都是重要的编程和计算结构。
1.2 区分静态类型和动态类型
所谓强类型语言,指的是错误地使用了类型时,编译器将引发错误;所谓动态类型(也叫松散类型或弱类型)语言,指的是为了执行程序,运行时会将一种类型转换为另一种类型,或者编译器没有实现类型系统。哪种语言更好呢?这存在很大争议,计算机科学家看重强类型语言的正确性和安全性,而其他人则看重动态语言的简单性和开发速度。
下面是静态类型语言的一些优点:
- 性能高于动态类型语言。
- Bug 通常会被编译器发现。
- 代码编辑器可提供代码不全和其他功能。
- 数据完整性更好。
下面是动态类型语言的一些优点:
- 使用动态类型语言编写软件的速度通常更快。
- 无须为执行代码而等待编译器完成编译。
- 动态类型语言通常不那么死板,因此有些人认为变更代码更容易。
- 有些人认为动态类型语言门槛更低。
在 Go 中,程序员可显示地声明类型,也可让编译器推断类型。这里我们将显示地声明类型:
从sayHello函数的参数声明可知,这个函数接受一个类型为 string 的参数;这个函数的返回值也是字符串。因此,编译这个程序时,编译器将检查传递给这个函数的参数是否是字符串;如果不是,编译器将发生错误。这正是我们希望的,因为这意味着错误可能根本不会让用户遇到。
Python 也是强类型语言,我们再看看 Python 的函数,它接受两个值,将它们相加并返回结果:
>>> def func(a,b):
... return a + b;
...
给这个函数提供两个数字时,它能够正确地运行:
然而,如果向它传递一个数字和一个字符串呢?
我们再尝试弱类型语言 Javascript 函数,它也接受两个值,将它们相加并返回结果。
var addition = function(a,b) {
return a,b;
};
如 python 函数一样,我们这里也给这个函提供两个数字时,看看它能够正确地运行吗?
给这个 Javasscript 函数提供两个数字数值的参数时候,是能够正常地运行的。现在我们再向它传递一个数字和一个字符串,看看结果如何:
可以看的到, 执行 Javascript 函数后,虽然也可以的到结果,但是结果很奇怪。
在这种情况下,这个函数返回一个字符串。怎么会这样呢?虽然 JavaScript 有类型的概念,但其类型使用规则非常宽松。在这个示例中,Javascript 对数字值执行类型转换,将其转换为字符串,因此返回字符串 7eight。Javascript 提供的这种灵活性虽然很有吸引力,但可能导致微妙乃至灾难性的 Bug。
上述的 Javascript 函数 myFunction 函数可能返回一个字符串,也可能返回一个整数。如果传递给它的值至少有一个字符串,返回的就是字符串。如果这个返回值被插入到需要整数的数据库字段中,将引发错误。更糟糕的是,这种错误发生在运行阶段,这意味着它将影响使用程序的用户。这种错误除非得到妥善处理,否则可能导致程序崩溃。
而在使用 Go 语言编写的函数中,对参数和返回值的类型都做了声明。
下面我们也运行下 Go 语言的情况,如 Python 函数一样,我们这里也给这个函提供两个数字时,看看它能够正确地运行吗?
结果是可以正确地运行。如果我们传入字符串试试呢?
尝试运行程序后,发生了报错。报错信息为:".\int+int.go:14:28: cannot use "8" (type untyped string) as type int in argument to myFunction",原因是因为在需要 int 类型的地方使用了字符串。
1.3 使用布尔类型
对类型有了基本认识后,就可以探索 Go 是如何实现一些基本数据类型了。首先来看布尔类型。布尔值只能为 true 或 false。虽然有些语言允许使用值 1 和 0 来表示 true 和 false,但 Go 语言不允许。可像下面这样声明布尔变量:
var a bool
如果没有给布尔变量赋值,它将默认为 false:
布尔变量可在声明后重新赋值,它们是很有用的编程元素:
1.4 理解数值类型
对编程来说,数值不可或缺。然而,如果您没有计算机科学或数学方面的知识,可能对有些术语感到迷惑。您可能听说过浮点数、整数、无符号整数、8位、64位、bigint、smallint、tinyint,这些都是整型(数值)类型。要明白这些术语的含义,必须知道数字在计算机内部是以二进制位的方式存储的。二进制位就是一些列布尔值,取值要么为1,要么为0。1位可表示1或0,对于4位整数,下面对其二进制位和十进制位表示做了比较。从该表可知,4位可表示16个不同的数字:
4位的无符号整数
二进制 | 十进制 |
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
二进制 | 十进制 |
0111 | 7 |
1000 | 8 |
1001 | 9 |
1010 | 10 |
1011 | 11 |
1100 | 12 |
1101 | 13 |
二进制 | 十进制 |
1110 | 14 |
1111 | 15 |
1.4.1 带符号整数和无符号整数
对于带符号整数,需要使用一位来表示符号,这通常是符号 - 。上表列出了无符号的 4 位整数,其取值范围为 0~15。带符号整数可正可负,因此 4 位带符号整数的取值范围为 -8 到 7。
4位的无符号整数
二进制 | 十进制 |
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
二进制 | 十进制 |
0111 | 7 |
1000 | -8 |
1001 | -7 |
1010 | -6 |
1011 | -5 |
1100 | -4 |
1101 | -3 |
二进制 | 十进制 |
1110 | -2 |
1111 | -1 |
在 Go 语言中,声明整型变量的方式如下:
var i int =3
类型 int 表示带符号整数,因此可正科负。根据计算机的底层体系结构,int 可能是 32 位的带符号整数,也可能是 64 位的带符号整数。除非要处理的数字非常大,或者非常在乎性能,否则只需要使用 int,而无须关心编译器是如何做的。
1.4.2 浮点数
计算语言中将数学中的小数称作浮点数,这是由小数在计算机中的表达形式而产生的叫法(通过小数点的浮动来表示更大范围小数)。
在 Go 语言中常用的浮点数有两种:float32 和float64。其中,float32 类型的浮点数由 32 个二进制位组成的,而 float64 则是由 64 个二进制位组成的。显而易见,float64 类型的浮点数可以表达的数字范围更大或者精度更高。在实际应用中,我们可以自行选择使用 float32 还是 float64 作为变量类型,唯一需要考虑的是 Go 语言标准库或第三方库中的函数要求使用哪种类型。
目前,Go 语言标准库中大多数涉及浮点数计算的函数已经以使用 float64 参数为主,因此,建议定义变量类型时使用 float64 类型。
1.4.3 字符串
字符串可以是任何字符序列,其中的字符可能是数字、字母和字符。下面是一些简单的字符串:
- cow
- $%^&*
- a1234
几乎所有的编程语言都支持字符串,它们通常包含数字、字母或符号。在 Go 语言中,声明并初始化字符串变量很简单。
var a string = 'foo'
字符串变量可以为空,这种变量非常适合用来积累其他变量中的数据以及存储临时数据。
var s string = ""
创建字符串变量后,可将其与其他数据相加,但不能修改原来的值。下面的代码创建个空字符串,再将字符串 foo 附加到末尾,这在 Go 语言中是合法的。
var s sting = ""
s += "foo"
不能对字符串执行数学运算,即便它看起来像个数字。要看对看起来像数字的字符串执行数学运算,必须先将其转换为数字类型。
1.4.4 数组
数组是几乎所有编程语言都支持的另一种数据类型,这是一种比较复杂的类型,因为它包含一系列元素。例如,要表示乐队的成员,使用字符串数组是不错的选择。声明数组时,必须制定其长度和类型。
var beatles [4]string
在这个示例中,方括号内的数字表示数组的长度,而紧跟在方括号后的是数组的类型,这里为字符串:
beatles[0] = "John"
beatles[1] = "Paul"
beatles[2] = "Ringo"
beatles[3] = "George"
数组索引是从 0 开始。在上述变量声明中,指定的数组长度为 4,但访问这个数组的元素时,索引最大为 3.这是因为在所有数组中,索引都从 0 开始。
1.5 检查变量的类型
有些情况下,需要检查变量的类型,为此可使用标准库中 reflect 包,它让您能够访问变量的底层类型。在大多数情况下,编译器能发现类型不正确的情形,但在调试或必须核实底层类型时,reflect 包将很有用。
程序:使用 reflect 包检查变量类型
package main
import (
"fmt"
"reflect"
)
func main() {
var s string = "string"
var i int = 10
var f float32 = 1.2
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(f))
}
1.6 类型转换
将数据从一种类型转换为另一种类型是常见的编程任务,这通常是在从网络或数据库读取数据进行的。Go 标准库提供了良好的类型转换支持。
valueOfTypeB = typeB(valueOfTypeA)
简单的转换操作:
// 浮点 a := 5.0 //转换为 int 类型 b := int(a)
Go允许在底层结构相同的两个类型之间互转。例如:
// blue 类型的底层是 int 类型 type blue int // a的类型为 blue,底层是 int var a blue = 5 // 将a(blue)转换为 int,b现在是 int 类型 b := int(5) // 将b(int)转换为 blue,c现在是 blue 类型 c := blue(b)
- 不是所有数据类型都能转换的,例如字母格式的string类型"abcd"转换为int肯定会失败
- 低精度转换为高精度时是安全的,高精度的值转换为低精度时会丢失精度。例如int32转换为int16,float32转换为int
- 这种简单的转换方式不能对int(float)和string进行互转,要跨大类型转换,可以使用strconv包提供的函数
strconv 包提供了一整套类型转换方法,可用于转换为字符串或字符串转换为其他类型。这个包里提供了很多函数,大概分为几类:
- 字符串转int:Atoi()
- int转字符串: Itoa()
- ParseTP类函数将string转换为TP类型:ParseBool()、ParseFloat()、ParseInt()、ParseUint()。因为string转其它类型可能会失败,所以这些函数都有第二个返回值表示是否转换成功
- FormatTP类函数将其它类型转string:FormatBool()、FormatFloat()、FormatInt()、FormatUint()
- AppendTP类函数用于将TP转换成字符串后append到一个slice中:AppendBool()、AppendFloat()、AppendInt()、AppendUint()
1.6.1 strconv:string 和 int 转换
最常见的是字符串和 int 之间的转换。
1、 int 转换为字符串:Itoa()
// Itoa(): int -> string println("a" + strconv,Itoa(32)) //a32
2、string 转换为 int:Atoi()
func Atoi(s string) (int, error)
由于string可能无法转换为int,所以这个函数有两个返回值:第一个返回值是转换成int的值,第二个返回值判断是否转换成功。
// Atoi(): string -> int i,_ := strconv.Atoi("3") println(3 + i) // 6 // Atoi()转换失败 i,err := strconv.Atoi("a") if err != nil { println("converted failed") }
1.6.2 strconv:Parse类函数
Parse类函数用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()。
由于字符串转换为其它类型可能会失败,所以这些函数都有两个返回值,第一个返回值保存转换后的值,第二个返回值判断是否转换成功。
b, err := strconv.ParseBool("true") f, err := strconv.ParseFloat("3.1415", 64) i, err := strconv.ParseInt("-42", 10, 64) u, err := strconv.ParseUint("42", 10, 64)
ParseFloat()只能接收float64类型的浮点数。
ParseInt()和ParseUint()有3个参数:
func ParseInt(s string, base int, bitSize int) (i int64, err error) func ParseUint(s string, base int, bitSize int) (uint64, error)
1.6.3 strconv:Format类函数
将给定类型格式化为string类型:FormatBool()、FormatFloat()、FormatInt()、FormatUint()。
//FormatInt()和FormatUint()有两个参数: func FormatInt(i int64, base int) string func FormatUint(i uint64, base int) string
1.6.4 strconv:Append类函数
AppendTP类函数用于将TP转换成字符串后append到一个slice中:AppendBool()、AppendFloat()、AppendInt()、AppendUint()。
Append类的函数和Format类的函数工作方式类似,只不过是将转换后的结果追加到一个slice中。