写在前面:本文的知识点并非完全的顺序结构,一些代码可能涉及后面的知识。但不论如何,你只需要理解代码真正想表达的内容即可,不必过于深究~
关键字
下面列举的是Go语言中的25个关键字或保留字:
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语言还有36个预定义标识符:
Constants: true false iota nil
Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
Functions: make len cap new append copy close delete
complex real imag
panic recover
基本数据类型
整型
整型分为以下两个大类: 按长度分为:int8、int16、int32、int64
对应的无符号整型:uint8、uint16、uint32、uint64
其中,uint8
就是我们熟知的byte
型(byte == 8bits),int16
对应C语言中的short
型,int32
对应C语言中的int
类型, int64
对应C语言中的long
型。
类型 | 描述 |
---|---|
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) |
特殊整型
类型 | 描述 |
---|---|
uint | 32位操作系统上就是uint32 ,64位操作系统上就是uint64 |
int | 32位操作系统上就是int32 ,64位操作系统上就是int64 |
uintptr | 无符号整型,用于存放一个指针 |
注意:
- 在使用
int
和uint
类型时,不能假定它是32位或64位的整型,而是考虑int
和uint
可能在不同平台上的差异。 - 获取对象的长度的内建
len()
函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int
来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int
和uint
。
数字字面量语法(Number literals syntax)
Go1.13版本之后引入了数字字面量语法,以便于用二进制、八进制或十六进制浮点数的格式定义数字,例如:
v := 0b00101101
, 代表二进制的 101101,相当于十进制的 45。 v := 0o377
,代表八进制的 377,相当于十进制的 255。 v := 0x1p-2
,代表十六进制的 1 除以 2²,也就是 0.25。
而且还允许我们用 _
来分隔数字,比如说: v := 123_456
表示 v 的值等于 123456。
我们可以借助fmt函数来将一个整数以不同进制形式表示:
package main
import "fmt"
func main() {
//十进制
var a int = 10
fmt.Printf("十进制: %d\n", a) // 10
fmt.Printf("二进制: %b\n", a) // 1010
//八进制
var b int = 077
fmt.Printf("八进制: %o\n", b) // 77
//十六进制
var c int = 0xff
fmt.Printf("十六进制小写: %x\n", c) // ff
fmt.Printf("十六进制大写: %X\n", c) // FF
}
浮点型
类型 | 描述 |
---|---|
float32 | IEEE-754标准 32位浮点型数,最大范围约为 3.4e38 |
float64 | IEEE-754标准 64位浮点型数,最大范围约为 1.8e308 |
这两种类型的最大值可以使用常量定义,分别是math.MaxFloat32
和math.MaxFloat64
。
和C语言类似,打印浮点数的时候可以使用%f
来表示:
package main
import (
"fmt"
"math"
)
func main() {
a, b := math.MaxFloat32, math.MaxFloat64
fmt.Println(a, b)
c := 3.1415926
fmt.Printf("c = %f", c)
fmt.Printf("c = %.3f,保留三位小数", c)
}
复数
类型 | 描述 |
---|---|
complex64 | 可表示32 位实数和虚数 |
complex128 | 可表示64 位实数和虚数 |
布尔值
Go语言中以bool
类型进行声明布尔型数据,布尔型数据只有true
和false
两个值。
注意:
- 布尔类型变量的默认值为
false
。 - Go 语言中不允许将整型强制转换为布尔型.
- 布尔型无法参与数值运算,也无法与其他类型进行转换。
字符串
Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8
编码。 字符串的值为""
中的内容,可以在Go语言的源码中直接添加非ASCII码字符。同时,关于字符串连接还有一个小trick:和Java类似,在fmt.Println
中,我们可以用+
来连接字符串:
package main
import "fmt"
func main() {
s1 := "我不知天上宫阙"
s2 := "今夕是何年何月"
fmt.Println(s1 + " " + s2)
}
字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转义符 | 含义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
\' | 单引号(不能直接打) |
\" | 双引号(不能直接打) |
\\ | 反斜杠(不能直接打) |
多行字符串
Go语言中要定义一个多行字符串时,就必须使用反引号
字符:
package main
import "fmt"
func main() {
s1 := `转朱阁
低绮户
照无眠
`
fmt.Println(s1)
}
反引号间换行将被作为字符串中的换行,但所有的义字符都没有影响,文本将会原样输出。
字符串的常用操作
方法 | 介绍 |
---|---|
len(str) | 求长度 |
+或fmt.Sprintf | 拼接字符串 |
strings.Split | 分割 |
strings.contains | 判断是否包含 |
strings.HasPrefix,strings.HasSuffix | 前缀/后缀判断 |
strings.Index(),strings.LastIndex() | 子串出现的位置 |
strings.Join(a[]string, sep string) | join操作 |
byte和rune类型
组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:
var a = '中'
var b = 'x'
Go 语言的字符有以下两种:
uint8
类型,或者叫 byte 型,代表了ASCII码
的一个字符。rune
类型,代表一个UTF-8字符
。
当需要处理中文、日文或者其他复合字符时,则需要用到rune
类型。rune
类型实际是一个int32
。
Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode 的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾。
// 遍历字符串
func traversalString() {
s := "hello沙河"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println()
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r)
}
fmt.Println()
}
输出:
104(h) 101(e) 108(l) 108(l) 111(o) 230(æ) 178(²) 153() 230(æ) 178(²) 179(³)
104(h) 101(e) 108(l) 108(l) 111(o) 27801(沙) 27827(河)
因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个byte数组,所以可以和[]byte
类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。
修改字符串
要修改字符串,需要先将其转换成[]rune
或[]byte
,完成后再转换为string
。无论哪种转换,都会重新分配内存,并复制字节数组。
func changeString() {
s1 := "big"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'p'
fmt.Println(string(byteS1))
s2 := "白萝卜"
runeS2 := []rune(s2)
runeS2[0] = '红'
fmt.Println(string(runeS2))
}
类型转换
Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。
强制类型转换的基本语法如下:
T(表达式)
其中,T表示要转换的类型。表达式包括变量、复杂算子和函数返回值等.
package main
import "fmt"
func main() {
a := 3.1415926
fmt.Println(a) // 3.1415926
b := int(a)
fmt.Println(b) // 3
}
变量
Go语言的变量名由数字、字母与下划线构成。注意:
-
变量名不能以数字开头
-
变量名不能是我们开头提到的关键字
-
匿名变量: 你可能会遇见以“_”作为变量名的情况,“_”就是Go语言中的匿名变量(在Lua等语言中或被称为“哑元变量”)。它的特点如下:
- 匿名变量可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算;
- 匿名变量不占用命名空间,不会分配内存;
- 匿名变量与匿名变量之间不会因为多次声明而无法使用;
- 和人生一样,有些事情你并不需要最终的结果,只需要接住它然后丢掉——那么,使用“_”就是一个很好的选择。
变量声明与初始化
下面,我将用代码和注释来展示在Go语言中如何声明变量。
声明变量一般用var
关键字:
var variable_name type
声明单个变量
package main
import "fmt"
// 第一种赋值方式:指定变量类型。若声明后不赋值,则使用该变量类型下的默认值
var a int = 7
var b bool //默认为false
// 第二种赋值方式:根据值自行判定变量类型
var c = "浮沉匆匆的一瞥"
func main() {
/*
第三种赋值方式:省略关键字var,运用:=。
注意 :=左边的变量不能是已经声明过的变量
注意 这种不带声明格式的只能在函数体内出现,不能用来声明全局变量
*/
d := 7.5
fmt.Println(a, b, c, d)
}
关于第一行注释提到的“该变量类型下的默认值”: Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0
。 字符串变量的默认值为空字符串
。 布尔型变量默认为false
。 切片、函数、指针变量的默认为nil
。
声明多个变量
package main
import "fmt"
// 类型相同多个变量,非全局变量
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"
fmt.Println(x, y, a, b, c, d, e, f, g, h)
}
值类型和引用类型
值类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向已经存在内存中的值。比如var i = 10
:
而当我们用等号为另一个变量赋值的时候,比如j = i
,我们实际上是在内存中对i
的值进行了拷贝:
值类型的变量的值存储在栈中,你可以用&i
获取变量i
的内存地址(内存地址会因为机器、程序的不同而改变)。例如,在我的计算机上var i = 10
中i
的地址为0xc000016098
。
引用类型
更复杂的数据通常会需要使用多个值,所以一般使用引用类型保存。
一个引用类型的变量 r1
存储的是 r1
的值所在的内存地址(数字),或内存地址中第一个值所在的位置。
这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个值中。
同一个引用类型的指针指向的多个值可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些值分散存放在内存中,每个值都指示了下一个值所在的内存地址。
当使用赋值语句 r2
= r1
时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
常量
相对于变量,常量是一个程序运行中不会被修改的恒定值,数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的声明与变量声明非常类似,只不过把var
换成了const
,而且声明的时候必须赋值。
下面给出一些声明常量的示例:
package main
import "fmt"
func main() {
const PI float32 = 3.1415
const MONTH = 12
const a, b, c = 1, false, "str" //多重赋值
const (
d = "GoPher"
e = false
)
const (
f = 7
//声明多个常量时,如果省略了值,则默认与上面一行相同
g
h
)
fmt.Println(PI, MONTH, a, b, c, d, e, f, g, h)
}
iota
iota
是go语言的常量计数器,可以认为是一个可以能够被编译器修改的常量,只能在常量表达式中使用。
iota
在const出现时将被重置为0,之后const每新增一行常量声明将使iota
计数一次(iota可理解为const语句块中的行索引)。当然也有例外,以下是一些示例:
package main
import "fmt"
func main() {
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
// 使用_可以跳过一些值
const (
n5 = iota //0
n6 //1
_
n7 //3
)
//可以中间插队
const (
n8 = iota //0
n9 = 82 //82
n10 = iota //2
n11 //3
)
const n12 = iota //0
// 0 1 2 3 0 1 3 0 82 2 3 0
fmt.Println(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12)
}