Day002 Go 基础语法知识

72 阅读6分钟

01. 指针

1.1 基本操作

符号名称作用
&变量取址符返回变量所在的地址
*指针变量取值符返回指针指地址存储的值
var x = 1
// 取址
fmt.Println(&x)

// p 指针变量类型,指针变量对应的值是什么类型呢?是 int
var p *int = &x
fmt.Println(p == &x) // true
// 获取指针变量的地址值(地址)对应的值(内容)?
fmt.Println(*p)

image.png

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

初始化数组的几种方式。

  1. 先声明再赋值。
var names [5]string
names[0] = "A"
names[1] = "B"
fmt.Println(names) // [A B   ]
  1. 声明并赋值。
var names = [5]string{"A", "B"}
fmt.Println(names) // [A B   ]
  1. 一个不限长度的数组。
var names = [...]string{"A", "B"}
fmt.Println(names) // [A B]
  1. 通过索引设置数组。
var names = [...]string{0: "A", 5: "B"}
fmt.Println(names) // [A     B]
  1. 基于索引访问和修改数组元素。
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
  1. 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
}