Skip to content

第一章:Go 语言基础

frp 工具采用 Go 语言开发。个人认为有以下两方面的考虑:

  1. Go 语言语法简单,易于学习;
  2. Go 语言中可以轻易实现高效的并发编程

Example

对于 Java/C++,开发者不仅需要维护线程池,包装一个个的任务,还需要自己去调度线程执行任务并进行上下文切换。相比之下,通过goroutine机制,Go 程序会智能地将goroutine中的任务合理地分配给每个 CPU。Go 语言之所以被称为现代化的编程语言,就是因为它在语言层面已经内置了调度和上下文切换的机制。

本章中,我们将从面向对象编程的角度出发,介绍 Go 语言的基础知识。

Go 语言简介

Go(又称 Golang)是 Google 开发的一种静态强类型编译型并发型,并具有垃圾回收功能的编程语言。

Go 语言中的面向对象

Go 语言是面向对象的编程语言吗?官方回答:“是也不是”。

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

Jaana Dogan 在《The Go type system for newcomers》一文中给出的观点是: Go is considered as an object-oriented language even though it lacks type hierarchy, 即“Go被认为是一种面向对象的语言,即使它缺少类型层次结构”。

尽管 Go 语言没有类和继承的概念,但是通过结构体接口的组合,依然以自己的方式实现了 面向对象编程中封装继承多态的特性。

封装

Go 语言中没有class类型,但是可以通过struct类型实现封装, 同时,我们也可以为struct类型定义方法(method)。

下面例子中定义了一个名为Pointstruct类型,它拥有两个字段xy,以及一个导出 方法Length()

package main

// 定义了一个 Point 类型
type Point struct {
    x, y float64
}

// 定义了一个方法 Length(),它属于 Point 类型
func (p Point) Length() float64 {
    return math.Sqrt(p.x * p.x + p.y * p.y)
}

func main() {
    // 创建一个 Point 类型的实例,并调用 Length() 方法
    p := Point{x: 3, y: 4}
    fmt.Println(p.Length())
}

与经典面向对象语言声明类的方法不同,Go 方法声明并不需要放在声明struct型的大括号中。 Length()方法与Point类型建立联系的纽带是一个被称为receiver参数的语法元素。

继承

Go 语言中没有继承,但是可以通过类型嵌入(type embedding)实现类似继承的功能。

下面例子中定义了一个名为ColoredPointstruct类型,它嵌入了Point类型:

package main

import (
    "fmt"
    "math"
)

// 定义 Point 结构体
type Point struct {
    x, y float64
}

// 为 Point 定义一个方法 Length
func (p Point) Length() float64 {
    return math.Sqrt(p.x*p.x + p.y*p.y)
}

// 定义 ColoredPoint 结构体,嵌入 Point
type ColoredPoint struct {
    Point
    Color string
}

func main() {
    // 创建一个 ColoredPoint 实例
    cp := ColoredPoint{
        Point: Point{x: 3, y: 4},
        Color: "red",
    }

    // 访问嵌入的 Point 的字段和方法
    fmt.Println("ColoredPoint coordinates:", cp.x, cp.y)
    fmt.Println("ColoredPoint color:", cp.Color)
    fmt.Println("Length from origin:", cp.Length())
}

通过这种方式,ColoredPoint结构体可以使用Point结构体的所有字段和方法,实现了类似继承的功能。

不过实际 Go 中的这种“继承”机制并非经典面向对象语言中的继承,而是一种组合的方式。 因为ColoredPoint类型并不是Point类型的子类型,它只是包含了Point类型的字段和方法。

多态

经典面向对象的多态实现依托的是类型的层次关系。Go 使用接口(interface)来解锁多态。

下面例子中定义了一个名为Shape的接口,它包含一个Area()方法:

package main

import (
    "fmt"
    "math"
)

// 定义 Shape 接口
type Shape interface {
    Area() float64
}

// 定义 Circle 结构体
type Circle struct {
    radius float64
}

// Circle 实现 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
    return math.Pi * c.radius * c.radius
}

// 定义 Rectangle 结构体
type Rectangle struct {
    width, height float64
}

// Rectangle 实现 Shape 接口的 Area 方法
func (r Rectangle) Area() float64 {
    return r.width * r.height
}

// 定义一个函数,接受 Shape 接口类型的参数
func PrintArea(s Shape) {
    fmt.Println("Area:", s.Area())
}

func main() {
    c := Circle{radius: 5}
    r := Rectangle{width: 3, height: 4}

    // 调用 PrintArea 函数,传入不同的 Shape 实现
    PrintArea(c) // 输出圆的面积
    PrintArea(r) // 输出矩形的面积
}

通过这种方式,PrintArea()函数可以接受任何实现了Shape接口的类型,从而实现多态。 这展示了 Go 语言如何通过接口实现多态,而不需要类型的层次关系。