您的位置:首页 > 游戏 > 手游 > Go语言编程 学习笔记整理 第2章 顺序编程 后半部分

Go语言编程 学习笔记整理 第2章 顺序编程 后半部分

2024/12/23 9:58:48 来源:https://blog.csdn.net/weixin_41987016/article/details/140665002  浏览:    关键词:Go语言编程 学习笔记整理 第2章 顺序编程 后半部分

1.流程控制

1.1 条件语句

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

注意:在有返回值的函数中,不允许将“最终的”return语句包含在if...else...结构中, 否则会编译失败!!!

func example(x int) int { if x == 0 { return 5 } else { return x } 
}// 编译失败的原因:Go编译器无法找到终止该函数的return语句

1.2 选择语句

switch i { case 0: fmt.Printf("0") case 1: fmt.Printf("1") case 2: fallthrough case 3: fmt.Printf("3") case 4, 5, 6: fmt.Printf("4, 5, 6") default: fmt.Printf("Default") 
}
运行上面的案例,将会得到如下结果:
i = 0 时,输出 0
i = 1 时,输出 1
i = 2 时,输出 3
i = 3 时,输出 3
i = 4 时,输出 4, 5, 6
i = 5 时,输出 4, 5, 6
i = 6 时,输出 4, 5, 6
i = 其他任意值时,输出 Default

 比较有意思的是,switch后面的表达式甚至不是必需的,比如下面的例子:

switch { case 0 <= Num && Num <= 3: fmt.Printf("0-3") case 4 <= Num && Num <= 6: fmt.Printf("4-6") case 7 <= Num && Num <= 9: fmt.Printf("7-9") 
}
  • 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case
  • 可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个 if...else...的逻辑作用等同
1.3 循环语句
  • 在条件表达式中也支持多重赋值
func ForCase() {sum := 0for i := 0; i < 10; i++ {sum += i}fmt.Println(sum) // 45// 在条件表达式中也支持多重赋值,如下表示: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]}fmt.Println(a) // [6 5 4 3 2 1]
}

另外,Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更加高级的break,可以选择中断哪一个循环,如下例子:

for j := 0; j < 5; j++ { for i := 0; i < 10; i++ { if i > 5 { break JLoop } fmt.Println(i) } 
} 
JLoop:
本例中, break 语句终止的是 JLoop 标签处的外层循环。
2.函数
  • 在Go中,函数的基本组成为:关键字func、函数名、参数列表、返回值、函数体和返回语句
2.1函数定义
用一个最简单的加法函数来进行详细说明:
package mainimport ("fmt"
)
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 // 支持多重返回值
}func main() {res, err := _case.Add(1, 2)if err != nil {return}fmt.Println(res)
}
如果参数列表中若干个相邻的参数类型的相同,比如上面例子中的 a b ,则可以在参数列表中省略前面变量的类型声明,如下所示:
func Add(a, b int)(ret int, err error) { // ... 
}
如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。
如果函数只有一个返回值,也可以这么写:
func Add(a, b int) int { // ... 
}

2.2 函数调用

函数调用非常方便,只要事先导入了该函数所在的包,就可以直接按照如下所示的方式调用函数:
import "mymath"// 假设Add被放在一个叫mymath的包中// ... 
c := mymath.Add(1, 2)
先牢记这样的规则:
  • 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用
  • 这个规则也适用于类型和变量的可见性
2.3 不定参数
(1)不定参数类型
不定参数: 指函数传入的参数个数为不定数量
首先需要将函数定义为接受不定参数类型:
func myfunc(args ...int) { for _, arg := range args { fmt.Println(arg) } 
}// 这段代码的意思是,函数myfunc()接受不定数量的参数,这些参数的类型全部是int,
// 所以它可以用如下方式调用:myfunc(2, 3, 4) 
myfunc(1, 3, 7, 13)

形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数

它是一 个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。

从内部实现机理来说说,类型...type本质上是一个数组切片,也就是[]type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数

假如没有...type这样的语法糖,开发者将不得不这么写:

func myfunc2(args []int) { for _, arg := range args { fmt.Println(arg) } 
}
从函数的实现角度来看,这没有任何影响,该怎么写就怎么写。但从调用方来说,情形则完
全不同:
myfunc2([]int{1, 3, 7, 13})
们不得不加上 []int{} 来构造一个数组切片实例。但是有了 ...type这个语法糖,我们就不用自己来处理了。
(2)不定参数的传递
package mainimport ("fmt"
)func processFunc(args ...int) {for i, _ := range args {args[i] += 1}fmt.Println(args)
}func myfunc(args ...int) {// 按照原样传递processFunc(args...)// 传递片段,实际上任意的int slice都可以传进去processFunc(args[1:]...)
}func main() {myfunc(2, 3, 4)
}

(3)任意类型的不定参数

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

用interface{}传递任意类型数据是Go语言的惯例用法。使用interface{}仍然是类型安全的,和C/C++不太一样

func MyPrintf(args ...interface{}) {for _, arg := range args {switch arg.(type) {case int:fmt.Println(arg, "is an int value.")case string:fmt.Println(arg, "is a string value.")case int64:fmt.Println(arg, "is an int64 value.")default:fmt.Println(arg, "is an unknown type.")}}
}func main() {var v1 int = 1var v2 int64 = 234var v3 string = "heheda"var v4 float32 = 1.234_case.MyPrintf(v1, v2, v3, v4)
}

(4)多返回值

与C、C++和Java等开发语言的一个极大不同在于,Go语言的函数或者成员的方法可以有多个返回值,这个特性能够让我们写出比其他语言更优雅,更简洁的代码。

比如File.Read()函数就可以同时返回读取的字节数和错误信息

如果读取文件成功,则返回值中的n为读取的字节数,err为nil,否则err为具体的出错信息:

func (file *File) Read(b []byte) (n int, err Error)

同样,从上面的方法原型可以看到,我们还可以给返回值命名,就像函数的输入参数一样。

  • 返回值被命名之后,它们的值在函数开始的时候被自动初始化为空
  • 在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值

Go语言并不需要强制命名返回值,但是命名后的返回值可以让代码更清晰,可读性更强,同时也可用于文档

如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线"_"来跳过这个返回值,比如下面的代码表示调用者在读文件的时候不想关心Read()函数返回的错误码:

n, _ := f.Read(buf)

(5)匿名函数与闭包

① 匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

func NMFunc() {f := func(x, y int) int {return x + y}fmt.Println(f(1, 2)) // 输出: 3// 创建一个通道replyChan := make(chan int)// 启动一个新的 Goroutine,执行匿名函数,并向通道发送数据go func(ch chan int) {ch <- 42}(replyChan) // 花括号后直接跟参数列表,表示调用// 从通道接收数据ack := <-replyChan// 打印接收到的数据fmt.Println(ack) // 输出: 42// 关闭通道close(replyChan)
}

②闭包:Go的匿名函数是一个闭包

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

闭包的价值:在于可以作为函数对象或者匿名函数

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

closure.go

package main 
import ( "fmt" 
) 
func main() { var j int = 5 a := func()(func()) { var i int = 10 return func() { fmt.Printf("i, j: %d, %d\n", i, j) } }() a() j *= 2 a() 
}
上述例子的执行结果是:
上述例子的执行结果是:
i, j: 10, 5
i, j: 10, 10
在上面的例子中,变量a指向 的闭包函数引用了局部变量 i j i的值被隔离,在闭包外不能被修改,改变j的值以后,再次调用a,发现结果是修改过的值。
2.4 错误处理
漂亮的错误处理规范是 Go 语言最大的亮点之一
(1)error接口
Go语言引入了一个关于错误处理的标准模式,即error接口,该接口的定义:
type error interface { Error() string
}
对于大多数函数,如果要返回错误,大致上都可以定义为如下模式,将 error 作为多种返回
值中的最后一个,但这并非是强制要求:
func Foo(param int)(n int, err error) { // ... 
}
调用时的代码建议按如下方式处理错误情况:
n, err := Foo(0) 
if err != nil { // 错误处理
} else { // 使用返回值n 
}

自定义的error类型

步骤一:定义一个用于承载错误信息的类型

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() 
}

当然,这里是一个完整的例子展示了如何定义一个自定义的 PathError 类型,并在文件操作中使用这个类型在出错时进行错误处理和信息提取

package mainimport ("fmt""os"
)// PathError 自定义错误类型
type PathError struct {Op   stringPath stringErr  error
}// 实现 error 接口的 Error 方法
func (e *PathError) Error() string {return e.Op + " " + e.Path + ": " + e.Err.Error()
}// 函数用来模拟文件操作,并返回一个自定义的 PathError 错误
func simulateFileOperation() error {// 尝试获取文件信息,这里故意对一个不存在的文件操作_, err := os.Stat("a.txt")if err != nil {// 返回自定义的 PathError 错误return &PathError{Op:   "stat",Path: "a.txt",Err:  err,}}return nil
}func main() {// 调用模拟文件操作的函数err := simulateFileOperation()if err != nil {// 把错误断言为 *PathError 类型if e, ok := err.(*PathError); ok && e.Err != nil {// 获取 PathError 类型变量 e 中的其他信息并处理fmt.Printf("Operation: %s\n", e.Op)fmt.Printf("Path: %s\n", e.Path)fmt.Printf("Error: %s\n", e.Err)fmt.Printf("Full Error: %s\n", e.Error())} else {// 其他类型的错误fmt.Println(err)}} else {// 文件操作成功fmt.Println("File operation succeeded")}
}

在这个示例中:

① 定义了一个PathError类型,这个类型有Op(操作)、Path(路径)和Err(底层错误)三个字段

② 实现了PathError类型的Error方法,以满足error接口

③ 编写了一个simulateFileOperation函数,这个函数尝试获取一个不存在的文件的状态,并返回一个自定义的PathError错误

④ 在main函数中,调用simulateFileOperation函数,接收并处理这个自定义错误

这样,当你运行这个示例时,如果文件a.txt不存在,会输出:

Operation: stat
Path: a.txt
Error: stat a.txt: no such file or directory
Full Error: stat a.txt: stat a.txt: no such file or directory

这个示例展示了如何自定义错误类型并在程序中使用它来进行错误处理和信息提取

这就是Go中error类型的使用方法,与其他语言中的异常相比,Go的处理相对比较直观,简单

2.5 defer 

  • 关键字defer是Go语言引入的一个非常有意思的特性

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com