Go 快速概览。






4.40/5 (7投票s)
本文将快速概览 Google 的 Go 编程语言。
目录
引言
Go 是一门通用的编程语言,由 Google 开发。它是一个开源项目,旨在提高程序员的生产力。Go 语言非常富有表现力且简洁。其并发编程能力有助于程序员编写充分利用多核和网络机器的程序。Go 语言是静态类型且是编译型编程语言。Go 语言能够快速地将程序编译成机器码。它还提供了垃圾回收机制。垃圾回收机制使得编写并发代码更加容易。除此之外,Go 语言还拥有一个非常丰富的标准库。
Go "Hello, World!"
让我们看下面这个用 Go 编写的“Hello, World!”程序
package main import "fmt" func main() { fmt.Println("Hello, World!") }
从这个 hello world 程序中,我们得到了以下内容:
-
一个名为 main 的包。但它到底是什么?
是的,每个 Go 程序都由包组成。我们可以编写库或可执行程序。可执行程序属于 main 包。这意味着可执行程序从 main 包开始运行。因此,在 Go 程序开头,需要以如下方式指定包名:
package main
这将告诉编译器“这是一个可执行程序”。不是库。
但是如果我们编写一个库,意味着我们正在编写一个新的包,并为该包命名。假设我们的新库/包名为
‘simplelib’
。如果是这样,那么在我们新库/包的每个源文件开头,必须包含以下行:package simplelib
这就是我们告诉编译器该源文件属于 ‘simplelib’ 包的方式。
-
我们发现了一个 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 的
printf
和scanf
。格式“动词”源自 C,但更简单。因此,当与控制台窗口交互时,此包是必需的。 -
一个用户定义的名为
main
的函数。此函数类似于 C 的入口点 main。Go 程序的代码执行从该函数开始。
-
在
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 中的变量声明语法如下:
- var 标识符列表 类型
- var 标识符列表 = 初始化表达式列表
- 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 - 包含两个预定义常量 true 和 false
- 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 的复数
注意:int、uint 和 uintptr 类型在 32 位系统上通常是 32 位宽,在 64 位系统上是 64 位宽。当您需要整数值时,应使用 int,除非您有特定原因要使用特定大小或无符号整数类型。
用户自定义类型
Go 中主要有两种用户自定义类型:
- 结构
- 接口
结构
结构体允许程序员将几个相同或不同类型的变量组合在一起。
在 Go 中,需要使用 type 和 struct 关键字来创建结构体。创建结构体的语法如下:
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 中有许多种类的语句。我们将讨论其中的一些。
-
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 语句。
-
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) }
当遍历切片时,每次迭代返回两个值。第一个是索引,第二个是该索引处元素的副本。
-
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?") }
-
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
结构体实现两个名为 width
和 height
的方法:
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 会自动处理方法调用中值和指针之间的转换。不过,您可能希望使用指针接收者类型来避免方法调用时的复制,或者允许方法修改接收结构体的数据。
参考文献
历史
- 第一个版本