安装
由于官网被墙,不用科学上网的话是访问不了的,不过可以从Go下载 - Go语言中文网 - Golang中文社区 (studygolang.com)下载最新的go安装包
-
windows
直接下载安装包安装即可,安装完后在命令行执行
go version
查看go版本检查是否安装成功 -
Linux环境
参考另一篇文章Ubuntu下go语言环境搭建_g++ : executable file not found in $path-CSDN博客
编辑器
用vs code或者goland都可以,看个人习惯
goland的话可以在Other Versions - GoLand (jetbrains.com)下载2022.1.4及更老的版本,这些版本下载下来是免激活的,有效期直接到2099年
运行和编译
运行:go run main.go
编译:go build main.go
编译成功后会生成可执行文件
go语法
数据类型
-
基本类型
整型:int
,
int8,
int16,
int32,
int64,
uint,
uint8,
uint16,
uint32,
uint64等浮点:float32,float64
布尔(bool):true,false
字符串
-
**聚合类型:**数组和结构体
-
**引用类型:**指针,切片,map集合,函数和Channel
-
接口类型:interface
变量声明
有以下三种方式。
# 1) a的默认值为0
var a int
# 2) 声明并初始化a,a自动赋值为int
var a = 1
# 3) 简写声明
message := "hello world"
还可以在一行声明多个变量
var b, c int = 2, 3
//不指定任何类型
var nhooo4, nhooo5, nhooo6 = 100, "GFG", 7896.46
var(
nhooo1 = 100
nhooo2 = 200.57
nhooo3 bool
nhooo4 string = "www"
)
数组
//一维数组声明、赋值和遍历
var a = [5]int{11, 22, 33, 44}
a[4] = 55
for k, v := range a {
fmt.Println(k, v)
}
//二维数组声明和遍历
var b = [3][2]int{{11, 22}, {33, 44}, {55, 66}}
for k, v := range b {
for ki, vi := range v {
fmt.Println(k, ki, vi)
}
}
//自动识别数组长度
var c = [...]int{11, 22, 33, 44}
fmt.Println(c)
数组有一定限制,比如不能修改数组长度、不能添加元素、不能获取子数组
如果不给数组赋值,那么数组的默认值是0或空值
字符串
声明
package main
import "fmt"
func main() {
//创建并初始化
//使用双引号,支持转义字符
My_value_1 := "Welcome to nhooo"
//添加转义字符
My_value_2 := "Welcome!\nnhooo"
//使用反引号,不支持转义字符
My_value_3 := `Hello!nhooo`
//添加转义字符
//原始文本
My_value_4 := `Hello!\nnhooo`
//显示
fmt.Println("String 1: ", My_value_1)
fmt.Println("String 2: ", My_value_2)
fmt.Println("String 3: ", My_value_3)
fmt.Println("String 4: ", My_value_4)
//创建和初始化一个字节片
myslice1 := []byte{0x47, 0x65, 0x65, 0x6b, 0x73}
//从切片创建字符串
mystring1 := string(myslice1)
fmt.Println("String 5: ", mystring1)
//输出字符串长度
fmt.Println("String 5.len: ", len(mystring1))
}
输出结果:
String 1: Welcome to nhooo
String 2: Welcome!
nhooo
String 3: Hello!nhooo
String 4: Hello!\nnhooo
String 5: Geeks
String 5.len: 5
常用函数
package main
import (
"fmt"
"strings"
)
func main() {
//比较字符串使用比较函数
fmt.Println(strings.Compare("gfg", "Geeks"))
//使用+运算符连接字符串
str3 := "hello"
str4 := "world"
result := str3 + " " + str4
fmt.Println(result)
//或者这样
result = fmt.Sprintf("%s %s", str3, str4)
fmt.Println(result)
//使用join()函数将切片连接成字符串
myslice := []string{"Welcome", "To", "nhooo", "Portal"}
result = strings.Join(myslice, "-")
fmt.Println(result)
//字符串拆分成切片
res4 := strings.Split("nhooo,geeks", ",")
fmt.Println(res4)
//包含
str1 := "Welcome to Nhooo for Nhooo "
res1 := strings.Contains(str1, "Nhooo")
fmt.Println(res1)
//索引
res2 := strings.Index(str1, "Nhooo")
fmt.Println(res2)
}
输出结果:
1
hello world
hello world
Welcome-To-nhooo-Portal
[nhooo geeks]
true
11
切片
- 声明
package main
import "fmt"
func main() {
//使用var关键字,创建切片
var my_slice_1 = []string{"nhooos", "for", "nhooos"}
fmt.Println("My Slice 1:", my_slice_1)
//使用简写声明
my_slice_2 := []int{12, 45, 67, 56, 43, 34, 45}
fmt.Println("My Slice 2:", my_slice_2)
//创建一个数组
arr := [7]string{"一", "二", "三", "四", "五", "六", "七"}
//用make创建切片,长度为4,容量为7
var my_slice_3 = make([]int, 4, 7)
my_slice_3[0] = 1//赋值
fmt.Printf("Slice 3 = %v, \nlength = %d, \ncapacity = %d\n", my_slice_3, len(my_slice_3), cap(my_slice_3))
//显示数组
fmt.Println("数组:", arr)
//通过截取数组或切片创建切片,从下标1(包括)到6(不包括)共5个元素
myslice := arr[1:6]
//显示切片
fmt.Println("切片:", myslice)
//显示切片的长度
fmt.Printf("切片长度: %d", len(myslice))
//显示切片的容量
fmt.Printf("\n切片容量: %d", cap(myslice))
}
输出结果:
My Slice 1: [nhooos for nhooos]
My Slice 2: [12 45 67 56 43 34 45]
Slice 3 = [1 0 0 0],
length = 4,
capacity = 7
数组: [一 二 三 四 五 六 七]
切片: [二 三 四 五 六]
切片长度: 5
切片容量: 6
在上面的实例中,我们从给定的数组中创建一个切片。这里,片的指针指向索引1,因为片的下界被设置为1,所以它开始访问来自索引1的元素。切片的长度为5,表示切片中元素的总数为5,而切片6的容量表示最多可以存储6个元素,计算方式为len(arr[切片第一个元素位置:])
,即不管切片截取数组的多少,直接用原数组容量-截取原数组的第一个位置下标。
- 切片扩容
package main
import "fmt"
func main() {
// 创建一个切片
slice := []int{1, 2, 3, 4, 5}
// 获取切片长度和容量
length := len(slice)
capacity := cap(slice)
fmt.Printf("Length: %d, Capacity: %d\n", length, capacity)
//结果:Length: 5, Capacity: 5
// 追加元素
slice = append(slice, 6)
// 再次获取长度和容量
length = len(slice)
capacity = cap(slice)
fmt.Printf("Length: %d, Capacity: %d\n", length, capacity)
//结果:Length: 6, Capacity: 10
// 2次追加元素
slice = append(slice, 7)
// 再次获取长度和容量
length = len(slice)
capacity = cap(slice)
fmt.Printf("Length: %d, Capacity: %d\n", length, capacity)
//结果:Length: 7, Capacity: 10
}
扩容策略:如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。总容量从原来的1个翻倍到现在的2个。
一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。
-
切片复制
func copy(dst, src []Type) int
此处,dst表示目标切片,而src表示源切片,它将返回要复制的元素数量
package main import "fmt" func main() { //创建切片 slc1 := []int{58, 69, 40, 45, 11, 56, 67, 21, 65} var slc2 []int slc3 := make([]int, 5) slc4 := []int{78, 50, 67, 77} //复制之前 fmt.Println("Slice_1:", slc1) fmt.Println("Slice_2:", slc2) fmt.Println("Slice_3:", slc3) fmt.Println("Slice_4:", slc4) //复制切片 copy_1 := copy(slc2, slc1) fmt.Println("\nSlice:", slc2) fmt.Println("复制的元素总数:", copy_1) copy_2 := copy(slc3, slc1) fmt.Println("\nSlice:", slc3) fmt.Println("复制的元素总数:", copy_2) copy_3 := copy(slc4, slc1) fmt.Println("\nSlice:", slc4) fmt.Println("复制的元素总数:", copy_3) //这里不要混淆,因为在上面 //复制了slc4的代码行 //并因此进行永久修改,即 // slc 4包含[58 69 40 45] copy_4 := copy(slc1, slc4) fmt.Println("\nSlice:", slc1) fmt.Println("复制的元素总数:", copy_4) } 输出: Slice_1: [58 69 40 45 11 56 67 21 65] Slice_2: [] Slice_3: [0 0 0 0 0] Slice_4: [78 50 67 77] Slice: [] 复制的元素总数: 0 Slice: [58 69 40 45 11] 复制的元素总数: 5 Slice: [58 69 40 45] 复制的元素总数: 4 Slice: [58 69 40 45 11 56 67 21 65] 复制的元素总数: 4
控制语句
// for 循环
for i := 0; i < 4; i++{
fmt.Printf(i)
}
// 无限循环
for {
fmt.Printf("nhooo\n")
}
//for range循环
for i, j:= range rvariable{
// 语句..
}
// for遍历 channel
chnl := make(chan int)
go func() {
chnl <- 100
chnl <- 1000
chnl <- 10000
chnl <- 100000
close(chnl)
}()
for i := range chnl {
fmt.Println(i)
}
//select
c := make(chan int)
select {
case <-c:
default:
fmt.Println("!.. Default case..!")
}
函数
package main
import (
"fmt"
"strings"
)
// 普通函数
func area(length, width int) int {
Ar := length * width
return Ar
}
// 可变参数函数联接字符串
func joinstr(element ...string) string {
return strings.Join(element, "-")
}
func main() {
//调用普通函数
Ar := area(10, 20)
fmt.Println(Ar)
//调用可变参数函数
fmt.Println(joinstr("hello", "world", "!!"))
//在匿名函数
func(ele string) {
fmt.Println(ele)
}("hi")
}
- main函数
在Go语言中,main包是一个特殊的软件包,与可执行程序一起使用,并且该package包含*main()函数。在main()函数是一种特殊类型的函数,它是可执行程序的入口点。它不带任何参数也不返回任何内容。由于可以自动调用main()函数,因此无需显式调用main()函数,并且每个可执行程序必须包含一个package main和main()*函数。
- init()函数
init()函数就像main函数一样,不带任何参数也不返回任何东西。 每个包中都存在此函数,并且在初始化包时将调用此函数
。 该函数是隐式声明的,因此您不能从任何地方引用它,并且可以在同一程序中创建多个init()函数,并且它们将按照创建顺序执行。 您可以在程序中的任何位置创建init()函数,并且它们以词汇文件名顺序(字母顺序)调用。 并允许在init()函数中放置语句,但始终记住要在main()函数调用之前执行init()函数,因此它不依赖于main()函数。 init()函数的主要目的是初始化无法在全局上下文中初始化的全局变量。
package main
import "fmt"
//多个init()函数
func init() {
fmt.Println("Welcome to init() function")
}
func init() {
fmt.Println("Hello! init() function")
}
func main() {
fmt.Println("Welcome to main() function")
}
其他函数
package main
import (
"fmt"
"strings"
)
// 普通函数
func area(length, width int) int {
Ar := length * width
return Ar
}
// 可变参数函数联接字符串
func joinstr(element ...string) string {
return strings.Join(element, "-")
}
// myfunc返回多个int类型的值
func myfunc(p, q int) (int, int, int) {
return p - q, p * q, p + q
}
func main() {
//调用普通函数
Ar := area(10, 20)
fmt.Println(Ar)
//调用可变参数函数
fmt.Println(joinstr("hello", "world", "!!"))
//在匿名函数
func(ele string) {
fmt.Println(ele)
}("hi")
}
方法
Go方法与Go函数相似,但有一点不同,就是方法中包含一个接收者参数。在接收者参数的帮助下,该方法可以访问接收者的属性。
-
结构类型接收器的方法
package main import "fmt" //Author 结构体 type author struct { name string branch string particles int salary int } //接收者的方法 func (a author) show() { fmt.Println("Author's Name: ", a.name) fmt.Println("Branch Name: ", a.branch) fmt.Println("Published articles: ", a.particles) fmt.Println("Salary: ", a.salary) } func main() { //初始化值 //Author结构体 res := author{ name: "Sona", branch: "CSE", particles: 203, salary: 34000, } //调用方法 res.show() }
-
非结构类型接收器的方法
package main import "fmt" //类型定义 type data int //定义一个方法 //非结构类型的接收器 func (d1 data) multiply(d2 data) data { return d1 * d2 } /* //如果您尝试运行此代码, //然后编译器将抛出错误 func(d1 int)multiply(d2 int)int{ return d1 * d2 } */ func main() { value1 := data(23) value2 := data(20) res := value1.multiply(value2) fmt.Println("最终结果: ", res) }
-
方法可以接受指针和值
package main import "fmt" // Author 结构体 type author struct { name string branch string } //带有指针的方法 //author类型的接收者 func (a *author) show_1(abranch string) { (*a).branch = abranch } //带有值的方法 //作者类型的接收者 func (a author) show_2() { a.name = "Gourav" fmt.Println("Author's name(Before) : ", a.name) } func main() { //初始化值 //作者结构体 res := author{ name: "Sona", branch: "CSE", } fmt.Println("Branch Name(Before): ", res.branch) //调用show_1方法 //(指针方法)带有值 res.show_1("ECE") fmt.Println("Branch Name(After): ", res.branch) //调用show_2方法 //带有指针的(值方法) (&res).show_2() fmt.Println("Author's name(After): ", res.name) }
输出:
Branch Name(Before): CSE Branch Name(After): ECE Author's name(Before) : Gourav Author's name(After): Sona
-
方法和函数之间的差异
方法 | 函数 |
---|---|
它包含接收器。 | 它不包含接收器。 |
它可以接受指针和值。 | 它不能同时接受指针和值。 |
可以在程序中定义相同名称但不同类型的方法。 | 程序中不允许定义相同名称但不同类型的函数。 |
空白标识符(下划线)
可以使用空白标识符定义和使用未使用的变量。未使用变量是指用户在整个程序中定义但从未使用过的变量
package main
import "fmt"
func main() {
//调用函数
//函数返回两个值
//分配给mul和div标识符
mul, div := mul_div(105, 7)
//仅使用mul变量
//编译器会报错
fmt.Println("105 x 7 = ", mul)
}
//函数返回两个
//整数类型的值
func mul_div(n1 int, n2 int) (int, int) {
//返回值
return n1 * n2, n1 / n2
}
defer关键字
在Go语言中,defer语句会延迟函数或方法或匿名方法的执行,直到附近的函数返回为止。换句话说,延迟函数或方法调用参数会立即求值,但是它们会执行到附近的函数返回为止。您可以使用defer关键字创建延迟的方法,函数或匿名函数。
语法:
// 函数
defer func func_name(parameter_list Type) return_type{
// Code
}
// 方法
defer func (receiver Type) method_name(parameter_list){
// Code
}
defer func (parameter_list)(return_type){
// code
}()
注意事项:
- 在Go语言中,同一程序中允许多个defer语句,并且它们按LIFO(后进先出)顺序执行,如示例2所示。
- 在defer语句中,将在执行defer语句时(而不是在调用它们时)评估参数。
- defer语句通常用于确保在完成文件处理后关闭文件,关闭通道或捕获程序中的紧急情况。
示例
package main
import "fmt"
// 函数
func add(a1, a2 int) int {
res := a1 + a2
fmt.Println("Result: ", res)
return 0
}
func main() {
fmt.Println("Start")
//多个延迟语句
//以LIFO顺序执行
defer fmt.Println("End")
defer add(34, 56)
defer add(10, 10)
}
输出:
Start
Result: 20
Result: 90
End
结构体
Golang中的结构(struct)是一种用户定义的类型,允许将可能不同类型的项分组/组合成单个类型。任何现实世界中拥有一组属性/字段的实体都可以表示为结构。
示例
package main
import "fmt"
//定义结构体 ,注意结构体属性必须以大写开头
type Car struct {
Name, Model, Color string
WeightInKg float64
}
func main() {
c := Car{Name: "Ferrari", Model: "GTC4", Color: "Red", WeightInKg: 1920}
//访问结构字段
//使用点运算符
fmt.Println("Car Name: ", c.Name)
fmt.Println("Car Color: ", c.Color)
//赋新值
//指向一个结构字段
c.Color = "Black"
//显示结果
fmt.Println("Car: ", c)
}
输出:
Car Name: Ferrari
Car Color: Red
Car: {Ferrari GTC4 Black 1920}
-
嵌套结构体
示例
package main import "fmt" //创建结构 type Student struct { name string branch string year int } //创建嵌套结构 type Teacher struct { name string subject string exp int details Student } func main() { //初始化结构字段 result := Teacher{ name: "Suman", subject: "Java", exp: 5, details: Student{"Bongo", "CSE", 2}, } fmt.Println("老师详细情况") fmt.Println("老师的名字: ", result.name) fmt.Println("学科: ", result.subject) fmt.Println("经历: ", result.exp) fmt.Println("\n学生详细资料") fmt.Println("学生的名字: ", result.details.name) fmt.Println("学生的部门名称: ", result.details.branch) fmt.Println("年龄: ", result.details.year) }
输出:
老师详细情况 老师的名字: Suman 学科: Java 经历: 5 学生详细资料 学生的名字: Bongo 学生的部门名称: CSE 年龄: 2
指针
指针是一个变量,用于存储另一个变量的内存地址。Golang中的指针也称为特殊变量
示例
package main
import "fmt"
func main() {
//使用var关键字
//我们没有定义
//没有指定变量的类型
var y = 458
//使用指针变量
// var关键字,不指定类型
var p = &y
fmt.Println("更改前存储在y中的值 = ", y)
fmt.Println("y的地址 = ", &y)
fmt.Println("存储在指针变量p中的值 = ", p)
//这是取消引用指针
//在指针之前使用*运算符
//变量以访问存储的值
//指向它所指向的变量
fmt.Println("更改前存储在y(*p)中的值 = ", *p)
//通过赋值改变y的值
//指针的新值
*p = 500
fmt.Println("更改后存储在y(*p)中的值 = ", y)
}
输出:
更改前存储在y中的值 = 458
y的地址 = 0xc0000120a8
存储在指针变量p中的值 = 0xc0000120a8
更改前存储在y(*p)中的值 = 458
更改后存储在y(*p)中的值 = 500
-
指针作为函数参数
示例
// Go程序创建一个指针 //并将其传递给函数 package main import "fmt" //接受指针作为参数 func ptf(a *int) { //解引用 *a = 748 } func main() { //正常变量 var x = 100 fmt.Printf("函数调用前x的值为: %d\n", x) //获取一个指针变量 //并分配地址 var pa *int = &x //通过以下方式调用函数 //将指针传递给函数 ptf(pa) fmt.Printf("函数调用后x的值为: %d\n", x) }
输出:
函数调用前x的值为: 100 函数调用后x的值为: 748
-
结构体和指针
示例
package main import "fmt" //定义结构体 type Employee struct { name string empid int } func main() { //创建的实例 //员工结构类型 emp := Employee{"ABC", 19078} //在这里,它是指向结构体的指针 pts := &emp //显示值 fmt.Println(pts) //更新name的值 pts.name = "XYZ" fmt.Println(pts) }
输出:
&{ABC 19078} &{XYZ 19078}
Golang为我们提供了使用emp8.firstName而不是显式解引用(* emp8).firstName来访问firstName字段的选项。 显示此示例如下:
示例
//指向结构的指针 package main import "fmt" //定义一个结构 type Employee struct { firstName, lastName string age, salary int } func main() { //获取指向结构体的指针 emp8 := &Employee{"Sam", "Anderson", 55, 6000} // emp8.firstName用于访问 //字段firstName fmt.Println("First Name: ", emp8.firstName) fmt.Println("Age: ", emp8.age) }
输出:
First Name: Sam Age: 55
接口
接口是一种自定义类型,用于指定一组一个或多个方法签名,并且该接口是抽象的,因此不允许您创建该接口的实例。但是您可以创建接口类型的变量,并且可以为该变量分配具有接口所需方法的具体类型值。换句话说,接口既是方法的集合,也是自定义类型。
示例
// Golang程序说明如何
//实现接口
package main
import "fmt"
//创建一个接口
type tank interface {
// 方法
Tarea() float64
Volume() float64
}
type myvalue struct {
radius float64
height float64
}
//实现方法
//桶的(Tank)接口
func (m myvalue) Tarea() float64 {
return 2*m.radius*m.height + 2*3.14*m.radius*m.radius
}
func (m myvalue) Volume() float64 {
return 3.14 * m.radius * m.radius * m.height
}
func main() {
// 访问使用桶的接口
var t tank
t = myvalue{10, 14}
fmt.Println("桶的面积 :", t.Tarea())
fmt.Println("桶的容量:", t.Volume())
}
输出:
桶的面积 : 908
桶的容量: 4396
注意事项:
- 接口的零值为nil。
- 当接口包含零个方法时,此类接口称为
空接口
。因此,所有类型都实现空接口。
示例
//类型断言
package main
import "fmt"
func myfun(a interface{}) {
//提取a的值
val := a.(string)
fmt.Println("值为: ", val)
}
func main() {
var val interface {
} = "nhooo"
myfun(val)
}
输出:
值为: nhooo
在上面的示例中,如果将val:= a。(string)语句更改为val:= a。(int),则程序会抛出panic异常。因此,为了避免此问题,我们使用以下语法:
value, ok := a.(T)
在这里,如果a的类型等于T,则该值包含a的动态值,并且ok将设置为true。并且如果a的类型不等于T,则ok设置为false并且value包含零值,并且程序不会抛出panic异常。如下面的程序所示:
示例
package main
import "fmt"
func myfun(a interface{}) {
value, ok := a.(float64)
fmt.Println(value, ok)
}
func main() {
var a1 interface {
} = 98.09
myfun(a1)
var a2 interface {
} = "nhooo"
myfun(a2)
}
输出:
98.09 true
0 false
-
接口嵌套和接口方法嵌套
示例
package main import "fmt" // 接口 1 type AuthorDetails interface { details() } // 接口 2 type AuthorArticles interface { articles() picked() } // 接口 3 //接口3嵌套了接口1和接口2,同时加入了自己的方法 type FinalDetails interface { details() AuthorArticles cdeatils() } // author 结构体 type author struct { a_name string branch string college string year int salary int particles int tarticles int cid int post string pick int } // 实现接口1的方法 func (a author) details() { fmt.Printf("作者: %s", a.a_name) fmt.Printf("\n部门: %s 通过日期: %d", a.branch, a.year) fmt.Printf("\n大学名称: %s", a.college) fmt.Printf("\n薪水: %d", a.salary) fmt.Printf("\n发表文章数: %d", a.particles) } // 实现接口2的方法 func (a author) articles() { pendingarticles := a.tarticles - a.particles fmt.Printf("\n待定文章数: %d", pendingarticles) } func (a author) picked() { fmt.Printf("\n所选文章的总数: %d", a.pick) } // 实现嵌入了接口的方法 func (a author) cdeatils() { fmt.Printf("\n作者Id: %d", a.cid) fmt.Printf("\n提交: %s", a.post) } func main() { //结构体赋值 values := author{ a_name: "Mickey", branch: "Computer science", college: "XYZ", year: 2012, salary: 50000, particles: 209, tarticles: 309, cid: 3087, post: "Technical content writer", pick: 58, } // 使用 FinalDetails 接口访问接口1,2的方法 var f FinalDetails = values f.details() f.articles() f.picked() f.cdeatils() }
输出:
作者: Mickey 部门: Computer science 通过日期: 2012 大学名称: XYZ 薪水: 50000 发表文章数: 209 待定文章数: 100 所选文章的总数: 58 作者Id: 3087 提交: Technical content writer
Goroutines
Goroutine是一种函数或方法,可与程序中存在的任何其他Goroutine一起独立且同时执行。换句话说,每个Go语言中同时执行的活动称为Goroutines,您可以将Goroutine视为轻量级线程。与线程相比,创建Goroutines的成本非常小。每个程序至少包含一个Goroutine,并且该Goroutine被称为主Goroutine。如果主Goroutine终止,则所有Goroutine在主Goroutine之下运行,那么程序中存在的所有goroutine也将终止;Goroutine始终在后台运行。
示例
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Welcome!! to Main function")
//创建匿名Goroutine
go func() {
fmt.Println("Welcome!! to (cainiaojc.com)")
}()
time.Sleep(1 * time.Second)
fmt.Println("GoodBye!! to Main function")
}
输出:
Welcome!! to Main function
Welcome!! to (cainiaojc.com)
GoodBye!! to Main function
-
多个goroutine
示例
package main import ( "fmt" "time" ) //goroutine 1 func Aname() { arr1 := [4]string{"Rohit", "Suman", "Aman", "Ria"} for t1 := 0; t1 <= 3; t1++ { time.Sleep(150 * time.Millisecond) fmt.Printf("%s\n", arr1[t1]) } } // goroutine 2 func Aid() { arr2 := [4]int{300, 301, 302, 303} for t2 := 0; t2 <= 3; t2++ { time.Sleep(500 * time.Millisecond) fmt.Printf("%d\n", arr2[t2]) } } func main() { fmt.Println("!...主 Go-routine 开始...!") // 调用 Goroutine 1 go Aname() // 调用 Goroutine 2 go Aid() time.Sleep(3500 * time.Millisecond) fmt.Println("\n!...主 Go-routine 结束...!") }
输出:
!...主 Go-routine 开始...! Rohit Suman Aman 300 Ria 301 302 303 !...主 Go-routine 结束...!
通道(Channel)
通道是goroutine与另一个goroutine通信的媒介,并且这种通信是无锁的。换句话说,通道是一种技术,它允许一个goroutine将数据发送到另一个goroutine。默认情况下,通道是双向的,这意味着goroutine可以通过同一通道发送或接收数据
-
声明
示例
package main import "fmt" func main() { //使用var关键字创建通道 var mychannel chan int fmt.Println("channel的值: ", mychannel) fmt.Printf("channel的类型: %T ", mychannel) // 使用 make() 函数创建通道 mychannel1 := make(chan int) fmt.Println("\nchannel1的值:", mychannel1) fmt.Printf("channel1的类型: %T ", mychannel1) }
输出:
channel的值: <nil> channel的类型: chan int channel1的值: 0xc0000160c0 channel1的类型: chan int
-
发送和接收数据
示例
package main import "fmt" func myfunc(ch chan int) { fmt.Println(234 + <-ch) } func main() { fmt.Println("主方法开始") //创建通道l ch := make(chan int) go myfunc(ch) ch <- 23 fmt.Println("主方法结束") }
输出:
主方法开始 257 主方法结束
-
关闭通道
示例
//Go程序说明如何 //关闭使用的通道 //range循环和关闭函数 package main import "fmt" func myfun(mychnl chan string) { for v := 0; v < 4; v++ { mychnl <- "nhooo" } close(mychnl) } func main() { //创建通道 c := make(chan string) // 使用 Goroutine go myfun(c) //当ok的值为为true时,表示通道已打开,可以发送或接收数据 //当ok的值设置为false时,表示通道已关闭 for { res, ok := <-c if ok == false { fmt.Println("通道关闭 ", ok) break } fmt.Println("通道打开 ", res, ok) } }
输出:
通道打开 nhooo true 通道打开 nhooo true 通道打开 nhooo true 通道打开 nhooo true 通道关闭 false
-
注意事项
-
**阻止发送和接收:**在通道中,当数据发送到通道时,控制在发送语句中被阻塞,直到其他goroutine从该通道读取数据。类似地,当通道从goroutine接收数据时,read语句块直到另一条goroutine语句。
-
零值通道:通道的零值为nil。
-
通道中的For循环: for循环可以遍历通道上发送的顺序值,直到关闭为止。
语法:
for item := range Chnl { // 语句.. }
示例
package main import "fmt" func main() { // 使用 make() 函数创建通道 mychnl := make(chan string) // 匿名 goroutine go func() { mychnl <- "GFG" mychnl <- "gfg" mychnl <- "Geeks" mychnl <- "nhooo" close(mychnl) }() //使用for循环 for res := range mychnl { fmt.Println(res) } }
输出:
GFG gfg Geeks nhooo
-
**通道的长度:**在通道中,您可以使用len()函数找到通道的长度。在此,长度表示在通道缓冲区中排队的值的数量。
示例
package main import "fmt" func main() { // 使用 make() 函数创建通道 mychnl := make(chan string, 4) mychnl <- "GFG" mychnl <- "gfg" mychnl <- "Geeks" mychnl <- "nhooo" // 使用 len() 函数查找通道的长度 fmt.Println("channel长度为: ", len(mychnl)) }
输出:
channel长度为: 4
-
**通道的容量:**在通道中,您可以使用cap()函数找到通道的容量。在此,容量表示缓冲区的大小。
示例
package main import "fmt" func main() { // 使用 make() 函数创建通道 mychnl := make(chan string, 4) mychnl <- "GFG" mychnl <- "gfg" mychnl <- "Geeks" mychnl <- "nhooo" // 使用 cap() 函数查找通道的容量 fmt.Println("channel容量为: ", cap(mychnl)) }
输出:
channel容量为: 4
-
单向通
默认情况下,通道是双向的,但是您也可以创建单向通道。只能接收数据的通道或只能发送数据的通道,就是单向通道。单向通道也可以通过make()函数创建,如下所示:
//仅接收数据 c1:= make(<- chan bool) //仅用于发送数据 c2:= make(chan<-bool)
单向通道使用示例1:
示例
package main import "fmt" func main() { //仅用于接收数据 mychanl1 := make(<-chan string) //仅用于发送数据 mychanl2 := make(chan<- string) //显示通道的类型 fmt.Printf("%T", mychanl1) fmt.Printf("\n%T", mychanl2) }
输出:
<-chan string chan<- string
将双向通道转换为单向通道
在Go语言中,允许将双向通道转换为单向通道,换句话说,您可以将双向通道转换为仅接收或仅发送通道,但反之亦然。如下面的程序所示:
双向通道转为单向通道示例
package main import "fmt" func sending(s chan<- string) { s <- "nhooo" } func main() { //创建双向通道 mychanl := make(chan string) //在这里,sending()函数将双向通道转换为仅发送通道 go sending(mychanl) //在这里,通道只在goroutine内部发送,而在goroutine之外,通道是双向的,所以它打印nhooo fmt.Println(<-mychanl) }
输出:
nhooo
**单向通道的使用:**单向通道用于提供程序的类型安全性,从而使程序产生的错误更少。或者,当您要创建只能发送或接收数据的通道时,也可以使用单向通道。
-
Select语句
select语句类似于switch语句,但是在select语句中,case语句指的是通信,即通道上的发送或接收操作。
示例
package main
import (
"fmt"
"time"
)
// 函数 1
func portal1(channel1 chan string) {
time.Sleep(3 * time.Second)
channel1 <- "Welcome to channel 1"
}
// 函数 2
func portal2(channel2 chan string) {
time.Sleep(9 * time.Second)
channel2 <- "Welcome to channel 2"
}
func main() {
// 创建通道
R1 := make(chan string)
R2 := make(chan string)
// 使用goroutine调用函数1和函数2
go portal1(R1)
go portal2(R2)
select {
// case 1
case op1 := <-R1:
fmt.Println(op1)
// case 2
case op2 := <-R2:
fmt.Println(op2)
}
}
输出:
Welcome to channel 1
-
如果select语句不包含任何case语句,则该select语句将永远等待。
语法:
select{}
示例
package main func main() { //没有任何case,将一直等待 select{ } }
输出:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() /home/runner/main.go:9 +0x20 exit status 2
错误处理
Go没有像Java中的try / catch这样的异常机制,我们不能在Go中抛出异常。
Go使用另一种机制,称为延迟恐慌和恢复机制。
Go语言检测和报告错误情况的方法是
- 可能导致错误的函数将返回两个变量:一个值和一个错误代码,如果成功,则为nil;如果错误条件,则为== nil。
- 在函数调用之后检查错误。如果发生错误( if error != nil),则停止执行实际功能(或必要时整个程序)。
示例
package main
import "errors"
import "fmt"
import "math"
func Sqrt(value float64) (float64, error) {
if (value < 0) {
return 0, errors.New("Math: 负数的平方根")
}
return math.Sqrt(value), nil
}
func main() {
result, err := Sqrt(-64)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
result, err = Sqrt(64)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
输出:
Math: 负数的平方根
8
Panic
Panic是一种我们用来处理错误情况的机制。紧急情况可用于中止函数执行。当一个函数调用panic时,它的执行停止,并且控制流程到相关的延迟函数。
示例
package main
import "os"
func main() {
panic("Error Situation")
_, err := os.Open("/tmp/file")
if err != nil {
panic(err)
}
}
输出:
panic: Error Situation
goroutine 1 [running]:
main.main()
/Users/pro/GoglandProjects/Panic/panic example1.go:6 +0x39
gin框架和gorm
参考另一篇文章
https://blog.csdn.net/C_jian/article/details/107822120
sql建表语句转gorm结构体
在线工具:SQL转GORM Model - 潇洒哥和黑大帅 (printlove.cn)
json转结构体
在线工具:JSON转Golang Struct - 潇洒哥和黑大帅 (printlove.cn)
zap日志
参考另一篇文章
https://blog.csdn.net/C_jian/article/details/111315457