在 Go 编程语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,
就可以充分利用内存。
Go 语言按类别有以下几种数据类型:
序号 | 类型和描述 |
---|---|
1 | 布尔型:布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
2 | 数字类型:整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位、的运算采用补码。 |
3 | 字符串类型:字符串就是一串固定长度的字符连接起来的字符序列,Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
4 | 派生类型:包括:(a) 指针类型(Pointer)、(b) 数组类型、© 结构化类型(struct)、(d) Channel 类型、(e) 函数类型、(f) 切片类型、(g) 接口类型(interface)、(h) Map 类型 |
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
序号 | 类型和描述 |
---|---|
1 | uint8 无符号 8 位整型 (0 到 255) |
2 | uint16 无符号 16 位整型 (0 到 65535) |
3 | uint32 无符号 32 位整型 (0 到 4294967295) |
4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) |
5 | int8 有符号 8 位整型 (-128 到 127) |
6 | int16 有符号 16 位整型 (-32768 到 32767) |
7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) |
8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
以下列出了其他更多的数字类型:
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
go 1.9
版本对于数字类型,无需定义int及float32、float64,系统会自动识别。
package main
import "fmt"
func main() {
var a = 1.5
var b = 2
// 1.5 2
fmt.Println(a, b)
}
在 Go 中,布尔值的类型为 bool,值是 true 或 false,默认为 false。
package main
import "fmt"
// 全局变量声明
var isActive bool
// 忽略类型的声明
var enabled, disabled = true, false
func main() {
// 一般声明
var available bool
// 简短声明
valid := false
// 赋值操作
available = true
// false
fmt.Println(isActive)
// true
fmt.Println(enabled)
// false
fmt.Println(disabled)
// false
fmt.Println(valid)
// true
fmt.Println(available)
}
strings
库包含了许多高效的字符串常用操作的函数和方法,巧用这些函数与方法,能极大的提高我们程序的性
能。
package main
import (
"fmt"
"strings"
)
func main() {
// 字符串去除空格和换行符
str := "这里是 www\n.baidu\n.com"
fmt.Println("-------- 原字符串 ----------")
/*
这里是 www
.baidu
.com
*/
fmt.Println(str)
// 去除空格
str = strings.Replace(str, " ", "", -1)
// 去除换行符
str = strings.Replace(str, "\n", "", -1)
fmt.Println("-------- 去除空格与换行后 ----------")
// 这里是www.baidu.com
fmt.Println(str)
}
变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。
变量可以通过变量名访问。
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
声明变量的一般形式是使用 var 关键字:
var identifier type
可以一次声明多个变量:
var identifier1, identifier2 type
package main
import "fmt"
func main() {
var a string = "Runoob"
// Runoob
fmt.Println(a)
var b, c int = 1, 2
// 1 2
fmt.Println(b, c)
}
第一种,指定变量类型,如果没有初始化,则变量默认为零值。
var v_name v_type
v_name = value
零值就是变量没有做初始化时系统默认设置的值。
package main
import "fmt"
func main() {
// 声明一个变量并初始化
var a = "RUNOOB"
// RUNOOB
fmt.Println(a)
// 没有初始化就为零值
var b int
// 0
fmt.Println(b)
// bool 零值为 false
var c bool
// false
fmt.Println(c)
}
0
false
""
(空字符串)var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
// error 是接口
var a error
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
// 0 0 false ""
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
第二种,根据值自行判定变量类型。
var v_name = value
package main
import "fmt"
func main() {
var d = true
// true
fmt.Println(d)
}
第三种,如果变量已经使用 var 声明过了,再使用 :=
声明变量,就产生编译错误,格式:
v_name := value
例如:
var intVal int
// 这时候会产生编译错误,因为intVal已经声明,不需要重新声明
intVal :=1
直接使用下面的语句即可:
// 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句
intVal := 1
intVal := 1 相等于:
var intVal int
intVal =1
可以将 var f string = "test"
简写为 f := "test"
:
package main
import "fmt"
func main() {
// var f string = "test"
// var f string,f = "test"
f := "test"
// test
fmt.Println(f)
}
//类型相同多个变量,非全局变量
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
// 这种因式分解关键字的写法一般用于声明全局变量
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"
// 0 0 0 false 1 2 123 hello 123 hello
println(x, y, a, b, c, d, e, f, g, h)
}
所有像 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 := 20 就是不被允许的,
编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个
新的值。
如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量
a:
package main
import "fmt"
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
尝试编译这段代码将得到错误 a declared but not used
。
此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用
fmt.Println("hello, world", a)
会移除错误。
但是全局变量是允许声明但不使用的。 同一类型的多个变量可以声明在同一行,如:
var a, b, c int
多变量可以在同一行进行赋值,如:
var a, b int
var c string
a, b, c = 5, 7, "abc"
上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:
a, b, c := 5, 7, "abc"
右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是abc。
这被称为并行或同时赋值。
如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。
空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你
并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:
val, err = Func1(var1)。
空白标识符在函数返回值时的使用:
package main
import "fmt"
func main() {
//只获取函数返回值的后两个
_, numb, strs := numbers()
// 2 str
fmt.Println(numb, strs)
}
// 一个可以返回多个值的函数
func numbers() (int, int, string) {
a, b, c := 1, 2, "str"
return a, b, c
}
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
const identifier [type] = value
你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
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
// 面积为 : 50
fmt.Printf("面积为 : %d\n", area)
// 1 false str
println(a, b, c)
}
常量还可以用作枚举:
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() {
// abc 3 16
println(a, b, c)
}
字符串字节长度:字符串类型在 go 里是个结构,包含指向底层数组的指针和长度,这两部分每部分都是 8 个字
节,所以字符串类型大小为 16 个字节。
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数
一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形
式:
const (
a = iota
b
c
)
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
)
// 0 1 2 ha ha 100 100 7 8
fmt.Println(a, b, c, d, e, f, g, h, i)
}
再看个有趣的的 iota 实例:
package main
import "fmt"
const (
i = 1 << iota
j = 3 << iota
k
l
)
func main() {
// i= 1
fmt.Println("i=", i)
// j= 6
fmt.Println("j=", j)
// k= 12
fmt.Println("k=", k)
// l= 24
fmt.Println("l=", l)
}
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:左移 3 位,变为二进制 11000,即 24。
左移运算符 <<
是双目运算符。左移 n 位就是乘以 2 的 n 次方。 其功能把 << 左边的运算数的各二进位全部左
移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补 0。
右移运算符 >> 是双目运算符。右移 n 位就是除以 2 的 n 次方。 其功能是把 >> 左边的运算数的各二进位全部右
移若干位, >> 右边的数指定移动的位数。
在定义常量组时,如果不提供初始值,则表示将使用上行的表达式。
package main
import "fmt"
const (
a = 1
b
c
d
)
func main() {
// 1
fmt.Println(a)
// b、c、d没有初始化,使用上一行(即a)的值
// 1
fmt.Println(b)
// 1
fmt.Println(c)
// 1
fmt.Println(d)
}
iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
package main
const (
i = iota
j = iota
x = iota
)
const xx = iota
const yy = iota
func main() {
// 0 1 2 0 0
println(i, j, x, xx, yy)
}
其它常见的写法:
// 类似枚举的iota
const (
c0 = iota // c0 = 0
c1 = iota // c0 = 1
c2 = iota // c0 = 2
)
// 简写模式
const (
c0 = iota // c0 = 0
c1 // c0 = 1
c2 // c0 = 2
)
// 注意iota逐行增加
const (
a = 1 << iota // a = 1 (iota = 0)
b = 1 << iota // b = 1 (iota = 1)
c = 3 // c = 3 (iota = 2, unused)
d = 1 << iota // d = 8 (iota = 3)
)
const(
u = iota * 42 // u == 0 (untyped integer constant)
v float = iota * 42 // v == 42.0 (float64 constant)
w = iota * 42 // w == 84 (untyped integer constant)
)
// 分开的const语句, iota每次从0开始
const x = iota // x = 0
const y = iota // y = 0
type newtype oldtype
newtype 和 oldtype 是两个完全不同的类型,newtype 不会继承 oldtype 的方法。无论 oldtype 是什么类型,使
用 type 声明的新类型都是一种命名类型,也就是说,自定义类型都是命名类型。
// INT是一个使用预声明类型声明的自定义类型
type INT int
// Map是一个使用类型字面量声明的自定义类型
type Map map[string]string
// myMap是一个自定义类型Map声明的自定义类型
type myMap Map
// INT、Map、myMap 都是命名类型
package main
import (
"fmt"
)
type Map map[string]string
func (m Map) Print() {
for _, key := range m {
fmt.Println(key)
}
}
type iMap Map
func (m iMap) Print() {
for _, key := range m {
fmt.Println(key)
}
}
type slice []int
func (s slice) Print() {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
mp := make(map[string]string, 10)
mp["hi"] = "tata"
var ma Map = mp
// 如下语句不能通过编译
//var im iMap = ma
var im iMap = (iMap)(ma)
//var im iMap = mp
ma.Print()
im.Print()
//Map实现了Print()所以其可以赋值给接口变量
var i interface {
Print()
} = ma
i.Print()
s1 := []int{1, 2, 3}
var s2 slice
s2 = s1
s2.Print()
s := "hello,世界!"
var a []byte
a = []byte(s)
var b string
b = string(a)
var c []rune
c = []rune(s)
fmt.Printf("%T\n", a)
fmt.Printf("%T\n", b)
fmt.Printf("%T\n", c)
}
# 程序输出
tata
tata
tata
1
2
3
[]uint8
string
[]int32
由于 Go 是强类型的语言,如果不满足自动转换的条件,则必须进行强制类型转换。任意两个不相干的类型如果进
行强制转换,则必须符合一定的规则。
类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:
type_name(expression)
type_name 为类型,expression 为表达式。
// 以下实例中将整型转化为浮点型,并计算结果,将结果赋值给浮点型变量
package main
import "fmt"
func main() {
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum) / float32(count)
// mean 的值为: 3.400000
fmt.Printf("mean 的值为: %f\n", mean)
}
// go 不支持隐式转换类型
package main
import "fmt"
func main() {
var a int64 = 3
var b int32
b = a
fmt.Printf("b 为 : %d", b)
}
此时会报错:
cannot use a (variable of type int64) as type int32 in assignment
但是如果改成 b = int32(a) 就不会报错了:
// 如果改成 b = int32(a) 就不会报错了
package main
import "fmt"
func main() {
var a int64 = 3
var b int32
b = int32(a)
// b 为 : 3
fmt.Printf("b 为 : %d", b)
}
注意:
(1)、数值类型和 string 类型之间的相互转换可能造成值部分丢失;其他的转换仅是类型的转换,不会造成值的改
变。string 和数字之间的转换可使用标准库 strconv。
(2)、Go语言没有语言机制支持指针和 interger 之间的直接转换,可以使用标准库中的 unsafe 包进行处理。