Go语言基础语法|青训营笔记

50 阅读10分钟

函数-递归调用

一个函数在函数体内又调用了本身,称为递归调用

package main


import "fmt"


func test(n int) {
    if n > 2 {
        n--
        test(n)
    }
    fmt.Println("n=", n)
}


func main() {
    test(4)
}

递归练习

斐波那契数

用递归方式,求出斐波那契数列1,1,2,3,5,8,13...

给出一个整数,求出它的斐波那契数

package main


import "fmt"


//斐波那契数
//用递归方式,求出斐波那契数列1,1,2,3,5,8,13...
//给出一个整数,求出它的斐波那契数


//1、当n==1||n==2,返回1
//2、当n>2时,返回前两个数之和


func fbn(n int) int {
    if n == 1 || n == 2 {
        return 1
    } else {
        return fbn(n-1) + fbn(n-2)
    }
}
func main() {
    var res int
    fmt.Scanln(&res)
    fmt.Println("res的斐波那契数是=", fbn(res))
}

已知f(1)=3;f(n)=2*f(n-1)+1;求函数值f(n)

package main


import "fmt"


//已知f(1)=3;f(n)=2*f(n-1)+1;求函数值f(n)


func f(n int) int {
    if n == 1 {
        return 3
    } else {
        return 2*f(n-1) + 1
    }
}
func main() {
    //测试
    fmt.Println("f(1)=", f(1))
    fmt.Println("f(5)=", f(5))


}

函数注意事项和细节

  1. 函数的形参列表可以是多个,返回值列表可以是多个
  2. 形参列表和返回值了表的数据类型可以是值类型,也可以是引用类型。
  3. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似private
  4. 函数中的变量是局部的,函数内不能生效
  5. 基本数据类型和数组都是值传递的,即进行值传递,即进行拷贝,在函数内修改,函数外不生效
func test02(n1 int) {
    n1 = n1 + 10
}


func main() {
    n1 := 20
    test02(n1)

    fmt.Println("n1=", n1)
}
  1. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针方式操作变量,从效果上来看类似引用
//n1是*int类型
func test03(n1 *int) {
    *n1 = *n1 + 100
}


func main() {
    n1 := 20
    test03(&n1)
    fmt.Println("n1=", n1)


}
  1. Go函数不支持重载
  2. 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用
func getsum(n1 int, n2 int) int {
    return n1 + n2
}


func main() {
    a := getsum
    fmt.Printf("a的数据类型是%T,getsum的数据类型是%T\n", a, getsum)
    res := getsum(1, 1)
    fmt.Println(res)
}
  1. 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
func getsum(n1 int, n2 int) int {
    return n1 + n2
}


func myFun(fanvar func(int, int) int, num1 int, num2 int) int {
    return fanvar(num1, num2)
}


func main() {
    a := getsum
    fmt.Printf("a的数据类型是%T,getsum的数据类型是%T\n", a, getsum)


    res := getsum(1, 1)
    fmt.Println(res)


    res2 := myFun(getsum, 50, 50)
    fmt.Println("res2=", res2)
}
  1. 为了简化数据类型定义,go支持自定义数据类型

基本语法:type 自定义数据类型名 数据类型

var num1 myInt
var num2 int
num1 = 40
num2 = int(num1)
fmt.Println("num1=",num1)
fmt.Println("num2=",num2)
package main


import "fmt"


func getsum(n1 int, n2 int) int {
    return n1 + n2
}


type myFunType func(int, int) int


func myFun(fanvar myFunType, num1 int, num2 int) int {
    return fanvar(num1, num2)
}


func main() {
    type myInt int


    var num1 myInt
    var num2 int
    num1 = 40
    num2 = int(num1)
    fmt.Println("num1=", num1)
    fmt.Println("num2=", num2)


    res3 := myFun(getsum, 50, 50)
    fmt.Println("res2=", res3)
}
  1. 支持对函数返回值名
package main


import "fmt"


func getsum(n1 int, n2 int) (sum int) {
    sum = n1 + n2
    return
}


func main() {
    var n1 int = 12
    var n2 int = 11
    fmt.Println(getsum(n1, n2))
}
  1. 使用_标识符,忽略返回值
  2. Go中的可变参数
func sum(args...int) sum int{
}
func sum(n1 int,args...int) sum int{
}

args是slice切片,通过args[index]可以访问到各个值

package main


import "fmt"


func sum(n1 int, args ...int) int {
    sum := n1
    for i := 0; i < len(args); i++ {
        sum += args[i]
    }
    return sum
}


func main() {
    res4 := sum(10, 1, 4, 5, 6, 4)
    fmt.Println("res4=", res4)
}

如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后

Init函数

每一个源文件都可以包含一个Init函数,该函数会在main函数执行钱,被Go运行框架调用,也就是说Init会在main函数前被调用

package main


import "fmt"


//Init函数,通常可以在Init函数中完成初始化操作
func init() {
    fmt.Println("Init()...")
}


func main() {
    fmt.Println("main()...")
}

细节

  1. 如果一个文件同时包含全局变量定义,Init函数和main函数,则执行的流程是:

全局变量定义->Init函数->main函数

var age = test()

func test() int {
    fmt.Println("test()")
    return 90
}


//Init函数,通常可以在Init函数中完成初始化操作
func init() {
    fmt.Println("Init()...")
}


func main() {
    fmt.Println("main()...")
}
  1. Init函数最主要的作用,就是完成一些初始化工作
package utils


var Age int
var Name string


func init() {
    Age = 55
    Name = "Tom"
}
package main


import (
    "fmt"
    utils "funcinit/utils"
)


var age = test()


func test() int {
    fmt.Println("test()")
    return 90
}


// Init函数,通常可以在Init函数中完成初始化操作
func init() {
    fmt.Println("Init()...")
}


func main() {
    fmt.Println("main()...")
    fmt.Println("Age=", utils.Age)
    fmt.Println("Name=", utils.Name)


}

匿名函数

Go支持匿名函数,如果某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用

使用方式

package main


import "fmt"


var (
    fun1 = func(n1 int, n2 int) int {
        return n1 + n2
    }
)


func main() {
    //方式1
    res1 := func(n1 int, n2 int) int {
        return n1 + n2
    }(10, 20)


    fmt.Println("res1=", res1)


    //方式2
    a := func(n1 int, n2 int) int {
        return n1 - n2
    }
    res2 := a(10, 30)
    fmt.Println("res2=", res2)


    //方式3
    //全局匿名函数的使用
    res3 := fun1(4, 9)
    fmt.Println("res3=", res3)


}

闭包

闭包就是一个函数与其相关的引用环境组合的一个整体

package main


import "fmt"


func AddUpper() func(int) int {
    var n int = 10
    return func(x int) int {
        n = n + x
        return n
    }
}


func main() {
    f := AddUpper()
    fmt.Println(f(1))
}
  1. AddUpper是一个函数,返回的数据类型是func(int)int
  2. 返回的是一个匿名函数,但是这个匿名函数引用到函数外的你,因此这个匿名函数就和n形成一个整体,构成闭包
  3. 可以这样理解:闭包是类,函数是操作,n是字段,函数和它使用到n构成闭包
  4. 当我们反复的调用f函数时,因为n时初始化一次,因此每调用一次就进行累计
  5. 我们要搞清楚闭包的关键,就是要分析出返回的函数它使用到哪些变量,因为函数和它引用到的变量共同构成闭包。

实践

  1. 编写一个函数makeSuffix(suffix string)可以接收一个文件后缀名,并返回一个闭包
  2. 调用闭包,可以传入一个文件名,如果该文件没有指定的后缀,则返回文件名.jpg,如果已经有了.jpg后缀,则返回原文件名
  3. 要求使用闭包的方式完成
  4. string.HasSuffix,该函数可以判断某个字符是否有指定后缀
package main


import (
    "fmt"
    "strings"
)


func makeSuffix(suffix string) func(string) string {
    return func(name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }
        return name
    }
}


func main() {
    //返回一个闭包
    f := makeSuffix(".jpg")
    f2 := makeSuffix(".jpg")
    fmt.Println("文件名处理后=", f("winter"))
    fmt.Println("文件名处理后=", f2("bird.jpg"))
}
  1. 返回的匿名函数和makeSuffix(Suffix string)的suffix变量组成一个闭包,因为返回的函数引用到suffix这个变量
  2. 如果使用传统的方法,需要每次都传入后缀名,比如jpg,二闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用

函数中-defer

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,即使的释放资源,Go的设计者提供defer(延时机制)。

package main


import "fmt"


func sum(n1 int, n2 int) int {


    //当执行到defer时,会将defer后面的语句压入到独立的栈
    //当函数执行完毕后,再从defer栈,按照先入后出的方式出栈
    defer fmt.Println("ok1,n1=", n1)
    defer fmt.Println("ok2,n2=", n2)
    res := n1 + n2
    fmt.Println("ok3,res=", res)
    return res
}


func main() {
    sum(10, 20)
}

细节

  1. 当go执行到一个defer时,不会立即执行defer后面的语句,而是将defer后的语句压入到一个栈中,然后继续执行下一个语句
  2. 但函数执行完毕后,在从defer栈中,一次从栈顶取出语句执行
  3. 在defer将语句放入到栈式,也会将相关的值拷贝同时入栈。

函数参数的传递方式

  1. 值传递
  2. 引用传递

不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。

值类型和引用类型

  1. 值类型:基本数据类型int系列,bool,string、数组和结构体struct
  2. 引用类型:指针、slice切片、map、管道chan、Interface等都是引用类型
  3. 如果希望函数内的变量能修改函数外的

变量的作用域

  1. 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部
  2. 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效
package main


import "fmt"


var age int = 50
var Name string = "jack~"


func test() {
    age := 10
    Name := "tom"
    fmt.Println("age=", age)
    fmt.Println("Name", Name)
}


func main() {
    fmt.Println("age=", age)
    fmt.Println("Name=", Name)
    test()
}
  1. 如果变量是在一个代码块,比如for/if中,那么这个变量的作用域就只在改代码块

字符串中常用的函数

  1. 统计字符串的长度,按字节len(str)
  2. 字符串遍历,同时处理有中文的问题
str2 := "hello 北京"
r := []rune(str2)
for i := 0; i < len(r); i++ {
        fmt.Printf("字符=%c,字符=%c\n", str2[i], r[i])
    }
  1. 字符串转整数 你,err =strconv.Atoi(str)
n, err := strconv.Atoi("123")
if err != nil {
        fmt.Println("转换错误", err)
    } else {
        fmt.Println("转成的结果是", n)
    }
  1. 整数转字符串 str = strconv.Itoa()
str3 := strconv.Itoa(12345)
fmt.Printf("str3=%v,st3r=%T", str3, str3)
  1. 字符串转[]byte var bytes = []byte("hello go")
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes)
  1. []byte 转字符串 str=string([]byte{97,98,99})
str4 := string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str4)
  1. 10进制转2、8、16进制: str = strconv.FormatInt(123,2)//2->8,16
str5 := strconv.FormatInt(123, 2)
fmt.Printf("123对应的二进制是=%v\n", str5)
str6 := strconv.FormatInt(123, 16)
fmt.Printf("123对应的二进制是=%v\n", str6)
  1. 查找子串是否在指定的字符串中:strings.Contain("seafood","foo")
str7 := strings.Contains("seafood", "foo")
fmt.Printf("str7+%v\n", str7)
  1. 统计一个字符串中有几个不重复的子串
str8 := strings.Count("ceheese", "ee")
fmt.Printf("str8=%v\n", str8)
  1. 不区分大小写的字符串比较(==是区分字母大小写的)strings.EqualFold:   
fmt.Println(strings.EqualFold("abc", "Abc"))
fmt.Println("abc" == "Abc")
  1. 返回子串在字符串第一次出现的index值,如果没有返回-1:strings.Index()
index := strings.Index("NLT_abc", "hello")
fmt.Printf("Index=%v\n", index)
  1. 返回子串在字符串最后一次出现的位置,如果没有返回-1:strings.LastIndex
index1 := strings.LastIndex("go golang", "go")
fmt.Printf("Index1=%v\n", index1)
  1. 将指定子串替换成另一个子串:strings.Replace("go go hello","go","golang",n)n可以指定你希望填几个,如果n=-1表示全部替换
str9 := strings.Replace("go go hello", "go", "北京", -1)
fmt.Printf("str=%v\n", str9)
  1. 按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组,strings.Split()
str10 := strings.Split("wo zhen de hao xiang ni a", " ")
for i := 0; i < len(str10); i++ {
        fmt.Printf("str10[%v]=%v\n", i, str10[i])
    }
fmt.Printf("str10=%v\n", str10)
  1. 将字符串的字母进行大小写转换:strings.ToLower()/strings.ToUpper()
str = "golang Hello"
str = strings.ToLower(str)
fmt.Printf("str=%v\n", str)
str = strings.ToUpper(str)
fmt.Printf("str=%v\n", str)
  1. 将字符串左右两边的空格去掉:strings.TrimSpace()
str = strings.TrimSpace(" tn a lone gopher ntrn  ")
fmt.Printf("str=%q\n", str)
  1. 将字符串左右两边指定的字符去掉:strings.Trim()
str = strings.Trim("! hel!lo! ", " !")
fmt.Printf("str=%q\n", str)
  1. 将字符串左边指定的字符去掉:strings.TrimLeft()
  2. 将字符串右边指定的字符去掉:strings.TrimRight()
  3. 判断字符串是否以指定字符串开头:strings.HasPrefix()
  4. 判断字符串是否以指定的字符串结束:strings.HasSuffix()

时间和日期相关函数

  1. 时间和日期相关函数,需要导入time包
  2. Time.time类型
  3. 格式化日期
package main


import (
    "fmt"
    "time"
)


func main() {
    now := time.Now()
    fmt.Printf("now=%v now type=%T", now, now)


    fmt.Printf("年=%v\n", now.Year())
    fmt.Printf("月=%v\n", int(now.Month()))
    fmt.Printf("日=%v\n", now.Day())
    fmt.Printf("时=%v\n", now.Hour())
    fmt.Printf("分=%v\n", now.Minute())
    fmt.Printf("秒=%v\n", now.Second())


    //格式化日期和时间
    fmt.Printf("当前年月日 %d-%d-%d %2d:%2d:%2d \n", now.Year(), now.Month(),
        now.Day(), now.Hour(), now.Minute(), now.Second())
    datestr := fmt.Sprintf("当前年月日 %d-%d-%d %2d:%2d:%2d \n", now.Year(), now.Month(),
        now.Day(), now.Hour(), now.Minute(), now.Second())
    fmt.Printf("dateStr=%v", datestr)


    //格式化日期的第二种方式
    fmt.Printf(now.Format("2006-01-02 15:04:05"))
    fmt.Println()
    fmt.Printf(now.Format("2006-01-02"))
    fmt.Println()
    fmt.Printf(now.Format("15:04:05"))
    fmt.Println()
}
  1. 时间的常量:在程序中可用于获取指定时间单位的时间

const{

    Nanosecond Duration = 1     //纳秒

    Microsecond = 1000*Nanosecond   //微秒

    Millisecond = 1000*Microsecond  //毫秒

    Second = 1000*Millisecondconst  //秒

    Minute = 60*Second              //分钟

    Hour = 60*Minute                //小时

}

  1. 休眠 func Sleep()
//每隔一秒钟打印一个数字,打印00时就退出
i := 0
for {
        i++
        fmt.Println(i)
        time.Sleep(time.Microsecond)
        if i == 100 {
            break
        }
    }
  1. time的Unix和UnixName的方法(获取随机数字)
fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())

内置函数

  1. len:用来求长度,比如string、array、slice、map、channel
  2. new:用来分配内存,主要用来分配值类型,比如int、float32、struct...返回的是指针
  3. make:用来分配内存,主要用来分配引用类型,比如