65.9K
CodeProject 正在变化。 阅读更多。
Home

Go 快速概览。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (7投票s)

2016年12月14日

CPOL

12分钟阅读

viewsIcon

13368

本文将快速概览 Google 的 Go 编程语言。

GO!

目录

  1. 引言
  2. Go "Hello, World!"
  3. 注释
  4. 分号
  5. 函数
  6. 变量
  7. 内置类型
  8. 用户自定义类型
  9. 数组
  10. 切片
  11. 常量
  12. 指针
  13. 语句
  14. 匿名函数
  15. 方法
  16. 参考文献
  17. 历史

引言

Go 是一门通用的编程语言,由 Google 开发。它是一个开源项目,旨在提高程序员的生产力。Go 语言非常富有表现力且简洁。其并发编程能力有助于程序员编写充分利用多核和网络机器的程序。Go 语言是静态类型且是编译型编程语言。Go 语言能够快速地将程序编译成机器码。它还提供了垃圾回收机制。垃圾回收机制使得编写并发代码更加容易。除此之外,Go 语言还拥有一个非常丰富的标准库。

Go "Hello, World!"

让我们看下面这个用 Go 编写的“Hello, World!”程序

package main

import "fmt"

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

从这个 hello world 程序中,我们得到了以下内容:

  1. 一个名为 main 的包。但它到底是什么?

    是的,每个 Go 程序都由包组成。我们可以编写库或可执行程序。可执行程序属于 main 包。这意味着可执行程序从 main 包开始运行。因此,在 Go 程序开头,需要以如下方式指定包名:

    package main

    这将告诉编译器“这是一个可执行程序”。不是库

    但是如果我们编写一个库,意味着我们正在编写一个新的包,并为该包命名。假设我们的新库/包名为‘simplelib’。如果是这样,那么在我们新库/包的每个源文件开头,必须包含以下行:

    package simplelib

    这就是我们告诉编译器该源文件属于 ‘simplelib’ 包的方式。

  2. 我们发现了一个 import 语句,它导入了名为“fmt”的内容。

    import 语句用于将外部 Go 包导入到另一个 Go 包/程序中。与 C 的 include 预处理器指令非常相似。

    import 语句的用法如下:

    import "package_name"

    或者(导入多个包时推荐的语法)

    import (
        "package_name"
        "package_name2"
        "package_name3"
        "package_nameN"
    )

    在 hello world 示例中,导入了一个名为 fmt 的包。fmt 包实现了格式化 I/O,其函数类似于 C 的printfscanf。格式“动词”源自 C,但更简单。因此,当与控制台窗口交互时,此包是必需的。

  3. 一个用户定义的名为 main 的函数。

    此函数类似于 C 的入口点 main。Go 程序的代码执行从该函数开始。

  4. main 函数内部,调用了另一个名为 Println 的函数来打印“Hello, World!”消息。

    Println 函数用于在控制台窗口打印内容。此函数属于 fmt 包,这就是为什么在 hello world 程序开头导入此包的原因。

    Go 的 Println 非常有用。我们可以使用此函数打印字符串或 int 等值。甚至可以打印更复杂的对象,如数组。我们不需要循环来打印数组的元素。Println 函数在打印完所有值后会自动插入一个换行符。

现在,我们将介绍 Go 编程语言的一些特性。

注释

我们程序员使用注释来描述代码段。这使得代码更易于理解和阅读。Go 中主要有两种注释形式:

  • 单行注释
  • 多行注释

以下是单行注释的示例:

// This is a single line comment.

以下是多行注释的示例:

/* This is a multi line comment.
   This is the second line of this multi line comment.
 */

现在我们可以看到,在 Go 中进行注释类似于 C++、C#、Java 等。

分号

与 C、C++、C#、Java 等编程语言不同,Go 语言不需要使用分号来表示语句的结束。Go 的词法分析器使用一个简单的规则来推断语句的结束。

惯用的 Go 程序仅在 for 循环子句等地方使用分号,以分隔初始化器、条件和继续元素。它们也用于在一行中分隔多个语句。

函数

在 Go 中,函数创建的语法如下:

func function_name(input_parameter_list) (return_type_list) {
    function_stataments
}

我们可以看到,函数创建以 func 关键字开头。然后我们必须指定函数的名称。函数名应该是任何有效的 Go 标识符。之后,我们放入所有输入参数。如果函数在参数列表中有多个参数,则需要用逗号分隔它们。并且,参数必须用括号 ( ) 括起来。然后我们指定函数的返回类型。但是,如果我们的函数不需要返回值,则可以省略返回类型。

例如

func sing() {
    // I'm singing!
}

另一个用于对两个数字进行加法并返回整数值的函数示例:

func add(x int, y int) int {
    return x + y
}

与 C 不同,Go 中的函数可以有多个返回值,这是一个内置功能。在这种情况下,它需要围绕返回类型使用括号 (),并且所有返回类型都必须用逗号分隔。

例如

func getall() (int, string) {
    return 10, "that's all!"
}

当我们调用函数时,多个返回值按如下方式获取(此示例代码使用了短变量声明形式。我们将在“短变量声明”部分讨论它)

i, s := getall()

现在变量 i 包含第一个返回值(即10),变量 s 包含第二个返回值(一个字符串,即“就是这些!”)。

在 Go 中,这种多返回值功能通常用于从函数返回结果和错误值。

入口点

一个名为 main 的全局函数被识别为程序的入口点。

func main() { 
    // function’s body
}

请注意,Go 的 main 函数没有参数(如 C 中的argc, argv)来访问命令行参数。但是,一个名为 os 的 Go 包为此问题提供了一个解决方案。os 包的 Args 变量提供了对原始命令行参数的访问。例如:

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println(len(os.Args), os.Args)
}

变量

Go 中的变量声明语法如下:

  1. var 标识符列表 类型
  2. var 标识符列表 = 初始化表达式列表
  3. var 标识符列表 类型 = 初始化表达式列表

示例

var i int
var a, b, c int
var k = 0
var x, y int = 12, 9

我们可以看到,Go 中的变量声明以 var 关键字开头。而且,并非总是需要显式指定类型。我们可以让编译器从初始化表达式推断变量的类型。这就像我们在 C++11 中使用 auto 关键字所做的那样。

短变量声明

短变量声明的语法如下,它不需要在声明的开头使用 var 关键字,并自动推断变量的类型:

identifier_list := initializer_expression_list

示例

i := 80

或者

a, b, c := 10, 20, 30

这使得编码更容易,并有助于更快地编写代码。

请注意,短变量声明形式只能在函数内部使用。您不能在全局范围内使用它。

内置类型

以下是 Go 中所有基本/内置类型的列表:

  • bool - 包含两个预定义常量 truefalse
  • string - 表示字符序列
  • int8 - 有符号 8 位整数(-128 到 127)
  • int16 - 有符号 16 位整数(-32768 到 32767)
  • int32 - 有符号 32 位整数(-2147483648 到 2147483647)
  • int64 - 有符号 64 位整数(-9223372036854775808 到 9223372036854775807)
  • int - 有符号 32 位或 64 位整数(取决于系统)
  • uint8 - 无符号 8 位整数(0 到 255)
  • uint16 - 无符号 16 位整数(0 到 65535)
  • uint32 - 无符号 32 位整数(0 到 4294967295)
  • uint64 - 无符号 64 位整数(0 到 18446744073709551615)
  • uint - 无符号 32 位或 64 位整数(取决于系统)
  • uintptr - 无符号 32 位或 64 位整数(取决于系统)
  • byte(uint8 的别名)
  • rune(int32 的别名)
  • float32 - IEEE-754 32 位浮点数
  • float64 - IEEE-754 64 位浮点数
  • complex64 - 实部和虚部为 float32 的复数
  • complex128 - 实部和虚部为 float64 的复数

注意:intuintuintptr 类型在 32 位系统上通常是 32 位宽,在 64 位系统上是 64 位宽。当您需要整数值时,应使用 int,除非您有特定原因要使用特定大小或无符号整数类型。

用户自定义类型

Go 中主要有两种用户自定义类型:

  1. 结构
  2. 接口

结构

结构体允许程序员将几个相同或不同类型的变量组合在一起。

在 Go 中,需要使用 typestruct 关键字来创建结构体。创建结构体的语法如下:

type identifier struct {
    structure_members
}

包含四个相同类型变量的结构体示例:

type Rectangle struct {
    left int
    top int
    right int
    bottom int
}

包含三个不同类型变量的结构体示例:

type Person struct {
    name string
    age int
    income float32
}

结构体的使用:

var rect Rectangle

rect.left = 20
rect.top = 10
rect.right = 50
rect.bottom = 60

fmt.Println("left: ", rect.left)
fmt.Println("top: ", rect.top)
fmt.Println("right: ", rect.right)
fmt.Println("bottom: ", rect.bottom)

Go 允许在变量声明期间初始化结构体的成员:

var r = Rectangle{20, 10, 50, 60}

接口

Go 中接口的语法如下:

type identifier interface {
    interface_methods
}

示例

type Person interface {
    Name() string
    Age() int
    Flee()
    Die()
}

使用接口的另一个实际示例:

package main

import "fmt"

type Greeting interface {
    Say()
}

type EngGreeting struct {
    // Implement the Greeting interface.
    Greeting
}

type BanGreeting struct {
    // Implement the Greeting interface.
    Greeting
}

func (e * EngGreeting) Say() {
    // Implement method Say for EngGreeting struct.
    fmt.Println("Hello Forhad Reja")
}

func (e * BanGreeting) Say() {
    // Implement method Say for BanGreeting struct.
    fmt.Println("ওহে ফরহাদ রেজা")
}

// This function doesn't care whether 'g' will be a Bengali or English greeting ;)
func say(g Greeting) {
    g.Say()
}

func main() {
    // create an instance of EngGreeting.
    e := new(EngGreeting)
    say(e)

    // create an instance of BanGreeting.
    b := new(BanGreeting)
    say(b)
}

数组

Go 中数组的创建语法与 C++、C# 等编程语言略有不同。方括号放在元素类型的前面:

[ array_length_expression ] element_type

数组长度表达式必须是大于零的整数常量。

创建单维和多维数组的示例:

var ar [10]int
var mat [4][4]int
var big [10][10][10]int

数组在创建时可以初始化:

var ar = [3]int{12, 10, 32}

让我们看一个给数组单个元素赋值的示例。请注意,数组索引从 0 开始,最后一个索引比数组总长度小 1:

ar[0] = 5
mat[0][0] = 12

访问数组的单个元素:

fmt.Println(ar[0], mat[0][0])

切片

Go 中没有内置的动态数组。但等等……有切片。

切片允许我们动态地增加数组的大小。切片可以使用 Go 中的以下语法创建:

[  ] element_type

例如

var da []int = []int{9, 8, 7}

fmt.Println(da)

输出:

[9 8 7]

Go 提供了内置函数来对切片/动态数组执行操作。以下是使用内置 append 函数向数组添加新项的示例:

da = append(da, 4)
fmt.Println(da)

现在输出:

[9 8 7 4]

常量

常量是固定值,在程序执行期间无法更改。常量声明以 const 关键字开头。语法类似于变量声明的语法,只是将 var 关键字替换为 const 关键字。

const Pi float64 = 3.14159265358979323846
const zero = 0.0
const (
	size int64 = 1024
	eof        = -1
)
const a, b, c = 3, 4, "foo"
const u, v float32 = 0, 3

指针

与 C 一样,Go 支持指针。我们可以创建可以保存另一个变量地址的指针变量。通过指针变量,我们可以为原始变量设置值。使用 ampersand & 运算符获取变量的地址,并使用星号 * 运算符进行解引用。

要创建具有显式类型的指针变量,必须将“*”符号放在类型名称的前面。

创建具有显式类型的指针变量的示例:

var p *int

用法

// create a value type variable
var n = 42
// let’s get the address of the variable ‘n’
p = &n
// now, through the pointer variable ‘p’, assign 10 to the variable ‘n’
*p = 10

现在,如果我们打印变量 n 的值,我们将得到 10

我们还可以使用短变量声明形式创建指针:

p := &n

需要注意的一点是,Go 没有指针算术。这意味着您不能简单地执行 p++p += 1 等操作。

因此,以下语句:

p++

将生成此错误:

invalid operation: p++ (non-numeric type *int)

语句

Go 中有许多种类的语句。我们将讨论其中的一些。

  1. If/Else 语句

    if condition_expression {
    	// if body
    } else {
    	// else body
    }

    if/else 语句基于条件工作。如果给定的条件表达式求值为 true,则执行“if”块的代码,否则,如果存在,则执行“else”块的代码。

    示例

    if 7%2 == 0 {
        fmt.Println("7 is even")
    } else {
        fmt.Println("7 is odd")
    }

    可以不带“else”使用“if”。例如:

    if age == 12 {
        fmt.Println("too young")
    }

    请注意,在 Go 中不需要像 C++、C#、Java 等编程语言中那样在条件周围加上括号。而且,Go 中没有三元运算符。即使是基本条件,您也必须使用完整的 if 语句。

  2. For 语句

    for 是 Go 唯一的循环语句。Go 中没有 while, do-while, foreach 等循环语句。尽管如此,Go 的 for 语句可以涵盖所有这些。

    以下是 for 语句最简单的形式,其工作方式类似于 while 循环语句:

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

    经典的初始化/条件/递增 for 循环:

    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }

    这是 for 语句的另一种形式,带有 range 子句。此形式用于迭代切片或映射:

    ar := []int{1, 2, 4, 8, 16, 32, 64, 128}
    
    for i, v := range ar {
       fmt.Printf(i, v)
    }

    当遍历切片时,每次迭代返回两个值。第一个是索引,第二个是该索引处元素的副本。

  3. Switch 语句

    Go 的 switch 语句非常类似于 C++ 的 switch 语句,不同的是每个 case 主体都会自动中断。无需显式放置 break 语句。

    switch age {
        case 10:
            fmt.Println("Kid")
        case 16:
            fmt.Println("Teen")
        default:
            fmt.Printf("Why are you?")
    }

    尽管如此,可以使用 fallthrough 语句从一个 case 贯穿到下一个 case。

    switch age {
        case 8:
            fallthrough
        case 10:
            fmt.Println("Kid")
        case 16:
            fmt.Println("Teen")
        default:
            fmt.Println("Why are you?")
    }
  4. Defer 语句

    defer 语句用于调用一个函数,该函数的执行不会立即发生。而是将执行推迟到包含该函数的函数返回时。defer 语句通常用于简化执行各种清理操作的函数。

    让我们看一个 defer 语句如何工作的示例:

    package main
    
    import "fmt"
    
    func main() {
        defer fmt.Println("Hoorraa!… I’m deferred!")
        defer fmt.Println("Yeah… I’m also deferred!")
    
        fmt.Printf("Stop deferring!")
        return
    }

    输出如下:

    Stop deferring!
    Yeah… I’m also deferred!
    Hoorraa!… I’m deferred!

    现在我们可以看到执行顺序完全颠倒了。这就是 defer 语句的工作原理。当包含的函数返回时,最后延迟的函数调用会先执行。

    另一个例子

    func main() {
        for i := 0; i <= 3; i++ {
            defer fmt.Print(i)
        }
    }

    输出:

    3 2 1 0

匿名函数

Go 语言支持匿名函数。当我们想创建没有名字的函数时,匿名函数很有用!它们通常被称为 lambda 函数或简称为 lambda

示例

package main

import "fmt"

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

示例 2

// put a function into variable ‘greeting’
greeting := func() {
    fmt.Println("Hello, World!")
}

// let’s invoke the function
greeting()

闭包

Go 的匿名函数可以形成闭包。闭包是一个引用其外部作用域变量的函数值。该函数可以访问并赋值给引用的变量;从这个意义上说,该函数与变量“绑定”。

示例

package main

import "fmt"

func adder() func(int) int {
	num := 0
	return func(x int) int {
		num += x
		return num
	}
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(pos(i), neg(-i))
    }
}

方法

Go 没有 。但是,我们可以在 struct 类型上定义方法。方法本质上是一个带有特殊接收者参数的函数。接收者参数出现在其自身的参数列表中,位于 func 关键字和方法名之间。

在下面的示例中,我们将为 Rectangle 结构体实现两个名为 widthheight 的方法:

package main

import "fmt"

type Rectangle struct {
    left int
    top int
    right int
    bottom int
}

func (r *Rectangle) width() int {
	return r.right - r.left
}

func (r Rectangle) height() int {
	return r.bottom - r.top
}

func main() {
    r := Rectangle{20, 10, 50, 50}
    fmt.Println("width: ", r.width())
    fmt.Println("height: ", r.height())
}

输出:

width: 30
height: 40

我们可以使用值类型或指针类型作为接收者参数。Go 会自动处理方法调用中值和指针之间的转换。不过,您可能希望使用指针接收者类型来避免方法调用时的复制,或者允许方法修改接收结构体的数据。

参考文献

历史

  • 第一个版本
© . All rights reserved.