【go学习笔记三】map、函数、闭包、指针、结构体
golang map
赋值、访问
key value
键值对的数据结构容器。内部实现是哈希表。引用类型。
声明方式:
var 变量名 map[变量类型] 值类型
声明之后不可直接用,需要初始化才能用。
例子:
func main() {
m1 := make(map[string]string)
m1["name"] = "hqinglau"
m1["site"] = "orzlinux.cn"
m1["email"] = "hqinglau@gmail.com"
fmt.Printf("m1: %#v\n", m1)
// m1: map[string]string{
// "email":"hqinglau@gmail.com",
// "name":"hqinglau",
// "site":"orzlinux.cn"
//}
m2 := map[string]string{
"email": "hqinglau@gmail.com",
"name": "hqinglau",
"site": "orzlinux.cn",
}
fmt.Printf("m2[\"email\"]: %v\n", m2["email"]) // m2["email"]: hqinglau@gmail.com
}
判断键是否存在
m2 := map[string]string{
"email": "hqinglau@gmail.com",
"name": "hqinglau",
"site": "orzlinux.cn",
}
v, ok := m2["age"] // 不存在
fmt.Printf("v: %#v\n", v) // 空串
fmt.Printf("ok: %v\n", ok) // false
遍历
for range
遍历key
或者遍历key, value
m1 := map[string]string{
"email": "hqinglau@gmail.com",
"name": "hqinglau",
"site": "orzlinux.cn",
}
for k := range m1 {
fmt.Printf("%v: %v\n", k, m1[k])
}
fmt.Println("========================")
for k, v := range m1 {
fmt.Printf("%v: %v\n", k, v)
}
golang函数
- 三种函数:普通函数,匿名函数,方法(struct上的函数)
- 不允许重载,也就是不允许函数同名
- 函数不能嵌套函数,但是可以嵌套匿名函数
- 函数可以赋值给一个变量,让变量也变成函数
- 函数可以作为参数传递
- 函数的返回值也可以是一个函数
- 函数传参是拷贝的过程
- 函数参数可以没有名字
定义
func 函数名 (参数) 返回类型 {
}
返回类型和参数都可以为空。
示例:
func sum(a int, b int) (ret int) {
ret = a + b
return ret
}
func hello(name string) {
fmt.Printf("Hello, %v\n", name)
}
func main() {
fmt.Printf("sum(1, 2): %v\n", sum(1, 2)) // sum(1, 2): 3
hello("hqinglau") // Hello, hqinglau
}
返回值
可以有零个,一个,或多个。
func add_sub(a int, b int) (int, int) {
return a + b, a - b
}
func main() {
c, d := add_sub(2, 4)
fmt.Printf("c,d: %v %v\n", c, d) // c,d: 6 -2
}
返回值里面有的有名字,有的没有,区别何在?
区别在于有名字的话,就不用return名字了,例如:
func add_sub1(a int, b int) (sum int, dif int) {
sum = a + b // 此处相当于被声明过了,不需要 := 了。
dif = a - b
return
// return sum dif // 不省略也可以
}
func add_sub2(a int, b int) (int, int) {
sum := a + b
dif := a - b
return sum, dif
}
func main() {
c, d := add_sub1(2, 4)
fmt.Printf("c,d: %v %v\n", c, d) // c,d: 6 -2
c, d = add_sub2(2, 4)
fmt.Printf("c,d: %v %v\n", c, d) // c,d: 6 -2
}
函数参数
0,1,多个。
形参:声明函数时的参数列表
实参:调用时传递的参数列表
go语言通过传值的方式传参。传递给函数的是拷贝后的副本。可以使用变长参数。
多种类型传参测试:
func test(a int, b string, c map[int]string, d [2]int, e []int) {
a = 999
b = "another"
c[1] = "change"
d[0] = 999
e[0] = 999
}
func main() {
i1 := 2
i2 := "test"
i3 := map[int]string{
1: "tt",
}
i4 := [...]int{1, 2}
i5 := []int{1, 2}
test(i1, i2, i3, i4, i5)
fmt.Printf("i1: %v\n", i1) // i1: 2
fmt.Printf("i2: %v\n", i2) // i2: test
fmt.Printf("i3: %v\n", i3) // i3: map[1:change]
fmt.Printf("i4: %v\n", i4) // i4: [1 2]
fmt.Printf("i5: %v\n", i5) // i5: [999 2]
}
分析:
数字,数组都是直接内存拷贝。
字符串相当于i2
指向test
字符串的内存,然后传参拷贝了一份b
,b
也指向test
字符串的内存,但是之后更改了b
的值,b
改为指向another
字符串的值,但是不影响i2
仍然指向test
字符串。
map和切片都是引用类型,实际上也都是指向的同一块内存,在上面做修改罢了。
可变长参数:
func test(args ...int) {
fmt.Println("-----------------")
for _, v := range args {
fmt.Printf("v: %v\n", v)
}
}
func main() {
test()
test(1)
test(1, 2, 3, 4, 5)
}
// -----------------
// -----------------
// v: 1
// -----------------
// v: 1
// v: 2
// v: 3
// v: 4
// v: 5
函数类型和函数变量
type关键字来定义函数类型,如:
type fun func(int,int) int
定义了一个fun函数类型,接收两个int类型的参数并返回一个int类型的返回值。
使用:
func sum(a int, b int) int {
return a + b
}
func max(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
type fun func(int, int) int
func main() {
var f fun
f = sum
s := f(1, 2)
fmt.Printf("s: %v\n", s) // 3
f = max
s = f(1, 2)
fmt.Printf("s: %v\n", s) // 2
}
高阶函数
函数作为另一个函数的参数,来进行参数传递,也可以作为返回值。
函数作为参数
func sayHello(name string) {
fmt.Printf("Hello, %s\n", name)
}
func sayBye(name string) {
fmt.Printf("Bye, %s\n", name)
}
func f1(name string, f func(string)) {
f(name)
}
func main() {
f1("tom", sayHello) // Hello, tom
f1("tom", sayBye) // Bye, tom
}
函数作为返回值
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func cal(s string) func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {
f := cal("+")
r := f(1, 2)
fmt.Printf("r: %v\n", r) // 3
f = cal("-")
r = f(1, 2)
fmt.Printf("r: %v\n", r) // -1
}
匿名函数
go语言函数不能嵌套,但是可以在函数内部定义匿名函数,简单调用。
例如:
func main() {
max := func(a, b int) int {
if a > b {
return a
} else {
return b
}
}
i := max(4, 5)
fmt.Printf("i: %v\n", i) // i: 5
}
== 闭包 ==
可以理解为定义在一个函数内部的函数。是将函数内部和函数外部连接起来的桥梁,或者说是函数和它引用环境的组合体。
闭包=函数+引用环境
demo1
外部作用域变量。
func add() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = add()
fmt.Printf("f(10): %v\n", f(10)) // f(10): 10
fmt.Printf("f(20): %v\n", f(20)) // f(20): 30
fmt.Printf("f(30): %v\n", f(30)) // f(30): 60
fmt.Println("-----------------")
f1 := add()
fmt.Printf("f1(10): %v\n", f1(10)) // f1(10): 10
fmt.Printf("f1(20): %v\n", f1(20)) // f1(20): 30
fmt.Printf("f(40): %v\n", f(40)) // f(40): 100
}
f
是一个函数并且引用了外部作用域的x
变量,此时f就是一个闭包,在f
的生命周期内,变量x
一直有效。
demo2
更改参数,构造相似功能的函数
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Printf("jpgFunc(\"test\"): %v\n", jpgFunc("test")) // jpgFunc("test"): test.jpg
fmt.Printf("jpgFunc(\"1.jpg\"): %v\n", jpgFunc("1.jpg")) // jpgFunc("1.jpg"): 1.jpg
fmt.Printf("txtFunc(\"1\"): %v\n", txtFunc("1")) // txtFunc("1"): 1.txt
}
demo3
也可以返回多个函数,类似普通函数的返回多个参数。
package main
import "fmt"
func cal(base int) (func(int) int, func(int) int) {
add := func(a int) int {
base += a
return base
}
sub := func(a int) int {
base -= a
return base
}
return add, sub
}
func main() {
add, sub := cal(0)
fmt.Printf("add(100): %v\n", add(100)) // add(100): 100
fmt.Printf("add(20): %v\n", add(20)) // add(20): 120
fmt.Printf("sub(10): %v\n", sub(10)) // sub(10): 110
}
递归
典型的,写一个斐波那契吧。
func f(a int) int {
if a == 1 || a == 2 {
return 1
}
return f(a-1) + f(a-2)
}
func main() {
for i := 1; i <= 10; i++ {
fmt.Printf("%v ", f(i))
// 1 1 2 3 5 8 13 21 34 55
}
}
明确终止条件和进入下一层的条件。
defer
将后面跟随的语句进行延迟处理,栈的形式,先出现的后执行。
特性:
- 用于注册延迟调用
- 知道
return
前才被执行,可以用来做资源清理 - 多个
defer
,先进后出执行 defer
语句中的变量,在defer
声明时就有了
func f() {
fmt.Println("start")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("end")
}
func main() {
f()
// start
// end
// 3
// 2
// 1
}
golang init函数
先于main
函数执行,实现包级别的一些初始化操作。
特点:
- 先于main函数自动执行,不能被调用
- 没有输入参数和返回值
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数
- 同一个包init函数的执行顺序不定
- 不同包的init函数根据依赖关系决定
初始化顺序
变量初始化 -> init()
-> main()
var a int = myA()
func myA() int {
fmt.Println("myA")
return 3
}
func init() {
fmt.Println("init1")
}
func init() {
fmt.Println("init2")
}
func main() {
fmt.Println("main...")
}
// myA
// init1
// init2
// main...
指针
取地址&
,根据地址取值*
。
声明指针:
var 变量名 *指针类型
demo:
func main() {
var ip *int
fmt.Printf("ip: %v\n", ip) // ip: <nil>
var i int = 100
ip = &i
fmt.Printf("ip: %v\n", ip) // ip: 0xc0000140d0
*ip = 200
fmt.Printf("i: %v\n", i) // i: 200
}
数组指针
go指针不能运算,要对应到数组每个元素的指针需要具体对标每个元素。
func main() {
a := []int{1, 3, 4}
var ptr [MAX]*int
fmt.Println(ptr) // [<nil> <nil> <nil>]
for i := 0; i < MAX; i++ {
ptr[i] = &a[i]
}
fmt.Println(ptr) // [0xc000016150 0xc000016158 0xc000016160]
var i int = 1
fmt.Println(unsafe.Sizeof(i)) // 8
}
类型定义和类型别名
类型定义
type 新类型名 旧类型名
如:
type MyInt int
var i MyInt = 100
fmt.Printf("%v: %T\n", i, i) // 100: main.MyInt
类型别名
type MyInt = int
var i MyInt = 100
fmt.Printf("%v: %T\n", i, i) // 100: int
区别
类型定义是定义了一个全新的类型,但是类型别名只在代码中存在,打印二者的类型名%T
也可以看出差别。
类型别名可以用原类型的方法,重定义的类型不能使用之前的方法。
结构体
go没有面向对象的概念,但是可以用结构体来实现
定义
type 变量名 struct {
member definition;
member definition;
member definition;
...
}
例如:
type Person struct {
id int
name string
age int
email string
}
func main() {
var tom Person
tom.age = 23
fmt.Printf("tom: %#v\n", tom)
kang := Person{name: "kang"}
fmt.Printf("kang: %#v\n", kang)
// tom: main.Person{id:0, name:"", age:23, email:""}
// kang: main.Person{id:0, name:"kang", age:0, email:""}
}
匿名结构体
有时候只想用一次,就不用起名字,直接用一下。
func main() {
var tom struct {
id int
name string
age int
email string
}
tom.age = 23
fmt.Printf("tom: %#v\n", tom)
// tom: struct { id int; name string; age int; email string }{id:0, name:"", age:23, email:""}
}
初始化
未初始化的结构体,成员都是默认值。
kang := Person{name: "kang"}
可以声明所有,也可以声明部分,也可以不声明,在下面用:
kang.name = "kang"
单独赋值。
结构体指针
type Person struct {
id int
name string
age int
email string
}
func main() {
var p *Person
tom := Person{
id: 1,
name: "tom",
age: 20,
email: "tom@gmail.com",
}
p = &tom
fmt.Printf("tom: %v\n", tom) // tom: {1 tom 20 tom@gmail.com}
fmt.Printf("p: %p\n", p) // p: 0xc00007a480
fmt.Printf("(*p): %v\n", (*p)) // (*p): {1 tom 20 tom@gmail.com}
}
new
func main() {
var p = new(Person)
p.name = "tom"
// 本该是(*p).name = "tom", 可省略
p.email = "df"
p.id = 1
p.age = 999
fmt.Printf("p: %p\n", p)
fmt.Printf("p: %v\n", *p)
// p: 0xc0000c0450
// p: {1 tom 999 df}
}
结构体作为函数参数
- 复制,和原结构体的修改互不影响
- 传递指针,能更改原结构体的数据
type Person struct {
id int
name string
age int
email string
}
func test1(person Person) {
person.id = 2
}
func test2(person *Person) {
person.id = 2
}
func main() {
p := Person{}
p.name = "tom"
p.email = "df"
p.id = 1
p.age = 999
test1(p)
fmt.Printf("p.id: %v\n", p.id) // p.id: 1
test2(&p)
fmt.Printf("p.id: %v\n", p.id) // p.id: 2
}
结构体嵌套
golang没有面向对象,也没有继承关系,但是可以用结构体嵌套来实现这种效果。
type Dog struct {
name string
age int
}
type Person struct {
name string
age int
email string
dog Dog
}
func main() {
p := Person{}
p.name = "tom"
p.email = "df"
p.age = 999
p.dog = Dog{
name: "di",
age: 3,
}
fmt.Printf("p: %#v\n", p)
// p: main.Person{name:"tom", age:999, email:"df", dog:main.Dog{name:"di", age:3}}
}
方法
可以声明一些方法,属于某个结构体。
方法就是有接收者的函数。
语法格式:
type myType struct{}
func (recv myType) my_method(para) return_type {}
func (recv *myType) my_method(para) return_type {}
方法的接受者可以结构体,也可以是结构体指针,区别:
- 值类型复制
- 指针类型不复制
type Person struct {
name string
age int
email string
}
func (p Person) eat() {
fmt.Println(p.name + " eating...") // tom eating...
p.age -= 1
}
func main() {
p := Person{}
p.name = "tom"
p.email = "df"
p.age = 999
p.eat()
fmt.Printf("p.age: %v\n", p.age) // p.age: 999
}
如果是指针类型?
func (p *Person) eat() {
fmt.Println(p.name + " eating...") // tom eating...
p.age -= 1
}
func main() {
p := Person{}
p.name = "tom"
p.email = "df"
p.age = 999
p.eat()
fmt.Printf("p.age: %v\n", p.age) // p.age: 998
}