JetBrains的IDE出的这个新UI挺好看的
01_Golang开发_函数、工程管理
函数
定义格式
自定义函数
无参无返回值
package main
import "fmt"
func Test() { //无参无返回值函数定义
fmt.Println("this is a test func")
}
func main() {
Test() //无参无返回值函数调用
}
有参无返回值
普通参数列表
func Test1(v1 int, v2 int) { //方式 1
fmt.Println("v1 = %d, v2 = %d\n", v1, v2)
}
func Test2(v1, v2 int) { //方式 2, v1, v2 都是 int 类型
fmt.Println("v1 = %d, v2 = %d\n", v1, v2)
}
func main() {
//函数调用
Test1(10, 20)
Test2(18, 20)
}
不定参数列表
不定参数类型
- 不定参数是指函数传入的参数个数为不定数量。
- 为了做到这点,首先需要将函数定义为接受 不定参数类型
(...type)
// 不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受
// 不定参数类型:
// 形如...type 格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Test3(args ...int) {
for _, n := range args {
fmt.Println(n)
}
}
func main() {
//函数调用
Test3()
Test3(1)
Test3(1, 9, 9)
}
不定参数的传递
// 不定参数的传递
func MyFunc01(args ...int) {
fmt.Println("MyFunc01")
for _, n := range args { //遍历参数列表
fmt.Println(n)
}
}
func MyFunc02(args ...int) {
fmt.Println("MyFunc02")
for _, n := range args { //遍历参数列表
fmt.Println(n)
}
}
func Test4(args ...int) {
MyFunc01(args...) //按原样传递, Test()的参数原封不动传递给 MyFunc01
MyFunc02(args[1:]...) //Test()参数列表中,第 1 个参数及以后的参数传递给 MyFunc02
}
func main() {
Test4(1, 2, 3) //函数调用
}
无参有返回值
- 有返回值的函数,必须有明确的终止语句,否则会引发编译错误。
一个返回值
- 最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。
// 一个返回值
func Test5() int { //方式 1
return 250
}
// 官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生 成的文档可读性差
func Test6() (value int) { //方式 2, 给返回值命名
value = 250
return value
}
func Test7() (value int) { //方式 3, 给返回值命名
value = 250
return
}
func main() {
v1 := Test5() //函数调用
v2 := Test6() //函数调用
v3 := Test7() //函数调用
fmt.Printf("v1 = %d, v2 = %d, v3 = %d\n", v1, v2, v3)
}
多个返回值
// 多个返回值
// 方式 1
func Test9() (int, string) {
return 260, "hello"
}
// 方式 2, 给返回值命名
func Test10() (a int, str string) {
a = 260
str = "holy"
return
}
func main() {
v1, v2 := Test9() //函数调用
_, v3 := Test10() //函数调用, 第一个返回值丢弃
v4, _ := Test10() //函数调用, 第二个返回值丢弃
fmt.Printf("v1 = %d, v2 = %s, v3 = %s, v4 = %d\n", v1, v2, v3, v4)
}
有参有返回值
例:求2个数的最小值和最大值
// 有参有返回值
// 求 2 个数的最小值和最大值
func MinAndMax(num1 int, num2 int) (min int, max int) {
if num1 > num2 {
min = num2
max = num1
} else {
max = num2
min = num1
}
return
}
func main() {
min, max := MinAndMax(33, 22)
fmt.Printf("min = %d, max = %d\n", min, max) //min = 22, max = 33
}
递归函数
- 递归指函数可以直接或间接的调用自身。
- 递归函数通常有相同的结构:一个跳出条件和一个递归体。
- 所谓跳出条件就是根据传入的参数判断是否需要停止递归,而递归体则是函数自身所做的一些处理。
例:计算1+2+3……+100
package main
import "fmt"
// 通过循环实现 1+2+3……+100
func Test01() int {
i := 1
sum := 0
for i = 1; i <= 100; i++ {
sum += i
}
return sum
}
// 通过递归实现 1+2+3……+100
func Test02(num int) int {
if num == 1 {
return 1
}
return num + Test02(num-1) //函数调用本身
}
// 通过递归实现 1+2+3……+100
func Test03(num int) int {
if num == 100 {
return 100
}
return num + Test03(num+1) //函数调用本身
}
func main() {
fmt.Println(Test01()) //5050
fmt.Println(Test02(100)) //5050
fmt.Println(Test03(1)) //5050
}
函数类型
- 在 Go 语言中,函数也是一种数据类型,我们可以通过 type 来定义它
- 它的类型就是所有拥有相同的参数,相同的返回值的一种类型。
package main
import "fmt"
type FuncType func(int, int) int //声明一个函数类型, func 后面没有函数名
// 函数中有一个参数类型为函数类型:f FuncType
func Calc(a, b int, f FuncType) (result int) {
result = f(a, b) //通过调用 f()实现任务
return
}
func Add(a, b int) int {
return a + b
}
func Minus(a, b int) int {
return a - b
}
func main() {
//函数调用,第三个参数为函数名字,此函数的参数,返回值必须和 FuncType 类型一致
result := Calc(1, 1, Add)
fmt.Println(result) //2
var f FuncType = Minus
fmt.Println("result = ", f(10, 2)) //result = 8
}
匿名函数与闭包
- 所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。
- 这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。
- 它不关心这捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
- 在 Go 语言里,所有的匿名函数(Go 语言规范中称之为函数字面量)都是闭包。
- 匿名函数是指不需要定义函数名的一种函数实现方式
package main
import "fmt"
func main() {
i := 11
str := "mike"
f1 := func() { //匿名函数,无参无返回值
//引用到函数外的变量
fmt.Printf("方式 1:i = %d, str = %s\n", i, str)
}
f1() //函数调用
//-------------------------
//方式 1 的另一种方式
type FuncType func() //声明函数类型, 无参无返回值
var f2 FuncType = f1
f2() //函数调用
//-------------------------
//方式 2
var f3 FuncType = func() {
fmt.Printf("方式 2:i = %d, str = %s\n", i, str)
}
f3() //函数调用
//-------------------------
//方式 3 ,直接调用此匿名函数
func() {
fmt.Printf("方式 3:i = %d, str = %s\n", i, str)
}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
//-------------------------
//方式 4, 匿名函数,有参有返回值
v := func(a, b int) (result int) {
result = a + b
return
}(1, 1) //别忘了后面的(1, 1), (1, 1)的作用是,此处直接调用此匿名函数, 并传参
fmt.Println("v = ", v)
}
闭包捕获外部变量特点
- 闭包以引用方式捕获外部变量
- 内部改了,外部也同样修改
//闭包捕获外部变量特点
func() {
i = 100
str = "go"
//内部:i = 100, str = go
fmt.Printf("内部:i = %d, str = %s\n", i, str)
}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
//外部:i = 100, str = go
fmt.Printf("外部:i = %d, str = %s\n", i, str)
函数返回值为匿名函数
- 函数 squares 返回另一个类型为 func() int 的函数。对 squares 的一次调用会生成一个局部变量 x 并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使 x 的值加 1,再返回 x 的平方。
- 第二次调用 squares 时,会生成第二个 x 变量,并返回一个新的匿名函数。新匿名 函数操作的是第二个 x 变量。
- 变量的生命周期不由它的作用域决定:squares 返回后,变量 x 仍 然隐式的存在于 f 中。
// squares 返回一个匿名函数,func() int
// 该匿名函数每次被调用时都会返回下一个数的平方
func squares() func() int {
var x int
return func() int {
x++
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
延迟调用defer
defer作用
- 关键字
defer
⽤于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。 - 注意,defer 语句只能出现在函数或方法的内部
- defer 语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。
- 通过 defer 机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。
- 释放资源的 defer 应该直接跟在请求资源的语句后。
package main
import "fmt"
func main() {
fmt.Println("hr")
defer fmt.Println("dd") //main 结束前调用
//运行结果:
//hr
//dd
}
多个defer执行顺序
- 如果一个函数中有多个 defer 语句,它们会以
LIFO(后进先出)
的顺序执行。 - 哪怕函数或某个延迟调用发生错误,这些调用依旧会被执⾏。
func test(x int) {
fmt.Println(100 / x) //x 为 0 时,产生异常
}
func main() {
defer fmt.Println("hr")
defer fmt.Println("dd") //main 结束前调用
defer test(0)
defer fmt.Println("end")
//运行结果:
// end
// dd
// hr
//panic: runtime error: integer divide by zero
}
defer和匿名函数结合使用
// defer和匿名函数结合使用
func main() {
a, b := 10, 20
defer func(x int) { // a 以值传递方式传给 x
fmt.Println("defer:", x, b) // b 闭包引用
}(a)
a += 100
b += 1000
fmt.Println(a, b)
//运行结果:
//110 1020
//defer: 10 1020
}
获取命令行参数
package main
import (
"fmt"
"os"
)
func main() {
args := os.Args //获取用户输入的所有参数
//如果用户没有输入,或参数个数不够,则调用该函数提示用户
if args == nil || len(args) < 2 {
fmt.Println("err: xxx ip port")
return
}
ip := args[1] //获取输入的第一个参数
port := args[2] //获取输入的第二个参数
fmt.Printf("ip = %s, port = %s\n", ip, port)
}
作用域
- 作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。
局部变量
- 在函数体内声明的变量、参数和返回值变量就是局部变量,它们的作用域只在函数体内
package main
import "fmt"
func test0(a, b int) {
var c int
a, b, c = 1, 2, 3
fmt.Println(a, b, c)
}
func main() {
//a, b, c = 1, 2, 3 //err, a, b, c 不属于此作用域
{
var i int
i = 10
fmt.Println("i=", i)
}
//i = 20 //err, i 不属于此作用域
if a := 3; a == 3 {
fmt.Println("a=", a)
}
//a = 4 //err,a 只能 if 内部使用
}
全局变量
- 在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。
// 全局变量
var a int //全局变量的声明
func test00() {
fmt.Println("test00=", a)
}
func main() {
a = 10
fmt.Println("a=", a)
test00()
//a= 10
//test00= 10
}
不同作用域同名变量
- 在不同作用域可以声明同名的变量
- 其访问原则为:在同一个作用域内,就近原则访问最近的变量;如果此作用域没有此变量声明,则访问全局变量,如果全局变量也没有,则报错。
// 不同作用域同名变量
var a int //全局变量的声明
func test01(a float32) {
fmt.Printf("a type = %T\n", a) //a type = float32
}
func main() {
fmt.Printf("a type = %T\n", a) //a type = int,说明使用全局变量的 a
var a uint8
{
var a float64
fmt.Printf("a type = %T\n", a) //a type = float64
}
fmt.Printf("a type = %T\n", a) //a type = uint8
test01(3.14)
test02()
}
func test02() {
fmt.Printf("a type = %T\n", a) //a type = int,说明使用全局变量的 a
}
工程管理
- 在工程中不会简单到只有一个源代码文件,且源文件之间会有相互的依赖关系。如果这样一个文件一个文件逐步编译,那不亚于一场灾难。
- 早期 Go 语言使用 makefile 作为临时方案,到了 Go 1 发布时引入了强大无比的 Go 命令行工 具。
- Go 命令行工具的革命性之处在于彻底消除了工程文件的概念,完全用目录结构和包名来推导工程结构和构建顺序。
- 针对只有一个源文件的情况讨论工程管理看起来会比较多余,因为这可以直接用
go run
和go build
搞定。
工作区
- Go 代码必须放在工作区中。
- 工作区其实就是一个对应于特定工程的目录,它应包含 3 个子目录:src 目录、pkg 目录和 bin 目录。
- 目录 src 用于包含所有的源代码,是 Go 命令行工具一个强制的规则,而 pkg 和 bin 则无需手动创建。
- 如果必要 Go 命令行工具在构建过程中会自动创建这些目录。
- 需要特别注意的是,只有当环境变量
GOPATH
中只包含一个工作区的目录路径时,go install
命令才会把命令源码安装到当前工作区的 bin 目录下。 - 若环境变量 GOPATH 中包含多个工 作区的目录路径,像这样执行
go install
命令就会失效,此时必须设置环境变量 GOBIN。
GOPATH设置
- 为了能够构建这个工程,需要先把所需工程的根目录加入到环境变量
GOPATH
中。 - 否则, 即使处于同一工作目录(工作区),代码之间也无法通过绝对代码包路径完成调用。
- 在实际开发环境中,工作目录往往有多个,这些工作目录的目录路径都需要添加至
GOPATH
。 - 当有多个目录时,请注意分隔符,多个目录的时候 Windows 是分号,Linux 系统是冒号,当有多个 GOPATH 时,默认会将 go get 的内容放在第一个目录下。
包
- 所有 Go 语言的程序都会组织成若干组文件,每组文件被称为一个包。
- 这样每个包的代码都可以作为很小的复用单元,被其他项目引用。
- 一个包的源代码保存在一个或多个以
.go
为文件后缀名的源文件中,通常一个包所在目录路径的后缀是包的导入路径。
自定义包
- 对于一个较大的应用程序,我们应该将它的功能性分隔成逻辑的单元,分别在不同的包里实现。
- 我们创建的的自定义包最好放在
GOPATH 的 src 目录下
(或者 GOPATH src 的某个子 目录)。 - 在 Go 语言中,代码包中的源码文件名可以是任意的。
- 但是,这些任意名称的源码文件都必须以包声明语句作为文件中的第一行,每个包都对应一个独立的名字空间
package calc
main包
- 在 Go 语言里,命名为 main 的包具有特殊的含义。
- Go 语言的编译程序会试图把这种名字的包编译为二进制可执行文件。
- 所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包,一个可执行程序有且仅有一个 main 包。
- 当编译器发现某个包的名字为 main 时,它一定也会发现名为
main()
的函数,否则不会创 建可执行文件。 - main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。
- 程序编译时,会使用声明 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
init 函数和 main 函数
main.go
// // main.go
package main
import (
"fmt"
"test"
)
func main() {
fmt.Println("main.go main() is called")
test.Test()
}
test.go
// test.go
package test
import "fmt"
func init() {
fmt.Println("test.go init() is called")
}
func Test() {
fmt.Println("test.go Test() is called")
}
导入包
- 导入包需要使用关键字 import,它会告诉编译器你想引用该位置的包内的代码。
- 包的路径可以是相对路径,也可以是绝对路径。
//方法 1
import "calc"
import "fmt"
//方法 2
import (
"calc"
"fmt"
)
- 标准库中的包会在安装 Go 的位置找到。
- Go 开发者创建的包会在 GOPATH 环境变量指定的目录里查找。
- GOPATH 指定的这些目录就是开发者的个人工作空间。
- 如果编译器查遍 GOPATH 也没有找到要导入的包,那么在试图对程序执行 run 或者 build 的时候就会出错。
点操作
import (
//这个点操作的含义是这个包导入之后在你调用这个包的函数时,可以省略前缀的包名
. "fmt"
)
func main() {
Println("hello go")
}
别名操作
- 在导⼊时,可指定包成员访问⽅式,⽐如对包重命名,以避免同名冲突
import (
io "fmt" //fmt 改为为 io
)
func main() {
io.Println("hello go") //通过 io 别名调用
}
_操作(忽略此包)
- 有时,用户可能需要导入一个包,但是不需要引用这个包的标识符。
- 在这种情况,可以使用
空白标识符_
来重命名这个导入 _
操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的 init 函数。
import (
_ "fmt"
)
GOPATH设置
go env
查看GOPATH路径
测试案例
main.go
package main
import (
"calc"
"fmt"
)
func main() {
a := calc.Add(1, 2)
fmt.Println("a = ", a)
}
calc.go
package calc
func Add(a, b int) int { //加
return a + b
}
func Minus(a, b int) int { //减
return a - b
}
func Multiply(a, b int) int { //乘
return a * b
}
func Divide(a, b int) int { //除
return a / b
}
go install
- 自动生成bin或pkg目录,需要使用
go install
命令(了解)
- 除了要配置GOPATH环境变量,还要配置GOBIN环境变量。
Comments | NOTHING