跳至主要內容

Golang 初探

chenxi编程Golang大约 6 分钟

因为自己是一名 Javaer,所以会把重点放在这两门语言不同的地方上。

Golang 初探

1 变量与常量

1.1 变量

Golang 是强类型的,Golang 中定义变量的方式和 Java 有不小的差别,倒是和 JS 有一些类似,例:

var x int // 定义一个整型变量,值为 0(默认值)
var y = 42 // 根据右侧值推导类型,这里推导出 int 类型
var z int = 100 // 显式指定类型
w := 88 // 简洁方式,但仅能用在函数体内

// 批量定义变量
var (
    x int    = 10
    y string = "chenxi"
    z bool   = true
)

这么多定义变量的方式,如何选择?

  • 函数内部局部变量,使用 := 简洁高效。
  • 全局变量,使用 var 显式指定类型。
  • 一次性定义多个变量,使用批量声明。

另外,Golang 中定义局部变量后,就必须使用,否则会报错。

1.2 常量

Golang 中的常量关键字为 const,声明方式类似于将声明变量的 var 替换为 const,就不再赘述。

但 Golang 中的常量中有个比较有意思的东西:iotaiota 是 Golang 中提供的常量关键字,一般用在使用 const() 批量定义常量时,它会在 const 关键字出现时被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次,例:

const (
    a = iota
    b
    c
    d
)
fmt.Println(a, b, c, d) // 0 1 2 3

const (
    x = iota
    y = 3
    z = iota
)
fmt.Println(x, y, z) // 0 3 2

2 函数

Golang 中的函数定义:

func function_name([parameter list]) [return_types] {
   函数体
}

Golang 中的函数可以有多个返回值:

package main

import "fmt"

func swap(x, y string) (string, string) {
   return y, x
}

func main() {
   a, b := swap("Google", "Runoob")
   fmt.Println(a, b)
}

Golang 中的函数可以作为参数传递:

type fc func() string

func func1() string {
    return "我是 func1"
}

func func2(f fc) string {
    return f()
}

func main() {
    f := func1
    fmt.Println(func2(f))
}

Golang 中的闭包函数,即在一个函数中定义一个新函数,函数内部的函数就称为闭包函数,闭包函数能访问外部函数的局部变量,且能保存外部函数的变量状态,算是 Golang 的特色了,例:

func outer() func() int {
    x := 1
    return func() int {
       x++
       return x
    }
}

func main() {
    fc := outer()
    x := fc()
    fmt.Println(x) // 2
    x = fc()
    fmt.Println(x) // 3
}

3 Golang 的面向对象

在 Golang 中没有像 Java 那样的类的概念,或者说 Golang 本身就不是一门面向对象的语言,但在 Golang 中仍可进行面向对象的编程。

3.1 封装

在 Golang 中使用结构体 struct 来组织和封装数据,虽然不能在结构体中直接添加方法,但可以通过为函数定义接收者,来为结构体添加方法,例:

// 定义一个结构体类型
type Person struct {
    Name string
    Age  int
}

// 给结构体定义一个方法
func (p Person) Greet() {
    fmt.Println("Hello, my name is", p.Name)
}

func main() {
    // 创建结构体实例
    p := Person{Name: "Alice", Age: 30}
    p.Greet()  // 调用结构体的方法
}

同时,对于结构体中的属性,若是首字母大写,则可以被其它包进行访问,反之若是首字母小写,则仅能在定义的包内进行访问。

3.2 继承

Golang 中没有像 Java 那样使用 extends 实现继承的概念,而是通过组合(在一个结构体中嵌入另一个结构体)的方式来实现继承:

type Animal struct {
    Name string
}

func (a Animal) Speak() {
    fmt.Println(a.Name, "makes a sound")
}

// 通过嵌套 Animal 类型实现组合
type Dog struct {
    Animal // 嵌套 Animal
    Breed  string
}

func main() {
    d := Dog{
       Animal: Animal{Name: "Buddy"},
       Breed:  "Golden Retriever",
    }
    d.Speak() // 调用 Animal 的 Speak 方法
}

3.3 多态

Golang 中是有着接口的,但无需显式的使用 implements,只要定义的结构体实现了接口定义的所有方法,就代表该结构体实现了该接口。

type UserService interface {
    getUserName() string
}

type UserServiceImpl struct {
    
}

func (u UserServiceImpl) getUserName() string {
    return "chenxi" 
}

func main() {
    var userService UserService = UserServiceImpl{}
    fmt.Println(userService.getUserName())
}

4 指针

Golang 中是有着指针的,和 C++ 中的指针有些相似。

func test(a *int) {
    *a = 10
}

func main() {
    a := 20
    test(&a)
    fmt.Println(a) // 10
}

5 数组与切片

Golang 中的数组:

numbers := [5]int{1, 2, 3, 4, 5}
numbers := [...]int{1, 2, 3, 4, 5}  // 数组长度不确定可使用 ...
numbers := [5]int{0: 2, 1: 3}  // 指定数组长度后,可通过指定下标来初始化元素

而 Golang 中的切片则类似于 Java 中的 ArrayList,是动态数组:

slice1 := []int{1, 2, 3}
fmt.Println(slice1, len(slice1), cap(slice1))  // [1 2 3] 3 3
slice1 = append(slice1, 4)
fmt.Println(slice1, len(slice1), cap(slice1))  // [1 2 3 4] 4 6

也可以使用 make 来定义切片,make([]T, length, capacity)

6 Map

Map 也算是各大语言中必备的数据类型了,Go 中的 Map:

// 使用 make 创建 Map
map_variable := make(map[KeyType]ValueType, initialCapacity)

// 使用字面量创建 Map
m := map[string]int{
    "apple": 1,
    "banana": 2,
    "orange": 3,
}

// 基于 key 删除元素
delete(countryCapitalMap, "France")

7 range

Go 中对数组、切片、Map 的遍历,往往会使用到 range,例:

for key, value := range oldMap {
    newMap[key] = value
}

for key := range oldMap

for _, value := range oldMap

如果是遍历数组、切片、字符串,rang 返回的第一个元素是下标,而第二个元素则是下标对应的值。

8 并发

8.1 Goroutine

Goroutine 是轻量级线程,由 Golang 在运行时进行管理,要在 Golang 中使用 Goroutine 非常简单,语法:

go 函数名(参数列表)

8.2 Channel

Channel 用于 Goroutine 之间数据的传递:

ch := make(chan int, 1) // 声明一个通道,缓存为 1
x := 3
ch <- x // 把 x 发送到通道 ch
v := <-ch // 从 ch 接收数据

注意:如果声明的通道不带缓冲区,发送方会阻塞到接收方进行了数据的接收,也就是从 Channel 中发送和接收数据变成了同步的操作。

要遍历 Channel 可使用 rangerange 的停止条件是 Channel 的关闭,所以若是声明通道后,没有进行通道的关闭,则会导致 range 循环无法退出。

同时,也可使用 selcet 用于接收多个通道的数据:

select {
    case <-ch1:
        // 如果通道 ch1 可读取数据,则执行此块
    case ch2 <- value:
        // 如果通道 ch2 可以接收数据,则执行此块
    default:
        // 如果以上两个操作都没有准备好,则执行此块
}

参考资料

Go 语言教程 | 菜鸟教程open in new window

上次编辑于: