通过示例学习-Go-语言-2023-三十三-
通过示例学习 Go 语言 2023(三十三)
Go 中的字符串常量(Golang)
目录
概述
-
示例
-
有类型的字符串常量
-
未类型化的命名字符串常量
-
未类型化的未命名字符串常量
-
概述
在 Go 中,字符串常量以两种方式表示
-
任何用双引号括起来的值
-
任何用反引号括起来的值
为了更好地理解 Golang 中的字符串常量,了解 Go 中的有类型和无类型常量是重要的。请参阅这篇文章:golangbyexample.com/typed-untyped-constant-golang/
一旦你阅读了这篇文章,你会理解常量可以以三种方式声明
-
有类型常量
-
未类型化的未命名常量
-
未类型化的命名常量
字符串的情况也是如此。让我们看一个程序来理解它。
示例
以下程序展示了一个示例
-
有类型的字符串常量
-
未类型化的未命名字符串常量
-
未类型化的命名字符串常量
package main
import "fmt"
func main() {
type myString string
//Typed String constant
const aa string = "abc"
var uu = aa
fmt.Println("Untyped named string constant")
fmt.Printf("uu: Type: %T Value: %v\n\nn", uu, uu)
//Below line will raise a compilation error
//var v myString = aa
//Untyped named string constant
const bb = "abc"
var ww myString = bb
var xx = bb
fmt.Println("Untyped named string constant")
fmt.Printf("ww: Type: %T Value: %v\n", ww, ww)
fmt.Printf("xx: Type: %T Value: %v\n\n", xx, xx)
//Untyped unnamed string constant
var yy myString = "abc"
var zz = "abc"
fmt.Println("Untyped unnamed string constant")
fmt.Printf("yy: Type: %T Value: %v\n", yy, yy)
fmt.Printf("zz: Type: %T Value: %v\n", zz, zz)
}
输出:
Untyped named string constant
uu: Type: string Value: abc
nUntyped named string constant
ww: Type: main.myString Value: abc
xx: Type: string Value: abc
Untyped unnamed string constant
yy: Type: main.myString Value: abc
zz: Type: string Value: abc
在上述程序中,我们在代码中创建了一个新类型myString。
type myString string
上述程序还展示了一个示例
-
有类型的字符串常量
-
未类型化的未命名字符串常量
-
未类型化的命名字符串常量
让我们理解它们的每一种及其行为
有类型的字符串常量
定义如下
const aa string = "abc"
上述内容注意,下面的行将导致编译错误。这是因为类型字符串常量aa的类型是string。因此,下面的行将导致编译错误,因为它无法分配给类型为myString的变量。
var v myString = aa
但是,有类型的字符串常量可以分配给用var关键字创建的变量,如下所示
var uu = aa
未类型化的命名字符串常量
定义如下
const bb = "abc"
未类型化的命名字符串常量可以分配给类型为myString的变量,以及用var关键字创建的变量,因为它是未类型化的,因此常量的类型将根据被分配的变量的类型决定。
var ww myString = bb
var xx = bb
未类型化的未命名字符串常量
它如下所示
abc
未类型化的未命名字符串常量可以分配给类型为myString的变量,以及用var关键字创建的变量,因为它是未类型化的,因此常量的类型将根据被分配的变量的类型决定。
var yy myString = "abc"
var zz = "abc"
Go (Golang)中的结构体字段元或标签
Go 中的结构体也允许向其字段添加元数据。这些元字段可以用于编码解码成不同形式,对结构体字段进行某些形式的验证等。因此,基本上可以与结构体的字段一起存储任何元信息,并可以被任何包或库用于不同的目的。
下面是附加元数据的格式。元数据是一个字符串字面量,即用反引号括起来。
type strutName struct{
fieldName type `key:value key2:value2`
}
让我们看一个 JSON 元或标签的示例。Marshal和MarshalIndent函数来自encoding/json包,可以用于以 JSON 格式打印结构体。这些函数在编码解码时利用结构体字段的 JSON 元标签。打印时,这些标签将用作键名。
让我们给员工结构体添加 JSON 标签,如下所示。MarshalIndent函数将使用标签中指定的键名。
type employee struct {
Name string `json:"n"`
Age int `json:"a"`
Salary int `json:"s"`
}
让我们看看完整的程序
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string `json:"n"`
Age int `json:"a"`
Salary int `json:"s"`
}
func main() {
emp := employee{Name: "Sam", Age: 31, Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, '', ' ')
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
输出:
{
"n": "Sam",
"a": 31,
"s": 2000
}
输出中的键名与 JSON 元标签中指定的相同。
Go(Golang)中的结构
这是 golang 综合教程系列的第十六章。有关系列其他章节,请参阅此链接 – Golang 综合教程系列
下一教程 – 数组
上一个教程 – 指针
现在让我们查看当前的教程。以下是当前教程的目录
目录
**概述
-
声明一个结构类型
-
创建一个结构变量
-
访问和设置结构字段
-
指向结构的指针
-
使用 & 运算符
-
使用 new 关键字
-
-
打印结构变量
-
使用 fmt 包
-
以 JSON 形式打印结构
-
-
结构字段元数据或标签
-
结构中的匿名字段
-
嵌套结构
-
匿名嵌套结构字段
-
结构的导出和未导出字段
-
结构相等性
-
结构是值类型
-
结论 * * # 概述
GO 结构是不同类型数据字段的命名集合。结构作为一个容器,包含不同的异构数据类型,代表一个实体。例如,不同的属性用于表示一个组织中的员工。员工可以具有
-
字符串类型的名称
-
int 类型的年龄
-
time.Time 类型的出生日期
-
int 类型的薪水
..等等。结构可以用来表示一个员工
type employee struct {
name string
age int
salary int
}
golang 中的结构可以与面向对象语言中的类相比较
声明一个结构类型
以下是声明结构的格式
type struct_name struct {
field_name1 field_type1
field_name2 field_type2
...
}
在上述格式中,struct_name是结构体的名称。它有一个名为field_name1的字段,类型为field_type1,还有一个名为field_name2的字段,类型为field_type2。这声明了一个新的命名结构体类型,作为蓝图。类型关键字用于引入新类型。
示例
type point struct {
x float64
y float64
}
上述声明定义了一个名为point的新结构体,具有两个字段x和y。这两个字段都是float64类型。一旦声明了新的结构体类型,我们可以从中定义新的具体结构体变量,如我们将在下一节看到的那样。
创建一个结构体变量
声明一个结构体仅声明一个命名的结构体类型。创建一个结构体变量则创建了该结构体的实例,同时也初始化了内存。我们可以创建一个空的结构体变量,而不为任何字段提供值。
emp := employee{}
在这种情况下,结构体中的所有字段都被初始化为该字段类型的默认零值。
在创建结构体变量时,我们也可以初始化每个结构体字段的值。有两种变体。
- 每个字段在同一行上。
emp := employee{name: "Sam", age: 31, salary: 2000}
- 每个字段在不同的行上。
emp := employee{
name: "Sam",
age: 31,
salary: 2000,
}
只初始化某些字段的值也是可以的。未初始化的字段将获得其类型的默认零值。
emp := employee{
name: "Sam",
age: 31,
}
在上述情况下,由于未初始化,薪水将获得默认值零。
让我们看看一个工作代码,说明上述要点:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{}
fmt.Printf("Emp1: %+v\n", emp1)
emp2 := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp2: %+v\n", emp2)
emp3 := employee{
name: "Sam",
age: 31,
salary: 2000,
}
fmt.Printf("Emp3: %+v\n", emp3)
emp4 := employee{
name: "Sam",
age: 31,
}
fmt.Printf("Emp4: %+v\n", emp4)
}
输出
Emp1: {name: age:0 salary:0}
Emp2: {name:Sam age:31 salary:2000}
Emp3: {name:Sam age:31 salary:2000}
Emp4: {name:Sam age:31 salary:0}
针对上述程序。
-
我们首先声明一个employee结构体。
-
emp1 的所有字段都被初始化为其类型的默认零值,即姓名为“”,年龄和薪水为 0。
-
emp2 已在同一行上初始化了所有字段。其字段的值被正确打印。
-
emp3 的所有字段在不同的行上初始化。其字段的值被正确打印。
-
emp4 的薪水字段被初始化为默认的零值 0,而其他两个字段的值则被正确打印。
需要注意的是,在结构体的初始化中,每个新行在花括号内必须以逗号结尾。因此,下面的初始化将引发错误。
"salary" : 2000
不以逗号结尾。
emp := employee{
name: "Sam",
age: 31,
salary: 2000
}
这将是可以的。
emp := employee{
name: "Sam",
age: 31,
salary: 2000}
不带字段名称
结构体也可以在不指定字段名称的情况下进行初始化。但在这种情况下,必须按顺序提供每个字段的所有值。
emp := employee{"Sam", 31, 2000}
如果在不使用字段名称时未提供所有值,将引发编译错误。
让我们看看一个程序。
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{"Sam", 31, 2000}
fmt.Printf("Emp: %+v\n", emp)
//emp = employee{"Sam", 31}
}
输出
Emp2: {name:Sam age:31 salary:2000}
取消注释该行。
emp = employee{"Sam", 31}
在上面的程序中,它将引发编译器错误。
too few values in employee literal
访问和设置结构体字段
结构体字段可以通过点运算符访问。获取值的格式如下。
n := emp.name
同样,也可以为结构体字段分配一个值。
emp.name = "some_new_name"
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
//Accessing a struct field
n := emp.name
fmt.Printf("Current name is: %s\n", n)
//Assigning a new value
emp.name = "John"
fmt.Printf("New name is: %s\n", emp.name)
}
输出
Current name is: Sam
New name is: John
指向结构体的指针
创建指向结构体的指针有两种方法。
-
使用&运算符。
-
使用 new 关键字。
让我们逐一查看上述每种方法。
使用&运算符
&运算符可用于获取指向结构体变量的指针。
emp := employee{name: "Sam", age: 31, salary: 2000}
empP := &emp
结构体指针也可以直接创建
empP := &employee{name: "Sam", age: 31, salary: 2000}
让我们来看一个程序
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
empP := &emp
fmt.Printf("Emp: %+v\n", empP)
empP = &employee{name: "John", age: 30, salary: 3000}
fmt.Printf("Emp: %+v\n", empP)
}
输出
Emp: &{name:Sam age:31 salary:2000}
Emp: &{name:John age:30 salary:3000}
使用 new 关键字
使用 new()关键字将:
-
创建结构体
-
将所有字段初始化为其类型的零默认值
-
返回指向新创建结构体的指针
这将返回一个指针
empP := new(employee)
可以使用%p格式修饰符打印指针地址
fmt.Printf("Emp Pointer: %p\n", empP)
解引用运算符‘*’可以用于打印指针所指向的值。
fmt.Printf("Emp Value: %+v\n", *empP)
它将打印
Emp Value: {name: age:0 salary:0}
当不使用解引用指针而是使用格式标识符%+v时,将在结构体前添加一个&符号,表明这是一个指针。
fmt.Printf("Emp Value: %+v\n", empP)
它将打印
Emp Value: &{name: age:0 salary:0}
让我们看看完整程序以说明上述要点
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
empP := new(employee)
fmt.Printf("Emp Pointer Address: %p\n", empP)
fmt.Printf("Emp Pointer: %+v\n", empP)
fmt.Printf("Emp Value: %+v\n", *empP)
}
输出
Emp Pointer Address: 0xc000130000
Emp Pointer: &{name: age:0 salary:0}
Emp Value: {name: age:0 salary:0}
打印一个结构体变量
有两种方法可以打印所有结构体变量,包括所有的键和值。
-
使用fmt包
-
使用json/encoding包以 JSON 形式打印结构体。这也允许以美观的方式打印结构体。
让我们看看两种打印员工结构体实例的方法。
使用 fmt 包
fmt.Printf()函数可以用来打印结构体。可以使用不同的格式标识符以不同方式打印结构体。让我们看看如何使用不同的格式标识符以不同格式打印结构体。
让我们首先创建一个员工实例
emp := employee{name: "Sam", age: 31, salary: 2000}
- %v – 它将只打印值。字段名称将不会被打印。这是打印结构体的默认方式。例如
fmt.Printf("%v", emp) - {Sam 31 2000}
- %+v – 它将同时打印字段和值。例如
fmt.Printf("%+v", emp) - {name:Sam age:31 salary:2000}
fmt.Println()函数也可以用于打印结构体。由于%v 是fmt.Println()函数的默认格式,因此输出将与使用%v 的fmt.Printf()相同。
fmt.Println(emp) - {Sam 31 2000}
让我们也看看一个工作程序
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp: %v\n", emp)
fmt.Printf("Emp: %+v\n", emp)
fmt.Printf("Emp: %#v\n", emp)
fmt.Println(emp)
}
输出
Emp: {Sam 31 2000}
Emp: {name:Sam age:31 salary:2000}
Emp: main.employee{name:"Sam", age:31, salary:2000}
{Sam 31 2000}
以 JSON 格式打印结构体
第二种方法是以 JSON 格式打印结构体。encoding/json包的Marshal和MarshalIndent函数可以用来以 JSON 格式打印结构体。这里是区别
- Marshal – 以下是Marshal函数的签名。该函数通过递归遍历值返回v的 JSON 编码。
Marshal(v interface{}) ([]byte, error)
- MarshalIndent– 以下是MarshalIndent函数的签名。它与Marshal函数相似,但应用缩进来格式化输出。因此可以用于美观地打印结构体。
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
值得注意的是,Marshal和MarshalIndent函数只能访问结构体的导出字段,这意味着只有大写字段才能被访问并编码为 JSON 格式。
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string
Age int
salary int
}
func main() {
emp := employee{Name: "Sam", Age: 31, salary: 2000}
//Marshal
empJSON, err := json.Marshal(emp)
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("Marshal funnction output %s\n", string(empJSON))
//MarshalIndent
empJSON, err = json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("MarshalIndent funnction output %s\n", string(empJSON))
}
输出:
Marshal funnction output {"Name":"Sam","Age":31}
MarshalIndent funnction output {
"Name": "Sam",
"Age": 31
}
薪资字段在输出中没有被打印,因为它以小写字母开头并且没有导出。Marshal函数的输出没有格式化,而MarshalIndent函数的输出是格式化的。
golang还允许通过使用结构体元字段来使 JSON 编码的结构体键名不同,如下一节所示。
结构体字段的元信息或标签
Go 中的结构体也允许为其字段添加元数据。这些元字段可以用于编码解码成不同形式,对结构体字段进行某些形式的验证等。因此,任何元信息都可以与结构体的字段一起存储,并可供任何包或库用于不同目的。
以下是附加元数据的格式。元数据是字符串字面量,即用反引号括起来。
type strutName struct{
fieldName type `key:value key2:value2`
}
现在针对我们的用例,我们将为 employee 结构体添加 JSON 标签如下。Marshal 函数将使用标签中指定的键名。
type employee struct {
Name string `json:"n"`
Age int `json:"a"`
Salary int `json:"s"`
}
让我们看看完整的程序。
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string `json:"n"`
Age int `json:"a"`
Salary int `json:"s"`
}
func main() {
emp := employee{Name: "Sam", Age: 31, Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, '', ' ')
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
输出:
{
"n": "Sam",
"a": 31,
"s": 2000
}
输出中的键名与 json 元标签中指定的相同。
结构体中的匿名字段
结构体可以有匿名字段,也就是说一个字段没有名称。类型将成为字段名称。在下面的例子中,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 没有任何公共、私有或受保护的关键字。控制包外可见性的唯一机制是使用大写和小写格式。
-
大写标识符是导出的。大写字母表示这是一个导出标识符,并且在包外可用。
-
小写标识符不被导出。小写表示该标识符未被导出,只能在同一包内访问。
所有以大写字母开头的结构体将被导出到其他包。同样,任何以大写字母开头的结构体字段将被导出,否则不会。让我们看看一个示例,展示结构体和结构体字段的导出与未导出。请参见下面的model.go和test.go。两者都属于主包。
-
结构
-
结构体Person是导出的。
-
结构体公司未被导出。
-
-
结构体的字段
-
Person结构体字段Name是导出的。
-
Person结构体字段age未被导出,但Name是导出的。
-
model.go
package main
import "fmt"
//Person struct
type Person struct {
Name string
age int
}
type company struct {
}
我们在同一主包中写一个文件test.go。请看下面。
test.go
package main
import "fmt"
//Test function
func Test() {
//STRUCTURE IDENTIFIER
p := &Person{
Name: "test",
age: 21,
}
fmt.Println(p)
c := &company{}
fmt.Println(c)
//STRUCTURE'S FIELDS
fmt.Println(p.Name)
fmt.Println(p.age)
}
运行该文件时,它能够访问model.go中的所有导出和未导出字段,因为两者都位于同一主包中。没有编译错误,并且输出如下。
输出:
&{test 21}
&{}
test
21
让我们将上述文件model.go移动到一个名为model的不同包中。现在注意运行‘go build’时的输出。它会出现编译错误。所有的编译错误都是因为main包中的test.go无法引用model包中model.go的未导出字段。
model.go
package model
//Person struct
type Person struct {
Name string
age int
}
type company struct {
}
test.go
package main
import (
"fmt"
//This will path of your model package
"<somepath>/model"
)
//Test function
func main() {
//STRUCTURE IDENTIFIER
p := &model.Person{
Name: "test",
age: 21,
}
fmt.Println(p)
c := &model.company{}
fmt.Println(c)
//STRUCTURE'S FIELDS
fmt.Println(p.Name)
fmt.Println(p.age)
}</somepath>
输出:
cannot refer to unexported name model.company
p.age undefined (cannot refer to unexported field or method age)
结构体相等性
在考虑结构体相等性之前,首先要了解的是所有结构体字段类型是否可比较。
根据 Go 规范定义的一些可比较类型有:
-
布尔值
-
数字
-
字符串,
-
指针
-
通道
-
接口类型
-
结构体 – 如果其所有字段类型是可比较的。
-
数组 – 如果数组元素的值类型是可比较的。
根据 Go 规范,某些类型不可比较,不能用作映射的键:
-
切片
-
映射
-
函数
因此,如果两个结构体的所有字段类型都是可比较的,且所有对应字段的值相等,则它们是相等的。让我们看一个例子。
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{name: "Sam", age: 31, salary: 2000}
emp2 := employee{name: "Sam", age: 31, salary: 2000}
if emp1 == emp2 {
fmt.Println("emp1 annd emp2 are equal")
} else {
fmt.Println("emp1 annd emp2 are not equal")
}
}
输出
emp1 annd emp2 are equal
如果结构体字段类型不可比较,那么在使用==运算符检查结构体相等性时会出现编译错误。
package main
import "fmt"
type employee struct {
name string
age int
salary int
departments []string
}
func main() {
emp1 := employee{name: "Sam", age: 31, salary: 2000, departments: []string{"CS"}}
emp2 := employee{name: "Sam", age: 31, salary: 2000, departments: []string{"EC"}}
if emp1 == emp2 {
fmt.Println("emp1 annd emp2 are equal")
} else {
fmt.Println("emp1 annd emp2 are not equal")
}
}
上面的程序会引发编译错误,因为employee结构体包含一个字段departments,它是字符串的一个切片。切片不是可比较类型,因此导致编译错误。
invalid operation: emp1 == emp2 (struct containing []string cannot be compared)
结构体是值类型
结构体是 Go 中的值类型。因此,结构体变量名并不是指向结构体的指针,而是表示整个结构体。当
-
一个结构体变量被赋值给另一个结构体变量。
-
一个结构体变量作为参数传递给一个函数。
让我们通过另一个例子来看上面的要点。
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp1 Before: %v\n", emp1)
emp2 := emp1
emp2.name = "John"
fmt.Printf("Emp1 After assignment: %v\n", emp1)
fmt.Printf("Emp2: %v\n", emp2)
test(emp1)
fmt.Printf("Emp1 After Test Function Call: %v\n", emp1)
}
func test(emp employee) {
emp.name = "Mike"
fmt.Printf("Emp in Test function: %v\n", emp)
}
输出
Emp1 Before: {Sam 31 2000}
Emp1 After assignment: {Sam 31 2000}
Emp2: {John 31 2000}
Emp in Test function: {Mike 31 2000}
Emp1 After Test Function Call: {Sam 31 2000}
在上面的例子中,
-
我们将emp1赋值给emp2,然后更改emp2的名称为不同的值。之后,当我们打印emp1时,发现它没有变化。这是因为当我们将emp1赋值给emp2时,会创建一个副本,改变emp2不会对emp1产生任何影响。
-
我们将emp1传递给测试函数,然后又在测试函数中更改了它的name字段。之后,当我们打印emp1时,发现它没有变化。原因是,当emp1作为参数传递给测试函数时,创建了emp1的副本。
结论
这就是关于 Go 语言中的结构体的全部内容。在这篇文章中,我们学习了初始化结构体、指向结构体的指针、不同的打印方式、匿名字段等。我希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。
下一篇教程 – 数组
上一篇教程 – 指针
Go 中的子字符串程序 (Golang)
目录
-
概述
-
程序
概述
在本教程中,我们将看到在给定字符串中查找子字符串的最简单方法。请注意,这可能不是最有效的策略。
策略将是
- 从给定字符串中的每个索引开始匹配子字符串。
这种方法的整体时间复杂度为O(mn),其中 m 是子字符串的长度,n是输入字符串的大小。
我们的程序将返回给定子字符串在原始字符串中开始的索引。如果给定字符串中不存在该子字符串,则返回-1。
示例
Input: "lion"
Substring: "io"
Output: 1
“io” 在 “lion” 的位置 1 处存在。
另一个示例
“tus” 在 “tub” 中不存在。
程序
这是相同程序的代码。
package main
import "fmt"
func strStr(haystack string, needle string) int {
runeHayStack := []rune(haystack)
runeNeedle := []rune(needle)
lenHayStack := len(runeHayStack)
lenNeedle := len(runeNeedle)
if lenNeedle == 0 && lenHayStack == 0 {
return 0
}
if lenNeedle > lenHayStack {
return -1
}
for i := 0; i <= lenHayStack-lenNeedle; i++ {
k := i
j := 0
for j < lenNeedle {
if runeHayStack[k] == runeNeedle[j] {
k = k + 1
j = j + 1
} else {
break
}
}
if j == lenNeedle {
return i
}
}
return -1
}
func main() {
output := strStr("lion", "io")
fmt.Println(output)
output = strStr("tub", "tus")
fmt.Println(output)
}
输出
1
-1
注意: 请查看我们的 Golang 高级教程。本系列的教程内容详尽,我们尽量用实例覆盖所有概念。本教程适合那些希望掌握 Golang 专业知识和扎实理解的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是这样,那么这篇文章就是为你准备的 - 所有设计模式 Golang
支持这个网站
如果你喜欢这个网站的内容,请帮助并支持这个网站。
在 Go (Golang) 中交换字符串的字符
在 Golang 中,字符串是字节的序列。字符串字面量实际上代表的是一段 UTF-8 字节序列。在 UTF-8 中,ASCII 字符对应于前 128 个 Unicode 字符,均为单字节。所有其他字符则在 1 到 4 字节之间。因此,无法直接在字符串中索引字符。在 Go 中,rune 数据类型表示一个 Unicode 点。一旦将字符串转换为 rune 数组,就可以在该数组中索引字符。你可以在这里了解更多关于 rune 的信息 – golangbyexample.com/understanding-rune-in-golang
。
因此,在下面的程序中,为了交换字符串中的字符,我们首先将字符串转换为 rune 数组,以便可以通过索引访问该 rune 数组中的单个字符。
package main
import "fmt"
func main() {
sample := "ab£d"
r := []rune(sample)
fmt.Printf("Before %s\n", string(r))
r[2], r[3] = r[3], r[2]
fmt.Printf("After %s\n", string(r))
}
输出:
Before: ab£d
After: abd£
在 Golang 中交换两个字符串
GO 提供了一种非常简洁的方式来交换两个字符串。请看下面的程序
package main
import "fmt"
func main() {
a := "123"
b := "xyz"
fmt.Printf("Before a:%s b:%s\n", a, b)
a, b = b, a
fmt.Printf("After a:%s b:%s\n", a, b)
}
输出:
Before a:123 b:xyz
After a:xyz b:123
Go(Golang)中的 Switch 语句。
这是 golang 综合教程系列的第十三章。有关系列的其他章节,请参考这个链接 – Golang 综合教程系列
下一个教程 – 延迟关键字
上一个教程 – 如果则
现在让我们查看当前的教程。以下是当前教程的目录。
概述
Switch 语句是防止 if-else 梯形结构的完美方法。以下是 switch 语句的格式。
switch statement; expression {
case expression1:
//Dosomething
case expression2:
//Dosomething
default:
//Dosomething
}
这就是 switch 的工作方式。给定一个 switch 表达式,它会遍历所有 case,尝试找到第一个匹配的 case 表达式,否则如果存在则执行默认 case。匹配的顺序是从上到下,然后从左到右(当 case 包含多个表达式时,如我们将在本教程后面看到的)。
重要事项
在我们进入代码示例之前,有一些关于 switch 的重要信息需要了解。
- switch 语句 和 switch 表达式 都是可选的语句。因此,它们存在四种可能的情况。
同时存在 switch 语句 和 switch 表达式。
switch statement; expression {
...
}
只有 switch 语句。请注意下面的语法。在 switch 语句后面需要有分号。
switch statement; {
...
}
只有 switch 表达式。请注意下面的语法。在 switch 表达式后面没有分号。
switch expression {
..
}
switch 语句 和 switch 表达式 都缺失。
switch {
...
}
-
如果未提供 switch 表达式,则编译器假定的默认类型是 布尔。
-
switch 表达式 的类型和所有 case 表达式 的类型应该匹配,否则会产生编译错误。当 switch 表达式 未提供时,所有 case 表达式 的类型也需要是布尔值。
-
switch 语句 可以是带有短声明、函数调用或赋值的任何语句。如果 switch 语句 包含变量声明,则该变量的作用域仅限于 switch 块。
-
它可以有任意数量的 case 语句。
-
默认 case 是可选的。
-
case 可以有多个用逗号分隔的表达式。然后 case 的样子如下。
case expression1_1, expression_2 ...:
-
case 块还允许使用 fallthrough 关键字,该关键字将控制权转移到下一个 case,即使当前 case 可能已经匹配。
-
在许多其他语言中需要的 break 语句在 Go 中是不需要的。Go 自动在每个 case 块的末尾提供 break 语句。然而,显式使用 break 关键字来终止 switch 语句的执行也是正确的。
-
两个 case 不能有相同的常量值。在这种情况下会产生编译错误。
-
switch
语句也可以用作类型开关,用于在运行时知道空接口的类型,正如我们在下面的示例中看到的。
示例
让我们看一些简单的示例来说明上述要点。
缺少 switch
语句和 switch
表达式
package main
import "fmt"
func main() {
switch ch := "b"; ch {
case "a":
fmt.Println("a")
case "b", "c":
fmt.Println("b or c")
default:
fmt.Println("No matching character")
}
//fmt.Println(ch)
}
输出:
b or c
一些注意事项:
-
在上述示例中,我们有一个带有短声明的
switch
语句。 -
然后,switch case 匹配了这里的
switch
表达式“b”。 -
此外,case 中可以有多个表达式,如上所示,第二个 case 有两个表达式。
case "b", "c":
- ch 变量仅在 switch 块内可用。取消注释位于
switch
块外的 fmt.Println(ch) 行,它将引发错误。
undefined: ch
缺少 switch
语句和 switch
表达式
让我们看另一个示例,其中我们省略了 switch 语句 和 switch 表达式。
package main
import "fmt"
func main() {
age := 45
switch {
case age < 18:
fmt.Println("Kid")
case age >= 18 && age < 40:
fmt.Println("Young")
default:
fmt.Println("Old")
}
}
输出
Old
关于上述示例有几点需要注意:
-
由于我们省略了
switch
表达式,switch
表达式的默认类型为布尔值。每个 case 表达式也评估为布尔值,因此程序运行正常。 -
默认 case 在上述示例中执行,因为没有任何 case 表达式匹配。
仅 switch
语句
注意语句后面的 ';'
package main
import "fmt"
func main() {
switch age := 29; {
case age < 18:
fmt.Println("Kid")
case age >= 18 && age < 40:
fmt.Println("Young")
default:
fmt.Println("Old")
}
}
输出:
Young
仅 switch
表达式
package main
import "fmt"
func main() {
char := "a"
switch char {
case "a":
fmt.Println("a")
case "b":
fmt.Println("b")
default:
fmt.Println("No matching character")
}
}
输出
a
重复的 case
两个 case 语句不能有相同的常量。例如,在下面的 case 中,会出现编译错误,因为 "a" 在两个 case 中都存在。
duplicate case "a" in switch
package main
import "fmt"
func main() {
switch "a" {
case "a":
fmt.Println("a")
case "a":
fmt.Println("Another a")
case "b":
fmt.Println("b")
default:
fmt.Println("No matching character")
}
}
Fallthrough 关键字
请查看下面的代码以了解 fallthrough 关键字的示例。在下面的示例中,尽管第二个 case 匹配,但由于 fallthrough 关键字,它进入了第三个 case。
package main
import "fmt"
func main() {
i := 45
switch {
case i < 10:
fmt.Println("i is less than 10")
fallthrough
case i < 50:
fmt.Println("i is less than 50")
fallthrough
case i < 100:
fmt.Println("i is less than 100")
}
}
输出
i is less than 50
i is less than 100
fallthrough 必须是 switch
块内的最后一条语句。如果不是,编译器将引发错误。
fallthrough statement out of place
下面的程序将在 fallthrough 语句之后有 fmt.Println 时引发上述错误。
package main
import "fmt"
func main() {
i := 45
switch {
case i < 10:
fmt.Println("i is less than 10")
fallthrough
case i < 50:
fmt.Println("i is less than 50")
fallthrough
fmt.Println("Not allowed")
case i < 100:
fmt.Println("i is less than 100")
}
}
Break 语句
下面是 break 语句示例。
package main
import "fmt"
func main() {
switch char := "b"; char {
case "a":
fmt.Println("a")
case "b":
fmt.Println("b")
break
fmt.Println("after b")
default:
fmt.Println("No matching character")
}
}
输出
b
break 语句将终止 switch
的执行,下面的行将永远不会执行。
fmt.Println("after b")
类型开关
switch
语句也可以用于在运行时知道接口的类型,如下面示例所示。类型开关比较类型而不是值。
package main
import "fmt"
func main() {
printType("test_string")
printType(2)
}
func printType(t interface{}) {
switch v := t.(type) {
case string:
fmt.Println("Type: string")
case int:
fmt.Println("Type: int")
default:
fmt.Printf("Unknown Type %T", v)
}
}
输出:
Type: string
Type: int
结论
这就是关于 Go 中 switch
语句的全部内容。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。
下一个教程 – Defer 关键字
上一个教程 – If Else
Go 中的模板方法设计模式(Golang)
来源:
golangbyexample.com/template-method-design-pattern-golang/
注意:如果有兴趣了解其他所有设计模式在 GO 中如何实现,请查看这个完整参考 – GO 中的所有设计模式(Golang)
目录
** 介绍:
-
例子
-
完整工作代码:
介绍:
模板方法设计模式是一种行为设计模式,它让你为特定操作定义一个模板或算法。让我们通过一个例子来理解模板设计模式。
考虑一次性密码(OTP)的例子。有不同类型的 OTP 可以触发,例如 OTP 可以是短信 OTP 或邮件 OTP。但无论是短信 OTP 还是邮件 OTP,整个 OTP 过程的步骤是相同的。步骤包括
-
生成一个随机的 n 位数字。
-
将此数字保存在缓存中以备后续验证。
-
准备内容
-
发送通知
-
发布指标
即使将来引入推送通知OTP,它仍然会经过上述步骤。
在这种情况下,当特定操作的步骤相同但不同实现者可以以不同方式实现这些步骤时,它就成为模板方法设计模式的候选者。我们定义一个包含固定数量方法的模板或算法。操作的实现者重写模板的方法。
现在查看下面的代码示例。
-
iOtp代表一个定义任何 OTP 类型应该实现的方法集的接口。
-
短信和邮件是iOtp接口的实现者。
-
otp是定义模板方法genAndSendOTP()的结构体,otp嵌入了iOtp接口。
重要提示:iOtp接口和otp结构的组合提供了 GO 中的抽象类的能力。参考见
golangbyexample.com/go-abstract-class/embed/#?secret=IJrupn5jVu#?secret=fqHnV3R5rZ
例子
otp.go
package main
type iOtp interface {
genRandomOTP(int) string
saveOTPCache(string)
getMessage(string) string
sendNotification(string) error
publishMetric()
}
type otp struct {
iOtp iOtp
}
func (o *otp) genAndSendOTP(otpLength int) error {
otp := o.iOtp.genRandomOTP(otpLength)
o.iOtp.saveOTPCache(otp)
message := o.iOtp.getMessage(otp)
err := o.iOtp.sendNotification(message)
if err != nil {
return err
}
o.iOtp.publishMetric()
return nil
}
sms.go
package main
import "fmt"
type sms struct {
otp
}
func (s *sms) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("SMS: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *sms) saveOTPCache(otp string) {
fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}
func (s *sms) getMessage(otp string) string {
return "SMS OTP for login is " + otp
}
func (s *sms) sendNotification(message string) error {
fmt.Printf("SMS: sending sms: %s\n", message)
return nil
}
func (s *sms) publishMetric() {
fmt.Printf("SMS: publishing metrics\n")
}
email.go
package main
import "fmt"
type email struct {
otp
}
func (s *email) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *email) saveOTPCache(otp string) {
fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}
func (s *email) getMessage(otp string) string {
return "EMAIL OTP for login is " + otp
}
func (s *email) sendNotification(message string) error {
fmt.Printf("EMAIL: sending email: %s\n", message)
return nil
}
func (s *email) publishMetric() {
fmt.Printf("EMAIL: publishing metrics\n")
}
main.go
package main
import "fmt"
func main() {
smsOTP := &sms{}
o := otp{
iOtp: smsOTP,
}
o.genAndSendOTP(4)
fmt.Println("")
emailOTP := &email{}
o = otp{
iOtp: emailOTP,
}
o.genAndSendOTP(4)
}
输出:
SMS: generating random otp 1234
SMS: saving otp: 1234 to cache
SMS: sending sms: SMS OTP for login is 1234
SMS: publishing metrics
EMAIL: generating random otp 1234
EMAIL: saving otp: 1234 to cache
EMAIL: sending email: EMAIL OTP for login is 1234
EMAIL: publishing metrics
完整工作代码:
package main
import "fmt"
type iOtp interface {
genRandomOTP(int) string
saveOTPCache(string)
getMessage(string) string
sendNotification(string) error
publishMetric()
}
type otp struct {
iOtp iOtp
}
func (o *otp) genAndSendOTP(otpLength int) error {
otp := o.iOtp.genRandomOTP(otpLength)
o.iOtp.saveOTPCache(otp)
message := o.iOtp.getMessage(otp)
err := o.iOtp.sendNotification(message)
if err != nil {
return err
}
o.iOtp.publishMetric()
return nil
}
type sms struct {
otp
}
func (s *sms) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("SMS: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *sms) saveOTPCache(otp string) {
fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}
func (s *sms) getMessage(otp string) string {
return "SMS OTP for login is " + otp
}
func (s *sms) sendNotification(message string) error {
fmt.Printf("SMS: sending sms: %s\n", message)
return nil
}
func (s *sms) publishMetric() {
fmt.Printf("SMS: publishing metrics\n")
}
type email struct {
otp
}
func (s *email) genRandomOTP(len int) string {
randomOTP := "1234"
fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
return randomOTP
}
func (s *email) saveOTPCache(otp string) {
fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}
func (s *email) getMessage(otp string) string {
return "EMAIL OTP for login is " + otp
}
func (s *email) sendNotification(message string) error {
fmt.Printf("EMAIL: sending email: %s\n", message)
return nil
}
func (s *email) publishMetric() {
fmt.Printf("EMAIL: publishing metrics\n")
}
func main() {
smsOTP := &sms{}
o := otp{
iOtp: smsOTP,
}
o.genAndSendOTP(4)
fmt.Println("")
emailOTP := &email{}
o = otp{
iOtp: emailOTP,
}
o.genAndSendOTP(4)
}
输出:
SMS: generating random otp 1234
SMS: saving otp: 1234 to cache
SMS: sending sms: SMS OTP for login is 1234
SMS: publishing metrics
EMAIL: generating random otp 1234
EMAIL: saving otp: 1234 to cache
EMAIL: sending email: EMAIL OTP for login is 1234
EMAIL: publishing metrics
Go(Golang)中的第 n 位数字序列程序
目录
-
概述
-
程序
概述
给定一个整数 n,找出无限序列 {1, 2, 3, 4 ….. 无限} 中的第 n 位数字。
示例
Input: 14
Output: 1
序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 … 的第 14 位数字是 1,它是数字 12 的一部分。
示例 2
Input: 17
Output: 3
序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 … 的第 17 位数字是 3,它是数字 13 的一部分。
程序
这里是相同的程序。
package main
import "fmt"
func findNthDigit(n int) int {
numDigits := 1
tenIncrement := 1
start := 1
counter := 9 * tenIncrement * numDigits
for n > counter {
n = n - counter
tenIncrement = tenIncrement * 10
numDigits++
start = start * 10
counter = 9 * tenIncrement * numDigits
}
return findNthDigitUtil(start, numDigits, n)
}
func findNthDigitUtil(start, numDigits, n int) int {
position := n % numDigits
digitWhichHasN := 0
if position == 0 {
digitWhichHasN = start - 1 + n/numDigits
return digitWhichHasN % 10
} else {
digitWhichHasN = start + n/numDigits
positionFromBehind := numDigits - position
answer := 0
for positionFromBehind >= 0 {
answer = digitWhichHasN % 10
digitWhichHasN = digitWhichHasN / 10
positionFromBehind--
}
return answer
}
return 0
}
func main() {
output := findNthDigit(14)
fmt.Println(output)
output = findNthDigit(17)
fmt.Println(output)
}
输出
1
3
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力涵盖所有概念及示例。本教程适合那些希望获得专业知识并深入理解 Golang 的人 – Golang 综合教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章就是为你准备的 – 所有设计模式 Golang
Tic Tac Toe 完整工作程序在 Go 语言中
目录
-
概述
-
程序
-
完整工作代码:
概述
让我们首先通过一个例子了解什么是井字游戏。
-
有一个 n*n 的棋盘,每个块只能在空时标记为交叉或圆圈。
-
最多两名玩家同时进行游戏,每人轮流进行。
-
第一名玩家在其回合中在棋盘的任意块上标记交叉。而第二名玩家在其回合中在棋盘的任意块上标记圆圈。
-
目标是让某一行、某一列或某一对角线的所有块都标记为任一符号,交叉或圆圈。
-
两位玩家将努力阻止对方实现这个目标。谁先实现目标,谁就获胜。
-
如果棋盘上的所有块都已填满,而没有任何玩家能够用自己的符号标记完整的行、列或对角线,那么游戏结果为平局。
-
一旦一名玩家赢得游戏,就不允许再进行更多的移动。
让我们通过一个例子来理解这个游戏。假设有一个 3*3 的网格。点(‘.’)表示一个空块。
Player 1 Move with Symbol * at Position X:1 Y:1
...
.*.
...
Player 2 Move with Symbol o at Position X:1 Y:2
...
.*o
...
Player 1 Move with Symbol * at Position X:2 Y:0
...
.*o
*..
Player 2 Move with Symbol o at Position X:0 Y:2
..o
.*o
*..
Player 1 Move with Symbol * at Position X:2 Y:2
..o
.*o
*.*
Player 2 Move with Symbol o at Position X:0 Y:0
o.o
.*o
*.*
Player 1 Move with Symbol * at Position X:2 Y:1
o.o
.*o
***
First Player Win
o.o
.*o
***
在上述游戏中,第一名玩家获胜,因为第三行完全被符号交叉占据 – ‘*’
程序
这里是完整的工作代码
symbol.go
package main
type Symbol uint8
const (
Cross Symbol = iota
Circle
Dot
)
iPlayer.go
package main
type iPlayer interface {
getSymbol() Symbol
getNextMove() (int, int, error)
getID() int
}
humanPlayer.go
package main
import "fmt"
var (
MovesPlayer1 = [4][2]int{{1, 1}, {2, 0}, {2, 2}, {2, 1}}
MovesPlayer2 = [4][2]int{{1, 2}, {0, 2}, {0, 0}, {0, 0}}
)
type humanPlayer struct {
symbol Symbol
index int
id int
}
func (h *humanPlayer) getSymbol() Symbol {
return h.symbol
}
func (h *humanPlayer) getNextMove() (int, int, error) {
if h.symbol == Cross {
h.index = h.index + 1
return MovesPlayer1[h.index-1][0], MovesPlayer1[h.index-1][1], nil
} else if h.symbol == Circle {
h.index = h.index + 1
return MovesPlayer2[h.index-1][0], MovesPlayer2[h.index-1][1], nil
}
return 0, 0, fmt.Errorf("Invalid Symbol")
}
func (h *humanPlayer) getID() int {
return h.id
}
computerPlayer.go
package main
type computerPlayer struct {
symbol Symbol
id int
}
func (c *computerPlayer) getSymbol() Symbol {
return c.symbol
}
func (c *computerPlayer) getNextMove() (int, int, error) {
//To be implemented
return 0, 0, nil
}
func (c *computerPlayer) getID() int {
return c.id
}
gameStatus.go
package main
type GameStatus uint8
const (
GameInProgress GameStatus = iota
GameDraw
FirstPlayerWin
SecondPlayerWin
)
board.go
package main
import "fmt"
type board struct {
square [][]Symbol
dimension int
}
func (b *board) printBoard() {
for i := 0; i < b.dimension; i++ {
for j := 0; j < b.dimension; j++ {
if b.square[i][j] == Dot {
fmt.Print(".")
} else if b.square[i][j] == Cross {
fmt.Print("*")
} else {
fmt.Print("o")
}
}
fmt.Println("")
}
}
func (b *board) markSymbol(i, j int, symbol Symbol) (bool, Symbol, error) {
if i > b.dimension || j > b.dimension {
return false, Dot, fmt.Errorf("index input is greater than dimension")
}
if b.square[i][j] != Dot {
return false, Dot, fmt.Errorf("input square already marked")
}
if symbol != Cross && symbol != Circle {
return false, Dot, fmt.Errorf("incorrect Symbol")
}
b.square[i][j] = symbol
win := b.checkWin(i, j, symbol)
return win, symbol, nil
}
func (b *board) checkWin(i, j int, symbol Symbol) bool {
//Check Row
rowMatch := true
for k := 0; k < b.dimension; k++ {
if b.square[i][k] != symbol {
rowMatch = false
}
}
if rowMatch {
return rowMatch
}
//Check Row
columnMatch := true
for k := 0; k < b.dimension; k++ {
if b.square[k][j] != symbol {
columnMatch = false
}
}
if columnMatch {
return columnMatch
}
//Check diagonal
diagonalMatch := false
if i == j {
diagonalMatch = true
for k := 0; k < b.dimension; k++ {
if b.square[k][k] != symbol {
diagonalMatch = false
}
}
}
return diagonalMatch
}
game.go
package main
import "fmt"
type game struct {
board *board
firstPlayer iPlayer
secondPlayer iPlayer
firstPlayerTurn bool
moveIndex int
gameStatus GameStatus
}
func initGame(b *board, p1, p2 iPlayer) *game {
game := &game{
board: b,
firstPlayer: p1,
secondPlayer: p2,
firstPlayerTurn: true,
gameStatus: GameInProgress,
}
return game
}
func (g *game) play() error {
var win bool
var symbol Symbol
for {
if g.firstPlayerTurn {
x, y, err := g.firstPlayer.getNextMove()
if err != nil {
return err
}
win, symbol, err = g.board.markSymbol(x, y, g.firstPlayer.getSymbol())
if err != nil {
return err
}
g.firstPlayerTurn = false
g.printMove(g.firstPlayer, x, y)
} else {
x, y, err := g.secondPlayer.getNextMove()
if err != nil {
return err
}
win, symbol, err = g.board.markSymbol(x, y, g.secondPlayer.getSymbol())
if err != nil {
return err
}
g.firstPlayerTurn = true
g.printMove(g.secondPlayer, x, y)
}
g.moveIndex = g.moveIndex + 1
g.setGameStatus(win, symbol)
if g.gameStatus != GameInProgress {
break
}
}
return nil
}
func (g *game) setGameStatus(win bool, symbol Symbol) {
if win {
if g.firstPlayer.getSymbol() == symbol {
g.gameStatus = FirstPlayerWin
return
} else if g.secondPlayer.getSymbol() == symbol {
g.gameStatus = SecondPlayerWin
return
}
}
if g.moveIndex == g.board.dimension*g.board.dimension {
g.gameStatus = GameDraw
return
}
g.gameStatus = GameInProgress
}
func (g *game) printMove(player iPlayer, x, y int) {
symbolString := ""
symbol := player.getSymbol()
if symbol == Cross {
symbolString = "*"
} else if symbol == Circle {
symbolString = "o"
}
fmt.Printf("Player %d Move with Symbol %s at Position X:%d Y:%d\n", player.getID(), symbolString, x, y)
g.board.printBoard()
fmt.Println("")
}
func (g *game) printResult() {
switch g.gameStatus {
case GameInProgress:
fmt.Println("Game in Between")
case GameDraw:
fmt.Println("Game Drawn")
case FirstPlayerWin:
fmt.Println("First Player Win")
case SecondPlayerWin:
fmt.Println("Second Player Win")
default:
fmt.Println("Invalid Game Status")
}
g.board.printBoard()
}
输出
在上述程序中,我们为两位玩家固定了移动,保存在humanPlayer.go文件中。以下是基于这些移动的输出。
Player 1 Move with Symbol * at Position X:1 Y:1
...
.*.
...
Player 2 Move with Symbol o at Position X:1 Y:2
...
.*o
...
Player 1 Move with Symbol * at Position X:2 Y:0
...
.*o
*..
Player 2 Move with Symbol o at Position X:0 Y:2
..o
.*o
*..
Player 1 Move with Symbol * at Position X:2 Y:2
..o
.*o
*.*
Player 2 Move with Symbol o at Position X:0 Y:0
o.o
.*o
*.*
Player 1 Move with Symbol * at Position X:2 Y:1
o.o
.*o
***
First Player Win
o.o
.*o
***
完整工作代码:
这里是一个文件中的完整工作代码
main.go
package main
import "fmt"
type Symbol uint8
const (
Cross Symbol = iota
Circle
Dot
)
type GameStatus uint8
const (
GameInProgress GameStatus = iota
GameDraw
FirstPlayerWin
SecondPlayerWin
)
type iPlayer interface {
getSymbol() Symbol
getNextMove() (int, int, error)
getID() int
}
var (
MovesPlayer1 = [4][2]int{{1, 1}, {2, 0}, {2, 2}, {2, 1}}
MovesPlayer2 = [4][2]int{{1, 2}, {0, 2}, {0, 0}, {0, 0}}
)
type humanPlayer struct {
symbol Symbol
index int
id int
}
func (h *humanPlayer) getSymbol() Symbol {
return h.symbol
}
func (h *humanPlayer) getNextMove() (int, int, error) {
if h.symbol == Cross {
h.index = h.index + 1
return MovesPlayer1[h.index-1][0], MovesPlayer1[h.index-1][1], nil
} else if h.symbol == Circle {
h.index = h.index + 1
return MovesPlayer2[h.index-1][0], MovesPlayer2[h.index-1][1], nil
}
return 0, 0, fmt.Errorf("Invalid Symbol")
}
func (h *humanPlayer) getID() int {
return h.id
}
type computerPlayer struct {
symbol Symbol
id int
}
func (c *computerPlayer) getSymbol() Symbol {
return c.symbol
}
func (c *computerPlayer) getNextMove() (int, int, error) {
//To be implemented
return 0, 0, nil
}
func (c *computerPlayer) getID() int {
return c.id
}
type board struct {
square [][]Symbol
dimension int
}
func (b *board) printBoard() {
for i := 0; i < b.dimension; i++ {
for j := 0; j < b.dimension; j++ {
if b.square[i][j] == Dot {
fmt.Print(".")
} else if b.square[i][j] == Cross {
fmt.Print("*")
} else {
fmt.Print("o")
}
}
fmt.Println("")
}
}
func (b *board) markSymbol(i, j int, symbol Symbol) (bool, Symbol, error) {
if i > b.dimension || j > b.dimension {
return false, Dot, fmt.Errorf("index input is greater than dimension")
}
if b.square[i][j] != Dot {
return false, Dot, fmt.Errorf("input square already marked")
}
if symbol != Cross && symbol != Circle {
return false, Dot, fmt.Errorf("incorrect Symbol")
}
b.square[i][j] = symbol
win := b.checkWin(i, j, symbol)
return win, symbol, nil
}
func (b *board) checkWin(i, j int, symbol Symbol) bool {
//Check Row
rowMatch := true
for k := 0; k < b.dimension; k++ {
if b.square[i][k] != symbol {
rowMatch = false
}
}
if rowMatch {
return rowMatch
}
//Check Row
columnMatch := true
for k := 0; k < b.dimension; k++ {
if b.square[k][j] != symbol {
columnMatch = false
}
}
if columnMatch {
return columnMatch
}
//Check diagonal
diagonalMatch := false
if i == j {
diagonalMatch = true
for k := 0; k < b.dimension; k++ {
if b.square[k][k] != symbol {
diagonalMatch = false
}
}
}
return diagonalMatch
}
type game struct {
board *board
firstPlayer iPlayer
secondPlayer iPlayer
firstPlayerTurn bool
moveIndex int
gameStatus GameStatus
}
func initGame(b *board, p1, p2 iPlayer) *game {
game := &game{
board: b,
firstPlayer: p1,
secondPlayer: p2,
firstPlayerTurn: true,
gameStatus: GameInProgress,
}
return game
}
func (g *game) play() error {
var win bool
var symbol Symbol
for {
if g.firstPlayerTurn {
x, y, err := g.firstPlayer.getNextMove()
if err != nil {
return err
}
win, symbol, err = g.board.markSymbol(x, y, g.firstPlayer.getSymbol())
if err != nil {
return err
}
g.firstPlayerTurn = false
g.printMove(g.firstPlayer, x, y)
} else {
x, y, err := g.secondPlayer.getNextMove()
if err != nil {
return err
}
win, symbol, err = g.board.markSymbol(x, y, g.secondPlayer.getSymbol())
if err != nil {
return err
}
g.firstPlayerTurn = true
g.printMove(g.secondPlayer, x, y)
}
g.moveIndex = g.moveIndex + 1
g.setGameStatus(win, symbol)
if g.gameStatus != GameInProgress {
break
}
}
return nil
}
func (g *game) setGameStatus(win bool, symbol Symbol) {
if win {
if g.firstPlayer.getSymbol() == symbol {
g.gameStatus = FirstPlayerWin
return
} else if g.secondPlayer.getSymbol() == symbol {
g.gameStatus = SecondPlayerWin
return
}
}
if g.moveIndex == g.board.dimension*g.board.dimension {
g.gameStatus = GameDraw
return
}
g.gameStatus = GameInProgress
}
func (g *game) printMove(player iPlayer, x, y int) {
symbolString := ""
symbol := player.getSymbol()
if symbol == Cross {
symbolString = "*"
} else if symbol == Circle {
symbolString = "o"
}
fmt.Printf("Player %d Move with Symbol %s at Position X:%d Y:%d\n", player.getID(), symbolString, x, y)
g.board.printBoard()
fmt.Println("")
}
func (g *game) printResult() {
switch g.gameStatus {
case GameInProgress:
fmt.Println("Game in Between")
case GameDraw:
fmt.Println("Game Drawn")
case FirstPlayerWin:
fmt.Println("First Player Win")
case SecondPlayerWin:
fmt.Println("Second Player Win")
default:
fmt.Println("Invalid Game Status")
}
g.board.printBoard()
}
func main() {
board := &board{
square: [][]Symbol{{Dot, Dot, Dot}, {Dot, Dot, Dot}, {Dot, Dot, Dot}},
dimension: 3,
}
player1 := &humanPlayer{
symbol: Cross,
id: 1,
}
player2 := &humanPlayer{
symbol: Circle,
id: 2,
}
game := initGame(board, player1, player2)
game.play()
game.printResult()
}
输出
在上述程序中,我们也为两位玩家固定了移动,保存在humanPlayer 类中。以下是基于这些移动的输出。
Player 1 Move with Symbol * at Position X:1 Y:1
...
.*.
...
Player 2 Move with Symbol o at Position X:1 Y:2
...
.*o
...
Player 1 Move with Symbol * at Position X:2 Y:0
...
.*o
*..
Player 2 Move with Symbol o at Position X:0 Y:2
..o
.*o
*..
Player 1 Move with Symbol * at Position X:2 Y:2
..o
.*o
*.*
Player 2 Move with Symbol o at Position X:0 Y:0
o.o
.*o
*.*
Player 1 Move with Symbol * at Position X:2 Y:1
o.o
.*o
***
First Player Win
o.o
.*o
***
注意: 请查看我们的 Golang 高级教程。本系列教程详细且我们尽力用示例覆盖所有概念。本教程适合希望掌握并深入理解 Golang 的读者 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章就是为你准备的 - 所有设计模式 Golang