通过示例学习-Go-语言-2023-三-
通过示例学习 Go 语言 2023(三)
Go (Golang) 中的所有设计模式
一份关于在 Go 中实现的设计模式的整理列表。如果你有兴趣学习 Golang,我们还提供了一系列全面的 Golang 教程,欢迎查看 – Golang 综合教程系列
目录
** 创建型设计模式
-
行为设计模式
-
结构设计模式
创建型设计模式
抽象工厂 |
---|
构建者 |
工厂 |
对象池 |
原型 |
单例 |
行为设计模式
责任链 |
---|
命令 |
迭代器 |
中介者 |
备忘录 |
空对象 |
观察者 |
状态 |
策略 |
模板方法 |
访问者 |
结构设计模式
适配器 |
---|
桥接 |
组合 |
装饰器 |
外观 |
享元模式 |
| 代理 |*
Go(Golang)中通道的所有操作/函数。
目录
-
概述
-
发送操作
-
接收操作
-
通道的关闭操作
-
使用 len() 函数的通道长度 函数的通道长度")
-
使用 cap() 函数的通道容量 函数的通道容量")
概述
下面的操作/函数适用于 Golang 中的通道。
-
将数据发送到通道。
-
从通道接收数据。
-
关闭通道。
-
使用 len() 函数的通道长度。
-
使用 cap() 函数的通道容量。
让我们逐一看一下通道上的每个操作/函数。
发送操作
发送操作用于将数据发送到通道。以下是发送到通道的格式。
ch <- data
在哪里
-
ch 是通道变量。
-
数据是发送到通道的内容。
请注意,数据类型和通道类型应匹配。
发送操作对于缓冲或无缓冲通道可以以下列方式阻塞。
-
缓冲通道 - 仅在缓冲区满时,发送到缓冲通道才会阻塞。
-
无缓冲通道 - 发送到通道将阻塞,除非有另一个 goroutine 来接收。
在我们理解接收操作后,让我们看看发送操作的程序。
接收操作
接收操作用于从通道读取数据。以下是从通道接收的格式。
data := <- ch
在哪里
-
ch 是通道变量。
-
data 是一个变量,用于存储从通道读取的数据。
让我们看一个示例,其中我们将从一个 goroutine 发送数据,并在另一个 goroutine 中接收这些数据。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
fmt.Println("Sending value to channnel")
go send(ch)
fmt.Println("Receiving from channnel")
go receive(ch)
time.Sleep(time.Second * 1)
}
func send(ch chan int) {
ch <- 1
}
func receive(ch chan int) {
val := <-ch
fmt.Printf("Value Received=%d in receive function\n", val)
}
输出
Sending value to channel
Receiving from channel
Value Received=1 in receive function
在上面的程序中,我们创建了一个只能传输 int 类型数据的通道。函数 send() 和 receive() 作为 goroutine 启动。我们在 send() goroutine 中向通道发送数据,并在 receive() goroutine 中接收数据。
接收操作对于缓冲或无缓冲通道可以以下列方式阻塞。
-
缓冲通道 - 仅在通道为空时接收才会阻塞。
-
无缓冲通道 - 接收会阻塞,直到另一侧有另一个 goroutine 发送。
通道的关闭操作
Close 是一个内置函数,可用于关闭通道。关闭通道意味着不能再向通道发送数据。当所有数据发送完毕且没有更多数据可发送时,通道通常会关闭。让我们看看一个程序。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go sum(ch, 3)
ch <- 2
ch <- 2
ch <- 2
close(ch)
time.Sleep(time.Second * 1)
}
func sum(ch chan int, len int) {
sum := 0
for i := 0; i < len; i++ {
sum += <-ch
}
fmt.Printf("Sum: %d\n", sum)
}
输出
Sum: 6
在上面的程序中,我们创建了一个通道。然后我们在一个 goroutine 中调用了sum函数。在主函数中,我们向通道发送了 3 个值,之后关闭了通道,表示不能再向通道发送更多的值。sum函数使用 for 循环迭代通道并计算总和。
在关闭的通道上发送将导致恐慌。请参见下面的程序。
package main
func main() {
ch := make(chan int)
close(ch)
ch <- 2
}
输出
panic: send on closed channel
关闭一个已关闭的通道将导致恐慌
在从通道接收时,我们还可以使用一个额外的变量来确定通道是否已关闭。以下是相应的语法。
val,ok <- ch
ok 的值将是
-
如果通道未关闭则为真
-
每个通道都已关闭为假
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 2
val, ok := <-ch
fmt.Printf("Val: %d OK: %t\n", val, ok)
close(ch)
val, ok = <-ch
fmt.Printf("Val: %d OK: %t\n", val, ok)
}
输出
Val: 2 OK: true
Val: 0 OK: false
在上面的程序中创建了一个容量为 1 的通道。然后我们向通道发送了一个值。在第一次接收中,ok变量为 true,因为通道未关闭。在第二次接收中,ok 变量为 false,因为通道已关闭。
使用 len()函数获取通道的长度
内置的len()函数可用于获取通道的长度。通道的长度是通道中已有元素的数量。因此,长度实际上表示通道缓冲区中排队的元素数量。通道的长度始终小于或等于通道的容量。
无缓冲通道的长度始终为零
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 5
fmt.Printf("Len: %d\n", len(ch))
ch <- 6
fmt.Printf("Len: %d\n", len(ch))
ch <- 7
fmt.Printf("Len: %d\n", len(ch))
}
输出
Len: 1
Len: 2
Len: 3
在上面的代码中,首先创建了一个容量为 3 的通道。之后,我们持续向通道发送一些值。从你的输出中可以注意到,在每次发送操作后,通道的长度增加了 1,因为长度表示通道缓冲区中的项目数量。
使用 cap()函数获取通道的容量
缓冲通道的容量是通道可以容纳的元素数量。容量指的是通道缓冲区的大小。通道的容量可以在创建通道时通过使用 make 函数指定。第二个参数是容量。
无缓冲通道的容量始终为零
package main
import "fmt"
func main() {
ch := make(chan int, 3)
fmt.Printf("Capacity: %d\n", cap(ch))
}
输出
Capacity: 3
在上面的程序中,我们在 make 函数中指定了容量为 3。
make(chan int, 3)
让我们来看一下总结表,展示了对不同类型通道的每个操作结果
命令 | 无缓冲通道(未关闭且不为 nil) | 缓冲通道(未关闭且不为 nil) | 已关闭通道 | 空通道 |
---|---|---|---|---|
发送 | 如果没有相应的接收者则阻塞,否则成功 | 如果通道已满则阻塞,否则成功 | 恐慌 | 永久阻塞 |
接收 | 如果没有相应的发送者则阻塞,否则成功 | 如果通道为空则阻塞,否则成功 | 如果通道为空则接收数据类型的默认值,否则接收实际值 | 永久阻塞 |
关闭 | 成功 | 成功 | 恐慌 | 恐慌 |
长度 | 0 | 在通道缓冲区中排队的元素数量 | - 如果是无缓冲通道则为 0 - 如果是有缓冲通道则为缓冲区中的排队元素数量 | 0 |
容量 | 0 | 通道缓冲区的大小 | - 如果是无缓冲通道则为 0 - 如果是有缓冲通道则为缓冲区大小 | 0 |
Golang 中字符串的所有排列
在 Golang 中,字符串是一系列字节。字符串字面量实际上表示的是 UTF-8 字节序列。在 UTF-8 中,ASCII 字符是单字节,对应前 128 个 Unicode 字符。所有其他字符的字节数在 1 到 4 之间。因此,不可能在字符串中对字符进行索引。在 GO 中,rune 数据类型表示一个 Unicode 点。一旦字符串被转换为 rune 数组,就可以对该数组中的字符进行索引。
你可以在这里了解更多关于符文的信息 – golangbyexample.com/understanding-rune-in-golang
出于这个原因,在下面的程序中生成排列时,我们首先将字符串转换为 rune 数组,以便能够对 rune 数组进行索引,从而获取单个字符。
package main
import "fmt"
func main() {
sample := "ab£"
sampleRune := []rune(sample)
generatePermutation(sampleRune, 0, len(sampleRune)-1)
}
func generatePermutation(sampleRune []rune, left, right int) {
if left == right {
fmt.Println(string(sampleRune))
} else {
for i := left; i <= right; i++ {
sampleRune[left], sampleRune[i] = sampleRune[i], sampleRune[left]
generatePermutation(sampleRune, left+1, right)
sampleRune[left], sampleRune[i] = sampleRune[i], sampleRune[left]
}
}
}
输出:
ab£
a£b
ba£
b£a
£ba
£ab
Go(Golang)中地图允许的键和值类型
地图是 Golang 内置数据类型,类似于哈希表,用于将键映射到值。以下是地图的格式:
map[key_type]value_type
key_type和value_type可以是不同类型或相同类型。在下面的示例中,键类型是字符串,值类型是整型
map[string]int
地图中允许的键类型
地图键可以是任何可比较的类型。根据 Go 规范定义的一些可比较类型是
-
布尔值
-
数值
-
字符串,
-
指针
-
通道
-
接口类型
-
结构体 – 如果它的所有字段类型都是可比较的
-
数组 – 如果数组元素的值类型是可比较的
根据 Go 规范,一些不可比较的类型不能作为地图中的键使用。
-
切片
-
地图
-
函数
参考 – golang.org/ref/spec#Comparison_operators
地图中允许的值类型
值可以是地图中的任何类型。
Go(Golang)中的正则表达式的交替(OR)
目录
-
概述
-
程序
概述
它类似于 OR 操作。通过使用|运算符连接两个正则表达式。如果有两个正则表达式r1和r2,则交替表示如下
r1|r2
它将匹配r1或r2,优先考虑r1。基本上,如果字符串s匹配正则表达式r1,字符串t匹配正则表达式r2,那么r1|r2将匹配s或t。当我们说将优先考虑r1时,这意味着如果在给定的样本字符串中,首先会尝试匹配r1,如果找不到r1,则会尝试匹配r2。
程序
package main
import (
"fmt"
"regexp"
)
func main() {
sampleRegex := regexp.MustCompile("abc|xyz")
match := sampleRegex.Match([]byte("abc"))
fmt.Println(match)
match = sampleRegex.Match([]byte("xyz"))
fmt.Println(match)
match = sampleRegex.Match([]byte("abcxyz"))
fmt.Println(match)
match = sampleRegex.Match([]byte("abd"))
fmt.Println(match)
}
输出
true
true
true
false
它匹配
abc
xyz
它也匹配。
abcxyz
这是因为它匹配前缀“abc”并给出了真实匹配。
此外,它不匹配
abd
交替也可以在超过两个正则表达式之间进行。下面是一个示例
package main
import (
"fmt"
"regexp"
)
func main() {
sampleRegex := regexp.MustCompile("abc|xyz|123")
match := sampleRegex.Match([]byte("abc"))
fmt.Println(match)
match = sampleRegex.Match([]byte("xyz"))
fmt.Println(match)
match = sampleRegex.Match([]byte("123"))
fmt.Println(match)
match = sampleRegex.Match([]byte("abcxyz123"))
fmt.Println(match)
match = sampleRegex.Match([]byte("abd"))
fmt.Println(match)
}
输出
true
true
true
true
false
它匹配
abc
xyz
123
它也匹配
abcxyz123
这是因为它匹配前缀“abc”并给出了真实匹配。
此外,它不匹配
abd
此外,请查看我们的 Golang 高级教程系列——Golang 高级教程
在 Go(Golang)中检查键是否存在于映射中的有效方法。
以下是检查键是否存在于映射中的格式。
val, ok := mapName[key]
有两种情况。
-
如果键存在,val 变量将是映射中键的值,而 ok 变量将为 true。
-
如果键不存在,val 变量将是值类型的默认零值,而 ok 变量将为 false。
让我们来看一个例子。
package main
import "fmt"
func main() {
//Declare
employeeSalary := make(map[string]int)
//Adding a key value
employeeSalary["Tom"] = 2000
fmt.Println("Key exists case")
val, ok := employeeSalary["Tom"]
fmt.Printf("Val: %d, ok: %t\n", val, ok)
fmt.Println("Key doesn't exists case")
val, ok = employeeSalary["Sam"]
fmt.Printf("Val: %d, ok: %t\n", val, ok)
}
输出
Key exists case
Val: 2000, ok: true
Key doesn't exists case
Val: 0, ok: false
在上面的程序中,当键存在时,val 变量被设置为实际值,这里是 2000,而 ok 变量为 true。当键不存在时,val 变量被设置为 0,这是 int 的默认零值,而 ok 变量为 false。这个 ok 变量是检查键是否存在于映射中的最佳方法。
如果我们只想检查键是否存在而不需要 val,那么可以用空标识符“_”替代 val。
_, ok = employeeSalary["Sam"]
Go (Golang) 中的结构体中的匿名字段。
结构体也可以包含匿名字段,意味着一个字段没有名称。类型将成为字段名。在下面的示例中,string 也将作为字段名。
type employee struct {
string
age int
salary int
}
匿名字段也可以被访问并赋值。
package main
import "fmt"
type employee struct {
string
age int
salary int
}
func main() {
emp := employee{string: "Sam", age: 31, salary: 2000}
//Accessing a struct field
n := emp.string
fmt.Printf("Current name is: %s\n", n)
//Assigning a new value
emp.string = "John"
fmt.Printf("New name is: %s\n", emp.string)
}
输出
Current name is: Sam
New name is: John
嵌套结构体
结构体可以嵌套另一个结构体。让我们看看一个嵌套结构体的示例。在下面的 employee 结构体中,嵌套了 address 结构体。
package main
import "fmt"
type employee struct {
name string
age int
salary int
address address
}
type address struct {
city string
country string
}
func main() {
address := address{city: "London", country: "UK"}
emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
fmt.Printf("City: %s\n", emp.address.city)
fmt.Printf("Country: %s\n", emp.address.country)
}
输出
City: London
Country: UK
注意嵌套结构体字段是如何被访问的。
emp.address.city
emp.address.country
目录
**匿名嵌套结构体字段
匿名嵌套结构体字段
嵌套结构体字段也可以是匿名的。此外,在这种情况下,嵌套结构体的字段可以直接访问。因此,下面是有效的。
emp.city
emp.country
还需注意,下面的代码在这种情况下仍然有效。
emp.address.city
emp.address.country
让我们看一个程序。
package main
import "fmt"
type employee struct {
name string
age int
salary int
address
}
type address struct {
city string
country string
}
func main() {
address := address{city: "London", country: "UK"}
emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
fmt.Printf("City: %s\n", emp.address.city)
fmt.Printf("Country: %s\n", emp.address.country)
fmt.Printf("City: %s\n", emp.city)
fmt.Printf("Country: %s\n", emp.country)
}
输出
City: London
Country: UK
City: London
Country: UK
注意在上述程序中,地址结构体的城市字段可以通过两种方式访问。
emp.city
emp.address.city
地址结构体的国家字段也是类似的。
Go(Golang)中的匿名函数
目录
-
概述
-
代码
概述
正如名称所示,匿名函数是没有名称的函数。在 Golang 中,函数是第一类变量,这意味着
-
它们可以被赋值给一个变量
-
作为函数参数传递
-
从函数返回
在 Go 中,函数是第一类变量,因此它也可以作为值使用。当将函数作为值使用时,它没有名称,可以赋值给变量。这种函数被称为匿名函数,因为函数没有名称。
它们通常是为了短期使用或有限功能而创建的。请看下面的例子。
在这个例子中,max变量被赋值为一个函数。赋值给max的函数没有任何名称。调用这个函数的唯一方式是使用max变量,这也是我们在这个程序中所做的。
还要注意,我们可以向匿名函数传递参数,并从中返回值。
代码
package main
import "fmt"
var max = func(a, b int) int {
if a >= b {
return a
}
return b
}
func main() {
res := max(2, 3)
fmt.Println(res)
}
输出:
3
匿名函数也可以被执行为 IIF 或立即调用函数。在这种情况下,你不需要将它赋值给任何变量。请看下面的例子:
package main
import "fmt"
func main() {
func() {
fmt.Println("From anoymous function")
}()
}
输出:
From anoymous function
Go(Golang)中的 append 函数
Go 内置包提供了一个append函数,可以用于在切片末尾追加。以下是此函数的签名。
func append(slice []Type, elems ...Type) []Type
第一个参数是切片本身。第二个是可变数量的参数。
elems ...Type
‘…’运算符是可变参数语法。因此,基本上…Type意味着 append 函数可以接受类型为Type的可变数量参数。以下是使用此函数的方法。在下面的代码中,我们将 4 附加到一个包含两个元素的切片中。它会在末尾追加并返回原始切片。这就是为什么我们再次在numbers变量中收集结果。将结果分配给其他变量也是可以的。
numbers := []int{1,2}
numbers := append(numbers, 4) //Slice will become [1, 2, 3, 4]
追加多个元素也是可以的,因为第二个参数是可变参数。
numbers := []int{1,2}
numbers := append(numbers, 3, 4, 5) //Slice will become [1, 2, 3, 4, 5]
此函数在后台增加切片的长度和容量。总共有两种情况。
- 当切片长度小于容量时
在这种情况下,使用 append 函数时,切片的长度将增加 1,而容量不变。让我们看一个例子。
package main
import "fmt"
func main() {
numbers := make([]int, 3, 5)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 4
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 4")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 5
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 5")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3]
length=3
capacity=5
Append Number 4
numbers=[1 2 3 4]
length=4
capacity=5
Append Number 5
numbers=[1 2 3 4 4]
length=5
capacity=5
在所有情况下,容量不变,仍为 5,而长度增加 1。
- 当切片长度等于容量时。
在这种情况下,由于没有更多的容量,因此无法容纳新元素。所以在这种情况下,底层会分配一个容量加倍的数组。当前由切片指向的数组将被复制到该新数组。现在切片将开始指向这个新数组。因此,容量将加倍,长度将增加 1。让我们看一个例子
package main
import "fmt"
func main() {
numbers := make([]int, 3, 3)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 4
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 4")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3]
length=3
capacity=3
Append Number 4
numbers=[1 2 3 4]
length=4
capacity=6
请注意,上例中的容量被加倍。
也可以将一个切片附加到另一个切片。以下是该格式。
res := append(slice1, slice2...)
请注意第二个切片后的‘…’。‘…’是运算符,表示参数是可变参数。意味着在运行时,slice2 将扩展为其各个元素,并作为多个参数传递给 append 函数。
package main
import "fmt"
func main() {
numbers1 := []int{1, 2}
numbers2 := []int{3, 4}
numbers := append(numbers1, numbers2...)
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3 4]
length=4
capacity=4
字符串的 append 函数
在 Go 中,字符串只是一系列字节。因此,将字符串附加到字节切片是合法的。以下是相关程序。请注意字符串末尾的‘…’。
package main
import "fmt"
func main() {
sample := "Hello"
suffix := "World"
result := append([]byte(sample), suffix...)
fmt.Printf("sample: %s\n", string(result))
}
输出
sample: HelloWorld
在 Go (Golang)中将一个切片附加或添加到另一个切片
目录
-
概述
-
程序
概述
也可以将一个切片追加到另一个切片。下面是该操作的格式。
res := append(slice1, slice2...)
注意第二个切片后的‘…’。‘…’是一个运算符,表示参数是一个可变参数。这意味着在运行时,slice2 将被扩展为其各个元素,并作为多个参数传递给 append 函数。
程序
package main
import "fmt"
func main() {
numbers1 := []int{1, 2}
numbers2 := []int{3, 4}
numbers := append(numbers1, numbers2...)
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3 4]
length=4
capacity=4
注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尽力覆盖所有概念并附上示例。本教程适合那些希望获得专业知识和对 golang 有深刻理解的人——Golang 高级教程
如果你对如何在 Golang 中实现所有设计模式感兴趣。如果是,那么这篇文章适合你——所有设计模式 Golang
在 Go(Golang)中追加或添加到切片或数组。
目录
概述
-
append() 函数 函数")
-
当切片长度小于容量时
-
当切片长度等于容量时
-
-
append() 函数用于字符串 函数用于字符串")
概述
在 golang 中,数组的大小是其类型的一部分。这就是为什么数组的长度在创建时是固定的,之后不能更改。这就是切片发挥作用的地方。切片比数组更强大和方便使用。实际上,切片更类似于其他编程语言中的数组。在本教程中,我们将探讨如何向切片添加或追加内容。
切片在内部由三个部分表示。
-
指向底层数组的指针。
-
底层数组的当前长度
-
总容量是底层数组可以扩展的最大容量。
在此处了解更多关于切片的信息 – golangbyexample.com/slice-in-golang/
append() 函数
go builtin包提供了一个append函数,可以在切片末尾追加或添加。以下是此函数的签名。
func append(slice []Type, elems ...Type) []Type
第一个参数是切片本身。第二个参数是可变数量的参数。
elems ...Type
‘…’运算符是可变参数语法。因此,基本上…Type表示追加函数可以接受类型为Type的可变数量的参数。以下是使用此函数的方法。在下面的代码中,我们正在向一个有两个元素的切片追加 4。它会在末尾追加并返回原始切片。这就是为什么我们再次将结果收集到numbers变量中的原因。将结果赋值给其他变量也是可以的。
numbers := []int{1,2}
numbers := append(numbers, 4) //Slice will become [1, 2, 3, 4]
追加任意数量的元素也是可以的,因为第二个参数是可变参数。
numbers := []int{1,2}
numbers := append(numbers, 3, 4, 5) //Slice will become [1, 2, 3, 4, 5]
该函数在后台增加切片的长度和容量。有两种情况。
-
当切片长度小于容量时
-
当切片长度等于容量时
当切片长度小于容量时
在这种情况下,通过使用追加函数,切片的长度将增加 1,而容量不会发生变化。让我们看一个例子。
package main
import "fmt"
func main() {
numbers := make([]int, 3, 5)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 4
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 4")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 5
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 5")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3]
length=3
capacity=5
Append Number 4
numbers=[1 2 3 4]
length=4
capacity=5
Append Number 5
numbers=[1 2 3 4 4]
length=5
capacity=5
在所有情况下,容量不会改变,始终是 5,而长度增加 1。
当切片长度等于容量时
在这种情况下,由于没有更多的容量,所以无法容纳新的元素。因此,在后台,将分配一个容量为两倍的数组。切片指向的当前数组将被复制到那个新数组。现在切片将开始指向这个新数组。因此,容量将翻倍,长度将增加 1。让我们看一个例子。
package main
import "fmt"
func main() {
numbers := make([]int, 3, 3)
numbers[0] = 1
numbers[1] = 2
numbers[2] = 3
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
//Append number 4
numbers = append(numbers, 4)
fmt.Println("\nAppend Number 4")
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3]
length=3
capacity=3
Append Number 4
numbers=[1 2 3 4]
length=4
capacity=6
注意上面的例子中容量翻倍。
也可以将一个切片附加到另一个切片。下面是该格式。
res := append(slice1, slice2...)
注意第二个切片后面的‘…’。‘…’是表示参数为可变参数的操作符。这意味着在运行时,slice2 将扩展为其单独元素,并作为多个参数传递给 append 函数。
package main
import "fmt"
func main() {
numbers1 := []int{1, 2}
numbers2 := []int{3, 4}
numbers := append(numbers1, numbers2...)
fmt.Printf("numbers=%v\n", numbers)
fmt.Printf("length=%d\n", len(numbers))
fmt.Printf("capacity=%d\n", cap(numbers))
}
输出
numbers=[1 2 3 4]
length=4
capacity=4
append()函数用于字符串
Go 中的字符串只是字节序列。因此,将字符串附加到字节切片是合法的。下面是该程序。注意字符串末尾的‘…’
package main
import "fmt"
func main() {
sample := "Hello"
suffix := "World"
result := append([]byte(sample), suffix...)
fmt.Printf("sample: %s\n", string(result))
}
输出
sample: HelloWorld
另外,查看我们的 Golang 进阶教程系列 – Golang 进阶教程
在 Go(Golang)中追加到现有文件
os 包的os.OpenFile()函数可以用于以追加模式打开文件,然后写入内容
让我们看一个例子。在下面的程序中:
-
首先,使用ioutil包写入文件
-
再次以追加模式打开文件并写入第二行
-
读取文件以验证内容。
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
)
func main() {
//Write first line
err := ioutil.WriteFile("temp.txt", []byte("first line\n"), 0644)
if err != nil {
log.Fatal(err)
}
//Append second line
file, err := os.OpenFile("temp.txt", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
}
defer file.Close()
if _, err := file.WriteString("second line"); err != nil {
log.Fatal(err)
}
//Print the contents of the file
data, err := ioutil.ReadFile("temp.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
输出:
first line
second line
ASCII 数字转换为 Go 语言中的字符
目录
-
概述
-
程序
概述
以下是一个简单的程序,用于将 ASCII 数字转换为其对应的字符。我们可以简单地将数字类型转换为字符串,这样就能将其转换为对应的 ASCII 字符。
程序
package main
import "fmt"
func main() {
sampleASCIIDigits := []int{97, 98, 99}
for _, digit := range sampleASCIIDigits {
fmt.Printf("Char %s\n", string(digit))
}
}
输出
Char a
Char b
Char c
Go(Golang)中的 Base64 编码/解码
目录
概述
-
标准编码
-
URL 编码
-
原始标准编码
-
原始 URL 编码
概述
Golang 提供了一个 encoding/base64 包,可用于将字符串编码为 base64 并将 base64 编码的字符串解码回原始字符串。golang.org/pkg/encoding/base64/
Go 支持四种不同方式的 base64。
- 标准编码– 标准的 64 个字符,带有填充。它编码为以下字符
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
- URL 编码 – 与标准编码相同,只是其中的 ‘+’ 和 ‘\’ 符号被替换为 ‘-‘ 和 ‘_’。它们被替换以使其与文件名和 URL 兼容。以下是 base64 URL 编码的字符集
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
-
原始标准编码 – 这与标准编码相同,只是省略了填充字符。因此,它是未填充的 base64 标准编码。
-
原始 URL 编码 – 这也与 URL 编码相同,只是省略了填充字符。因此,它是未填充的 base64 URL 编码。
上述每个编码由 Encoding 结构表示
golang.org/pkg/encoding/base64/#Encoding
Encoding 结构进一步定义了两个编码和解码的方法
- 编码为字符串 – 以下是该方法的签名。它将字节作为输入,并根据使用的四种编码之一返回 base64 编码的字符串。
func (enc *Encoding) EncodeToString(src []byte) string
- 解码字符串 – 以下是该方法的签名。它将编码字符串作为输入,并根据使用的四种编码之一返回原始字符串。
func (enc *Encoding) DecodeString(s string) ([]byte, error)
让我们详细看看每一个例子
标准编码
这是标准的 base64 编码,定义在 RFC 4648 中。它使用以下 64 个字符集以及填充字符 ‘=’。
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w (pad) =
15 P 32 g 49 x
16 Q 33 h 50 y
让我们看一个这个的例子
例子
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
sample := "a@"
encodedString := base64.StdEncoding.EncodeToString([]byte(sample))
fmt.Println(encodedString)
originalStringBytes, err := base64.StdEncoding.DecodeString(encodedString)
if err != nil {
log.Fatalf("Some error occured during base64 decode. Error %s", err.Error())
}
fmt.Println(string(originalStringBytes))
}
输出
YUA=
a@
请注意,在上面的例子中,我们使用 base64 标准编码对下面的字符串进行编码
sample := "a@"
encodedString := base64.StdEncoding.EncodeToString([]byte(sample))
然后我们将 base64 编码的字符串解码回原始字符串
originalStringBytes, err := base64.StdEncoding.DecodeString(encodedString)
if err != nil {
log.Fatalf("Some error occured during base64 decode. Error %s", err.Error())
}
fmt.Println(string(originalStringBytes))
它在解码时正确输出原始字符串
URL 编码
在这个例子中,‘+’ 和 ‘\’ 符号被替换为 ‘-‘ 和 ‘_’。它们被替换以使其与文件名和 URL 兼容。
URL:在 URL 中,‘+’ 和 ‘\’ 由于 URL 编码而进一步编码为十六进制序列,从而进一步增加了 URL 的长度。例如,‘+’ 将被转换为 ‘%2B’,‘\’ 将在 URL 编码中编码为 ‘%2F’。
文件名:在 Unix 和 Windows 中,文件路径使用 ‘\’。因此,它将 ‘\’ 替换为 ‘_’。
让我们看一个例子
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
sample := "�"
encodedStringURL := base64.URLEncoding.EncodeToString([]byte(sample))
fmt.Printf("URL Encoding: %s\n", encodedStringURL)
encodedStringSTD := base64.StdEncoding.EncodeToString([]byte(sample))
fmt.Printf("STD Encoding: %s\n", encodedStringSTD)
originalStringBytes, err := base64.URLEncoding.DecodeString(encodedStringURL)
if err != nil {
log.Fatalf("Some error occured during base64 decode. Error %s", err.Error())
}
fmt.Println(string(originalStringBytes))
}
输出
URL Encoding: 77-9
STD Encoding: 77+9
�
注意在上述示例中,我们取了下面字符串的示例
sample := "�"
然后我们打印上述字符串的标准编码和 URL 编码
encodedStringURL := base64.URLEncoding.EncodeToString([]byte(sample))
fmt.Printf("URL Encoding: %s\n", encodedStringURL)
encodedStringSTD := base64.StdEncoding.EncodeToString([]byte(sample))
fmt.Printf("STD Encoding: %s\n", encodedStringSTD)
注意输出中的区别
对于 URL 编码,它输出
77-9
而对于标准编码,它输出
77+9
这是因为在标准编码中存在 ‘+’,而在 URL 编码中 ‘+’ 被替换为 ‘-‘
原始标准编码
这与标准编码相同,只是省略了填充字符。因此这是未填充的标准 base64 编码。让我们看一个例子。在我们看到原始标准编码的例子之前,我们想解释为什么存在省略填充字符的标准编码的原始版本。这涉及到一个非常重要的主题,即 base64 编码中的 ‘填充是否必要’。答案是这取决于情况。
-
当你发送单个字符串时,填充不是必需的。
-
当你串联多个字符串的 base64 编码时,填充是重要的。如果未填充的字符串被串联,那么将不可能得到原始字符串,因为关于添加的字节的信息将会丢失。作为说明,请考虑以下内容
实际字符串 | 带填充的 Base64 编码 | 不带填充的 Base64 编码 |
---|---|---|
a | YQ== | YQ |
bc | YmM= | YmM |
def | ZGVm | ZGVm |
现在让我们考虑这两种情况。
当串联发送时不需要填充
在这种情况下,串联的 Base64 字符串将是
YQYmMZGVm
尝试解码它,你将得到如下的最终字符串,这是不正确的
a&1
当串联发送时需要填充
在这种情况下,串联的 Base64 字符串将是
YQ==YmM=ZGVm
尝试按 4 个字符分组解码,你将得到如下的最终字符串,这是正确的
abcdef
现在再次浮现的问题是,为什么需要串联多个 base64 编码的字符串。答案是,在有流数据的情况下,想要随着数据的到来发送 base64 编码的数据总是好的。例如,视频的缓冲。
所以这就是为什么填充是被鼓励的,尽管在所有情况下并非绝对必要。现在让我们看一个原始标准编码的例子。
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
sample := "a@"
encodedStringStdEncoding := base64.StdEncoding.EncodeToString([]byte(sample))
fmt.Printf("STD Encoding: %s\n", encodedStringStdEncoding)
encodedStringRawStdEncoding := base64.RawStdEncoding.EncodeToString([]byte(sample))
fmt.Printf("Raw STD Encoding: %s\n", encodedStringRawStdEncoding)
originalStringBytes, err := base64.RawStdEncoding.DecodeString(encodedStringRawStdEncoding)
if err != nil {
log.Fatalf("Some error occured during base64 decode. Error %s", err.Error())
}
fmt.Println(string(originalStringBytes))
}
输出
STD Encoding: YUA=
Raw STD Encoding: YUA
a@
在上面的示例中,我们打印了 标准编码 和 原始标准编码 的结果。
encodedStringStdEncoding := base64.StdEncoding.EncodeToString([]byte(sample))
fmt.Printf("STD Encoding: %s\n", encodedStringStdEncoding)
encodedStringRawStdEncoding := base64.RawStdEncoding.EncodeToString([]byte(sample))
fmt.Printf("Raw STD Encoding: %s\n", encodedStringRawStdEncoding)
注意输出中的区别
对于标准编码,它输出
YUA=
而对于原始标准编码,它输出
YUA
正如你所注意到的,原始标准编码省略了填充字符。
原始 URL 编码
这与 URL 编码相同,只是它省略了填充字符。所以这是未填充的标准 base64 编码。
package main
import (
"encoding/base64"
"fmt"
"log"
)
func main() {
sample := "a@"
encodedStringURLEncoding := base64.URLEncoding.EncodeToString([]byte(sample))
fmt.Printf("URL Encoding: %s\n", encodedStringURLEncoding)
encodedStringRawURLEncoding := base64.RawURLEncoding.EncodeToString([]byte(sample))
fmt.Printf("Raw URL Encoding: %s\n", encodedStringRawURLEncoding)
originalStringBytes, err := base64.RawStdEncoding.DecodeString(encodedStringRawURLEncoding)
if err != nil {
log.Fatalf("Some error occured during base64 decode. Error %s", err.Error())
}
fmt.Println(string(originalStringBytes))
}
输出
URL Encoding: YUA=
Raw URL Encoding: YUA
a@
在上述示例中,我们再次打印了 URL 编码和原始 URL 编码的结果,你可以注意到填充字符 ‘=’ 在原始 URL 编码中被省略了。
基于 Go(Golang)的基本 HTTP 服务器实现
目录
-
概述
-
请求
-
响应
-
API 签名及其处理程序的配对
-
路由器
-
监听器
-
-
使用服务器的 ListenAndServe 函数
-
使用 http 的 ListenAndServe 函数
-
结论
概述
HTTP(超文本传输协议)是应用层协议,以客户端-服务器模式工作。HTTP 服务器基本上是运行在机器上的程序。它监听并响应其 IP 地址上的 HTTP 请求,指定端口。由于 HTTP 是万维网的基础,并用于加载任何网页,因此每个软件开发人员都面临需要实现 HTTP 服务器以响应 HTTP 请求的情况。
本文涵盖 Go 编程语言中的 HTTP 服务器实现。Go 包net包含处理网络功能的实用程序包。
net包包含 http 包,提供 HTTP 客户端(用于发送 http 请求)和 HTTP 服务器(监听 http 请求)实现。本文将讨论 HTTP 服务器。下面是导入 http 包的语句:
import "net/http"
理解 HTTP 服务器实现的关键在于理解以下内容
-
请求 – 它定义请求参数,即方法、API 签名、请求头、主体、查询参数等。
-
响应 – 定义响应参数,即状态码、响应体、头部
-
API 签名及其处理程序的配对 – 每个 API 签名对应一个处理程序。你可以把处理程序想象成一个在请求该特定 API 签名时被调用的函数。mux注册这些 API 签名及其处理程序的配对。
-
路由器– 它充当路由器。根据请求的 API 签名,它将请求路由到该 API 签名的注册处理程序。处理程序将处理该传入请求并提供响应。例如,API 调用“/v2/teachers”可能由不同的函数处理,而 API 调用“/v2/students”可能由另一个函数处理。因此,基本上根据 API 签名(有时也考虑请求方法),它决定调用哪个处理程序。
-
监听器 – 它在机器上运行,监听特定端口。每当它在该端口接收到请求时,就会将请求转发给mux。它还处理其他功能,但我们在本文中不会讨论这些。
在 HTTP 方面还有更多内容,但为了简单起见,我们只讨论了以上五个要点。下面的图示展示了来自客户端的 API 请求的交互。
让我们看一个例子。下面两个 API 签名和处理程序对在 mux 中注册。
-
“/v1/abc” 和 handlerfunc_1。
-
“/v1/xyz” 和 handlerfunc_2。
客户端调用 “/v1/abc” API。监听器将其转发到 mux,而 mux 将其路由到适当的处理程序 handlerfunc_1。
客户端调用 “/v1/xyz” API。监听器将其转发到 mux,而 mux 将其路由到适当的处理程序 handlerfunc_2。
现在我们已经理解了以上部分,所以让我们继续看看以上每一部分在 Go 中是如何实现的,最后我们将看到一个完整的程序及整个端到端流程。
请求
在 Go 中,请求由 Request 结构表示。这里是结构的链接 – golang.org/pkg/net/http/#Request
。
它包含请求方法、API 签名、请求头、主体、查询参数等。
响应
在 Go 中,响应由 ResponseWriter 接口表示。这里是接口的链接 – golang.org/pkg/net/http/#ResponseWriter
。ResponseWriter 接口由 HTTP 处理程序用于构造 HTTP 响应。它提供三个函数来设置响应参数。
-
头部 – 用于编写响应头。
-
Write([]byte) – 用于写入响应主体。
-
WriteHeader(statusCode int) – 用于写入 HTTP 状态码。
API 签名及其处理程序的对。
API 签名和它的处理程序是成对的。当接收到与 API 签名匹配的 API 调用时,mux 会调用处理程序。一个 Go 处理程序可以是一个 函数 或一个 类型。
- 函数 – 函数应具有以下签名。
func(ResponseWriter, *Request)
- 类型 – 该类型应实现 Handler 接口。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
让我们逐一查看每个。
- 函数 – 处理程序可以只是具有以下签名的简单函数。
func(ResponseWriter, *Request)
它接受两个参数作为输入。第一个是 ResponseWriter,第二个是指向 Request 结构的指针。我们之前也讨论过这两个。
如果 API 签名和具有上述签名的函数在 mux 中注册为一对,则当发出与 API 签名匹配的 API 调用时,将调用该函数。
- 类型 – 该类型应实现 Handler 接口 –
golang.org/pkg/net/http/#Handler
。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler 接口定义了 ServeHttp 函数。如果 API 签名和实现了 Handler 接口的类型在 mux 中注册为一对,则在进行与 API 签名匹配的 API 调用时,该类型的 ServeHTTP 方法将被调用。
如果你注意到,作为处理程序的function的 API 签名和ListenAndServe函数是相同的。
func(ResponseWriter, *Request)
这些函数将根据处理程序的类型由 mux 调用。还要注意的是,两种不同的 API 签名可以具有相同的处理程序。
Mux
mux 或多路复用器的工作是根据 API 签名(有时也根据请求方法)将请求路由到注册的处理程序。如果签名及其处理程序未在 mux 中注册,则会引发 404 错误。
Go 提供了一个内置的默认 mux – golang.org/pkg/net/http/#ServeMux
。市场上还有其他可用于 golang 的 mux。不同的 Web 框架,如 gin,提供自己的 mux。
这就是我们创建 mux 的方式。
mux := http.NewServeMux()
让我们看看如何将一对 API 签名及其处理程序与 mux 注册。这里有两种情况。
- 当处理程序是一个function时,它注册的是 API 签名的模式和作为处理程序的函数。
mux.HandleFunc(pattern, handlerFunc)
- 当处理程序是实现了Handler接口的type时。
mux.Handle(pattern, handler)
Listener
监听器监听一个端口,并将请求转发到mux,然后等待响应。一旦接收到响应,它会将其发送回客户端。在 golang 中,可以使用服务器结构实现监听器 – golang.org/pkg/net/http/#Server
。
这就是我们创建服务器的方式。创建服务器时,我们还可以指定一些其他参数,例如 ReadTimeout、WriteTimeout 等,但这超出了本教程的范围。所有未提供的参数都取默认零值。
s := &http.Server{
Addr: ":8080",
Handler: mux,
}
Addr属性的类型为字符串,是将在其上启动 HTTP 服务器的机器的地址。
该地址的形式为。
{ip_address}:{port}
如果仅使用:{port}作为addr参数,那么这意味着 HTTP 服务器可以从机器的所有 IP 地址(回环地址、公共 IP、内部 IP)访问。
还可以将“:http”作为addr参数值,用于地址端口“:80”,将“:https”用于地址端口“:443”。
这里需要注意的一件非常重要的事情是,ServerMux是语言中内置的默认 mux,它也有一个ServeHttp方法 golang.org/pkg/net/http/#ServeMux.ServeHTTP
。因此,ServerMux也实现了Handler接口,因为它定义了ServeHttp方法。在创建服务器时,你可能注意到我们必须提供一个类型为Handler接口的处理程序。这就是ServerMux实现Handler接口的便利之处,因为我们可以在创建服务器时传递ServerMux的实例。理解ServerMux是Handler接口的一种类型这一点很重要,此外它还注册了不同的 API 签名及其处理程序。
在服务器创建后,我们调用 server 的 ListenAndServe 方法。然后,服务器开始监听提供的端口,并在该端口收到任何 API 调用时调用 mux 的 ServeHttp,进而将请求路由到注册的处理程序。希望以上五个内容现在清晰了。让我们看看一个演示上述要点的工作程序。
使用服务器的 ListenAndServe 函数
main.go
package main
import (
"net/http"
)
func main() {
//Create the default mux
mux := http.NewServeMux()
//Handling the /v1/teachers. The handler is a function here
mux.HandleFunc("/v1/teachers", teacherHandler)
//Handling the /v1/students. The handler is a type implementing the Handler interface here
sHandler := studentHandler{}
mux.Handle("/v1/students", sHandler)
//Create the server.
s := &http.Server{
Addr: ":8080",
Handler: mux,
}
s.ListenAndServe()
}
func teacherHandler(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of teacher's called")
res.WriteHeader(200)
res.Write(data)
}
type studentHandler struct{}
func (h studentHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of student's called")
res.WriteHeader(200)
res.Write(data)
}
在运行程序之前,我们先了解一下它。
- 我们定义了一个名为 teacherHandler 的函数,该函数的签名接受 http.ResponseWriter 和指向 http.Request 的指针。
func teacherHandler(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of teacher's called")
res.Header().Set("Content-Type", "application/text")
res.WriteHeader(200)
res.Write(data)
}
- 我们定义了一个名为 studentHandler 的结构体,该结构体定义了 ServeHTTP 方法。因此,studentHandler 是一个实现了 Handler 接口的类型。
type studentHandler struct{}
func (h studentHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of student's called")
res.Header().Set("Content-Type", "application/text")
res.WriteHeader(200)
res.Write(data)
}
- 我们创建了一个 ServerMux 的实例。
mux := http.NewServeMux()
- 我们注册了 API 签名“/v1/teachers”及其处理程序 teacherHandler。
mux.HandleFunc("/v1/teachers", teacherHandler)
- 我们注册了 API 签名“/v1/students”及其处理程序 studentHandler,它是一个实现了 Handler 接口的类型。
sHandler := studentHandler{}
mux.Handle("/v1/students", sHandler)
- 我们创建了服务器,并提供了 ServerMux 的实例和要监听的端口,即 8080。然后调用了服务器实例上的 ListenAndServe 方法。
s := &http.Server{
Addr: ":8080",
Handler: mux,
}
s.ListenAndServe()
现在让我们运行服务器。
go run main.go
它将开始监听 8080 端口。这个程序不会退出,进程会保持锁定状态,直到被强制终止,这是推荐的,因为任何 HTTP 服务器都应始终处于运行状态。现在进行 API 调用。
调用 “v1/teachers” API – 它返回正确的响应 – ‘V1 of teacher’s called’,以及正确的状态码 200。
curl -v -X GET http://localhost:8080/v1/teachers
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /v1/teachers HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/text
< Date: Sat, 11 Jul 2020 16:03:33 GMT
< Content-Length: 22
<
* Connection #0 to host localhost left intact
V1 of teacher's called
调用 "v1/students" API - 它返回正确的响应 - 'V1 of student's called',以及正确的状态码 200。
curl -v -X GET http://localhost:8080/v1/students
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /v1/students HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/text
< Date: Sat, 11 Jul 2020 16:04:27 GMT
< Content-Length: 22
<
* Connection #0 to host localhost left intact
V1 of student's called
你也可以在浏览器上尝试这些 API。
对于 API "/v1/teachers"。
对于 API "/v1/students"。
使用 http 的 ListenAndServe 函数。
所以我们查看了一个程序,在那里我们构建了一个 mux,然后添加了一对 API 签名及其处理程序。最后,我们创建了一个服务器并启动了它。net/http 包还提供了一个 ListenAndServe 函数,该函数创建一个默认服务器并使用默认的 mux 实现我们上面讨论的内容。这是一种启动 HTTP 服务器的简便方法。
ListenAndServe 函数有一个 addr 和 handler 作为输入参数,并启动一个 HTTP 服务器。它开始监听传入的 HTTP 请求,并在收到请求时进行服务。下面是 ListenAndServe 函数的签名。
func ListenAndServe(addr string, handler Handler) error
以下是调用此函数的方法。
http.ListenAndServe(:8080, nil)
如果你注意到上面,我们以 nil 值调用了 ListenAndServe 函数。
http.ListenAndServe(:8080, nil)
在这种情况下,将创建一个默认实例的 ServeMux (golang.org/pkg/net/http/#ServeMux
)。
package main
import (
"net/http"
)
func main() {
//Handling the /v1/teachers
http.HandleFunc("/v1/teachers", teacherHandler)
//Handling the /v1/students
sHandler := studentHandler{}
http.Handle("/v1/students", sHandler)
http.ListenAndServe(":8080", nil)
}
func teacherHandler(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of teacher's called")
res.WriteHeader(200)
res.Write(data)
}
type studentHandler struct{}
func (h studentHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
data := []byte("V1 of student's called")
res.WriteHeader(200)
res.Write(data)
}
net/http 包提供了 HandleFunc 和 Handle。这两个函数的工作方式与 mux 的方法相同。
运行服务器。
go run main.go
输出将与我们之前讨论的一样。这就是 golang 中基本 HTTP 服务器实现的全部内容。
结论
我们了解到可以通过两种方式创建 HTTP 服务器。
-
使用 server.ListenAndServe -
golang.org/pkg/net/http/#Server.ListenAndServe
-
使用 http.ListenAndServe -
golang.org/pkg/net/http/#ListenAndServe
从内部来看,它们执行的都是相同的操作。第二种使用默认设置,而第一种则明确允许你创建 mux 和服务器实例,从而可以指定更多选项,因此第一种选项更灵活。
Go (Golang) 中接口的好处
以下是使用接口的一些好处。
- 有助于在代码库的不同部分之间编写更模块化和解耦的代码——这有助于减少代码库不同部分之间的依赖关系,并提供松耦合。
例如,想象一个与数据库层交互的应用程序。如果应用程序通过接口与数据库交互,那么它就不会知道后台使用的是哪种数据库。你可以在后台更改数据库类型,比如从 Arango DB 更改为 Mongo DB,而无需对应用层进行任何更改,因为它是通过实现了接口的数据库层与数据库交互的。
- 接口可以用于实现 Golang 中的运行时多态性。运行时多态性意味着调用在运行时解析。让我们通过一个示例理解接口如何用于实现运行时多态性。
不同国家有不同的税收计算方式。这可以通过接口来表示。
type taxCalculator interface{
calculateTax()
}
现在,不同国家可以拥有自己的结构并实现 calculateTax() 方法。相同的 calculateTax 方法在不同的上下文中用于计算税款。当编译器看到这个调用时,它会推迟在运行时调用哪个具体的方法。
package main
import "fmt"
type taxSystem interface {
calculateTax() int
}
type indianTax struct {
taxPercentage int
income int
}
func (i *indianTax) calculateTax() int {
tax := i.income * i.taxPercentage / 100
return tax
}
type singaporeTax struct {
taxPercentage int
income int
}
func (i *singaporeTax) calculateTax() int {
tax := i.income * i.taxPercentage / 100
return tax
}
func main() {
indianTax := &indianTax{
taxPercentage: 30,
income: 1000,
}
singaporeTax := &singaporeTax{
taxPercentage: 10,
income: 2000,
}
taxSystems := []taxSystem{indianTax, singaporeTax}
totalTax := calculateTotalTax(taxSystems)
fmt.Printf("Total Tax is %d\n", totalTax)
}
func calculateTotalTax(taxSystems []taxSystem) int {
totalTax := 0
for _, t := range taxSystems {
totalTax += t.calculateTax() //This is where runtime polymorphism happens
}
return totalTax
}
输出:
Total Tax is 300
用 Go (Golang) 编写的最佳买卖股票程序
目录
-
概述
-
程序
概述
给定一个数组prices,其中prices[i]表示第 i 天的股票价格。你只能买入和卖出一次。找出通过一次买入和一次卖出可以获得的最大利润。
示例
Input: [4,2,3,8,1]
Output: 6
Buy on the second day at 2 and sell on the 4th day at 8\. Profit = 8-2 = 6
原始顺序应予以保留
程序
下面是相应的程序。
package main
import "fmt"
func maxProfit(prices []int) int {
lenPrices := len(prices)
buy := -1
sell := -1
maxProphit := 0
for i := 0; i < lenPrices; {
for i+1 < lenPrices && prices[i] > prices[i+1] {
i++
}
if i == lenPrices-1 {
return maxProphit
}
buy = i
i++
for i+1 < lenPrices && prices[i] < prices[i+1] {
i++
}
sell = i
if (prices[sell] - prices[buy]) > maxProphit {
maxProphit = prices[sell] - prices[buy]
}
i++
}
return maxProphit
}
func main() {
output := maxProfit([]int{4, 2, 3, 8, 1})
fmt.Println(output)
}
输出
6
注意: 查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力覆盖所有概念并提供示例。这个教程是为那些希望获得专业知识和深入理解 Golang 的人准备的——Golang 高级教程
如果你有兴趣了解如何在 Golang 中实现所有设计模式。 如果是的话,这篇文章就是为你准备的——所有设计模式 Golang