数据类型简介
基本数据类型(原生数据类型):
整型,浮点型,布尔型,字符串,字符(byte,rune)
复合数据类型(派生数据类型)
指针(pointer), 数组(array) , 切片(slice) , 映射(map) , 函数(function), 结构体(struct) , 通道(channel)
关键字
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
标识符
标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_
、和数字组成,且第一个字符必须是字母。通俗的讲就是凡可以自己定义的名称都可以叫做标识符。
下划线_
是一个特殊的标识符,称为空白标识符,它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用_
作为变量对其它变量进行赋值或运算。
在使用标识符之前必须进行声明,声明一个标识符就是将这个标识符与常量、类型、变量、函数或者代码包绑定在一起。在同一个代码块内标识符的名称不能重复。
标识符的命名需要遵守以下规则:
- 由 26 个英文字母、0~9、
_
组成; - 不能以数字开头,例如 var 1num int 是错误的;
- Go语言中严格区分大小写;
- 标识符不能包含空格;
- 不能以系统保留关键字作为标识符,比如 break,if 等等。
命名标识符时还需要注意以下几点:
- 标识符的命名要尽量采取简短且有意义;
- 不能和标准库中的包名重复;
- 为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;
当然Go语言中的变量、函数、常量名称的首字母也可以大写,如果首字母大写,则表示它可以被其它的包访问(类似于 Java 中的 public);如果首字母小写,则表示它只能在本包中使用 (类似于 Java 中 private)。
在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。
布尔类型bool
一个简单的例子
package main
import "fmt"
var b bool = true
func main() {
fmt.Println(b)
}
Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口interface,它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。 布尔型的常量和变量也可以通过和逻辑运算符(非
!
、和&&
、或||
)结合来产生另外一个布尔值,这样的逻辑语句就其本身而言,并不是一个完整的 Go 语句。 逻辑值可以被用于条件结构中的条件语句(第 5 章),以便测试某个条件是否满足。另外,和&&
、或||
与相等==
或不等!=
属于二元运算符,而非!
属于一元运算符。在接下来的内容中,我们会使用 T 来代表条件符合的语句,用 F 来代表条件不符合的语句。
Go语言中包含以下逻辑运算符
// 非运算符 !
!T -> false
!F -> true
// 非运算符用于取得和布尔值相反的结果。
// 和运算符 %%
T && T -> true
T && F -> false
F && T -> false
F && F -> false
// 只有当两边值都为true时,和运算符结果才是true
// 或运算符: ||
T || T -> true
T || F -> true
F || T -> true
F || F -> false
// 只有当两边的值都为 false 的时候,或运算符的结果才是 false,其中任意一边的值为 true 就能够使得该表达式的结果为 true
// 对于布尔值的好的命名能够很好地提升代码的可读性,例如以 is 或者 Is 开头的 isSorted、isFinished、isVisible,
package main
import "fmt"
var isBoy string = "youmen"
var isGirl string = "youmen"
func main() {
fmt.Println("&&")
fmt.Println(true || true)
// 关系运算
fmt.Println(isBoy == isGirl)
fmt.Println(isBoy != isGirl)
}
字符类型(byte和runne)
代码语言:javascript复制字符串的每一个元素叫做"字符",在遍历或者单个获取字符串元素时可以获得字符 Go语言的字符有以下两种: 一种是uint8类型,或者叫byte型, 代表了ASCII码的一个字符. 另一个是rune类型, 代表了一个UTF-8字符,当需要处理中文,日文或者其他复合字符时,则需要用到runne类型,runne类型等价于int32类型. byte类型是uint8的别名,对于只占用1个字节的传统ASCII编码的字符来说,完全没问题,例如 var ch byte='A',字符使用单引号括起来. 在 ASCII 码表中,A 的值是 65,使用 16 进制表示则为 41,所以下面的写法是等效的:
// var ch byte = 65 或 var ch byte = 'x41' //(x 总是紧跟着长度为 2 的 16 进制数)
另外一种可能的写法是
后面紧跟着长度为 3 的八进制数,例如 377。 Go语言同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U hhhh 来表示,其中 h 表示一个 16 进制数。 在书写 Unicode 字符时,需要在 16 进制数之前加上前缀
u
或者U
。因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则使用u
前缀,如果需要使用到 8 个字节,则使用U
前缀。
Example1
func main() {
var ch int = 'u0041'
// integer
fmt.Println(ch)
// utf-8 bytes
fmt.Printf("%X - %X - %Xn",ch)
// utf-8 code point
fmt.Printf("%U - %U - %U",ch)
}
// 输出
// 65
// 41 - %!X(MISSING) - %!X(MISSING)
// U 0041 - %!U(MISSING) - %!U(MISSING)
// Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):
// 判断是否为字母:unicode.IsLetter(ch)
// 判断是否为数字:unicode.IsDigit(ch)
// 判断是否为空白符号:unicode.IsSpace(ch)
Utf-8和unicode有何区别
Unicode与ASCII类似,都是一种字符集 字符集为每个字符分配一个唯一的ID, 我们使用的所有字符在Unicode字符集中都有一个唯一的ID, 例如上面的a在Unicode与ASCII的编码都是97,汉字"你"在unicode中的编码是20320,在不同国家的字符集中,字符所对应的ID也不同,而无论任何情况,Unicode中的字符的ID都是不会变化的. Utf-8是编码规则,将unicode中字符的id以某种方式进行编码, utf-8的是一种变长编码规则,从1到4个字节不等,编码规划如下:
- 0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
- 从 128 到 0x10ffff 表示其他字符。
根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节。 广义的 Unicode 指的是一个标准,它定义了字符集及编码规则,即 Unicode 字符集和 UTF-8、UTF-16 编码等。
数字类型
整型和浮点型float
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码 Go 也有基于架构的类型,例如:int、uint 和 uintptr。 这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int和uint
代码语言:javascript复制# int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。
# uintptr 的长度被设定为足够存放一个指针即可。
那些情况下使用int和uint
程序逻辑对整型范围没有特殊需求,例如, 对象的长度使用内建len()函数返回,这个长度可以根据不同平台的字节长度进行变化,切片map的元素数量都可以用int来表示 反之,在二进制传输,读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int和uint.
Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。
与操作系统架构无关的类型都有固定的大小,并在类型的名称中就可以看出来:
整数
代码语言:javascript复制int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数
代码语言:javascript复制uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
浮点型
代码语言:javascript复制float32( - 1e-45 -> - 3.4 * 1e38)
float64( - 5 * 1e-324 -> 107 * 1e308)
int型是计算最快的一种类型
代码语言:javascript复制整型的零值为 0,浮点型的零值为 0.0。 float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用
==
或者!=
来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。 你应该尽可能地使用 float64,因为math
包中所有有关数学运算的函数都会要求接收这个类型。Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):
package main
import "fmt"
func main() {
var a int
var b int32
a = 15
b = a a
b = b 5
fmt.Println(a)
}
// 如果你尝试编译该程序,则将得到编译错误 cannot use a a (type int) as type int32 in assignment。
// 同样地,int16 也不能够被隐式转换为 int32。
// 我们可以通过显式转换来避免这个问题
package main
import "fmt"
func main() {
var a int16 = 34
var m int32
m = int32(a)
fmt.Println(m,a)
}
数字值转换
代码语言:javascript复制当进行类似
a32bitInt = int32(a32Float)
的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。下面这个例子展示如何安全地从 int 型转换为 int8:
func Uint8FromInt(n int) (uint8, error) {
if 0 <= n && n <= math.MaxUint8 { // conversion is safe
return uint8(n), nil
}
return 0, fmt.Errorf("%d is out of the uint8 range", n)
}
或者安全的从float64转换成int
func IntFromFloat64(x float64) int {
if math.MinInt32 <= x && x <= math.MaxInt32 { // x lies in the integer range
whole, fraction := math.Modf(x)
if fraction >= 0.5 {
whole
}
return int(whole)
}
panic(fmt.Sprintf("%g is out of the int32 range", x))
}
// 大转小,一般会引发panic异常
复数
代码语言:javascript复制// complex64 (32 位实数和虚数)
// complex128 (64 位实数和虚数)
package main
import "fmt"
func main() {
var c1 complex64 = 5 10i
fmt.Println("The value is: %v",c1)
}
c = complex(re, im)
// 函数 real(c) 和 imag(c) 可以分别获得相应的实数和虚数部分。
// 复数使用 re imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。
在使用格式化说明符时,可以使用
%v
来表示复数,但当你希望只表示其中的一个部分的时候需要使用%f
。 复数支持和其它数字类型一样的运算。当你使用等号==
或者不等号!=
对复数进行比较运算时,注意对精确度的把握。cmath
包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。
打印格式化
通用
代码语言:javascript复制# %v 值的默认格式表示value
# % v 类似%v,但输出结构体时会添加字段名
# %#v 打印包含字段和限定类型名称在内的实例的完整信息
# %T 打印某个类型的完整说明.
# 使用panic 获取栈跟踪信息(直到panic时所有调用函数的列表)
# 使用关键字defer跟踪代码执行过程
布尔值
代码语言:javascript复制# %t 单词true 或false true
整数
代码语言:javascript复制# %b 表示为二进制 binary
# %c 该值对应的unicode码值 char
# %d 表示为十进制 digital
# � 表示该整型长度是8,不足8则在数值前补空格。如果超出8,则以 实际为准。
# d数字长度是8,不足8位的,在数字前补0。如果超出8,则以实际为 准。
# %o 表示为八进制octal
# %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示quotation
# %x 表示为十六进制,使用a-f hex
# %X 表示为十六进制,使用A-F
# %U 表示为Unicode格式:U 1234,等价于"U X" Unicode
浮点数和复数
代码语言:javascript复制# %b 无小数部分、二进制指数的科学计数法,如-123456p-78 ;参见strconv.FormatFloat
# %e (=%.6e)有6位小数部分的科学计数法,如-1234.456e 78
# %E 科学计数法,如-1234.456E 78
# %f (=%.6f)有6位小数部分,如 123.456123 float
# %F 等价于%f
# %g 根据实际情况采用%。或%£格式(以获得更简洁、准确的输出)
# %G 根据实际情况采用%£或%「格式(以获得更简洁、准确的输出)
字符串和byte
代码语言:javascript复制# %s 直接输出字符串或者[]byte string
# %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
# %x 每个字节用两字符十六进制数表示(使用a-f)
# %X 每个字节用两字符十六进制数表示(使用A-F) (六)、指针
# %p 表示为十六进制,并加上前导的Ox pointer
运算符
逻辑运算符
代码语言:javascript复制Go 中拥有以下逻辑运算符:
==
、!=
、<
、<=
、>
、>=
。 它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值bool
。例如:
b3:= 10 > 5 // b3 is true
算术运算符
常见可用于整数和浮点数的二元运算符有
、
-
、*
和/
。 (相对于一般规则而言,Go 在进行字符串拼接时允许使用对运算符的重载,但 Go 本身不允许开发者进行自定义的运算符重载)
/
对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2
。 取余运算符只能作用于整数:9 % 4 -> 1
。 整数除以 0 可能导致程序崩溃,将会导致运行时的恐慌状态(如果除以 0 的行为在编译时就能被捕捉到,则会引发编译错误);第 13 章将会详细讲解如何正确地处理此类情况。 浮点数除以 0.0 会返回一个无穷尽的结果,使用Inf
表示。
运算符与优先级
代码语言:javascript复制有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 运算符
7 ^ !
6 * / % << >> & &^
5 - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||
// 当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。
类型别名
代码语言:javascript复制当你在使用某个类型时,你可以给它起另一个名字,然后你就可以在你的代码中使用新的名字(用于简化名称或解决名称冲突)。 在
type TZ int
中,TZ 就是 int 类型的新名称(用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据。
package main
import "fmt"
type TZ int
func main() {
var a, b TZ = 3, 4
c := a b
fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}
实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法;TZ 可以自定义一个方法用来输出更加人性化的时区信息。