基本运算符和变量
下划线
“_”是特殊标识符,用来忽略结果。
下划线在import中
在Golang里,import的作用是导入其他package。
import 下划线(如:import hello/imp)的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。
举例:
import _ “github.com/mysql”
不直接使用该包,只是执行该包的init函数。
下划线在代码中
两种解释
第一种:忽略这个返回值的变量,有些函数返回值有两个,但是我们只需要用第一个或者是第二个,此时就可以用下划线来忽略不需要关注的变量。
第二种:表示了占位符的意思,本来这个下划线的位置要赋值给某个变量,但是我们不需要该变量,则用下划线进行占位。因为赋值了变量,但是不用该变量,则编译器会报错。
变量
定义
数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。
功能
功能是存储数据,不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。
Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。
声明
Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。并且Go语言的变量声明后必须使用。
Go语言的变量声明格式为:
var 变量名 变量类型
批量声明
每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:
var (
a string
b int
c bool
d float32
)
变量的初始化
Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。
我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:
var 变量名 类型 = 表达式
一次初始化多个变量,如下:
var 变量名, 变量名 = 表达式, 表达式
变量的类型推导
我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。
var 变量名 = 表达式, 表达式
短变量声明(常用–重要)
函数内部,可以使用更简略的 := 方式声明并初始化变量。
package main
import (
"fmt"
)
// 全局变量m
var m = 100
func main() {
n := 100
m := 1000 // 此处声明局部变量m
fmt.Println(m, n)
}
匿名变量
在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示。
注:
匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
函数外的每个语句都必须以关键字开始(var、const、func等)
:=不能使用在函数外
_多用于占位,表示忽略值
常量
相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。
常量单独声明如下:
const a = 1
const b = 2
多个常量一起声明如下:
const (
a = 1
b = 2
)
const同时声明多个常量时,如果省略了值则表示和上面一行的值相同:
const (
a = 1
b // b = 1
c // c = 1
)
iota
- iota是go语言的常量计数器,只能在常量的表达式中使用。
- iota在const关键字出现时将被重置为0。
- const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。
- 使用iota能简化定义,在定义枚举时很有用:
const (
a = iota //0
b //1
c //2
d //3
)
iota使用场景
- _跳过某些值
const (
n1 = iota //0
n2 //1
_
n4 //3
)
- iota声明中间插队
const (
n1 = iota //0
n2 = 100 //100
n3 = iota //2
n4 //3
)
const n5 = iota //0
- 定义数量级
<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
- 多个iota定义在一行
const (
a, b = iota + 1, iota + 2 //1,2
c, d //2,3
e, f //3,4
)
基本类型
注:空指针值 nil,而非C/C++ NULL。
整型
按长度分为:int8、int16、int32、int64
对应的无符号整型:uint8、uint16、uint32、uint64
注:
uint8就是我们熟知的byte型
int16对应C语言中的short型
int64对应C语言中的long型
浮点型
float32和float64
数据格式遵循IEEE 754标准:
float32 的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32
float64 的浮点数的最大范围约为1.8e308,可以使用常量定义:math.MaxFloat64
复数
complex64和complex128
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位
布尔值
Go语言中以bool类型进行声明布尔型数据,布尔型数据只有true(真)和false(假)两个值
注:
- 默认false
- 不能将整型强制转换为布尔型
- 布尔型无法参与数值运算,也无法与其他类型进行转换
字符串
- 字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样
- Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为 双引号 中的内容
- Go语言的源码中可以直接添加非ASCII码字符
字符串转义符
Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等
转义 | 含义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
’ | 单引号 |
" | 双引号 |
\ | 反斜杠 |
多行字符串
Go语言中要定义一个多行字符串时,就必须使用反引号字符:
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类型
组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用 单引号 包裹起来
字符类型如下:
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符
rune类型,代表一个 UTF-8字符
当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理
数组
- 数组:是同一种数据类型的固定长度的序列。
- 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
- 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。
- 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
for i := 0; i < len(a); i++ {
}
for index, v := range a {
}
- 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
- 支持 “==”、“!=” 操作符,因为内存总是被初始化过的。
- ** 指针数组 [n]*T,数组指针 *[n]T。 **
数组的初始化
全局:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
局部:
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
多维数组
全局
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
局部:
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
多维数组的遍历:(后续有文章具体举例并描述)
package main
import (
"fmt"
)
func main() {
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
for k2, v2 := range v1 {
fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
}
fmt.Println()
}
}
输出结果:
(0,0)=1 (0,1)=2 (0,2)=3
(1,0)=7 (1,1)=8 (1,2)=9
切片Slice
slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案
注:
1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
2. 切片的长度可以改变,因此,切片是一个可变的数组。
3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
5. 切片的定义:var 变量名 []类型,比如 var str []string ; var arr []int。
6. 如果 slice == nil,那么 len、cap 结果都等于 0。
创建切片的方式
package main
import "fmt"
func main() {
//1.声明切片
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println(s1, s2, s3)
// 4.初始化赋值
var s4 []int = make([]int, 0, 0)
fmt.Println(s4)
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println(s6)
}
切片初始化
全局:
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素
局部:
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[start:end]
slice6 := arr[:end]
slice7 := arr[start:]
slice8 := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
解释:
- s[n]:切片中索引位置为n的项
- s[:]:从切片的索引位置0~len()-1处所得到的切片
- s[low:]:从切片的索引位置low到len()-1处所得到的切片
- s[:high]:从切片的索引位置0到high处所得到的切片
- s[low:high:max]:从切片的索引位置low到high处所得到的切片
len = high - low
cap = max - low - len():切片的长度,小于等于cap()
- cap():切片的容量,大于等于len()
通过make来创建切片
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)
用append内置函数操作切片(切片追加)
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3}
fmt.Printf("slice a : %v\n", a)
var b = []int{4, 5, 6}
fmt.Printf("slice b : %v\n", b)
c := append(a, b...)
fmt.Printf("slice c : %v\n", c)
d := append(c, 7)
fmt.Printf("slice d : %v\n", d)
e := append(d, 8, 9, 10)
fmt.Printf("slice e : %v\n", e)
}
slice a : [1 2 3]
slice b : [4 5 6]
slice c : [1 2 3 4 5 6]
slice d : [1 2 3 4 5 6 7]
slice e : [1 2 3 4 5 6 7 8 9 10]
注:append :向 slice 尾部添加数据,返回新的 slice 对象。
超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满
通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
切片拷贝
package main
import (
"fmt"
)
func main() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice s1 : %v\n", s1)
s2 := make([]int, 10)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
s3 := []int{1, 2, 3}
fmt.Printf("slice s3 : %v\n", s3)
s3 = append(s3, s2...)
fmt.Printf("appended slice s3 : %v\n", s3)
s3 = append(s3, 4, 5, 6)
fmt.Printf("last slice s3 : %v\n", s3)
}
/*
结果:
slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
slice s3 : [1 2 3]
appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
*/
copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("array data : ", data)
s1 := data[8:]
s2 := data[:5]
fmt.Printf("slice s1 : %v\n", s1)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
fmt.Println("last array data : ", data)
}
/*
结果:
array data : [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data : [8 9 2 3 4 5 6 7 8 9]
*/
应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。
slice遍历
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := data[:]
for index, value := range slice {
fmt.Printf("inde : %v , value : %v\n", index, value)
}
}
切片resize(调整大小)
如果从一个数组进行切片,则得到一个slice。若对该切片继续进行切片,且切片长度要大于第一次切片的长度。会从原数组中的数据进行调整大小。保证最后一次切片的内容最少要包括原数组内容。
字符串和切片
字符串底层是byte的数组,可以进行切片
string本身是不可变的,因此要改变string中字符。需要如下操作:
package main
import (
"fmt"
)
func main() {
str := "Hello world"
s := []byte(str) //中文字符需要用[]rune(str)
s[6] = 'G'
s = s[:8]
s = append(s, '!')
str = string(s)
fmt.Println(str)
}
/*
结果:
Hello Go!
*/
a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x
数组or切片转字符串
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)