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

你离开我真会死。 提交于 2020-02-16 01:44:42

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)
    }
}()

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

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!