Golang 教程
什么是围棋?
Go (也称为 Golang)是 Google 开发的一种开源编程语言。它是一种静态类型编译语言。Go 支持并发编程,即允许同时运行多个进程。这是使用通道、goroutine 等实现的。Go 语言具有垃圾收集功能,它本身负责内存管理并允许延迟执行函数。
我们将在本学习 Go 语言教程中学习 Golang 的所有基础知识。
如何下载并安装 GO
步骤1) 在MyCAD中点击 软件更新 https://golang.org/dl/. 下载适用于您的操作系统的二进制文件。
步骤2) Double 单击安装程序,然后单击运行。
步骤3) 点击下一步
步骤4) 选择安装文件夹并单击“下一步”。
步骤5) 安装完成后单击“完成”。
步骤6) 安装完成后,你可以打开终端并输入以下命令来验证
go version
这将显示已安装的 go 版本
你的第一个 Go 程序 – Go Hello World!
创建一个名为studyGo的文件夹。在本Go语言教程中,我们将在此文件夹中创建Go程序。Go文件以扩展名创建 。走. 你可以使用以下语法运行 Go 程序
go run <filename>
创建一个名为 first.go 的文件,将下面的代码添加到其中并保存
package main import ("fmt") func main() { fmt.Println("Hello World! This is my first Go program\n") }
在终端中导航到此文件夹。使用命令运行该程序
先运行.go
您可以看到输出打印
Hello World! This is my first Go program
现在我们来讨论一下上述程序。
包 main – 每个 Go 语言程序都应以包名开头。Go 允许我们在另一个 Go 程序中使用包,因此支持代码可重用性。Go 程序的执行从名为 main 的包内的代码开始。
import fmt – 导入包 fmt。该包实现 I/O 功能。
func main() – 这是程序开始执行的函数。main 函数应始终放在 main 包中。在 main() 下,您可以在 { } 内编写代码。
fmt.Println – 这将通过 fmt 的 Println 函数在屏幕上打印文本。
注意:在本 Go 教程的以下部分中,当您提到执行/运行代码时,这意味着将代码保存在扩展名为 .go 的文件中,并使用语法运行它
go run <filename>
数据类型
类型(数据类型)表示存储在变量中的值的类型、函数返回值的类型等。
Go语言中有三种基本类型
数字类型 – 表示数值,包括整数、浮点数和复数。各种数值类型包括:
int8 – 8 位有符号整数。
int16 – 16 位有符号整数。
int32 – 32 位有符号整数。
int64 – 64 位有符号整数。
uint8——8位无符号整数。
uint16——16位无符号整数。
uint32——32位无符号整数。
uint64——64位无符号整数。
float32 – 32 位浮点数。
float64 – 64 位浮点数。
complex64 – 具有 float32 实部和虚部。
complex128 – 具有 float32 实部和虚部。
字符串类型 – 表示字节(字符)序列。您可以对字符串执行各种操作,如字符串连接、提取子字符串等
布尔类型 – 代表 2 个值,要么真,要么假。
Golang 接口
Golang 接口 是 Type 用来实现对象行为的方法签名的集合。Golang 接口的主要目标是提供带有名称、参数和返回类型的方法签名。声明和实现方法由 Type 决定。Golang 中的接口可以使用关键字“interface”来声明。
变量
变量指向存储某种值的内存位置。类型参数(在下面的语法中)表示可以存储在内存位置的值的类型。
可以使用语法声明变量
var <variable_name> <type>
一旦声明了某种类型的变量,您就可以将该变量分配给该类型的任何值。
您还可以在声明期间使用以下命令为变量赋予初始值
var <variable_name> <type> = <value>
如果你声明变量时指定了初始值,那么 Go 可以根据赋值的类型推断变量的类型。因此,你可以在声明过程中使用以下语法省略类型
var <variable_name> = <value>
另外,你可以使用语法声明多个变量
var <variable_name1>, <variable_name2> = <value1>, <value2>
本 Go 教程中的以下程序包含一些 Golang 变量声明的示例
package main import "fmt" func main() { //declaring a integer variable x var x int x=3 //assigning x the value 3 fmt.Println("x:", x) //prints 3 //declaring a integer variable y with value 20 in a single statement and prints it var y int=20 fmt.Println("y:", y) //declaring a variable z with value 50 and prints it //Here type int is not explicitly mentioned var z=50 fmt.Println("z:", z) //Multiple variables are assigned in single line- i with an integer and j with a string var i, j = 100,"hello" fmt.Println("i and j:", i,j) }
输出将是
x: 3 y: 20 z: 50 i and j: 100 hello
Go 语言还提供了一种简单的方法来声明具有值的变量,即省略 var 关键字
<variable_name> := <value>
请注意您使用了 := 而不是 =。您不能仅使用 := 来为已声明的变量赋值。:= 用于声明和赋值。
创建一个名为assign.go的文件,其中包含以下代码
package main import ("fmt") func main() { a := 20 fmt.Println(a) //gives error since a is already declared a := 30 fmt.Println(a) }
执行 go runassign.go 可以看到结果为
./assign.go:7:4: no new variables on left side of :=
如果声明的变量没有初始值,则数字类型为 0,布尔类型为 false,字符串类型为空字符串
常量
常量变量是那些一旦赋值就不能改变值的变量。Go 编程语言中的常量使用关键字“const”声明
创建一个名为 constant.go 的文件并写入以下代码
package main import ("fmt") func main() { const b =10 fmt.Println(b) b = 30 fmt.Println(b) }
执行 go run constant.go 可以看到结果如下
.constant.go:7:4: cannot assign to b
For 循环示例
循环用于根据条件重复执行语句块。大多数编程语言提供 3 种类型的循环 - for、while、do while。 但是Go编程语言仅支持for循环。
Golang for 循环的语法是
for initialisation_expression; evaluation_expression; iteration_expression{ // one or more statement }
在 Golang 的 for 循环中,initialisation_expression 首先执行(且仅执行一次)。
然后对 evaluation_expression 进行求值,如果为真,则执行块内的代码。
执行 iteration_expression id,并再次评估 evaluation_expression。如果为真,则再次执行语句块。这将持续到 evaluation_expression 变为 false。
将以下程序复制到一个文件中并执行它,以查看 Golang 循环打印从 1 到 5 的数字
package main import "fmt" func main() { var i int for i = 1; i <= 5; i++ { fmt.Println(i) } }
输出为
1 2 3 4 5
如果别的
If else 是条件语句。语法如下
if condition{ // statements_1 }else{ // statements_2 }
这里对条件进行评估,如果条件为真,则执行 statements_1,否则执行 statements_2。
您也可以使用不带 else 的 if 语句。您还可以使用链式 if else 语句。以下程序将更详细地解释 if else。
执行以下程序。它检查数字 x 是否小于 10。如果是,它将打印“x 小于 10”
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x < 10 fmt.Println("x is less than 10") } }
这里由于 x 的值大于 10,所以 if 块条件内的语句将不会执行。
现在看下面的程序。在本 Go 编程语言教程中,我们有一个 else 块,它将在 if 评估失败时执行。
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else { //Executes if x >= 10 fmt.Println("x is greater than or equals 10") } }
该程序将为您提供输出
x is greater than or equals 10
现在,在本 Go 教程中,我们将看到一个包含多个 if else 块(链接的 if else)的程序。执行下面的 Go 示例。它检查数字是否小于 10 或介于 10-90 之间或大于 90。
package main import "fmt" func main() { var x = 100 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else if x >= 10 && x <= 90 { //Executes if x >= 10 and x<=90 fmt.Println("x is between 10 and 90") } else { //Executes if both above cases fail i.e x>90 fmt.Println("x is greater than 90") } }
这里首先 if 条件检查 x 是否小于 10,如果小于,则检查下一个条件(else if)是否介于 10 和 90 之间,如果结果为 false,则执行 else 部分下的代码块,输出结果
x is greater than 90
Switch 开关
Switch 是另一个条件语句。Switch 语句评估表达式,并将结果与一组可用值(case)进行比较。一旦找到匹配项,就会执行与该匹配项(case)相关的语句。如果没有找到匹配项,则不会执行任何操作。您还可以向 switch 添加默认case,如果没有找到其他匹配项,则会执行该case。switch 的语法是
switch expression { case value_1: statements_1 case value_2: statements_2 case value_n: statements_n default: statements_default }
这里将表达式的值与每个案例中的值进行比较。一旦找到匹配项,就会执行与该案例相关的语句。如果没有找到匹配项,则会执行默认部分下的语句。
执行以下程序
package main import "fmt" func main() { a,b := 2,1 switch a+b { case 1: fmt.Println("Sum is 1") case 2: fmt.Println("Sum is 2") case 3: fmt.Println("Sum is 3") default: fmt.Println("Printing default") } }
您将获得如下输出
Sum is 3
将 a 和 b 的值改为 3,结果将是
Printing default
您还可以在一个案例中拥有多个值,通过使用逗号分隔它们。
阵列
数组表示固定大小、命名的相同类型元素序列。数组中不能同时包含整数和字符。一旦定义了数组的大小,就无法更改其大小。
声明数组的语法是
var arrayname [size] type
可以使用语法为每个数组元素赋值
arrayname [index] = value
数组索引开始于 0 至尺寸-1.
您可以在声明期间使用语法为数组元素赋值
arrayname := [size] type {value_0,value_1,…,value_size-1}
您还可以在声明数组时忽略 size 参数,方法是将 size 替换为 ... 编译器会根据值的数量找到长度。语法是
arrayname := […] type {value_0,value_1,…,value_size-1}
您可以使用以下语法找到数组的长度
len(arrayname)
执行下面的 Go 示例来理解数组
package main import "fmt" func main() { var numbers [3] string //Declaring a string array of size 3 and adding elements numbers[0] = "One" numbers[1] = "Two" numbers[2] = "Three" fmt.Println(numbers[1]) //prints Two fmt.Println(len(numbers)) //prints 3 fmt.Println(numbers) // prints [One Two Three] directions := [...] int {1,2,3,4,5} // creating an integer array and the size of the array is defined by the number of elements fmt.Println(directions) //prints [1 2 3 4 5] fmt.Println(len(directions)) //prints 5 //Executing the below commented statement prints invalid array index 5 (out of bounds for 5-element array) //fmt.Println(directions[5]) }
输出
Two 3 [One Two Three] [1 2 3 4 5] 5
Golang 切片和附加函数
切片是数组的一部分或片段。或者,它是它指向的底层数组的视图或部分视图。您可以使用切片名称和索引号访问切片的元素,就像在数组中一样。您无法更改数组的长度,但可以更改切片的大小。
切片的内容实际上是指向数组元素的指针。这意味着 如果更改切片中的任何元素,底层数组内容也会受到影响。
创建切片的语法是
var slice_name [] type = array_name[start:end]
这将从名为 array_name 的数组中创建一个名为slice_name的切片,其元素索引从 start 到 end-1。
现在,在本 Golang 教程中,我们将执行以下程序。该程序将从数组创建一个切片并打印它。此外,您可以看到修改切片中的内容将修改实际数组。
package main import "fmt" func main() { // declaring array a := [5] string {"one", "two", "three", "four", "five"} fmt.Println("Array after creation:",a) var b [] string = a[1:4] //created a slice named b fmt.Println("Slice after creation:",b) b[0]="changed" // changed the slice data fmt.Println("Slice after modifying:",b) fmt.Println("Array after slice modification:",a) }
这将打印结果为
Array after creation: [one two three four five] Slice after creation: [two three four] Slice after modifying: [changed three four] Array after slice modification: [one changed three four five]
有一些函数,比如 Golang len、Golang append,可以应用于切片
len(切片名称) – 返回切片的长度
附加(切片名称,值 1,值 2) – Golang append 用于将 value_1 和 value_2 附加到现有切片。
附加(切片名称1,切片名称2…) – 将切片名称 2 附加到切片名称 1
执行以下程序。
package main import "fmt" func main() { a := [5] string {"1","2","3","4","5"} slice_a := a[1:3] b := [5] string {"one","two","three","four","five"} slice_b := b[1:3] fmt.Println("Slice_a:", slice_a) fmt.Println("Slice_b:", slice_b) fmt.Println("Length of slice_a:", len(slice_a)) fmt.Println("Length of slice_b:", len(slice_b)) slice_a = append(slice_a,slice_b...) // appending slice fmt.Println("New Slice_a after appending slice_b :", slice_a) slice_a = append(slice_a,"text1") // appending value fmt.Println("New Slice_a after appending text1 :", slice_a) }
输出将是
Slice_a: [2 3] Slice_b: [two three] Length of slice_a: 2 Length of slice_b: 2 New Slice_a after appending slice_b : [2 3 two three] New Slice_a after appending text1 : [2 3 two three text1]
程序首先创建 2 个切片并打印其长度。然后将一个切片附加到另一个切片,再将字符串附加到结果切片。
功能
函数表示执行特定任务的语句块。函数声明告诉我们函数名称、返回类型和输入参数。函数定义表示函数中包含的代码。声明函数的语法是
func function_name(parameter_1 type, parameter_n type) return_type { //statements }
参数和返回类型是可选的。此外,您可以从一个函数返回多个值。
现在,在本 Golang 教程中,让我们运行以下 Golang 示例。此处名为 calc 的函数将接受 2 个数字并执行加法和减法并返回两个值。
package main import "fmt" //calc is the function name which accepts two integers num1 and num2 //(int, int) says that the function returns two values, both of integer type. func calc(num1 int, num2 int)(int, int) { sum := num1 + num2 diff := num1 - num2 return sum, diff } func main() { x,y := 15,10 //calls the function calc with x and y an d gets sum, diff as output sum, diff := calc(x,y) fmt.Println("Sum",sum) fmt.Println("Diff",diff) }
输出将是
Sum 25 Diff 5
包用于组织代码。在大型项目中,将代码写在单个文件中是不可行的。Go 编程语言允许我们在不同的包下组织代码。这提高了代码的可读性和可重用性。可执行的 Go 程序应包含一个名为 main 的包,程序执行从名为 main 的函数开始。您可以使用语法在我们的程序中导入其他包
import package_name
在本 Golang 教程中,我们将看到并讨论如何在以下 Golang 示例中创建和使用包。
步骤1) 创建一个名为 package_example.go 的文件并添加以下代码
package main import "fmt" //the package to be created import "calculation" func main() { x,y := 15,10 //the package will have function Do_add() sum := calculation.Do_add(x,y) fmt.Println("Sum",sum) }
在上面的程序中,fmt 是 Go 编程语言为我们提供的一个主要用于 I/O 目的的包。此外,你还可以看到一个名为“calculation”的包。在 main() 中,你可以看到一个步骤 sum :=calculation.Do_add(x,y)。这意味着你正在调用来自“calculation”包的 Do_add 函数。
步骤2) 首先需要在go的src文件夹下创建一个同名文件夹,里面是计算包,go的安装路径可以在PATH变量中找到。
对于 Mac,通过执行 echo $PATH 查找路径
所以路径是/usr/local/go
对于 windows,通过执行 echo %GOROOT% 找到路径
此处的路径是 C:\Go\
步骤3) 导航到 src 文件夹(对于 mac,为 /usr/local/go/src;对于 windows,为 C:\Go\src)。现在从代码中可以看出,包名称是计算。Go 要求将包放在 src 目录下的同名目录中。在 src 文件夹中创建一个名为计算的目录。
步骤4) 在计算目录中创建一个名为 calc.go 的文件(你可以给出任何名字,但代码中的包名很重要。这里应该是计算)并添加以下代码
package calculation func Do_add(num1 int, num2 int)(int) { sum := num1 + num2 return sum }
步骤5) 从计算目录运行命令 go install,它将编译 calc.go。
步骤6) 现在返回 package_example.go 并运行 go run package_example.go。输出将是 Sum 25。
请注意,函数 Do_add 的名称以大写字母开头。这是因为在 Go 中,如果函数名称以大写字母开头,则意味着其他程序可以看到(访问)它,否则其他程序无法访问它。如果函数名称是 do_add ,那么您将收到错误
无法引用未导出的名称calculation.calc.。
延迟和堆叠延迟
Defer 语句用于推迟函数调用的执行,直到包含 defer 语句的函数执行完成。
让我们通过一个例子来学习这一点:
package main import "fmt" func sample() { fmt.Println("Inside the sample()") } func main() { //sample() will be invoked only after executing the statements of main() defer sample() fmt.Println("Inside the main()") }
输出将是
Inside the main() Inside the sample()
这里,sample() 的执行被推迟到封闭函数 (main()) 的执行完成为止。
堆叠 defer 是使用多个 defer 语句。假设您在一个函数中有多个 defer 语句。Go 将所有延迟函数调用放在一个堆栈中,一旦封闭函数返回,堆栈中的函数将在 后进先出 (LIFO) 顺序。 您可以在下面的例子中看到这一点。
执行以下代码
package main import "fmt" func display(a int) { fmt.Println(a) } func main() { defer display(1) defer display(2) defer display(3) fmt.Println(4) }
输出将是
4 3 2 1
这里首先执行main()内部的代码,然后按相反的顺序执行延迟函数调用,即4、3,2,1、XNUMX、XNUMX。
Pointers
在解释指针之前,我们先来讨论一下“&”运算符。“&”运算符用于获取变量的地址。这意味着“&a”将打印变量 a 的内存地址。
在本 Golang 教程中,我们将执行以下程序来显示变量的值及其地址
package main import "fmt" func main() { a := 20 fmt.Println("Address:",&a) fmt.Println("Value:",a) }
结果将是
Address: 0xc000078008 Value: 20
指针变量存储另一个变量的内存地址。您可以使用语法定义指针
var variable_name *type
星号(*)表示变量是指针。通过执行以下程序,您将了解更多
package main import "fmt" func main() { //Create an integer variable a with value 20 a := 20 //Create a pointer variable b and assigned the address of a var b *int = &a //print address of a(&a) and value of a fmt.Println("Address of a:",&a) fmt.Println("Value of a:",a) //print b which contains the memory address of a i.e. &a fmt.Println("Address of pointer b:",b) //*b prints the value in memory address which b contains i.e. the value of a fmt.Println("Value of pointer b",*b) //increment the value of variable a using the variable b *b = *b+1 //prints the new value using a and *b fmt.Println("Value of pointer b",*b) fmt.Println("Value of a:",a)}
输出将是
Address of a: 0x416020 Value of a: 20 Address of pointer b: 0x416020 Value of pointer b 20 Value of pointer b 21 Value of a: 21
结构
结构是用户定义的数据类型,其本身包含一个或多个相同或不同类型的元素。
使用结构是一个两步过程。
首先,创建(声明)一个结构类型
其次,创建该类型的变量来存储值。
当您想将相关数据存储在一起时,主要使用结构。
假设有一条员工信息,其中包含姓名、年龄和地址。你可以用两种方式处理它
创建 3 个数组 - 一个数组存储员工姓名,一个数组存储年龄,第三个数组存储年龄。
声明一个具有 3 个字段(姓名、地址和年龄)的结构类型。创建该结构类型的数组,其中每个元素都是具有姓名、地址和年龄的结构对象。
第一种方法效率不高。在这种情况下,结构更方便。
声明结构的语法是
type structname struct { variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type }
结构声明的一个例子是
type emp struct { name string address string age int }
这里创建了一个名为 emp 的新用户定义类型。现在,您可以使用以下语法创建 emp 类型的变量
var variable_name struct_name
一个例子是
var empdata1 emp
您可以将 empdata1 的值设置为
empdata1.name = "John" empdata1.address = "Street-1, Bangalore" empdata1.age = 30
您还可以创建结构变量并通过以下方式分配值
empdata2 := emp{"Raj", "Building-1, Delhi", 25}
这里需要维护元素的顺序。Raj 将被映射到名称,下一个元素将被映射到地址,最后一个元素将被映射到年龄。
执行下面的代码
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //function which accepts variable of emp type and prints name property func display(e emp) { fmt.Println(e.name) } func main() { // declares a variable, empdata1, of the type emp var empdata1 emp //assign values to members of empdata1 empdata1.name = "John" empdata1.address = "Street-1, London" empdata1.age = 30 //declares and assign values to variable empdata2 of type emp empdata2 := emp{"Raj", "Building-1, Paris", 25} //prints the member name of empdata1 and empdata2 using display function display(empdata1) display(empdata2) }
输出
John Raj
方法(不是函数)
方法是具有接收者参数的函数。 Archi从结构上讲,它位于 func 关键字和方法名称之间。方法的语法是
func (variable variabletype) methodName(parameter1 paramether1type) { }
我们将上面的示例程序转换为使用方法而不是函数。
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //Declaring a function with receiver of the type emp func(e emp) display() { fmt.Println(e.name) } func main() { //declaring a variable of type emp var empdata1 emp //Assign values to members empdata1.name = "John" empdata1.address = "Street-1, Lodon" empdata1.age = 30 //declaring a variable of type emp and assign values to members empdata2 := emp { "Raj", "Building-1, Paris", 25} //Invoking the method using the receiver of the type emp // syntax is variable.methodname() empdata1.display() empdata2.display() }
Go 不是面向对象的语言,也没有类的概念。方法让你感受到在面向对象程序中所做的事情,其中使用语法 objectname.functionname() 调用类的函数
并发
Go 支持并发执行任务。这意味着 Go 可以同时执行多个任务。它与并行的概念不同。在并行中,任务被拆分为小的子任务并并行执行。但在并发中,多个任务同时执行。在 Go 中,并发是使用 Goroutines 和 Channels 实现的。
Goroutine
goroutine 是一个可以与其他函数并发运行的函数。通常,当调用一个函数时,控制权会转移到被调用函数,一旦该函数执行完毕,控制权就会返回到调用函数。然后,调用函数继续执行。调用函数等待被调用函数执行完毕,然后再继续执行其余语句。
但是在 goroutine 的情况下,调用函数不会等待被调用函数执行完成。它将继续执行下一个语句。一个程序中可以有多个 goroutine。
此外,主程序一旦完成执行其语句就会退出,并且不会等待调用的 goroutines 完成。
Goroutine 的调用使用关键字 go 加上函数调用。
例如:
go add(x,y)
您将通过以下 Golang 示例了解 goroutines。执行以下程序
package main import "fmt" func display() { for i:=0; i<5; i++ { fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() //The main() continues without waiting for display() for i:=0; i<5; i++ { fmt.Println("In main") } }
输出将是
In main In main In main In main In main
这里主程序在 goroutine 启动之前就已完成执行。display() 是一个使用语法调用的 goroutine
go function_name(parameter list)
在上面的代码中,main() 不会等待 display() 完成,main() 在 display() 执行其代码之前就已完成执行。因此 display() 中的 print 语句没有被打印出来。
现在我们修改程序以打印来自 display() 的语句。我们在 main() 的 for 循环中添加 2 秒的时间延迟,并在 display() 的 for 循环中添加 1 秒的延迟。
package main import "fmt" import "time" func display() { for i:=0; i<5; i++ { time.Sleep(1 * time.Second) fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() for i:=0; i<5; i++ { time.Sleep(2 * time.Second) fmt.Println("In main") } }
输出有点类似于
In display In main In display In display In main In display In display In main In main In main
这里您可以看到,由于并发执行,两个循环都以重叠的方式执行。
频道
通道是函数之间相互通信的一种方式。可以将其视为 Golang 服务器中一个例程放置数据并被另一个例程访问的媒介。
可以使用以下语法声明通道
channel_variable := make(chan datatype)
计费示例:
ch := make(chan int)
您可以使用语法将数据发送到通道
channel_variable <- variable_name
例如:
ch <- x
您可以使用语法从通道接收数据
variable_name := <- channel_variable
例如:
y := <- ch
在上面的 Go 语言 goroutine 示例中,您已经看到主程序不会等待 goroutine。但是当涉及通道时情况并非如此。假设一个 goroutine 将数据推送到通道,main() 将等待接收通道数据的语句,直到它获取数据。
您将在下面的 Go 语言示例中看到这一点。首先,编写一个普通的 goroutine 并查看其行为。然后修改程序以使用通道并查看其行为。
执行以下程序
package main import "fmt" import "time" func display() { time.Sleep(5 * time.Second) fmt.Println("Inside display()") } func main() { go display() fmt.Println("Inside main()") }
输出将是
Inside main()
main() 完成执行并在 goroutine 执行之前退出。因此 display() 中的打印未执行。
现在修改上述程序以使用通道并查看行为。
package main import "fmt" import "time" func display(ch chan int) { time.Sleep(5 * time.Second) fmt.Println("Inside display()") ch <- 1234 } func main() { ch := make(chan int) go display(ch) x := <-ch fmt.Println("Inside main()") fmt.Println("Printing x in main() after taking from channel:",x) }
输出将是
Inside display() Inside main() Printing x in main() after taking from channel: 1234
这里发生的事情是,当 main() 到达 x := <-ch 时,它将等待通道 ch 上的数据。display() 等待 5 秒,然后将数据推送到通道 ch。当从通道接收到数据时,main() 将被解除阻塞并继续执行。
向通道推送数据的发送者可以通过关闭通道来通知接收者不再向通道添加数据。这主要用于使用循环向通道推送数据时。可以使用以下命令关闭通道
close(channel_name)
在接收端,可以使用附加变量检查通道是否关闭,同时使用以下方法从通道获取数据:
variable_name, status := <- channel_variable
如果状态为 True,则表示您从通道接收到了数据。如果为 false,则表示您正在尝试从已关闭的通道读取数据
您还可以使用通道在 goroutine 之间进行通信。需要使用 2 个 goroutine - 一个将数据推送到通道,另一个从通道接收数据。请参阅以下程序
package main import "fmt" import "time" //This subroutine pushes numbers 0 to 9 to the channel and closes the channel func add_to_channel(ch chan int) { fmt.Println("Send data") for i:=0; i<10; i++ { ch <- i //pushing data to channel } close(ch) //closing the channel } //This subroutine fetches data from the channel and prints it. func fetch_from_channel(ch chan int) { fmt.Println("Read data") for { //fetch data from channel x, flag := <- ch //flag is true if data is received from the channel //flag is false when the channel is closed if flag == true { fmt.Println(x) }else{ fmt.Println("Empty channel") break } } } func main() { //creating a channel variable to transport integer values ch := make(chan int) //invoking the subroutines to add and fetch from the channel //These routines execute simultaneously go add_to_channel(ch) go fetch_from_channel(ch) //delay is to prevent the exiting of main() before goroutines finish time.Sleep(5 * time.Second) fmt.Println("Inside main()") }
这里有 2 个子程序,一个将数据推送到通道,另一个将数据打印到通道。函数 add_to_channel 将数字从 0 添加到 9 并关闭通道。同时,函数 fetch_from_channel 等待
x, flag := <- ch 一旦数据可用,它就会打印数据。一旦标志为假(意味着通道已关闭),它就会退出。
main() 中的等待是为了防止 main() 退出,直到 goroutines 完成执行。
执行代码并查看输出
Read data Send data 0 1 2 3 4 5 6 7 8 9 Empty channel Inside main()
选择
Select 可以看作是作用于通道的 switch 语句。这里的 case 语句将是一个通道操作。通常,每个 case 语句都将从通道中读取尝试。当任何 case 就绪(通道已读取)时,将执行与该 case 关联的语句。如果多个 case 已就绪,它将随机选择一个。您可以有一个默认 case,如果所有 case 均未就绪,则执行该 case。
我们来看下面的代码
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements wait for data in the chan1 or chan2. //chan2 gets data first since the delay is only 2 sec in data2(). //So the second case will execute and exits the select block select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } }
执行上述程序将给出输出:
from data2()
此处 select 语句等待任何通道中的数据可用。data2() 在休眠 2 秒后将数据添加到通道,这将导致执行第二种情况。
在同一个程序中向 select 添加一个默认案例并查看输出。在这里,到达 select 块时,如果没有任何案例在通道上准备好数据,它将执行默认块而不等待任何通道上都有数据可用。
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements check for data in chan1 or chan2. //But data is not available (both routines have a delay of 2 and 4 sec) //So the default block will be executed without waiting for data in channels. select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) default: fmt.Println("Default case executed") } }
该程序将给出输出:
Default case executed
这是因为当 select 块到达时,没有通道有数据可读。因此,执行 default case。
互斥
Mutex 是互斥的缩写。当您不想允许多个子程序同时访问资源时,可以使用 Mutex。Mutex 有两种方法 - Lock 和 Unlock。Mutex 包含在 sync 包中。因此,您必须导入 sync 包。必须互斥执行的语句可以放在 mutex.Lock() 和 mutex.Unlock() 中。
让我们通过一个计算循环执行次数的示例来学习互斥锁。在此程序中,我们期望例程运行循环 10 次,计数存储在 sum 中。您调用此例程 3 次,因此总计数应为 30。计数存储在全局变量 count 中。
首先,运行没有互斥锁的程序
package main import "fmt" import "time" import "strconv" import "math/rand" //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
查看结果
Count after i=1 Count: 11 Count after i=3 Count: 12 Count after i=2 Count: 13 Final Count: 13
执行时结果可能会有所不同,但最终结果不会是 30。
这里发生的事情是 3 个 goroutine 试图增加存储在变量 count 中的循环计数。假设此刻 count 为 5,而 goroutine1 将把计数增加到 6。主要步骤包括
将计数复制到临时
增加温度
将温度存储回计数
假设 goroutine3 执行完步骤 1 后不久,另一个 goroutine 可能有一个旧值,比如说 3,执行上述步骤并将 4 存回,这是错误的。可以使用互斥锁来防止这种情况,当一个例程已经在使用该变量时,互斥锁会导致其他例程等待。
现在您将使用互斥锁运行该程序。这里上述 3 个步骤是在互斥锁中执行的。
package main import "fmt" import "time" import "sync" import "strconv" import "math/rand" //declare a mutex instance var mu sync.Mutex //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) //lock starts here mu.Lock() temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp //lock ends here mu.Unlock() } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
现在输出将是
Count after i=3 Count: 21 Count after i=2 Count: 28 Count after i=1 Count: 30 Final Count: 30
这里我们得到了预期的结果作为最终输出。因为读取、增加和写回计数的语句是在互斥锁中执行的。
错误处理
错误是指异常情况,例如关闭未打开的文件、打开不存在的文件等。函数通常将错误作为最后的返回值。
下面的例子进一步解释了该错误。
package main import "fmt" import "os" //function accepts a filename and tries to open it. func fileopen(name string) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { fmt.Println(er) return }else{ fmt.Println("file opened", f.Name()) } } func main() { fileopen("invalid.txt") }
输出将是:
open /invalid.txt: no such file or directory
这里我们尝试打开一个不存在的文件,并将错误返回给 er 变量。如果文件有效,则错误将为空
自定义错误
使用此功能,您可以创建自定义错误。这是通过使用 error 包的 New() 来完成的。我们将重写上述程序以使用自定义错误。
运行以下程序
package main import "fmt" import "os" import "errors" //function accepts a filename and tries to open it. func fileopen(name string) (string, error) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { //created a new error object and returns it return "", errors.New("Custom error message: File name is wrong") }else{ return f.Name(),nil } } func main() { //receives custom error or nil after trying to open the file filename, error := fileopen("invalid.txt") if error != nil { fmt.Println(error) }else{ fmt.Println("file opened", filename) } }
输出将是:
Custom error message:File name is wrong
这里 area() 返回正方形的面积。如果输入小于 1,则 area() 返回错误消息。
读取文件
文件用于存储数据。Go 允许我们从文件中读取数据
首先在当前目录中创建一个文件 data.txt,内容如下。
Line one Line two Line three
现在运行下面的程序,看看它是否将整个文件的内容打印为输出
package main import "fmt" import "io/ioutil" func main() { data, err := ioutil.ReadFile("data.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
这里的数据,err := ioutil.ReadFile(“data.txt”) 读取数据并返回一个字节序列。打印时将其转换为字符串格式。
写入文件
您将通过一个程序看到这一点
package main import "fmt" import "os" func main() { f, err := os.Create("file1.txt") if err != nil { fmt.Println(err) return } l, err := f.WriteString("Write Line one") if err != nil { fmt.Println(err) f.Close() return } fmt.Println(l, "bytes written") err = f.Close() if err != nil { fmt.Println(err) return } }
这里创建了一个文件 test.txt。如果文件已经存在,则文件的内容将被截断。Writeline() 用于将内容写入文件。之后,使用 Close() 关闭文件。
备忘单
在本 Go 教程中,我们介绍了:
话题 | 描述 | 句法 |
---|---|---|
基本类型 | 数字、字符串、布尔值 | |
变量 | 声明变量并为其赋值 | var 变量名 类型 var 变量名 类型 = 值 var 变量名1,变量名2 = 值1,值2 变量名 := 值 |
常量 | 一旦赋值就不能改变的变量 | const 变量 = 值 |
对于循环 | 循环执行语句。 | 对于初始化表达式;评估表达式;迭代表达式{ // 一个或多个语句 } |
如果别的 | 这是一个条件语句 | 如果条件{ // 语句_1 } { // 语句_2 } |
开关 | 具有多个案例的条件语句 | 开关表达式 { 案例值_1: 语句_1 案例值_2: 语句_2 案例值_n: 语句_n 默认情况下: 语句_默认 } |
排列 | 固定大小的相同类型元素的命名序列 | 数组名称 := [大小] 类型 {value_0,value_1,…,value_size-1} |
切片 | 数组的一部分或片段 | var 切片名称 [] 类型 = 数组名称[起始:结束] |
功能 | 执行特定任务的语句块 | func 函数名称(参数1类型,参数n类型) 返回类型 { //语句 } |
用于组织代码。提高代码的可读性和可重用性 | 导入包名称 | |
推迟 | 推迟执行某个函数,直到包含该函数的函数完成执行 | 延迟函数名称(参数列表) |
Pointers | 存储另一个变量的内存地址。 | var 变量名 *类型 |
结构 | 用户定义的数据类型本身包含一个或多个相同或不同类型的元素 | 类型结构名称结构{ 变量_1 变量_1_类型 变量_2 变量_2_类型 变量_n 变量_n_类型 } |
方法 | 方法是具有接收者参数的函数 | func (变量 变量类型) 方法名称(参数列表) { } |
协程 | 可以与其他函数同时运行的函数。 | 转到函数名称(参数列表) |
频道 | 函数之间相互通信的方式。一个例程将数据存放到其中,另一个例程可访问该数据。 | 宣布: ch := 制作(chan int) 发送数据到频道: 通道变量<-变量名称 从频道接收: 变量名称 := <- 通道变量 |
选择 | 适用于通道的 Switch 语句。case 语句将是一个通道操作。当任何通道准备好数据时,将执行与该 case 关联的语句 | 选择 { 情况 x := <-chan1: fmt.Println(x) 案例 y := <-chan2: fmt.Println(y) } |
互斥 | 当您不想让多个子程序同时访问资源时,可以使用 Mutex。Mutex 有两种方法 - Lock 和 Unlock | 互斥锁() //语句 互斥锁。解锁()。 |
读取文件 | 读取数据并返回字节序列。 | 数据,错误:= ioutil.ReadFile(文件名) |
写入文件 | 将数据写入文件 | l,错误:= f.WriteString(text_to_write) |