Go的零散知识点(一)

1. 格式化输出

对数值进行格式化输出的时候,使用:
%v 可用来表示复数;
%t 来表示输出的值为布尔型;
%d 来表示输出的值为整数;
%x和%X 来表示输出的值为16进制的数字;
%g 来格式化浮点型;
%f 用来输出浮点数;
%e 输出科学计数表示法;
%0d 用于规定输出定长的整数,其中开头的数字 0 是必须的;
%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00

2. int 和 int64 是相同的类型吗?

不是。
用代码去验证:

package main

func main(){
    var a int
    var b int64
    a = 15
    // b = a + a   //编译错误: cannot use a + a (type int) as type int64 in assignment
    b = a + 5   //5
}

总结一句话:

int 在32位系统上是4个字节
int 在64位系统上是8个字节

int32 在多少位系统都是4个字节
int64 在多少位系统都是8个字节

3. 算数优先级

优先级     运算符
 7      ^ !
 6      * / % << >> & &^
 5      + - | ^
 4      == != < <= >= >
 3      <-
 2      &&
 1      ||

4. 类型更名

可通过使用type TZ int ,将TZ变成int类型的新名称,如

package main
import "fmt"

type TZ int

func main() {
    var a, b TZ = 3, 4
    c := a + b
    fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}

注意:类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法

5. 字符的格式化输出

格式化说明符 %c 用于表示字符;
当和字符配合使用时,%v 或 %d 会输出用于表示该字符的整数;
%U 输出格式为 U+hhhh 的字符串。

6. go不支持函数重载

函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参 / 或者不同的返回值。

7. 值类型和引用类型

  • 值类型:基本数据类型,如 int系列、float系列、bool、string、数组结构体 struct
  • 引用类型:指针、slice切片、map、管道chan、interface等。

区别:

  • 值类型:变量直接存储值,内存通常在栈中分配
  • 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据值,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,有GC来回收。

8. 函数命名规范

函数的命名规范遵循标识符命名规范,首字母不能是数字。

首字母大写该函数可以被本包文件和其它包文件使用。类似public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似privat。

9. init函数的执行顺序

每一个源文件都可以包含一个init函数,init函数总是在main函数之前执行的。
如果一个文件同时包含全局变量定义、init 函数和main 函数,则执行的流程为:全局变量定义 —> init 函数 —> main 函数

package main
import (
    "fmt"
)

var age = test()  //全局变量

//为了可以看到全局变量是先被初始化的
func test() int {
    fmt.Println("test()执行了")
    return 23
}

//init函数,通常用来完成初始化操作
func init(){
    fmt.Println("init()执行了") 
}

func main(){
     fmt.Println("main()执行了...age=", age)
}

10. 包之间的函数执行顺序

假设有两个源文件 main.go 和 utils.go ,它们分别在main目录和util目录下,main.go需要引用utils.go并使用其内部的函数。且这两个源文件的内部都有全局变量和init函数。

则执行的顺序是:utils的全局变量 —> utils的init函数 —> main的全局变量 —> main的init函数 —> main函数

11. 匿名函数

Go支持匿名函数,匿名函数就是没有名字的函数。如果某个函数只是使用一次的话,可以考虑使用匿名函数。当然,匿名函数也可以实现多次调用。

匿名函数的使用方式:

  1. 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。
func main(){
    
    res := func(n1 int, n2 int) int {
        return n1 + n2    
    }(10, 20)
    
    fmt.Println("res=", res)
}
  1. 将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数。
func main(){
    
    a := func(n1 int, n2 int) int {
        return n1 - n2    
    }
    
    res1 := a(10, 30)
    fmt.Println("res1=", res1)
    res2 := a(50, 30)
    fmt.Println("res2=", res2)
}

扩展:全局匿名函数
如果将匿名函数赋给一个全局变量,那么这个匿名函数就成为一个全局匿名函数。

package main
import (
    "fmt"
)
//demo为一个全局匿名函数
var (
    demo = func (n1 int, n2 int) int {
        return n1 * n2    
    }
)

func main(){
    res := demo(8, 9)
     fmt.Println("res=", res)
}

12. 闭包

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

//AddUper()函数返回的是一个函数
func AddUper() func (int) int {
    
    var n int = 10
    return func(x int) int {
        n = n + x
        return n    
    }
}

func main(){
    
    f := AddUper() // 这时f是一个函数
    fmt.Println(f(1))  // 11
    fmt.Println(f(2))  // 13
    fmt.Println(f(3))  // 16
}

上述代码说明:

  • AddUper() 是一个函数,其返回的数据类型是一个函数,即 func (int) int
  • AddUper()返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,所以这个匿名函数就和n形成一个整体,构成闭包。
  • 当反复调用f函数时,因为n是初始化一次,所以每调用一次就进行累计。

再举一个例子:

func add(demo int) func(int) int {
    
    fmt.Printf("%p\n", &demo)  //打印变量地址,可以看出来 内部函数对外部传入参数的引用
    
    f := func(i int) int {
       fmt.Printf("%p\n", &demo) 
       demo += i
       return demo    
    }
    return f
}

func main(){
    t := add(5)
    fmt.Println(t(1), t(2))  //输出打印的值为:6  8
}

add()函数返回的是一个匿名函数,该匿名函数引用了外部传入的参数demo,从而匿名函数和demo构成了一个整体。

闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

13. defer

defer其实是一种延时机制,功能上等同于Java的finally语句,会在函数执行完后,最后去执行。

func main(){
    res := sum(20, 30)
    fmt.Println("res=", res)
}

func sum(n1 int, n2 int) int {
    
    //当函数执行到defer时,会暂不执行
    defer fmt.Println("n1=", n1)
    defer fmt.Println("n2=", n2)
    
    res := n1 + n2
    defer fmt.Println("ans res=", n1)
    return res
}

注意:碰到defer的语句的时候,会将defer后面的语句压入到独立的栈中(像defer栈),然后在函数执行完后,再从defer栈中按照先入后出的方式执行。所以会先打印n2,再打印n1。

在上面的例子的基础上,再加入代码。

func main(){
    res := sum(20, 30)
    fmt.Println("res=", res)
}

func sum(n1 int, n2 int) int {
    
    //当函数执行到defer时,会暂不执行
    defer fmt.Println("n1=", n1)
    defer fmt.Println("n2=", n2)
    //新增代码
    n1++
    n2++
    
    res := n1 + n2
    defer fmt.Println("ans res=", n1)
    return res
}

注意:这时也是先输出n2的值,再输出n1,但是n2=30,n1=20。因为函数是先执行了defer语句,已经压栈了,再值增加也不会产生影响。

14. 函数参数传递方式

有两种传递方式:

  • 值传递
  • 引用传递

值类型参数默认就是值传递,引用类型参数默认的是引用传递。

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

15. 切片创建方法对比

方法一:定义一个切片,然后让切片去引用一个已经创建好的数组。

func main(){
    //创建一个数组
    var arr [5]int = [...]int {1,2,3,4,5}
    //创建一个切片
    var sli = arr[1:3]
    fmt.Println("arr=", arr)
    fmt.Println("sli=", sli)
    fmt.Println("sli len=", len(sli))
    fmt.Println("sli=", cap(sli))
}

方法二:通过make的方式来创建切片

func main(){
    //创建一个切片
    var sli []float64 = make([]float64, 5, 10)
    sli[1] = 20
    sli[2] = 30
    fmt.Println("sli=", sli)
    fmt.Println("sli len=", len(sli))
    fmt.Println("sli=", cap(sli))
}

方法一和方法二的区别:
方法一是直接引用数组,数组原先就存在,程序员是可见的。
方法二是通过make来创建的,make也会创建一个数组,但是由切片在底层进行维护,程序员是不可见的。

16. 切片(slice)

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。
在这里插入图片描述

17. 切片的拷贝

func main(){
    var a[]int = []int {1,2,3,4,5}
    var sli = make([]int, 1)
    fmt.Println(sli)   // [0]
    copy(sli, a)
    fmt.Println(sli)  // [1]
}

上述代码表示
当需要拷贝的切片(sli)长度小于被拷贝切片(a)的长度,都能够拷贝成功。

18. 切片的引用传递底层分析

代码1

func main(){
    var slice []int
    var arr [5]int =[...]int {1,2,3,4,5}
    slice = arr[:]
    var slice2 = slice 
    slice2[0] = 10
    
    fmt.Println("slice2=", slice2)   // [10,2,3,4,5]
    fmt.Println("slice=", slice)   // [10,2,3,4,5]
    fmt.Println("arr=", arr)   // [10,2,3,4,5]
}

代码2

func test(slice []int){
    slice[0] = 100 // 修改了slice[0]的值,会改变实参
}

func main(){
    var slice = []int {1,2,3,4,5}
    fmt.Println("slice=", slice)   // [1,2,3,4,5]
    test(slice)
    fmt.Println("slice=", slice)   // [100,2,3,4,5]
}

两段代码可以看到切片是引用类型,在进行传递的时候,必须遵守引用传递机制(即传递的是地址)。

19. map的遍历

golang的map默认是无序的,也不是按照添加的顺序存放的,所以每次遍历,得到的输出结果可能不一样。

使用方法:先将key进行排序,然后根据key值遍历输出。


不断在学习go中,这些为学习中个人觉得需要记录下来的知识点,后续会不断地更新。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是哈猿啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值