GO语言的进阶之路-面向过程式编程

时间:2022-03-21 16:22:31

                  GO语言的进阶之路-面向过程式编程

                                                作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

  

  我们在用Golang写一个小程序的时候,未免会在多个地方调用同一块代码,这个时候如何优化你的代码呢?这就是本片文章要学习的内容,即函数。当然这篇文章不仅仅会介绍函数的知识,还会介绍一些系统的标准输入和标准输出的知识点。

一.函数;

  有可能你学习过其他语言的函数,那么在学习Golang这门语言的时候,你先忘记之前学习的函数如何定义的吧。还记得《倚天屠龙记》中的男主张无忌吗?有一集再演太极拳创始人张三丰在教给张无忌武功的时候,对他说:“无忌,忘记之前学习的功夫(九阳神功)”,结果张无忌看了一遍张三丰打的太极立马就会了。其实,我们在学习任何一门语言的时候都应该这样,要有一种空杯心态。这样,你学习起来既方便又顺手。当然,当你熟练掌握这门语言的函数的用法时,你在拿他和其他语言对比语法上的不同也是未尝不可的。好了,废话,不多说,让我们开始上菜吧。

1.多返回值;

  我们在写程序的时候,有的数据时需要一个函数去处理,最后拿到我们想要的结果,那么这个结果如何通过函数拿回来呢?没错,就是用return函数拿到最终的值。当然,这个return可以拿到多个值,包括函数哟!下面让我们一起定义一下这个函数吧。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func swap(x,y string) (string,string) { //第一个括号是定义两个变量的类型为字符串;第二个括号定义是定义返回值的两个变量的数据类型。
return y,x
} func main() {
a,b := swap("hello","world")
fmt.Println(a,b)
} #以上代码输入结果如下:
world hello

2.命令(指定)返回值;

  在定义函数的时候,我们就可以指定返回值,当输入return的时候默认就会返回我们之前定义好的值。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func split(sum int) (x,y int) { //指定返回类型,x,y类型为int.也就是说在调用该函数的时候如要传入一个int类型的参数,会返回用户2个int类型的数字。
x = sum / 10
y = sum % 10
return
} func main() {
fmt.Println(split(101))
} #以上代码执行结果如下:
10 1

  要注意的是return表示退出当前函数,不会终止程序,终止程序需要用到os模块,感兴趣的小伙伴,可以看看下面的代码。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"fmt"
"os"
) func print() {
fmt.Println("hello")
return //退出当前函数
fmt.Println("")
} func main() {
_,err := os.Open("yinzhengjie.txt") //打开一个文件,直接写错误内容
if err != nil { //如果打开文件失败会执行一下的代码。
fmt.Println(err)
os.Exit(200) //退出整个程序,需要在该函数里面输入一个int类型,表示程序结束时的状态码,如果是0表示程序正常结束。
}
print()
} #以上代码输出结果如下:
hello

3.可变参数;

  你定义一个函数的时候,是定义用户必须传一个参数好呢?还是定义用户必须传两个参数好呢?还是定义一个用户相传几个就传几个,而且不管你传几个值都有对应的解决方案?那么这个时候你就会想到了可变参数,学习过Python 的小伙伴应该知道*args和**kwargs吧,都是用户自定义传惨的个数。我建议大家定义传参个数的时候就用这种方法,扩展性更高点嘛,话句话说,容错率也回高点嘛,哈哈。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func sum(args ...int) int { //表示将"..."的所有int参数传给args,最终返回给用户的是一个INT的数据类型。
n := 0
for i := 0;i < len(args) ; i++ {
n += args[i]
}
return n
} func main() {
fmt.Println(sum(100,200,300,500)) //第一种调用姿势
s := []int{1,2,3}
fmt.Println(sum(s...)) //第二种调用姿势
} #以上代码输出结果如下:
1100
6

4.递归函数;

  说简单直白一点递归函数就是自己调用自己,但是你需要一定一个结束条件。那么有的小伙伴会问了,为什么要指定结束条件呢?请你动脑子想一想递归函数的工作原理,每次调用递归函数的时候都会在系统内存中重新开辟出一块内存,如果你一直调用它不去给它一个终止点的话,那就呵呵了,及时你电脑再牛逼,资源也会被分分钟耗尽。现在知道递归函数的可怕之处了吧,其实你想想数学的“线段”和“射线”的概念应该就明白为什么要定义结束条件了吧。其实递归函数在我们生活中经常用,有的高手在爬虫上就用到递归函数,我这里就不举例子,接下来我会举个简单靠谱的案例给大家分享。

A.用递归函数实现斐波拉契数列;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func fib(n int) int {
if n == 1 || n == 2 {
return 1
}
return fib(n-1) + fib(n-2) //指定结束条件
} func main() {
fmt.Println(fib(7)) //第7个数字所对应的值。
} #以上代码执行结果如下:
13

B.通项公式案例;

  已知a(n)=2*a(n-1)+ n -1,且a(1)=2,求a(10)。

  通过这个案例,我们可以看出递归函数其内部工作机制应该是一种迭代方式。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" /*
解题分析:
递归函数可以无限次调用,直到拖垮你的电脑内存!内存溢出之时,递归函数终止之日。
a(1) = 2
a(2) = 2 * a(1) + 1 =5
a(3) = 2* a(2) + 2 =12
a(4) = 2*a(3) + 3 = 27
a(5) = 2*a(4) + 4 =58
a(n)=2*a(n-1)+ n -1
*/ func num(n int) int {
fmt.Println("enter n",n)
if n<= 1 {
return 2 //递归函数必须指定结束条件
}
m := 2*num(n-1) + n -1
fmt.Println("return from a(n-1):",n)
return m
} func main() {
fmt.Println(num(10))
} #以上代码输出结果如下:
enter n 10
enter n 9
enter n 8
enter n 7
enter n 6
enter n 5
enter n 4
enter n 3
enter n 2
enter n 1
return from a(n-1): 2
return from a(n-1): 3
return from a(n-1): 4
return from a(n-1): 5
return from a(n-1): 6
return from a(n-1): 7
return from a(n-1): 8
return from a(n-1): 9
return from a(n-1): 10
2037

5.函数类型;

  一说到string大家都会想到字符串,一说到int大家又会想到整数型;一说到byte大家又会想到字节,这种数据类型还有很多,比如rune,uint,等等,感兴趣的去官网看看,但是这都不是我要说的重点,重点是我要说函数也是一种数据类型,在定义一个函数的时候,我们可以在这个函数中返回另外一个函数的内存地址或是执行结果;甚至我们还可以定义一个变量,声明其就是一个函数类型。比如,我们可以定义一个字典,用每个key都对应一个函数。在生产环境中,也是开发工程师常用的手段。还记得我们上次写的计算器的脚本吗?不如看看这种方式来实现计算器的功能:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"strconv"
"os"
"fmt"
) func add(m,n int) int {
return m + n
} func sub(m,n int) int {
return m - n
} func mul(m,n int) int {
return m * n
} func exec(m,n int)int {
return m / n
} func rem(m,n int) int {
return m % n
} func main() {
funcmap := map[string]func(int, int)int{ //定义一个字典,字典的VALUE是一个函数。
"+" : add,
"-" : sub,
"*" : mul,
"/" : exec,
}
m,_ := strconv.Atoi(os.Args[1])
n,_ := strconv.Atoi(os.Args[3]) f := funcmap[os.Args[2]]
if f != nil {
fmt.Println(f(m,n))
}
}

6.匿名函数;

  匿名函数最明显的优点就是没有函数名,因为匿名函数被调用的次数很少,可能执行一次就以后就不会被调用,这个时候我们就可以用匿名函数来定义一段代码帮我们实现自己想要实现的功能。匿名函数可以是在全局变量上定义,也可以是在函数内部出现匿名函数,下面就来看看匿名函数的酸甜苦辣吧。

A:匿名函数的定义和调用;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"strings"
"fmt"
) /*匿名函数最明显的区别就是函数名。*/ var f = func (x,y int) int { //定义一个匿名函数
//return
s:=x + y
return s
} func toupper(s string) string {
return strings.Map(func(r rune) rune { //类似python中的嵌套函数.即在函数里在定义一个新的函数。
return r - 32
},s)
} func main() {
fmt.Println(toupper("hello"))
fmt.Println(f(10,20))
} #以上代码执行结果如下:
HELLO
30

B:匿名函数的应用;

  我们知道Golang不仅仅没有集合这么一说,也没有迭代器这么一说,不过话说回来,用Golang定义一个迭代器很方便,也不是很麻烦,下面就跟我一起看个案例吧:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"errors"
"fmt"
) func iter(s []int) func() (int, error) { //定义一个函数,传的参数是一个切片,其返回值是个匿名函数。
var i = 0
return func() (int, error) { //定义这个匿名函数的内容。定义返回两个参数,一个数int,另外一个是错误信息。
if i >= len(s) {
return 0,errors.New("end") //定义程序报错时返回的字符串。
}
n := s[i]
i += 1
return n,nil //返回切片下标所对应的值
}
} func main() {
end_num :=10 //定义需要生成数字的个数。
for i:=1;i < end_num ;i++ {
f := iter([]int{i}) n,err := f() //用n和err接受我们自定义的两个参数。
if err != nil{
break
}
fmt.Println(n) }
//当然大家也可以用以下的这种方式调用。
//f := iter([]int{1,2,3,4}) //往自定义的装饰器传入了4个参数
//fmt.Println(f) //打印迭代器的内存地址
//for {
// n,err := f()
// if err !=nil { //如果有错误信息就终端循环。
// break
// }
// fmt.Println(n) //如果么有错误,就打印正常的结构。
//}
} #以上代码执行结果如下:
1
2
3
4
5
6
7
8
9

C.用匿名函数对字符串进行排序;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"sort"
"fmt"
) type student struct { //定义一个机构体;
id int
name string
} func main() {
num := []int{2,4,1,5,3,9,7,6} //定义一个切片;
sort.Slice(num, func(i, j int) bool { //利用sort包给字符串排序
return num[i] > num[j]
})
fmt.Println(num) str := []student{}
str = append(str,student{
name:"aaaa",
id:2,
}) str = append(str,student{
name:"cccc",
id:1,
}) str = append(str,student{
name:"bbbb",
id:6,
}) sort.Slice(str, func(i, j int) bool {
return str[i].name < str[j].name //按照字母排序
})
fmt.Println(str) sort.Slice(str, func(i, j int) bool {
return str[i].id < str[j].id //按照数字排序
})
fmt.Println(str) } #以上代码执行结果如下:
[9 7 6 5 4 3 2 1]
[{2 aaaa} {6 bbbb} {1 cccc}]
[{1 cccc} {2 aaaa} {6 bbbb}]

7.闭包函数;

  我们知道return可以返回一个字符串或是一个整数型也是可以返回一个函数,我们利用它可以返回函数的特性,可以在调用函数的时候,得到返回的函数,再去执行返回的函数,最终得到我们想要的结果,要注意的是这个返回的函数的是可以获取到定义它的函数的环境变量。拥有这种定义方式和调用方法的函数,我们称之为比包函数。其实比包函数的应用是很广的,下面我们来举几个例子大家就清楚了:

A:利用闭包函数求和;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func addn(n int) func(int) int { //定义一个函数,并且返回值应该是个函数。
return func(m int) int {
return m + n //该函数只有m参数,但是它可以获取到他的上级作用域的一个参数n,这样就能返回两个参数了。
}
} func main() {
f := addn(3) //调用函数
fmt.Println(f(20))
fmt.Println(f(100))
} #以上代码执行结果如下:
23
103

B.闭包函数一起踩的坑;

  上面只是一个闭包函数的用法距离,其实刚刚接触的闭包函数的小伙伴,可能会遇到一些坑,我有两段代码给大家分享一下。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func main() {
var flist []func()
for i :=0;i < 3 ; i++ {
i := i //给i变量重新赋值,
flist = append(flist, func() {
fmt.Println(i)
//fmt.Println(&i)
})
}
for _,f := range flist{
f()
}
} //坑代码,最后输出会有3个3,而不是0,1,2
//package main
//
//import "fmt"
//
//func main() {
// var flist []func()
// for i :=0;i < 3 ; i++ {
// flist = append(flist, func() {
// fmt.Println(i)
// })
// }
// for _,f := range flist{
// f()
// }
//} #以上代码输出结果如下:
0
1
2

二.错误处理;

  我们在定义一个程序的时候难免会出现错误的代码,出现错误的时候会抛出各种不同类型的异常,那么我们遇到错误时,应该如何处理呢?是重视程序,还是重启服务呢?这些都是我们要思考的问题,在我们学习异常处理之前,我们需要先了解一些小技巧。且听我娓娓道来。

1.定义一个defer;

A.defer会在函数结束之时才会触发,也就是说:当这个函数执行完毕之时,才会触发defer功能。要注意的是它不能放在return之后,我们通常会将它放在首行。当然也要轮情况而定,会在我的举例子关于defer的应用就会看到,有时候defer并非放在函数的第一行的哟!

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" func print() {
defer func() {
fmt.Println("程序退出时,我才会被执行哟!")
}() //在函数结束之时才会执行,也可以说在return之前执行。
fmt.Println("hello")
} func main() {
print()
} #以上代码输出结果如下:
hello
程序退出时,我才会被执行哟!

B.defer的应用;

  其实defer可以和Python中的with相提并论了,当然大家知道with可以自动帮我们关闭文件,但不仅仅局限于自动关闭文件,还有很多高级的用法。当然defer也可以帮我们关闭文件,举个例子:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"log"
) func main() {
f,err := os.Open("a.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close() //处理忘记关闭文件的应用。
}

2.error输出类型;

  我们一般在读取文件会用一个参数来接受,有的小伙伴喜欢直接打印这个err,有的小伙伴喜欢在其前面加上日志输出,甚至有的小伙伴根部就不打印错误信息,而是输出别的信息,具体如何操作呢?跟我一起来看看这三种方式处理error的输出吧。

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"io/ioutil"
"time"
"fmt"
"log"
) func read(f *os.File)(string,error) { //定义一个读取文件内容的函数,并返回给用户2个参数。
buf,err := ioutil.ReadAll(f) //读取文件所有的内容,适合去读小文件,较大的文件不建议这么写。
if err != nil {
return "",err
}
return string(buf),nil //返回用户读取到的内容和一个空参数
} func main() {
f,err := os.Open("a.txt")
if err != nil {
//log.Fatal(err) //会抛出错误,优势是可以打印出当前程序执行时的时间并停止程序运行。
fmt.Printf("文件不存在,现在的时间是:%v\n",time.Now())
}
defer f.Close()
var content string
var err1 error
retries := 3
for i := 1; i <= retries; i++ {
content,err1 = read(f) //用2个参数接受我们自定义的函数。此处我忽略了接受错误的参数,直接用下划线丢弃该参数。
if err1 != nil {
//fmt.Println(err) //会抛出错误:invalid argument
log.Fatal(err1) //会抛出具体的时间和报错信息并终止程序运行:2017/07/04 09:54:32 invalid argument
}
time.Sleep(time.Second << uint(i)) //time.Second单位是1秒,time.Minute单位是1分钟,其基数为2,指数为uint(i)
fmt.Printf("程序需要休息:2的%v次方秒\n",uint(i))
fmt.Printf("现在的时间是:%v\n",time.Now())
fmt.Printf("读取到文件的内容是:[%v]\n",content) //由于文件是被一次性读取的,所以第一次循环就会把整个文件都读取完,所以第二次循环开始是读不到内容的。
} fmt.Println("程序运行结束")
} #以上代码输出结果如下:
程序需要休息:2的1次方秒
现在的时间是:2017-07-04 10:27:22.5472426 +0800 CST
读取到文件的内容是:[{"ID":1,"Name":"bingan"}
{"ID":2,"Name":"yinzhengjie"}]
程序需要休息:2的2次方秒
现在的时间是:2017-07-04 10:27:26.562352 +0800 CST
读取到文件的内容是:[]
程序需要休息:2的3次方秒
现在的时间是:2017-07-04 10:27:34.5626745 +0800 CST
读取到文件的内容是:[]
程序运行结束

3.EOF错误类型;

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"io"
"log"
"fmt"
) func read(f *os.File)(string,error) {
var total []byte
buf := make([]byte,1024) //定义一个切片,定义其容量是1024。
for {
n,err := f.Read(buf) //实际读取的字节数,用n表示
if err == io.EOF { //EOF表示已经读取到文件结尾了,
break
//fmt.Println(err) //到文件结尾了就会抛出异常,并终止程序:如:2017/07/04 11:17:28 read error:%vEOF
}
if err != nil {
return "",err
}
total = append(total,buf[:n]...) //取出实际读取到的内容!用n取下标的形式将实际内容锁定
}
return string(total),nil
} func main() {
f,err := os.Open("yinzhengjie.txt")
if err != nil {
log.Printf("打开文件失败,报错内容是::%v",err)
}
s,err := read(f)
if err != nil {
log.Printf("读取文件失败,报错内容是::%v",err)
}
fmt.Println(s) } #以上代码执行结果如下:
2017/07/04 11:26:45 打开文件失败,报错内容是::open yinzhengjie.txt: The system cannot find the file specified. 2017/07/04 11:26:45 读取文件失败,报错内容是::invalid argument

4.panic和recover;

  panic和recover两个都是一场处理的手段,前者是的错误的严重级别会更高些,recover是可以将接受的报错信息,进行处理打印出来,而panic需要主动触发,一般不建议用pannic来处理你的程序,可能会导致程序的性能出现问题!其实大家从执行的结果就可以看出来2者的严重性了,recover是不会让程序结束的,但是pannic一旦抛出来就会让整个程序崩溃!我们可以一起来看一个案例:

 /*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import "fmt" /*
panic在Linux系统是有panic报错的,在golng程序报错也会出现panic。可以手动触发,它是最高报错级别,一旦他出现可以让程序挂掉。
recover必须在panic出现之前执行,其可以终止当前函数,不会退出程序。
*/ func print() {
defer func() {
err := recover() //用recover()函数将错误捕捉到
fmt.Printf("要知道recover的报错内容是 :>>%v\n",err)
}() //在该函数结束之时才会执行这段代码。
fmt.Println("我是正常的代码")
var p *int
//q := 20
//p = &q
fmt.Println(*p) //由于p还未指定具体的值,直接打印它的值就会报错:panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println("我是执行不到的代码")
} func main() {
print() //调用函数。
fmt.Println("我是可以被执行到的代码哟!")
panic("anic发飙了,说不想执行下去了!!") //主动抛出 panic!其下面的代码就不会被执行。当然如果你不手动挂掉程序的话,下面的代码也会报错:index out of range,不推荐使用,可能会导致性能问题!
var i = 3
var slice [3]int
fmt.Println(slice[i])
} #以上代码执行结果如下:
panic: anic发飙了,说不想执行下去了!!
我是正常的代码 goroutine 1 [running]:
要知道recover的报错内容是 :>>runtime error: invalid memory address or nil pointer dereference
main.main()
我是可以被执行到的代码哟!
D:/Golang环境/Golang Program/Golang lesson/Day5/18.panic和recover.go:26 +0xe9
exit status 2

三.话是调用系统命令;

  在工作中难免会用到系统的一些数据,通过调用系统命令很方便帮我们处理很多事情,比如我想要调用系统的网卡信息还如何处理呢?

1.调用系统命令;

 [root@yinzhengjie tmp]# more  cmd.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"log"
"fmt"
"os/exec"
) func main() {
cmd := exec.Command("ifconfig","-a") //需要输入的调用的系统命令.
out,err := cmd.CombinedOutput() //在系统调执行该命令,把标准输出和错误输出都交给out。当然此时的它还是bytes类型. if err != nil {
log.Fatal(err)
}
fmt.Println(string(out)) //将bytes转换成string,这个时候大家就要注意了,中文字符的问题,这里我暂时先不说解决方案,下次给大家分享。
} [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run cmd.go
eth0 Link encap:Ethernet HWaddr 00:0C:29:52:11:50
inet addr:172.16.3.211 Bcast:172.16.3.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe52:1150/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:659860 errors:0 dropped:0 overruns:0 frame:0
TX packets:117414 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:71833756 (68.5 MiB) TX bytes:23422543 (22.3 MiB) lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:172294 errors:0 dropped:0 overruns:0 frame:0
TX packets:172294 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:17823856 (16.9 MiB) TX bytes:17823856 (16.9 MiB) [root@yinzhengjie tmp]#

2.交互式调用系统命令;

 [root@yinzhengjie tmp]# more cmd.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"bufio"
"os"
"fmt"
"strings"
"log"
"time"
"os/exec"
) var (
s string
n string
line string
)
func main() {
f := bufio.NewReader(os.Stdin) //读取输入的内容
for {
fmt.Print("请输入一些字符串>")
line,_ = f.ReadString('\n') //定义一行输入的内容分隔符。
if len(line) == 1 {
continue //如果用户输入的是一个空行就让用户继续输入。
}
line = strings.Replace(line,"\n"," ",-1)
//fmt.Printf("您输入的是:%s\n",line)
fmt.Sscan(line,&s,&n) //将s和n的值传给line,如果不传值的话就
if s == "stop" {
break
}
cmd := exec.Command(s,n) //定义需要执行的命令。
out,_ := cmd.StdoutPipe() //定义一个管道。
if err := cmd.Start();err != nil{
log.Fatal(err)
}
f := bufio.NewReader(out)
for {
line,err := f.ReadString('\n') //将数据按照行处理
if err != nil {
break
}
fmt.Println(line)
}
time.Sleep(time.Second)
cmd.Wait() //如果不定义这一行就会产生一个僵尸进程,可以通过该参数来获取当前程序的终止状态,可以告知程序员当前程序是如何终止的。 }
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run cmd.go
请输入一些字符串>ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 00:0c:29:52:11:50 brd ff:ff:ff:ff:ff:ff inet 172.16.3.211/24 brd 172.16.3.255 scope global eth0 inet6 fe80::20c:29ff:fe52:1150/64 scope link valid_lft forever preferred_lft forever 请输入一些字符串>ls -l
total 12 -rw-r--r-- 1 root root 1352 Jul 4 15:23 cmd.go drwx------ 2 root root 4096 Jul 4 17:51 go-build332249839 drwx------ 3 root root 4096 Jul 4 17:51 go-build842764162 请输入一些字符串>
请输入一些字符串>

四.标准输入和输出;

  每个程序都有一个标准输入,标准错误输出和标准正确输出。其实管道就是将上一个结果的执行结果输出传递给另外一个程序的标准输入,然后通过另外一个程序标准输出给用户,如果这个程序处理的结果还是不满意,可以继续将处理的结果通过这个程序(第二个程序)的输出接口传递下一个程序的编程标准输入接口。已达到最终实现的目的。其实这种要调用多个命令实现最终的效果的方式我们称之为管道。

1.标准输入和输出;

  操作系统在运行一个程序的时候,这个程序提供三个接口,一个是标准输入,一个是标准正确输出,一个是标准错误输出。想要了解他们的工作机制可以自行百度,我这里就不cp百度的内容了。好了,只要你知道程序有3个接口。那么看下段代码你就很轻松了。

  有的小伙伴也许不太明白为什么我定义了2个文件又一个有内容有一个没有内容呢?这个你得学习一些linux的知识了,在linux操作系统一切皆文件。

 [root@yinzhengjie tmp]# more cmd.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"log"
"os/exec"
) func main() {
f1,err := os.OpenFile("/dev/null",os.O_WRONLY,0755) //打开一个文件,这个文件是linux上的黑洞文件,也就是说你把内容存在这个文件里面就会被丢弃!
f,err := os.Create("ls.out") //这里我是创建一个文件。一般不会报错,但是要看你执行代码的用户是否有创建文件的权限哟!
if err != nil {
log.Fatal(err)
}
defer f.Close() //这个不用说了吧,当函数结束后自动关闭文件。
defer f1.Close()
cmd := exec.Command("ifconfig","-a") //这个是我自定义让当前OS执行的命令!
cmd.Stdout = f //"Stdout"表示将标准正确输出传给f, 扩展:"Stdin"表示将标准输入传给f,"Stderr"表示将错误传给f。
cmd.Stderr = f1 //表示将错误输出丢弃掉!
cmd.Start()
cmd.Wait() //cmd.Start和cmd.Wait命令的可以换成cmd.Run因为没他们在这个的实现效果上是等效的。
}
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run cmd.go
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# cat ls.out
eth0 Link encap:Ethernet HWaddr 00:0C:29:52:11:50
inet addr:172.16.3.211 Bcast:172.16.3.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe52:1150/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:753463 errors:0 dropped:0 overruns:0 frame:0
TX packets:139618 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:81575544 (77.7 MiB) TX bytes:27870976 (26.5 MiB) lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:205236 errors:0 dropped:0 overruns:0 frame:0
TX packets:205236 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:21236303 (20.2 MiB) TX bytes:21236303 (20.2 MiB) [root@yinzhengjie tmp]#

2.标准输入和输出的应用;

  既然我们已经知道了标准输入和输出已经调用系统命令的方法,那么我们就可以简单的写一个伪shell,将用户输出的值传给程序,程序在自己调用系统去执行该命令,将结果通过处理再返回给用户等等。比如下段代码就是一个伪shell,他模拟的root权限,但是压根就没有得到root的权限。不信你可以试试,不过这种方法的确可以帮我们干很多事情的,希望对大家有所启发吧。

 [yinzhengjie@yinzhengjie tmp]$ more cmd.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"fmt"
"bufio"
"strings"
"os/exec"
) func main() {
host,_ := os.Hostname() //拿到系统的主机名
prompt := fmt.Sprintf("[yinzhengjie@%v]#",host) //格式化命令行,自定义内容
r := bufio.NewScanner(os.Stdin) //把用户输入传给f,定义一个按行读取的对象。
for {
fmt.Print(prompt)
if !r.Scan(){ //"Scan"表示读取一行的内容存起来!,如果读取到结尾(EOF)就结束程序了。
break
}
line := r.Text() //"Text"表示将"Scan"存取的内容拿出来传给line。
if len(line) == 0 { //如果用户无输出就让用户继续输入。
continue
}
//fmt.Println(line) //如果开启这两行,执行该程序后,你输入是吗它就打印什么。
//continue
args := strings.Fields(line)
cmd := exec.Command(args[0],args[1:]...)
cmd.Stdin = os.Stdin //调用系统的标准输入传给该程序的输入,其实就是调用系统的输入,输出接口。
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run() //cmd,Run和cmd.Start与cmd.Wait命令是可以拿来直接赋值给一个变量。
if err != nil {
fmt.Println(err)
}
}
}
[yinzhengjie@yinzhengjie tmp]$
[yinzhengjie@yinzhengjie tmp]$
[yinzhengjie@yinzhengjie tmp]$ go run cmd.go
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#ipconfig -a
exec: "ipconfig": executable file not found in $PATH
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#ifconfig
eth0 Link encap:Ethernet HWaddr 00:0C:29:52:11:50
inet addr:172.16.3.211 Bcast:172.16.3.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe52:1150/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:757987 errors:0 dropped:0 overruns:0 frame:0
TX packets:140951 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:82022412 (78.2 MiB) TX bytes:28093494 (26.7 MiB) lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:206210 errors:0 dropped:0 overruns:0 frame:0
TX packets:206210 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:21337152 (20.3 MiB) TX bytes:21337152 (20.3 MiB) [yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#ls -l
total 16
-rw-r--r-- 1 root root 1359 Jul 5 10:27 cmd.go
drwx------ 2 yinzhengjie yinzhengjie 4096 Jul 5 10:42 go-build122489028
drwx------ 3 yinzhengjie yinzhengjie 4096 Jul 5 10:42 go-build713762643
-rw-r--r-- 1 root root 914 Jul 5 10:09 ls.out
[yinzhengjie@yinzhengjie]#
[yinzhengjie@yinzhengjie]#pwd
/tmp
[yinzhengjie@yinzhengjie]#

 

五.文件的读取高级读取姿势玩法;

  文件的读取方式有很多种,总有一种适合你,相信之前你用过其他的文件读取方式,今天我们要“”io.Copy()”来帮我们实现读取文件的操作,又是一种高逼格代码,哈哈!

 [root@yinzhengjie tmp]# more read.go
/*
#!/usr/bin/env gorun
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
EMAIL:y1053419035@qq.com
*/ package main import (
"os"
"log"
"io"
) func main() { /*
姿势一:裸读取,很少使用;
buf := make([]byte,1024)
n,err := f.Read(buf)
buf[:n]
*/ /*
姿势二:加上buffer的读取,很高效率;
r := bufio.NewReader(f)
r.Read(f)
*/ /*
姿势三:按行读取,按分隔符读取;
r1 := bufio.NewScanner(f)
*/ /*
姿势四:小文件一次性读取;
r2,_ := ioutil.ReadFile("a.txt")
r3,_ := ioutil.ReadAll(f)
*/
/*
姿势五:将右边的内容读到左边;
io.Copy()
*/
var f *os.File
var err error
if len(os.Args) > 1 {
f,err = os.Open(os.Args[1]) //如果有参数就将第一个位置参数传给f
if err != nil {
log.Fatal(err)
}
defer f.Close()
}else {
f = os.Stdin //如果没有参数就将系统的标准输入传给f
}
io.Copy(os.Stdout,f) //将f的参数的内容输出。 } [root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# ll
total 8
-rw-r--r-- 1 root root 914 Jul 5 10:09 ls.out
-rw-r--r-- 1 root root 1061 Jul 5 10:50 read.go
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run read.go ls.out
eth0 Link encap:Ethernet HWaddr 00:0C:29:52:11:50
inet addr:172.16.3.211 Bcast:172.16.3.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe52:1150/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:753463 errors:0 dropped:0 overruns:0 frame:0
TX packets:139618 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:81575544 (77.7 MiB) TX bytes:27870976 (26.5 MiB) lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:205236 errors:0 dropped:0 overruns:0 frame:0
TX packets:205236 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:21236303 (20.2 MiB) TX bytes:21236303 (20.2 MiB) You have new mail in /var/spool/mail/root
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]# go run read.go /yinzhengjie/golang/local/go/README.md
# The Go Programming Language Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software. ![Gopher image](doc/gopher/fiveyears.jpg) Our canonical Git repository is located at https://go.googlesource.com/go.
There is a mirror of the repository at https://github.com/golang/go. Unless otherwise noted, the Go source files are distributed under the
BSD-style license found in the LICENSE file. ### Download and Install #### Binary Distributions Official binary distributions are available at https://golang.org/dl/. After downloading a binary release, visit https://golang.org/doc/install
or load doc/install.html in your web browser for installation
instructions. #### Install From Source If a binary distribution is not available for your combination of
operating system and architecture, visit
https://golang.org/doc/install/source or load doc/install-source.html
in your web browser for source installation instructions. ### Contributing Go is the work of hundreds of contributors. We appreciate your help! To contribute, please read the contribution guidelines:
https://golang.org/doc/contribute.html Note that the Go project does not use GitHub pull requests, and that
we use the issue tracker for bug reports and proposals only. See
https://golang.org/wiki/Questions for a list of places to ask
questions about the Go language.
[root@yinzhengjie tmp]#
[root@yinzhengjie tmp]#