分享
  1. 首页
  2. 文章

go语言顺序编程

Al_xin · · 3139 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1、变量

声明:

Go语言的变量声明方式与C和C++语言有明显的不同。对于纯粹的变量声明,Go语言引入了关键字var,而类型信息放在变量名之后,实例如下:

Go Code
1
2
3
4
5
6
7
8
9
10
var v1 int
var v2 string
var v4 [10]int //数组
var v4 []int //数组切片
var v5 struct{
f int
}
var v6 *int //指针
var v7 map[string]int //key-string value-int
var v8 func(a int) int //函数
初始化:

对于声明变量时需要进行初始化的场景,var关键字可以保留,但不再是必要的元素。

JavaScript Code
1
2
3
var v1 int=10
var v2=10
v3:=10
赋值:

go语言的变量赋值与多数语言一致,但go语言中提供了C/C++程序员期盼多年的多重赋值功能,比如下面这个交换i和j变量的语句:

Go Code
1
i,j=j,i
匿名变量:

我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没有用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。

假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:

C++ Code
1
2
3
4
5
6
func GetName() (fistName,lastName,nickName string)
{
return "May","Chan","Chibi Maruko"
}
//只想获得nickName,则函数调用语句可以用如下方式编写
_,_,nickName:=GetName()


2、常量

常量定义:

C++ Code
1
2
3
4
5
6
7
8
9
const Pi float64=3.14
const zero=0.0
const(
size int64=1024
eof=-1
)
const u,v float32=0,3
const a,b,c=3,4,"foo"
const mask=1<<3
预定义常量:

Go语言预定义了这些常量:true、false和iota

iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表数字会自动增1.

从以下例子可以基本理解iota的用法:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const(
c0=iota //0
c1=iota //1
c2=iota //2
)
const(
a=1<<iota //1
b=1<<iota //2
c=1<<iota //4
)
const(
u=iota*42 //0
v float64=iota*42 //42
w=iota*42 //84
)
const x=iota //0
const y=iota //0

枚举:

枚举指一系列相关的常量,比如下面关于一个星期中每天的定义。通过上一节的例子,我们看到可以用const后跟一对圆括号方式定义一组常量,这种定义法在Go语言中通常用于定义枚举值。Go语言并不支持众多其他语言明确支持的enum关键字。

C++ Code
1
2
3
4
5
6
7
8
9
10
const(
Sunday=iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberofDays//这个常量没有导出
)

通Go语言的其他符号一样,以大写字母开头的常量在包外可见。

以上例子中numberofDays为包内私有,其他符号则可被其他包访问。

3、类型

基础类型:

布尔类型:bool

整型:int8、byte、int16、int、uint、uintptr等

浮点类型:float32、float64

复数类型:complex64、complex128

字符串:string

字符类型:rune

错误类型:error

此外,Go语言也支持以下这些复合类型:

指针(pointer)

数组(array)

切片(slice)

字典(map)

通道(chan)

结构体(struct)

接口(interface)


浮点数比较:

C++ Code
1
2
3
4
5
import "math"

func IsEqual(f1,f2,p float64) bool{
return math.Fdim(f1,f2)<p
}

常规数组声明:

C++ Code
1
2
3
4
5
6
//常规数组声明
[32]byte //长度为32的数组,每个元素为一个字节
[2*N] struct{x,y int32} //复杂类型数组
[1000]*float64 //指针数组
[3][5]int //二维数组
[2][2][2]float64 //等同[2]([2]([2]float64))

值类型:

需要特别注意的是,在Go语言中数组是一个值类型,所有的值类型变量再赋值和作为参数传递时都产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

下面是一个例子:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func modify(array [5]int){
array[0]=10
fmt.Println("the modify array is:",array)
}

func main(){
array:=[5]int{1,2,3,4,5}

modify(array)

fmt.Println("the original array is:",array)
}
输出结果如下所示:

the modify array is: [10 2 3 4 5]
the original array is: [1 2 3 4 5]

从执行结果可以看出,函数modify()内操作的那个数组跟main()中传入的数组是两个不同的实例。这个问题,可以采用数组切片功能来达成。

数组切片:

基于数组的切片,数组切片可以基于一个已存在的数组创建。例如如下:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main(){
var array [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
var Slipce []int=array[:5]

fmt.Println("the original array is:")
for _,v :=range array{
fmt.Print(v," ")
}
fmt.Println("\nthe splice array is:")
for _,v := range Slipce{
fmt.Print(v," ")
}
fmt.Println()

}
输出结果如下:

the original array is:
1 2 3 4 5 6 7 8 9 10
the splice array is:
1 2 3 4 5

也可以直接创建数组切片,具体如下所示:

C++ Code
1
2
3
4
//Go语言提供内置函数make()可以用于灵活的创建数组切片
mySlice1:=make([]int,5) //初始元素个数为5,的数组切片,元素初始值为0
mySlice2:=make([]int,5,10)
mySlice3:=[]int{1,2,3,4,5}
数组切片支持Go语言内置cap()函数和len()函数,可以看出,cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。
C++ Code
1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main(){
myslice:=make([]int,5,10)
fmt.Println("the length use cap:",cap(myslice))
fmt.Println("the length use len:",len(myslice))
}

打印结果如下:

the length use cap: 10
the length use len: 5

如果需要往上例中mySlice已包含的5个元素后面继续新增元素,可以使用append()函数。

C++ Code
1
mySlice=append(mySlice,1,2,3)
内容复制:数组切片支持Go语言的另一个内置函数copy(),用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片大一样大,就会按其中较小的那个数组切片的元素个数进行复制。
C++ Code
1
2
3
4
slice1:=[]int{1,2,3,4,5}
slice2:=[]int{5,3,4}
copy(slice2,slice1) //只会复制slice1的前三个元素到slice2中
copy(slice1,slice2) //只会复制slice2的3个元素到slice1的前3个位置


map:

map是一堆键值对的未排序集合。比如身份证号码作为唯一键来标识一个人的信息,则可以使用map进行定义。

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"

type Personinfo struct{
ID string
name string
Address string
}

func main(){
var personDB map[string] Personinfo
personDB=make(map[string] Personinfo)
personDB["12345"]=Personinfo{"12345","Jhon","Room 1"}
personDB["1"]=Personinfo{"1","Smith","Room 2"}
person,ok:=personDB["12"]
if ok{
fmt.Println("Found Person",person.name," with ID 12")
}else{
fmt.Println("Not Found the ID 12")
}
}


元素删除:

delete(myMap,"1234")

4、流程控制

if语句

C++ Code
1
2
3
4
5
6
7
8
func example(x int) int{
if x==0{
return 5
}else{
return x
}

}


选择语句:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch i{
case 0:
fmt.Println("0")
case 1:
fmt.Println("1")
case 2:
fallthrough
case 3:
fmt.Println("3")
case 4,5,6:
fmt.Println("4,5,6")
default:
fmt.Println("Default")
}
与C语言等规则相反,Go语言不需要用break来明确退出一个case

只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case

循环语句:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sum:=0
for i:=0;i<10;i++{
sum+=i
}
//无限for循环写法
sum:=0
for{
sum++
if sum > 100{
break
}
}
a:=[]int{1,2,3,4,5,6}
for i,j:=0,len(a)-1;i<j;i,j=i+1,j-1{
a[i],a[j]=a[j],a[i]
}


跳转语句:

C++ Code
1
2
3
4
5
6
7
8
9
func myfunc(){
i:=0
HERE:
fmt.Println(i)
i++
if i<10{
goto HERE
}
}

5、函数

函数构成代码执行的逻辑结构。在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。

需要注意的是,小写字母开头的函数只是本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。

任意类型的不定参数:

之前的例子中将不定参数类型约束为int,如果你希望传任意类型,可以指定类型为interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:

C++ Code
1
2
3
func Printf(format string,args ...interface{}){
//...
}
用interface{}传递任意类型数据时Go语言的惯例用法。使用interface{} 仍然是类型安全的,这和C/C++不太一样。
C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func MyPrintf(args ...interface{}){
for _,arg := range args{
switch arg.(type){
case int:
fmt.Println(arg,"is a int value")
case int64:
fmt.Println(arg,"is a int64 value")
case string:
fmt.Println(arg,"is a string value")
default:
fmt.Println(arg,"is unkown value")
}
}
}
func main(){

var v1 int=1
var v2 int64=2
var v3 string="foo"
var v4 float32=1.234
MyPrintf(v1,v2,v3,v4)
}
打印结果如下:

1 is a int value
2 is a int64 value
foo is a string value
1.234 is unkown value


匿名函数与闭包:

匿名函数是指不需要定义函数名的一种函数实现方式,JavaScript、C#和Objective-C等语言都提供了匿名函数特性,当然也包含Go语言。

1、匿名函数

在Go里面,函数可以像普通变量一样被传递或者使用,这与C语言的回调函数比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

C++ Code
1
2
3
func(a,b int,z float64) bool{
return a*b<int(z)
}
匿名函数可以直接赋值为一个变量或者直接执行
C++ Code
1
2
3
4
5
6
7
8
f:=func(x,y int) int{
return x+y

}

func(ch chan int){
ch<-ACK
}(reply_cha) //花括号后直接跟参数列表表示函数调用

2、闭包

Go的匿名函数是一个闭包

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义,要执行代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。

Go语言中的闭包同样也引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main(){
var j int=5
a:=func()(func()){
var i int=10
return func(){
fmt.Printf("i,j is %d,%d\n",i,j)

}
}()
a()
j*=2
a()
}

输出结果如下所示:

1、变量

声明:

Go语言的变量声明方式与C和C++语言有明显的不同。对于纯粹的变量声明,Go语言引入了关键字var,而类型信息放在变量名之后,实例如下:

Go Code
1
2
3
4
5
6
7
8
9
10
var v1 int
var v2 string
var v4 [10]int //数组
var v4 []int //数组切片
var v5 struct{
f int
}
var v6 *int //指针
var v7 map[string]int //key-string value-int
var v8 func(a int) int //函数
初始化:

对于声明变量时需要进行初始化的场景,var关键字可以保留,但不再是必要的元素。

JavaScript Code
1
2
3
var v1 int=10
var v2=10
v3:=10
赋值:

go语言的变量赋值与多数语言一致,但go语言中提供了C/C++程序员期盼多年的多重赋值功能,比如下面这个交换i和j变量的语句:

Go Code
1
i,j=j,i
匿名变量:

我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没有用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。

假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:

C++ Code
1
2
3
4
5
6
func GetName() (fistName,lastName,nickName string)
{
return "May","Chan","Chibi Maruko"
}
//只想获得nickName,则函数调用语句可以用如下方式编写
_,_,nickName:=GetName()


2、常量

常量定义:

C++ Code
1
2
3
4
5
6
7
8
9
const Pi float64=3.14
const zero=0.0
const(
size int64=1024
eof=-1
)
const u,v float32=0,3
const a,b,c=3,4,"foo"
const mask=1<<3
预定义常量:

Go语言预定义了这些常量:true、false和iota

iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表数字会自动增1.

从以下例子可以基本理解iota的用法:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const(
c0=iota //0
c1=iota //1
c2=iota //2
)
const(
a=1<<iota //1
b=1<<iota //2
c=1<<iota //4
)
const(
u=iota*42 //0
v float64=iota*42 //42
w=iota*42 //84
)
const x=iota //0
const y=iota //0

枚举:

枚举指一系列相关的常量,比如下面关于一个星期中每天的定义。通过上一节的例子,我们看到可以用const后跟一对圆括号方式定义一组常量,这种定义法在Go语言中通常用于定义枚举值。Go语言并不支持众多其他语言明确支持的enum关键字。

C++ Code
1
2
3
4
5
6
7
8
9
10
const(
Sunday=iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberofDays//这个常量没有导出
)

通Go语言的其他符号一样,以大写字母开头的常量在包外可见。

以上例子中numberofDays为包内私有,其他符号则可被其他包访问。

3、类型

基础类型:

布尔类型:bool

整型:int8、byte、int16、int、uint、uintptr等

浮点类型:float32、float64

复数类型:complex64、complex128

字符串:string

字符类型:rune

错误类型:error

此外,Go语言也支持以下这些复合类型:

指针(pointer)

数组(array)

切片(slice)

字典(map)

通道(chan)

结构体(struct)

接口(interface)


浮点数比较:

C++ Code
1
2
3
4
5
import "math"

func IsEqual(f1,f2,p float64) bool{
return math.Fdim(f1,f2)<p
}

常规数组声明:

C++ Code
1
2
3
4
5
6
//常规数组声明
[32]byte //长度为32的数组,每个元素为一个字节
[2*N] struct{x,y int32} //复杂类型数组
[1000]*float64 //指针数组
[3][5]int //二维数组
[2][2][2]float64 //等同[2]([2]([2]float64))

值类型:

需要特别注意的是,在Go语言中数组是一个值类型,所有的值类型变量再赋值和作为参数传递时都产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。因此,函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。

下面是一个例子:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func modify(array [5]int){
array[0]=10
fmt.Println("the modify array is:",array)
}

func main(){
array:=[5]int{1,2,3,4,5}

modify(array)

fmt.Println("the original array is:",array)
}
输出结果如下所示:

the modify array is: [10 2 3 4 5]
the original array is: [1 2 3 4 5]

从执行结果可以看出,函数modify()内操作的那个数组跟main()中传入的数组是两个不同的实例。这个问题,可以采用数组切片功能来达成。

数组切片:

基于数组的切片,数组切片可以基于一个已存在的数组创建。例如如下:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main(){
var array [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
var Slipce []int=array[:5]

fmt.Println("the original array is:")
for _,v :=range array{
fmt.Print(v," ")
}
fmt.Println("\nthe splice array is:")
for _,v := range Slipce{
fmt.Print(v," ")
}
fmt.Println()

}
输出结果如下:

the original array is:
1 2 3 4 5 6 7 8 9 10
the splice array is:
1 2 3 4 5

也可以直接创建数组切片,具体如下所示:

C++ Code
1
2
3
4
//Go语言提供内置函数make()可以用于灵活的创建数组切片
mySlice1:=make([]int,5) //初始元素个数为5,的数组切片,元素初始值为0
mySlice2:=make([]int,5,10)
mySlice3:=[]int{1,2,3,4,5}
数组切片支持Go语言内置cap()函数和len()函数,可以看出,cap()函数返回的是数组切片分配的空间大小,而len()函数返回的是数组切片中当前所存储的元素个数。
C++ Code
1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main(){
myslice:=make([]int,5,10)
fmt.Println("the length use cap:",cap(myslice))
fmt.Println("the length use len:",len(myslice))
}

打印结果如下:

the length use cap: 10
the length use len: 5

如果需要往上例中mySlice已包含的5个元素后面继续新增元素,可以使用append()函数。

C++ Code
1
mySlice=append(mySlice,1,2,3)
内容复制:数组切片支持Go语言的另一个内置函数copy(),用于将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片大一样大,就会按其中较小的那个数组切片的元素个数进行复制。
C++ Code
1
2
3
4
slice1:=[]int{1,2,3,4,5}
slice2:=[]int{5,3,4}
copy(slice2,slice1) //只会复制slice1的前三个元素到slice2中
copy(slice1,slice2) //只会复制slice2的3个元素到slice1的前3个位置


map:

map是一堆键值对的未排序集合。比如身份证号码作为唯一键来标识一个人的信息,则可以使用map进行定义。

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"

type Personinfo struct{
ID string
name string
Address string
}

func main(){
var personDB map[string] Personinfo
personDB=make(map[string] Personinfo)
personDB["12345"]=Personinfo{"12345","Jhon","Room 1"}
personDB["1"]=Personinfo{"1","Smith","Room 2"}
person,ok:=personDB["12"]
if ok{
fmt.Println("Found Person",person.name," with ID 12")
}else{
fmt.Println("Not Found the ID 12")
}
}


元素删除:

delete(myMap,"1234")

4、流程控制

if语句

C++ Code
1
2
3
4
5
6
7
8
func example(x int) int{
if x==0{
return 5
}else{
return x
}

}


选择语句:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch i{
case 0:
fmt.Println("0")
case 1:
fmt.Println("1")
case 2:
fallthrough
case 3:
fmt.Println("3")
case 4,5,6:
fmt.Println("4,5,6")
default:
fmt.Println("Default")
}
与C语言等规则相反,Go语言不需要用break来明确退出一个case

只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case

循环语句:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sum:=0
for i:=0;i<10;i++{
sum+=i
}
//无限for循环写法
sum:=0
for{
sum++
if sum > 100{
break
}
}
a:=[]int{1,2,3,4,5,6}
for i,j:=0,len(a)-1;i<j;i,j=i+1,j-1{
a[i],a[j]=a[j],a[i]
}


跳转语句:

C++ Code
1
2
3
4
5
6
7
8
9
func myfunc(){
i:=0
HERE:
fmt.Println(i)
i++
if i<10{
goto HERE
}
}

5、函数

函数构成代码执行的逻辑结构。在Go语言中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句。

需要注意的是,小写字母开头的函数只是本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。

任意类型的不定参数:

之前的例子中将不定参数类型约束为int,如果你希望传任意类型,可以指定类型为interface{}。下面是Go语言标准库中fmt.Printf()的函数原型:

C++ Code
1
2
3
func Printf(format string,args ...interface{}){
//...
}
用interface{}传递任意类型数据时Go语言的惯例用法。使用interface{} 仍然是类型安全的,这和C/C++不太一样。
C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func MyPrintf(args ...interface{}){
for _,arg := range args{
switch arg.(type){
case int:
fmt.Println(arg,"is a int value")
case int64:
fmt.Println(arg,"is a int64 value")
case string:
fmt.Println(arg,"is a string value")
default:
fmt.Println(arg,"is unkown value")
}
}
}
func main(){

var v1 int=1
var v2 int64=2
var v3 string="foo"
var v4 float32=1.234
MyPrintf(v1,v2,v3,v4)
}
打印结果如下:

1 is a int value
2 is a int64 value
foo is a string value
1.234 is unkown value


匿名函数与闭包:

匿名函数是指不需要定义函数名的一种函数实现方式,JavaScript、C#和Objective-C等语言都提供了匿名函数特性,当然也包含Go语言。

1、匿名函数

在Go里面,函数可以像普通变量一样被传递或者使用,这与C语言的回调函数比较类似。不同的是,Go语言支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

C++ Code
1
2
3
func(a,b int,z float64) bool{
return a*b<int(z)
}
匿名函数可以直接赋值为一个变量或者直接执行
C++ Code
1
2
3
4
5
6
7
8
f:=func(x,y int) int{
return x+y

}

func(ch chan int){
ch<-ACK
}(reply_cha) //花括号后直接跟参数列表表示函数调用

2、闭包

Go的匿名函数是一个闭包

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义,要执行代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。

Go语言中的闭包同样也引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main(){
var j int=5
a:=func()(func()){
var i int=10
return func(){
fmt.Printf("i,j is %d,%d\n",i,j)

}
}()
a()
j*=2
a()
}

输出结果如下所示:

i,j is 10,5
i,j is 10,10

在上面的例子中,变量a指向的闭包函数引用了局部变量i和j,i的值被隔离,在闭包外不能被修改,改变j的值以后,再次调用a,发现结果是修改过的值。

在变量a指向的闭包函数中,只有内部的匿名函数才能访问变量i,而无法通过其他途径访问到,因此保证了i的安全性。


6、错误处理

漂亮的错误处理规范是Go语言最大的亮点之一。

1、error接口

Go语言引入了一个关于错误处理的标准模式,即error接口,该接口的定义如下:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
type error interface{
Error() string
}
//当syscall.Stat()失败返回err时,将该err包装到了一个PathError对象返回:
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
}


2、defer

关键字defer是Go语言引入的一个非常有意思的特性,相信很多C++程序员都写过类似下面的代码:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
class file_closer{
File _f;
public:
file_closer(FILE f):_f(f){}
~file_closer(){ if(f) fclose(f);}
};
//在需要使用地方
void f(){
FILE f=open_file("file.txt");//打开一个文件句柄
file_closer _closer(f);
//对f句柄进行操作
}
开发者可以将需要释放的资源变量都声明在函数的开头部分,并在函数的末尾部分统一释放资源。函数需要退出时,就必须使用goto语句跳转到指定位置先完成资源清理工作,而不能调用return语句直接返回。

Go语言使用defer关键字简简单单的解决了这个问题,比如以下的例子:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
func CopyFile(dst,src string)(v int64,err error){
srcFile,err:=os.Open(src)
if err!=nil{
return
}
defer srcFile.Close()
dstFile,err:=os.Create(dstName)
if err!=nil{
return
}
defer dstFile.Close()
return io.Copy(dstFile,srcFile)
}

即使其中的Copy()函数抛出异常,Go仍然会保证dstFile和srcFile会被正常关闭。

3、panic()和recover()

Go 语言引入了两个内置函数panic()和recover()以报告和处理运行时错误和程序中错误场景:

C++ Code
1
2
func panic(interface{})
func recover() interface{}
当一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将报告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程。

从panic()的参数类型interface{} 我们可以得知,该函数接受任意类型的数据,比如整型、字符串、对象等。调用方法很简单,下面是几个例子:

C++ Code
1
2
3
panic(404)
panic("network broken")
panic(Error("file not exists"))
recover()函数用于终止错误处理流程。一般情况下,recover()应用在一个使用defer关键字的函数中执行以有效截取错误处理流程。如果没有在发送异常的goroutine中明确调用恢复过程,会导致该goroutine所属的进程打印异常信息后直接退出。

下面是一个例子:

C++ Code
1
2
3
4
5
6
defer func(){
if r:=recover;r!=nil{
log.Printf("Runtime error caught:%v",r)
}
}()
foo()
无论foo()中是否触发了错误处理流程,该匿名defer函数都将在函数退出时得到执行,假如foo()中触发了错误处理流程,recover()函数执行将使得该错误处理过程终止。如果错误处理流程被触发时,程序给panic函数的参数不为nil,则该函数还会打印详细的错误信息。


7 、完整实例

现在我们用本章学到的知识来实现一个完整的程序。我们准备开发一个排序算法的比较程序,从命令行指定输入的数据文件和输出的数据文件,并指定对应的排序算法。该程序的用法如下所示:

USAGE:sorter -i <in> -o <out> -a <qsort|bubblesort>

一个具体的执行过程如下:

$./sorter -i in.dat -o out.dat -a qsort

The sorting process costs 10us to complete.

当然,如果输入不合法,应该给出对应的提示,接下来我们一步步实现这个程序。

1、程序结构

我们将该函数分成两类:主程序和排序算法函数。每个排序算法都包装成一个静态库,虽然现在看起来似乎有些多此一举,但这只是为了顺便演示包之间的依赖方法。

假设我们的程序根目录为~/goyard/sorter,因此需要再环境变量GOPATH中添加这个路径。根目录结构如下:

<sorter>

|--<src>

|--<sorter>

|--sorter.go

|--<algorithms>

|--<qsort>

|--qsort.go

|--qsort_test.go

|--<bublesort>

|--bublesort.go

|--bublesort_test.go

|--<pkg

|--<bin>

其中sorter.go是主程序,qsort.go用于实现快速排序,bubblesort.go用于实现冒泡排序。

下面我们先定义一下排序算法函数的函数原型:

func QuickSort(int [] int)[] int

func BubbleSort(int []int)[]int


2、主程序

我们的主程序需要做的工作包含一下几点:

1、获取并解析命令行输入

2、从对应文件中读取输入数据

3、调用对应的排序函数

4、将排序结果输出到对应的文件中

5、打印排序所花费的时间的信息

接下来我们一步步地编写程序。

1、命令行参数

Go语言标准库提供了用于快速解析命令行参数的flag包。对于本示例的参数需求,我们可以利用flag包进行实现,如下代码所示:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
import "flag"
var infile *string=flag.String("i","infile","File contains values for sorting")
var outfile *string=flag.String("o","outfile","File contains values sorted")
var algorithm *string=flag.String("a","algorithm","The algorithm to sort")

func main(){

flag.Parse()
if infile!=nil{
fmt.Println("the infile is ",*infile,", the outfile is ",*outfile,", the algorithm is ",*algorithm)
}

}
因为这个程序需要输入参数,所以我们不能直接用go run来跑,而是需要先编译出二进制程序。可以用go build来完成这个过程:

$ go build sorter.go
$ ./sorter -i unsorted.dat -o sorted.dat -a bubblesort
infile = unsorted.dat outfile = sorted.dat algorithm = bubblesort

可以看到,传入的各个命令行参数已经被正确读取到各个变量中。flag包使用起来非常方便,大大简化了C语言时代解析命令行参数的过程。

2、读取输入文件

我们需要先从一个文件中把包含内容读取到数组中,将该数组排好序后再写回到另一个文件中,因此还需要学习如何在Go语言中操作文件。

我们先设计输入文件的格式。输入文件是一个纯文本文件,每一行是一个需要被排序的数字。下面是一个示例unsorted.dat 文件内容:

123

3064

3

64

490

然后需要逐行从这个文件中读取内容,并解析为int类型的数据,再添加到一个int类型的数组切片中。接下来我们实现这部分功能,如下所示:

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main
import "fmt"
import "flag"
import "bufio"
import "io"
import "os"
import "strconv"

var infile *string=flag.String("i","unsorted.dat","File contains values for sorting")
var outfile *string=flag.String("o","sorted.dat","File contains values sorted")
var algorithm *string=flag.String("a","qsort","The algorithm to sort")

func readValues(infile string)(values []int,err error){
file,err:=os.Open(infile)
if err!=nil{
fmt.Println("Failed to open the file:",infile)
return
}
defer file.Close()
br:=bufio.NewReader(file)
values=make([]int,0)
for{
line,isPrefix,err1:=br.ReadLine()
if err1!=nil{
if err1!=io.EOF{
err=err1
}
break
}
if isPrefix{
fmt.Println("A too long line is unexpected.")
return
}
str:=string(line)
value,err1:=strconv.Atoi(str)
if err1!=nil{
err=err1
return
}
values=append(values,value)
}
return
}

func main(){

flag.Parse()
if infile!=nil{
fmt.Println("the infile is ",*infile,", the outfile is ",*outfile,", the algorithm is ",*algorithm)
}
values,err:=readValues(*infile)
if err==nil{
fmt.Println("ReadValue:",values)
}else{
fmt.Println("Read Error:",err)
}

}

在实现readValues()函数的过程中,我们用到了os,io,bufio和strconv等Go语言标准包,用于读写和字符串处理。熟练掌握这些包的基本用法,将会大幅度提高使用Go语言的工作效率。

我们还示范了数组切片的使用,并使用defer关键字以确保关闭文件句柄。

3、写到输出文件

在数据处理结束后,我们需要将排序结果输出到另一个文本文件。这个过程比较简单,因此这里只列出writeValues()函数的实现,读者可以自行对照Go语言标准库以熟悉相关包的用法。








有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:Al_xin

查看原文:go语言顺序编程

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
3139 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏