环境变量配置:
- 在系统变量的Path中添加go的安装bin目录:
D:\allAppInstall\go\bin
- 在系统中新建变量GOROOT项目,值为go的安装目录:
D:\allAppInstall\go
配置go环境:
在PowerShell中
$env:GO111MODULE="on"
$env:GOPROXY="http://goproxy.cn"
go语言命名规范
go是一门区分大小写的语言
任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则以小写字母开头
当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如: GetUserName,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的private )
包名称
保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写
package dao
package service
文件名称
应尽量采取有意义的文件名,简短有意义,应尽量使用小写单词,使用下划线分隔各个单词
customer_dao.go
结构体命名
采用驼峰命名法,首字母根据访问控制大写或者小写
struct申明和初始化采用多行
type CustomerOrder struct{
Name string
Address string
}
order := CustomerOrder{"zhangsan","北京海淀"}
接口命名
单个函数的结构命名以"er"作为后缀,例如 Reader,Writer
type Reader interface{
Read(p []byte)(n int ,err error)
}
变量命名
遵循驼峰命名法,首字母根据访问控制原则大写或则小写,但遇到特有名词时,需要遵循以下规则:
如果变量为私有,而且特有名词为首个单词,则使用小写
var appService int
如果变量类型为bool,则名称应以Has,Is,Can或者Allow开头
var isExist bool
var hasConflict bool
var canMansge bool
var allowGitHook bool
常量命名
常量使用全部大写字母组成,并且使用下划线分词
const APP_URL = "https://www.bilibili.com"
错误处理
错误处理的原则就是不能丢弃任何有返回err的调用,不要使用_丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来尽早return:一旦有错误发生,马上返回,尽量不要使用panic,除非你知道在做什么,错误描述如果是英文必须为小写,不需要标点结尾,采用独立的错误流进行处理
//错误写法
if err != nil{
//错误处理
}else{
//正常代码
}
//正确写法
if err != nil{
//错误处理
return //或者继续
}
//正常代码
单元测试
单元测试文件命名规范为:example_test.go
测试用例的函数名称必须以Test开头,例如:TestExample
每个重要的函数都要首先写测试用例,测试用例和正规代码一起提交方便进行回归测试
golang变量
变量是计算机语言中能储存计算结果或能表示值的抽象概念。不同的变量保存的数据类型可能会不一样。
声明变量
go语言中的变量需要声明后使用,同一作用域内不支持重复声明,并且go语言的变量声明后必须使用。
声明变量语法
var identifier type
var
:声明变量关键字
identifier
:变量名称
type
:变量类型
例如:
var name string
批量声明
var(
name string
age int
flag bool
)
变量的初始化
go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如:整型和浮点型变量的默认值为0
。字符串变量的默认值为空字符串""
。布尔型变量默认为false
。切片、函数、指针变量的默认为nil
。
变量初始化语法
var 变量名 类型 = 表达式
例如:
var name string = "zhangsan"
var age int =20
类型推导
我们在声明变量时,可以根据初始化值进行类型推导,从而省略类型。
var name = "zhangsan"
var age =20
根据打印函数打印出数据类型
fmt.Printf("%T\n", name)
初始化多个变量
var name,site,age="zhangsan","www.xxx.com",20
短变量声明
在函数内部,可以使用:=
运算符对变量进行声明和初始化
package
func main(){
name := "zhangsan"
site := "www.xxx.com"
age := 20
}
匿名变量
如果我们接收到多个变量,有一些变量使用不到,可以使用下划线_
表示变量名称,这种变量叫做匿名变量。例如:
package main
import "fmt"
func t1() (int, int) {
return 100, 200
}
// 匿名变量 _
func main() {
//a, b := t1()
//fmt.Print(a, b)
a, _ := t1()
fmt.Print(a)
}
golang常量
常量,就是在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值。在Go程序中,常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型等。
定义常量的语法
定义一个常量使用const关键字,语法格式:
const constantName [type] =value
const
:定义常量关键字
constantName
:常量名称
type
:常量类型
value
:常量的值
实例
package main
import "fmt"
func main() {
const URL string = "www"
fmt.Println(URL)
}
const
同时声明多个常量时,如果省略了值则表示一和上面一行的值相同
const (
A = 1
B
)
fmt.Println(A, B)//A,B都是1
iota
iota比较特殊,可以被认为是一个可被编译器修改的常量,它默认开始值是0,每调用一次加1。遇到const关键字时被重置为0。
- 同时声明多个常量,并且赋值都为iota,则常量的数据依次增加1
package main
import "fmt"
func main() {
const (
a = iota
b = iota
c = iota
)
fmt.Println(a, b, c) //a,b,c 分别是1,2,3
}
可以使用下划线_
跳过赋值
package main
import "fmt"
func main() {
const (
a = iota
_
c = iota
)
fmt.Println(a, c) //a,c 分别是1,3
}
golang数据类型
在go编程语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。
go语言按照类别有以下几种数据类型
类型 | 描述 |
---|---|
布尔型 | 值只能是true或者false |
数字类型 | 整型int 和浮点型 (float32和float64) |
字符串类型 | go语言的字符串的字节使用UTF-8编码标识Unicode文本 |
派生类型 | (a) 指针类型(Pointer) (b) 数组类型 © 结构化类型(struct) (d) Channel 类型 (e) 函数类型 (f) 切片类型 (g) 接口类型(interface) (h) Map 类型 |
布尔类型
package main
import "fmt"
func main() {
var flag1 bool = true
var flag2 = true
flag3 := true
fmt.Printf("%v\n", flag1)
fmt.Printf("%v\n", flag2)
fmt.Printf("%v\n", flag3)
}
数字类型
go语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码
go也有基于架构的类型,例如:int
,uint
,和uintptr
这些类型的长度都是根据运行程序所在的操作系统类型决定的:
int
和uint
在32位操作系统上,他们均使用32位(4字节),在64位操作系统上,他们均使用64位(8字节)uintptr
的长度被设定为足够存放一个指针即可
go语言中没有float类型。(go语言中只有float32和float64)没有double类型。
整型
序号 | 类型和描述 |
---|---|
1 | uint8 无符号 8 位整型 (0 到 255) |
2 | uint16 无符号 16 位整型 (0 到 65535) |
3 | uint32 无符号 32 位整型 (0 到 4294967295) |
4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) |
5 | int8 有符号 8 位整型 (-128 到 127) |
6 | int16 有符号 16 位整型 (-32768 到 32767) |
7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) |
8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
以二进制,八进制,十六进制格式定义数字
package main
import "fmt"
func main() {
var flag1 bool = true
var flag2 = true
flag3 := true
fmt.Printf("%v\n", flag1)
fmt.Printf("%v\n", flag2)
fmt.Printf("%v\n", flag3)
//十进制
var a int = 8
fmt.Printf("%d\n", a) //8
fmt.Printf("%b\n", a) //1000
//八进制 以0开头
var b int = 010
fmt.Printf("%d\n", b) //8
fmt.Printf("%o\n", b) //10
//十六进制 以0x开头
var c int = 0xa
fmt.Printf("%d\n", c) //10
fmt.Printf("%x\n", c) //a
fmt.Printf("%X\n", c) //A
}
浮点型
go语言支持两种浮点型数: float32和float64。这两种浮点型数据格式遵循IEEE 754标准:
float32 的浮点数的最大范围约为3.4e38,可以使用常量定义: math.MaxFloat32。
float64 的浮点数的最大范围约为1.8e308,可以使用一个常量定义: math.MaxFloat64。
打印浮点数时,可以使用fmt包配合动词%f:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi) //3.141593
fmt.Printf("%.2f\n", math.Pi) //3.14
}
关于复数
package main
import "fmt"
func main() {
var c complex64 = 1 + 3i
fmt.Println(c)
fmt.Printf("%T", c)
}
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
其他数据类型
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
golang字符串
一个go语言字符串是一个任意字节的常量序列 []byte
字符串字面量
在Go语言中,字符串字面量使用双引号""或者反引号`来创建。双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符。双引号创建可解析的字符串应用最广泛,反引号用来创建原生的字符串则多用于书写多行消息,HTML以及正则表达式。
package main
import "fmt"
func main() {
var str1 string = "hello"
var str2 = "zhangsan"
str3 := "lisi"
fmt.Printf("str1:%s\n", str1)
fmt.Printf("str2:%s\n", str2)
fmt.Printf("str3:%s\n", str3)
var str string = `
hahaha
hahaha`
fmt.Printf("str:%s\n", str)
}
字符串拼接
使用+
或者fmt.Sprintf
等等
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
str1 := "hello"
str2 := "zhangsan"
str3 := str1 + str2
fmt.Printf("%s\n", str3) //hellozhangsan
str4 := fmt.Sprintf("%s:%s", str2, str1)
fmt.Printf("%s\n", str4) //zhangsan:hello
s := strings.Join([]string{str2, str1}, ":")
fmt.Printf("%s\n", s) //zhangsan:hello
var buffer bytes.Buffer
buffer.WriteString(str2)
buffer.WriteString(":")
buffer.WriteString(str1)
fmt.Printf("%v\n", buffer.String())//zhangsan:hello
}
字符串转义字符
转义符 | 含义 |
---|---|
\r | 回车符 |
\n | 换行符 |
\t | 制表符 |
\' | 单引号 |
\" | 双引号 |
\\ | 反斜杠 |
字符串切片操作
package main
func main() {
str := "hello world"
println(str[0]) //获取字符串索引位置字符的原始字节
println(str[1:5]) //截取字符串索引位置1到5的字符串
println(str[1:]) //截取字符串索引位置1到最后的字符串
println(str[:5]) //截取字符串起始位置到索引位置的字符串
}
字符串处理函数
Contains:包含
func Contains(s, substr string) bool
//功能:字符串s中是否包含substr,返回bool值
例子
str := "hello world"
fmt.Printf("%t\n", strings.Contains(str, "ll"))//true
Join:拼接
func Join(a []string, sep string) string
//功能:字符串链接,把slice a通过sep链接起来
例子
s := []string{"abc", "def", "ghi"}
fmt.Printf("%s\n", strings.Join(s, "_"))//abc_def_ghi
Index:索引
func Index(s, sep string) int
//功能:在字符串s中查找sep所在的位置,返回位置值,找不到返回-1
例子
fmt.Printf("%d\n", strings.Index("helloworld", "hello"))//0
fmt.Printf("%d\n", strings.Index("helloworld", "nihao"))//-1
Repeat:重复
func Repeat(s string, count int) string
//功能:重复s字符串count次,最后返回重复的字符串
例子
fmt.Printf("%s\n", strings.Repeat("go", 3))//gogogo
Replace:替换
func Replace(s, old, new string, n int) string
//功能:在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换
例子
fmt.Printf("%s\n", strings.Replace("hello", "l", "h", 1))//hehlo
Split:分割
func Split(s, sep string) []string
//功能:把s字符串按照sep分割,返回slice
例子
fmt.Printf("%s\n", strings.Split("hello_world", "_"))//[hello world]
Trim:去除开头结尾
func Trim(s string, cutset string) string
//功能:在s字符串的头部和尾部去除cutset指定的字符串
例子
fmt.Printf("#%s#\n", strings.Trim(" hello go ", " ")) //#hello go#
Fields:删除前后空格
func Fields(s string) []string
//功能:去除s字符串的空格符,并且按照空格分割返回slice
例子
fmt.Printf("%s\n", strings.Fields(" hello world "))//[hello world]
golang格式化输出的占位符
fmt.Printf("%s\n","hello world")
普通占位符
package main
import "fmt"
type WebSite struct {
Name string
}
func main() {
site := WebSite{Name: "bilibili"}
fmt.Printf("%v\n", site) //{bilibili}
fmt.Printf("%#v\n", site) //main.WebSite{Name:"bilibili"}
fmt.Printf("%T\n", site) //main.WebSite
}
布尔占位符
package main
import "fmt"
func main() {
var flag bool = true
fmt.Printf("%t\n", flag) //true
fmt.Printf("%v\n", flag) //true
}
整数占位符
package main
import "fmt"
func main() {
fmt.Printf("b:%b\n", 10) //二进制数
fmt.Printf("d:%d\n", 10) //十进制
fmt.Printf("o:%o\n", 10) //八进制
fmt.Printf("c:%c\n", 0x5F7F) //相应Unicode码对应的字符
fmt.Printf("q:%q\n", 0x5F7F) //单引号围绕的字符值
fmt.Printf("x:%x\n", 10) //十六进制表示,字母形式的小写
fmt.Printf("X:%X\n", 10) //十六进制表示,字母形式的大写
fmt.Printf("U:%U\n", 10) //Unicode格式
}
/**
输出结果:
b:1010
d:10
o:12
c:彿
q:'彿'
x:a
X:A
U:U+000A
*/
浮点数和复数占位符
package main
import "fmt"
func main() {
fmt.Printf("b:%b\n", 3.141) //无小数部分的,指数为二的幂的科学计数法
fmt.Printf("e:%e\n", 3.1415) //科学计数法
fmt.Printf("E:%E\n", 3.1415) //科学计数法
fmt.Printf("f:%f\n", 3.1415) //有小数点,无指数
fmt.Printf("f:%.2f\n", 3.1415) //指定小数位数
fmt.Printf("g:%g\n", 3.14150+7.00i) //没有末尾0
fmt.Printf("G:%G\n", 3.14150+6.770i) //没有末尾0
}
/**
输出结果:
b:7072903214785364p-51
e:3.141500e+00
E:3.141500E+00
f:3.141500
f:3.14
g:(3.1415+7i)
G:(3.1415+6.77i)
*/
字符串与字节切片占位符
package main
import "fmt"
func main() {
fmt.Printf("s:%s\n", "hello go") //字符串
fmt.Printf("q:%q\n", "hello go") //双引号的字符串
fmt.Printf("x:%x\n", "hello") //十六进制,小写字母,每字节两个字符
fmt.Printf("X:%X\n", "hello") //十六进制,大写字母,每字节两个字符
}
/**
输出结果:
s:hello go
q:"hello go"
x:68656c6c6f
X:68656C6C6F
*/
指针的占位符
package main
import "fmt"
func main() {
x := 100
fmt.Printf("%p\n", &x)
}
/*
输出结果:
0xc00001e098
*/
golang语言中的流程控制
顺序,选择,循环
if
package main
func main() {
a := 1
b := 2
if a > b {
println(a)
} else {
println(b)
}
}
初始变量可以声明在布尔表达式里面,但是作用域仅限于if语句
package main
func main() {
if age := 20; age > 18 {
println(true)
} else {
println(false)
}
}
请注意
-
不需要使用括号讲条件包含起来
-
{}必须存在,即使只有一行语句
-
左括号必须在if或else的同一行
-
if之后,条件语句之前,可以添加变量初始化语句,使用分号;进行分隔
-
不能使用0或者非0表示布尔值
switch
package main
import "fmt"
func main() {
grade := 'A'
switch grade {
case 'A':
fmt.Println("优秀")
case 'B':
fmt.Println("良好")
default:
fmt.Println("其他")
}
}
- case匹配多个值
package main
import "fmt"
func main() {
day := 1
switch day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("休息日")
default:
fmt.Println("非法值")
}
}
- switch后面不接变量,在case后面使用布尔表达式
package main
import "fmt"
func main() {
score := 77
switch {
case score >= 60 && score < 70:
fmt.Println("及格")
case score >= 70 && score < 80:
fmt.Println("中等")
case score >= 80:
fmt.Println("优秀")
default:
fmt.Println("不及格")
}
}
- fallthrough,执行完当前case分支之后执行下一个case分支
package main
func main() {
a := 1
switch a {
case 1:
println(1)
fallthrough
case 2:
println(2)
case 3:
println(3)
}
}
请注意:
- 支持多条件匹配
- 不同的
case
之间不使用break
分隔,默认只会执行一个case
- 如果想要执行多个
case
分支,需要使用fallthrough
关键字,也可使用break
终止 - 分支还可以使用布尔表达式,此时switch后无需添变量
for
循环结构只有for关键字,没有while
和do while
package main
func main() {
for i := 0; i < 10; i++ {
println(i)
}
}
无限循环:
package main
func main() {
var i int = 0
for {
i++
if i > 100 {
break
}
println(i)
}
}
for range
遍历数组:
package main
import "fmt"
func main() {
var a = [...]int{1, 2, 3, 4, 5}
for i, v := range a {
fmt.Printf("%d:%d ", i, v)
}
}
/*
0:1 1:2 2:3 3:4 4:5
*/
遍历切片(动态数组)
package main
import "fmt"
func main() {
var s = []int{1, 2, 3}
for _, v := range s {
fmt.Printf("v: %v\n", v)
}
}
/**
v: 1
v: 2
v: 3
*/
遍历map
package main
import "fmt"
func main() {
m := make(map[string]string, 0)
m["name"] = "zhangsan"
m["age"] = "20"
m["email"] = "1234"
for key, value := range m {
fmt.Printf("%v:%v\n", key, value)
}
}
/**
name:zhangsan
age:20
email:1234
*/
遍历字符串:
package main
import "fmt"
func main() {
s := "hello world"
for _, v := range s {
fmt.Printf("v:%c ", v)
}
}
/**
v:h v:e v:l v:l v:o v: v:w v:o v:r v:l v:d
*/
goto
goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go语言中使用goto语句能简化一些代码的实现过程。例如双层嵌套的for循环要退出时:
跳到指定的标签
package main
func main() {
a := 1
if a >= 2 {
println(a)
} else {
goto END
}
END:
println("拜拜")
}
跳出双层循环
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if i == 2 && j == 2 {
goto END
}
fmt.Printf("%v,%v\n", i, j)
}
}
END:
println("拜拜")
}
golang数组
数组是相同数据类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(或者叫索引)来访问元素。
数组的定义
var variable_name [size] variable_type
varible_name
:数组名称
size
:数组长度,需要时常量
variable_type
:数组保存元素的类型
package main
import "fmt"
func main() {
var a [2]int
var s [3]string
fmt.Printf("%T\n", a)
fmt.Printf("%T\n", s)
fmt.Printf("%v\n", a)
fmt.Printf("%v\n", s)
}
/**
[2]int
[3]string
[0 0]
[ ]
*/
数组的初始化
初始化就是给数组的元素赋值,没有初始化的数组,默认元素值为零,布尔类型位false,字符串是空值
func f2() {
a1 := [3]int{1, 2, 3}
fmt.Printf("a1: %v\n", a1) //a1: [1 2 3]
}
//省略数组的长度...
func f3() {
s1 := [...]string{"hello", "nihao", "zhangsan"}
fmt.Printf("s1: %v\n", s1) //s1: [hello nihao zhangsan]
}
//指定位置初始化
func f4() {
a2 := [...]int{0: 1, 2: 2, 4: 4}
fmt.Printf("a2: %v\n", a2) // a2: [1 0 2 0 4]
}
数组的访问
可以通过下标的方式,来访问数组元素,数组的最大下标为length-1,大于这个下标回发生数组越界
func f4() {
a2 := [...]int{0, 1, 2, 3}
fmt.Printf("a2: %v\n", a2)
fmt.Printf("数组a2的长度:%d\n", len(a2))
}
/**
a2: [0 1 2 3]
数组a2的长度:4
*/
golang切片
前面我们学习了数组,数组是固定长度,可以容纳相同数据类型的元素的集合。当长度固定时,使用还是带来一些限制,比如:我们申请的长度太大浪费内存,太小又不够用。
鉴于上述原因,我们有了go语言的切片,可以把切片理解为,可变长度的数组,其实它底层就是使用数组实现的,增加了自动扩容功能。**切片(Slice)**是一个拥有相同类型元素的可变长度的序列。
切片的语法
声明一个切片和声明一个数组类似,只要不添加长度就可以了
var identifier []type
切片类型是引用数据类型,可以使用make函数来创建切片:
var slice []type = make([]type ,len)
//简写
slice := make([]type ,len)
例如
func f101() {
var s1 []int
var s2 []string
fmt.Printf("s1:%v\n", s1) //s1:[]
fmt.Printf("s2:%v\n", s2) //s2:[]
}
也可指定容量,其中capacity为可选参数
make([]T,length,capacity)
length是数组的长度并且也是切片的初始长度
例如:
func f102() {
s1 := make([]int, 2)
fmt.Printf("s1的值:%v\n", s1) //s1的值:[0 0]
fmt.Printf("s2的类型:%T\n", s1) //s2的类型:[]int
}
切片的初始化
直接初始化:
s := []int{1, 2, 3}
使用数组初始化:
arr := [...]int{1, 2, 3}
s1 := arr[:]
fmt.Printf("%v\n", s1) //[1 2 3]
使用数组的部分元素初始化(切片表达式)
切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。切片表达式中的low和high表示一个索引范围(左包含,右不包含),得到切片的长度=high-low,容量等于得到的切片的底层数组的容量
package main
import "fmt"
func main() {
arr := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := arr[:]
fmt.Printf("s1:%v\n", s1)
s2 := arr[2:5]
fmt.Printf("s2:%v\n", s2)
s3 := arr[2:]
fmt.Printf("s3:%v\n", s3)
s4 := arr[:5]
fmt.Printf("s4:%v\n", s4)
}
/**
s1:[1 2 3 4 5 6 7]
s2:[3 4 5]
s3:[3 4 5 6 7]
s4:[1 2 3 4 5]
*/
切片的遍历
遍历方法和数组的遍历非常类似,可以使用for循环遍历,或者使用for range循环
for循环遍历
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7}
fmt.Printf("s1:%v\n", s1)
for i := 0; i < len(s1); i++ {
fmt.Printf("s1[%d]:%v ", i, s1[i])
}
}
/*
s1:[1 2 3 4 5 6 7]
s1[0]:1 s1[1]:2 s1[2]:3 s1[3]:4 s1[4]:5 s1[5]:6 s1[6]:7
*/
for range遍历
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7}
fmt.Printf("s1:%v\n", s1)
for i, v := range s1 {
fmt.Printf("s1[%v]:%v, ", i, v)
}
}
/*
s1:[1 2 3 4 5 6 7]
s1[0]:1, s1[1]:2, s1[2]:3, s1[3]:4, s1[4]:5, s1[5]:6, s1[6]:7,
*/
切片元素的添加与删除
切片是一个动态数组,可以使用append()
函数添加元素,go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。由于,切片是引用类型,通过赋值的方式,会修改原有内容,go提供了copy()
函数来拷贝切片
添加
package main
import "fmt"
func main() {
s1 := []int{2}
fmt.Printf("s1:%v\n", s1) //s1:[2]
s1 = append(s1, 1)
fmt.Printf("s1:%v\n", s1) //s1:[2 1]
s1 = append(s1, 0, -1)
fmt.Printf("s1:%v\n", s1) //s1:[2 1 0 -1]
s2 := []int{100, 101}
s1 = append(s1, s2...)
fmt.Printf("s1:%v\n", s1) //s1:[2 1 0 -1 100 101]
}
删除
package main
import "fmt"
func main() {
s1 := []int{10, 20, 30, 40, 50}
fmt.Printf("s1:%v\n", s1) //s1:[10 20 30 40 50]
s1 = append(s1[:2], s1[3:]...)
fmt.Printf("s1:%v\n", s1) //s1:[10 20 40 50]
}
修改
package main
import "fmt"
func main() {
s1 := []int{10, 20, 30, 40, 50}
fmt.Printf("s1:%v\n", s1) //s1:[10 20 30 40 50]
s1[1] = 2
fmt.Printf("s1:%v\n", s1) //s1:[10 2 40 50]
}
golang Map
map是一种key :value键值对的数据结构容器。map内部实现是哈希表( hash )。
map最重要的一点是通过 key来快速检索数据,key类似于索引,指向数据的值。
map是引用类型的。
map的定义
语法格式:
//使用map关键字定义map集合
var map_variable map[key_data_type]value_data_type
//使用make函数
map_variable = make(map[key_data_type]value_data_type)
map_variable
:map变量名称
key_data_type
:key的数据类型
value_data_type
:值的数据类型
package main
import "fmt"
func main() {
m0 := map[string]string{
"name": "yewenjie",
"age": "30",
"tenet": "Don't answer",
}
fmt.Printf("%v\n", m0)
m1 := make(map[string]string)
m1["name"] = "zhangsan"
m1["age"] = "20"
m1["tenet"] = "legal"
fmt.Printf("%v\n", m1)
}
/**
map[age:30 name:yewenjie tenet:Don't answer]
map[age:20 name:zhangsan tenet:legal]
*/
map遍历
遍历key
package main
import "fmt"
func main() {
m1 := make(map[string]string)
m1["name"] = "zhangsan"
m1["age"] = "20"
m1["tenet"] = "legal"
for key := range m1 {
fmt.Printf("%v\n", key)
}
}
/*
name
age
tenet
*/
遍历key和value
package main
import "fmt"
func main() {
m1 := make(map[string]string)
m1["name"] = "zhangsan"
m1["age"] = "20"
m1["tenet"] = "legal"
//fmt.Printf("%v\n", m1)
for key, value := range m1 {
fmt.Printf("%v:%v\n", key, value)
}
}
/*
name:zhangsan
age:20
tenet:legal
*/
golang函数
函数的go语言中的一级公民,我们把所有的功能单元都定义在函数中,可以重复使用。
函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名(signature) 。
函数特征
- go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上的函数)
- go语言中不允许函数重载(overload),也就是说不允许函数同名
- go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
- 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
- 函数可以作为参数传递给另—个函数。
- 函数的返回值可以是一个函数。
- 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
- 函数参数可以没有名称。
函数的定义
函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复调用,从而达到代码重用。
定义语法
func function_name([parameter list]) [return_types]{
//函数体
}
func
:定义函数的关键字
function_name
:函数名称,函数名和参数列表一起构成了函数签名
[parameter list]
:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型,顺序,及参数个数。参数是可选的,函数可以没有参数
[return_types]
:返回类型,函数可以没有返回值
例如:
package main
import "fmt"
func sum(a int, b int) (num int) {
return a + b
}
func sub(a int, b int) int {
return a - b
}
func main() {
fmt.Printf("%d\n", sum(1, 1))
fmt.Printf("%d\n", sub(1, 1))
}
函数的返回值
无返回值
func t01() {
fmt.Printf("hello\n")
}
有返回值
func t01() (flag bool) {
return true
}
多个返回值
package main
import "fmt"
func t01() (a int, b int) {
return 1, 2 //使用return覆盖命名返回值,返回值名称没有被使用
}
func main() {
a, b := t01()
fmt.Printf("%d,%d", a, b)
}
函数的参数
-
可以有零个或多个参数,参数需要指定数据类型
-
go语言是通过传值的方式传参的,这意味着传递给函数的参数是拷贝后的副本,所以函数内部访问,修改的也是这个副本
- map 、 slice、 interface 、 channel这些数据类型本身就是指针类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数仍然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。
-
go语言可以使用变长参数,有时候并不能确定参数的个数,可以使用边长参数,可以在函数定义语句的参数部分使用
ARGS...TYPE
的方式。这时会将...
代表的参数全部保存到一个名为ARGS
的slice
中,注意这些参数的数据类型都是TYPE
可变参数
package main
import "fmt"
func t01(args ...int) {
for _, v := range args {
fmt.Printf("%d ", v)
}
fmt.Printf("\n")
}
func t02(name string, age int, args ...int) {
fmt.Printf("%s ", name)
fmt.Printf("%d\n", age)
for _, v := range args {
fmt.Printf("%d ", v)
}
}
func main() {
t01(1, 2, 3, 4, 5)
t02("zhangsan", 20, 3, 4, 5)
}
/*
1 2 3 4 5
zhangsan 20
3 4 5
*/
函数类型与函数变量
定义一个函数类型的变量
可以使用type关键字来定义一个函数类型,语法格式如下:
type fun func(int int)int
//定义一个fun函数类型,这种函数接收两个int类型的参数,并且返回一个int类型的返回值
例如:
package main
import "fmt"
func sum(a int, b int) int {
return a + b
}
func max(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
func main() {
type f1 func(int, int) int //定义一个函数类型的变量
var fufu f1
fufu = sum
num := fufu(1, 2)
fmt.Printf("%v\n", num)
fufu = max
num2 := fufu(1, 2)
fmt.Printf("%v\n", num2)
}
高阶函数
go语言的函数,可以作为函数的参数,传递给另外一个函数,作为另外一个函数的返回值返回
函数作为参数
package main
import "fmt"
func sayHello(name string) {
fmt.Printf("Hello %s!", name)
}
func f1(name string, f func(string)) {
f(name)
}
func main() {
f1("tom", sayHello)
}
函数作为返回值
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func sub(a int, b int) int {
return a - b
}
func cal(operator string) func(int, int) int {
switch operator {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {
ff := cal("+")
r := ff(1, 2)
fmt.Printf("%v\n", r)
ff2 := cal("-")
r2 := ff2(1, 2)
fmt.Printf("%v\n", r2)
}
匿名函数
go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一下简单功能调用。
所谓匿名函数就是,没有名称的函数。
语法格式:
func (参数列表)(返回值)
匿名函数的举例:
package main
import "fmt"
func main() {
//匿名函数
max := func(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
a := max(1, 2)
fmt.Printf("max:%v\n", a)
}
匿名函数的自调用
package main
import "fmt"
func main() {
num := func(a int, b int) int {
return a + b
}(1, 2)
fmt.Printf("%v\n", num) //3
}
闭包
闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁,或者说是函数和其引用环境的组合体。
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+作用域。
package main
func add() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = add()
println(f(10))
println(f(20))
}
递归
函数内部调用函数自身的函数称为递归函数。使用递归函数最重要的三点:
- 递归就是自己调用自己。
- 必须先定义函数的退出条件,没有退出条件,递归将成为死循环。
- go语言递归函数很可能会产生一大堆的goroutine,也很可能会出现栈空间内存溢出问题。
package main
import "fmt"
func f11(a int) int {
if a == 1 {
return 1
} else {
return a * f11(a-1)
}
}
func main() {
fmt.Printf("%d\n", f11(6))
}
golang defer
go语言中的defer
语句会将其后面跟随的语句进行延迟处理,在defer
归属的函数即将返回时,将延迟处理的语句按defer
定义的逆序进行执行
例子
package main
import "fmt"
func main() {
fmt.Printf("start\n")
defer fmt.Printf("%d\n", 1)
defer fmt.Printf("%d\n", 2)
defer fmt.Printf("%d\n", 3)
fmt.Printf("end\n")
}
/**
start
end
3
2
1
*/
defer特性
- 关键字
defer
用于注册延迟调用 - 这些调用直到
return
前被执行,因此可以用来做资源管理 - 多个
defer
语句,按先进后出的方式执行 defer
语句中的变量,在defer
声明时就决定了
defer的用途
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
golang init
init函数,先于main函数执行,实现包级别的一些初始化操作’
init函数的主要特点
- init函数先于main函数自动执行,不能被其他函数调用
- init函数没有输入参数、返回值
- 每个包可以有多个init函数
- 包的每个文件也可以有多个init函数,
- 同一个包的init执行顺序没有明确的定义,编程时,主要不要依赖这个执行顺序
- 不同包的init函数按照包导入的依赖关系决定执行顺序
初始化顺序
初始化变量—>init()
—>mian()
package main
import "fmt"
func init() {
fmt.Printf("%s\n", "init")
}
var i int = initVar()
func initVar() int {
fmt.Printf("%s\n", "initVar")
return 100
}
func main() {
fmt.Printf("%s\n", "main")
}
golang指针
go支持指针, 允许在程序中通过引用传递来传递值和数据结构。
类型指针不能进行偏移和运算
&
(取地址), *
(根据地址取值)
指针地址和指针类型
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置
使用&
字符放在变量前面对变量进行取地址操作
go语言中的类型值(int、float、bool、string、array、struct)都有对应的指针类型,如:
*int
、*int64
、*string
等
指针语法
一个指针变量指向了一个值的内存地址。
声明语法:
var var_name *var-type
var-type
:为指针类型
var_name
:指针变量名
*
:用于指定变量是作为一个指针
指针声明实例
var ip *int //指向整型
var ip *float32 //指向浮点型
例子:
package main
import "fmt"
func main() {
var ip *int //声明指针变量
fmt.Printf("ip:%v\n", ip)
var i int = 100 //声明一个实际变量
ip = &i //指针变量指向实际变量的地址
fmt.Printf("ip:%v\n", ip)
fmt.Printf("ip:%v\n", *ip)
}
/*
ip:<nil>
ip:0xc00001c0e0
ip:100
*/
glang指向数组的指针
指针数组的每一个值都指向其对应的值
定于语法:
var ptr [MAX]*int
例子
package main
import "fmt"
func main() {
a := [3]int{1, 2, 3}
var pa [3]*int
fmt.Printf("a:%v\n", a)
fmt.Printf("pa:%v\n", pa)
for i := 0; i < len(a); i++ {
pa[i] = &a[i]
}
fmt.Printf("pa:%v\n", pa)
for i := 0; i < len(a); i++ {
fmt.Printf("pa[%d]:%v ", i, *pa[i])
}
}
/*
a:[1 2 3]
pa:[<nil> <nil> <nil>]
pa:[0xc000010138 0xc000010140 0xc000010148]
pa[0]:1 pa[1]:2 pa[2]:3
*/
golang类型定义和类型别名
类型定义
类型定于的语法
type NewType Type
实例
package main
import "fmt"
func main() {
type MyInt int
var i MyInt
i = 100
fmt.Printf("i:%v\n", i)
fmt.Printf("i:%T\n", i)
}
/*
i:100
i:main.MyInt
*/
类型别名
类型别名定义语法
type NewType = type
实例
package main
import "fmt"
func main() {
type MyInt1 = int
var j MyInt1
j = 100
fmt.Printf("j:%v\n", j)
fmt.Printf("j:%T\n", j)
}
/*
j:100
j:int
*/
golang结构体
定义语法:
type struct_variable_type struct{
member definition;
member definition;
...
}
type
:类型定义关键字
struct_variable_type
:结构体类型名称
struct
:结构体定义关键字
member definition
:成员定义
实例
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func main() {
var tom Person
tom.name = "tom"
tom.age = 20
tom.email = "123"
fmt.Printf("%v\n", tom)
}
/*
{0 tom 20 123}
*/
结构体的初始化
未初始化的结构体,成员都是零值(int 0 ,float 0.0 , bool false , string nil )
初始化的两种方式:
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func main() {
//键值对方式
kite := Person{
id: 1,
name: "kite",
age: 20,
email: "kite@qq.com",
}
fmt.Printf("kite:%v\n", kite)
//列表方式
tom := Person{
2, "tom", 21, "tom@qq.com",
}
fmt.Printf("tom:%v\n", tom)
}
/*
kite:{1 kite 20 kite@qq.com}
tom:{2 tom 21 tom@qq.com}
*/
结构体指针
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func main() {
//键值对的方式
kite := Person{
id: 1,
name: "kite",
age: 20,
email: "kite@qq.com",
}
var p_person *Person
p_person = &kite
fmt.Printf("tom:%p\n", p_person)
fmt.Printf("tom:%v\n", *p_person)
}
/*
tom:0xc00007e4b0
tom:{1 kite 20 kite@qq.com}
*/
可以使用new关键字创建结构体指针
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func main() {
var tom = new(Person)
tom.name = "tom"
tom.id = 1
tom.age = 20
tom.email = "1234"
fmt.Printf("tom:%v\n", *tom)
}
/*
tom:{1 tom 20 1234}
*/
结构体作为函数参数
go结构体可以像普通变量一样,作为函数的参数,传递给函数,这里分为两种情况:
- 直接传递结构体,这是一个副本(拷贝),在函数内部不会改变外面结构体内容
- 传递结构体指针,这时在函数内部,能够改变外部结构体内容
例如:函数的参数是一个结构体
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func setP(person Person) {
person.age = 18
}
func main() {
tom := Person{1, "tom", 20, "123"}
fmt.Printf("%v\n", tom)
setP(tom)
fmt.Printf("%v\n", tom)
}
/*
{1 tom 20 123}
{1 tom 20 123}
*/
修改之后,原来的结构体变量没有变化
改成指针类型的结构体之后:
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func setP(person *Person) {
person.age = 18
}
func main() {
tom := Person{1, "tom", 20, "123"}
fmt.Printf("%v\n", tom)
setP(&tom)
fmt.Printf("%v\n", tom)
}
/*
{1 tom 20 123}
{1 tom 18 123}
*/
发现结果已经被修改
嵌套结构体
go语言没有面向对象编程思想,也没有继承关系,但是可以通过结构体的嵌套来实现这种效果
package main
import "fmt"
func main() {
type Dog struct {
name string
age int
}
type Person struct {
name string
age int
dog Dog
}
dog := Dog{"wangcai", 2}
tom := Person{"tom", 20, dog}
fmt.Printf("%v\n", tom)
fmt.Printf("人的名字:%v\n", tom.name)
fmt.Printf("狗的名字:%v\n", tom.dog.name)
}
/*
{tom 20 {wangcai 2}}
人的名字:tom
狗的名字:wangcai
*/
golang方法
go语言没有面向对象的特性,也没有类对象的概念,但是,可以使用结构体来模拟这些特性
可以声明一些方法,属于某个结构体
方法语法
go中的方法,是一种特殊的函数,定义于struct之上(与struct关联,绑定),被称为struct的接受者(receiver),即:方法就是有接收者的函数
type mytype struct{}
func(recv mytype) my_method(para) return_type{}
func(recv *mytype) my_method(para) return_type{}
mytype
:定义一个结构体
recv
:接受该方法的结构体
my_method
:方法名称
para
:参数列表
return_type
:返回值类型
例子:
package main
import "fmt"
type Person struct {
name string
age int
}
// (per Person)接收者 receiver
func (per Person) eat() {
fmt.Printf("%v,eat...\n", per.name)
}
func main() {
per := Person{
name: "zhangsan",
}
per.eat()
}
方法的注意事项:
- 方法的receiver type并非要是struct类型,type定义的类型别名,slice,map,channel,func等类型都可以
- struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
- 方法有两种接收类型:(T Type)和(T *Type),它们之间有区别。
- 方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
- 如果receiver是一个指针类型,则会自动解除引用。
- 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。
方法接收者类型
结构体实例,有值类型和指针类型,那么方法的接收者是结构体,也有值类型和指针类型
区别就是接收者是否复制结构体副本,值类型复制,指针类型不复制
golang接口
interface类型可以定义一组方法,但是这些方法不需要实现,并且interface不能包含任何变量,到某个自定义类型(比如结构体类型)要使用的时候,再根据具体情况把这些方法实现出来
接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想
go中的接口不需要显示实现,只要一个变量含有接口类型的所有方法,那么这个变量就实现了这个接口,没有implements关键字
定义语法
//定义接口
type interface_name interface{
method_name1 [return_type]
method_name2 [return_type]
...
}
//定义结构体
type struct_name struct{
}
//实现接口的方法
func (struct_name_variable struct_name) method_name1()[return_type]{
//方法实现
}
...
//实现接口的方法
func (struct_name_variable struct_name) method_namen()[return_type]{
//方法实现
}
实例:定义一个usb接口,有读read和写write两个方法,在再定义一个电脑Computer和手机Mobile来实现这个接口
package main
import "fmt"
// 定义接口
type USB interface {
read()
write()
}
type Computer struct {
name string
}
type Mobile struct {
name string
}
// computer实现usb接口方法
func (c Computer) read() {
fmt.Printf("c.name:%v\n", c.name)
println("computer read")
}
func (c Computer) write() {
fmt.Printf("c.name:%v\n", c.name)
println("computer write")
}
func main() {
c := Computer{
name: "联想",
}
c.read()
c.write()
}
/*
c.name:联想
computer read
c.name:联想
computer write
*/
接口方法接收者值类型或指针类型
值类型是将值复制一份传递给方法,不会改变原变量的值
指针类型是将变量的地址直接传递给函数,直接改变原变量
例子
值类型:
package main
import "fmt"
type Pet interface {
eat(string) string
}
type Dog struct {
name string
}
func (dog Dog) eat(name string) string {
dog.name = name
return name
}
func main() {
dog := Dog{
name: "huahua",
}
s := dog.eat("奥里给")
fmt.Printf("s:%v\n", s)
fmt.Printf("dog:%v\n", dog)
}
/**
s:奥里给
dog:{huahua}
*/
指针类型:
package main
import "fmt"
type Pet interface {
eat(string) string
}
type Dog struct {
name string
}
func (dog *Dog) eat(name string) string {
dog.name = name
return name
}
func main() {
dog := &Dog{
name: "huahua",
}
s := dog.eat("奥里给")
fmt.Printf("s:%v\n", s)
fmt.Printf("dog:%v\n", dog)
}
/**
s:奥里给
dog:&{奥里给}
*/
golang接口和类型的关系
- 一个类型可以实现多个接口
- 多个类型可以实现同一个接口(多态)
一个类型可以实现多个接口
type Music interface {
playMusic()
}
type Video interface {
playVideo()
}
type MobilePhone struct {
}
func (phone MobilePhone) playMusic() {
fmt.Printf("%v\n", "playMusic...")
}
func (phone MobilePhone) playVideo() {
fmt.Printf("%v\n", "playVideo...")
}
func main() {
m := MobilePhone{}
m.playMusic()
m.playVideo()
}
/*
playMusic...
playVideo...
*/
多个类型可以实现同一个接口(多态)
type Music interface {
playMusic()
}
type MobilePhone struct {
}
type MobileComputer struct {
}
func (phone MobilePhone) playMusic() {
fmt.Printf("%v\n", "MobilePhone playMusic...")
}
func (c MobileComputer) playMusic() {
fmt.Printf("%v\n", "MobileComputer playMusic...")
}
func main() {
var m Music
m = MobilePhone{}
m.playMusic()
m = MobileComputer{}
m.playMusic()
}
/*
MobilePhone playMusic...
MobileComputer playMusic...
*/
golang接口的嵌套
接口可以通过嵌套,创建新的接口。例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口Swim,飞鱼接口有这两个接口组成。
type Flyer interface {
fly()
}
type Swimmer interface {
swim()
}
type FlyFish interface {
Flyer
Swimmer
}
type Fish struct {
//属性
}
func (fish Fish) fly() {
fmt.Printf("%v\n", "fly...")
}
func (fish Fish) swim() {
fmt.Printf("%v\n", "swim...")
}
func main() {
var ff FlyFish
ff = Fish{}
ff.swim()
ff.fly()
}
/**
swim...
fly...
*/
golang通过接口实现OCP设计原则
而面向对象的可复用设计的第一块基石,便是所谓的”开-闭“原则(Open-Closed Principle,常缩写为OCP)。虽然go不是面向对象语言,但是也可以模拟实现这个原则。对扩展是开放的,对修改是关闭的。
golang包
包可以区分命令空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。go中创建一个包,一般是创建一个文件夹,在该文件夹里面的go文件中,使用package关键字声明包名称,通常,文件夹名称和包名称相同。并且,同一个文件下面只有一个包
要使用某个包下面的变量或者方法,需要导入该包,
golang并发编程
golang并发编程之协程
Golang 中的并发是函相互独立运行的能力。Goroutines 是并发运行的函数。Golang 提供了Goroutines作为并发处理操作的一种方式。
创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字:
go task()
package main
import (
"fmt"
"time"
)
func showMsg(msg string) {
for i := 0; i < 5; i++ {
fmt.Printf("mag: %v\n", msg)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
go showMsg("java") //启动了一个协程
showMsg("golang")
}
/*
mag: golang
mag: java
mag: java
mag: golang
mag: java
mag: golang
mag: golang
mag: java
mag: java
mag: golang
*/
golang并发编程之通道channel
Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。
需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个goroutine 可以访问数据项:因此按照设计不会发生数据竞争。
根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换。缓冲通道没有这样的保证。
通道由make函数创建,该函数指定chan关键字和通道的元素类型。
创建无缓冲和缓冲通道:
Unbuffered := make(chan int)//整型无缓冲通道
buffered := make(chan int,10)//整型有缓存通道
将值发送到通道需要使用<-
运算符:
goroutine1 := make(chan string,5) //字符串缓存通道
goroutine1 <- "American" //通过通道发送字符串
一个包含5个值的缓存区的字符串类型的goroutine1通道,然后我们通过通道发送字符串"American"
接收通道中的值需要使用<-
运算符:
data := <-goroutine1 //从通道接收字符串
通道的发送和接收特性
- 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的
- 发送操作和接收操作中对元素值的处理都是不可分割的
- 发送操作在完全完成之前会被阻塞。接收操作也是如此
package main
import (
"fmt"
"math/rand"
"time"
)
// 创建int类型通道,只能传入int类型值
var values = make(chan int)
func send() {
rand.Seed(time.Now().UnixNano())
value := rand.Intn(10)
fmt.Printf("send: %v\n", value)
values <- value
}
func main() {
//从通道接受值
defer close(values)
go send()
fmt.Println("wait..")
value := <-values
fmt.Printf("receive: %v\n", value)
fmt.Println("end..")
}
golang并发编程之WaitGroup实现同步
实现主协程等待某个协程结束
package main
import (
"fmt"
"sync"
)
var wp sync.WaitGroup
func showMessage(i int) {
defer wp.Done()
fmt.Printf("%v\n", i)
}
func main() {
for i := 0; i < 10; i++ {
//启动一个协程来执行
go showMessage(i)
wp.Add(1)
}
wp.Wait()
//主协程
fmt.Println("end..")
}
/**
9
4
0
1
2
3
6
5
7
8
end..
*/
golang并发编程之runtime包
runtime包里面定义了一些协程管理相关的api
runtime.Gosched()
让出cpu时间片,重新等待安排任务
package main
import (
"fmt"
"runtime"
)
func show(s string) {
for i := 0; i < 2; i++ {
println(s)
}
}
func main() {
go show("java")
for i := 0; i < 2; i++ {
runtime.Gosched()
fmt.Printf("golang\n")
}
}
/**
java
java
golang
golang
*/
runtime.GOMAXPROCS
显示最大核心数
package main
import (
"fmt"
"runtime"
"time"
)
func a() {
for i := 0; i < 10; i++ {
fmt.Printf("A:%v, ", i)
time.Sleep(time.Millisecond * 100)
}
}
func b() {
for i := 0; i < 10; i++ {
fmt.Printf("B:%v, ", i)
time.Sleep(time.Millisecond * 100)
}
}
func main() {
fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())
runtime.GOMAXPROCS(2)
go a()
go b()
time.Sleep(time.Second)
}
/*
runtime.NumCPU(): 8
B:0, A:0, A:1, B:1, B:2, A:2, A:3, B:3, B:4, A:4, A:5, B:5, B:6, A:6, A:7, B:7, B:8, A:8, B:9, A:9,
*/
golang并发编程之Mutex互斥锁实现同步
除了使用channel实现同步之外,还可以使用Mutex互斥锁的方式实现同步
package main
import (
"fmt"
"sync"
"time"
)
var i int = 100
var wg sync.WaitGroup
var lock sync.Mutex
func add() {
lock.Lock()
defer wg.Done()
i += 1
fmt.Printf("i++: %v\n", i)
time.Sleep(time.Millisecond * 10)
lock.Unlock()
}
func sub() {
lock.Lock()
defer wg.Done()
i -= 1
fmt.Printf("i--:%v\n", i)
time.Sleep(time.Millisecond * 3)
lock.Unlock()
}
func main() {
for i := 0; i < 100; i++ {
wg.Add(1)
go add()
wg.Add(1)
go sub()
}
wg.Wait()
fmt.Printf("end i: %v\n", i)
}
//使用锁的方式实现同步
golang并发编程之channel的遍历
package main
import "fmt"
var c = make(chan int)
func t1() {
for i := 0; i < 2; i++ {
c <- i
}
close(c)
}
func main() {
go t1()
//for i := 0; i < 3; i++ {
// r := <-c
// fmt.Printf("r: %v\n", r)
//}
for v := range c {
fmt.Printf("V: %v\n", v)
}
}
注意:如果通道关闭,读多写少,没有了就是默认值,例如,int就是0,如果没有关闭就会死锁。
golang并发编程之select switch
select
时Go
中的一个控制结构,类似于switch
语句,用于处理异步IO操作。select
会监听case
语句中channel
的读写操作,当case
中channel
读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
select
中的case
语句必须是一个channel
操作
select
中的default
子句总是可运行的。- 如果有多个
case
都可以运行,select
会随机公平地选出一个执行,其他不会执行。 - 如果没有可运行的
case
语句,且有default
语句,那么就会执行default
的动作。 - 如果没有可运行的
case
语句,且没有default
语句,select
将阻塞,直到某个case
通信可以运行
package main
import (
"fmt"
"time"
)
var chanInt = make(chan int)
var chanStr = make(chan string)
func main() {
go func() {
chanInt <- 100
chanStr <- "hello"
close(chanInt)
close(chanStr)
}()
for {
select {
//读出来的时int类型的时候
case r := <-chanInt:
fmt.Printf("chanInt: %v\n", r)
//读出来的时char类型的时候
case r := <-chanStr:
fmt.Printf("chanStr: %v\n", r)
default:
fmt.Printf("default\n")
}
time.Sleep(time.Second)
}
}
golang并发编程之Timer
定时器,可以实现一些定时操作,内部也是通过channel来实现的
package main
import (
"fmt"
"time"
)
func main() {
//代表等待两秒之后
time1 := time.NewTimer(time.Second * 2)
t1 := time.Now()
fmt.Printf("t1: %v\n", t1)
t2 := <-time1.C
fmt.Printf("t2: %v\n", t2)
}
golang并发编程之Ticker
Timer只执行一次,Ticker可以周期的执行
package main
import (
"fmt"
"time"
)
func main() {
var count int = 0
//创建tacker,周期时1s
ticker := time.NewTicker(time.Second)
for _ = range ticker.C {
fmt.Printf("ticler\n")
//计数停止
count++
if count >= 5 {
ticker.Stop()
break
}
}
}
golang并发编程之原子变量的引入
可以通过加锁的方式实现原子操作
package main
import (
"sync"
"time"
)
var a1 int = 100
var locks sync.Mutex
func adds() {
locks.Lock()
a1++
locks.Unlock()
}
func subs() {
locks.Lock()
a1--
locks.Unlock()
}
func main() {
for i := 0; i < 100; i++ {
go adds()
go subs()
}
time.Sleep(time.Second * 2)
print(a1)
}
或者是引入原子变量
package main
import (
"sync/atomic"
"time"
)
var a1 int32 = 100
func adds() {
atomic.AddInt32(&a1, 1)
}
func subs() {
atomic.AddInt32(&a1, -1)
}
func main() {
for i := 0; i < 100; i++ {
go adds()
go subs()
}
time.Sleep(time.Second * 2)
print(a1)
}
atomic提供的原子操作能够确保任何一时刻只有一个goroutine对变量进行操作,善用atomic能够避免程序中出现大量的锁操作。
atomic常见操作有:
- 增减
- 载入 read
- 比较并交换
- 交换
- 存储 write
//载入和read
func load_store() {
var i int32 = 100
atomic.LoadInt32(&i)
fmt.Printf("i %v\n", i)
atomic.StoreInt32(&i, 200)
fmt.Printf("i %v\n", i)
}
//比较并交换
func cas() {
var i int32 = 100
b := atomic.CompareAndSwapInt32(&i, 100, 200)
println(i)
println(b)
}
golang标准库os包
https://pkg.go.dev/std
文件目录相关
创建文件
//在指定目录位置创建文件
func createFile() {
create, err := os.Create("stand/a.txt")
if err != nil {
println(err)
} else {
println(create.Name())
}
}
创建目录
//创建目录为 stand/a/b
func createDir() {
err := os.MkdirAll("stand/a/bstand/a/b", os.ModePerm)
if err != nil {
println(err)
}
}
删除目录
//删除的目录为上述的stand/a/b中的b目录
func delDir() {
err := os.RemoveAll("stand/a/b")
if err != nil {
fmt.Printf("err: %v\n", err)
}
}
获得工作目录(项目工程所在的位置)
func getDir() {
dir, err := os.Getwd()
if err != nil {
println(err)
} else {
println(dir)
}
}
//D:\go\Test04
修改工作目录
func changeWorkSpace(){
err := os.Chdir("d:/")
if err!= nil{
println(err)
}
println(os.Getwd())
}
修改文件名称
func rename() {
err := os.Rename("stand/a.txt", "stand/b.txt")
if err != nil {
println(err)
}
}
写文件
//文件的内容将被替换
func write() {
s := "hello"
err := os.WriteFile("stand/b.txt", []byte(s), os.ModePerm)
if err != nil {
println(err)
}
}
读文件
func read() {
file, err := os.ReadFile("stand/b.txt")
if err != nil {
println(err)
} else {
println(string(file[:]))
}
}
文件读操作
// 打开和关闭
func openClose() {
//f, err := os.Open("stand/b.txt")
//if err != nil {
// fmt.Printf("%v\n", err)
//} else {
// fmt.Printf("%v\n", f.Name())
// f.Close()
//}
file, err := os.OpenFile("stand/b.txt", os.O_RDWR|os.O_CREATE, 755)
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("file.Name(): %v\n", file.Name())
file.Close()
}
}
// 创建文件
func create() {
//等价于:OpenFile("fileName", O_RDWR|O_CREATE|O_TRUNC, 755)
file, _ := os.Create("stand/a2.txt")
fmt.Printf("file.Name(): %v\n", file.Name())
temp, _ := os.CreateTemp("", "temp")
fmt.Printf("temp.Name(): %v\n", temp.Name())
}
// 读文件
func readOps() {
open, _ := os.Open("stand/b.txt")
for {
buf := make([]byte, 10)
n, err := open.Read(buf)
if err == io.EOF {
return
}
fmt.Printf("n: %v\n", n)
fmt.Printf("string(buf): %v\n", string(buf))
}
}
文件写操作
func writeFiles() {
//O_RDWR常量用于指定以读写模式打开文件的读写模式。
//O_APPEND常量用于追加写入模式
//O_TRUNC常量用于覆盖写入模式
file, _ := os.OpenFile("stand/a2.txt", os.O_RDWR|os.O_TRUNC, 0755)
n, err := file.Write([]byte("hello go!"))
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("n: %v\n", n)
}
file.Close()
}
//在指定位置插入指定的字符串
func writeAt() {
file, _ := os.OpenFile("stand/a2.txt", os.O_RDWR, 0755)
n, err := file.WriteAt([]byte("aaa"), 1)
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Printf("n: %v\n", n)
}
file.Close()
}
galang标准库io包
Go
语言中,为了方便开发者使用,将IO
操作封装在了如下几个包中:
io
为IO
原语(I/O primitives
)提供基本的接口os File
io/ioutil
封装一些实用的I/O
函数fmt
实现格式化I/O
,类似C语言
中的printf
和scanf
bufio
实现带缓冲I/O
io 基本的IO接口
Reader接口
type Reader interface {
Read(p []byte) (n int, err error)
}
Writer接口
type Writer interface {
Write(p []byte) (n int, err error)
}
galang标准库sort包
sort包提供了排序切片和用户自定义数据集以及相关功能的函数。
sort包主要针对[]int
,[ ]float64
、[ ]string
、以及其他自定义切片的排序。
galang标准库time包
获取时间
now := time.Now()
fmt.Printf("now: %v\n", now,now)
fmt.Printf("Year: %v\n", now.Year())
fmt.Printf("Month: %v\n", now.Month())
fmt.Printf("Day: %v\n", now.Day())
fmt.Printf("Hour: %v\n", now.Hour())
fmt.Printf("Minute: %v\n", now.Minute())
fmt.Printf("Second: %v\n", now.Second())
/*
now: 2023-01-29 16:43:49.0496523 +0800 CST m=+0.005771001
Year: 2023
Month: January
Day: 29
Hour: 16
Minute: 43
Second: 49
*/
获取时间戳
fmt.Printf("时间戳: %v\n", now.Unix())//时间戳: 1674982190
fmt.Printf("纳秒时间戳: %v\n", now.UnixNano())//纳秒时间戳: 1674982313549030400
将时间戳转化为时间
unix := time.Now().Unix()
time := time.Unix(unix, 0)
fmt.Printf("%v\n", time)
fmt.Printf("Year: %v\n", time.Year())
fmt.Printf("Month: %v\n", time.Month())
Add函数和Sub函数
now := time.Now()
addTime := now.Add(time.Hour)
fmt.Printf("%v\n", addTime)
fmt.Printf("%v\n", addTime.Sub(now))
Equal函数,判断两个时间是否相同,会考虑时区的影响,因此不同时区也可以正确的比较
func (t Time) Equal(u Time) bool
例子
now := time.Now()
addTime := now.Add(time.Hour)
fmt.Printf("now.Equal(addTime): %v\n", now.Equal(addTime))
//now.Equal(addTime): false
Before函数,t时间点在u之前,返回值为真,否则为假
func (t Time) Before(u Time) bool
After函数,t时间点在u之后,返回值为真,否则为假
func (t Time) After(u Time) bool
定时器,使用time.Tick(时间间隔) 来设置定时器,定时器的本质上是一个通道(channel)
func tick() {
ticker := time.Tick(time.Second)
for i := range ticker {
fmt.Printf("%v\n", i)
}
}
/*
2023-01-30 08:51:00.1026028 +0800 CST m=+1.011140801
2023-01-30 08:51:01.107312 +0800 CST m=+2.015850001
2023-01-30 08:51:02.1117447 +0800 CST m=+3.020282701
2023-01-30 08:51:03.1071476 +0800 CST m=+4.015685601
*/
时间格式化
func format() {
now := time.Now()
//格式化时间的时候不是使用的Y-m-d H:M:S 而是使用的Go语言诞生时间2006-01-02 15:04:05.000 Mon Jan
fmt.Printf("%v\n", now.Format("2006-01-02 15:04:05.000 Mon Jan")) //24h制
fmt.Printf("%v\n", now.Format("2006-01-02 15:04:05.000 PM Mon Jan")) //12h制
fmt.Printf("%v\n", now.Format("2006-01-02 15:04"))
fmt.Printf("%v\n", now.Format("2006-01-02"))
}
将字符串解析成为时间对象
func stringToTime() {
//加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Printf("%v\n", err)
return
}
locationTimeObject, err := time.ParseInLocation("2006-01-02 15:04:05", "2023-01-30 08:51:10", loc)
if err != nil {
fmt.Printf("%v\n", err)
return
}
fmt.Printf("locationTimeObject: %v\n", locationTimeObject)
}
//locationTimeObject: 2023-01-30 08:51:10 +0800 CST
golang标准库encoding/json
这个包可以实现json的编码和解码,就是将json字符串转化为struct,或者将struct转化为json
将struct编码成json,可以接收任意类型
func Marshal(v interface{})([]byte,error)
将json转化为struct
func Unmarshal(data []byte, v interface{} ) error
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string
Age int
Email string
}
//将struct转化为json
func t1() {
person := Person{
Name: "tom",
Age: 20,
Email: "tom@qq.com",
}
b, _ := json.Marshal(person)
fmt.Printf("marshal: %v\n", string(b))
}
//将json转化为struct
func t2() {
b := []byte(`{"Name":"tom","Age":20,"Email":"tom@qq.com"}`)
var person Person
json.Unmarshal(b, &person)
fmt.Printf("person: %v\n", person)
}
/*
marshal: {"Name":"tom","Age":20,"Email":"tom@qq.com"}
person: {tom 20 tom@qq.com}
*/
golang标准库encoding/xml
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Age int `xml:"age"`
Email string `xml:"email"`
}
// 将struct转化为xml
func t1() {
person := Person{
Name: "tom",
Age: 20,
Email: "tom@qq.com",
}
b, _ := xml.MarshalIndent(person, " ", " ")
fmt.Printf("%v\n", string(b))
}
//将xml转化为struct
func t2() {
s := "<person>\n <name>tom</name>\n <age>20</age>\n <email>tom@qq.com</email>\n </person>\n"
bytes := []byte(s) //字节切片
//或者直接从文件中读取直接切片
//file, _ := os.ReadFile("stand/b.txt")
//bytes := file[:]
var person Person
xml.Unmarshal(bytes, &person)
fmt.Printf("person: %v\n", person)
}
/*
<person>
<name>tom</name>
<age>20</age>
<email>tom@qq.com</email>
</person>
person: {{ person} tom 20 tom@qq.com}
*/
golang标准库math
一些常量
fmt.Printf("%v\n", math.MaxInt)
fmt.Printf("%v\n", math.MaxInt32)
fmt.Printf("%v\n", math.Pi)
/*
9223372036854775807
2147483647
3.141592653589793
*/
一些函数
//绝对值
fmt.Printf("%v\n", math.Abs(-10))
//x的y次方
fmt.Printf("%v\n", math.Pow(10, 4))
//10的n次方
fmt.Printf("%v\n", math.Pow10(4))
//开平方
fmt.Printf("%v\n", math.Sqrt(100))
//开立方
fmt.Printf("%v\n", math.Cbrt(8))
//向上取整
fmt.Printf("%v\n", math.Ceil(4.1))
//向下取整
fmt.Printf("%v\n", math.Floor(4.1))
//取余数
fmt.Printf("%v\n", math.Mod(10, 3))
//分别取到整数部分和小数部分
modf, frac := math.Modf(3.1415)
fmt.Printf("整数部分: %v, 小数部分: %v\n", modf, frac)
随机数
//随机一个int类型的随机值
func f1() {
fmt.Printf("%v\n", rand.Int())
}
//随机一个0~10的随机值
func f2() {
a := rand.Intn(10)
fmt.Printf("%v\n", a)
}
//为了实现每次随机值不一样
func init() {
rand.Seed(time.Now().UnixMicro())
}