Golang学习系列(3)-面向过程的函数编程

本文是学习Golang语言的系列文章之一,主要描述go语言中的流程控制及函数操作,以及进行实践的相关代码

前言

Go可以用于写纯过程式程序,也可用于写面向对象程序,还可以写出将两者结合的程序。Go语言的过程式编程是面向对象编程的基础,所以本文主要讨论下过程式编程中涉及的一些知识。


流程控制

Go语言的流程控制分为三大类:条件判断、循环控制和无条件跳转。

条件判断

条件判断又称分支语,包括三类,即if、switch和select。

if

格式:

if optionalStatment1;booleanExpression1 {
    block1;
} else if optionalStatement2;booleanExpression2{
    block2;
} else {
    block3;
}

从上述格式可以看到,go中if语句的用法与java中有区别,它可以在条件判断语句里面声明一个变量,判断语句可以不需要括号。这里需要主要作用域的问题。

swtich

Go语言中有两种类型的switch语句,表达式开关和类型开关,表达式开关在C、C++及Java中经常见到,但类型开关属于Go的专有。Go的swtich不会自上向下贯穿,即不必再每个case末尾添加一个break语句,注意啊,这里就比java等语言显得高明了许多,当然在需要的时候,可以通过显式地调用fallthrough语句来这样做。

(1)表达式开关

格式:

switch optionalStatement;optionalExpression {
case expressionList1:block1
...
case expressionListN:blockN
default:blockD
}

代码示例:

func BoundedInt(minimum,value,maximum int) {
    switch{
    case value < minimum:
        return minimum
    case value >maximum:
        return maximum
    }
    return value
}

经典用法;
switch Suffix(file) {
case ".gz":
    return GzipFileList(file)
case ".tar",".tar.gz",".tgz":
    return TarFileList(file)
case ".zip":
    return ZipFileList(file)
}
这里介绍了goto的用法,但goto在程序中使用时需要多加注意。
package gotofallthrough
import "fmt"
func TestGoFallthrough() {
    i := 0
Here: //以冒号结束作为标签
    fmt.Println(i)
    i++
    if i < 5 {
        goto Here
    } else {
        fmt.Println("goto is over")
    }
    switch i {
    case 4:
        fmt.Println("The integer is 4")
    case 5:
        fmt.Println("The integer is 5")
        fallthrough
    case 6:
        fmt.Println("The integer is 6")
    default:
        fmt.Println("The default tag")
    }
}

(2)类型开关

格式:

switch optionalStatement;typeSwitchGuard {
case typeList1:block1
...
case typeList2:blockN
default:blockD
}

代码示例(这里还包括了goto的用法):

func classifier(items...interface{}){
    for i,x := range items{
        switch x.(type){
        case bool:
            fmt.Printf("param #%d is a bool\n",i)
        case float64:
            fmt.Printf("param #%d is a float64\n",i)
        case int,int8,int16,int32,int64:
            fmt.Printf("param #%d is an int \n",i)
        case uint,uint8,uint16,uint32,uint64:
            fmt.Printf("param #%d is an unsigned int \n",i)
        case nil:
            fmt.Printf("param #%d is nil \n",i)
        case string:
            fmt.Printf("param #%d is string \n",i)
        default:
            fmt.Printf("param #%d's type is unknow\n",i)
        }
        
    }
}

select

格式:select {
    case sendOrReceive1: block1
    ...
    case sendorReceive2: block2
    default:blockD
}

一个select 语句的逻辑结果为:一个没有default语句的select语句会阻塞,只有当至少有一个通信(接收或者发送)到达时才完成阻塞。一个包含default语句的select语句是非阻塞的,并且会立即执行。select 属于并发编程的范畴,这块具体示例代码,将在并发编程中进行详细描述。


for 循环

Go中使用两种类型的for语句进行循环,一种是无格式的for语句,另一种是for … range语句。

for循环是Go中最强大的控制逻辑。

以下是for语法示例(参见《Go语言程序设计》)
for { //无限循环
    block
}

for booleanExpression { //while循环
    block
}

for optionalPreStatement;booleanExpress;optionalPostStatement { //分号在可选的前置或后置声明存在时使用
    block
}

for index,char := range aString{ //一个字符一个字符地迭代一个字符串
    block
}

for index := range aString { //一个字符一个字符地迭代一个字符串
    block //char ,size := utf8.DecodeRunInString(aString[index])
}

for index,item := range anArrayOrSlice { //数组或者切片迭代
    block
}

for index := range anArrayOrSlice { // 数组或者切片迭代
    block //item := anArrayOrSlice[index]
}

for key, value := range aMap { //映射迭代 
    block
}

for key := range aMap { //映射迭代
    block //value:=aMap[key]
}

for item := range aChannel { //通道迭代
    block
}

这里有时候可能使用平行赋值,例如:i,j = i+1,j-1

循环中有两个关键操作,一个是break,另一个是continue;break是跳出当前循环,即当前循环结束,而continue是跳过本次循环,循环可能还未结束;break和continue还可以跟着标号,用来跳到多重循环中的外层循环。

由于Go支持多值返回,对于”声明而未被调用”的变量,编译器会报错,这种情况使用_来丢弃不需要的返回值。

for循环中,有时还会用到goto语句进行跳转。


函数

函数是面向过程编程的根本,是Go语言的核心设计,用func来定义。

格式:func funcName(input1 type1,input2 type2)(output1 type1,output2 type2){
    //逻辑处理代码
    //返回多个值
    return value1,value2
}

如果只有一个返回值且不声明返回值变量,那么可以省略”包括返回值” 的括号,还可以直接写返回的类型;如果没有返回值,就直接省略最后的返回信息。Go语言比C语言更先进的特性之一是函数能够返回多个值。

变参

Go语言函数支持变参。接收变参的函数有不定数量的参数。

格式:func myfunc(arg … type){}

arg … type表示Go语言接受不定数量的参数,且具有相同类型type。

传值与传指针

Go语言中的函数值传递与Java语言中一样,均是指当传递一个参数值到被调用函数里面时,实际上传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中实参不会发生变化。

指针传递是指将变量存放在内存中的地址&x传入函数,若函数中改变了该地址的值,则意味着原传入函数的变量值也被改变了。

package valueaddress

import (
    "fmt"
)

func TestTransferValue() {
    x := 3
    fmt.Println("The initValue of x is :%d", x)
    x1 := addvalue(x)
    fmt.Println("The x+1= is :%d", x1)
    fmt.Println("The initValue of x is :%d", x)
}

func TestTransferAdderess() {
    x := 3
    fmt.Println("The initValue of x is :%d", x)
    x1 := addaddress(&x)
    fmt.Println("The x+1= is :%d", x1)
    fmt.Println("The initValue of x is :%d", x)
}

func addvalue(a int) int {
    a = a + 1
    return a
}

func addaddress(a *int) int {
    *a = *a + 1
    return *a
}

defer

Go语言中有延迟(defer)语句,可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。这种设计特别在因正常或因不知原因的异常出现关闭资源使用。相关示例代码可参见《Go Web 编程》。

示例一
package defertest

import (
    "fmt"
)

func TestDefer() {
    for i := 0; i < 5; i++ {
        fmt.Printf("The order is :%d\n", i)
    }
    
    for j := 0; j < 5; j++ {
        defer fmt.Printf("The reverse order is :%d\n", j) //输出4,3,2,1,0
    }
}
示例二
func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

函数作为值、类型

在Go语言中函数也是一种变量,可以通过type定义它,它的类型是所有拥有相同的参数,相同的返回值。格式:

type typeName func(input1 inputType1,input2 inputType2 [,…]) (return1 returnType1 [,…])

/**
*函数测试
*作者:xialingsc
*时间:2015-3-09
*
**/

package functest

import (
    "fmt"
)

type testInt func(int) bool //声明一个函数类型

func Testfunc() {
     x := 3
     y := 4
     z := 5
     max_xy := max(x, y)
     max_yz := max(y, z)
     fmt.Printf("max(%d,%d)=%d\n", x, y, max_xy)
     fmt.Printf("max(%d,%d)=%d\n", y, z, max_yz)
     fmt.Printf("max(%d,%d)=%d\n", x, z, max(x, z))
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

//函数类型作为一个参数
func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }                                        
    }
    return result
}

func TestFuncAsPara() {
    slice := []int{1, 2, 3, 4, 5, 6, 7}
    fmt.Println("slice=", slice)
    odd := filter(slice, isOdd)
    fmt.Println("the odd element of slice is:", odd)
    even := filter(slice, isEven)
    fmt.Println("the even element of slice is:", even)
}

main函数和init函数

Go里面有两个保留函数,一个是init函数和main函数,前者能够在不同package中被调用,后者只能用于package main。这两个函数共同点是在定义时不能有任何的参数和返回值。尽管一个包里可以写任意多个init函数,但建议只写一个,便于阅读。每个init是可选的,但一个package有且只有一个main函数。

执行过程:程序的初始化和执行都起始于main包,若main包还导入了其他的包,在编译时就会依次导入,若一个包被多个同时导入,则只会被导入一次。当一个包被导入时,如果该包还导入了其他包,那么会先将其他包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数,依次类推,等所有导入的包被加载完毕,就开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数,最后执行main函数。

Panic和Recover

Go语言没有像java语言的异常机制,不能抛出异常,而是使用panic和recover机制,正常情况下,代码中都没有或者很少有panic的东西,但它的确很强大。panic是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程当函数F调用panic时,函数F的执行被中断,但是F中的延迟函数会正常执行然后F返回到调用它的地方。Recover是一个内建函数,可以让进入令人恐慌的流程中的goroutine恢复过来recover仅在延迟函数中有效,在正常的执行过程中,调用recover会返回nil,并且没有任何效果

/**
*
*作者:xialingsc
*时间:2015-3-09
*
**/

package panicrecover

import (
    "fmt"
    "os"
)

var user = os.Getenv("USER")

func init() {
    fmt.Println("This is panicrecover init()")
    if user == "" {
        fmt.Println("The user is nil")
        panic("no value for  $USER")
    } else {
        fmt.Println("The user is %s", user)
    }
}

func throwsPanic(f func()) (b bool) {
    defer func() {
        fmt.Println("This is throwsPanic func")
        if x := recover(); x != nil {
            b = true
        }
}()

f() //执行函数f,如果f中出现了panic,那么就恢复回来
    return
}

import

格式: import “path/package”

import ( “path1/package1” “path2/package2” … )

相对路径:import "./model" //当前文件同一目录的model目录,不建议这么用

绝对路径:import "shorturl/model" //加载gopath/src/shorturl/model模块

点操作示例 :import (
            . "fmt"
         ) 
//.的含义表示这个包导入之后,在调用这个包的函数时,你可以省略前缀的包名
fmt.Println("ceshi") => Println("ceshi")

别名操作示例:import (
            f "fmt"
          )
//fmt.Println("ceshi")  => f.Println("ceshi")

_操作示例 : import (
            _ "ray/tech/goceshi"
         )
// _是引入goceshi该包,不直接使用包里面的函数,而是调研了该包里的init函数。