相同点:
不同点:
package mainimport "fmt"func main() { // 使用 new 创建一个整数的指针 var numPtr *int numPtr = new(int) *numPtr = 42 fmt.Println("Value of numPtr:", *numPtr) // 输出: Value of numPtr: 42 // 使用 make 创建一个切片 slice := make([]int, 3, 5) // 创建一个长度为3,容量为5的切片 slice[0] = 1 slice[1] = 2 slice[2] = 3 fmt.Println("Slice:", slice) // 输出: Slice: [1 2 3] // 使用 make 创建一个映射 m := make(map[string]int) m["apple"] = 5 m["banana"] = 3 fmt.Println("Map:", m) // 输出: Map: map[apple:5 banana:3] // 使用 make 创建一个通道 ch := make(chan int) go func() { ch <- 42 }() value := <-ch fmt.Println("Channel value:", value) // 输出: Channel value: 42}
相同点:
不同点:
package mainimport "fmt"func main() { // 声明一个数组 var arr [3]int arr[0] = 1 arr[1] = 2 arr[2] = 3 // 打印数组 fmt.Println("Array:", arr) // 输出: Array: [1 2 3] // 尝试修改数组的值 modifiedArr := arr modifiedArr[0] = 100 fmt.Println("Modified Array:", modifiedArr) // 输出: Modified Array: [100 2 3] fmt.Println("Original Array:", arr) // 输出: Original Array: [1 2 3](原始数组未受影响) // 声明一个切片 slice := []int{1, 2, 3} // 打印切片 fmt.Println("Slice:", slice) // 输出: Slice: [1 2 3] // 尝试修改切片的值 modifiedSlice := slice modifiedSlice[0] = 100 fmt.Println("Modified Slice:", modifiedSlice) // 输出: Modified Slice: [100 2 3] fmt.Println("Original Slice:", slice) // 输出: Original Slice: [100 2 3](原始切片受到影响) // 使用切片的 append 函数动态增加元素 slice = append(slice, 4, 5) fmt.Println("Updated Slice:", slice) // 输出: Updated Slice: [100 2 3 4 5]}
迭代变量的地址不会发生变化。每次迭代都会创建一个新的迭代变量,该变量的值是切片或数组中的元素,但地址不同。这是因为Go在每次迭代中会重新分配内存来存储迭代变量的副本。
package mainimport "fmt"func main() { slice := []int{1, 2, 3} for index, value := range slice { fmt.Printf("Index: %d, Value: %d, Address: %p\n", index, value, &value) }}
for range 循环迭代了切片 slice,并打印了每个元素的索引、值以及值的地址。会发现,每次迭代中 value 的地址都不同,这表明每次迭代都创建了一个新的变量。
因此,在 for range 循环中,不要依赖迭代变量的地址来保持状态,因为它们会在每次迭代中重新分配。如果需要保持某个迭代变量的状态,可以将其复制到一个新的变量中。
用于延迟执行函数调用。当使用 defer 时,它会将函数调用推迟到包含 defer 语句的函数即将返回之前执行。defer 常用于清理操作,如关闭文件、释放资源等,以确保在函数执行完毕时执行这些操作。
defer 的原理是通过一个栈(defer stack)来实现的,使用链表实现,将新的defer插入头节点,结束时,依次从头部取出。每次遇到 defer 语句,它会将要执行的函数及其参数入栈,但不会立即执行。当函数即将返回时,会按照后进先出(LIFO)的顺序执行栈中的 defer 函数调用。
package mainimport "fmt"func main() { fmt.Println("Start") defer fmt.Println("Deferred 1") defer fmt.Println("Deferred 2") defer fmt.Println("Deferred 3") fmt.Println("End")}#Start#End#Deferred 3#Deferred 2#Deferred 1
在Go语言中,rune是一种数据类型,用于表示Unicode字符。Unicode是一种字符编码标准,它包含了世界上几乎所有的字符,包括常见字符(如字母、数字、标点符号)以及各种特殊字符(如表情符号、非拉丁字母等)。
rune类型实际上是int32类型的别名,用于表示Unicode字符的整数值。每个rune代表一个字符,无论字符的编码有多大。这使得Go语言能够处理各种不同字符集的文本数据。
golang中的字符串底层实现是通过byte数组的,中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8。
package mainimport "fmt"func main() { // 创建一个包含Unicode字符的字符串 str := "Hello, 世界!" // 使用 for range 迭代字符串中的每个字符 for i, r := range str { fmt.Printf("Character %d: %c (Unicode: %U)\n", i, r, r) }}
Character 0: H (Unicode: U+0048)Character 1: e (Unicode: U+0065)Character 2: l (Unicode: U+006C)Character 3: l (Unicode: U+006C)Character 4: o (Unicode: U+006F)Character 5: , (Unicode: U+002C)Character 6: (Unicode: U+0020)Character 7: 世 (Unicode: U+4E16)Character 10: 界 (Unicode: U+754C)Character 13: ! (Unicode: U+FF01)
可以使用反射来解析结构体字段的标记(tag)。反射是Go语言的一种特性,它允许程序在运行时检查和操作变量、方法、结构体等程序结构信息。通过反射,可以获取结构体字段的标记信息,以及动态访问、修改这些字段的值。
要解析结构体字段的标记,需要使用reflect包,该包提供了一些函数和类型,用于处理反射操作。
package mainimport ( "fmt" "reflect")// 定义一个结构体并添加标记type Person struct { Name string `json:"name"` Age int `json:"age"` Address string `json:"address"`}func main() { // 创建一个示例结构体 p := Person{ Name: "Alice", Age: 30, Address: "123 Main St", } // 获取结构体类型 t := reflect.TypeOf(p) // 遍历结构体的字段 for i := 0; i < t.NumField(); i++ { field := t.Field(i) // 获取字段名和标记值 fieldName := field.Name tagValue := field.Tag.Get("json") fmt.Printf("Field: %s, Tag: %s\n", fieldName, tagValue) }}
Field: Name, Tag: nameField: Age, Tag: ageField: Address, Tag: address
这个扩容机制的好处是,开发者无需关心切片的容量,可以专注于操作切片的长度。这简化了代码,并且避免了手动管理内存分配和复制数据的繁琐工作。
select 是用于处理多个通道操作的控制结构,实现 I/O 多路复用机制,它允许等待多个通道中的任何一个可以操作(发送或接收),并执行相应的操作。select 通常用于解决并发编程中的问题,例如等待多个任务中的一个完成,或者处理多个输入源的数据。
package mainimport ( "fmt" "time")func main() { // 创建两个通道 ch1 := make(chan string) ch2 := make(chan string) // 启动两个并发的 goroutine,分别向通道发送数据 go func() { time.Sleep(2 * time.Second) ch1 <- "Hello from goroutine 1" }() go func() { time.Sleep(1 * time.Second) ch2 <- "Hello from goroutine 2" }() // 使用 select 来等待多个通道操作 select { case msg1 := <-ch1: fmt.Println("Received:", msg1) case msg2 := <-ch2: fmt.Println("Received:", msg2) case <-time.After(3 * time.Second): fmt.Println("Timeout: No data received") } // 关闭通道 close(ch1) close(ch2)}
处理并发访问map时需要注意,因为map不是线程安全的,多个goroutine同时对同一个map进行读写操作可能会导致数据竞态问题。为了安全地并发访问map,可以采用以下几种方式:
package mainimport ( "fmt" "sync")func main() { var mu sync.Mutex m := make(map[int]int) // 启动多个goroutine并发写入map for i := 0; i < 5; i++ { go func(i int) { mu.Lock() defer mu.Unlock() m[i] = i * 2 }(i) } // 等待所有goroutine完成 for i := 0; i < 5; i++ { go func(i int) { mu.Lock() defer mu.Unlock() fmt.Println(m[i]) }(i) }}
package mainimport ( "fmt" "sync")func main() { var mu sync.RWMutex m := make(map[int]int) // 启动多个goroutine并发写入map for i := 0; i < 5; i++ { go func(i int) { mu.Lock() defer mu.Unlock() m[i] = i * 2 }(i) } // 启动多个goroutine并发读取map for i := 0; i < 5; i++ { go func(i int) { mu.RLock() defer mu.RUnlock() fmt.Println(m[i]) }(i) }}
package mainimport ( "fmt" "sync")func main() { var m sync.Map // 启动多个goroutine并发写入map for i := 0; i < 5; i++ { go func(i int) { m.Store(i, i*2) }(i) } // 启动多个goroutine并发读取map for i := 0; i < 5; i++ { go func(i int) { if value, ok := m.Load(i); ok { fmt.Println(value) } }(i) }}
context 是Go语言标准库中的一个包,用于在多个goroutine之间传递上下文信息和取消信号。它的设计旨在解决在并发环境中管理请求范围的值、控制goroutine的生命周期以及处理取消请求的问题。context 在处理HTTP请求、数据库查询、RPC等场景中非常有用。
原理
context 的核心概念是创建一个上下文(context)对象,它包含了一个取消通道(Done)、截止时间(Deadline)、上下文值(Value)等信息。当需要在多个goroutine之间传递上下文信息或取消请求时,可以将这个上下文对象传递给相关的goroutine,从而实现跨goroutine的信息传递和控制。
使用场景
控制goroutine的生命周期: context 可以用于在父goroutine中控制子goroutine的生命周期。当父goroutine取消上下文时,所有从该上下文派生的子goroutine都会收到取消信号并退出。
package mainimport ( "context" "fmt" "time")func worker(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("Worker: Context canceled") return default: fmt.Println("Worker: Working...") time.Sleep(1 * time.Second) } }}func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx) time.Sleep(3 * time.Second) cancel() // 取消上下文,停止worker time.Sleep(1 * time.Second)}
传递请求范围的值: context 可以用于在多个goroutine之间传递请求范围的值,如请求ID、用户信息等。这些值可以在整个请求范围内传递,而不需要在每个函数参数中传递。
package mainimport ( "context" "fmt")func logRequestID(ctx context.Context) { if reqID, ok := ctx.Value("requestID").(string); ok { fmt.Println("Request ID:", reqID) } else { fmt.Println("Request ID not found") }}func main() { ctx := context.WithValue(context.Background(), "requestID", "12345") logRequestID(ctx)}
处理超时和取消: context 可以用于设置超时和处理取消请求。通过设置截止时间,可以确保某个操作在指定的时间内完成,否则会自动取消。
package mainimport ( "context" "fmt" "time")func main() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() select { case <-time.After(3 * time.Second): fmt.Println("Operation completed") case <-ctx.Done(): fmt.Println("Operation canceled or timed out") }}
context 是Go语言中处理并发操作的强大工具,可以用于控制goroutine的生命周期、传递请求范围的值以及处理超时和取消请求。根据具体的应用场景,可以使用不同的context类型,如context.Background()、context.WithCancel()、context.WithTimeout()等。这样可以确保Go程序在并发操作中更加健壮和可控。
IT技术栈:程序员面试宝典之Golang的Channel(管道)使用与原理
IT技术栈:程序员面试宝典之Golang的GMP模型
IT技术栈:程序员面试宝典之Golang的GC机制
package mainimport "fmt"func multiReturn() (int, string) { return 42, "Hello, World!"}func main() { // 调用 multiReturn 函数并获取返回值 result1, result2 := multiReturn() // 处理返回值 fmt.Println("Result 1:", result1) fmt.Println("Result 2:", result2)}
在这个示例中,multiReturn 函数返回两个值:一个整数和一个字符串。当 multiReturn 被调用时,这两个返回值按顺序存储在栈帧中。然后,调用方函数 main 通过多重赋值操作从栈帧中读取这两个返回值,并进行后续的处理。
总的来说,Go语言的多返回值原理是基于栈帧的机制,它允许函数返回多个值,并由调用方函数负责读取和处理这些返回值。这种机制使得Go语言可以方便地返回多个相关的值,例如错误信息和结果,而不需要使用额外的数据结构来传递。
【申明:文章部分图片来源于网路,侵权,删除】