分类 技术专栏 下的文章

问题:火狐浏览器安装Video Downloadhelper后,提示需要检查合作应用,点击下载无法访问(需要翻墙)

解决办法:
1、登录gitcode https://gitcode.com/open-source-toolkit/a7441/
2、点击右上角下载zip包,或者使用git工具下载到本地
3、解压后选择对应系统版本的pkg包执行安装
4、安装完后重启插件即可

在Go语言的旅程中,&和*这对符号就像是一对形影不离的搭档。它们看似简单,却是理解Go内存管理、性能优化的关键所在。今天,让我们彻底揭开它们的神秘面纱。
符号概览:各司其职的兄弟
&(取地址符):获取变量的内存地址

*(解引用符):通过指针访问或修改目标值

package main

import "fmt"

func main() {
    x := 42
    
    // & 获取地址
    ptr := &x
    fmt.Printf("x的值: %d\n", x)           // 42
    fmt.Printf("x的地址: %p\n", &x)         // 0xc000018030
    fmt.Printf("ptr的值: %p\n", ptr)        // 0xc000018030
    
    // * 解引用
    fmt.Printf("通过ptr访问x: %d\n", *ptr) // 42
    
    // 通过指针修改值
    *ptr = 100
    fmt.Printf("修改后x的值: %d\n", x)     // 100
}

基础篇:理解内存模型
让我们通过一个生动的比喻来理解:

package main

import "fmt"

func main() {
    // 情景:房子与地址
    house := "漂亮的别墅"  // 实际的数据(房子)
    address := &house    // 地址卡片(指针)
    
    fmt.Printf("房子本身: %s\n", house)               // 直接访问房子
    fmt.Printf("地址卡片上写的: %p\n", address)        // 查看地址内容
    fmt.Printf("按地址找到的房子: %s\n", *address)     // 根据地址找到房子
    
    // 通过地址重新装修房子
    *address = "豪华的庄园"
    fmt.Printf("装修后的房子: %s\n", house)           // 房子真的变了!
}

实战篇:函数中的指针传递
场景1:修改函数外部变量

package main

import "fmt"

// 值传递 - 无法影响外部世界
func swapByValue(a, b int) {
    a, b = b, a
    fmt.Printf("函数内交换: a=%d, b=%d\n", a, b)
}

// 指针传递 - 真正改变外部变量
func swapByPointer(a, b *int) {
    *a, *b = *b, *a
    fmt.Printf("函数内交换: a=%d, b=%d\n", *a, *b)
}

func main() {
    x, y := 10, 20
    
    fmt.Printf("交换前: x=%d, y=%d\n", x, y)
    swapByValue(x, y)
    fmt.Printf("值传递交换后: x=%d, y=%d\n", x, y) // 无变化!
    
    swapByPointer(&x, &y)
    fmt.Printf("指针传递交换后: x=%d, y=%d\n", x, y) // 真正交换!
}

场景2:避免大结构体复制

package main

import (
    "fmt"
    "time"
)

type BigData struct {
    Content [10000]int
    Tag     string
}

// 值传递 - 复制整个大结构体,性能杀手!
func processValue(data BigData) string {
    start := time.Now()
    // 模拟处理...
    elapsed := time.Since(start)
    return fmt.Sprintf("值传递处理时间: %v", elapsed)
}

// 指针传递 - 只传递地址,高效!
func processPointer(data *BigData) string {
    start := time.Now()
    // 模拟处理...
    elapsed := time.Since(start)
    return fmt.Sprintf("指针传递处理时间: %v", elapsed)
}

func main() {
    bigData := BigData{Tag: "大型数据集"}
    
    fmt.Println(processValue(bigData))   // 慢,复制开销大
    fmt.Println(processPointer(&bigData)) // 快,只传指针
}

进阶篇:结构体与方法
指针接收者 vs 值接收者

package main

import "fmt"

type BankAccount struct {
    balance float64
    owner   string
}

// 值接收者方法 - 操作副本,不影响原对象
func (b BankAccount) DisplayBalance() {
    fmt.Printf("账户 %s 余额: $%.2f\n", b.owner, b.balance)
}

// 指针接收者方法 - 可以修改原对象状态
func (b *BankAccount) Deposit(amount float64) {
    b.balance += amount
    fmt.Printf("存入 $%.2f, 新余额: $%.2f\n", amount, b.balance)
}

func (b *BankAccount) Withdraw(amount float64) bool {
    if b.balance >= amount {
        b.balance -= amount
        fmt.Printf("取出 $%.2f, 新余额: $%.2f\n", amount, b.balance)
        return true
    }
    fmt.Println("余额不足!")
    return false
}

func main() {
    account := BankAccount{balance: 1000, owner: "Alice"}
    
    // Go自动转换:account.Deposit() 等价于 (&account).Deposit()
    account.DisplayBalance()
    account.Deposit(500)
    account.Withdraw(200)
    account.DisplayBalance()
}

复合字面量的指针语法糖

package main

import "fmt"

type Config struct {
    Host    string
    Port    int
    Timeout int
}

func main() {
    // 传统方式:先创建,再取地址
    config1 := Config{Host: "localhost", Port: 8080}
    configPtr1 := &config1
    
    // Go的语法糖:直接创建指针
    configPtr2 := &Config{
        Host:    "example.com", 
        Port:    443,
        Timeout: 30,
    }
    
    // 访问时都需要解引用
    fmt.Printf("配置1: %+v\n", *configPtr1)
    fmt.Printf("配置2: %+v\n", *configPtr2)
    
    // 数组和切片同样适用
    intSlicePtr := &[]int{1, 2, 3, 4, 5}
    fmt.Printf("切片值: %v\n", *intSlicePtr)
}

总结:何时使用&和*
场景 推荐方式 理由
A、修改函数外部变量 使用指针 允许函数改变外部状态
B、大结构体传递 使用指针 避免复制开销
C、方法需要修改接收者 指针接收者 可以修改结构体字段
D、小型结构体、基本类型 值传递 复制开销小,更安全
E、不需要修改的配置数据 值传递或常量 保证不可变性
F、实现接口方法 根据需求选择 一致性原则

黄金法则
1、默认使用值传递,除非有明确的理由使用指针

2、指针不是性能银弹,对小对象可能适得其反

3、清晰胜于聪明,代码可读性很重要

4、善用Go的自动转换,让编译器帮你处理简单的指针操作

掌握&和*的艺术,意味着你真正理解了Go语言的内存管理和性能特性。希望这篇博客能帮助你在Go的指针世界中游刃有余!

在 Go 语言中,channel 是一种强大的并发原语,它允许不同的 goroutine 之间进行通信和同步。今天我们就来深入探讨 channel 的各个方面,并通过丰富的实例来展示其强大功能。

什么是 Channel?
Channel 是 Go 语言中实现 CSP(Communicating Sequential Processes)并发模型的核心数据结构。它类似于一个类型化的管道,可以在 goroutine 之间安全地传递数据。

Channel 的基本操作
创建 Channel

go
// 无缓冲 channel
ch1 := make(chan int)

// 有缓冲 channel,容量为 10
ch2 := make(chan int, 10)

// 只读 channel
var readOnly <-chan int = ch1

// 只写 channel
var writeOnly chan<- int = ch1

发送和接收数据

go
ch := make(chan int, 1)

// 发送数据
ch <- 42

// 接收数据
value := <-ch
fmt.Println(value) // 输出: 42

Channel 类型详解

  1. 无缓冲 Channel
    无缓冲 channel 是同步的,发送和接收操作会阻塞,直到另一端准备好。

    func unbufferedChannelDemo() {
     ch := make(chan string)
     
     go func() {
         fmt.Println("Goroutine: 准备发送数据")
         ch <- "Hello from goroutine!"
         fmt.Println("Goroutine: 数据发送完成")
     }()
     
     time.Sleep(1 * time.Second)
     fmt.Println("Main: 准备接收数据")
     msg := <-ch
     fmt.Println("Main: 接收到数据:", msg)
     
     time.Sleep(1 * time.Second)
    }
    

    输出:

    Goroutine: 准备发送数据
    Main: 准备接收数据
    Main: 接收到数据: Hello from goroutine!
    Goroutine: 数据发送完成
    
  2. 有缓冲 Channel
    有缓冲 channel 是异步的,只有在缓冲区满时发送才会阻塞,在缓冲区空时接收才会阻塞。

    func bufferedChannelDemo() {
     ch := make(chan int, 3)
     
     // 发送数据不会阻塞,因为缓冲区有空间
     ch <- 1
     ch <- 2
     ch <- 3
     fmt.Println("已发送 3 个数据到缓冲区")
     
     // 如果再发送就会阻塞,因为缓冲区已满
     // ch <- 4  // 这行会阻塞
     
     // 接收数据
     fmt.Println("接收数据:", <-ch) // 1
     fmt.Println("接收数据:", <-ch) // 2
     fmt.Println("接收数据:", <-ch) // 3
    }
    

Channel 的高级用法

  1. 使用 select 处理多个 Channel

    func selectDemo() {
     ch1 := make(chan string)
     ch2 := make(chan string)
     
     go func() {
         time.Sleep(2 * time.Second)
         ch1 <- "来自 ch1 的消息"
     }()
     
     go func() {
         time.Sleep(1 * time.Second)
         ch2 <- "来自 ch2 的消息"
     }()
     
     for i := 0; i < 2; i++ {
         select {
         case msg1 := <-ch1:
             fmt.Println("接收到:", msg1)
         case msg2 := <-ch2:
             fmt.Println("接收到:", msg2)
         case <-time.After(3 * time.Second):
             fmt.Println("超时!")
             return
         }
     }
    }
    
  2. 关闭 Channel 和 range 循环

    func closeChannelDemo() {
     ch := make(chan int, 5)
     
     // 生产者
     go func() {
         for i := 0; i < 5; i++ {
             ch <- i
         }
         close(ch) // 关闭 channel
     }()
     
     // 消费者 - 方法1: 使用 ok 判断
     for {
         if value, ok := <-ch; ok {
             fmt.Printf("方法1 - 接收到: %d\n", value)
         } else {
             fmt.Println("Channel 已关闭")
             break
         }
     }
     
     // 消费者 - 方法2: 使用 range
     ch2 := make(chan int, 3)
     go func() {
         for i := 10; i < 13; i++ {
             ch2 <- i
         }
         close(ch2)
     }()
     
     for value := range ch2 {
         fmt.Printf("方法2 - 接收到: %d\n", value)
     }
    }
    
  3. Channel 作为函数参数

    // 只允许从 channel 读取
    func reader(ch <-chan int) {
     for value := range ch {
         fmt.Println("读取:", value)
     }
    }

// 只允许向 channel 写入
func writer(ch chan<- int, numbers []int) {

for _, num := range numbers {
    ch <- num
}
close(ch)

}

func channelAsParameter() {

ch := make(chan int, 3)
numbers := []int{1, 2, 3, 4, 5}

go writer(ch, numbers)
reader(ch)

}

在windows环境下,我们执行

go get github.com/pilu/fresh 
下载后直接使用
fresh

会报错:无法将“fresh”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

我们应该使用:

fresh.exe
执行该命令后,会在项目目录中出现一个tmp/runner-build.exe

这是fresh才是真正启动起来了

描述:这个错误 panic: reflect: call of reflect.Value.FieldByName on ptr Value 发生在你尝试使用 reflect 包的 FieldByName() 方法时,但传入的 reflect.Value 是一个指针类型(ptr),而不是结构体本身。

v := reflect.ValueOf(s)

name := v.FieldByName("Name") //错误
name := v.Elem().FieldByName("Name") //正确