Golang中实现类似C语言的指针功能

Golang中实现类似C语言的指针功能 在C语言中,我们可以创建一个函数,使其不改变参数中指针所指向的值:

void f(const char *str, int len);

因此,我们看到该函数无法修改数据。这是一个非常有用的特性,因为我们可以向函数传递指针而无需复制数据,并且函数头告诉我们它不会改变数据。在Go语言中,我们可以实现类似的功能吗?

4 回复

好的,谢谢你的回答。

使用不可变数据对我来说似乎非常有用。它可以让代码更容易理解。我发现有一个名为“immutable”的包。有人在实践中使用它吗?

更多关于Golang中实现类似C语言的指针功能的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


源自此讨论串

Go 没有类似 C++ 中用于方法的 const 修饰符。 以这种方式使用的 const 是 C++ 的发明,并且像大多数 C++ 的发明一样,它确实有其好处,但也带来了 很多包袱。不知何故,大多数其他语言没有它也能 很好地运行,而且 Go 似乎很可能也是如此。

Russ

还有这个:

嗯,我个人似乎确实不像你那样重视它。所有的语言设计决策都涉及成本与收益的权衡。 C/C++ 中的 const 限定符带来了实实在在的成本。最终, 语言设计者必须权衡成本与收益并做出 决定。(并且,需要明确的是,我个人对语言规范的 贡献相对较小。)

我认为,对于因数据过大而需要不可变且通过引用传递的场景,已经被决定不应成为一项语言特性。因此,在大多数情况下,我认为人们会直接按值传递这个字符串。在涉及包设计、且该包旨在供其他开发者使用时:如果你想在你的方法中修改我包中的数据,请自便。

我无法确切说明那个包的使用范围(我自己不用,也没在其他地方见过)。当我在Go中看到指针被传递时,通常假设是以下三种情况之一:

  1. 它将要修改我的变量。
  2. 它假设传递的变量会非常大。
  3. 它表明该变量可以是 nil

如果你想明确表示属于哪种情况,我个人可能会直接使用文档说明。以处理大字符串的情况为例:

// LargeString 将用这个大字符串做某事。使用指针是因为假设 v 很大。不会改变 v。郑重承诺。
func LargeString(v *string) { }

再假设我有一个要修改 v 的例子:

// MutateString 将在 v 的末尾添加 "all your base"。
func MutateString(v *string) { }

最后是我们使用指针来区分 nil 和空字符串的情况:

// NilString 将向控制台打印重要信息。
// nil 值将打印 "not logged in",但空字符串将打印 "unknown user"。
func NilString(v *string) { }

这里的核心思想是我们在沟通/记录意图。我不编写C语言程序,但我的理解是,你上面描述的行为也并非完全安全

另一个选择可能是这样的:

// Immutable 表示一个不可变的值
type Immutable[T any] struct {
	value T
}

// 获取存储的值
func (i Immutable[T]) Value() T {
	return i.value
}

// 创建一个新的不可变值
func New[T any](v T) Immutable[T] {
	return Immutable[T]{value: v}
}

这并不完美,但它再次表明了一种意图。你可以看看这篇文章以获取更多想法。

在Go语言中,可以通过使用指针和常量约束来实现类似C语言const指针的功能。虽然Go没有直接的const指针语法,但可以通过以下方式达到类似效果:

1. 使用只读接口

对于切片和映射,可以定义只读接口来限制修改:

type ReadOnlySlice[T any] interface {
    Get(index int) T
    Len() int
}

type readOnlySlice[T any] struct {
    data []T
}

func (r *readOnlySlice[T]) Get(index int) T {
    return r.data[index]
}

func (r *readOnlySlice[T]) Len() int {
    return len(r.data)
}

func NewReadOnlySlice[T any](data []T) ReadOnlySlice[T] {
    return &readOnlySlice[T]{data: data}
}

func processData(data ReadOnlySlice[string]) {
    // 只能读取,不能修改
    for i := 0; i < data.Len(); i++ {
        _ = data.Get(i)
    }
}

2. 使用不可变结构体

通过返回结构体的副本而不是指针:

type ImmutableData struct {
    value string
}

func (d ImmutableData) Value() string {
    return d.value
}

func processImmutable(data ImmutableData) {
    // 只能通过Value()方法读取,无法修改原始数据
    _ = data.Value()
}

3. 对于字符串的特殊情况

Go中的字符串本身就是不可变的:

func processString(s string) {
    // s是字符串的副本,但底层字节数组不会改变
    _ = s[0] // 只能读取
    // s[0] = 'a' // 编译错误:字符串不可变
}

4. 使用函数参数的值传递

对于指针类型,传递指针的副本:

type Data struct {
    value int
}

func readOnlyProcess(d *Data) int {
    // 可以读取但不能修改(通过约定)
    return d.value
}

// 或者使用闭包封装
func createReader(d *Data) func() int {
    return func() int {
        return d.value
    }
}

5. 使用unsafe.Pointer进行类型转换(不推荐)

仅在必要时使用,这会绕过类型安全检查:

import "unsafe"

func readOnlyUnsafe(p unsafe.Pointer, size int) {
    // 通过unsafe.Pointer读取数据
    data := *(*[10]byte)(p)
    _ = data
}

在实际Go代码中,通常通过接口设计和代码约定来实现类似C语言const指针的功能,而不是依赖语言级别的强制约束。

回到顶部