Go语言基础1
简介
-
Go语言特点
-
高性能、高并发
-
语法简单、学习曲线平缓
//创建一个静态文件服务器 package main import ( "net/http" ) func main(){ http.Handle("/",http.FileServer(http.Dir(".")))//注册了一个处理器,将根 URL "/" 映射到文件服务器。http.FileServer() 函数创建一个文件服务器处理器,它会将请求映射到当前目录(.)中的文件。换句话说,它会将当前目录下的文件暴露给客户端,允许客户端通过 HTTP 访问这些文件。 http,ListenAndServe(":8080",nil)//启动了 HTTP 服务器并监听来自 8080 端口的请求。http.ListenAndServe() 函数在指定的端口上启动一个 HTTP 服务器,并且它会一直运行,直到遇到错误或显式地被关闭。 } -
丰富的标准库
-
完善的工具链
-
静态链接
-
快速编译
-
跨平台
-
垃圾回收
-
入门
基础语法
-
Hello World
package main import "fmt" func main() { fmt.Println("hello,world") }package main: 这是 Go 语言中的包声明。每个 Go 程序都必须有一个main包。main包是一个特殊的包,它标识了这是一个可执行程序而不是一个库。import "fmt": 这是导入所需的包。在这里,我们导入了fmt包,它是 Go 标准库中的格式化(I/O)包。它提供了在终端输出内容和格式化输出的功能。func main() { ... }: 这是程序的入口点。Go 语言中的每个可执行程序都必须包含一个main()函数,它会在程序启动时自动调用。fmt.Println("hello, world"): 这行代码使用fmt包中的Println()函数将字符串 "hello, world" 输出到控制台(终端)。
-
变量与常量
-
Go 语言中的基本变量类型:
类型 描述 取值范围 int 有符号整数 -2147483648 到 2147483647(32位系统) -9223372036854775808 到 9223372036854775807(64位系统) int8 8位有符号整数 -128 到 127 int16 16位有符号整数 -32768 到 32767 int32 32位有符号整数 -2147483648 到 2147483647 int64 64位有符号整数 -9223372036854775808 到 9223372036854775807 uint 无符号整数 0 到 4294967295(32位系统) 0 到 18446744073709551615(64位系统) uint8 8位无符号整数 0 到 255 uint16 16位无符号整数 0 到 65535 uint32 32位无符号整数 0 到 4294967295 uint64 64位无符号整数 0 到 18446744073709551615 float32 32位浮点数 约 -3.4e38 到 3.4e38 float64 64位浮点数 约 -1.7e308 到 1.7e308 complex64 由两个 32 位浮点数组成的复数 实部和虚部都是 32 位浮点数 complex128 由两个 64 位浮点数组成的复数 实部和虚部都是 64 位浮点数 bool 布尔类型 true 或 false string 字符串类型 一个 UTF-8 字符序列 byte 字节类型,uint8 的别名 0 到 255 rune 符文类型,int32 的别名 一个 Unicode 码点 -
变量声明方式
在 Go 语言中,变量的声明方式有两种:短变量声明和常规变量声明。
-
短变量声明:短变量声明使用
:=运算符,可以在函数内部创建新的变量并对其赋值。这种声明方式只能在函数内部使用,不能用于全局变量。func main() { name := "pidan" age := 23 fmt.Println(name, age) } -
常规变量声明:常规变量声明使用
var关键字,可以在全局范围或函数内部声明变量。如果在全局范围声明变量,则该变量可以在整个包内使用。// 全局变量声明 var globalName string = "pidan" func main() { // 函数内部变量声明 var localAge int = 23 fmt.Println(globalName, localAge) }对于全局变量声明,也可以使用简短形式,但需要注意全局变量的简短形式必须使用
var关键字,并且不能使用:=运算符。// 全局变量声明的简短形式 var globalName = "pidan" func main() { // 函数内部变量声明的简短形式 localAge := 23 fmt.Println(globalName, localAge) }无论是短变量声明还是常规变量声明,Go 语言都会根据变量值的类型自动推断变量的类型。如果变量在声明时已经赋值,那么 Go 编译器将根据右侧表达式的类型来确定变量的类型。例如,
age := 23会将age推断为int类型。如果变量声明时没有赋值,那么需要显式指定变量类型,例如var name string。
-
-
常量声明
在 Go 语言中,可以使用
const关键字来声明常量。常量是在程序运行时不可更改的值,其值在编译时就已经确定。以下是一些常量声明的示例:const pi = 3.14 const age = 23 const name = "pidan"在 Go 语言中,常量的作用范围是包级别的,也就是说,常量在整个包内都可以访问。如果希望常量只在特定的代码块内可见,可以使用块级常量(在代码块内使用
const关键字声明)。
-
-
if-else
在 Go 语言中,
if-else是一种条件控制语句,用于在程序中根据条件的真假来执行不同的代码块。if后面的条件表达式会被求值,如果条件为真,则执行if代码块中的语句;如果条件为假,则执行else代码块中的语句(如果有的话)。if-else的一般语法结构如下:if condition { // 条件为真时执行的代码块 } else { // 条件为假时执行的代码块 }其中,
condition是一个布尔表达式,可以是一个返回布尔值的条件判断。func main() { score := 100 if score >= 120 { fmt.Println("优秀") } else if score >= 90 { fmt.Println("良好") } else if score >= 75 { fmt.Println("及格") } else { fmt.Println("不及格") } }在这个例子中,根据
score变量的值,判断学生成绩的等级。根据条件的不同,输出不同的等级。需要注意的是,
if和else后面的代码块必须用花括号{}括起来,即使代码块只有一行语句也不能省略。另外,在条件判断时,条件表达式的结果必须是布尔值,不能是其他类型。在 Go 中,没有隐式的类型转换。 -
循环
for循环是 Go 语言中最常用的循环语句,用于重复执行一段代码块,直到指定的条件不再满足。for循环有两种形式:基本的for循环和无限循环。- 基本的 for 循环:
for 初始化语句; 条件表达式; 循环后操作 { // 循环体 }func main() { for i := 1; i <= 5; i++ { fmt.Println(i) } }在这个例子中,
for循环会从 1 开始,每次递增 1,打印出 1 到 5。- 无限循环:
for { // 循环体 }func main() { for { fmt.Println("无限") } }在这个例子中,使用无限循环的形式,
for循环将一直执行循环体中的代码,形成无限循环。要注意的是,这种无限循环必须通过break或其他条件来终止,否则程序将一直执行下去。在循环中,可以使用
break关键字来提前结束循环,也可以使用continue关键字来跳过本次循环,进入下一次循环。循环体内的代码块必须用花括号{}括起来,即使代码块只有一行语句也不能省略。 -
switch
在 Go 语言中,
switch是一种条件控制语句,用于根据表达式的不同值执行不同的代码块。switch语句可以取代一系列连续的if-else语句,使代码更加简洁易读。switch语句可以用于处理多个选项,以及默认情况下的处理。switch的一般语法结构如下:switch expression { case value1: // 当 expression 的值等于 value1 时执行这里的代码 case value2: // 当 expression 的值等于 value2 时执行这里的代码 // 更多 case ... default: // 如果 expression 的值不匹配任何 case 时执行这里的代码 }其中,
expression是一个表达式,它的值将与每个case的值进行比较。如果expression的值与某个case的值相匹配,就会执行该case下的代码块。如果没有匹配到任何case,就会执行default下的代码块(可选的)。func main() { num := 2 switch num { case 1: fmt.Println("1") case 2: fmt.Println("2") case 3: fmt.Println("3") default: fmt.Println("4") } } 需要注意的是,在 Go 语言的
switch语句中,每个case的值必须是唯一的,不能有重复。而且,每个case下的代码块会自动终止,不需要显式地使用break关键字来跳出switch块。如果希望继续执行下一个case,可以使用fallthrough关键字。func main() { num := 2 switch num { case 1: fmt.Println("1") fallthrough case 2: fmt.Println("2") case 3: fmt.Println("3") } }在这个例子中,由于
num的值是 2,它会匹配case 2,输出 "数字是 2",同时由于有fallthrough关键字,会继续执行下一个case,输出 "数字是 3"。 -
数组
在 Go 语言中,数组是一种固定长度、类型相同的数据结构,它可以用来存储一组相同类型的元素。数组的长度在创建时就确定,并且不能动态地改变大小。Go 语言中的数组声明的语法如下:
var name [len]Type其中,
name是数组的名称,len是数组的长度(即可以存储的元素个数),Type是数组中元素的类型。var numbers [5]int上面的代码声明了一个包含 5 个整数的数组,数组名为
numbers,元素类型为int。数组的索引从 0 开始,到
length-1结束。可以使用索引来访问数组中的元素。func main() { var numbers [5]int numbers[0] = 10 numbers[1] = 20 numbers[2] = 30 numbers[3] = 40 numbers[4] = 50 fmt.Println(numbers[0]) // 输出 10 fmt.Println(numbers[2]) // 输出 30 }在这个例子中,声明了一个长度为 5 的整数数组
numbers,并对数组中的元素进行赋值。通过索引访问数组元素,例如numbers[0]将输出 10。如果在声明数组时不指定具体的元素值,Go 语言会使用相应类型的零值来初始化数组。
func main() { var numbers [5]int fmt.Println(numbers) // 输出 [0 0 0 0 0] }除了上面的示例中使用的数组初始化方式,还可以使用值来声明和初始化数组。
func main() { // 使用值初始化数组 numbers := [5]int{10, 20, 30, 40, 50} fmt.Println(numbers) // 输出 [10 20 30 40 50] }数组的长度是数组类型的一部分,因此不同长度的数组是不同的类型,所以不能直接将长度为 5 的数组赋值给长度为 3 的数组。如果你需要动态大小的数组,可以使用切片(slice)类型。切片是 Go 语言中更加灵活和常用的数据结构,它相比于数组更具扩展性。
-
切片
在 Go 语言中,切片(Slice)是一种动态数组,它提供了更灵活的数组操作和动态大小的功能。切片允许你对数组进行部分或整体操作,而无需重新创建一个新的数组。切片是对底层数组的引用,因此对切片的修改会影响到底层数组的内容。
切片的声明语法如下:
var name []Type其中,
name是切片的名称,Type是切片中元素的类型。不同于数组,切片的长度不固定,它会根据需要动态增长或缩小。切片的创建有几种方式:
- 使用
make函数创建切片:
slice := make([]Type, len, cap)Type是切片中元素的类型。len是切片的初始长度,表示当前切片中的元素个数。cap是可选参数,表示底层数组的容量,即底层数组最多能容纳的元素个数。如果不指定cap,则默认与length相同。
func main() { // 创建一个长度为 3,容量为 5 的整数切片 slice := make([]int, 3, 5) fmt.Println(slice) // 输出 [0 0 0] fmt.Println(len(slice)) // 输出 3 fmt.Println(cap(slice)) // 输出 5 }- 使用值初始化切片:
slice := []Type{value1, value2, ...}func main() { // 使用值初始化切片 slice := []int{10, 20, 30, 40, 50} fmt.Println(slice) // 输出 [10 20 30 40 50] fmt.Println(len(slice)) // 输出 5 fmt.Println(cap(slice)) // 输出 5 }切片也可以通过索引来访问和修改元素,类似于数组。它还提供了一些有用的内置函数,如
append()用于在切片末尾追加元素,copy()用于复制切片内容等。切片是动态大小的,它会根据需要自动扩容。当切片长度超过容量时,Go 语言会自动分配更大的底层数组,并将原有的元素复制到新的底层数组中。因此,切片在底层是对数组的引用,而不会像数组一样存在大小固定的限制。总之,切片是 Go 语言中非常重要且常用的数据结构,用于处理动态大小的数据集合。 - 使用
-
map
在 Go 语言中,
map是一种无序的键值对(key-value)集合,也被称为字典。map提供了一种快速查找的数据结构,可以根据键(key)来获取对应的值(value)。map中的键必须是唯一的,并且是支持相等运算符(==)的类型(基本类型、字符串、指针、数组、结构体等),而值可以是任意类型。map的声明语法如下:var name map[keyType]valueType其中,
name是map的名称,keyType是键的类型,valueType是值的类型。func main() { // 声明一个键为 string 类型,值为 int 类型的 map scores := make(map[string]int) // 添加键值对 scores["Alice"] = 90 scores["Bob"] = 85 scores["Eve"] = 95 // 获取值 fmt.Println("Alice's score:", scores["Alice"]) // 输出 "Alice's score: 90" // 删除键值对 delete(scores, "Bob") // 遍历 map for name, score := range scores { fmt.Println(name, "scored", score) } }在上面的示例中,创建了一个
scores的map,键是字符串类型,值是整数类型。使用scores["Alice"]的方式给键值对中添加了元素,使用delete()函数删除了一个键值对。最后,使用for range循环遍历了map中的所有键值对。需要注意的是,如果对于一个不存在的键访问其值,
map会返回值类型的零值。如果想检查键是否存在,可以使用多返回值的方式,通过判断第二个返回值(布尔值)来确定键是否存在。func main() { scores := make(map[string]int) scores["Alice"] = 90 scores["Eve"] = 95 // 检查键是否存在 score, exists := scores["Bob"] if exists { fmt.Println("Bob's score:", score) } else { fmt.Println("Bob's score not found") } }在这个示例中,
Bob这个键不存在于scores的map中,所以exists将为false,并输出 "Bob's score not found"。map是一种引用类型,当将一个map赋值给另一个变量时,它们指向同一个底层数据结构,因此对其中一个变量的修改会影响到另一个变量。 -
函数
-
函数定义: 在Go语言中,使用
func关键字定义函数。函数定义的一般形式如下:func 函数名(参数列表) 返回值列表 { // 函数体 }函数名:标识函数的名称,要遵循标识符规则。参数列表:用于接收调用函数时传递的参数。可以有零个或多个参数,每个参数由参数名和参数类型组成,多个参数之间使用逗号分隔。返回值列表:用于指定函数返回的结果。可以有零个或多个返回值,每个返回值由返回值类型组成,多个返回值之间使用逗号分隔。
-
函数调用: 调用函数时,通过函数名和实参列表来调用函数。如果函数有返回值,可以将其赋值给变量。
result := 函数名(参数1, 参数2, ...) -
无参数函数: 如果函数不需要接收任何参数,函数名后面的括号保持空白。
func sayHello() { fmt.Println("Hello!") } -
无返回值函数: 如果函数没有返回值,返回值列表可以省略。
func printSum(a, b int) { sum := a + b fmt.Println(sum) } -
多返回值函数: 在Go语言中,函数可以返回多个值。
func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } -
命名返回值: 函数定义时可以为返回值命名,使得在函数体内可以直接使用这些变量,并且可以通过
return语句省略明确指定返回值。func divide(a, b float64) (result float64, err error) { if b == 0 { err = errors.New("division by zero") return } result = a / b return } -
可变参数: 使用
...语法可以创建可变参数的函数,允许函数接受任意数量的参数。func sum(nums ...int) int { total := 0 for _, num := range nums { total += num } return total } -
匿名函数: 在Go语言中,可以创建匿名函数并将其赋值给变量。这些函数可以像其他变量一样传递、调用和返回。
add := func(a, b int) int { return a + b } -
闭包: 一个函数和与其相关的引用环境组合而成的实体,闭包=函数+引用环境。 在Go语言中,函数可以形成闭包。闭包是指捕获其所在函数内部变量的函数。闭包允许函数访问和操作在其外部作用域声明的变量。
func counter() func() int { count := 0 return func() int { count++ return count } }
-
指针
指针是一种特殊的变量类型,它存储了一个变量的内存地址。使用指针可以在程序中间直接操作变量所在的内存地址,从而避免数据的拷贝,提高程序的执行效率。在Go语言中,通过使用
&操作符可以获取变量的内存地址,通过*操作符可以解引用指针,即获取指针指向的变量的值。- 创建指针
var num int = 42 var ptr *int // 声明一个整数类型的指针变量 ptr = &num // 将num的内存地址赋值给指针ptr- 解引用指针
var value int = *ptr // 从指针ptr获取它指向的变量的值,并赋值给value- 使用指针修改变量的值
*ptr = 100 // 通过指针ptr修改它指向的变量的值,此时num的值也会被修改为100- 空指针
在Go语言中,指针的零值是
nil,表示指针不指向任何有效的内存地址。var ptr *int = nil- 传递指针给函数
可以将指针作为参数传递给函数,这样函数可以直接修改指针指向的变量的值。
func modifyValue(ptr *int) { *ptr = 200 } // 使用 modifyValue(&num)注意事项
- 指针使用时需要确保指针指向的变量已经被初始化,否则会导致运行时错误。
- 在使用指针之前,通常需要进行非空判断,以避免空指针引发的错误。
-
-
结构体
结构体是一种自定义数据类型,它允许将不同类型的数据组合在一起形成一个新的数据类型。通过使用
type关键字和大括号{}来定义一个结构体。-
创建结构体
// 定义一个结构体类型 type Person struct { Name string Age int Address string } -
创建结构体变量
// 使用结构体类型创建结构体变量 var p1 Person p1.Name = "Alice" p1.Age = 30 p1.Address = "123 Main St" -
结构体初始化
// 使用字面值初始化结构体 p2 := Person{ Name: "Bob", Age: 25, Address: "456 Elm St", } -
结构体指针
可以使用结构体指针来操作结构体变量,避免数据的拷贝。
// 创建结构体指针 var ptr *Person ptr = &p1 // 通过指针修改结构体字段的值 ptr.Age = 31 -
匿名结构体
可以直接定义匿名结构体,通常用于临时存储数据。
// 定义匿名结构体并初始化 data := struct { ID int Message string }{ ID: 1, Message: "Hello, World!", } -
结构体嵌套
结构体可以嵌套其他结构体,形成复杂的数据结构。
type Address struct { City string State string } type Person struct { Name string Age int Address Address } // 使用嵌套结构体 p := Person{ Name: "John", Age: 35, Address: Address{ City: "New York", State: "NY", }, }
-
-
结构体方法
方法是一种特殊类型的函数,它和结构体关联在一起,可以用于对结构体数据进行操作和处理。通过结构体方法,可以将数据和操作封装在一起,使代码更加模块化和易于维护。在Go语言中,通过在函数名前添加接收者(receiver)来定义结构体方法。接收者相当于方法的调用者,用于绑定方法与特定的结构体类型。
-
定义结构体类型
// 定义一个结构体类型 type Rectangle struct { Width float64 Height float64 } -
定义结构体方法
// 定义一个计算矩形面积的方法,接收者为Rectangle类型 func (r Rectangle) Area() float64 { return r.Width * r.Height } -
调用结构体方法
// 创建Rectangle结构体变量 rect := Rectangle{ Width: 10, Height: 5, } // 调用结构体方法计算面积 area := rect.Area() -
指针接收者的方法
使用指针接收者可以在方法中修改结构体数据,而不仅仅是对副本进行操作。
// 定义一个修改矩形高度的方法,接收者为指向Rectangle的指针 func (r *Rectangle) SetHeight(height float64) { r.Height = height } -
调用指针接收者的方法
// 创建Rectangle结构体指针变量 rectPtr := &Rectangle{ Width: 8, Height: 6, } // 调用指针接收者的方法修改高度 rectPtr.SetHeight(7) -
值接收者 vs. 指针接收者
- 值接收者:用于不需要修改结构体数据的场景,避免不必要的内存拷贝。
- 指针接收者:用于需要修改结构体数据的场景,可以避免数据的拷贝。
注意:结构体方法的接收者类型必须在同一包内定义,不能对来自其他包的类型定义方法。
-
-
错误处理
在Go语言中,错误通常由函数的返回值来表示,通常是最后一个返回值,类型为
error。-
定义错误类型
在标准库中,错误通常由内置的
errors包来处理,通过errors.New函数可以创建一个新的错误。import "errors" func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } -
返回错误值
在函数中,当发生错误的时候,使用
return语句返回错误值,同时将结果设为零值或合适的默认值。func doSomething() error { if someCondition { return errors.New("something went wrong") } // ... 正常情况下的处理 ... return nil } -
调用函数并处理错误
在调用可能返回错误的函数时,需要显式地检查错误,并根据错误情况采取相应的处理措施。
result, err := divide(10.0, 2.0) 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{Message: "something went wrong"} }
-
-
字符串
-
创建字符串
在Go语言中,可以使用双引号
""或反引号`来创建字符串。str1 := "Hello, World!" // 使用双引号创建字符串 str2 := `Hello, Go!` // 使用反引号创建字符串,支持多行字符串 -
字符串长度
可以使用
len()函数获取字符串的字节长度(不是字符数)。length := len(str1) // 获取字符串str1的字节长度 -
字符串拼接
可以使用
+运算符来拼接字符串。greeting := "Hello, " name := "Alice" message := greeting + name // 将两个字符串拼接为一个新的字符串 -
字符串索引
可以使用下标运算符
[]来访问字符串中的单个字节。char := str1[0] // 获取字符串str1中第一个字节(即第一个字符) -
字符串遍历
可以使用
for range循环遍历。for _, char := range str1 { // 处理每个字符 fmt.Println(char) } -
字符串切片
可以使用切片操作截取字符串的子串。
subStr := str1[0:5] // 截取str1中从索引0到索引4的子串(不包含索引5) -
字符串比较
可以使用
==和!=运算符来比较字符串是否相等。str1 := "hello" str2 := "Hello" isEqual := (str1 == str2) // 比较字符串str1和str2是否相等 -
注意事项
- 字符串是不可变的,一旦创建就不能直接修改其内容,任何修改操作都会创建一个新的字符串。
- 使用
rune类型处理Unicode字符可能更加方便,因为rune类型可以表示一个Unicode码点。
-