effectiveGO(03)--函数

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 构造函数与复合字面
  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
}
  1. 复合字面
    以上 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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值