Go(又称Golang)是Google的Robert Griesemer,Rob Pike及Ken Thompson开发的一种静态强类型、编译型语言。Go语言语法与C相近,但功能上有内存安全,GC(垃圾回收),结构形态及CSP-style并发计算等特点。Go语言是一种功能强大、高效、可靠、易学的编程语言,特别适用于高性能、高并发的分布式系统领域,也适用于Web开发、云计算、大数据等领域。

作为一种新兴的编程语言,Go语言被设计成一门应用于搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

Go语言的特点包括简单、可靠和高效。它可以在不损失应用程序性能的情况下降低代码的复杂性。Go语言的类型系统没有层级,因此用户不需要在定义类型之间的关系上花费时间,这样感觉起来比典型的面向对象语言更轻量级。Go语言完全是垃圾回收型的语言,并为并发执行与通信提供了基本的支持。按照其设计,Go语言打算为多核机器上系统软件的构造提供一种方法。

此外,Go语言具有跨平台的特性,可以在不同的操作系统上编译和运行。自2009年发布以来,Go语言已经得到了广泛的应用和发展,拥有一个活跃的社区和丰富的生态系统,提供了许多优秀的库和工具,使得开发者可以更加高效地构建应用程序。

Go官方网址:The Go Programming Language (google.cn)

菜鸟教程:Go 语言教程 | 菜鸟教程 (runoob.com)

安装

Windows

Linux

第一个Go程序

新建一个文件,命名为hello.go,将如下代码添加进该文件中:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

保存,进入命令行,进入到hello.go所在的文件夹,输入如下代码编译执行:

go run hello.go

如果你想要编译这个程序为一个可执行文件,可以使用如下命令:

go build hello.go

执行成功后,会生成一个与你的系统架构相对应的可执行文件(在Windows上是hello.exe,在Unix-like系统上是hello)。然后你可以直接运行这个可执行文件来看到"Hello, World!"的输出。

注释

在Go语言中,注释是用来解释代码的文本,它不会被编译器执行。注释对于其他阅读你代码的人来说是非常有帮助的,因为它们可以解释代码的目的、用法或者任何特殊的实现细节。

使用注释的最佳实践包括:

  • 解释代码的目的和功能,特别是对于复杂的算法或逻辑。

  • 注释应该清晰、简洁,并且与代码保持同步。

  • 避免在注释中重复代码本身已经清晰表达的信息。

  • 使用有意义的注释来解释变量、函数、类或模块的用途。

  • 在修改代码时,确保相关的注释也得到更新,以保持其准确性。

  • 避免在代码中使用过多的注释来“屏蔽”掉不想要的代码行。如果代码不再需要,最好直接删除它。如果暂时需要禁用某些代码,可以考虑使用条件编译或其他技术。

单行注释

单行注释以//开始,并且从//开始到行尾的所有内容都会被编译器忽略

// 这是一个单行注释  
fmt.Println("Hello, World!") // 这也是单行注释

多行注释

多行注释以/*开始,以*/结束。在这两个符号之间的所有内容都会被编译器忽略

/*
这是一个多行注释
它可以跨越多行,用来解释更复杂的代码块或者提供函数的文档说明。
*/
fmt.Println("Hello, World!")

数据类型

整数

  • int8, int16, int32, int64

  • uint8, uint16, uint32, uint64

  • int, uint(机器相关的大小)

  • byte(与uint8相同)

  • rune(与int32相同,表示Unicode字符)

package main

import "fmt"

func main() {
    var a int = 42
    var b int8 = 42
    fmt.Println(a, b)
}

浮点数

  • float32

  • float64

package main

import "fmt"

func main() {
    var a float32 = 3.14
    var b float64 = 2.71828
    fmt.Println(a, b)
}

复数

  • complex64

  • complex128

package main

import "fmt"

func main() {
    var a complex64 = complex(3, 4)
    var b complex128 = complex(6, 8)
    fmt.Println(a, b)
}

布尔

  • bool

package main  
  
import "fmt"  
  
func main() {  
    var a bool = true  
    var b bool = false  
    fmt.Println(a, b)  
}

字符串

  • string

package main

import "fmt"

func main() {
    var a string = "Hello, World!"
    fmt.Println(a)
}

变量

在Go语言中,变量是存储特定类型值的基本单元。你可以声明变量并赋予它们初始值,也可以在之后的代码中修改这些变量的值。变量的声明需要指定变量的名称和类型,或者通过类型推导来推断出变量的类型。

package main

import "fmt"

func main() {
    // 声明一个变量并赋予初始值
    var a int = 42
    fmt.Println(a) // 输出: 42

    // 声明变量时省略类型,Go会根据初始值自动推断类型
    var b = "Hello, World!"
    fmt.Println(b) // 输出: Hello, World!

    // 使用短变量声明方式,只能在函数内部使用
    c := true
    fmt.Println(c) // 输出: true

    // 声明多个变量
    var (
        d int     = 10
        e float64 = 3.14
        f string  = "Go"
    )
    fmt.Println(d, e, f) // 输出: 10 3.14 Go

    // 声明变量但不指定初始值,变量会被初始化为类型的零值
    var g int
    fmt.Println(g) // 输出: 0,因为int类型的零值是0

    // 声明一个切片变量(引用类型),初始值为nil
    var h []int
    fmt.Println(h) // 输出: [],表示一个空的切片

    // 声明一个映射变量(引用类型),初始值为nil
    var i map[string]int
    // 在使用映射之前,需要先使用make函数进行初始化
    i = make(map[string]int)
    i["one"] = 1
    fmt.Println(i) // 输出: map[one:1]
}

常量

在Go语言中,常量是一个在编译时被创建且其值在程序运行期间不能被改变的量。常量可以是数字、字符(rune)、字符串或布尔值。常量使用关键字 const 进行声明。

package main

import "fmt"

func main() {
    // 声明一个常量
    const Pi = 3.14

    // 声明多个常量
    const (
        StatusOK = 200
        StatusNotFound = 404
    )

    // 常量也可以用作枚举
    const (
        Monday = iota + 1  // iota 被重置为0,Monday 将是 1
        Tuesday            // 2
        Wednesday          // 3
        Thursday           // 4
        Friday             // 5
        Saturday           // 6
        Sunday             // 7
    )

    // 使用常量
    fmt.Println("Pi:", Pi)
    fmt.Println("Status OK:", StatusOK)
    fmt.Println("Status Not Found:", StatusNotFound)
    fmt.Println("Today is:", Monday)

    // 常量组
    const (
        a = 1
        b
        c = 2
        d
    )
    fmt.Println(a, b, c, d) // 输出: 1 1 2 2,未明确赋值的常量将使用上一行的值
}

iota 是一个特殊的常量生成器,它只能在 const 声明中使用,并且每次 const 声明块被重新开始时,iota 都会被重置为0。在上面的例子中,我们使用 iota 来生成一周中的天数,从1开始。

常量组是一个便捷的方式来声明多个常量,其中未明确赋值的常量将采用上一个常量的值。在上面的常量组中,bd 没有被明确赋值,因此它们分别采用了 ac 的值。

常量在编译时被确定,因此它们不会占用程序运行时的内存,这有助于提高程序的性能。常量也用于增加代码的可读性和可维护性,因为它们可以为特定的值提供描述性的名称。

条件判断语句

在Go语言中,条件语句用于根据条件执行不同的代码块。最常见的条件语句是ifswitch语句,它允许你根据一个或多个条件的真假来执行相应的代码。

if

下面是if语句的基本语法:

if condition {
    // 条件为真时执行的代码
}

如果还需要在条件不满足时执行其他代码,可以添加else块:

if condition {
    // 条件为真时执行的代码
} else {
    // 条件为假时执行的代码
}

你还可以使用else if来添加更多的条件判断:

if condition1 {
    // 条件1为真时执行的代码
} else if condition2 {
    // 条件2为真时执行的代码
} else {
    // 以上条件都不满足时执行的代码
}

条件可以是任何布尔表达式,比如比较操作符(==!=<><=>=)的结果、逻辑操作符(&&||!)的组合,或者函数调用等。

以下是if的示例:

package main

import "fmt"

func main() {
    x := 10

    if x > 5 {
        fmt.Println("x is greater than 5")
    } else {
        fmt.Println("x is not greater than 5")
    }

    // 使用 else if
    if x > 10 {
        fmt.Println("x is greater than 10")
    } else if x > 5 {
        fmt.Println("x is greater than 5 but not greater than 10")
    } else {
        fmt.Println("x is not greater than 5")
    }
}

switch

switch语句用于基于不同的情况(cases)执行不同的代码块。它类似于其他编程语言中的switch-case结构。

switch variable {
case value1:
    // 当 variable 等于 value1 时执行
case value2:
    // 当 variable 等于 value2 时执行
default:
    // 当没有匹配的情况时执行(可选)
}

switch语句中,variable是要评估的变量,value1value2等是要与variable进行比较的值。如果variable与某个case的值匹配,则执行相应的代码块。如果没有任何casevariable匹配,并且提供了default情况,则执行default代码块。

Go语言中的switch语句还有一些特殊用法,比如不需要显式地写出case后面的值,而是使用条件表达式:

switch {
case condition1:
    // 当 condition1 为真时执行
case condition2:
    // 当 condition2 为真时执行
default:
    // 当所有条件都不为真时执行(可选)
}

在这个形式的switch语句中,没有提供要比较的变量,而是直接在case关键字后面列出了条件。

以下是switch的示例:

package main

import "fmt"

func main() {
    x := 2

    switch x {
    case 1:
        fmt.Println("x is 1")
    case 2:
        fmt.Println("x is 2")
    default:
        fmt.Println("x is not 1 or 2")
    }
}

循环语句

在Go语言中,循环语句用于重复执行一段代码,直到满足特定的条件为止。

for循环

for循环是Go中最常用的循环结构,其语法如下:

for initialization; condition; post {
    // 循环体
}
  • initialization:初始化语句,在循环开始前执行(可选)。

  • condition:循环条件,每次循环迭代前都会检查。如果条件为真(true),则执行循环体;如果为假(false),则退出循环。

  • post:每次循环迭代结束后执行的语句(可选)。

然而,在Go中,initializationpost语句通常是省略的,因为可以在循环之前或之后编写这些逻辑。因此,更常见的for循环形式如下:

for condition {
    // 循环体
}

要创建一个无限循环,可以使用没有条件的for循环:

for {  
    // 循环体  
    // 需要使用 break 语句来退出循环  
}

示例如下:

package main

import "fmt"

func main() {
    // for 循环示例:打印 1 到 5
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
    }

    i := 1
    for i <= 5 {
        fmt.Println(i)
        i++
    }

    // 无限循环示例:打印数字直到遇到 0(使用 break 退出)
    for {
        i := 0 // 假设这里是从某处获取的值,而不是硬编码的 0
        if i == 0 {
            break // 当 i 为 0 时退出循环
        }
        fmt.Println(i) // 这个例子实际上不会打印任何内容,因为 i 立即为 0 导致退出循环
    }
}

for-range

range关键字用于迭代数组、切片、字符串、映射和通道等集合的元素。它返回两个值:索引和该索引对应的值(对于映射,则是键和值)。

for index, value := range collection {
    // 使用 index 和 value
}

如果只关心值而不关心索引,可以使用下划线_来忽略索引:

for _, value := range collection {
    // 使用 value
}

以下是range关键字循环的示例:

package main

import "fmt"

func main() {

    // range 循环示例:迭代切片并打印元素
    numbers := []int{1, 2, 3, 4, 5}
    for index, value := range numbers {
        fmt.Printf("Index: %d, Value: %d\n", index, value)
    }
}

函数

在Go语言中,函数是执行特定任务的代码块,它可以被调用以执行这些任务。函数可以接受参数,并且可以返回结果。在Go中,函数的声明需要指定函数名、参数列表、返回值列表以及函数体。

Go 语言最少有个 main() 函数。在程序执行时会执行main函数中的代码。

函数的基本语法如下:

func functionName(parameter1 type1, parameter2 type2) returnType {
    // 函数体
    // 执行语句
    return value // 返回值
}
  • func 是声明函数的关键字。

  • functionName 是函数的名称,遵循标识符命名规则。

  • parameter1 type1, parameter2 type2 是函数的参数列表,每个参数由参数名和参数类型组成,多个参数之间用逗号分隔。

  • returnType 是函数返回值的类型,如果函数没有返回值,则返回类型为省略。

  • 函数体是包含在大括号 {} 内的代码块,用于实现函数的功能。

  • return 关键字用于从函数中返回值。

下面是一个简单的函数示例,该函数接受两个整数参数并返回它们的和:

package main

import "fmt"

// 声明一个函数,接受两个int类型的参数,返回一个int类型的结果
func add(a int, b int) int {
    // 计算两个数的和
    sum := a + b
    // 返回结果
    return sum
}

func main() {
    // 调用add函数,并将结果打印到控制台
    result := add(5, 3)
    fmt.Println("The sum is:", result)
}

注意:Go语言支持多返回值,返回值之间用逗号分隔。

package main

import "fmt"

// 声明一个函数,返回两个字符串
func getGreetingAndName() (string, string) {
    greeting := "Hello"
    name := "World"
    return greeting, name
}

func main() {
    // 调用getGreetingAndName函数,并接收两个返回值
    greeting, name := getGreetingAndName()
    fmt.Println(greeting, name) // 输出: Hello World
}

你也可以使用命名返回值的方式,这样在函数体中可以更清晰地看到返回值的名称和类型:

package main

import "fmt"

// 声明一个函数,使用命名返回值
func getGreetingAndName() (greeting, name string) {
    greeting = "Hello"
    name = "World"
    return // 这里可以省略返回值,因为它们是命名的,并且在函数体中已经被赋值
}

func main() {
    // 调用getGreetingAndName函数,并接收两个返回值
    g, n := getGreetingAndName()
    fmt.Println(g, n) // 输出: Hello World
}

数组

在Go语言中,数组是一种基本的数据结构,用于存储一系列具有相同类型的元素。数组的长度是固定的,一旦定义,它的大小就不能改变。数组中的每个元素都可以通过索引来访问,索引从0开始。

Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:

var arrayName [size]dataType

其中,arrayName是数组的名称,size是数组的大小(即元素的数量),dataType是数组中元素的类型。

例如,声明一个包含5个整数的数组:

var numbers [5]int

数组初始化

在声明数组时,可以选择性地初始化数组的元素。可以通过以下方式初始化数组:

  • 使用数组字面量初始化:

var numbers = [5]int{1, 2, 3, 4, 5}
  • 通过索引赋值初始化:

var numbers [5]int
numbers[0] = 1
numbers[1] = 2
// ...

访问数组元素

可以通过索引来访问数组中的元素。索引从0开始,到数组长度减1。

var numbers = [5]int{1, 2, 3, 4, 5}
fmt.Println(numbers[0]) // 输出: 1
fmt.Println(numbers[2]) // 输出: 3

数组遍历

可以使用for循环或range关键字来遍历数组中的元素

  • 使用for循环遍历:

var numbers = [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(numbers); i++ {
    fmt.Println(numbers[i])
}
  • 使用range关键字遍历:

var numbers = [5]int{1, 2, 3, 4, 5}
for index, value := range numbers {
    fmt.Println(index, value)
}

切片

在Go语言中,数组切片(slice)是对数组的一个连续引用(或窗口)。切片提供了更为强大、灵活且功能丰富的序列类型,相比于数组,切片在使用上更为便捷。切片会根据需要动态增长和缩小。

切片是对底层数组的抽象,它包含了三个字段:指向底层数组的指针、切片的长度(len)和切片的容量(cap)。长度是切片当前包含的元素个数,容量是从切片的第一个元素到底层数组末尾元素的个数。

定义切片

你可以声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

或使用 make() 函数来创建切片:

var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

这里 len 是数组的长度并且也是切片的初始长度。

切片初始化

s :=[] int {1,2,3}

直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。

s := arr[:]

初始化切片 s,是数组 arr 的引用。

s := arr[startIndex:endIndex]

将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

s := arr[startIndex:]

默认 endIndex 时将表示一直到arr的最后一个元素。

s := arr[:endIndex]

默认 startIndex 时将表示从 arr 的第一个元素开始。

s1 := s[startIndex:endIndex]

通过切片 s 初始化切片 s1。

s :=make([]int,len,cap)

通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。

len() 和 cap() 函数

切片是可索引的,并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

以下为具体实例:

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

以上实例运行输出结果为:

len=3 cap=5 slice=[0 0 0]

指针

在Go语言中,指针是一种特殊类型的变量,用于存储其他变量的内存地址。指针提供了一种直接访问和操作内存中的数据的方式。通过指针,可以间接地访问和操作它所指向的变量。

在Go语言中,指针的使用相对简单且安全。与C/C++中的指针相比,Go语言中的指针不能进行偏移和运算,这有助于减少指针操作可能引发的错误。

要定义一个指针变量,需要使用*符号和类型名称。例如,*int表示指向整数的指针类型。可以使用&操作符获取一个变量的内存地址,并将其赋值给一个指针变量。同样地,可以使用*操作符来访问指针所指向的变量的值。

var value int = 10
var pointer *int // 声明一个指向整数的指针变量

pointer = &value // 将value的内存地址赋值给pointer

fmt.Println("Value:", value)          // 输出变量的值
fmt.Println("Address of value:", &value) // 输出变量的内存地址
fmt.Println("Pointer:", pointer)      // 输出指针变量的值(即内存地址)
fmt.Println("Value through pointer:", *pointer) // 通过指针访问变量的值

// 通过指针修改变量的值
*pointer = 20
fmt.Println("Modified value:", value) // 输出修改后的变量的值

空指针

在Go语言中,空指针是一个指向无效内存地址的指针,其值为nil。空指针在Go中扮演着特殊的角色,用于表示指针不指向任何有效的对象或内存位置。nil在概念上和其它语言的nullNoneNULL一样,都指代零值或空值。

一个指针变量通常缩写为 ptr。

下面是一个关于Go中空指针的示例:

package main

import "fmt"

func main() {
    var ptr *int // 声明一个指向整数的指针,默认值为nil

    fmt.Println("ptr is nil:", ptr == nil) // 输出: ptr is nil: true

    // 尝试访问空指针所指向的值会导致运行时错误(panic)
    // 下面的代码是错误的,不应该尝试解引用一个空指针
    // fmt.Println(*ptr) // 这行代码会引发panic: runtime error: invalid memory address or nil pointer dereference

    // 正确的做法是先给指针分配内存或将其指向一个已存在的变量
    value := 42
    ptr = &value // 将ptr指向value的内存地址

    fmt.Println("ptr is nil:", ptr == nil) // 输出: ptr is nil: false
    fmt.Println("Value through pointer:", *ptr) // 输出: Value through pointer: 42
}

结构体

在Go语言中,结构体(struct)是一种用户定义的数据类型,用于封装一系列相关属性(字段)和它们对应的值。结构体可以表示一个事物的全部或部分属性,允许开发者将相关的数据整合在一起,形成一个有意义的集合。

结构体的定义使用typestruct关键字,具体语法如下:

type 结构体名称 struct {
    字段名1 字段类型1
    字段名2 字段类型2
    // ...
}

其中,结构体名称是用户自定义的名称,用于标识该结构体类型。在同一个包内,结构体名称必须唯一。字段名是结构体中的属性名称,每个字段名在结构体内部必须唯一。字段类型指定了字段的数据类型,可以是Go语言中的基本数据类型、数组、切片、映射等,甚至可以是其他结构体类型或自定义类型。

例如,定义一个表示员工信息的结构体:

type Employee struct {
    FirstName string
    LastName  string
    Age       int
    Salary    float64
}

其中,Employee是结构体的名称,FirstNameLastNameAgeSalary是结构体的字段,分别表示员工的名字、姓氏、年龄和薪水。

结构体实例化后,可以访问其字段并进行操作。例如,创建一个Employee类型的变量并初始化其字段:

var emp Employee
emp.FirstName = "John"
emp.LastName = "Doe"
emp.Age = 30
emp.Salary = 5000.0

或者通过字面量初始化结构体:

emp := Employee{
    FirstName: "John",
    LastName:  "Doe",
    Age:       30,
    Salary:    5000.0,
}

集合

Map 是一种无序的键值对的集合。

Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。

Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

package main

import "fmt"

func main() {
    // 创建一个整数集合
    intSet := make(map[int]bool)
	
	// 使用字面量创建 Map
	m := map[string]int{
	    "apple": 1,
	    "banana": 2,
	    "orange": 3,
	}
	
    // 添加元素到集合中
    intSet[1] = true
    intSet[2] = true
    intSet[3] = true

    // 检查元素是否存在于集合中
    fmt.Println(1, "exists in set:", intSet[1]) // 输出: 1 exists in set: true
    fmt.Println(4, "exists in set:", intSet[4]) // 输出: 4 exists in set: false
}

文章作者: Vsoapmac
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 soap的会员制餐厅
GoLang 个人分享
喜欢就支持一下吧