目录
一、概述
1.1 Go语言的起源和设计理念
1.2 Go语言的特性和应用领域
二、环境配置
2.1 下载和安装
2.2 设置环境变量
2.3 验证安装
三、基础语法
3.1 变量定义和赋值
3.2 类型别名
3.3 常量
3.4 控制流语句
3.4.1 条件语句(if)
3.4.2 switch语句
3.4.3 循环语句(for)
3.4.4 无限循环语句(for without conditions)
3.4.5 跳转语句(break)
3.4.6 返回语句(return)
3.4.7 错误处理(defer)
3.5 函数定义和调用
3.6 数组和切片
3.7 映射
3.8 结构体
3.9 方法
3.10 并发编程
3.10.1 goroutine
3.10.2 channel
Go语言(或 Golang)起源于2007年,并在2009年正式对外发布。Go是非常年轻的一门语言,它的主要目标是"兼具Python等动态语言的开发速度和C/C++等编译型语言的性能与安全性"。Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。
Go语言的设计理念是不损失应用程序性能的情况下降低代码的复杂性,旨在创造一种新的编程语言,既能够保持C和C++的效率,又能够拥有像Python和Ruby的灵活性和易读性
Go语言的特性和应用领域如下12:
/usr/local/go
打开终端并输入以下命令来添加环境变量:
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin
其中,GOROOT
是Go语言的安装路径,PATH
是环境变量中的一个路径,用于寻找Go命令。
1. 安装一个编辑器,例如Visual Studio Code或Sublime Text等。
2. 使用编辑器创建一个Hello World程序,保存为hello.go
。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
3. 在终端中进入Hello World程序所在的目录,输入以下命令:
go run hello.go
如果一切正常,终端中应该输出“Hello, World!”
在Go语言中,可以使用关键字var
来定义变量,并使用赋值运算符=
对变量进行赋值。
例如,定义一个整数类型的变量并赋值为10的语句如下:
var age int = 10
在上面的语句中,var
关键字用于声明一个变量,age
是变量的名称,int
是变量的类型,=
是赋值运算符,将10赋给变量age
。
除了直接赋值,还可以使用类型推导来定义变量,例如:
var name = "Alice"
在上面的语句中,变量name
的类型会被自动推导为字符串类型,并将字符串"Alice"赋值给变量name
。
还可以使用简短声明方式来定义和赋值变量,例如:
age := 20
在上面的语句中,:=
是简短赋值运算符,表示将20赋值给新声明的变量age
。由于没有明确指定变量的类型,因此编译器会根据右侧表达式的值来自动推导变量的类型。
需要注意的是,在Go语言中,变量名必须以字母或下划线开头,不能以数字开头。此外,变量名是区分大小写的。
Go语言中的类型别名就是为已存在的类型定义一个别名,使用type关键字来定义,语法为type TypeAlias = Type
。
类型别名是Go 1.9版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题;在C/C++语言中,代码重构升级可以使用宏快速定义新的一段代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题1。
例如,定义一个整数类型的别名Age
:
type Age int
那么Age
就是int
类型的别名。以后凡是使用Age
的地方,都可以使用int
代替。
在Go语言中,常量是一种不可变的值。常量是在编译时就确定且在运行时不能被修改的量。
常量可以是任何基本类型,如整数、浮点数、布尔值、字符串或常量表达式。
定义常量的语法如下:
const constantName = value
其中,constantName
是常量的名称,遵循标识符命名规则;value
是常量的值。
例如,定义一个整数类型的常量Pi
并赋值为3.14:
const Pi = 3.14
常量在编译时会被计算,因此可以在编译期间进行类型检查和类型推断
在Go语言中,控制流语句用于控制程序的执行流程。以下是一些常用的控制流语句:
根据条件执行不同的代码块:
if condition {
// 如果条件为真,执行这里的代码块
} else if condition2 {
// 如果条件为假,检查条件2,如果条件2为真,执行这里的代码块
} else {
// 如果条件和条件2都为假,执行这里的代码块
}
switch
语句根据不同的条件执行不同的代码块:
switch expression {
case value1:
// 当expression等于value1时执行的代码块
case value2:
// 当expression等于value2时执行的代码块
...
default:
// 当expression不等于任何一个case值时执行的代码块
}
重复执行同一段代码:
for initialization; condition; post {
// 执行循环体
}
无限循环,除非使用break语句跳出循环:
for {
// 无限循环,除非使用break语句跳出循环
}
用于跳出循环或switch语句:
break; // 跳出当前循环或switch语句
从函数中返回值:
return value; // 返回一个值给调用者
延迟执行某些代码,通常用于资源清理:
defer func() {
// 延迟执行的代码,通常用于资源清理操作
}()
Go语言中还有一些更详细的控制流语句,包括空语句(empty statement)、无限循环语句(infinite loop)、标签和goto语句。由于篇幅有限,不再一一列举,有兴趣可自行学习。
在Go语言中,函数是一种可重复使用的代码块,用于执行特定的任务。函数定义指定了函数的名称、参数和返回值,而函数调用则是执行函数的过程。
下面是一个简单的函数定义和调用的示例:
package main
import "fmt"
// 函数定义
func greet(name string) string {
return "Hello, " + name + "!"
}
func main() {
// 函数调用
fmt.Println(greet("Alice"))
fmt.Println(greet("Bob"))
}
在上面的代码中,我们定义了一个名为greet
的函数,它接受一个字符串参数name
,并返回一个带有问候语的字符串。在main
函数中,我们通过调用greet
函数并传递不同的参数来打印不同的问候语。
在函数定义中,函数的名称指定了函数的名称,参数列表指定了函数接受的输入参数的类型和名称,而返回值列表指定了函数返回值的类型和名称。函数的定义以关键字func
开头,后面跟着函数名称、参数列表和返回值列表。
在函数调用中,我们使用函数名称加上参数列表的方式来执行函数。参数列表中的参数数量和类型必须与函数定义中的参数列表匹配。调用函数后,返回的结果可以直接使用,也可以通过变量接收。
请注意:在Go语言中,函数的定义可以出现在任何地方,但通常建议将函数定义放在package
级别以下。同时,函数名称的命名应该具有描述性,以便于阅读和理解。
在Go语言中,数组和切片是常用的数据结构,它们有一些共同点和区别。
数组的定义方式如下:
var arr [n]type
其中,n
表示数组的长度,type
表示数组元素的类型。例如,定义一个包含5个整数的数组:
var arr [5]int
可以通过索引访问数组元素,例如:
arr[0] = 10
fmt.Println(arr[0]) // 输出:10
切片的定义方式如下:
var slice []type
其中,type
表示切片元素的类型。切片是一个动态长度的序列,可以通过追加元素来扩展。例如,创建一个包含3个整数的切片:
var slice []int
slice = append(slice, 10, 20, 30)
可以通过索引访问切片元素,例如:
slice[0] = 10
fmt.Println(slice[0]) // 输出:10
数组和切片的区别在于动态性和长度。数组的长度是固定的,不能改变,而切片的长度可以动态增长。此外,切片还有一些内置的方法和操作,例如len()
和append()
函数,这些方法在数组中不可用。
需要注意的是,切片在创建时可以指定初始容量,例如:
var slice []int = make([]int, 5, 10)
这将创建一个初始长度为5,容量为10的切片。当切片的容量不足以容纳更多的元素时,切片会自动扩容。
在Go语言中,映射(Map)是一种无序的键值对集合。它使用一个哈希表实现,提供了一种将键映射到值的方式。
映射的定义方式如下:
var m map[keyType]valueType
其中,keyType
表示键的类型,valueType
表示值的类型。
以下是一个使用映射的示例:
package main
import "fmt"
func main() {
// 创建一个映射
m := make(map[string]int)
// 添加键值对
m["apple"] = 1
m["banana"] = 2
m["orange"] = 3
// 访问映射的值
fmt.Println(m["apple"]) // 输出:1
// 删除映射中的键值对
delete(m, "banana")
// 遍历映射
for key, value := range m {
fmt.Println(key, value)
}
}
在上述示例中,我们首先使用make
函数创建了一个映射m
,然后通过key
来添加、访问和删除映射中的值。使用delete
函数可以删除指定键值对。最后,使用range
循环遍历映射并打印键值对。
Go语言中的结构体(struct)是一种自定义数据类型,可以将多个基本数据类型组合到一起形成一个自定义的复合数据类型。
结构体的定义语法如下:
type 结构体名称 struct {
字段1 字段1类型
字段2 字段2类型
...
}
结构体中的字段名必须唯一,每个字段对应一个成员变量。结构体的定义和使用方法类似于C++中的结构体。
下面是一个简单的示例:
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
}
func main() {
// 声明结构体变量
var p Person
// 直接赋值
p.Name = "Alice"
p.Age = 20
// 通过成员访问运算符访问结构体成员变量
fmt.Println(p.Name) // 输出:Alice
fmt.Println(p.Age) // 输出:20
}
结构体可以定义在函数内部或函数外部,定义位置影响到结构体的访问范围。如果结构体定义在函数外面,结构体名称首字母是否大写影响到结构体是否能跨包访问。如果结构体能跨包访问,属性首字母是否大写影响到属性是否跨包访问。通过结构体可以封装多个变量,实现面向对象编程。
在Go语言中,方法(method)是一种与特定类型关联的函数。它允许我们为自定义类型定义行为,并在该类型的实例上调用方法。
方法的定义使用func
关键字,后面跟着方法名、接收者(receiver)和参数列表。方法的接收者是一个特殊的参数,它指定了该方法与哪个类型相关联。
方法的定义语法如下:
func (接收者类型) 方法名(参数列表) {
// 方法的实现
}
其中,接收者类型
表示与该方法关联的类型,可以是自定义类型或内置类型。方法名
是方法的名称,可以自定义。参数列表
表示方法的参数,可以是零个或多个,与方法实现的具体逻辑相关。
以下是一个示例,演示了如何在Go语言中定义和使用方法:
package main
import "fmt"
// 自定义类型 Person
type Person struct {
name string
age int
}
// 为 Person 类型定义一个方法 SayHello
func (p Person) SayHello() {
fmt.Println("Hello, my name is", p.name)
}
func main() {
// 创建一个 Person 实例
person := Person{name: "Alice", age: 25}
// 调用 Person 类型的方法 SayHello
person.SayHello() // 输出:Hello, my name is Alice
}
在上述示例中,我们定义了一个名为Person
的自定义类型,它包含name
和age
两个字段。然后,我们为Person
类型定义了一个方法SayHello
,该方法用于输出问候信息。在main
函数中,我们创建了一个Person
类型的实例,并调用了其SayHello
方法。
需要注意的是,如果方法的接收者类型是自定义类型,那么方法可以访问该类型的所有公共(public)字段和方法。如果方法的接收者类型是内置类型,那么方法只能访问该类型的公开(public)方法和字段。
在Go语言中,Goroutine是一种轻量级的线程,它是Go语言实现并发编程的关键部分。Goroutine比操作系统线程更轻量级,启动和销毁Goroutine的开销更小,因此可以更高效地处理并发任务。
Goroutine由Go运行时环境管理,可以在程序中创建和调度。通过使用关键字go
,可以将一个函数或方法标记为Goroutine,从而使其在后台并发执行。
下面是一个简单的示例,展示了如何使用Goroutine:
package main
import (
"fmt"
"time"
)
func main() {
go printHello() // 创建Goroutine执行printHello函数
printWorld() // 在主函数中顺序执行printWorld函数
// 等待一段时间,以确保Goroutine有足够的时间执行
time.Sleep(1 * time.Second)
}
func printHello() {
fmt.Println("Hello!")
}
func printWorld() {
fmt.Println("World!")
}
在上述示例中,我们使用go
关键字创建了一个Goroutine来执行printHello
函数。同时,主函数中的printWorld
函数按照顺序执行。由于Goroutine是并发执行的,因此printHello
和printWorld
可能会交替输出。为了确保程序不会立即退出,我们在主函数末尾添加了time.Sleep
语句,以等待Goroutine执行完成。
需要注意的是,Goroutine的执行是异步的,因此不能直接在主函数中等待Goroutine的返回值。如果需要获取Goroutine的返回值,可以使用通道(channel)进行通信。此外,由于Goroutine的执行是并发的,因此需要注意避免竞态条件(race condition)等问题。
在Go语言中,channel是一种用于在goroutine之间进行通信和同步的特殊类型。它提供了一种安全且高效的方式来进行数据传递。
要使用channel,首先需要使用make
函数创建一个channel。例如,ch := make(chan int)
会创建一个整数类型的channel。
channel有两种主要的操作:发送(send)和接收(receive)。发送操作将数据发送到channel中,接收操作从channel中接收数据。
发送操作使用<-
运算符,后面跟着要发送的值。例如,ch <- 10
会将整数10发送到channel中。
接收操作使用<-
运算符,前面跟着要接收的channel。例如,x := <-ch
会从channel中接收一个整数,并将其赋值给变量x。
还可以使用带有默认值的接收操作,例如x := <-ch
。如果channel中没有数据可接收,那么会返回该类型的零值。
需要注意的是,发送和接收操作必须在同一个goroutine中进行,或者在同一个匿名函数中使用。不能在一个goroutine中发送数据,然后在另一个goroutine中接收数据,这是不允许的。
还可以使用close
函数关闭一个channel。关闭后的channel不能再发送数据,但仍然可以从中接收数据。例如,close(ch)
会关闭名为ch的channel。
下面是一个使用channel的简单示例:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 10 // 发送整数10到channel
}()
x := <-ch // 从channel中接收整数,并将其赋值给变量x
fmt.Println(x) // 输出:10
}
这个示例中,我们创建了一个整数类型的channel ch
,然后在一个goroutine中将整数10发送到channel中。在主goroutine中,我们从channel中接收整数并将其赋值给变量x,然后打印出来。输出结果为10。