go语言基础语法(下) | 青训营笔记

61 阅读6分钟

函数

格式:func 函数名(变量名 变量类型) 返回值 {...}

func test1(a int, b int) int {
    return a+b
}

golang可以让函数有两个返回值,return后面用逗号隔开

函数返回值可以自己命名,这样就直接return就行

func test2(a int,b int)(c int,d int) {
    c=a
    d=b
    return
}
func test3(a int, b int)(int, string){
    return a+b,"successful"
}

函数也是一种数据类型,可以被赋值给变量。

函数可以作为形参传递给另一个函数,也可以作为一个函数的返回值(此时称为函数闭包)

func t1(a int)int{
    return a
}
func t2(b func(int))int{
    return b
}

type f1 func(int)
func t3(c f1)int{
    return c
}
func main(){
    a := test
    t2(a)
    t3(a)
}

匿名函数

定义的时候调用

func main(){
    res := func(a int)int{
        return a+10
    }(10)
    fmt.Println(res)
}

结合前文内容,匿名函数可以作为数据类型赋值给一个变量,然后调用

闭包

闭包(Closure)在某些编程语言中也被称为 Lambda 表达式。

闭包的函数内的变量不会被垃圾回收,可以重复使用。

闭包就是一个函数和与其相关的引用环境组合的一个整体。即引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。被捕获到闭包中的变量让闭包本身拥有了记忆效应,变量会跟随闭包生命期一直存在。

func AddUpper() func(int)int{ 
        var n int = 0
        return func(x int) int {
                n = n + x 
                return n 
            } 
        } 
func main() {
    fmt.Println("闭包返回:",f(1)) //>>1
    fmt.Println("闭包返回:",f(2)) //>>3
    fmt.Println("闭包返回:",f(3)) //>>6
}

init函数

  • init函数是初始化函数,会在main函数被调用之前被自动调用
  • 每个源文件都可以包含一个init函数
  • 执行顺序:全局变量的定义->init函数调用->main函数调用
  • 包导进来的时候,该包下的init函数就会被调用

new函数

  • new函数分配内存
  • 返回对应类型的指针,因此获取其中的值时要加*

指针

相比于c和c++,golang的指针操作非常有限,只能对传入参数进行修改

func add1ptr(n *int){
    *n += 1
}
func main(){
    n := 5
    fmt.Printf(add1prt( &n )) // >>11
}

传入时记得取址,操作时记得*号

结构体

格式:type 结构体名 struct{...}

初始化时:变量名 := 结构体名:{ 结构体元素名 : 值 ,结构体元素名 : 值 }

访问时用 “.” 访问,使用结构体的指针时也是 "." 不需要 "->",在函数内修改结构体内容时需要用指针传入

type test struct{
    a string
    b string
}
func main(){
    temp := test{a:"a",b:"b"}
    temp.a = "c"
}

根据开闭原则,应该对结构体进行封装,将元素设置为私有(首字母小写),然后使用Get、Set函数访问。

结构体方法

类似于类成员函数

创建:func (结构体) 函数名 (传入参数) 返回值 {...}

调用:结构体变量.方法名(参数名)

func(temp test)testtest(input string) bool {
    return test.a == input
}
func main(){
    fmt.Printf(temp.testtest("a"))
}

结构体类型是值类型,方法调用时是值拷贝的方式。因此,直接在方法里对结构体内的变量赋值,不会改变结构体变量的值

若要改变结构体的变量的值,要使用结构体指针

func(u *User)test(){    //结构体指针
    (*u).Name = "user"    //*可写可不写,go内部帮我们优化了
}
func main(){
    var u User
    (&u).test()    //&也是可写可不写
}

String()方法

和java的toString()作用一致,当打印该结构体时自动调用String()函数

func (u *User)String() string{
    str := fmt.Sprintf("Name=%v , Age=%v",u.Name,u.Age)
    return str
}

继承

golang不支持extend关键字,而是使用组合的方式实现继承

// 模拟动物行为的接口
type IAnimal interface {
    Eat() // 描述吃的行为
}

// 动物 所有动物的父类
type Animal struct {
    Name string
}

// 动物去实现IAnimal中描述的吃的接口
func (a *Animal) Eat() {
    fmt.Printf("%v is eating\n", a.Name)
}

// 动物的构造函数
func newAnimal(name string) *Animal {
    return &Animal{
        Name: name,
    }
}

// 猫的结构体 组合了animal
type Cat struct {
    *Animal
}

// 实现猫的构造函数 初始化animal结构体
func newCat(name string) *Cat {
    return &Cat{
        Animal: newAnimal(name),
    }
}

cat := newCat("cat")
cat.Eat() // cat is eating

重写

// 猫结构体IAnimal的Eat方法
func (cat *Cat) Eat() {
    fmt.Printf("children %v is eating\n", cat.Name)
}

cat.Eat()
// children cat is eating

多态

传入的是接口,可以处理所有组合了Animal的单位类型

func check(animal IAnimal) {
    animal.Eat()
}

断言

方法1 通过是否能赋值判断是否是这个类型
user1,flag := person1.(User) //判断person1是否是User类型
//是的话赋值给user1变量
//flag判断是否成功

可以放在if语句中使用

if user1,flag := person1.(User);flag{
    fmt.Println("是User")
}
方法2 通过type关键字判断类型
switch person1.(type){
    case User:
        break
}

异常处理

golang用error类型来储存错误

在使用函数时,返回值中加入error的变量来返回错误信息

func test(a int, b int) (c int, err error) {
    if(b != 0){
        return a/b, nil //没有错误时返回nil
    }
    return a, error.New("分母不能为0")
}

如果出现了错误想退出,可以将err传入panic()函数,这样之后的语句就不会执行

panic(err)

字符串操作

  • 对字符串本身的操作 (import "strings")

    s := "aaabbbccc"
    len(s) //长度 >>9
    strings.Contains(s, "bbb") //s中是否存在”bbb”
    strings.Index(s, "bb") //bb的下标  >>3
    strings.Join([]string{"aaa","ccc"},"bbb") //连接多个字符串 >> aaabbbccc
    strings.Repeat("aa", 3) //重复多个字符串 >>aaaaaa
    strings.Replace(a, "bbb", "ddd", -1) // 替换字符串 >>aaadddccc
    strings.Split(a, "bbb") //分割字符串(与java相同)
    
  • 字符串转其他类型 (import "strconv")

    f, _ := strconv.ParseInt("111", 10, 64)   //转十进制64位
    
  • Atoi 和 itoA

    Atoi 是字符串转数字,itoA是数字转字符串(自动)

    i1, err := strconv.Atoi("123") //il = 123
    

    当输入不合法时err储存错误信息

JSON

当结构体的每个变量名都是大写的时候,可以将其转为JSON类型(将json输出时要强转为string型)

a := data{Id:"001",Name:"aaa"}
js := json.Marshal(a)
fmt.Println(string(js)) // >>{"Id":"001","Name":"aaa"}

时间处理

now := time.Now() //获取当前时间
now.Unix() //获取时间戳

构造带时区的时间

date1 := time.Date(2023,1,15,21,30,50,0,time.UTC)
fmt.Println(date.Year(),date.Month(),date.Minute())

进程

os.Args() //获取命令行参数
os.Getenv() //获取环境变量
os.Setenv() //写入环境变量
buf,err := exec.Command("gred","127.0.0.1","/etc/hosts").CombinedOutput() //快速启动子进程

defer

defer:延迟执行机制,用于在函数执行完毕后及时释放资源

带defer关键字的语句不会被立即执行,而是压入栈中,待函数执行完毕后(return语句之后),再将defer栈中的语句取出执行

func add(a,b int)int{
    defer fmt.Println(a)
    defer fmt.Println(b)
    a+=100    //压入栈中时a1,所以打印出来的a还是1
    sum := a+b
    fmt.Println(sum)
    return sum
}
func main(){
    sum := add(1,2)
    fmt.Println(sum)
}

结果:

>>103
>>2
>>1
>>103

defer+recover处理错误

recover是一个内置函数,可以捕获错误

func test(){
    defer func(){
        err := recover()
        if err!=nil{
            fmt.Println("发生错误")
        }
    }()   //这个括号是调用该匿名函数,参阅上文
    //正常代码
}