Go 语言数据类型
- 在 Go 编程语言中,数据类型用于声明函数和变量。
- 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
- Go 语言按类别有以下几种数据类型:
- 布尔类型 - 它们是布尔类型,由两个预定义常量组成:(a) true (b) false
- 数字类型 - 它们是算术类型,在整个程序中表示:a)整数类型或 b)浮点值
- 字符串类型 - 字符串类型表示字符串值的集合。它的值是一个字节序列。 字
符串是不可变的类型,一旦创建后,就不可能改变字符串的内容。预先声明的
字符串类型是 string 。派生类型: - 包括(a)指针类型,(b)数组类型,(c)结构类型,(d)联合类型和
- (e)函数类型(f)切片类型(g)函数类型(h)接口类型(i) 类型
数组类型和结构类型统称为聚合类型。函数的类型指定具有相同参数和结果类型的所有函数的集合。我们将在下一节中看到基本类型,而其他类型将在后续章节中介绍 。
1.数字类型
预定义与体系结构无关的整数类型是:
- uint8 - 无符号 8 位整数(0 到 255)
- uint16 - 无符号 16 位整数(0 到 65535)
- uint32 - 无符号 32 位整数(0 至 4294967295)
- uint64 - 无符号 64 位整数(0 至 18446744073709551615)
- int8 - 带符号的 8 位整数(-128 到 127)
- int16 - 带符号的 16 位整数(-32768 到 32767)
- int32 - 带符号的 32 位整数(-2147483648 至 2147483647)
- int64 - 带 符 号 的 64 位 整 数 (-9223372036854775808 至9223372036854775807)
2.浮点类型
预定义与体系结构无关的整数类型是:
- float32 - IEEE-754 32 位浮点数
- float64 - IEEE-754 64 位浮点数
- complex64 - 复数带有 float32 实部和虚部
- complex128 - 复数带有 float64 实部和虚部
n 位整数的值是 n 位,并且使用二进制补码算术运算来表示。
3. 其他数字类型
还有一组具有特定大小的数字类型:
- byte - 与 uint8 相同
- rune - 与 int32 相同
- uint - 32 或 64 位
- int - 与 uint 大小相同
- uintptr - 无符号整数,用于存储指针值的未解释位
Go 语言变量
变量只是给程序可以操作的存储区域的名字。Go 中的每个变量都有一个特定的类型,它决定了变量的内存大小和布局; 可以存储在存储器内的值的范围; 以及可以应用于该变量的一组操作。变量的名称可以由字母,数字和下划线字符组成。它必须以字母或下划线开头。大写和小写字母是不同的名称,因为 Go 是区分大小写的。声明变量的一般形式是使用 var 关键字:
var 标识符 变量类型
变量声明
- 指定变量类型,声明后若不赋值,使用默认值。
var 变量名 变量类型变量名 = 值
- 根据值自行判定变量类型。
var 变量名 = 值
- 省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。 这种不带声明格式的只能在函数体中出现
v_name := value
例子:
var a int = 10
var b = 10
c := 10
实例如下:
package main
var a = "wek"
var b string = "laosiji"
var c bool
func main(){
println(a, b, c)
}
结果为:
wek laosiji false
多变量声明
类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
//和 python 很像,不需要显示声明类型,自动推断
var vname1, vname2, vname3 = v1, v2, v3
//出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误
vname1, vname2, vname3 := v1, v2, v3
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
实例如下:
package main
var x, y int //未赋初值,自动设置为 0
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "hello"
//这种不带声明格式的只能在函数体中出现,
//g, h := 123, "hello"
func main(){
g, h := 123, "hello"
println(x, y, a, b, c, d, e, f, g, h)
}
结果为:
0 0 0 false 1 2 123 hello 123 hello
值类型和引用类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:
当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:
你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。值类型的变量的值存储在栈中。内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
这个内存地址称之为指针,这个指针实际上也被存在另外的某一个字中。同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
简短形式,使用 := 赋值操作符
我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50 或 b := false。a 和 b 的类型(int 和 bool)将由编译器自动推断。这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。
注意事项
- 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,当变量 a在之前已经声明过了以后,又输入了 a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。
- 如果你在定义变量 a 之前使用 a=20,则会得到编译错误 undefined: a。
- 如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:
package main
import "fmt"
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
尝试编译这段代码将得到错误 a declared and not used。
4.单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用fmt.Println("hello, world", a)会移除错误。
5.全局变量是允许声明但不使用。 同一类型的多个变量可以声明在同一行,如:var a, b, c int
6.多变量可以在同一行进行赋值,如:a, b, c = 5, 7, "abc",上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:a, b, c := 5, 7, "abc"。右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。这被称为 并行 或 同时 赋值。
7.如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a。空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1函数同时得到:val, err = Func1(var1)。
Go 语言常量
常量是指程序在执行过程中可能不会改变的固定值。 这些固定值也称为文字。常量可以是任何基本数据类型,如整数常量,浮点常量,字符常量或字符串常量。 还有枚举常量。常量一般会被编译器视为常规变量,只是它们的值不能在定义之后被修改。
整型常量
可以是十进制,八进制或十六进制常数。 前缀指定基数:前缀是 0x 或 0X 为十六进制,前缀是 0的为八进制,十进制的前缀则无任何内容。整型常量还可以有一个后缀,它是 U 和 L 的组合,分别用于 unsigned 和 long。后缀可以是大写或小写,可以是任意顺序。这里是一些有效的整型常量的例子:
212 /* 合法 */
215u /* 合法 */
0xFeeL /* 合法 */
078 /* 非法: 8 不是 8 进制中的值 */
032UU /* 非法: 不能添加两个 U */
85 /* 10 进制 */
0213 /* 8 进制 */
0x4b /* 16 进制 */
30 /* int */
30u /* unsigned int */
30l /* long */
30ul /* unsigned long */
浮点型常量
有整数部分,小数点,小数部分和指数部分。您可以以十进制形式或指数形式来表示浮点文字。在使用十进制形式表示时,必须包括小数点,指数或两者,并且在使用指数形式表示时,必须包括整数部分,小数部分或两者。带符号的指数由 e 或 E 引入。
下面是一些浮点文字的示例:
3.14159 /* 合法 */
314159E-5L /* 合法 */
510E /* 非法: 无效的指数 */
210f /* 非法: 没有小数*/
.e55 /* 非法: 没有整数部分 */
8.3 转义序列
Go 中有一些字符,当它们前面有一个反斜杠,它们将具有特殊的意义,它们用于
表示类似换行符(\n)或制表符(\t)。 这里,有一些这样的转义序列代码的列表:
转义序列 含义
\\ \字符
\' '字符
\" "字符
\? ?字符
\a 警报或响铃
\b 退格
\f 换页
\n 新行
\r 回车
\t 水平制表格
\v 水直制表格
\ooo 八位数字一到三位数
\xhh... 一位或多位的十六进制数
以下是显示几个转义序列字符的示例:
package main
import "fmt"
func main() {
fmt.Printf("Hello\tWorld!")
}
当上述代码被编译和执行时,它产生以下结果:
Hello World!
字符串常量
字符串文字或常量用双引号""括起来。字符串包含与字符文字类似的字符:纯字符,转义序列和通用字符。可以使用字符串文字将长行拆分为多个行,并使用空格分隔它们。这里是一些字符串文字的例子。下面这三种形式都是相同的字符串。
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
常量的定义格式:
const 标识符[类型] = 值
你可以省略类型说明符 [ 类型 ],因为编译器可以根据变量的值来推断其类型。
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
多个相同类型的声明可以简写为:
const c_name1, c_name2 = value1, value2
以下实例演示了常量的应用:
package main
import "fmt"
func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" //多重赋值
area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area)
println()
println(a, b, c)
}
以上实例运行结果为:
面积为 : 50
1 false str
常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
数字 0、1 和 2 分别代表未知性别、女性和男性。
常量可以用 len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
以上实例运行结果为:
abc 3 16
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。在每一个 const 关键字出现时,被重置为 0,然后再下一个 const 出现之前,每出现一次 iota,其所代表的数字会自动增加 1。iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1,c=2 可以简写为如下形式:
const (
a = iota
b
c
)
iota用法
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
以上实例运行结果为:
0 1 2 ha ha 100 100 7 8
再看个有趣的的 iota 实例:
package main
import "fmt"
const (
i=1<<iota
j=3<<iota
k
l
)
func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}
i= 1
j= 6
k= 12
l= 24
iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<<表示左移的意思),即:i=1,j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。
简单表述:
- i=1:左移 0 位,不变仍为 1;
- j=3:左移 1 位,变为二进制 110, 即 6;
- k=3:左移 2 位,变为二进制 1100, 即 12;
- l=3:左移 2 位,变为二进制 11000,即 24