Golang语法混淆问题解析

Golang语法混淆问题解析 作为一名Go语言新手,我发现有些语法规则令人困惑,感觉像是“生搬硬套”进来的。

例如,如果导出的函数和类型必须大写,那为什么像“len”这样的内置函数是小写,却仍然能被所有包使用?这难道不是打破规则了吗?

接收器方法的概念感觉像是事后才添加的,目的是在决定不使用类之后,为已声明的类型赋予一些类似“类”的行为。这感觉并不是为了提高代码的可读性而设计的。

也许我的强迫症在这里发作了,但在解决这些困惑之前,我感觉自己完全无法继续学习Go语言了。

4 回复

关于“len”的要点是,如果规则是任何类型或函数必须以大写字母开头才能被视为“公开”,那么为什么内置包是例外?

关于我在接收器上发表的评论:根据Go Playground的说法,“Go没有类。但是,你可以在类型上定义方法”,然后继续定义方法,这些方法是“带有接收器类型的函数”……因此我在评论中进行了类比较。

进一步研究接口等其他特性,感觉这门语言试图在拥有类似类的行为和继承的同时,又回避它。

更多关于Golang语法混淆问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我能想到的所有类C语言都有内置类型。在Python、Java、C、C++、C#等语言中,你不需要导入任何东西就可以使用int类型。像appendcaplen等内置函数与语言中的其他任何函数都不同:它们更像是宏或模板/泛型。编译器将len(mySlice)转换为与len(myMap)不同的操作。你无法自己实现这些函数;你需要编译器的支持。他们本可以将这些内置函数实现在一个包内,就像任何其他类C语言本可以将它们的内置函数放入一个包中一样,但我看不出这样做有什么好处(对我来说,go.Intlang.Intsystem.Int并不比单纯的int更好)。

我不太确定你所说的接收者概念是什么意思;它与类没有任何关系。

内置包是一个例外,因为它是“内置的”。无论你在哪个包中,你都可以直接使用 len(thing) 来调用内置的 len 函数。我关于 int 的观点是,它是一个内置类型,就像 len 是一个内置函数一样。没有内置的 int 类型,你就无法构建自己的 int 类型。len 就是特殊的,就像切片和映射就是特殊的一样,因为它们是语言中唯一的泛型类型。你无法在 Go 中编写一个泛型的栈类型,但你可以在切片之上构建一个栈类型。内置类型是基本的构建模块,你可以在它们之上构建更大的结构。

语言设计者本可以决定不包含内置类型或函数,而是使用 sysgo 等包,这样你就会有像 sys.Intsys.Append 这样的东西,但他们没有这样做。他们也没有为内置类型添加任何方法,这就是为什么是 slice = append(slice, value) 而不是 slice.Append(value)

在没有类的情况下拥有方法并非事后才想到的。这是有意为之。Go 的设计者不想要继承,但他们确实想要多态性。他们想要编译时的类型安全,但他们不希望类型需要列出它们想要实现的每一个接口。他们不想要运算符或函数重载等等。

我在 2016 年开始使用 Go 时并不喜欢它。直到今年,我更喜欢 C# 或 Python。关于 Go 的一个常见抱怨是,该语言似乎对其特性或设计决策持有武断的立场。在某些方面可能确实如此。对我来说,现在我已经习惯了它的怪癖,编写易于阅读、推理和修改的乏味并发代码对我来说更容易了。它也可能让我成为了一个更好的 C# 和 Python 程序员

在Go语言中,导出规则和内置函数的处理确实容易引起混淆,但这是设计上的明确选择。让我通过代码示例来解析你的疑问。

1. 导出规则 vs 内置函数

内置函数如lenappendmake等是语言的一部分,不是包级别的导出标识符。它们由编译器直接处理,不遵循包的导出规则。

package main

import "fmt"

// 包级别的导出函数 - 必须大写
func ExportedFunc() {
    fmt.Println("This is exported")
}

// 包级别的非导出函数 - 小写
func internalFunc() {
    fmt.Println("This is internal")
}

func main() {
    // 内置函数 - 编译器特殊处理
    s := []int{1, 2, 3}
    length := len(s)        // len 是内置函数
    s = append(s, 4)       // append 是内置函数
    
    // 包导出函数
    ExportedFunc()          // ✓ 可以访问
    
    // internalFunc()       // ✗ 编译错误:未导出
}

2. 接收器方法的设计

接收器方法确实是Go实现面向对象的方式,但它是类型系统的自然延伸,不是事后添加的。看这个例子:

package main

import (
    "fmt"
    "math"
)

// 自定义类型
type Point struct {
    X, Y float64
}

// 普通函数
func Distance(p1, p2 Point) float64 {
    dx := p1.X - p2.X
    dy := p1.Y - p2.Y
    return math.Sqrt(dx*dx + dy*dy)
}

// 接收器方法 - 更自然的调用方式
func (p Point) DistanceTo(other Point) float64 {
    dx := p.X - other.X
    dy := p.Y - other.Y
    return math.Sqrt(dx*dx + dy*dy)
}

// 接口实现
type Shape interface {
    Area() float64
}

type Circle struct {
    Point
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func main() {
    p1 := Point{0, 0}
    p2 := Point{3, 4}
    
    // 两种调用方式的对比
    fmt.Println(Distance(p1, p2))      // 函数调用
    fmt.Println(p1.DistanceTo(p2))     // 方法调用 - 更清晰
    
    c := Circle{Point{0, 0}, 5}
    fmt.Printf("Circle area: %.2f\n", c.Area())
    
    // 接口使用
    var s Shape = c
    fmt.Printf("Shape area: %.2f\n", s.Area())
}

3. 类型系统的连贯性

接收器方法与Go的类型系统紧密结合:

package main

import "fmt"

// 基础类型别名
type Meters float64
type Feet float64

// 为类型别名添加方法
func (m Meters) ToFeet() Feet {
    return Feet(m * 3.28084)
}

func (f Feet) ToMeters() Meters {
    return Meters(f / 3.28084)
}

// 指针接收器 - 修改原值
type Counter struct {
    value int
}

func (c *Counter) Increment() {
    c.value++
}

func (c Counter) GetValue() int {
    return c.value
}

func main() {
    // 值类型的方法
    distance := Meters(100)
    fmt.Printf("%.2f meters = %.2f feet\n", 
        distance, distance.ToFeet())
    
    // 指针接收器的方法
    counter := &Counter{value: 0}
    counter.Increment()
    counter.Increment()
    fmt.Printf("Counter value: %d\n", counter.GetValue())
    
    // 值也可以调用指针接收器方法(Go自动转换)
    var c2 Counter
    c2.Increment()  // Go自动转换为 (&c2).Increment()
    fmt.Printf("Counter2 value: %d\n", c2.GetValue())
}

4. 设计哲学的一致性

Go的设计强调显式性和一致性:

package main

import "fmt"

// 清晰的可见性控制
type User struct {
    ID       int      // 导出字段
    Username string   // 导出字段
    password string   // 非导出字段
}

// 导出方法
func (u *User) SetPassword(pwd string) {
    u.password = pwd
}

// 非导出方法
func (u *User) validatePassword(pwd string) bool {
    return u.password == pwd
}

// 工厂函数模式
func NewUser(id int, username string) *User {
    return &User{
        ID:       id,
        Username: username,
    }
}

func main() {
    user := NewUser(1, "john_doe")
    user.SetPassword("secret123")
    
    // user.validatePassword("secret123") // ✗ 编译错误:未导出
    // user.password = "newpass"         // ✗ 编译错误:未导出
}

内置函数的小写是历史遗留和实用性的平衡,而接收器方法是Go类型系统的核心特性。这种设计让Go在保持简单性的同时提供了足够的表达能力。

回到顶部