Go
Go语言
Go语言介绍
全称Golang,是Google公司2009年11月对外公布的一门编程语言
静态
,强类型
(静态
:类型固定 强类型
:不同类型不允许直接运算)
属于 编译型语言
源代码编译成机器语言,由机器直接执行机器码即可执行。
python是解释型语言,想要编译成可执行文件需要借助于 pipinstaller
https://zhuanlan.zhihu.com/p/430490285
python代码防泄漏方案
1.启动起来后删除源代码
2.使用pipinstaller,打包成可执行文件
3. 做到docker镜像中,运行容器需要进行授权码校验。即-e PASSWORD
Go语言特性
1. 跨平台的编译型语言
2. 语法接近c语言
3. 管道(channel),切片(slice),并发(routine)
4. 支持面向对象和面向过程编程模式
Go语言发展
2015年8月19日 go1.5 —— 实现的架构变化,同时保留了和旧版本的兼容性,本次更新中移除了”最后残余的C代码”---》以后再写go不用c写了,用go写---》语言的自举
2018年8月24日 go1.11 —— modules和WebAssembly支持 go mod模式 包管理
最新 1.20版
Go语言应用程序
像k8s,docker,七牛都是Go语言编写的
环境搭建
1.下载go的sdk(开发环境)
https://golang.google.cn/dl/
下载msi文件
2.下载编辑器 goland ,vscode
https://www.jetbrains.com.cn/go/download/other.html
先安装环境在安装编辑器,一路下一步
hello world
package main // package 声明 这个go文件属于哪个包
import (
"fmt" // fmt.Println 会自动导入这个 goland做的
"time"
)
func main() {
fmt.Println("hello world")
time.Sleep(10 * time.Second)
}
// 编译代码成可执行文件,在哪个平台就默认编译成当前平台的
// 命令行执行go build 文件名.go 就可以生成可执行文件
变量名命名规范
Go语言中的函数名、变量名、常量名、类型名和包名等所有的命名,都遵循一个简单的命名规则
- 一个名字必须用字母开头或下划线开头,后面可以跟着任意数量的数字字母
- 大写字母与小写字母有不同的含义。
- 关键字和保留字不能作为变量名
25个关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
37个保留字
内置常量: true false iota nil
内置类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内置函数: make len cap new append copy close delete
complex real imag
panic recover
建议定义变量,函数名使用:用驼峰
go文件命名:建议用下划线
变量的定义和使用
package main
import "fmt"
// 变量定义
func main() {
// 1.完整定义var关键字 变量名 变量类型 = 变量值
//var name string = "lxj" // string 必须用双引号
//var age int = 19 // 数字
// 2. 类型推导,根据值自动推导出变量类型
//var name = "lxj"
//name = "pyy" // 只能更改成同一类型
// 3.简略声明 必须使用:= 不能分家
//name := "lqz"
// 4.一次性声明多个变量
// 完整定义 1
//var name, age, hobby string = "坤", "21", "唱跳rap打篮球" // 问题是只能指定一个类型
// 完整定义多个变量 2
//var (
// name string = "爱坤"
// age int = 19
// hobby string = "唱跳Rap打篮球"
//)
// 5.类型推导 1
//var name, age, hobby = "坤", 19, "唱跳rap打篮球"
// 方法 2
//var (
// name = "爱坤"
// age = 19
// hobby = "唱跳Rap打篮球"
//)
// 6.简略声明
//name, age, hobby := "坤", 19, "唱跳rap打篮球"
// 7.完整定义和类型推导混合使用
//var (
// name = "坤"
// age int = 19
// hobby = "唱跳rap打篮球"
//)
// 8 变量名不能重复定义
//var name = "坤"
//var name = "lxj" // 报错 重复定义
//name := "lxj" // 报错没有新变量在左侧
// 9 变量可以先定义在赋值,必须使用完整定义反射
//var name string // 默认值空字符串
//var age int // 默认值0
//
//age = 19 // 可以后面在赋值
//fmt.Println(name, age) // 定义了变量必须用,不用会报错
// 10,查看变量类型
a := 19
fmt.Printf("a的值是:%v,类型是:%T", a, a) // printf可以打印出变量的值和类型
}
变量类型
数字
# 整数有正负
int8 int16 int32 int64
"rune 是 int32的别名"
# 正整数
uint8 uint16 uint(32位系统是32,64位系统是64)
"byte 是uint8的别名"
# 浮点型
float32 float64 # 小数后面范围不一样
# 复数类型
complex64 complex128
int8
:-128 ~ 127int16
:-32768 ~ 32767int32
:-2147483648 ~ 2147483647int64
:-9223372036854775808 ~ 9223372036854775807
字符
# string
双引号包裹不能换行, 反引号可以回车换行
"go 用单引号包裹字符,会返回对应的ASCII码"
布尔类型
true
false
常量
定义常量
func main() {
//1 .定义常量
const name string = "lxj"
const username = "nxm"
println(name, username)
}
常量与变量的作用范围
在内部可以重新定义变量的
package main
var name string = "lxj"
func main() {
// 内部没有用外部的
//name := "pyy" // 内部有优先用内部的
println(name)
}
内置常量iota
想要使用必须用const
关键字,他的作用是自增
func main() {
const (
a = iota // 0
b // 1
c // 2
d // 3
)
println(a, b, c, d)
}
===================================================================
func main() {
const (
a = 10 // 10
b = iota // 1 iota在哪一行 就从那一行开始自增
c // 2
d // 3
)
println(a, b, c, d)
}
iota 后续的行都会自增,不需要在+iota
函数
基础用法
普通函数
func main() {
test()
}
func test() {
fmt.Println("我是普通函数")
}
有参函数
func main() {
test(1, "1")
}
// 必须指定参数的类型
func test(a int, b string) {
fmt.Println(a, b)
}
===================================================================
func main() {
test(1, 10, "ss")
}
// 多个参数相同类型可以简写
func test(a, c int, b string) {
fmt.Println(a, b, c)
}
有返回值的有参函数
func main() {
res := test(1, 10)
fmt.Println(res)
}
// 需要在test(a, b int) 后面指定返回值的类型
func test(a, b int) int {
return a + b
}
===================================================================
// 多个参数多个返回值
func main() {
res, res1 := test(1, 10, "成功")
fmt.Println(res, res1)
}
func test(a, b int, c string) (int, string) {
return a + b, c
}
高级用法
同一个包下的变量函数只能定义一次,用了同样的名字会报错
匿名函数,一般定义在其他函数的内部
package main
import "fmt"
func main() {
test()
}
func test() {
func() {
fmt.Println("我是内部的匿名函数")
}() // 需要加括号调用,不然报错
}
定义变量接收函数,匿名函数带参数
package main
import "fmt"
func main() {
test1()
}
func test() {
//f := func() {
// fmt.Println("我是内部的匿名函数")
//} // 报错需要指定f的类型
var f func() = func() {
fmt.Println("我是内部的匿名函数")
}
fmt.Printf("%T", f) // 打印出来是func() 类型,可以自定义
}
func test1() {
var f func() = func() {
fmt.Println("内层函数")
}
// func(a int) 也可以不写 函数的参数和返回值都是类型的一部分(如果写类型也要写参数)
var f1 func(a int) = func(a int) {
fmt.Println(a)
}
var f2 func(b int) int = func(b int) int {
fmt.Println(b)
return b + 10
}
var f3 func(b int) string = func(b int) string {
fmt.Println(b)
return "我是你爹"
}
fmt.Printf("%T\n", f) // 打印出来是func()
fmt.Printf("%T\n", f1) // 打印出来是func(int)
fmt.Printf("%T\n", f2) // 打印出来是func(int) int
fmt.Printf("%T\n", f3) // 打印出来是func(int) string
f3(1)
}
可以简写:
f3 := func(b int) string {
fmt.Println(b)
return "我是你爹"
}
函数作为返回值
func main() {
res := test2()
fmt.Printf("%T", res)
}
func test2() func() {
return func() {
fmt.Println("我是内层函数")
}
}
调用函数返回闭包函数 (闭包函数内用到了外部作用域的变量)
func main() {
res := test2()
res() // 取决于闭包函数有没有参数,有就要传
fmt.Printf("%T", res)
}
func test2() func() {
a := 10
f := func() {
fmt.Println(a)
}
return f
}
复杂用法
package main
import "fmt"
func main() {
f := test(func() {
fmt.Println("我是传给c的函数")
}) // 传给了test参数c一个函数,并返回了一个func(int, int) int 函数
d := f(1, 2) // f 调用了返回的func(int, int) int 函数 返回值给了d
fmt.Println(d) // 3
}
// test函数接收了一个c参数 这个参数是函数类型,没有参数也没有返回值
// test 有返回值,返回了一个函数,函数有两个参数,和一个返回值,上面就要指定func(int, int) int
func test(c func()) func(int, int) int {
c()
return func(a, b int) int {
return a + b
}
}
类型重命名
type MyFunc = func(int, int) int
这种写法是重命名了
type Myint int
这种写法是重新定义了
package main
import "fmt"
// 类型重命名
type MyFunc = func(int, int) int
func main() {
//// 完整定义 写起来太麻烦
//var f func(int, int) int = test(func() {
// fmt.Println("传给了c")})
// 类型重命名
var f MyFunc = test(func() {
fmt.Println("传给了c")
})
// 传给了test参数c一个函数,并返回了一个func(int, int) int 函数
d := f(1, 2) // f 调用了返回的func(int, int) int 函数 返回值给了d
fmt.Println(d) // 3
}
// test函数接收了一个c参数 这个参数是函数类型,没有参数也没有返回值
// test 有返回值,返回了一个函数,函数有两个参数,和一个返回值,上面就要指定func(int, int) int
func test(c func()) func(int, int) int {
c()
return func(a, b int) int {
return a + b
}
}
可变长参数
// 可变长参数
func test(a ...int) {
fmt.Println(a, "\n") //[1 2 3 4 5 6] 切片类型
fmt.Printf("%T", a) // []int int类型切片
}
func main() {
test(1, 2, 3, 4, 5, 6)
}
defer 关键字延迟调用与执行顺序
func main() {
// 1.普通用法
func() {
fmt.Println("我他妈是个帅哥")
}()
defer fmt.Println("呃") // 延迟调用,main函数代码执行完了才会走这个
fmt.Println("zyg是狗屎")
// 闭包函数
var a = 10
//defer func() {
// fmt.Println(a) // 99 整个代码走完了才会走。因为下面给a重新赋值了
// fmt.Println("我他妈是个帅哥")
//}()
defer func(i int) {
fmt.Println(i) // 10 因为走到这个函数的时候(a)就已经把a传进来了
fmt.Println("我他妈是个帅哥")
}(a)
a = 99
fmt.Println("我觉得你很傻")
// 多个defer执行的顺序 ,
// 由此得知当前函数代码执行完毕会按照defer从下往上的顺序执行
defer fmt.Println("我zyg挺帅的") // 4
defer fmt.Println("我李晓健更帅") // 执行顺序3
fmt.Println("zyg就是坨屎") // 执行顺序1
fmt.Println("我同意李晓健帅") // 执行顺序2
}
包的使用
在python
中模块是一个py文件,包是一个文件夹里面有__init__
go
的包是在一个文件下, 这个文件夹下所有的go文件第一行要声明包
go
中也有sdk内置包
,自定义包
,第三方包
注意事项:
1. 在包里的go文件的参数想要在外部使用,需要`大写字母`开头声明。
2. 同一个包下无论是函数,变量的名字都只能定义一次
-
在同一个包下使用函数 不需要导入直接使用
不区分大小写
-
包的
package
声明的名字可以和包名不一样,必须要重命名import 自定义名 "awesomeProject/包名"
-
包内的
init
函数,可以写多个,会依次执行。如果包内的多个go
文件有多个init
按照从上往下的顺序执行 -
包下还可以建别的(文件夹)包,直接导入就可以使用且可以和外部的名字重复。
bao
b1.go
b2.go // 不能用b1里出现过的名字
bao2
c1.go // 可以用b1或b2的名字,也可以导入bao用 b1或b2里的函数
c2.go
b1.go 包
package qqq // 修改包名
// package bao
import "fmt"
// 在go中 大写字母开头表示导出,如果小写在外部用不了
func Add(a, b int) int {
return a + b
}
func Test() {
// 同一个包下的函数可以直接调用
res := Add(1, 2)
fmt.Println(res)
}
执行文件
我们在导入包以后必须要使用不然会报错,如果你是个犟种那么可以使用impory _ "awesomeProject/bao"
导入
package main
// 正常情况下
//import (
// "awesomeProject/bao"
// "fmt"
//)
//
//func main() {
// res1 := bao.Add(5, 67)
// fmt.Println(res1)
// bao.Test()
//}
import (
abc "awesomeProject/bao" // 包里的package 修改名字后必须要重命名
"fmt"
)
func main() {
res1 := abc.Add(2, 3)
fmt.Println(res1)
abc.Test()
}
第三方包的使用
gin
包 web服务
使用gin
包开一个web服务监听本地端口,从而可以从浏览器访问
安装
go get github.com/gin-gonic/gin
// 局部修改 当前项目
打开编辑器的File 》 settings 》 Go 》Go Moduiles 》 在Environment里输入
GOPROXY=https://goproxy.cn,direct
// 全局修改
go env -w GOPROXY=https://goproxy.cn,direct
改完之后重启一下goland
使用
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 做路由
r.LoadHTMLGlob("templates/*") // 加载整个文件夹
r.GET("/lxj", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "李晓健是帅哥",
}) // 向/lxj 发送get请求就会执行func这个视图函数
})
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"name": "李晓健", "age": 18}) // gin.H 填写需要渲染的东西
})
//r.Run() // 可以传参数 监听0.0.0.0:8080
r.Run(":8001") // 可以传参数 监听0.0.0.0:8001
}
if-else
go 的流程控制
package main
import "fmt"
func main() {
score := 44
if score >= 90 {
fmt.Println("成绩好啊")
} else if score < 90 && score > 80 { // go中的and 用 && or 用 ||
fmt.Println("下次给我加把劲")
} else if score < 80 && score > 60 {
fmt.Println("你怎么考的")
} else {
fmt.Println("你真该死啊")
}
}
循环
go中只有for
循环,包含了while
功能
package main
import "fmt"
func main() {
// 1.基本语法
//for i := 0; i < 10; i++ {
// fmt.Println(i)
//} // i的作用范围只在for的内部作用域有效
// 省略掉第一部分 ;不可以省
//i := 0
//for ; i < 10; i++ {
// fmt.Println(i)
//}
// 省略第一和第三部分
//i := 0
//for i < 10 {
// fmt.Println(i)
// i++
//}
// 实现while 死循环
//for true {
// fmt.Println("死循环了")
//}
// 基于索引循环
a := "lxj李晓健" // range是按字符的,中文也可以1个中文占3个字节,也就是3个索引
for i, v := range a {
fmt.Println(i) // 拿的是索引
fmt.Println(v) // 拿的是字母对应的ascii码
}
// break 与continue
for i := 0; i < 10; i++ {
if i == 3 {
continue // 遇到3 返回开始下次一循环
}
if i > 5 {
break //遇到大于5直接结束循环
}
fmt.Println(i)
}
}
switch
是一个条件语句,用来替代if else 优雅的替换掉
有个关键字fallthrough
条件成立也会无条件走下一个
package main
import "fmt"
func main() {
//score := 92
// 有表达式
//switch score {
//case 90, 92, 93: // 多条件
// fmt.Println("90")
//case 80:
// fmt.Println("80")
//case 70:
// fmt.Println("70")
//
////不符合上述条件走default
//default:
// fmt.Println("不知道")
//}
//score := 44
//// 无表达式
//switch {
//case score >= 90:
// fmt.Println("考的好")
//case score > 80 && score < 90:
// fmt.Println("还行")
//case score < 80 && score > 60:
// fmt.Println("没考好啊")
//default:
// fmt.Println("考的啥啊")
//}
//
// fallthrough 默认·情况下每个条件执行完会默认加break,其他语言必须要加,不加就会无条件执行下一个,Fallthrough就可以在go里无条件执行下一个
score := 92 // 就算符合条件也会一直走到最后一个
switch {
case score >= 90:
fmt.Println("考的好")
fallthrough
case score > 80 && score < 90:
fmt.Println("还行")
fallthrough
case score < 80 && score > 60:
fmt.Println("没考好啊")
fallthrough
default:
fmt.Println("考的啥啊")
}
}
数组
数组 只能存放同一类型元素的集合,形参一个数组,不能存放不同类型的元素。
[3]int
长度为3的int数组
数组是在内存中申请一个连在一起的多个空间,每个空间里只能存放相同类型元素。
而python中的列表是存放的内存地址,每个地址又指向数据,可以是不同的数据类型。
定义数组
package main
import "fmt"
func main() {
var a [3]int // 定义一个长度为3的int数组
fmt.Println(a) // [0 0 0] 数字类型没有值的情况下默认为0
// 使用数组的方法
a[0] = 1
a[1] = 3
fmt.Println(a) // [1 3 0] 如果是int8 类型不能存放超过int8范围的数字,其他类型也是同样情况、
// 定义数组并初始化
var b [4]int = [4]int{1, 2, 3, 4} // [4]int{1,2,3,4} 初始化设置必须也要指定类型
c := [5]int{1, 2, 3, 4, 5}
fmt.Println(b, c)
}
定义后长度不可以增加。