通过示例学习-Go-语言-2023-九-
通过示例学习 Go 语言 2023(九)
在 Go(Golang)中声明或创建/初始化结构变量
来源:
golangbyexample.com/declare-initialize-struct-variable-golang/
目录
-
概述
-
声明一个结构类型
-
创建一个结构变量
概述
GO 结构是不同类型数据字段的命名集合。结构充当容器,具有不同的异构数据类型,共同表示一个实体。
例如,不同的属性用于表示组织中的员工。员工可以拥有
-
字符串类型的名称
-
整数类型的年龄
-
时间类型的出生日期
-
整数类型的薪水
.. 以及其他内容。结构可以用于表示员工
type employee struct {
name string
age int
salary int
}
在 Golang 中,结构可以与面向对象语言中的类相比较
声明一个结构类型
以下是声明结构的格式
type struct_name struct {
field_name1 field_type1
field_name2 field_type2
...
}
在上述格式中,struct_name 是结构的名称。它有一个名为 field_name1 的字段,类型为 field_type1,还有一个名为 field_name2 的字段,类型为 field_type2。这声明了一个新的命名结构类型,作为蓝图。type 关键字用于引入新类型
示例
type point struct {
x float64
y float64
}
上述声明声明了一个名为 point 的新结构,它有两个字段 x 和 y。两个字段都是 float64 类型。一旦声明了新的结构类型,我们可以从中定义新的具体结构变量,如下节所示
创建一个结构变量
声明一个结构只是声明一个命名的结构类型。创建一个结构变量,会创建该结构的一个实例,同时内存也会被初始化。我们可以创建一个空的结构变量,而不为任何字段赋值
emp := employee{}
在这种情况下,结构中的所有字段都被初始化为该字段类型的默认零值。我们在创建结构变量时也可以为每个结构字段初始化值。这里有两种变体
- 在同一行
emp := employee{name: "Sam", age: 31, salary: 2000}
- 每个字段在不同的行上
emp := employee{
name: "Sam",
age: 31,
salary: 2000,
}
仅初始化某些字段的值也是可以的。未初始化的字段将获得其类型的默认零值
emp := employee{
name: "Sam",
age: 31,
}
在上述情况下,薪水将得到默认的零值,因为它没有被初始化
让我们看看一个演示上述要点的工作代码:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{}
fmt.Printf("Emp1: %+v\n", emp1)
emp2 := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp2: %+v\n", emp2)
emp3 := employee{
name: "Sam",
age: 31,
salary: 2000,
}
fmt.Printf("Emp3: %+v\n", emp3)
emp4 := employee{
name: "Sam",
age: 31,
}
fmt.Printf("Emp4: %+v\n", emp4)
}
输出
Emp1: {name: age:0 salary:0}
Emp2: {name:Sam age:31 salary:2000}
Emp2: {name:Sam age:31 salary:0}
对于上述程序
-
我们首先声明一个 employee 结构。
-
emp1 的所有字段都被初始化为其类型的默认零值,即名称为“”,年龄和薪水为 0。
-
emp2 已在同一行上初始化了所有字段。其字段正确打印了它们的值
-
emp3 的所有字段都在不同的行上初始化。其字段正确打印了它们的值
-
emp4 的薪水字段被初始化为默认零值 0。其他两个字段正确打印了它们的值。
需要注意的是,在结构体的初始化中,每个在大括号内的新行必须以逗号结尾。因此,下面的初始化会引发错误,因为
"salary" : 2000
不以逗号结束。
emp := employee{
name: "Sam",
age: 31,
salary: 2000
}
下面的代码是可以的,因为最后的括号与最后一个字段在同一行。
emp := employee{
name: "Sam",
age: 31,
salary: 2000}
没有字段名称
结构体也可以在不指定字段名称的情况下进行初始化。但在这种情况下,每个字段的所有值必须按顺序提供。
emp := employee{"Sam", 31, 2000}
如果没有使用字段名称而没有提供所有值,将会引发编译器错误。
让我们来看一个程序。
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{"Sam", 31, 2000}
fmt.Printf("Emp: %+v\n", emp)
//emp = employee{"Sam", 31}
}
输出
Emp2: {name:Sam age:31 salary:2000}
取消注释这一行。
emp = employee{"Sam", 31}
在上述程序中,它将引发编译器错误。
too few values in employee literal
在 Go(Golang)中声明常量
来源:
golangbyexample.com/declaring-constant-go/
常量是指任何不会改变其值的东西。在 Go 中,const 可以是字符串、数字、布尔值和字符类型。
常量可以使用const关键字声明。需要注意的重要一点是,值必须在声明常量时赋值。这与变量不同,变量的值可以稍后赋值。
- 声明const并指定类型 – 它以const关键字开头,后接名称,然后是类型。值也必须立即赋值,如上所述。
const c string = "circle"
- 不指定类型声明常量 – 没有类型声明的 const 是无类型常量。我们稍后会深入了解有类型和无类型常量。目前,重要的是要知道,无类型声明的 const 具有默认的隐藏类型。常量在以任何方式(直接初始化、传递给函数等)赋值给变量时会被赋予一个类型。
const c = "circle"
- 一起声明多个 const
const (
c = "circle"
s = "square"
)
在 Go (Golang) 中声明和实现接口
来源:
golangbyexample.com/declaring-implementing-interface-golang/
目录
-
概述
-
声明接口
-
实现接口
概述
接口是 Go 中的一种类型,它是方法签名的集合。这些方法签名的集合旨在表示一种行为。接口仅声明方法集,任何实现接口所有方法的类型都是该接口类型。
声明接口
让我们定义一个名为动物的接口。动物接口有两个方法呼吸和行走。它仅定义方法签名,别无其他。
type animal interface {
breathe()
walk()
}
方法签名包括
-
方法名称
-
参数的数量及每个参数的类型
-
返回值的数量及每个返回值的类型
通过上述声明,我们创建了一个新的接口类型,即动物。
定义一个动物类型的变量是可以的。让我们创建一个动物接口类型的变量。
package main
import "fmt"
type animal interface {
breathe()
walk()
}
func main() {
var a animal
fmt.Println(a)
fmt.Printf("Underlying Type: %T\n", a)
fmt.Printf("Underlying Value: %v\n", a)
}
输出
<nil>Underlying Type: <nil>Underlying Value:</nil></nil>
如上程序所示,创建一个接口类型的变量是可以的。它打印出 nil,因为接口的默认零值是 nil。
实现接口
任何实现了呼吸和行走方法的类型都可以被称为实现了动物接口。因此,如果我们定义一个狮子结构体并实现呼吸和行走方法,那么它将实现动物接口。
package main
import "fmt"
type animal interface {
breathe()
walk()
}
type lion struct {
age int
}
func (l lion) breathe() {
fmt.Println("Lion breathes")
}
func (l lion) walk() {
fmt.Println("Lion walk")
}
func main() {
var a animal
a = lion{age: 10}
a.breathe()
a.walk()
}
输出
Lion breathes
Lion walk
我们声明一个动物接口类型的变量
var a animal
然后我们将一个狮子结构体的实例赋值给它。
a = lion{}
将狮子结构体的实例赋值给动物接口类型的变量是可行的,因为狮子结构体实现了动物的呼吸和行走方法。赋值时并不会检查类型,而只需检查赋值的类型是否实现了呼吸和行走方法。这个概念类似于鸭子类型,一个狮子可以像动物一样呼吸和行走,因此它就是一种动物。
没有明确声明一个类型实现了一个接口。实际上,在 Go 中并不存在类似 Java 的“implements”关键字。如果一个类型实现了接口的所有方法,则它实现了该接口。
Go(Golang)中的装饰器设计模式。
来源:
golangbyexample.com/decorator-pattern-golang/
。
目录。
概述。
-
UML 图。
-
代码。
-
完整工作代码
概述。
装饰器设计模式是一种结构型设计模式。它允许你提供额外功能或装饰一个对象,而不改变该对象。
用一个例子会更好理解。假设你要开一家比萨连锁店,你开始时有两种比萨。
-
素食狂热比萨。
-
活力豆腐比萨。
上述每种比萨都有其价格。因此你将创建如下比萨接口。
package main
type pizza interface {
getPrice() int
}
你还需要创建两个比萨结构体,具有一个getPrice函数,返回价格。这两个比萨结构体实现比萨接口,因为它们定义了 getPrice()方法。
之后,你开始提供配料,并为每种配料额外收费。因此,原始基础比萨现在需要添加配料。想象一下你在菜单中增加了下面两种配料。
-
番茄配料。
-
奶酪配料。
此外,请记住,带配料的比萨仍然是一种比萨。顾客可以以不同的方式选择他们的比萨。例如。
-
带番茄配料的素食狂热。
-
带番茄和奶酪配料的素食主菜。
-
不带任何配料的活力印度奶酪比萨。
-
带奶酪配料的活力印度奶酪比萨。
-
…
那么现在考虑到你也有配料,你会如何设计呢?装饰器模式将会发挥作用。它可以在不实际修改任何现有结构体的情况下提供额外功能。装饰器模式建议在这种情况下为每种可用配料创建单独的结构体。每个配料结构体将实现上述比萨接口,并嵌入一个比萨实例。
现在我们有不同类型比萨的单独结构体和可用配料的单独结构体。每种比萨和配料都有自己的价格。每当你给比萨添加任何配料时,该配料的价格就会加到基础比萨的价格上,这样你就得到了最终价格。
因此,装饰器模式允许你在不改变原始基础比萨对象的情况下装饰该比萨对象。比萨对象对配料一无所知,只知道其价格,别无他物。
UML 图。
以下是装饰器设计模式的 UML 图。

具体组件(这里的素食狂热和活力豆腐)和具体装饰器(这里的配料)实现组件接口(这里的比萨)。此外,具体装饰器还会嵌入一个组件实例。
如下面的例子所示。
-
该组件由比萨接口表示。
-
具体组件由veggieMania和peppyPanner 结构表示。它们都实现了披萨接口。
-
具体装饰器由 cheeseTopping 和 tomatoTopping 结构表示。它们都实现了披萨接口。此外,它们还嵌入了一个pizza类型的实例。
代码
pizza.go
package main
type pizza interface {
getPrice() int
}
peppyPaneer.go
package main
type peppyPaneer struct {
}
func (p *peppyPaneer) getPrice() int {
return 20
}
veggeMania.go
package main
type veggeMania struct {
}
func (p *veggeMania) getPrice() int {
return 15
}
cheeseTopping.go
package main
type cheeseTopping struct {
pizza pizza
}
func (c *cheeseTopping) getPrice() int {
pizzaPrice := c.pizza.getPrice()
return pizzaPrice + 10
}
tomatoTopping.go
package main
type tomatoTopping struct {
pizza pizza
}
func (c *tomatoTopping) getPrice() int {
pizzaPrice := c.pizza.getPrice()
return pizzaPrice + 7
}
main.go
package main
import "fmt"
func main() {
veggiePizza := &veggeMania{}
//Add cheese topping
veggiePizzaWithCheese := &cheeseTopping{
pizza: veggiePizza,
}
//Add tomato topping
veggiePizzaWithCheeseAndTomato := &tomatoTopping{
pizza: veggiePizzaWithCheese,
}
fmt.Printf("Price of veggieMania pizza with tomato and cheese topping is %d\n", veggiePizzaWithCheeseAndTomato.getPrice())
peppyPaneerPizza := &peppyPaneer{}
//Add cheese topping
peppyPaneerPizzaWithCheese := &cheeseTopping{
pizza: peppyPaneerPizza,
}
fmt.Printf("Price of peppyPaneer with tomato and cheese topping is %d\n", peppyPaneerPizzaWithCheese.getPrice())
}
输出
Price of veggieMania pizza with tomato and cheese topping is 32
Price of peppyPaneer with tomato and cheese topping is 30
完整工作代码
package main
import "fmt"
type pizza interface {
getPrice() int
}
type peppyPaneer struct {
}
func (p *peppyPaneer) getPrice() int {
return 20
}
type veggeMania struct {
}
func (p *veggeMania) getPrice() int {
return 15
}
type tomatoTopping struct {
pizza pizza
}
func (c *tomatoTopping) getPrice() int {
pizzaPrice := c.pizza.getPrice()
return pizzaPrice + 7
}
type cheeseTopping struct {
pizza pizza
}
func (c *cheeseTopping) getPrice() int {
pizzaPrice := c.pizza.getPrice()
return pizzaPrice + 10
}
func main() {
veggiePizza := &veggeMania{}
//Add cheese topping
veggiePizzaWithCheese := &cheeseTopping{
pizza: veggiePizza,
}
//Add tomato topping
veggiePizzaWithCheeseAndTomato := &tomatoTopping{
pizza: veggiePizzaWithCheese,
}
fmt.Printf("Price of veggieMania pizza with tomato and cheese topping is %d\n", veggiePizzaWithCheeseAndTomato.getPrice())
peppyPaneerPizza := &peppyPaneer{}
//Add cheese topping
peppyPaneerPizzaWithCheese := &cheeseTopping{
pizza: peppyPaneerPizza,
}
fmt.Printf("Price of peppyPaneer with tomato and cheese topping is %d\n", peppyPaneerPizzaWithCheese.getPrice())
}
输出
Price of veggieMania pizza with tomato and cheese topping is 32
Price of peppyPaneer with tomato and cheese topping is 30
- 装饰器* go* golang*
Go(Golang)中所有类型的默认零值及示例
来源:
golangbyexample.com/go-default-zero-value-all-types/
默认值表
类型 | 默认值 |
---|---|
整数 | 0 |
浮点数 | 0 |
复数 | 0 实部和 0 虚部 |
字节 | 0 |
字符 | 0 |
字符串 | “” |
布尔值 | false |
数组 | 每个数组值都为其默认值 |
结构体 | 每个字段都为其默认值 |
映射 | nil |
通道 | nil |
接口 | nil |
切片 | nil |
指针 | nil |
函数 | nil |
示例:
让我们看看每个的示例。使用侧边菜单进行导航。
整数
package main
import "fmt"
func main() {
var a int
fmt.Print("Default zero value of int: ")
fmt.Println(a)
var b uint
fmt.Print("Default zero value of uint: ")
fmt.Println(b)
}
输出:
Default zero value of int: 0
Default zero value of uint: 0
浮点数
package main
import "fmt"
func main() {
var a float32
fmt.Print("Default zero value of float: ")
fmt.Println(a)
}
输出:
Default zero value of float: 0
复数
package main
import "fmt"
func main() {
var a complex64
fmt.Print("Default zero value of complex: ")
fmt.Println(a)
}
输出:
Default zero value of complex: (0+0i)
字节
package main
import "fmt"
func main() {
var a byte
fmt.Print("Default zero value of byte: ")
fmt.Println(a)
}
输出:
Default zero value of byte: 0
字符
package main
import "fmt"
func main() {
var a rune
fmt.Print("Default zero value of rune: ")
fmt.Println(a)
}
输出:
Default zero value of rune: 0
字符串
package main
import "fmt"
func main() {
var a string
fmt.Print("Default zero value of string: ")
fmt.Println(a)
}
输出:
Default zero value of string: //An emtpy string. Hence nothing is printed
布尔值
package main
import "fmt"
func main() {
var a bool
fmt.Print("Default zero value of bool: ")
fmt.Println(a)
}
输出:
Default zero value of bool: false
数组
数组的默认值是其值的默认值。例如,在下面的代码中,有一个长度为 2 的 bool 类型数组。当我们打印时,输出是[false false]。
package main
import "fmt"
func main() {
var a [2]bool
fmt.Print("Default Zero Value of an array: ")
fmt.Println(a)
}
输出:
Default Zero Value of a array: [false false]
结构体
结构体的默认值是其字段的默认值。例如,在下面的代码中,有一个包含两个字段的结构体样本。其中一个是 int 类型,另一个是 bool 类型。我们创建这个结构体的一个实例,当我们打印它时,输出是{0 false}。
package main
import "fmt"
func main() {
type sample struct {
a int
b bool
}
a := sample{}
fmt.Print("Default Zero Value of an struct: ")
fmt.Println(a)
}
输出:
Default Zero Value of a struct: {0 false}
映射
映射的默认值是nil。这就是为什么fmt.Println(a==nil)的输出为 true。当映射传递给fmt.Println时,它尝试打印映射中的值。这就是输出为 map[]的原因。
package main
import "fmt"
func main() {
var a map[bool]bool
fmt.Println(a == nil)
fmt.Print("Printing map: ")
fmt.Println(a)
}
输出:
true
Printing map: map[]
通道
package main
import "fmt"
func main() {
var a chan int
fmt.Print("Default zero value of channel: ")
fmt.Println(a)
}
输出:
通道的默认零值:
接口
package main
import "fmt"
func main() {
var a chan interface{}
fmt.Print("Default zero value of interface: ")
fmt.Println(a)
}
输出:
接口的默认零值:
切片
切片的默认值是nil。这就是为什么fmt.Println(a==nil)的输出为 true。当切片传递给fmt.Println时,它尝试打印切片中的值。这就是输出为[]的原因。
package main
import "fmt"
func main() {
var a []int
fmt.Println(a == nil)
fmt.Print("Printing slice: ")
fmt.Println(a)
}
输出:
true
Printing slice: []
函数
package main
import "fmt"
func main() {
var a func()
fmt.Print("Default Zero Value of a func: ")
fmt.Println(a)
}
输出:
函数的默认零值:
指针
package main
import "fmt"
func main() {
var a *int
fmt.Print("Default Zero Value of a pointer: ")
fmt.Println(a)
}
输出:
指针的默认零值:
- 数据类型 默认值 示例 go golang 侧边目录 类型 零值
Go(Golang)中的指针默认零值
来源:
golangbyexample.com/default-zero-value-pointer-golang/
目录
-
概述
-
程序
概述
指针的默认零值是 nil。让我们看看一个程序
程序
package main
import "fmt"
func main() {
var a *int
fmt.Print("Default Zero Value of a pointer: ")
fmt.Println(a)
}
输出:
Default value of pointer:
在 Go (Golang) 中延迟一个 goroutine
来源:
golangbyexample.com/defer-goroutine-golang/
目录
-
概述
-
示例
概述
直接延迟一个 goroutine 是不可能的。但有一种变通方法。在 defer 函数中,你可以像下面这样在 goroutine 中调用另一个函数
defer func() {
go some_function()
}()
示例
让我们看看一个程序
package main
import (
"fmt"
"time"
)
func main() {
call()
time.Sleep(time.Second * 2)
}
func call() {
defer func() {
go test()
}()
fmt.Println("In call function")
}
func test() {
fmt.Println("In test function")
}
输出
In call function
In test function
查看我们如何在 调用 函数中间接延迟一个 goroutine
defer func() {
go test()
}()
Go 语言中的延迟和方法
来源:
golangbyexample.com/defer-methods-golang/
目录
-
概述
-
示例
概述
defer语句同样适用于方法,就像它适用于函数一样。让我们看看一个示例。
示例
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := writeToTempFile("Some text")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("Write to file succesful")
}
func writeToTempFile(text string) error {
file, err := os.Open("temp.txt")
if err != nil {
return err
}
defer file.Close()
n, err := file.WriteString("Some text")
if err != nil {
return err
}
fmt.Printf("Number of bytes written: %d", n)
return nil
}
在上述程序中,我们在打开文件后做defer file.Close()。Close是定义在file实例上的方法。这确保了即使写入文件出现错误,文件也会被关闭。延迟函数确保无论函数中有多少个返回语句,文件都会被关闭。
让我们看看一个在延迟函数中调用自定义结构体的方法的另一个示例。
package main
import (
"errors"
"fmt"
)
type employee struct {
name string
}
func (e *employee) setName(name string) error {
defer e.setDefaultName()
if len(name) < 3 {
fmt.Println("Length of name passed is less than 3")
return errors.New("Length of name cannnot be less than 3")
}
e.name = name
return nil
}
func (e *employee) setDefaultName() {
fmt.Println("In the setDefaultName function")
if e.name == "" {
e.name = "DefaultName"
fmt.Println("Default name is set")
}
}
func main() {
e1 := &employee{}
e1.setName("John")
fmt.Printf("First employee name is: %s\n", e1.name)
fmt.Println()
e2 := &employee{}
e2.setName("Ko")
fmt.Printf("Second employee name is: %s\n", e2.name)
return
}
输出
In the setDefaultName function
First employee name is: John
Length of name passed is less than 3
In the setDefaultName function
Default name is set
Second employee name is: DefaultName
在上述程序中,我们有一个自定义结构体employee。
type employee struct {
name string
}
结构体employee有一个setName函数,用于设置名称。但如果传入的名称长度小于 3,该函数也会抛出错误。我们在setName中有一个延迟函数,它在setName完成后执行,并检查名称是否为空。如果为空,它会设置一个默认名称。这个延迟函数实际上是一个方法setDefaultName。
func (e *employee) setDefaultName() {
if e.name == "" {
e.name = "DefaultName"
fmt.Println("Default name is set")
}
}
然后我们创建一个employee实例 e1 并设置它的名称。
由于“John”的长度大于 3,setName函数不会抛出错误。但如果你注意输出,延迟函数setDefaultName仍然被执行。
In the setDefaultName function
First employee name is: John
然后我们创建一个employee实例e2并设置它的名称。
e1 := &employee{}
e1.setName("Ko")
由于“Ko”的长度小于 3,setName函数抛出错误。在这种情况下,延迟函数setDefaultName也会执行,并设置默认名称。这就是为什么你在下面的输出中看到这种情况。
Length of name passed is less than 3
In the setDefaultName function
Default name is set
Second employee name is: DefaultNam
Go (Golang)中的 Defer 函数和命名返回值
来源:
golangbyexample.com/defer-named-return-values-golang/
目录
-
概述
-
示例
概述
在函数中如果有命名返回值,defer 函数可以读取和修改这些命名返回值。如果 defer 函数修改了命名返回值,则该修改后的值将被返回。
让我们看看一个程序
示例
package main
import "fmt"
func main() {
s := test()
fmt.Println(s)
}
func test() (size int) {
defer func() { size = 20 }()
size = 30
return
}
输出
20
在上面的程序中,我们在测试函数中命名返回值为“size”。在 defer 函数中,我们修改了命名返回值,并将值更改为 20。然后我们将 size 设置为 30。在主函数中,我们打印测试函数的返回值,输出为 20 而不是 30,因为 defer 函数已修改测试函数中 size 变量的值。
Go 语言中的defer关键字
来源:
golangbyexample.com/defer-golang/
这是 Golang 综合教程系列的第十四章。请参考此链接获取系列的其他章节 – Golang 综合教程系列
下一个教程 – Pointer
上一个教程 – Switch
现在让我们来看一下当前的教程。下面是当前教程的目录。
目录
-
概述
-
defer 中的自定义函数
-
defer 中的内联函数
-
defer 是如何工作的
-
延迟参数评估
-
同一函数中的多个 defer 函数
-
defer 函数和命名返回值
-
defer 和方法
-
defer 和 panic
-
结论
概述
defer(延迟)顾名思义用于延迟函数中的清理操作。这些清理操作将在函数结束时执行。这些清理操作将在一个由defer调用的不同函数中完成。这个不同的函数会在周围函数返回之前被调用。下面是defer函数的语法。
defer {function_or_method_call}
关于 defer 函数需要注意的事项
-
延迟函数的执行会被推迟到周围函数返回的时刻。
-
如果封闭函数异常终止,延迟函数也会被执行。例如在发生 panic 的情况下。
理解defer函数的一个好例子是查看写入文件的使用案例。一个为写入而打开的文件也必须关闭。
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := writeToTempFile("Some text")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("Write to file succesful")
}
func writeToTempFile(text string) error {
file, err := os.Open("temp.txt")
if err != nil {
return err
}
n, err := file.WriteString("Some text")
if err != nil {
return err
}
fmt.Printf("Number of bytes written: %d", n)
file.Close()
return nil
}
在上述程序中,在writeToTempFile函数中,我们打开一个文件,然后尝试向文件写入一些内容。在我们写入文件内容后关闭文件。在写入操作期间,可能会导致错误,函数会在不关闭文件的情况下返回。Defer函数帮助避免这些问题。Defer函数总是在周围函数返回之前执行。让我们在这里重写上述程序,使用defer函数。
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := writeToTempFile("Some text")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("Write to file succesful")
}
func writeToTempFile(text string) error {
file, err := os.Open("temp.txt")
if err != nil {
return err
}
defer file.Close()
n, err := file.WriteString("Some text")
if err != nil {
return err
}
fmt.Printf("Number of bytes written: %d", n)
return nil
}
在上述程序中,我们在打开文件后执行defer file.Close()。这将确保即使写入文件时发生错误,文件也会被关闭。defer
函数确保文件会被关闭,无论函数中有多少个返回语句。
自定义函数在 defer 中
我们也可以在defer中调用自定义函数。让我们看看一个例子。
package main
import "fmt"
func main() {
defer test()
fmt.Println("Executed in main")
}
func test() {
fmt.Println("In Defer")
}
输出
Executed in main
In Defer
在上述程序中,有一个defer语句调用名为test的自定义函数。从输出中可以看到,test函数在主函数中的所有内容执行完毕后、主函数返回之前被调用。这就是原因。
Executed in main
被打印在前。
In Defer
上述函数还显示在主函数中使用defer
是完全可以的。
defer 中的内联函数
也可以在defer
中使用内联函数。让我们看看一个例子。
package main
import "fmt"
func main() {
defer func() { fmt.Println("In inline defer") }()
fmt.Println("Executed")
}
输出
Executed
In inline defer
在上述代码中,我们有一个带内联函数的defer。
defer func() { fmt.Println("In inline defer") }()
在 Go 中这是允许的。此外,请注意在函数后添加“()”是强制性的,否则编译器会抛出错误。
expression in defer must be function call
从输出中可以看到,内联函数在主函数中的所有内容执行完毕后、主函数返回之前被调用。这就是原因。
Executed in main
被打印在前。
In inline Defer
defer 是如何工作的
当编译器在函数中遇到defer
语句时,它将其推送到一个列表中。该列表在内部实现了一个堆栈数据结构。所有在同一函数中遇到的defer
语句都被推送到这个列表中。当外层函数返回时,堆栈中的所有函数从上到下执行,然后才能开始调用函数的执行。调用函数中也会发生同样的事情。
让我们了解当多个defer
函数在不同函数中时会发生什么。想象一下从main函数调用到f1函数,再到f2函数。
main->f1->f2
以下是f2
返回后将发生的顺序。
-
如果存在,f2中的
defer
函数将被执行。控制将返回到调用者,即f1函数。 -
如果存在,f1中的
defer
函数将被执行。控制将返回到调用者,即main函数。注意,如果中间还有更多函数,处理过程将以类似方式继续向上堆栈。 -
在主函数返回后,如果在主函数中存在
defer
函数,将被执行。
让我们看看一个程序。
package main
import "fmt"
func main() {
defer fmt.Println("Defer in main")
fmt.Println("Stat main")
f1()
fmt.Println("Finish main")
}
func f1() {
defer fmt.Println("Defer in f1")
fmt.Println("Start f1")
f2()
fmt.Println("Finish f1")
}
func f2() {
defer fmt.Println("Defer in f2")
fmt.Println("Start f2")
fmt.Println("Finish f2")
}
输出
Stat main
Start f1
Start f2
Finish f2
Defer in f2
Finish f1
Defer in f1
Finish main
Defer in main
defer 参数的评估
defer
参数在defer
语句被求值时被评估。
让我们看看一个程序。
package main
import "fmt"
func main() {
sample := "abc"
defer fmt.Printf("In defer sample is: %s\n", sample)
sample = "xyz"
}
输出
In defer sample is: abc
在上述程序中,当defer
语句被评估时,sample变量的值为“abc”。在defer
函数中,我们打印sample
变量。在defer
语句之后,我们将sample变量的值更改为“xyz”。但程序输出的是“abc”而不是“xyz”,因为当defer
参数被评估时,sample变量的值是“abc”。
同一函数中的多个 defer 函数
如果在特定函数中有多个 defer 函数,那么所有的 defer 函数将按照后进先出顺序执行。
让我们来看一个程序。
package main
import "fmt"
func main() {
i := 0
i = 1
defer fmt.Println(i)
i = 2
defer fmt.Println(i)
i = 3
defer fmt.Println(i)
}
输出
3
2
1
在上面的程序中,我们有三个 defer 函数,每个函数打印变量 i 的值。变量 i 在每个 defer 之前递增。代码首先输出 3,这意味着第三个 defer 函数首先执行。然后输出 2,表示第二个 defer 之后执行,最后输出 1,意味着第一个 defer 最后执行。这表明在特定函数中存在多个 defer 函数时,它们遵循“后进先出”规则。因此程序输出。
3
2
1
延迟函数和命名返回值
在函数中,如果有命名返回值,defer 函数可以读取并修改这些命名返回值。如果 defer 函数修改了命名返回值,则将返回该修改后的值。
让我们来看一个程序。
package main
import "fmt"
func main() {
s := test()
fmt.Println(s)
}
func test() (size int) {
defer func() { size = 20 }()
size = 30
return
}
输出
20
在上面的程序中,我们在测试函数中有命名返回值 “size”。在 defer 函数中,我们修改了命名返回值,并将值改为 20。然后我们将 size 设置为 30。在主函数中,我们打印测试函数的返回值,结果输出 20 而不是 30,因为 defer 函数在测试函数中修改了 size 变量的值。
延迟和方法
defer 语句同样适用于方法,类似于它对函数的适用。在第一个示例中,我们已经看到在文件实例上调用的 Close 方法。这表明 defer 语句同样适用于方法。
延迟和恐慌
即使在程序中发生了恐慌,defer 函数也会被执行。当在某个函数中触发 panic 时,该函数的执行将被停止,任何被延迟的函数将被执行。实际上,栈中所有函数调用的延迟函数也会被执行,直到所有函数都返回。此时程序将退出,并打印 panic 消息。
所以如果存在一个 defer 函数,它将被执行,控制将返回给调用函数,调用函数如果存在 defer 函数,则会再次执行,链条将继续,直到程序退出。
让我们来看一个示例。
package main
import "fmt"
func main() {
defer fmt.Println("Defer in main")
panic("Panic with Defer")
fmt.Println("After painc in f2")
}
输出
Defer in main
panic: Panic Create
goroutine 1 [running]:
main.main()
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/deferWithPanic/main.go:7 +0x95
exit status 2
在上面的程序中,我们首先有一个 defer 函数,然后手动触发 panic。正如你从输出中看到的,defer 函数得到了执行,输出中打印了以下行。
Defer in main
结论
这就是 Golang 中的 defer。希望你喜欢这篇文章,请在评论中分享反馈/改进/错误。
下一个教程 – 指针
上一个教程 – 切换
在 Go (Golang)中删除文件
来源:
golangbyexample.com/delete-file-go/
os.Remove() 函数可用于在 Golang 中删除文件。以下是该函数的签名。
func Remove(name string) error
代码:
package main
import (
"log"
"os"
)
func main() {
err := os.Remove("sample.txt")
if err != nil {
log.Fatal(err)
}
}
输出:
Deletes sample.txt file from the current working directory
- 删除 * golang * 移除
在 Go (Golang)中删除单链表的第 k 个节点
来源:
golangbyexample.com/delete-kth-node-back-linked-list-golang/
目录
-
概述
-
程序
-
解释
概述
在单链表中删除从尾部的第 k 个节点
输入链表:
A-> B-> C-> D-> E-> F-> Null
要移除的节点是从尾部的第 3 个节点,然后
输出链表:
A-> B-> C-> E-> F-> Null
程序
package main
import "fmt"
type node struct {
data string
next *node
}
type singlyLinkedList struct {
len int
head *node
}
func initList() *singlyLinkedList {
return &singlyLinkedList{}
}
func (s *singlyLinkedList) AddFront(data string) {
node := &node{
data: data,
}
if s.head == nil {
s.head = node
} else {
node.next = s.head
s.head = node
}
s.len++
return
}
func (s *singlyLinkedList) Traverse() error {
if s.head == nil {
return fmt.Errorf("TraerseList: List is empty")
} else {
current := s.head
for current != nil {
fmt.Println(current.data)
current = current.next
}
}
return nil
}
func (s *singlyLinkedList) RemovekthFromEnd(k int) error {
if s.head == nil {
return fmt.Errorf("List is empty")
}
if k > s.len {
return fmt.Errorf("Err: Given number is greater than linked list length")
}
if k == s.len {
s.head = s.head.next
} else {
var prev *node
current := s.head
for i := 1; i < s.len-k+1; i++ {
prev = current
current = current.next
}
prev.next = current.next
}
s.len--
return nil
}
func main() {
singleList := initList()
fmt.Printf("AddFront: F\n")
singleList.AddFront("F")
fmt.Printf("AddFront: E\n")
singleList.AddFront("E")
fmt.Printf("AddFront: D\n")
singleList.AddFront("D")
fmt.Printf("AddFront: C\n")
singleList.AddFront("C")
fmt.Printf("AddFront: B\n")
singleList.AddFront("B")
fmt.Printf("AddFront: A\n")
singleList.AddFront("A")
fmt.Println("Traversal")
err := singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
// Remove 5th node from back
fmt.Println("\nRemoving 5th node from the end")
err = singleList.RemovekthFromEnd(5)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Traversal after 5th node is removed from the end")
err = singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
// Remove first node from back
fmt.Println("\nRemoving 1st node from the end")
err = singleList.RemovekthFromEnd(1)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Traversal after 1st node is removed from the end")
err = singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
// Trying to delete node from a place greater than list size
fmt.Println("\nTrying to delete node from a place greater than list size")
err = singleList.RemovekthFromEnd(7)
if err != nil {
fmt.Println(err.Error())
}
}
输出
AddFront: F
AddFront: E
AddFront: D
AddFront: C
AddFront: B
AddFront: A
Traversal
A
B
C
D
E
F
Removing 5th node from the end
Traversal after 5th node is removed from the end
A
C
D
E
F
Removing 1st node from the end
Traversal after 1st node is removed from the end
A
C
D
E
Trying to delete a node from a place greater than list size
Err: Given number is greater than linked list length
解释
我们创建了如下的链表。
A-> B-> C-> D-> E-> F-> Null
然后我们从尾部移除第 5 个节点,即B。我们遍历链表。从输出中可以看到B被移除了。
A
C
D
E
F
然后我们从尾部移除第 1 个节点,即F。我们遍历链表。从输出中可以看到F被移除了。
A
C
D
E
然后我们尝试从链表中删除一个位置大于链表大小的节点。这会产生以下错误。
Err: Given number is greater than linked list length
从单链表的前面删除第 k 个节点,使用 Go (Golang)
来源:
golangbyexample.com/kth-node-front-linked-list-golang/
目录
-
概述
-
程序
-
解释
概述
从单链表的前面删除第 k 个节点
输入链表:
A-> B-> C-> D-> E-> F-> Null
假设要移除的节点是前面的第 3 个节点,那么
输出链表:
A-> B-> D-> E-> F-> Null
程序
package main
import "fmt"
type node struct {
data string
next *node
}
type singlyLinkedList struct {
len int
head *node
}
func initList() *singlyLinkedList {
return &singlyLinkedList{}
}
func (s *singlyLinkedList) AddFront(data string) {
node := &node{
data: data,
}
if s.head == nil {
s.head = node
} else {
node.next = s.head
s.head = node
}
s.len++
return
}
func (s *singlyLinkedList) Traverse() error {
if s.head == nil {
return fmt.Errorf("TraverseError: List is empty")
}
current := s.head
for current != nil {
fmt.Println(current.data)
current = current.next
}
return nil
}
func (s *singlyLinkedList) Removekth(k int) error {
if s.head == nil {
return fmt.Errorf("List is empty")
}
if k > s.len {
return fmt.Errorf("Err: Given number is greater than linked list length")
}
if k == 1 {
fmt.Println("Node to be removed is front node")
s.head = s.head.next
} else {
var prev *node
current := s.head
for i := 1; i < k; i++ {
prev = current
current = current.next
}
prev.next = current.next
}
s.len--
return nil
}
func main() {
// Initiaise singly linked list
singleList := initList()
// Adding nodes in linked list from front
fmt.Printf("AddFront: F\n")
singleList.AddFront("F")
fmt.Printf("AddFront: E\n")
singleList.AddFront("E")
fmt.Printf("AddFront: D\n")
singleList.AddFront("D")
fmt.Printf("AddFront: C\n")
singleList.AddFront("C")
fmt.Printf("AddFront: B\n")
singleList.AddFront("B")
fmt.Printf("AddFront: A\n")
singleList.AddFront("A")
fmt.Println("Traversal")
err := singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("\nRemoving 2nd node from the front which is B")
err = singleList.Removekth(2)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Traversal after 2nd node is removed from the front")
err = singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("\nRemoving 5th node from the front which is F now")
err = singleList.Removekth(5)
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("Traversal after 5th node is removed from the front")
err = singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
// Trying to delete node from a place greater than list size
fmt.Println("\nTrying to delete node from a place greater than list size")
err = singleList.Removekth(5)
if err != nil {
fmt.Println(err.Error())
}
}
输出
AddFront: F
AddFront: E
AddFront: D
AddFront: C
AddFront: B
AddFront: A
Traversal
A
B
C
D
E
F
Removing 2nd node from the front which is B
Traversal after 2nd node is removed from the front
A
C
D
E
F
Removing 5th node from the front which is F now
Traversal after 5th node is removed from the front
A
C
D
E
Trying to delete node from a place greater than list size
Err: Given number is greater than linked list length
解释
我们创建了如下链表
A-> B-> C-> D-> E-> F-> Null
然后我们移除前面的第 2 个节点,即B。我们遍历链表。从输出中可以看出B被移除。
A
C
D
E
F
然后我们移除前面的第 5 个节点,即F。我们遍历链表。从输出中可以看出F被移除。
A
C
D
E
然后我们尝试从链表中删除一个节点,位置超过链表的大小。这会产生以下错误。
Err: Given number is greater than linked list length
另外,查看我们的 Golang 高级教程系列 – Golang 高级教程 *
在 Go (Golang)中按索引删除字符串
来源:
golangbyexample.com/golang-delete-index-string/
在 Golang 中,字符串是字节的序列。字符串字面量实际上表示的是 UTF-8 字节序列。在 UTF-8 中,ASCII 字符是对应前 128 个 Unicode 字符的单字节。所有其他字符的字节数在 1 到 4 之间。因此,无法对字符串中的字符进行索引。在 GO 中,rune 数据类型表示一个 Unicode 点。一旦字符串被转换为 rune 数组,就可以对该 rune 数组中的字符进行索引。
你可以在这里了解更多关于 rune 的信息 – golangbyexample.com/understanding-rune-in-golang
因此,在下面的程序中,我们通过索引删除给定字符串中的字符,首先将字符串转换为 rune 数组,以便可以对 rune 数组进行索引,然后通过索引删除字符。
package main
import "fmt"
func main() {
sample := "ab£c"
s := []rune(sample)
res := delChar(s, 2)
fmt.Println(string(res))
}
func delChar(s []rune, index int) []rune {
return append(s[0:index], s[index+1:]...)
}
输出:
abc
在 Go(Golang)中删除链表的中间节点
来源:
golangbyexample.com/delete-middle-node-linked-list-golang/
目录
-
概述
-
程序
概述
目标是删除链表的中间节点。如果 x 是链表的大小,则中间节点是
mid = x/2
示例
Input: 1->2->3->4->5
Output: 1->2->4->5
程序
下面是相应的程序。
package main
import "fmt"
func main() {
first := initList()
first.AddFront(5)
first.AddFront(4)
first.AddFront(3)
first.AddFront(2)
first.AddFront(1)
first.Head.Traverse()
deleteMiddle(first.Head)
fmt.Println("")
first.Head.Traverse()
}
func initList() *SingleList {
return &SingleList{}
}
type ListNode struct {
Val int
Next *ListNode
}
func (l *ListNode) Traverse() {
for l != nil {
fmt.Println(l.Val)
l = l.Next
}
}
type SingleList struct {
Len int
Head *ListNode
}
func (s *SingleList) AddFront(num int) {
ele := &ListNode{
Val: num,
}
if s.Head == nil {
s.Head = ele
} else {
ele.Next = s.Head
s.Head = ele
}
s.Len++
}
func deleteMiddle(head *ListNode) *ListNode {
if head == nil {
return nil
}
size := sizeOfList(head)
mid := size / 2
if mid == 0 {
return head.Next
}
curr := head
for i := 0; i < mid-1; i++ {
curr = curr.Next
}
prev := curr
midNode := prev.Next
if midNode == nil {
return head
}
midNext := midNode.Next
prev.Next = midNext
return head
}
func sizeOfList(head *ListNode) int {
l := 0
for head != nil {
l = l + 1
head = head.Next
}
return l
}
输出
1
2
3
4
5
1
2
4
5
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽力涵盖所有概念及其示例。本教程适合那些希望获得专业知识和深入理解 Golang 的人 - Golang 高级教程
如果你对理解如何在 Golang 中实现所有设计模式感兴趣。如果是的话,这篇文章就是为你准备的 - 所有设计模式 Golang
在 Go (Golang)中删除或移除映射中的键
来源:
golangbyexample.com/delete-key-map-golang/
目录
-
概述
-
键存在于映射中
-
键在映射中不存在
概述
以下是从映射中删除给定键的格式
delete(mapName, keyToDelete)
有两个情况
-
keyToDelete在映射中存在。在这种情况下,它将简单地删除该键
-
keyToDelete在映射中不存在。在这种情况下,它将不执行任何操作。A
让我们看一下这两种情况的示例
键存在于映射中
以下是键存在于映射中的程序
package main
import "fmt"
func main() {
sample := make(map[string]int)
sample["a"] = 1
sample["b"] = 2
sample["c"] = 3
fmt.Println(sample)
delete(sample, "a")
fmt.Println(sample)
}
输出
map[a:1 b:2 c:3]
map[b:2 c:3]
注意输出中,键“a”已从映射中删除。检查键是否存在于映射中,然后再删除总是一个好习惯。以下是相关的代码片段。
package main
import "fmt"
func main() {
sample := make(map[string]int)
sample["a"] = 1
sample["b"] = 2
sample["c"] = 3
fmt.Println(sample)
if _, ok := sample["a"]; ok {
delete(sample, "a")
}
fmt.Println(sample)
}
输出
map[a:1 b:2 c:3]
map[b:2 c:3]
键在映射中不存在
在这种情况下,检查键是否存在然后再删除也是一个好习惯。即使我们直接删除而不检查,也不是问题。以下代码片段显示了这两种情况
package main
import "fmt"
func main() {
sample := make(map[string]int)
sample["a"] = 1
sample["b"] = 2
sample["c"] = 3
fmt.Println(sample)
//Check and delete
if _, ok := sample["d"]; ok {
delete(sample, "d")
}
fmt.Println(sample)
//Directly delete
delete(sample, "d")
fmt.Println(sample)
}
输出
map[a:1 b:2 c:3]
map[a:1 b:2 c:3]
map[a:1 b:2 c:3]
注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尝试用示例覆盖所有概念。这个教程是为了那些希望获得 Golang 专业知识和扎实理解的人—Golang 高级教程
如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是的话,这篇文章就是为你准备的—
所有设计模式 Golang*