【go学习笔记二】变量、数据类型、流程控制、数组切片
golang变量
声明变量
go的变量需要声明后才能使用,同一作用域不支持重复声明,且需要声明后使用。
语法:
var 变量名 类型
如:var name string
var:声明变量关键字
name:变量名称,或任何符合命名规范的其他名称
string:变量类型,类似的如int
如:
func main()
var name string
var age int
var _sys int
}
也可以批量声明:
func main() {
var (
name string
age int
level int
)
}
声明后不赋值也会有默认值:
package main
import "fmt"
func main() {
var (
name string
age int
ok bool
)
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("ok: %v\n", ok)
}
// name:
// age: 0
// ok: false
变量初始化
声明变量时,会自动初始化成类型的默认值,如整型是0,字符串是空字符串,布尔型是false。
var 变量名 类型 = xx
如:var name string = "hqinglau"
类型推断:从初始化的值就可以得知变量的类型
func main() {
var name = "hqinglau"
fmt.Printf("name: %v\n", name)
}
批量初始化
func main() {
var name, age, ok = "hqinglau", 20, true
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("ok: %v\n", ok)
}
// name: hqinglau
// age: 20
// ok: true
短变量声明
只能用在函数内部,用:=
对变量进行初始化和声明。
func main() {
name := "hqinglau"
age := 20
ok := false
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("ok: %v\n", ok)
}
// name: hqinglau
// age: 20
// ok: true
匿名变量
用下划线,表示不感兴趣。例如对函数的age变量不感兴趣:
import "fmt"
func getNameAndAge() (string, int) {
return "hqinglau", 20
}
func main() {
name, _ := getNameAndAge()
fmt.Printf("name: %v\n", name)
}
// name: hqinglau
golang常量
在编译期间就确定下来的值,在运行时无法改变该值。
语法:
const 常量名 类型(可省略)= value
例子:
除了一旦定义了不能再修改,其他用法和变量差不多。
func main() {
const PI float64 = 3.14
// const PI = 3.14 类型可省略
// PI = 3.145 错误,不能对常量赋值
const PI2, PI3 = 3.1415, 3.1415926
const (
a = 100
b = 200
)
}
iota
是一个可以被编译器修改的常量,默认开始值是0,调用一次加1,只在当前const
有效。
func main() {
const (
a = iota
b = iota
_ //表示跳过一个值
c = iota
)
const d = iota
fmt.Printf("a: %v\n", a)
fmt.Printf("c: %v\n", c)
fmt.Printf("b: %v\n", b)
fmt.Printf("a: %v\n", a)
fmt.Printf("d: %v\n", d)
fmt.Printf("a: %v\n", a)
fmt.Printf("c: %v\n", c)
fmt.Printf("b: %v\n", b)
// a: 0
// c: 3
// b: 1
// a: 0
// d: 0
// a: 0
// c: 3
// b: 1
}
中间插队,不影响其他的。
const (
a = iota //0
b //1
c = 100 //100
d = iota //3
e //4
)
// 但是如果没有d = iota,就会接着100排序
func main() {
const (
a = iota //0
b //1
c = 100 //100
d //100 :100+0
e //101 :100+1
)
fmt.Printf("d: %v\n", d)
}
为何有iota这个东西?
go中没有枚举类,可以用iota创造出一组值不同的常量。
//一般写法 const ( CategoryBooks = 0 CategoryHealth = 1 CategoryClothing = 2 ) //用iota的写法 const ( CategoryBooks = iota //0 CategoryHealth //1 CategoryClothing //2 ) 链接:https://www.zhihu.com/question/274481696/answer/374881523
还有特殊场景,例如表达式:
func main() { const ( a = iota*2 + 1 //1 b //3 c //5 d //7 e //9 ) fmt.Printf("d: %v\n", d) // 7 }
golang数据类型
按类别分:
- 布尔型
- 数字型
- 字符串类型
- 派生类型,如指针,数组,结构体,切片。。。
demo
import "fmt"
func fa() { // func()类型
}
func main() {
name := "tom" // string: tom
age := 20 // int: 20
p := &age // *int: 0xc000014098 int类型的指针
ok := true // bool: true
var array = [...]int{1, 2, 3} // [3]int: [1 2 3]
//切片,动态数组
slice := []int{1, 2, 4} // []int: [1 2 4]
fmt.Printf("%T: %v\n", name, name)
//...
// %T打印类型
}
数字类型
整数:int,uint(无符号),uintptr(足够存放一个指针即可)。如:uint8,uint16,uint32,uint64,int8,int16,int32,int64。
int和uint在32位系统上是4个字节,64位系统是8个字节。
浮点型:float32,float64,complex64,complex128实数和虚数。
package main
import (
"fmt"
"math"
"unsafe"
)
func main() {
var i8 int8 //int8 1B -128~127
var i32 int32 //int32 4B -2147483648~2147483647
fmt.Printf("%T %dB %v~%v\n", i8, unsafe.Sizeof(i8), math.MinInt8, math.MaxInt8)
fmt.Printf("%T %dB %v~%v\n", i32, unsafe.Sizeof(i32), math.MinInt32, math.MaxInt32)
}
点击math,会跳转到官网。
二进制、八进制、十六进制
func main() {
a := 16
fmt.Printf("%d \n", a) //16
fmt.Printf("%b \n", a) //10000
fmt.Printf("%x \n", a) //10
b := 077
fmt.Printf("%o \n", b) //77 八进制
c := 0xff
fmt.Printf("%x \n", c) //ff
fmt.Printf("%X \n", c) //FF
fmt.Printf("%d \n", c) //255
}
浮点类型
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi) //3.141593
fmt.Printf("%.2f\n", math.Pi) //3.14
}
默认int
布尔类型
两个值:true
、false
。
func main() {
// a := false
// var b = false
// var c bool = false
age := 16
if age >= 18 {
fmt.Println("成年了")
} else {
fmt.Println("未成年")
}
}
字符串
一个golang字符串是一个任意字节的常量序列。
字符串字面量
使用双引号或者反引号来创建。
- 双引号:可解析,支持转义,但是不能引用多行。
- 反引号:创建原生的字符串字面量,可以多行,不支持转义,可以包含除了反引号的其他所有字符。
func main() {
var str1 string = "hi"
var html1 string = `
<html>
<body>da</body>
</html>
`
fmt.Printf("str1: %v\n", str1)
fmt.Printf("html1: %v\n", html1)
}
字符串连接
go中字符串不可变。
加号
func main() {
s1:="hello"
s2:="world"
msg:=s1+s2
fmt.Printf("msg: %v\n", msg) //msg: helloworld
}
Sprintf
func main() {
s1 := "hello"
s2 := "world"
msg := fmt.Sprintf("%s, %s!", s1, s2)
fmt.Printf("msg: %v\n", msg) //msg: hello, world!
}
strings.Join()
func main() {
s1 := "hello"
s2 := "world"
msg := strings.Join([]string{s1, s2}, ",")
fmt.Printf("msg: %v\n", msg) //msg: hello,world!
}
join会先根据字符串数组的内容,计算出拼接后的长度,然后申请内存,填入字符串。在已有数组的情况下效率比较高,没有的话,构造数组代价也不小。
buffer.WriteString()
func main() {
var buffer bytes.Buffer
s1 := "hello"
s2 := "world"
buffer.WriteString(s1)
buffer.WriteString(",")
buffer.WriteString(s2)
fmt.Printf("buffer: %v\n", buffer)
//buffer: {[104 101 108 108 111 44 119 111 114 108 100] 0 0}
fmt.Printf("buffer.String(): %v\n", buffer.String())
//buffer.String(): hello,world
}
转义字符
\r \n \t之类的。
切片
顾前不顾后。前闭后开。
func main() {
str := "hello world"
fmt.Printf("str[3]: %v\n", str[3]) //str[3]: 108
fmt.Printf("str[3:5]: %v\n", str[3:5]) //str[3:5]: lo
// fmt.Printf("str[3:100]: %v\n", str[3:100]) wrong:out of range
fmt.Printf("str[3:]: %v\n", str[3:]) //str[3:]: lo world
fmt.Printf("str[:5]: %v\n", str[:5]) //str[:5]: hello
}
常用函数
import (
"fmt"
"strings"
)
func main() {
str := "hello world"
fmt.Printf("len(str): %v\n", len(str))
//len(str): 11
fmt.Printf("strings.Split(str, \" \"): %v\n", strings.Split(str, " "))
// 分割为数组:strings.Split(str, " "): [hello world]
fmt.Printf("strings.Contains(str, \"hell\"): %v\n", strings.Contains(str, "hell"))
// 判断是否含有某个字符串:strings.Contains(str, "hell"): true
fmt.Printf("strings.ToUpper(str): %v\n", strings.ToUpper(str))
// 转大写:strings.ToUpper(str): HELLO WORLD
fmt.Printf("strings.Index(str, \"l\"): %v\n", strings.Index(str, "l"))
// 第一个出现匹配串的位置:strings.Index(str, "l"): 2
fmt.Printf("strings.HasPrefix(str, \"hi\"): %v\n", strings.HasPrefix(str, "hi"))
// 是否含有前缀:strings.HasPrefix(str, "hi"): false
}
格式化输出
如:
type Website struct {
Name string
Owner string
}
func main() {
var site = Website{Name: "orzlinux.cn",Owner: "hqingLau"}
fmt.Printf("site: %v\n", site) //site: {orzlinux.cn hqingLau}
fmt.Printf("site: %+v\n", site) //site: {Name:orzlinux.cn Owner:hqingLau}
fmt.Printf("site: %#v\n", site) //site: main.Website{Name:"orzlinux.cn", Owner:"hqingLau"}
fmt.Printf("site: %T\n", site) //site: main.Website
}
运算符
和其它语言相似。
+-*/%
,==,大于,小于,大于等于,小于等于。
func main() {
a := 100
b := 30
fmt.Printf("a+b: %v\n", a+b) //130
fmt.Printf("a-b: %v\n", a-b) //70
fmt.Printf("a*b: %v\n", a*b) //3000
fmt.Printf("a/b: %v\n", a/b) //3
fmt.Printf("a%%b: %v\n", a%b) //10
a++
fmt.Printf("a: %v\n", a) //101
b--
fmt.Printf("b: %v\n", b) //29
fmt.Printf("a>=b: %v\n", a >= b) //true
fmt.Printf("a==b: %v\n", a == b) //false
fmt.Printf("a<b: %v\n", a < b) //false
}
逻辑运算:&&
、||
、!
。与或非。
位运算:&
、|
、^
、<<
、>>
赋值:&=
、|=
等。
流程控制
if else
if a > b {
fmt.Println("a is larger than b")
} else if a < b {
fmt.Println("a is smaller than b")
} else {
fmt.Println("a is equal to b")
}
else if 可以有零个或多个。
遍历也可以写在if里面,如:
if who := "manager"; who == "manager" {
fmt.Println("equal") //进入了
}
//fmt.Printf("who: %v\n", who) // 报错,who只能在if的作用域
牵扯到作用域的问题。
demo1:
who := "manager"
if who = "boss"; who == "manager" {
fmt.Println("equal") //没进入
}
fmt.Printf("who: %v\n", who) // who: boss
demo2:
who := "manager"
if who := "boss"; who == "boss" {
fmt.Println("equal") // 进入了
}
fmt.Printf("who: %v\n", who) // who: manager
获取用户输入:
var name string
var age int
var email string
fmt.Println("输入name, age, email,空格分隔:")
fmt.Scan(&name, &age, &email)
fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
fmt.Printf("email: %v\n", email)
// > go run main.go
// 输入name, age, email,空格分隔:
// hqinglau 23 hqinglau@gmail.com
// name: hqinglau
// age: 23
// email: hqinglau@gmail.com
判断奇偶数例子:
func main() {
f2()
}
func f2() {
var num int
fmt.Println("请输入一个数字:")
fmt.Scan(&num)
fmt.Printf("num: %v\n", num)
if num%2 == 0 {
fmt.Println("偶数")
} else {
fmt.Println("奇数")
}
}
// ---------------输入非法整数例子---
// 输入:str
// num: 0
// 偶数
// 输入:1.345
// num: 1
// 奇数
switch
go里面的case末尾不需要添加break。
一个case后面可以跟多个:
func main() {
who := "manager"
switch who {
case "manager", "boss":
fmt.Println("leader")
case "employee":
fmt.Println("follower")
default:
fmt.Println("else")
}
}
循环
go里循环只有for循环,去除了while
、do while
等。
for i := 0; i < 10; i++ {
fmt.Printf("i: %v\n", i)
}
x := [...]int{1, 3, 2, 3}
for _, v := range x {
fmt.Printf("v: %v\n", v)
}
break,continue之类。
goto
goto被诟病很久了,golang里面还有goto,看到stackoverflow的一个解释如下:
在go标准库里面,goto经常被使用。当你真正知道自己在做什么,并且知道内建特性没有办法满足你的目的时,goto可能很方便。例如多层for嵌套要退出,不用goto就需要使用大量的bool语句来作为判断标志。
如果其他控制语句能达到你的目的,那就不要用goto。而且golang里面的goto加了一些限制,来防止错误:
A goto跳到的地方B,B处所有的变量必须在A处都有。
goto L // BAD v := 3 L:
类似这样是不可以的,如果L下面代码用到了v,而v的声明都跳过了。
在标签所在的block(就是大括号)外面的goto不能跳进这个标签。
if n%2 == 1 { goto L1 } for n > 0 { f() n-- L1: f() n-- }
跳出多重循环场景:判断一个多维数组里面是否有某个数,有就跳出循环,不再遍历:
func main() {
var multiDimArray [100][100][100][100]int
multiDimArray[50][60][70][80] = 1
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
for k := 0; k < 100; k++ {
for m := 0; m < 100; m++ {
if multiDimArray[i][j][k][m]==1 {
fmt.Println("find 1")
goto LABEL1
}
}
}
}
}
LABEL1:
fmt.Println("DO STH ELSE...")
}
数组
声明
golang里面的数组一旦定义之后,长度不能改变。
var 数组名 [数组大小] 元素类型
如:
func main() {
var a1 [2]int
var a2 [3]string
fmt.Printf("a1: %T\n", a1) //a1: [2]int
fmt.Printf("a2: %T\n", a2) //a2: [3]string
}
声明之后没有初始化里面的元素就是默认值,如整型就是0,字符串就是空串。
初始化
var a1 = [2]int{1, 2}
var a2 = [...]int{1, 2} // 省略数组长度
var a3 = [3]int{1, 2} //1,2,0
var a4 = [4]string{0: "hi", 2: "you"} //{"hi", "", "you", ""}
...
会根据初始化列表的长度默认大小;指定大小3但是只初始化了两个数,剩下的就默认0.
也可以根据index进行初始化。
访问、修改
直接通过下标访问修改。
var a4 = [4]string{0: "hi", 2: "you"} //{"hi", "", "you", ""}
a4[1] = "love"
a4[0] = "I" //a4: [4]string{"I", "love", "you", ""}
长度
var a1 = [2]int{1, 2}
fmt.Printf("len(a1): %v\n", len(a1)) //2
遍历
根据长度和index:
func main() {
var a1 = [...]int{1, 2, 3, 4, 5}
for i := 0; i < len(a1); i++ {
fmt.Printf("a1[%d]: %v\n", i, a1[i])
}
// a1[0]: 1
// a1[1]: 2
// a1[2]: 3
// a1[3]: 4
// a1[4]: 5
}
range:
var a1 = [...]int{1, 2, 3, 4, 5}
for i, v := range a1 {
fmt.Printf("a1[%d]: %v\n", i, v)
}
切片
slice。
数组一旦定义长度就定死了,切片可以理解为可变长度的数组,底层使用数组实现,增加了自动扩容功能。是一个拥有相同元素数据类型的可变长度的序列。
var 数组名 [] 元素类型
中括号里面不指定大小。
func main() {
var slice1 []int
fmt.Printf("slice1: %v\n", slice1) //slice1: []
fmt.Printf("len(slice1): %v\n", len(slice1)) //len(slice1): 0
}
初始化:
func main() {
var slice1 []int = make([]int, 5)
var slice2 = make([]int, 5)
fmt.Printf("len(slice1): %v\n", len(slice1)) //len(slice1): 5
fmt.Printf("len(slice2): %v\n", len(slice2)) //len(slice1): 5
}
demo
append
func test() {
var a1 = []int{1, 2, 3}
fmt.Printf("a1: %v\n", a1) // a1: [1 2 3]
a1 = append(a1, 4)
fmt.Printf("a1: %v\n", a1) //a1: [1 2 3 4]
}
func main() {
test()
}
make
var a1 = make([]int, 2) // 已经声明了两个长度的切片
fmt.Printf("a1: %v\n", a1) // a1: [0 0]
a1 = append(a1, 4)
fmt.Printf("a1: %v\n", a1) // a1: [0 0 4]
长度和容量
func test() {
var a1 = make([]int, 3) // 默认长度和容量都是3
fmt.Printf("len(a1): %v\n", len(a1)) //3
fmt.Printf("cap(a1): %v\n", cap(a1)) //3
a1 = append(a1, 4)
fmt.Printf("len(a1): %v\n", len(a1)) //4
fmt.Printf("cap(a1): %v\n", cap(a1)) //6
a1 = append(a1, 5, 6, 7)
fmt.Printf("len(a1): %v\n", len(a1)) //7
fmt.Printf("cap(a1): %v\n", cap(a1)) //12
}
可以看到,达到容量之后是两倍扩容。
初始化手段
数组或者数组的部分元素初始化:
func test() {
var a1 = [...]int{1, 2, 3, 4, 5} //切片也行
slice1 := a1[:] // 全部元素
slice2 := a1[:4] // 某一段
fmt.Printf("slice1: %T\n", slice1) //[]int
fmt.Printf("slice2: %T\n", slice2) //[]int
}
空切片
切片在未初始化之前为nil,长度和容量都是0。
func test() {
var a1 []int
fmt.Println(a1 == nil) // true
fmt.Printf("len(a1): %v\n", len(a1)) // len(a1): 0
fmt.Printf("cap(a1): %v\n", cap(a1)) // cap(a1): 0
}
初始化空:
var s1 = []int{}
fmt.Println(s1 == nil) // false
遍历
参照数组遍历。
添加、删除、拷贝
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要打散
参数三个点是可以传0到多个参数:
func test(args ...int) { for i, v := range args { fmt.Printf("%v: %v\n", i, v) } } func main() { test() test(1) test(1, 2, 3) test([]int{1, 2, 3}...) // 数组或者切片后面三个点,表示变成一个一个的元素,将其打散 }
具体详细注释版代码:
var s1 = []int{}
// 增加
s1 = append(s1, 1)
s1 = append(s1, 2)
s1 = append(s1, 3)
fmt.Printf("s1: %v\n", s1) // s1: [1 2 3]
// 删除索引为1的元素
// 用前后的切片组合
s1 = append(s1[:1], s1[2:]...)
fmt.Printf("s1: %v\n", s1) // s1: [1 3]
拷贝
切片
// 拷贝
// 只是引用指向了同一个内存地址
var s2 = s1
s2[0] = 2
fmt.Printf("s1: %v\n", s1) // s1: [2 3]
fmt.Printf("s2: %v\n", s2) // s2: [2 3]
// 仍然是同一个内存地址
var s3 = s1[:]
s3[0] = 100
fmt.Printf("s1: %v\n", s1) // s1: [100 3]
fmt.Printf("s3: %v\n", s3) // s3: [100 3]
// 真拷贝
var s4 = make([]int, len(s1))
copy(s4, s1)
s4[0] = 99
fmt.Printf("s1: %v\n", s1) // s1: [100 3]
fmt.Printf("s4: %v\n", s4) // s4: [99 3]
数组
var s1 = [2]int{1, 3}
// 拷贝
// ====数组的话变成了真拷贝=====
var s2 = s1
s2[0] = 2
fmt.Printf("s1: %v\n", s1) // s1: [1 3]
fmt.Printf("s2: %v\n", s2) // s2: [2 3]
// 同一个内存地址
var s3 = s1[:]
s3[0] = 100
fmt.Printf("s1: %v\n", s1) // s1: [100 3]
fmt.Printf("s3: %v\n", s3) // s3: [100 3]
// 真拷贝
var s4 = make([]int, len(s1))
copy(s4, s1[:])
s4[0] = 99
fmt.Printf("s1: %v\n", s1) // s1: [100 3]
fmt.Printf("s4: %v\n", s4) // s4: [99 3]
既然切片是数组连续片段的引用,那之后又增加元素的话呢?
func main() {
var s1 = [2]int{1, 3}
// 同一个内存地址
var s3 = s1[:]
s3[0] = 100
fmt.Printf("s1: %#v\n", s1) // [2]int{100, 3}
fmt.Printf("s3: %#v\n", s3) // []int{100, 3}
s3 = append(s3, 5)
fmt.Printf("s1: %#v\n", s1) // [2]int{100, 3}
fmt.Printf("s3: %#v\n", s3) // []int{100, 3, 5}
}
再增加就是另一个隐形数组
的了,和s1
没关系。