01. 指针
1.1 基本操作
| 符号 | 名称 | 作用 |
|---|---|---|
| &变量 | 取址符 | 返回变量所在的地址 |
| *指针变量 | 取值符 | 返回指针指地址存储的值 |
var x = 1
// 取址
fmt.Println(&x)
// p 指针变量类型,指针变量对应的值是什么类型呢?是 int
var p *int = &x
fmt.Println(p == &x) // true
// 获取指针变量的地址值(地址)对应的值(内容)?
fmt.Println(*p)
var x int = 100
// * 表示指针类型,p1 的类型是指针(地址)
// *int 表示,这个指针指向的内容是一个 int 类型
// & 是取址符号
// p1 是指针变量,&x 是(地址)值
// p1 是 int 类型的指针变量
var p1 *int = &x
var s string = 'ifer'
// p2 是指针类型,这个指针对应的值是 string
var p2 *string = &s
var a = 100
var b = &a
// b 的地址给了 c
var c = &b
**c = 200
fmt.Println(a)
1.2 new 函数
基本数据类型声明后都有默认值,例如 int、string、bool 等
var x int
fmt.Println(x) // 0
引用类型
// 声明一个 int 类型的指针变量
var p *int
fmt.Println(p) // <nil> 空对象
*p = 100 // 报错
指针类型声明后并没有默认值,直接操作是会报错的,如下:
var p *int
fmt.Println(p) // <nil>
*p = 100 // invalid memory address or nil pointer dereference
如何处理呢?答案:new,new 的返回值是指向类型的指针。
var p *int
p = new(int) // 弄一个空间(默认存的 int 的初始值 0),假如地址是 0x11,把这个地址 ox11 给了 p
*p = 100
fmt.Println(p, *p) // 0xc000094098, 100
2. 数组
var 数组名 [元素数量]元素类型
声明数组。
var names [5]string
fmt.Println(names, reflect.TypeOf(names)) // [ ] [5]string
初始化数组的几种方式。
- 先声明再赋值。
var names [5]string
names[0] = "A"
names[1] = "B"
fmt.Println(names) // [A B ]
- 声明并赋值。
var names = [5]string{"A", "B"}
fmt.Println(names) // [A B ]
- 一个不限长度的数组。
var names = [...]string{"A", "B"}
fmt.Println(names) // [A B]
- 通过索引设置数组。
var names = [...]string{0: "A", 5: "B"}
fmt.Println(names) // [A B]
- 基于索引访问和修改数组元素。
var names = [...]string{"A", "B", "C", "D"}
// 访问
fmt.Println(names[0]) // A
// 修改
names[0] = "X"
fmt.Println(names[0]) // X
// 切片取值
fmt.Println(names[0:2]) // [X B]
fmt.Println(reflect.TypeOf(names[0:2])) // []string
- for 和 for range 循环数组的一个差异?
for i:=0;i<len(names);i++{
names[i] = "能改"
}
for k,v := range names{ // range 表达式是副本参与循环
v = "不能改"
}
3. 切片
算容量就从起始地址开始算
容量:数组总 - 切片起始位置
切片里面存的是:起始地址、长度、容量
var a = []int{1, 2, 3}
b := a // 值拷贝,只不过值里面装的都是地址,一切皆为值拷贝,只不过拷贝的值里面有地址
a[0] = 100
fmt.Println(b)
4. make
带地址的就没有默认值,没有默认值赋值就会有 Bug。需要自己初始化(new 或 make)
扩容会创建一个新数组!!!
// 对原数组修改从 x,往后扩容了一个 1
// 这句话什么意思?底层原理是什么?GPT
append(x, 1)
append(x, 2)
// 注意和上面的区别
x = append(x, 1)
5. 学习函数使用
Go 代码中至少要有一个 main 入口函数。
返回大数
package main
func main() {
r := getMax(1, 2)
println(r)
}
// int 类型的,比完大小之后我希望返回大的那一个数值
func getMax(num1 int, num2 int) int {
var result int
if num2 > num1 {
result = num2
} else {
result = num1
}
return result
}
交换字符串
函数可以有多个返回值。
package main
func main() {
a, b := swip("san", "zhang")
println(a, b)
}
func swip(x, y string) (string, string) {
return y, x
}
计算周长和面积
可以给返回类型指定名称。
package main
func main() {
zc, area := calc(2, 4)
println(zc, area)
}
// 计算长方形的面积和周长,返回值这儿起的有名字,return 是根据名字对应的
func calc(len, wid float64) (zc float64, area float64) {
// 注意这儿是 =
area = len * wid
zc = (len + wid) * 2
// 函数的返回值的类型也可以命名,这两个的顺序无所谓
return zc, area
}
// 计算长方形的面积和周长,返回指这儿没有起名字,return 是根据顺序对应的
func calc(len, wid float64) (float64, float64) {
// 注意这儿是 :=
area := len * wid
zc := (len + wid) * 2
// 函数的返回值的类型也可以命名,这两个的顺序无所谓
return zc, area
}
可变参数
求任意多个实参的和。
package main
func main() {
r := getSum(1, 2)
println(r)
}
func getSum(nums ...int) int {
sum := 0
// nums 是一个切片
for i := 0; i < len(nums); i++ {
// 取出来
//sum = sum + nums[i]
sum += nums[i]
}
return sum
}
作用域
函数内部定义的变量,只能在函数内部调用(根据括号区分)。
package main
import "fmt"
func main() {
if temp := 1; true {
println(temp)
}
// 这里是访问不到 temp 的
fmt.Println(temp)
}
递归函数
package main
import "fmt"
func main() {
r := getSum(5)
fmt.Println(r)
}
func getSum(n int) int {
if n == 1 {
return 1
}
return getSum(n-1) + n
}
defer
多个 defer
如果有多个 defer,先进后出(栈)。
defer 作用:处理一些善后的问题,比如错误,文件、网络流关闭等。
package main
import "fmt"
func main() {
go f("1")
fmt.Println("2")
// 等其他执行完毕之后,再执行
defer f("3")
fmt.Println("4")
}
func f(s string) {
fmt.Println(s)
}
// 2 4 3 1
传递参数
package main
import "fmt"
func main() {
n := 10
fmt.Println("main n=", n)
// defer 的时候,传递过去的是那个瞬时状态
defer f(n)
n++
fmt.Println("main end n=", n)
}
func f(n int) {
fmt.Println("f 函数中 n=", n)
}
关闭文件
package main
func main() {
文件.open()
// 读写操作
// ...
defer 文件.close() // 最后关闭文件
}
函数的本质
package main
import "fmt"
func main() {
// 正常的调用
f12()
f2 := f12
// 函数赋值给了另外一个变量,也是可以调用这个变量
// f12 和 f2 本质指向了同一个内存空间,空间中的代码一致 { fmt.Println("我是f12函数") }
f2()
}
func f12() {
fmt.Println("我是f12函数")
}
匿名函数
package main
import "fmt"
func main() {
// 匿名函数,在函数体后增加(),调用了这个函数,匿名函数只能被调用一次
func() {
fmt.Println("我是一个匿名函数")
}()
// 将匿名函数进行赋值,就可以实现多次调用
f3 := func() {
fmt.Println("我是一个匿名函数")
}
f3()
// 匿名函数是否可以添加参数和返回值
r1 := func(a, b int) int {
return a + b
}(1, 2)
fmt.Println(r1) // 3
}
回掉函数
高阶函数。
package main
import "fmt"
// 回调函数
func main() {
// 高阶函数调用
r2 := oper(1, 2, add)
fmt.Println(r2)
r3 := oper(1, 2, sub)
fmt.Println(r3)
}
func oper(a, b int, fun func(int, int) int) int {
r := fun(a, b)
return r
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
关于闭包
package main
import "fmt"
func main() {
// 调用 increment 后返回的是一个函数,还没有执行
fn := increment()
fn()
fn()
fn()
r := fn()
fmt.Println(r) // 4
}
func increment() func() int {
// 定义一个局部变量
i := 0
// 在外层函数内部定义一个匿名函数,给变量自增并返回
fun := func() int {
i++
return i
}
return fun
}
函数中的参数传递
数组默认是值传递。
package main
import "fmt"
func main() {
// 定义一个数组
arr := [4]int{1, 2, 3, 4}
update(arr)
fmt.Println("arr1修改前的数据:", arr) // 1 2 3 4
}
func update(arr [4]int) {
arr[0] = 100
}
切片默认是引用传递。
package main
import "fmt"
func main() {
// 定义一个切片
s := []int{1, 2, 3, 4}
update(s)
fmt.Println("s1修改后的数据:", s) // 100 2 3 4
}
func update(s []int) {
s[0] = 100
}