编写程序的目的之一就是处理数据,本章主要介绍 Go 的基础数据类型。
2.1 什么是数据类型
数据类型定义了编程语言、编译器、数据库以及代码在执行时如何操作和处理数据的。例如,数据类型是数字,则可以执行数学运算。编程语言和数据库会根据数据类型向开发者提供功能。大多数编程语言提供了处理通用数据的标准库。数据库提供有查询语言,以便开发者与基础数据类型进行交互、查询数据。数据类型不管是否被明确声明,它们都是编程和计算的重要构成。
2.2 静态和动态语言
编程语言可分为静态类型(Static Typing)和动态类型(Dynamic Typing)。如果在数据类型使用不正确时,编译器会抛出错误,则这类语言为静态类型(也称为强类型)。如果运行时将语言从一种类型转换为另一种类型以执行程序,或者编译器未强制执行类型系统,则为动态类型(也称弱类型)。就像不能争论出科比更强还是詹姆斯更厉害一样,我们也无法得出哪种类型的语言更好。有些人重视静态类型语言的正确性和安全性,另一些人则喜欢动态类型语言简化了开发速度。
静态类型语言的优点:
性能比动态类型语言更好
错误通常可由编译器捕获
更好的数据完整性
编辑器可以提供代码完成及其他工具
动态类型语言的优点:
开发速度通常更快
无需等待编译器编译代码
学习曲线短
风格灵活
Go 属于静态语言,具有数据类型的概念,数据类型要么由开发人员明确声明,要么由编译器推断:
package main
import (
"fmt"
)
func sayHello(s string) string {
return "Hello " + s
}
func main() {
fmt.Println(sayHello("Curry"))
}
不用担心看不懂上面这段代码。重要的是 sayHello 这个函数,声明了一个参数 s,s 的数据类型是 string,函数返回值也是 string。因此,当编译器编译程序时,它会检查传递给函数的参数是否为 string 类型。如果不是 string 类型,编译器将报错,进而避免将错误展示给用户。
为了更直观的比较动态语言和静态语言,我们看一段 JavaScript 的代码,JavaScript 是比较流行的动态类型语。如果你不了解它,也没有关系,重点是观察它处理不同数据类型的方式:
var addition = function (a, b) {
return a + b;
}
给 addition 函数传递两个数字,我们能得到一个正确的结果:
addition(1, 3)
4
如果传递一个数字,一个字符串,则会返回一个奇怪的结果:
addition(1,"three")
1three
执行 addition 后,返回了一个字符串。虽然 JavaScript 有数据类型的概念,但在使用方式上却不做限制。在这个示例中,JavaScript 将数字类型强制转换成了字符串,返回字符串“1three”。看上去用法很灵活,却极有可能导致错误。
假设程序中使用了 addition 函数对输入进行计算,并将结果存储在数据库中。数据库也有数据类型的概念,如果在数据库中存储该执行结果的字段类型为整数,那么我们必须传递整数给数据库。而 JavaScript 的 addition 函数可以返回字符串或整数。如果用户输入的是字符串,那么一个字符串类型的数据将被插入数据库字段中,与数据库期望的数据类型不同,进而引发错误。更糟糕的是,此错误是运行时错误,意味着错误将直接影响用户。除非在程序中对错误有相应的处理,否则很可能会导致程序崩溃。
我们来看 Go 的示例,在 Go 中创建相同功能的函数,并声明了参数及返回值的数据类型。仅通过代码就能确定,addition 函数接收两个 int 类型的参数,并返回一个 int 类型的值。如果开发者错误的将 string 类型传递给了 addition 函数,编译器将捕获错误。
package main
import (
"fmt"
)
func addition(x int, y int) int {
return x + y
}
func main() {
fmt.Println(addition(1, 3))
}
我们试着传递一个非 int 类型的数据,就会引发编译错误,帮助我们在程序提供给用户前发现错误:
package main
import (
"fmt"
)
func addition(x int, y int) int {
return x + y
}
func main() {
var s string = "three"
fmt.Println(addition(1, s))
}
执行这段代码:
$ go run addition.go
cannot use s (type string) as type int in argument to addition
Go 编译器还可以捕获其他错误,例如传递的参数个数不匹配。
2.3 布尔类型
在上面的示例中,我们接触了 Go 的数据类型。接下来,开始探索 Go 都有哪些基础数据类型的。首先是布尔类型,Go 中的布尔类型的值是 true 或 false。
我们声明一个变量名为 b 的布尔类型:
var b bool
在声明变量的时候,如果未给变量赋值,Go 默认会为变量分配一个该类型的零值,布尔类型的零值为 false,我们可以执行 example1.go 看下结果:
package main
import (
"fmt"
)
func main() {
var b bool
fmt.Println(b)
}
$ go run example1.go
false
2.4 数字类型
数字是编程的基础,但如果你没有计算机或数学背景,可能会混淆某些术语。你可能听说过整数、有符号整数、无符号整数、浮点数、32位、64位、byte、rune。这些都是数字类型。要了解这些术语,首先要明白数字是以位的形式存储在计算机中。位是一系列0或1的布尔值,一个位可以是0或1。4个原始位与十进制的对照如表2-1,可以看到4个原始位总共对应16个二进制数字。
二元 | 小数 |
---|---|
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 |
表2-1
2.4.1 有符号和无符号整数
对于有符号整数,可以将其中的一位用作表示其值的符号——负号。表2-1表示无符号整数,其值范围是0到15。有符号整数的值可以是负数或正数,其值范围是-8到7。表2-2是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 |
表2-2
声明整数类型:
var i int = 1
Int 类型是带符号的整数,因此支持正数和负数。根据计算机的基础体系结构,int 可以是带符号的 32 位整数或带符号的 64 位整数。
2.4.2 浮点数
浮点数就是包含小数点的数字,如11.1、12.22、3.1415926。int只支持整数类型,因此要使用小数,必须使用浮点数类型。Go 中的浮点数也可以是 32 位或 64 位。对于大多数现代 CPU,建议使用 float64。
声明浮点数类型:
var f float64 = 0.111
Go 的数字类型中还包含复数类型,但比较少用,这里就不再介绍了。
2.5 字符串类型
String 类型可以是任何字符序列:数字、字符、字母。String 的简单示例:cow、$^#%、a13453。
声明字符串类型:
var s string = "魏文弟"
2.6 检查变量的数据类型
有时,编译器会捕获到类型错误,处于调试或验证目的,我们需要检查变量的数据类型,根据检查结果再做下一步操作。
使用 fmt 包的 Printf 函数做变量数据类型检查:
package main
import (
"fmt"
)
func main() {
var s string = "魏文弟"
var i int = 1
var f float64 = 11.1
fmt.Printf("%T\n", s)
fmt.Printf("%T\n", i)
fmt.Printf("%T\n", f)
}
也可以使用 reflect 包进行类型检查:
package main
import (
"fmt"
"reflect"
)
func main() {
var s string = "魏文弟"
var i int = 1
var f float64 = 11.1
fmt.Println(reflect.TypeOf(s))
fmt.Println(reflect.TypeOf(i))
fmt.Println(reflect.TypeOf(f))
}
2.7 类型转换
常见的编译任务是将一种数据类型转换为另一种。通常,这些数据是从网络、文件或数据库中读取的。Go 的 strconv 标准库对类型转换提供了良好的支持。
假设变量s作为字符串类型,其值为”true”,我们需要它和一个布尔值进行比较,这就需要我们先把s转换成布尔类型,因为 Go 中不同类型是不能进行比较的。
var s string = "true"
b, err := strconv.ParseBool(s)
变量 b 是 bool 类型,同样,也可以将布尔类型转换为字符串类型:
strconv.FormatBool(b)
完整转换示例:
package main
import (
"fmt"
"strconv"
)
func main() {
var s string = "true"
b, err := strconv.ParseBool(s)
fmt.Printf("%T, %#v\n", b, err)
}
数据类型错误,是编译过程中常见的错误,使用数据源时,值得花一些时间来确认数据类型是正确的。如果数据源是数据库,请先确认数据库的字段类型,这可以节省大量的调试时间!
2.8 总结
这一章主要介绍了 Go 的基础数据类型,了解了静态和动态编程语言间的区别及其优缺点。介绍了如何检查和转换变量的数据类型,Go 不支持隐式的数据类型转换。如果你没有编程基础,可能一些知识超出了你的认知范围,没有关系,请继续往下看。