什么是GO语言
Go语言的好处
1、高性能、高并发 2、语法简单、学习曲线平缓 3、丰富的标准库 4、完善的工具链 5、静态编译 6、快速编译 7、跨平台 8、垃圾回收
go基础语法
第一个helloworld程序
package main
import "fmt"
func main() {
fmt.Println("Hello World") // 使用fmt标准库下的打印函数进行语句的打印行为
}
变量
【go语言的变量类型】
- int int32 int64 bool string
- rune bool
- float32 float64 complex32 complext64
代码示例
func main() {
// 变量的声明规则 定义规则1 可以不声明变量数据类型的定义 或者是忽略var关键字 使用":="来进行声明 包内部变量不能使用":="
// 如果变量只定义只声明的话 默认会赋值该类型的初始化 如果是结构体的话,属性成员会赋初始值
var a = "initial"
var b string = "hello world"
c := "hello world"
var d = true
var e float64
f := float32(e) // 进行强制类型砖混
g := a + "top" // 字符串拼接
fmt.Printf("%v %f, %s", d, f, g)
fmt.Printf("%s, %s, %s", a, b, c)
// 常量的定义 如果定义多个常量的话 那么就可以下述语法进行声明 常量的定义可以在赋值表达式进行简单的运算符运算 只能赋值一次
const (
s string = "constant"
h = 500000
l = 3020 / h
)
fmt.Println(s, " ", h, " ", l, " ")
}
if-else条件分支
- 特点
- 条件表达式不需要写括号
- 条件表达式
可以先赋值,再进行条件判断;如:if bool = getTrue(); bool {} 这样的语法是允许的 - if-else-elseif必须带上一个大括号
代码示例 1、if-else不带赋值语句的
func main() {
a := rand.Intn(1000) + 4
if a > 500 {
fmt.Println("a > 500 ")
} else {
fmt.Println("a <= 500")
}
}
2、if-else带赋值语句的
if a := rand.Intn(1000) + 4; a > 500 {
fmt.Println("a > 500")
}else {
fmt.Println("a <= 500")
}
循环
-
特点
- go语言只使用for循环 使用for循环做到关于while循环的事情
-
遍历方式
- for i := 1;i < 100;i ++ {}
- for index, value := range array {}
- for {} (这个是死循环)
- for 条件表达式 {}
代码示例
死循环
// 死循环
func deadFor() {
for {
fmt.Println("dead for")
}
}
变量循环
// 定义变量循环
func defineVariableFor() {
start := 0
for i := 1; i < 100; i++ {
fmt.Println("start = ", start)
start++
}
}
容器遍历循环
// 容器遍历循环
func collectionForeach() {
a := []int{1, 2, 3, 4, 5, 6}
for index, value := range a {
fmt.Println("index = ", index, ";value = ", value)
}
// 使用_下划线可以忽略部分值
for _, value := range a {
fmt.Println("index = ", "_ ignored;value = ", value)
}
}
条件表达式遍历循环
// 条件表达式循环
func conditionForeach() {
arr := []int{1, 2, 3, 4}
for len(arr) > 0 {
arr = arr[1:] // 切片处理
}
}
switch
- 特点
- 可以不需要条件表达式,直接在case语句直接声明 (相比于直接使用条件表达式 直接将条件写在case会更加清晰易懂)
- 不像其他语言;case会自动break;如果需要继续执行的话,那么就需要手动声明
fallthrough - 如果有多个case条件的结果导向相同;那么就可以以逗号分隔表示不同的case
代码示例 1、带条件表达式的switch
// 带条件表达式的switch语句
func conditionExpressionSwitch() {
a := 2
switch a {
case 1:
fmt.Println("a = 1")
case 2 :
fmt.Println("a = 2")
default:
fmt.Println("a = ", a)
}
}
2、不带条件表达式的switch
// 不带条件表达式的switch
func nonConditionalExpressSwitch() {
now := time.Now()
switch {
case now.Hour() < 12:
fmt.Println("now's hour is less than 12")
default:
fmt.Println("now's hour is ge 12")
}
}
数组
-
特点
- 数组的长度可以是不定长长度;如 a := [...]int
- 数组的长度不能使用变量来作为长度 如:a := 2 arr := [a]int 这样是不被允许的
- 当数组有长度且不被赋值的时候 默认数组的元素值取其元素类型的默认值
- 添加元素时,建议使用“append()”方法进行添加
- 数组建议搭配切面一起使用;(可以理解为切面是对数组的一个映射视图)
- 数组是一个不可变对象 但切片不是 切片是一个可变长的对象
-
定义方式
- [length]type
- [...]type
- make([]type, len, [cap])
- []type{}
代码示例
1、带有var关键字的定义方式
// 数组的定义赋值规则
func define1() {
// 如果一开始初始化的时候不想要向数组添加任何的元素的话 建议使用第一种方式
var arr [5]int
fmt.Printf("%X\n", arr)
// 如果是不确定长度或者是清除得到数组中元素的值 建议使用第二种方式
var arr1 []int = []int{1, 2, 3, 4}
fmt.Printf("%X\n", arr1)
// 不定长的话使用"..."代替长度
var arr2 = [...]int{1, 2, 3, 4}
fmt.Printf("%X\n", arr2)
var arr3 = make([]int, 16)
fmt.Printf("%X\n", arr3)
}
2、不带有var关键字的定义方式
func define2() {
arr := []int{1, 2, 3, 4, 5}
arr1 := [...]int{1, 2, 3, 4, 5}
arr2 := make([]int, 16) // 相当于是 var arr [16]int
fmt.Println(arr, " ", arr1, " ", arr2)
}
切片
【 概述 】:切片是对数组的一个视图对象;可以通过切片来改变数组的值
- 特点
- 可以通过切片对数组的值进行修改
- 对一个切片reslice的时候,如果此时cap != len的话,那么reslice的切片可能会出现前一个切片隐藏的值
- 切片的start、end的区间规则是
[start, end) - 注意,如果此时slice的最后一个元素不是在最后的话,那么切片可以在适当的位置下标越界
- 切片的元素通过“append()”方法来实现;由于不清楚append()后的数组对象是否需要进行扩容 所以使用这种格式:
arr = append(arr, element) - 如果需要数据的拷贝,可以调用"copy(dest, src)"来拷贝
- 如果slice := arr[:]的话 表示的就是映射整一个数组; 如果slice := arr[0:],同样也是
代码示例
切片数据一致性
// 切片数据的一致性
func sliceDataConsistent() {
// 数组为[1, 2, 3, 4, 5]
arr := []int{1, 2, 3, 4, 5}
fmt.Println("arr = ", arr) // arr = [1 2 3 4 5]
// 切片取数组的第二个元素到第4个元素
slice := arr[1:4]
fmt.Println("slice = ", slice) // slice = [2 3 4]
// 对数组元素进行修改 首先修改视图显示元素
slice[0] = 3
fmt.Println("now: arr = ", arr, "; slice = ", slice) // now: arr = [1 3 3 4 6] ; slice = [3 3 4]
// 对隐藏元素进行修改 不能通过下述这种方式进行隐藏元素的修改 而是应该这样 通过append()进行修改
// slice[4] = 6
// fmt.Println("now: arr = ", arr, "; slice = ", slice) 注意 如果切片最后一个元素不是数组的最后一个元素,那么就是修改数值
slice = append(slice, 6)
fmt.Println("now: arr = ", arr, "; slice = ", slice) // now: arr = [1 3 3 4 6] ; slice = [3 3 4 6]
// 可以观察到此时slice的值变了 但是原来映射的数组的值没有改变 原因在于go底层拷贝了原来的数组 之后数组扩容后添加了7 此时slice映射的不是原来数组 而是系统底层的数组
slice = append(slice, 7)
fmt.Println("now : arr = ", arr, "; slice = ", slice) // now: arr = [1 3 3 4 6] ; slice = [3 3 4 6 7]
}
reslice
// reslice 对切片进行二次切片 切片并不是重新拷贝一个数组进行截取 而是将不需要的元素值隐藏起来 由len、start、cap进行记录共同协作
// 如果对一个切片进行截取 发现end已经大于了start+len的话 那么就会判断len跟cap的关系 之后重新切片的slice可能会带有原来切片没有的数据
func reSlice() {
arr := []int{1, 2, 3, 4, 5}
slice1 := arr[1:4]
slice2 := slice1[2:4]
fmt.Println("slice1 = ", slice1, ";slice2 = ", slice2) // slice1 = [2 3 4] slice2 = [4 5]
}
wildcard(:)
func wildword() {
arr := []int{1, 2, 3, 4, 5}
slice := arr[0:]
slice2 := arr[:]
fmt.Println("slice = ", slice, "; slice2 = ", slice2) // slice = [1 2 3 4 5] ; slice2 = [1 2 3 4 5]
}
map
- 定义方式:var a map[type]type
- 特点
- 可以调用delete()方法执行key、value的删除行为
- 在执行
map[key]会返回两个返回值,一个是获取到的value, 一个是map中是否包含这个key
代码示例
包括语法特点:定义严格类型的map集合、定义并进行map的赋值、在定义时将map注入键值对、宽松定义,类型为
interface{}
func defineMap() {
// 只定义不赋值的话 是一个nil map 在go语言中 nil是可以调用函数对象的
var map1 = make(map[string]int)
map1["zhangsan"] = 1
map1["lisi"] = 2
fmt.Println("map1 = ", map1) // map1 = map[lisi:2 zhangsan:1]
// 执行删除行为
delete(map1, "lisi")
// 再次获取lisi 看是否有值
if value, ok := map1["lisi"]; ok {
fmt.Println("value exists : value = ", value)
} else {
fmt.Println("value no exists, key = ", "lisi") // value no exists, key = lisi
}
// 定义规则2
map2 := map[string]int{
"lisi": 1,
"zhangsan": 2,
}
fmt.Println("map2 = ", map2) // anyMap = map[lisi:1 zhangsan:wangwu zhaoliu:true]
// map允许存储不同类型的键值对
anyMap := map[string]interface{}{
"lisi": 1,
"zhangsan": "wangwu",
"zhaoliu": true,
}
fmt.Println("anyMap = ", anyMap) // anyMap = map[lisi:1 zhangsan:wangwu zhaoliu:true]
}
range
概述:可以使用range快速遍历 操作对象:集合、数组、channel等
- 注意
- 1、遍历数组 返回的是
index, value - 2、遍历Map 返回的是
key, value - 3、遍历channel 返回的是
value - 4、如果不想要接受某一个值的话 使用
_替换值变量名
- 1、遍历数组 返回的是
语法定义规则: for index, value := range [collection | array | channel]...
代码示例
rangeArray
func rangeArray() {
arr := []int{1, 2, 3, 4, 5}
/*
index = 0 ; value = 1
index = 1 ; value = 2
index = 2 ; value = 3
index = 3 ; value = 4
index = 4 ; value = 5
*/
for index, value := range arr {
fmt.Println("index = ", index, "; value = ", value)
}
}
rangeMap
func rangeMap() {
tempMap := map[string]int{
"zhang": 1,
"lisi": 2,
}
/*
key = zhang ; value = 1
key = lisi ; value = 2
*/
for key, value := range tempMap {
fmt.Println("key = ", key, "; value = ", value)
}
}
rangeChannel
func rangeChannel() {
temp := make(chan int)
go func() {
i := 0
for {
temp <- i
i++
}
}()
<-time.After(1 * time.Second)
for value := range temp {
fmt.Println("value = ", value)
}
}
函数
- 特点
- 函数是一级对象 可以作为参数、返回值、数据类型
- go语言推荐使用函数式编程 即返回值或数据类型定义为函数对象
- 定义成员函数的时候,分为值接收者或者是指针接收者;
- 如果是值接收者的话,那么在方法调用时会对接收者进行复制
- 如果是指针接收者的话,那么在方法调用时会引用原始接收者
- go语言存在指针对象,函数的参数类型可以是指针
- 如果参数是指针类型的话,那么会对参数状态进行修改
- 如果参数是值的话,即进行拷贝后传入
代码示例
定义匿名内部类
匿名内部函数包括:”匿名内部类、最终执行函数“
语法格式:a := func(a, b int) {} 、 defer func(){} ()
func defineNestedFunction() {
// 定义匿名函数
a := func(a, b int) int {
return a + b
}
result := a(1, 2)
fmt.Println("result = ", result)
// 定义最终处理函数
defer func() {
fmt.Println("默认是main协程执行最后会执行 通常处理的是函数执行完毕的资源释放行为")
}()
}
定义函数
// 定义函数
func add(a, b int) int {
return a + b
}
函数式编程
// 定义累加器 传回的类型是函数值
func adder(a, b int) func(a, b int) int {
return func(a, b int) int {
return a + b
}
}
参数类型是指针
特点:如果执行的是修改逻辑的话 那么外部传入参数的状态会被修改
// 函数参数类型是指针 如果是指针的话 那么就会修改到外部数据
func updateArray(arr *[]int) {
*arr = append(*arr, 2)
}
参数类型是值
特点:如果执行的是修改逻辑的话 那么外部传入参数的状态不会被修改 其会被拷贝一份
// 函数参数类型是值类型
func updateArray1(arr []int) []int {
return append(arr, 2)
}
返回值类型是指针
特点:go语言允许将一个函数的局部变量的作用域升级 可以被外部引用所引用
// 函数返回值类型是指针类型 如果返回值类型是指针的话 返回的是地址
func updateArray3(arr []int) *[]int {
arr = append(arr, 3)
return &arr
}
返回值类型是值
特点:返回的是方法执行逻辑最后结果的一个拷贝副本
// 函数返回值类型是值类型 如果返回值类型是值类型的话 返回的是拷贝返回值对象
func updateArray4(arr []int) []int {
return append(arr, 3)
}
成员方法
值接收者
特点:如果是值接收者的话 方法内部执行关于结构体对象状态的修改 调用者的状态不会被修改到
// 值接收者 通常情况下 如果一个结构体当中的成员方法有一个是指针接收者 建议其他也统一为指针接收者
func (a Adder) setB(value int) {
a.b = value
}
指针接收者
特点:如果是指针接收者的话 方法内部执行关于结构体对象状态的修改 调用者的状态会被修改到
type Adder struct {
a, b int
}
// 指针接收者 调用者就是原始类型
func (a *Adder) addAndGet() int {
return a.a + a.b
}
func (a *Adder) setA(value int) {
a.a = value
}
指针
- 特点
- go语言的指针对象其作用不像C++、C那么明显
- 如果使用的是指针 引用的是原始地址 如果使用的值 引用的是拷贝副本的地址
代码示例
func main() {
a := 2
addN(a)
fmt.Println("a = ", a) // a = 2
addN1(&a)
fmt.Println("a = ", a) // a = 4
}
func addN(n int) {
n += 2
}
func addN1(n *int) {
*n += 2
}
结构体
- 特点
- 如果执行的是
a := struct{}的话;那么属性未进行赋值的话 那么赋值的就是成员本身类型的默认值 - 结构体的成员属性类型可以是struct、func等对象
- 结构体的命名方式:结构体的名字不应该出现包名 因为在使用的时候是调用
包名.结构体名 - go语言的访问权限控制:使用的是camel的方式 首字母大写为开放 全小写为包私有 结构体属性、函数、结构体都是这样的命名规则
- 如果执行的是
代码示例:定义一个struct{name, password string};执行一系列方法
func main() {
a := User{"zhangsan", "123456"} // 全部赋值
a1 := User{username: "zhangsan"} // 部分赋值
a2 := User{} // 只定义 不赋值
fmt.Println("a = ", a, "; a1 = ", a1, "; a2 = ", a2) // a = {zhangsan 123456} ; a1 = {zhangsan } ; a2 = { }
a2.username = "zhangsan"
fmt.Println("isValidate : password =>", validatePassword(a, "123456")) // isValidate : password => true
fmt.Println("isValidate : username =>", validateUsername(a, "zhangsan")) // isValidate : username => true
}
type User struct {
username, password string
}
func validatePassword(u User, password string) bool {
return u.password == password
}
func validateUsername(u User, username string) bool {
return u.username == username
}
结构体方法
分为
值接收者 指针接收者
推荐:> 结构体的成员方法如果至少有一个是指针接收者或值接收者的话 那么建议结构体的所有成员方法都是指针接收者或者是值接收者
- 特点
- 值接收者 方法执行的调用者为拷贝副本
- 指针接收者 方法执行的调用者为原始地址
代码示例
// 其内部方法逻辑修改 不会传播到外部调用者
func (u *User) setUsername(username string) {
u.username = username
}
// 其内部方法逻辑修改 会传播到外部调用者
func (u User) setPassword(password string) {
u.password = password
}
错误处理
- 特点
- 介于go语言函数的特殊性(可以传递两个参数);通常情况下 第一个返回的是方法正确执行结果 第二个返回的是错误类型或者是一些bool标识
- 可以使用简单的if-else就可以进行错误处理
- 可以使用”panic()“进行错误信息的报告,也可以使用函数式编程将错误处理进行包装,统一处理某一个底层业务方法的错误处理
- 注意:在服务器应用程序下,panic()不会终止程序,当执行panic()的时候,程序会被保护 建议在自己的错误代码下定制化保护机制 defer func() { err := recover() }
代码示例
使用if-else错误处理
func readFile(filename string) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
} else {
fmt.Printf("%s\n", bytes)
}
}
使用if-else赋值错误处理
func readFile1(filename string) {
if bytes, err := ioutil.ReadFile(filename); err != nil {
panic(err)
} else {
fmt.Printf("%s\n", bytes)
}
}
定制保护机制
func customizeRecover(filename string) {
defer func() {
r := recover()
if r != nil {
fmt.Printf("readFile方法出现了错误 触发了保护机制")
}
}()
bytes, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
} else {
fmt.Printf("%s\n", bytes)
}
}
字符串操作
代码示例:展示了一些常用的字符串操作
func main() {
s := "hello"
fmt.Println("is Contains => ", strings.Contains(s, "ll")) // true
fmt.Println("ll's count => ", strings.Count(s, "l")) // 2
fmt.Println("s is start with 'll' => ", strings.HasPrefix(s, "ll")) // false
fmt.Println("s is end with 'll' => ", strings.HasSuffix(s, "ll")) // false
fmt.Println("s search with 'l' index => ", strings.Index(s, "l")) // 2
fmt.Println("join result =>", strings.Join([]string{"he", "llo"}, "-")) // 拼接的时候顺便加上分隔符 he-llo
fmt.Println("s repeat two => ", strings.Repeat(s, 2)) // 进行拼接 重复拼接两次 hellohello
fmt.Println(strings.Replace(s, "e", "E", -1)) // -1 表示在s这个字符串找到第一个 进行替换 hEllo
fmt.Println(strings.ToLower(s)) // 小写 hello
fmt.Println(strings.ToUpper(s)) // 大写 HELLO
}
字符串格式化
语法规则
1、%v:打印值的数据类型相关的值 如果是int的话;打印的就是%d;如果是string的话;打印的就是%s
2、%d:打印数值
3、%s:打印字符串 或 将字节数组转换为字符串输出
4、%c:打印为rune
5、%+v:进一步打印 适用于struct 打印属性成员的值
6、%#v: 5后更进一步打印 适用于struct有属性成员仍然是struct 打印结构体所在的全路径类名{属性成员的值}
7、%.2f:取两位浮点数
代码示例
func main() {
a := "hello world"
fmt.Printf("%s", a) // hello world
fmt.Printf("%v", a) // hello world
fmt.Printf("%+v", struct {
a, b int
}{1, 2}) // {a : 1 b : 2}
fmt.Printf("%#v", struct {
a, b int
}{1, 2}) // struct{ a int; b int}{a : 1, b : 2}
fmt.Printf("%.2f", 5.0) // 5.00
}
JSON处理
方法
-
Marshal:将结构体对象值转换为json数据
-
Unmarshal:将json数据转换为结构体对象
-
特点
- 介于json在网络传输的通用性和go语言的属性名的命名规则,建议在每一个属性名后面打上tag;如:“name string
json:"name"” 输出的json为{"name": ""} - 执行Unmarshal的时候 需要先声明一个结构体对象 再传入进去 如:
json.Unmarshal(content, &structObj)
- 介于json在网络传输的通用性和go语言的属性名的命名规则,建议在每一个属性名后面打上tag;如:“name string
代码示例
不打上tag
type UserInfo struct {
Name string
Age int
Hobby []string
}
func jsonMarshalNotTag() {
a := UserInfo{
Name: "zhangsan",
Age: 22,
Hobby: []string{"basketball", "football"},
}
result, _ := json.MarshalIndent(a, "", "\t")
/*
Result = {
"Name": "zhangsan",
"Age": 22,
"Hobby": [
"basketball",
"football"
]
}
*/
fmt.Printf("Result = %s", result)
}
打上tag
func jsonMarshalTag() {
a := UserInfo{
Name: "zhangsan",
Age: 22,
Hobby: []string{"basketball", "football"},
}
result, _ := json.MarshalIndent(a, "", "\t")
/*
Result = {
"name": "zhangsan",
"age": 22,
"hobbies": [
"basketball",
"football"
]
}
*/
fmt.Printf("Result = %s", result)
}
将json转化为对象
func jsonUnmarshal() {
a := ` {
"name": "zhangsan",
"age": 22,
"hobbies": [
"basketball",
"football"
]
}`
var result UserInfo // 需要自己创建一个对象后 再将其传入到Unmarshal()方法下
err := json.Unmarshal([]byte(a), &result)
if err != nil {
panic(err)
}
fmt.Printf("%+v", result) // {Name:zhangsan Age:22 Hobby:[basketball football]}
}
时间处理
常用的就是:time.Now()
代码示例
func main() {
a := time.Now()
fmt.Println(a)
date := time.Date(2022, 3, 27, 25, 36, 0, 0, time.UTC) // 构建date对象
date1 := time.Date(2022, 3, 27, 25, 36, 0, 0, time.UTC)
fmt.Println("date = ", date, "\n date1 = ", date1)
fmt.Println("date.year = ", date.Year(), "; date.month = ", date.Month(), "; date.day = ", date.Day(), "; date.hour = ", date.Hour(), "date.minute = ", date.Minute()) // 调用getter()方法 进行属性成员的获取行为
fmt.Println(date.Format("2002-01-02 15:04:05")) // 判断是否匹配给定值
diff := date1.Sub(date) // 计算时间差距
fmt.Println(diff)
fmt.Println("diff.minutes = ", diff.Minutes(), "; diff.seconds = ", diff.Seconds())
date3, err := time.Parse("2006-04-02 15:04:05", "2005-01-02 14:04:54") // 前一个为固定的解析格式 后一个为目标值
if err != nil {
panic(err)
}
fmt.Println(date3 == date)
fmt.Println(a.Unix()) // 十六进制
}
数字解析
跟数字解析相关的包都在strconv包下
1、ParseFloat(string, type):将字符串转化为float32 | float64的浮点数类型
2、ParseInt(string, base, type):将字符串转化为什么进制表示的 int | int8 | int16 | int32 | int64的类型(字符串内容可以是不同进制的)
3、Atoi(string):将字符串转化为十进制数;返回两个参数(value, err)
代码示例
func main() {
f, _ := strconv.ParseFloat("1234", 32) // 第二个参数传0的话 表示自动推测
fmt.Println(f) // 1234
number, _ := strconv.ParseInt("123", 10, 32)
fmt.Println(number) // 123
number1, _ := strconv.ParseInt("0x123", 10, 32)
fmt.Println(number1) // 0
number2, _ := strconv.Atoi("123")
fmt.Println(number2) // 123
number3, err := strconv.Atoi("aaa") // 字符出内容不是数值型 无法转化 返回错误
if err != nil {
panic(err)
} else {
fmt.Println(number3)
}
}
获取进程信息
func main() {
fmt.Println(os.Args) // 获取程序启动的一些启动参数
fmt.Println(os.Getenv("PATH")) // 获取环境变量
fmt.Println(os.Setenv("s1", "v1")) // 设置环境变量
output, err := exec.Command("ping").CombinedOutput() // 执行命令并获得控制台输出信息
if err != nil {
panic(err)
} else {
fmt.Printf("%s", output)
}
}