Go语言做Web编程非常方便,并且在开发效率和程序运行效率方面都非常优秀。相比于Java,其最大的优势就是简便易用,而相比于PHP,它最大的优势就是性能好。
(go做web)推荐Gorilla的库,里面的路由,csrf的包用起来都很方便。
如果你要使用Go语言做Web后端开发,我推荐你用Beego。如果你对性能有超高的要求(不想因为用了框架而降低一点点性能),我推荐你用Gin。
Go语言要求public的变量必须以大写字母开头,private变量则以小写字母开头
无闻视频笔记
代码部分并未格式化!!
第一讲:go 常用命令
go get 获取远程包(需提前安装git)
go run 运行
go build 测试编译(package main 的文件)
go fmt 格式化代码
go install 编译包文件和整个程序
go test 运行测试文件(**_test.go是测试文件,命名自己文件的时候需要注意)
go doc 查看文档(本地官网 go -http=:8080 & 后台执行)
注释:
//
/* */
go结构
第二讲:go程序结构
- package main包含main函数
一个程序有且只有一个main包和一个main函数 - package 要放在非注释的第一行
- package 后加import,import 引入非main包
- 一般建议package的名称和目录名一致 pacage 名称不要使用驼峰标记法,使用下划线
var 全局变量
type 基本类型 比如:type newtype struct/interface{(内容)}
packagename.funcname包中方法的调用
改包名:
import abc "fmt"/import . "fmt"
调用的时候
abc.Println/Println
不推荐使用.那种,容易混淆
第三讲 go 可见性规则(大小写!)
首字母大小写决定 常量,变量,类型,接口,结构 是否可以被外部调用
函数名首字母小写=private
函数名首字母大写=public
问:声明多个全局变量,常量,结构体能不能像import那样一起弄呢?
答:可以,组。只能全局变量,函数体中不可以
type(
newtype int
type1 string
)
函数体中aaa, bbb = 1, 2 就可以了
a, b, c, d := 1, 2, 3, 4
(注意::=这种声明方式不能使用在函数外,函数外的每个语法块都必须以关键字开始。)
第三讲 go 基本类型
- bool 必须true、false。数字不行
- int/uint/int8/unit8(最后一个,64位,8字节)
- byte(unit8)
- int32(rune)提示处理unicode字符
- 复数:complex64/complex128
- 足够保存指针的32、64位整数型
- 引用类型:slice,map,chan
- interface
- func,可以赋值给变量
类型零值:某种类型的默认值,eg:int--0,bool--false,string--空字符串:“”,数组不给类型--空数组[] 数组指定int(var a []int)--[0]
类型别名type(byte int8)奇奇怪怪的东西,给类起个自己喜欢的名字 ┑( ̄Д  ̄)┍
var b (int可要可不要) = 1 声明的同时赋值
如果不加类型,系统会自己判断的,如果后面要用的类型和现在声明的时候不一样,声明的时候最好标出之后想使用的类型
d := 456
_下划线,空白标识符,可以忽略
变量类型转换
- (没有隐式转换)
- 只能在两种互相兼容的类型之间转换
- 类型转换格式:
var a float32 = 1.1
b := int(a)
第四讲:常量和常量枚举
常量枚举
- 定义常量组:如果不提供初始值,则使用上行表达式
- 使用相同表达式不代表具有相同值
- iota是常量计数器,从0起,组中每定义一个常量自动递增1
- 通过初始化规则和iota可以达到枚举效果
- 每遇到一个const关键字,iota就重置为0
eg
const(
a='A'
b
c=iota
d
)
output: 65 65 2 3
第四讲,最后讲了移位运算,感觉短时间用不到,马一下有空再看
第五讲 指针,递增递减,控制语句
指针
用 . 来操作
&
默认值nil 而非NULL
正常运用:
a:=1
var p *int = &a
fmt.Println(p)
//output:addr
fmt.Println(*p)
//output:1即a
递增递减语句
a++,a-- 不能给参数赋值,必须单独放一句
--,++也不能放在变量左边
if
没有括号,空格分隔
if a := 1 ; a > 1
//a为局部变量
if a, b :=1, 2; a > 0
//判断多个
for 循环
注:条件语句中建议不使用函数;左大括号需要和语句在同一行
三种for循环
- 第一种(只写核心代码)
a:=1
for{
a++
if a > 3 {
break
}
fmt.Println(a)
}
fmt.Println("over")
- 第二种
a:=1
for a <= 3{
a++
fmt.Println(a)
}
fmt.Println("over")
- 比较常见的形式
a:=1
for i:= 0; i < 3; i++{
a++
fmt.Println(a)
}
fmt.Println("over")
switch 不用break,匹配自动停,如果匹配还想继续,要fallthrough
支持初始化表达式,右侧要分号(switch a := 1; {...)这个初始化 都是局部变量,出了switch就不能用了
goto; break; continue
配合标签名使用
break 和 continue 可跳出多层循环(结合label使用,label在哪级就可以跳到哪级)
问题是:continue换成goto会怎么样,--无限循环
数组
数组长度也算是类型
var a [2]int
var b [1]int
a =b 不合法
a := [...]int{0:1,1:2,2:3}
//初始化可以不给定元素个数,索引也可以只指定某一个值
var p [100]int = &a 指向数组的指针
a := [...]int{&x,&y} 一个数组保存了两个变量的指针
两种传递方式:
- 数组是值类型,传递的时候是拷贝的而不是传地址的
- 如果想传地址,叫引用类型,slice,可以实现,类似动态数组,引用传递
数组之间可以 == 或者 !=(看上面,不同类型的数组不可以做任何直接比较)
指向数组的指针(new的返回值是指针,一般不new)
p := new([10]int)//输出也是指向数组的指针
多元数组(最外层最好定义好每个数组长度,不要用...)
a := [2][3]int {
{1,1,1}
{2,2,2}
}
第七讲 slice
- 本身不是数组,指向底层数组,可以关联底层的局部或全部
- 类型
- len()获取元素个数,cap()获取容量,容量就是后面连续的内存块还有多少就是多少,比如数组1-10,slice3-5,cap3-10
- 多个slice指向相同底层数组数据,改动一个,全部改动
- 声明var si []int / a:= []int和数组的区别在于[]中没有数字也没有...
也可以用make()声明
s1 := make([]int,3,10) int型,3个元素,cap10,如果大于10,会变成20,不设置容量会认为容量=元素个数
slice今天遇到的一个小坑,初始化slice的时候,想给slice的长度的个变量,只能用exampleSlice:=make([]int,variableA+1)这种make的初始化方式,examSlice:=[]int{variableA+1:0}这种也不行,必须是个const,var那种显然也不行,问题跟这个一样
new和make的区别:
func new(Type) *Type
# 返回一个指针
func make(Type,size,IntegerType) Type
# Type必须是引用类型(Slice,Map,Channal等)返回一个对象,而非指针
reslice
在slice上slice
越界会报错
append
在slice尾部追加元素,或者追加另一个slice(很像拼接两个数组)
如果超过cap,会拷贝原始数据
第一次追加后,输出的地址不变,因为没超过cap,第二次追加后地址变,超过了的话会拷贝到重新分配的内存处
copy
第八讲,map
key-value 比线性查找快,但比索引(数组和slice)慢很多
key必须是可以比较的,slice,func不可以
声明: var m map[int]string /m: = make(map[int]string)
赋值:m[1] = "ok"//key=1,value="ok"
删除: delete(m,1)
var m map[int]map[int]string
m=make(map[int]map[int]string)//只初始化外层map
m[1] = make(map[int]string)
m[1][1] = "ok"
a:= m[1][1]
输出a为ok
迭代
for range(类似for each)
一般形式:for i,v := range slice {输出i,v},相当于枚举,v是元素值,但是是拷贝,不能修改slice本身的元素值,但可以修改slice[i]
for k,v := range map{输出键值对,但都是对拷贝值的操作,map[k]才可以改变map中值}
map[k]
有一个传说中很牛逼的例子
func main(){
sm := make([]map[int]string,5)//以map为元素的slice
for _,v := range sm {
v= make(map[int]string,1)
v[1] = "OK"
fmt.Println(v)//打印map[1:ok]
}
fmt.Println(sm)//打印空map,因为v改变不影响slice
}
输出结果map[1:OK]map[1:OK]map[1:OK]map[1:OK]map[1:OK][map[] map[] map[] map[] map[]]
果然很牛逼的例子,有点绕
应用:可以通过对mapkey排序从而达到给map排序
把key转为int放在一个slice里然后sort.ints(s)
作业:把map中v,k对调,
解:for k,v := range m1{
m2[v] = k}
第九讲:func
- 不支持嵌套,重载,默认参数
- 支持 不需要声明原型就可以使用, 不定长度变参(参数地方可以写 ... ), 多返回值, 命名返回值参数, 匿名函数, 闭包
- 可以作为类型使用
- 关键字func {要在同一行
声明:如果参数类型一样(参数和返回值处type1=type2,可以只写最后一个;返回值可以不起名) func funcname (para1 type1, para2 type2) (returnvalue1 type1, returnvalue2 type2){
}
匿名函数
a := func(){
fmt.Println("Func A")
}
a()
a是后面那个函数的类型的变量,那个函数叫匿名函数
闭包
需要编程基础和对闭包的理解
闭包
另一个闭包例子,看懂一个就都能看懂了
返回一个匿名函数
defer
- 类似析构函数
- 函数体执行结束后,按调用顺序相反顺序执行
- 函数有严重错误也会执行
- 支持匿名函数
- 应用:资源清理,解锁,记录时间,文件关闭,return后修改计算结果
- 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时就获得了拷贝,否则是引用,引用地址
比如:
for i:=0; i<3;i++{
defer func(){打印i}
}
//输出210,i作为参数传递进去
for i:=0; i<3;i++{
defer func(){
打印i
}() //这个括号是类似defer a()
}
//输出333,闭包,i一直作为引用
go中的异常处理机制,类似finaly--panic/recover 模式来处理,panic可以在任何地方引发,recovery只有在defer中才有效
panic(),执行的话程序会终止
作业:
先输出的是fs[i],然后用下一个for循环输出fs这个slice的值,i为外层for循环中i的引用地址,执行完第一个for循环,i=4,所以第二个for循环输出的都是=4
然后执行第一次第一个for循环中的第二个defer,第一个defer是值拷贝,所以值被修改了。。。其实并不是很懂┑( ̄Д  ̄)┍
第十讲 结构struct
- go 中没有class,struct类似class
- 比较,名字等各个地方都一样才可以比较,名字不一样也不可以,名字不一样是不同类型
- 支持匿名,匿名结构可用于map,可以比较
- 允许通过指针读写结构成员
- 初始化:
meStruct := new(SmallSoho) //初始化一个空的结构体,返回指针
meStruct := &SmallSoho{} //同上
meStruct := SmallSoho{} //返回一个空结构体
meStruct := &SmallSoho{Name:"SmallSoho"} //也可以键值对这样来初始化
推荐: 对struct初始化的时候习惯加上取地址符eg:&person,方便后续操作
- 匿名结构
a := &struct {
Name string
Age int
}{
Name: "Joe",
Age : 19,
}
fmt.Println(a)
嵌套结构体(像继承),匿名结构
如果想设置sex,a := teacher{Name: "joe",Age:19,human:human{Sex:0}}
如果想修改
a.Name = "Joe2"
a.Sex = 100//注意,这里直接加sex就可以了
十一讲 method
func(a A)print(){}
调用的时候 a.print
A类型的a是接收者
同级名称不能冲突
调用最先找到的方法
方法访问权限问题:
同一个包里:方法可以访问struct的私有和公有字段
作业:
十二讲 interface
- 只有声明,没有实现,没有数据
- 实现接口:structural typing,只要实现某类型拥有该接口所有方法的签名,就算是实现接口
- 通过接口实现类似继承的功能,
- go中所有类型都实现了空接口,空接口可以作为任何类型数据的容器
- 接口转换,大接口转小接口可以,嵌套进去的小接口转不了大街口
- 对象赋值给接口时发生拷贝, 所以不能通过修改对象改变接口内容
- 接口存储的类型和对象都是nil 接口才是nil,如果接口里存的是指向nil的指针,也不行
- 类型断言 ok pattern/ type switch 类型判断,接口转换
接口
若某个具体类型实现了某个接口,则: 这个具体类型的值既可以当做该接口类型的值来使用, 也可以当做该具体类型的值
注解:指针方法集,如果传进来指针,可以调用reveicer是指针和不是指针的方法,如果传进来的是非指针方法集(拷贝的)不能调用指针方法解,所以接口的receiver不做自动转换,因为是拷贝的
十三讲reflect
(开始有点听不懂了,目前看代码阶段,估计了解几个基本方法就会跳过,以后再看)
- TypeOf ValueOf 从接口中获取信息,结合空接口interface{}使用
reflect包中field 反射出结构信息
反射
Go语言实现了反射,反射就是动态运行时的状态。我们一般用到的包是reflect包。
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。
这两种获取方式如下:
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
动态调用方法
十四讲 并发 concurrency
goroutine 超级线程池
每个实例4-5k,轻便
并发不是并行:并发是由切换时间来实现“同时”运行,并行是多核多线程
goroutine 通过通信来共享内存,而不是共享内存来通信
一个基本的并发🌰
//引入time包
func main(){
go Go()
time.sleep(2*time.second)//暂停两秒
}
//加一个东西,使函数和main可以通信
func Go(){
fmt.Println("gogogo")
}
channel
- 阻塞同步
- make(c:=make(chan bool)//bool 是存储的数据类型),close
- 引用类型
- for range 可以不断操作, 这时候channel必须关的,不然死锁
- 单向(参数类型传递),双向
- 缓存大小(无缓存channal,取出在放前,有缓存channel)
select类似switch
- 处理一个或多个channel的发送和接收
- 同时多个channel 会被随机处理
- 空select 可以阻塞main(for+select)
4.可以设置超时
判断channel是否关闭x, ok := <-ch //看ok的值
复杂点的channel
var m map[string] chan bool
// key--string, val--bool,这个map是channel
作业:
最后一讲!项目和坑
slice
初始化的时候注意,不设置cap的话,设置的元素个数就是cap,超过cap就不是引用传递了,变成了拷贝
保险起见,加返回值,如图:
time 修改时间格式
format里最好是拷贝const,不然有坑
for range
引用传递
for range 启动goroutine
虽然看完了,但是接口复杂用法,reflect除typeof,valueof外其他函数,并发后半节,都有不太懂的地方,,以后再战, 先下手项目去啦(≧▽≦)/
最后提一点错误处理,go中强制返回所有err,然后自己决定怎么处理,项目中返回一个正常返回值加一个err的情况比比皆是。个人觉得算是go的优点。
concurrency, go routine补充
goroutine替代原来的线程概念成为最小的调度单位。一旦运行goroutine时,先去当线程查找,如果线程阻塞了,则被分配到空闲的线程,如果没有空闲的线程,那么就会新建一个线程。
注意,当goroutine执行完毕后,线程不会回收推出,而是成为了空闲的线程。
最后,这里是部分leetcode solutions in Golang,有兴趣的可以去玩一玩,算法题比较有意思,语言要求也不高。:)