2.2 函数
2.2.1 参数
golang 传参都是值传递,会将形参拷贝一份给实参。
2.2.2 多值返回以及返回结果可命名
首先看一个例子:
// 本函数获取 byte 数组中第 i 个位置的值
func nextInt([] byte, i int)(int, int){
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i]) - '0'
}
return x, i
}
// 可以看出,当返回值命名之后不同显示的 return i, j,直接 return 即可。
// 返回值和形参具有相同的作用域
func TestFunc003()(i, j int){
i , j = 1, 2
return
}
这个例子很明显显示出 go 在函数上的特点:函数允许多返回值,且返回值允许命名。被命名的返回值和形参一样可以在函数内直接使用。当返回值被命名时可以直接 return 返回,而无需 return 接变量名。
2.3 defer 延迟执行函数
- 不论什么时候,被 defer 关键字修饰的函数都会在函数 return 之后马上执行(类似于 java try-catch 块中的 final) 。
- defer FuncName(funcVar Type) 要延迟执行的函数如果有参数,那么参数的值会在 defer 语句的位置马上求值,而不是最后调用 defer 语句延迟执行的函数时求值,具体见如下代码中的 TestFunc004()
- 如果有多个 defer,那么将按照后进先出的顺序执行。
defer 语句常用于关闭 I/O 流,能够避免打开文件后忘记关闭文件。
// 1. defer 语句中函数的参数值会立即确定而不是在执行 defer 语句时执行
// 2. 多个 defer 存在时,后进先出
// 3. defer 在 return 之后马上执行
func TestFunc004() (int, error) {
for i := 0; i < 5; i++ {
defer fmt.Printf("第 %d 个执行 defer 开始执行\n", i)
}
return fmt.Println("return 语句正在运行")
}
/*
【输出】
return 语句正在运行
第 4 个执行 defer 开始执行
第 3 个执行 defer 开始执行
第 2 个执行 defer 开始执行
第 1 个执行 defer 开始执行
第 0 个执行 defer 开始执行
*/
func GetStringByFile(fileName string)(result string){
fileHandle, err := os.Open(fileName)
checkErr(err) // chechErr(err) 是我们自定义的打印异常的函数
defer fileHandle.Close() // 这一句确保在本函数结束之前文件流一定会被关闭
var resultByte []byte
bufTemp := make([]byte, 100)
for {
n, err := fileHandle.Read(bufTemp[0:])
resultByte = append(resultByte, bufTemp[0:n])
if err != nil {
if err == io.EOF {
break
}
return ""
}
}
return string(result) // 在此句之后将会调用 fileHandle.Close() 释放资源
}
2.4 new 函数分配内存
new 是 golang 中用来分配内存的内建函数,和其他语言中的同名函数不同,它并不会初始化内存,只会置零内存(即将所有字段置零)。也就是说 new (User) 这条语句会为一个新的 User 对象分配一块已经被置零的内存块,并返回它的地址(即一个指针)。
2.4.1 构造函数与复合字面
- 构造函数:
new 会将所有字段置零,我们写一个构造函数做一些初始化的工作。
type User struct {
name string
age int
phone string
}
// 为 User 提供一个构造函数
func NewUser(name string, age int) (user *User) {
user = new(User)
user.name = name
user.age = age
return
}
- 复合字面
以上 NewUser 方法中创建一个新的 User 对象可以用复合字面来简化。效果相同。
func NewUser(name string, age int) (user *User) {
user = &User{name, age, ""}
return
}
- 复合字面的字段必须按顺序全部列出。但如果以 ‘字段:值’ 键值对的形式明确地标出元素,初始化字段时就可以按任何顺序出现,未给出的字段值将赋予零值。
- 若复合字面不包括任何字段,它将创建该类型的零值。表达式 new(File) 和 &File{} 是等价的
- 复合字面还可以用于创建数组、切片、字典
func TestFunc005() {
array := [2]string{"字符串数组的第一个内容", "字符串数组的第二条内容"}
slice := []string{"切片的第一个内容", "切片的第二个内容"}
mapTemp := map[int]string{1: "001", 2: "002"}
fmt.Println(array, "\n", slice, "\n", mapTemp)
}
/*
【输出】
[字符串数组的第一个内容 字符串数组的第二条内容]
[切片的第一个内容 切片的第二个内容]
map[1:001 2:002]
*/
2.5 make 分配内存
和 new() 一样,make(T, args) 也用于内存分配,但不同于 new() 可以为任意类型分配内存,make(T, args) 只用于创建切片、字典和管道,并返回一个已经初始化的值(new 返回的是分配内存后的地址)。
// 用 new() 创建一个切片
var p *[]int = new([]int) // 这句话相当于初始化了指针 p 为一
// 个 []int 类型的指针。要说明的是如果
// 不写 new([]int) 是无法声明成功的(报异常)
*p = make([]int, 10) // 为 *p 赋值。切片 p 现在指向了一个长度为 10 的数组
// 用 make 一步实现上述
q := make([]int, 10)