备份一下之前的学习记录。
var 语句用于声明一个变量列表,和函数的参数意义,类型在后
var name string
var a, b, c bool
var 语句可以出现在包或函数级别
在函数中,简洁赋值语句 :=
可在类型明确的地方代替 var 声明
函数外的每个语句都必须以关键字开始(var、func等等),因此 :=
结构不能在函数外使用
Golang的变量如果没有赋初值,编译器会使用默认值,比如int默认值是 0
,string 默认值为 ""
空串,布尔类型默认是 false
在变量的作用域下,可以改变它的值,但是不能改变数据类型
例如:
var a = 32
a = 20
a = 32.4 // 此时就会报错
Golang变量使用的三种方式
package main
import "fmt"
func main () {
var i int
fmt.Println("没有赋值的i的默认值为:", i) // 0
}
var j = 20.12
fmt.Println("根据值自行判断变量类型(类型推导):", j)
第三种:省略var,注意 :=
左侧的变量不应该是已声明过的,否则会导致编译错误. 下面这种方式等价于 var name string
name = "tom"
:= 不能省略,否则错误
k := "tom"
var l string
l = "jack"
fmt.Println(k, "' :=' => 'var l string '", l)
package main
import "fmt"
func main () {
// 定义变量 变量类型 如果没有赋值,会有一个默认值 0
var i int
// 给i赋值
i = 10
fmt.Println("i的值:", i) // 10
}
多变量声明
package main
import "fmt"
func main () {
// 一次性声明多个变量
// 第一种
var n1, n2, n3 int
fmt.Println("n1=", n1, "n2=", n2, "n3=", n3)
// 第二种
var a1, name, a2 = 100, "tom", 888
fmt.Println("a1=", a1, "name=", name, "n2=", a2)
// 第三种
b1, b2, b3 := 666, "jerry", 888
fmt.Println("b1=", b1, "b2=", b2, "b3=", b3)
}
全局变量定义
package main
import "fmt"
var a1, a2, a3 = "tom", 12, 55
var b1 = "jerry"
var b2 = 43
var b3 = 39
var (
c1 = "jack"
c2 = 88
c3 = 99
)
func main () {
fmt.Println("a1=", a1, "a2=", a2, "a3=", a3)
fmt.Println("b1=", b1, "b2=", b2, "b3=", b3)
fmt.Println("c1=", c1, "c2=", c2, "c3=", c3)
}
func 函数名 (参数名 参数类型) 返回值类型
func add (x int, y int) int {
return x + y
}
func add (x int, y int) (int, int) {
return x + y, x - y
}
和JavaScript中的匿名函数一样。
func (参数列表) (返回值列表) {
函数体
}
在定义匿名函数时,调用函数
func (data int) {
fmt.Println("data: ", data)
}(100)
// 注意后面的(100), 表示对匿名函数的调用
// 传递参数为 100
将匿名函数赋值给变量
add := func (x, y int) int {
return x + y;
}
package main
import "fmt"
func main () {
add := func (x, y int) int {
return x + y
}
sum := add(3, 5)
fmt.Println("sum=", sum)
}
匿名函数用作回调函数
package main
import "fmt"
func deal (x int, y int, add func(a, b int) int) int {
return add(x * 2, y * 2)
}
func main () {
result := deal(3, 4, func (x, y int) int{
return x + y
})
fmt.Println("result=", result)
}
如下代码所示,函数形参:如果多个参数的类型相同,前面的参数类型可以省略,最后一个参数标明 参数类型
命名返回值:直接定义返回值 变量,没有参数的return语句就会返回 已命名的返回值。
直接返回语句应当在短函数中,在长的函数中 它们会影响代码的可读性
package main
import "fmt"
// 命名返回值
func add (x, y int) (a, b int) {
a = x + y
b = x - y
return
}
func main () {
fmt.Println(add(9, 8))
a, b := add(9, 8)
fmt.Println("a=", a, "b=", b)
}
/*
输出:
17 1
a= 17 b= 1
*/
表达式 T(v)
将值v转换为类型 T
package main
import "fmt"
func main () {
var i int = 42
fmt.Printf("i=%d, type=%T \n", i, i)
var f float64 = float64(i)
fmt.Printf("f=%f, type=%T \n", f, f)
var u uint = uint(f)
fmt.Printf("u=%d, type=%T \n", u, u)
}
/*
输出:
i=42, type=int
f=42.000000, type=float64
u=42, type=uint
/*
更加简单的方式 写法
package main
import "fmt"
func main () {
i := 42
fmt.Printf("i=%d, type=%T \n", i, i)
f := float64(i)
fmt.Printf("f=%f, type=%T \n", f, f)
u := uint(f)
fmt.Printf("u=%d, type=%T \n", u, u)
}
在if的简短语句中声明的变量同样可以在任何对应的else块中使用。
package main
import (
"fmt"
"math/rand"
"time"
)
func main () {
var a int
// 设置 种子
rand.Seed(time.Now().UnixNano())
a = rand.Intn(10)
if v := a*2; v > 5 {
fmt.Println("v的值大于5, a=", a, "v=", v)
} else {
fmt.Println("v小于等于5, a=", a, "v=", v)
}
// 从这里开始就不能使用v了,只能在定义变量的if以及对应else块中使用
}
Switch case
package main
import (
"fmt"
"runtime"
)
func main () {
fmt.Println("Go runs on")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X .")
case "linux":
fmt.Println("Linux .")
default:
fmt.Printf("%s . \n", os)
}
}
在Java语言中switch case 要和break搭配。
Java语言中:如果当前匹配成功的case语句块没有break语句,则从当前case开始
后续的所有case的值都会输出,如果后续的case语句块有break语句则会跳出判断
public class JavaSwitch {
public static void main (String[] args) {
int a = 3;
switch(a) {
case 2: System.out.println("a=2");
case 3: System.out.println("a=3");
case 4: System.out.println("a=4");
default: System.out.println("default 执行");
}
}
}
a=3
a=4
default 执行
在go语言中的switch case中没有用break,当匹配到第一个符合的case,就会跳出判断。
package main
import "fmt"
func main () {
a := 5
switch {
case a > 2:
fmt.Println("a大于2")
case a > 4:
fmt.Println("a大于4")
default:
fmt.Println("其他...")
}
}
a大于2
摘自:https://studygolang.com/articles/10167
在golang中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用
而是会在函数正常返回之后,添加一个函数调用。
因此 defer通常用来释放函数内部变量;例如:打开、创建文件,需要释放。
我们平时都是在最后释放资源,如果中间发送了异常,就直接return了,后面的代码不再执行
可以使用defer,避免这种情况。
为什么第一个defer输出的值还是3 ?
实际上 当 到第一个refer的时候,a的值已经确定了
var a int = 3
// 推迟调用, defer 跟 函数调用defer fmt.Println("推迟执行后:a的值:", a)
// 此时 这里 就相当于 解析成了 fmt.Println("推迟执行后:a的值:", 3)
package main
import "fmt"
func main () {
var a int = 3
// 推迟调用, defer 跟 函数调用
defer fmt.Println("推迟执行后:a的值:", a)
fmt.Println("a的值:", a)
a += 3
defer fmt.Println("最后,a的值:", a)
fmt.Println("我是main函数的最后一行代码")
}
a的值: 3
我是main函数的最后一行代码
最后,a的值: 6
推迟执行后:a的值: 3
第一个defer,是最后执行的
defer可以读取 有名返回值
package main
import "fmt"
func c () (i int) {
defer func () {
i++
}()
return 1
}
func main () {
a := c()
fmt.Println("a=", a)
}
输出
a=2
defer是在return调用之后才执行的。这里需要明确defer代码块的作用域
仍然在函数之内
defer的作用域仍然在c函数之内,因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢...)
当执行return 1之后,i的值就是1,此时此刻,defer代码块开始执行,对i进行自增操作,因此输出2
Go拥有指针。指针保存了值的内存地址
类型 *T
是指向T类型值的指针。其零值为 nil
var p *int
&
操作符会生成一个指向其操作数的指针
i := 42
p = &i
*
操作符表示指针指向的底层值
fmt.Println(*p) // 通过指针读取 i
*p = 21 // 通过指针p设置i
这也就是通常所说的 “简介引用” 或 “重定向”。
与C不同,Go没有指针运算
package main
import "fmt"
func main () {
var p *int
i := 42
p = &i
// p= 0x14000124008 获取的i所在的 内存地址
fmt.Println("p=", p)
// *p=42 获取i的值
fmt.Println("*p=", *p)
// 通过*p 对i重新赋值
*p = 66
// i= 66
fmt.Println("i=", i)
}
一个结构体 struct
就是一组字段 field
结构体字段 使用 .
来访问 v.X
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main () {
v := Vertex{1, 2}
fmt.Println(v)
v.X = 6
fmt.Println(v.X)
}
{1 2}
6
结构体字段可以通过结构体指针来访问
如果我们有一个指向结构体的指针p,那么可以通过(*p).X来访问其字段X。不给这么写太啰嗦了。所以语言也允许我们使用隐式间接引用,直接写p.X就可以
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main () {
v := Vertex{1, 2}
fmt.Println(v)
p := &v
p.X = 1e9
fmt.Println(p.X)
}
{1 2}
1000000000
结构体文法
结构体文法通过直接列出字段的值新分配一个结构体。
使用Name:语法可以仅列出部分字段(字段名的顺序无关)
特殊的前缀 & 返回一个指向结构体的指针
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
// 创建一个Vertex类型的结构体
v1 = Vertex{1, 2}
// Y: 0被隐式赋予
v2 = Vertex{X: 1}
// X: 0, Y: 0
v3 = Vertex{}
// 创建一个 *Vertex 类型的结构体(指针)
v4 = &Vertex{1, 2}
)
func main () {
// fmt.Println(*v4)
fmt.Println(v1, v2, v3, v4)
}
类型 [n]T
表示拥有n个T类型的值的数组
表达式
var a [10]int
会将变量a声明为拥有10个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小
Go提供了更加便利的方式来使用数组
package main
import "fmt"
func main () {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
在多行切片、数组或映射文字中,每行必须以逗号结尾
例如上面的程序:
primes := [6] int {2, 3, 5, 7, 11,
13
}
如果这样写,编译的时候会报错。
syntax error: unexpected newline, expecting comma
解决办法就是在13后面加个 逗号
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角
在实践中,切片比数组更常用。
类型 [ ]T 表示一个元素类型为 T的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
左开右闭 [low : high )
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
切片就像数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改器底层数组中对应的元素
与它共享底层数组的切片都会观测到这些改变。
package main
import "fmt"
func main () {
names := [4]string {
"John",
"Paul",
"George",
"Ringo"}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXXX"
fmt.Println(a, b)
fmt.Println(names)
}
ps:这里有个细节,names的最后的花括号 没有单独占一行,如果最后的花括号
输出:
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXXX] [XXXX George]
[John XXXX George Ringo]
切片文法
切片文法类似于没有长度的数组文法
这是一个数组 文法
[3]bool {true, true, false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片
[]bool {true, true, false}
s := []struct{
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, trye},
{11, false},
{13, true}, // 这里的逗号不能少
}
切片的长度和容量
切片拥有 长度 和 容量
切片的长度就是它所包含的元素个数
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
切片s的长度和容量可通过表达式len(s) 和 cap(s)来获取