Hello World
首先,让我们看看 Hello world 程序中发生了什么?
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
让我们来看下以上程序的各个部分:
package声明
代码以 package main 开头,表明这个Go文件属于 main 包。在Go中,每个可执行程序都必须有一个 main 包,并且在 main 包中必须有一个main()函数作为程序的入口点。
导入语句
使用 import "fmt" 导入了 fmt 包。fmt 包是Go标准库中的一个包,提供了输入输出和格式化文本的功能。在本代码中,使用了 fmt.Println() 函数来输出文本。
main函数
在Go程序中,main() 函数是程序的入口点。当程序执行时,它会首先执行 main() 函数。在这段代码中,main() 函数简单地调用 fmt.Println() 函数,将字符串"Hello, World!"输出到控制台。
执行输出
代码通过调用 fmt.Println("Hello, World!") 将 “Hello, World!” 这个字符串输出到终端。fmt.Println() 函数是一个方便的方法,用于在控制台输出文本,并在输出的最后自动添加一个换行符。
注意:在Go中,函数名首字母的大小写决定了该函数的可见性。
如果函数名以大写字母开头,那么它是可导出的 ,其他包可以访问该函数。(类似面向对象语言中的 public)
如果函数名以小写字母开头,它是私有的,其他包无法访问该函数。(类似面向对象语言中的 protected )
在这个例子中,main()函数是可导出的,因为它以大写字母开头,允许被其他包调用。
变量和常量 在Go中,变量和常量的声明有一些不同。以下是它们的声明方式和特点
变量声明
使用关键字var来声明一个变量,语法为:var variableName dataType
变量名必须以字母或下划线开头,可以包含字母、数字和下划线,但不能使用Go的关键字。
可以一次性声明多个变量,例如:var x, y int
如果声明时未初始化变量,则变量会被赋予其数据类型的零值,比如:int类型的零值是0,string类型的零值是空字符串""。
可以使用短变量声明来创建并初始化变量,语法为:variableName := value
示例:
var age int // 声明一个整数变量 age
var name string // 声明一个字符串变量 name
var x, y int // 声明两个整数变量 x, y
x = 10 // 给 x 赋值 10
name = "John" // 给 name 赋值 "John"
var count = 5 // 声明一个整数变量 count 并初始化为 5
email := "example@go.com" // 使用短变量声明声明一个字符串变量 email 并初始化为 "example@go.com"
在Go中,变量声明后必须使用,否则会导致编译错误。
如果想要声明一个变量但不使用它,可以使用下划线 _ 来代替变量名,表示该变量被丢弃,不会占用内存空间。例如:
_ = 100 // 声明一个未使用的变量并初始化为 100,但在后续代码中并不使用该变量
总结:Go中的变量声明可以使用var关键字或短变量声明。变量声明时可以指定数据类型和初始值。未使用的变量可以使用下划线 _ 来代替。
常量声明
使用关键字const来声明常量,语法为:const constantName dataType = value
常量的值在声明时必须初始化,并且一旦赋值不能再修改。
常量可以是字符、字符串、布尔值或数值类型(整数、浮点数)。
常量名的命名规则与变量相同,以字母或下划线开头,可以包含字母、数字和下划线,但不能使用Go的关键字。
常量的值必须是一个编译时可以确定的表达式,例如:const PI = 3.14
示例:
const PI = 3.14 // 声明一个名为 PI 的常量并赋值为 3.14
const appName = "MyApp" // 声明一个名为 appName 的常量并赋值为 "MyApp"
const isDebug = true // 声明一个名为 isDebug 的常量并赋值为 true
const (
monday = "Monday"
tuesday = "Tuesday"
// 可以在常量组中一次性声明多个常量
)
常量必须在声明时初始化,并且其值在程序运行期间不能改变。
数组和切片 (Array & Slice)
数组是一个固定大小的数据结构,它包含一组相同类型的元素。在 Go 语言中,创建数组时,需要指定数组的大小,并且该大小在声明后无法改变。数组的大小是其类型的一部分,因此类型为 [size]dataType ,其中 size 表示数组大小, dataType 表示数组元素的数据类型。
数组是值类型,当将一个数组赋值给另一个数组时,会复制所有的元素。这意味着对于大型数组,复制操作可能会比较耗时和内存。
// 声明一个包含5个整数的数组
var numbers [5]int
// 初始化数组元素
numbers = [5]int{1, 2, 3, 4, 5}
// 声明并初始化一个数组
numbers := [5]int{1, 2, 3, 4, 5}
切片是对数组的引用。切片不需要指定大小,可以动态增长和收缩。在声明切片时,不需要指定大小,只需要指定元素的类型。
对切片的修改会影响到底层数组。当将一个切片赋值给另一个切片时,它们会引用相同的底层数组。
// 声明一个切片
var mySlice []int
// 使用 make() 函数创建切片,第一个参数是切片类型,第二个参数是切片长度,第三个参数是切片容量(可选)
mySlice = make([]int, 5) // 长度为5,容量也为5的切片
// 初始化一个切片
mySlice = []int{1, 2, 3, 4, 5}
// 切片的动态增长
mySlice = append(mySlice, 6, 7, 8) // 添加元素到切片末尾
需要注意的是,切片本身并不存储数据,它只是一个引用,指向底层数组的一部分。当切片的容量不足时,底层数组会被自动扩容。
你可以创建一个新的切片,它引用了现有切片或数组的一部分。这被称为“切片的切片”。切片的切片的语法是 slice[start:end],其中 start 是第一个元素的索引(包括),end 是最后一个元素后面的索引(不包括)。结果切片的长度是 end - start,容量是从 start 索引到底层数组末尾的元素数量。
// 切片的切片,获取子切片
subSlice := mySlice[2:5] // 这将创建一个从索引 2 到索引 4(不包括 5)的子切片
映射(Map)
在Go中,映射(Map)是一种键值对的无序集合,类似于Python中的字典(dict)。它提供了一种方便的方式来存储和检索键值对,并且允许根据键快速查找对应的值。
创建映射(Map)
在Go中,使用map[keyType]valueType的语法来声明一个映射。其中keyType表示键的数据类型,valueType表示值的数据类型。
// 声明一个映射,键为字符串类型,值为整数类型
var myMap map[string]int
初始化映射
使用make()函数来初始化一个映射。初始化映射后,才能对其进行赋值和操作。
// 初始化一个映射
myMap := make(map[string]int)
添加和更新键值对
可以使用赋值操作符=来添加或更新映射中的键值对。如果指定的键不存在,它将被添加到映射中;如果指定的键已经存在,它将更新对应的值。
myMap["apple"] = 10
myMap["banana"] = 5
myMap["apple"] = 15 // 更新键 "apple" 对应的值
访问和检查键值对
可以通过指定键来访问映射中的值。如果指定的键不存在,将返回值类型的零值。为了区分键不存在和值为零值两种情况,可以使用多返回值的方式来检查键是否存在。
value := myMap["apple"] // 访问键 "apple" 对应的值
// 检查键是否存在
value, exists := myMap["orange"]
if exists {
fmt.Println("The value for 'orange' is:", value)
} else {
fmt.Println("Key 'orange' does not exist.")
}
删除键值对
可以使用delete()函数来删除映射中的键值对。如果指定的键不存在,delete()函数不会产生错误。
delete(myMap, "banana") // 删除键 "banana" 对应的键值对
遍历映射
使用for range循环可以遍历映射中的所有键值对。
for key, value := range myMap {
fmt.Println(key, value)
}
Go 中的映射是一个强大且方便的数据结构,它在许多场景下都非常有用,特别是用于表示键值对的集合。与 Python 中的字典类似,Go 的映射提供了快速的键值查找和更新,是处理键值对数据的理想选择。
结构体(Struct)
在Go语言中,结构体(Struct)是一种自定义的复合数据类型,它允许我们将不同类型的数据组合在一起,形成一个新的数据结构。结构体是由一系列字段(fields)组成,每个字段可以是不同的数据类型。结构体中的字段称为成员(members),它们表示结构体的特征和属性。
声明结构体
使用type关键字来声明一个新的结构体类型。结构体的定义以关键字type开头,后面紧跟结构体的名称,然后是一个由字段组成的花括号代码块。
type Person struct {
Name string
Age int
Height float64
}
创建结构体实例
通过声明一个结构体变量并为其成员赋值,我们可以创建结构体的实例。
// 创建一个Person结构体的实例
person1 := Person{
Name: "Alice",
Age: 30,
Height: 1.75,
}
访问结构体成员
可以使用.运算符来访问结构体的成员。
fmt.Println(person1.Name) // 输出: "Alice"
fmt.Println(person1.Age) // 输出: 30
fmt.Println(person1.Height) // 输出: 1.75
结构体的匿名字段
结构体允许字段没有名称,这样的字段称为匿名字段。匿名字段的数据类型必须是命名的类型或具有命名的类型。
type Circle struct {
float64 // 匿名字段,代表圆的半径
}
嵌套结构体
结构体可以包含其他结构体作为其成员,这被称为嵌套结构体。通过嵌套结构体,我们可以建立更复杂的数据结构。
type Address struct {
City string
State string
}
type Person struct {
Name string
Age int
Address Address // 嵌套结构体作为成员
}
结构体的方法
结构体可以关联方法,通过这些方法可以为结构体类型添加行为。方法是特殊类型的函数,它们与结构体关联,可以在结构体实例上调用。
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
rect := Rectangle{Width: 10, Height: 5}
area := rect.Area() // 调用结构体的方法
条件语句
在Go语言中,条件语句主要包括if、else和switch三种类型。它们用于根据不同条件执行不同的代码块。
if 语句
if语句用于根据一个表达式的结果来执行相应的代码块。如果表达式的值为true,则执行if后的代码块;如果表达式的值为false,则跳过if后的代码块。
num := 10
if num > 0 {
fmt.Println("Positive")
} else if num == 0 {
fmt.Println("Zero")
} else {
fmt.Println("Negative")
}
else语句用于在if条件为false时执行一个备用的代码块。
num := 10
if num%2 == 0 {
fmt.Println("Even")
} else {
fmt.Println("Odd")
}
switch 语句
switch 是编写一连串 if-else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。
Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
day := "Sunday"
switch day {
case "Monday":
fmt.Println("Start of the week")
case "Tuesday":
fmt.Println("Second day")
case "Wednesday", "Thursday":
fmt.Println("Middle of the week")
case "Friday":
fmt.Println("End of the work week")
case "Saturday", "Sunday":
fmt.Println("Weekend")
default:
fmt.Println("Invalid day")
}
实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。除非以 fallthrough 语句结束,否则分支会自动终止。
num := 2
switch num {
case 1:
fmt.Println("One")
fallthrough
case 2:
fmt.Println("Two")
case 3:
fmt.Println("Three")
}
在上面的例子中,当num为2时,会输出:
Two
Three
这是因为在第一个case中使用了fallthrough关键字,导致继续执行下一个case的代码块。
循环语句
Go 只有一种循环结构:for 循环。
基本的 for 循环由三部分组成,它们用分号隔开:
初始化语句:在第一次迭代前执行
条件表达式:在每次迭代前求值
后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。
一旦条件表达式的布尔值为 false,循环迭代就会终止。
注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号,
大括号 {} 则是必须的。
基本的for循环:类似于C语言的for循环,使用for关键字和循环条件。循环条件可以是一个布尔表达式,当条件为true时循环会继续执行。
for i := 0; i < 5; i++ {
fmt.Println(i)
}
for循环省略初始化和步进:在for循环中,初始化和步进语句都是可选的。如果省略初始化语句,那么相当于一个无限循环。
i := 0
for ; i < 5; {
fmt.Println(i)
i++
}
for循环省略全部条件:可以将for循环的条件部分省略,相当于一个无限循环。
i := 0
for {
fmt.Println(i)
i++
if i >= 5 {
break
}
}
在循环中,我们常常会用到 range 来遍历可迭代的数据结构。
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
range循环会返回两个值,一个是索引(或键),另一个是对应索引(或键)的元素值。可以使用空白标识符_来忽略其中的某个值。
for _, value := range numbers {
fmt.Println(value)
}
range循环也可以用于遍历映射的键值对。
mymap := map[string]int{"apple": 1, "banana": 2, "orange": 3}
for key, value := range mymap {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
在for循环和range循环中,我们可以使用break和continue关键字来控制循环的执行流程,实现各种不同的逻辑。
函数
函数的声明和定义
使用func关键字来声明和定义一个函数。函数的声明包括函数名、参数列表和返回值。如果函数没有返回值,则可以省略返回值的部分。
func add(a, b int) int {
return a + b
}
func greet(name string) {
fmt.Println("Hello, " + name)
}
调用函数
要调用一个函数,只需要写出函数名并传递必要的参数。如果函数有返回值,可以使用变量接收返回值。
result := add(5, 3)
greet("Alice")
多返回值
Go语言中的函数可以返回多个值。多返回值在很多场景下非常有用,例如可以返回结果和错误,或者返回多个计算结果。
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
匿名函数
在Go中,可以创建匿名函数(没有名字的函数),并将其分配给变量。匿名函数通常用于简短的代码块,例如作为函数参数传递或在闭包中使用。
add := func(a, b int) int {
return a + b
}
result := add(3, 5)
变长参数
Go语言支持变长参数(可变参数),通过在参数类型前加上三个点(...)来指示。这允许函数接受任意数量的参数。
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
result := sum(1, 2, 3, 4, 5) // result = 15
函数作为参数和返回值
在Go中,函数可以作为参数传递给其他函数,也可以作为函数的返回值。
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
func calculate(op func(int, int) int, a, b int) int {
return op(a, b)
}
result1 := calculate(add, 5, 3) // result1 = 8
result2 := calculate(sub, 5, 3) // result2 = 2
错误处理
Go语言中的错误处理是一种机制,用于处理函数执行中可能发生的错误情况。在Go中,错误是一个接口类型(error),它通常用于表示函数是否执行成功以及错误的具体信息。
错误的定义
在Go中,error接口是一个预定义的接口,它只有一个方法:
type error interface {
Error() string
}
一个实现了error接口的类型可以表示一个错误。通常情况下,返回error类型的值表示函数执行失败,返回nil表示函数执行成功。
函数返回错误
在函数执行过程中,如果发生错误,可以通过返回error类型的值来表示错误。函数可以返回一个非nil的error值,用于指示函数执行失败,并携带错误信息。
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
处理错误
在调用可能返回错误的函数时,通常需要检查错误并根据不同的错误情况采取相应的处理措施。
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
自定义错误
除了使用errors.New()函数创建基本的错误信息,我们还可以定义自己的错误类型,只需要实现error接口的Error()方法即可。
type MyError struct {
message string
}
func (e *MyError) Error() string {
return e.message
}
func doSomething() error {
return &MyError{"Something went wrong"}
}
处理多个错误
在实际编程中,一个函数可能会返回多个错误,通常使用多个if语句或switch语句来处理不同的错误情况。
result1, err1 := doSomething()
result2, err2 := doAnotherThing()
if err1 != nil {
fmt.Println("Error 1:", err1)
}
if err2 != nil {
fmt.Println("Error 2:", err2)
}
// 继续处理其他结果
常用特性解析
Defer 语句 在Go语言中,defer语句用于在函数返回之前执行一些操作。这些操作可能包括资源释放、文件关闭、锁解锁等。defer语句通常用于确保某些操作在函数返回前一定会被执行,不管函数是否发生了错误或提前返回。
使用defer关键字,可以在函数中的任意位置注册一个需要在函数退出时执行的操作。defer语句后面跟随一个函数调用。
func doSomething() {
fmt.Println("Doing something...")
defer fmt.Println("Operation completed.")
}
在上面的例子中,当doSomething函数执行完成时,无论函数是正常结束还是发生了错误,Operation completed.都会被打印出来。
defer 的执行顺序
如果在函数中有多个defer语句,它们的执行顺序与注册的顺序相反。即最后注册的defer语句将最先执行,最先注册的defer语句将最后执行。
func printMessage(msg string) {
defer fmt.Println("Deferred 2")
defer fmt.Println("Deferred 1")
fmt.Println(msg)
}
func main() {
printMessage("Hello, World!")
}
输出结果为:
Hello, World!
Deferred 1
Deferred 2
defer语句与参数的求值: 在注册defer语句时,会对其参数进行求值,但实际执行是在函数返回前。这可能会导致一些潜在的问题,例如当参数是函数调用时,可能导致不符合预期的结果。
func main() {
x := 0
defer fmt.Println(x) // 输出 0,因为此时 x 的值为 0
x++
}
常见用途:
关闭文件:在打开文件后使用defer语句关闭文件,确保文件在函数返回前被关闭,从而避免资源泄漏。
释放锁:在使用锁时,可以在加锁后使用defer语句来释放锁,确保锁的正确使用。
数据库连接的关闭:在使用数据库连接时,使用defer语句关闭连接,确保连接得到释放。