• Go语言学习笔记(1)——顺序编程


    Go语言学习笔记这一堆主要是《Go语言编程》(人民邮电出版社)的读书笔记。中间会穿插一些零碎的点,比如源码学习之类的。大概就是这样吧。

    1. 顺序编程

    1.1 变量

    变量的声明:

    var 变量名 类型

    var v1 int

    也可以把若干变量的声明用大括号括起来

    var {
        v1 int
        v2 string
    }

    变量初始化:

    变量的初始化可以用如下的方法:

    var v1 int = 10
    var v2 = 10
    v3 := 10

    这三种方法的效果大体上是一样的。需要注意的有:第三种方法不能用于声明全局变量;以及:=赋值符号不能用于已声名过的变量名。

    变量的赋值:

    赋值这里唯一特别的是,Go语言支持多重赋值,比如交换i和j的值:

    i, j = j, i

    匿名变量:

    Go语言有一个特性,就是在引入的包和变量在没有使用的时候,会在编译阶段报错。所以对于不需要的变量,可以使用匿名变量进行处理。

    两个例子,第一个是多重返回函数的返回值的处理:

    func GetName() (firstName, lastName, nickName string) {
        return "May", "Chan", "Chibi Maruko"
    }
    
    _, _, nickName := GetName()

    如果只需要函数的部分返回值的时候,就可以利用匿名变量。

    第二个是for循环:

    var a int[] = {5, 4, 3, 2, 1}
    for _, value := range(a) {
        balabala
    }

    当我们不需要range返回的部分结果的时候,就可以利用匿名变量。

    1.2 常量

    字面常量:大概只有一点要说,就是不需要额外做特别的声明。比如对于long类型的12,不存在12l这种写法。

    常量定义:通过const关键字进行定义。

    const Pi float64 = 3.1415926

    需要注意的大概有:声明的时候可以不限定类型;常量定义的右值也可以是编译期运算的常量表达式。

    const Zero = 0.0
    const mask = 1 << 3
    const Home = os.GetEnv("HOME")

    这里第三句会导致错误。因为右值只有在运行期才能知道结果。

    预定义常量:true,false,iota。

    这里true和false没有什么说的。

    iota可以被认为是一个可被编译器修改的常量,该常量在每一个const关键字出现的时候被重置为0。在下一个const出现之前,每出现一次iota,其所代表的数字自动加1。

    Golang不支持enum关键字的枚举类型,通常把const和iota结合起来表示枚举,例如:

    const {
        Sunday = iota
        Monday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
        numberOfDays
    }

    这样就将星期与整数对应了起来。

    注意,numberOfDays并没有被导出。

    1.3 类型

    Golang支持以下类型:

    基本类型:布尔型,整型,浮点型,复数型,字符串,字符型,错误型。

    复合类型:指针,数组,切片,字典,通道,结构体,接口。

    基本类型中,需要注意的问题如下;

    int和int32在Golang中被认为是不同的两个类型。所以二者不能互相赋值(编译期不会自动做类型转换),需要进行强制的类型转换。

    自动推导的浮点数类型是float64。

    复数类型是其他大部分语言不支持的。例子如下:

    var value1 complex64
    
    value1 = 3.2 + 12i
    value2 := 3.2 + 12i
    value3 := complex(3.2, 12)
    
    x := real(value1)
    y := imag(value1)

    字符串可以用下表的方式获取其内容,但是字符串在初始化之后,其内容就不能进行修改了。

    字符串的遍历,这还跟len()函数作用在字符串上的返回值有关,例子如下:

    str := "Hello, 世界"
    n := len(str)     //n = 13
    for i:=0, i<n, i++ {
        fmt.Println(i, str[i])
    }
    
    for i, ch := range str {
        fmt.Println(i, ch)
    }      //该段代码输出了9行结果

    另外需要注意的,range有两个返回值,第一个是下标,第二个是下标对应的值。

    数组在声明了以后,就不能再修改其长度。

    数组切片的实质可以理解成3部分:一个指向原数组的指针;数组切片中的元素个数;数组切片已分配的存储空间。

    数组切片的声明方式如下:

    //基于数组创建数组切片
    var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    var mySlice []int
    mySlice = myArray[:]    //基于所有数组元素创建切片
    mySlice = myArray[:5]    //基于数组的前5个(0~4)元素创建切片
    mySlice = myArray[5:]    //基于数组第5个开始(5~end)的元素创建切片
    
    //直接创建数组切片
    mySlice1 := make([]int, 5)    //创建一个初始长度为5的数组切片,元素初始值是0
    mySlice2 := make([]int, 5, 10)    //除以上之外,预留了10个元素的存储空间
    mySlice3 := []int{1, 2, 3, 4, 5}    //直接创建并初始化一个包含5个元素的数组切片

    append函数可以增加切片的元素。注意append函数并不是原地的。其应用例子如下:

    mySlice := make([]int, 2, 10)
    
    mySlice = append(mySlice, 1)
    mySlice = append(mySlice, 2, 3)
    
    mySlice2 := []int{4, 5}
    mySlice = append(mySlice, mySlice2...)

    注意最后的...,它将mySlice2的元素打散做为append的参数。

    也可以通过数组切片来创建切片。例如:

    oldSlice := make([]int, 5, 10)
    newSlice1 := oldSlice[:3]    //用oldSlice的前三个元素创建新的切片
    newSlice2 := oldSlice[:7]    //也可以超出原切片的数量,但是不能超过cap,超出的部分填0

    copy函数可以将一个数组切片的内容复制到另一个数组切片。例如:

    slice1 := []int{1, 2, 3, 4, 5}
    slice2 := []int{6, 7, 8}
    
    copy(slice2, slice1)    //复制slice1的前三个元素到slice2
    copy(slice1, slice2)    //复制slice2到slice1的前三个位置

    map的元素查找可以用以下方法:

    myMap := make(map[string]int, 100)    //声明一个map,cap为100
    
    value, ok := myMap["1234"]
    if ok {    //找到了
        //对value的处理
    }

    1.4 流程控制

    条件语句if的例子如下:

    if a < 5 {
        return 0
    } else {
        return 1
    }

    需要注意的有:条件语句不需要括号;花括号必须存在;以及最重要的,不允许将最终的return包含在if...else...结构中。

    选择语句switch的例子如下:

    switch i {
        case 0:
            fmt.Println(0)
        case 1:
            fmt.Println(1)
            fallthrough
        case 3:
            fmt.Println(3)
        default:
            fmt.Println("Default")
    }

    需要注意的有:除非明确的标明fallthrough,否则认为分支默认break;另外,switch后面的条件表达式也可以不设定,这时可以将switch看作是多个if...else...。

    循环语句if的例子如下:

    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
    
    j := 0
    for j<10 {
        fmt.Print(j)
        j++
    }
    
    k := 0
    for {
        fmt.Print(k)
        k++
        if k>10 {
            break
        }
    }

    总之,Golang是把for和while结合到了一起。同时,也有break和continue。break也可以选择中断哪个循环。

    goto就是跳转到label。这个很少用,先写在这里。

    1.5 函数

    函数的定义和调用:

    package mymath
    import "errors"
    
    func Add(a int, b int) (ret int, err error) {
        if a<0 || b<0 {
            err = errors.New("Should be non-negative numbers!")
            return
        }
        return a + b, nil
    }
    
    c := mymath.Add(1, 2)

    需要注意的有:函数、类型和变量的可见性是由其首字母的大小写来确定的,大写开头的是public,小写开头的是private;函数的返回值的变量名可以先声明出来,在函数体内对变量进行处理后直接return即可;函数可以同时返回多个值。

    同时,Golang的函数也支持不定参数,例如:

    func myfunc(args ...int) {
        for _, arg := range args {
            fmt.Println(arg)
        }
    }
    
    myfunc(2, 3, 4)
    myfunc(1, 3, 7, 13)
    
    func myfunc2(args ...int) {
        myfunc(args...)
        myfunc(args[1:]...)
    }

    通过例子可以看到不定参数的函数的遍历方法和调用方法。以及作为变参函数嵌套的传参方法。我觉得最关键的是记住,...在跟类型一起出现的时候,表明的是变参类型;...在跟变量名一起出现的时候,表明是将该变量内容打散。这种记法与*和&的区别方法一致。

    如果希望传递任意类型的不定参数,可以利用interface{}。这里先举一个例子,后面会再具体描述interface{}。

    func MyPrintf(args ...interface{}) {
        for _, arg := range args {
            switch arg.(type) {
                case int:
                    fmt.Println(arg, "int")
                case string:
                    fmt.Println(arg, "string")
                default:
                    fmt.Println(arg, "unknown")
            }
        }
    }

    匿名函数简单的说就是没有函数名的函数。匿名函数可以赋值给一个变量名,也可以直接执行。例如:

    f := func(x, y int) int {
        return x + y
    }
    
    func (ch chan int) {
        ch <- ACK
    } (reply_chan)

    立即执行的匿名函数,需要把传入的参数放到函数后面的括号中。

    这里还有一个闭包的概念,目前还没太懂,待补充。

    错误处理,Golang提供了error,defer,panic和recover工具。

    error是一个接口,其定义如下:

    type error interface {
        Error() string
    }

    对于需要返回错误的函数,多数情况下定义成下面的形式,并按以下方式调用:

    func Foo(param int) (n int, err error) {
        // ...
    }
    
    n, err := Foo(0)
    if err != nil {
        //  错误处理
    } else {
        //  使用返回值n
    }

    那么,对于自定义的error类型(即实现了error接口的类型),通常定义的形式如下:

    type PathError struct {
        Op string
        Path string
        Err error
    }
    
    func (e *PathError) Error() string {
        return e.Op + " " + e.Path + ": " + e.Err.Error()
    }
    
    func Stat (name string) (fi FileInfo, err error) {
        var stat syscall.Stat_t
    
        err =syscall.Stat(name, &stat)
        if err != nil {
            return nil, &PathError{"stat", name, err}
        }
    
        return fileInfoFromStat(&stat, name), nil
    }

    defer关键字主要是用来处理代码中需要在最后完成的任务,比如关闭管道,关闭文件等。例子如下:

    func CopyFile(dst, src string) (w int64, err error) {
        srcFile, err := os.Open(src)
        if err != nil {
            return
        }
        defer srcFile.Close()
        return io.Copy(dstFile, srcFile)
    }

    另外,如果一个函数中有多个defer关键字,那么它们是以后进先出的方式(即自下而上的)执行。

    panic和recover两个函数是Golang中用来处理异常的函数。其流程大致为:当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。recover()函数用于终止错误流程。一般情况下,recover()应该在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程,会导致该goroutine所属的进程打印异常信息后直接退出。

    常见的recover()调用方法如下:

    defer func() {
        if r := recover(); r != nil {
            log.Printf("Runtime error caught: %v", r)
        }
    }()

    这样写,无论函数中是否出发了错误处理流程,该匿名函数都将在函数退出时得到执行。

    第一节大概是这样~如果有任何问题还会及时更新~~~

  • 相关阅读:
    JS 逻辑运算符&&与||的运算
    Jquery on("click") 方法绑定事件后执行多次解决办法
    java的web开发使用struts2/springMVC和spring框架理解
    HTTPClient
    eclipse中配置tomcat内存大小
    杀掉window占用端口
    Unirest
    乐观锁
    自定义标签
    xss和csrf攻击
  • 原文地址:https://www.cnblogs.com/wangzhao765/p/9201252.html
Copyright © 2020-2023  润新知