golang之defer语句

本文详细解析了Go语言中defer语句的使用方法及其特点,包括如何利用defer进行资源释放、变量捕获以及如何通过defer影响函数的返回值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


defer语句会将其对应的函数延迟执行。

defer语句

defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。延迟函数可以有参数:

  • 延迟函数的参数在defer语句出现时就已确定下来(传值的就是当前值);
  • 延迟函数执行按后进先出顺序执行;
  • 延迟函数可操作主函数的具名返回值(修改返回值);

释放资源

defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放、句柄关闭等问题。

func fileSize(filename string) int64 {
    f, err := os.Open(filename)
    if err != nil {
        return 0
    }
    // 延迟调用Close, 此时Close不会被调用
    defer f.Close()
	
    info, err := f.Stat()
    if err != nil {
        // defer机制触发, 调用Close关闭文件
        return 0
    }
    size := info.Size()
    // defer机制触发, 调用Close关闭文件
    return size
}

变量捕获

defer中的变量会被提前捕获,后续的修改不会影响到已捕获的值:

func deferTest() {
	i := 0
	defer fmt.Println("Defer:", i)
	i = 10
	fmt.Println("Fun:", i)
}

// Fun: 10
// Defer: 0  

defer语句中打印的值是修改前的值。

返回值影响

return不是原子操作, 执行过程是: 保存返回值(若有)—>执行defer( 若有) —>执行ret返回调用函数;

有具名返回值时,return可看做是以下语句的结合体:

返回值 = xxx
调用defer函数(依次逆序调用)
空的return

具名返回值

在defer中修改具名返回值时,会影响到函数的实际返回值。

func DeferShow() {
	fmt.Println("Out:", deferValueParam())
}
func deferValueParam() (ret int) {
	ret = 0

	defer func() { // 会直接修改栈中对应的返回值
		ret += 10
		fmt.Println("Defer Ret:", ret)
	}()
	ret = 5
	fmt.Println("Ret:", ret)
	return
}

// Ret: 5       
// Defer Ret: 15
// Out: 15  

非具名返回值

但是当函数为非具名返回值时,defer将无法影响返回值(因在return时,对应返回值已存入栈中):

func DeferShow() {
	fmt.Println("Out2:", deferValueParam())
}
func deferRetValue() int {
	ret := 0
	defer func() {
		ret += 10
		fmt.Println("Defer Ret:", ret)
	}()
	ret = 5
	return ret
}

// Defer Ret: 15
// Out2: 5  
<think>嗯,用户问的是在Go语言中使用“select *”获取返回值到对象的问题。首先,我需要确定用户的具体需求。可能他们是从其他数据库查询语言(比如SQL)转过来的,因为在SQL里常用“SELECT *”来选取所有字段。但Go语言中的select语句是用于处理多个通道操作的,和数据库查询无关。所以这里可能存在一些概念上的混淆。 接下来,我需要确认用户是否想了解如何将数据库查询结果映射到Go的结构体。比如使用GORM或者database/sql包时,如何将查询到的所有列(SELECT *)自动填充到结构体对象里。如果是这样的话,可能需要解释ORM库的使用方法,比如结构体标签如何对应数据库字段,或者如何手动扫描查询结果到对象。 也有可能用户误解了Go的select关键字,以为它可以用来获取通道中的数据并直接映射到结构体,这时候需要解释select在通道处理中的作用,比如多路复用的通道操作,如何通过case语句接收不同通道的数据,并将结果赋值给变量或结构体字段。 另外,用户提到的“获取返回值到对象”可能需要分情况讨论:如果是通道返回值,可能需要展示如何从通道接收数据并填充到结构体实例;如果是数据库查询,需要展示如何使用Rows.Scan()或ORM的Find方法。 还要考虑用户可能的错误使用场景,比如试图在select语句中直接转换数据类型,导致类型不匹配的问题,这时候需要提醒类型断言和正确扫描数据的方法。 最后,总结用户的问题,提供清晰的示例代码,说明不同场景下的正确做法,并指出常见的错误或注意事项,帮助用户理解Go语言中相关概念的正确应用。</think>在 Go 语言中,`select` 并不是用于数据库查询的关键字,而是用于处理多个 **channel 的 I/O 操作**(多路复用)。如果你的问题是想将数据(例如数据库查询结果)映射到对象中,可能是想了解以下两种情况: --- ### 1. 如果是 **数据库查询**(如 SQL) 当使用 `SELECT *` 查询数据库时,可以通过标准库 `database/sql` 或 ORM 框架(如 GORM)将结果映射到结构体对象中。 #### 示例代码(使用 `database/sql`): ```go type User struct { ID int Name string Age int } // 查询数据 rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() // 遍历结果并映射到对象 var user User for rows.Next() { err := rows.Scan(&user.ID, &user.Name, &user.Age) // 按字段顺序 Scan if err != nil { log.Fatal(err) } fmt.Println(user) } ``` #### 使用 ORM(如 GORM)自动映射: ```go var users []User result := db.Where("age > ?", 20).Find(&users) // SELECT * FROM users WHERE age > 20 if result.Error != nil { log.Fatal(result.Error) } ``` --- ### 2. 如果是 **channel 操作** Go 的 `select` 关键字用于监听多个 channel 的读写事件。可以通过 `case` 分支接收数据并赋值给变量或对象: #### 示例代码: ```go ch1 := make(chan int) ch2 := make(chan string) go func() { ch1 <- 42 }() go func() { ch2 <- "hello" }() var ( num int str string ) select { case num = <-ch1: // 从 ch1 接收数据并赋值给 num fmt.Println("Received number:", num) case str = <-ch2: // 从 ch2 接收数据并赋值给 str fmt.Println("Received string:", str) } ``` --- ### 常见误区 - **`SELECT *` 是 SQL 语法**:Go 的 `select` 与 SQL 无关,仅用于 channel 操作。 - **字段顺序必须匹配**:使用 `rows.Scan()` 时,字段顺序需与 SQL 查询结果一致,否则会出错。 - **类型安全**:确保接收数据的变量类型与 channel 或数据库字段类型一致。 如果需要进一步解释某个场景,可以补充说明具体需求!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值