请选择 进入手机版 | 继续访问电脑版
 找回密码
 立即注册
搜索

本文来自

安全运维工具

安全运维工具

人已关注

请添加对本版块的简短描述

gogogo+无闻go编程基础笔记

[复制链接]
160 abc 发表于 2019-2-10 15:25:17
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






fallthrough

支持初始化表达式,右侧要分号(switch a := 1; {...)这个初始化 都是局部变量,出了switch就不能用了

goto; break; continue
配合标签名使用
break 和 continue 可跳出多层循环(结合label使用,label在哪级就可以跳到哪级)





break+label






continue+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,会拷贝原始数据





example

第一次追加后,输出的地址不变,因为没超过cap,第二次追加后地址变,超过了的话会拷贝到重新分配的内存处
copy





输出是7 8 9 4 5 6,反过来s2--s1 结果是1 2 3(以短的为准)

第八讲,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





map嵌套map时候注意每个都要初始化,检查是否初始化的方法:双返回值,如果没初始化,ok处返回false

迭代
for range(类似for each)
一般形式:for i,v := range slice {输出i,v},相当于枚举,v是元素值,但是是拷贝,不能修改slice本身的元素值,但可以修改slice
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[1k]
  }
  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[]]






sm,sm[1]可以改变slice中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){
}





funcA中改动不影响slice值,(int型参数传递,也不改动原始值)输出a,b 还是1,2






funcA中改动影响slice值,直接传入slice,引用传递,拷贝了地址






如果想改int值,需要传递指针






凡事皆类型,函数也可以当类型,a调用A,输出结果会打印Func A

匿名函数
a := func(){
fmt.Println("Func A")
}
a()
a是后面那个函数的类型的变量,那个函数叫匿名函数
闭包
需要编程基础和对闭包的理解
闭包
另一个闭包例子,看懂一个就都能看懂了
返回一个匿名函数





输出为11,12 和相同的地址(证实确实是闭包)

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(),执行的话程序会终止





defer要放在panic前面,图右下角是输出

作业:






分析程序运行结果

先输出的是fs,然后用下一个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 init






如果想让func中的修改对原数据生效,需要取地址,不然的话得到的是一个值拷贝,改变一下并不能改变原来的值

推荐: 对struct初始化的时候习惯加上取地址符eg:&person,方便后续操作


匿名结构

a := &struct {
  Name string
  Age int
}{
  Name: "Joe",
  Age : 19,
}
fmt.Println(a)

嵌套结构体(像继承),匿名结构






嵌套的结构体,匿名,用.调用,name age 这些也可以不写,如果不写,需要注意顺序






human sex嵌套进去






输出结果

如果想设置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是接收者





输出TZ






两种调用方式:method-value; method-expression

同级名称不能冲突
调用最先找到的方法
方法访问权限问题:
同一个包里:方法可以访问struct的私有和公有字段
作业:






Paste_Image.png






改课上代码得出的作业






answer github上有代码文件

十二讲 interface

只有声明,没有实现,没有数据
实现接口:structural typing,只要实现某类型拥有该接口所有方法的签名,就算是实现接口
通过接口实现类似继承的功能,
go中所有类型都实现了空接口,空接口可以作为任何类型数据的容器
接口转换,大接口转小接口可以,嵌套进去的小接口转不了大街口
对象赋值给接口时发生拷贝, 所以不能通过修改对象改变接口内容
接口存储的类型和对象都是nil 接口才是nil,如果接口里存的是指向nil的指针,也不行
类型断言 ok pattern/ type switch 类型判断,接口转换

接口
若某个具体类型实现了某个接口,则: 这个具体类型的值既可以当做该接口类型的值来使用, 也可以当做该具体类型的值
注解:指针方法集,如果传进来指针,可以调用reveicer是指针和不是指针的方法,如果传进来的是非指针方法集(拷贝的)不能调用指针方法解,所以接口的receiver不做自动转换,因为是拷贝的






connecter 嵌套接口

十三讲reflect
(开始有点听不懂了,目前看代码阶段,估计了解几个基本方法就会跳过,以后再看)

TypeOf ValueOf 从接口中获取信息,结合空接口interface{}使用






例子.png

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()  //获取存储在第一个字段里面的值





用reflect包函数找出指定字段名字,并更改其value

动态调用方法





output: hello jeo, my name is ok

十四讲 并发 concurrency
goroutine 超级线程池
每个实例4-5k,轻便
并发不是并行:并发是由切换时间来实现“同时”运行,并行是多核多线程
goroutine 通过通信来共享内存,而不是共享内存来通信
一个基本的并发&#127792;
//引入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

作业:





code






output

最后一讲!项目和坑
slice
初始化的时候注意,不设置cap的话,设置的元素个数就是cap,超过cap就不是引用传递了,变成了拷贝
保险起见,加返回值,如图:





Pingpang 加返回值[]int

time 修改时间格式
format里最好是拷贝const,不然有坑
for range
引用传递
for range 启动goroutine





go func 匿名函数的参数值记得写不然全输出c

虽然看完了,但是接口复杂用法,reflect除typeof,valueof外其他函数,并发后半节,都有不太懂的地方,,以后再战, 先下手项目去啦(≧▽≦)/
最后提一点错误处理,go中强制返回所有err,然后自己决定怎么处理,项目中返回一个正常返回值加一个err的情况比比皆是。个人觉得算是go的优点。
concurrency, go routine补充
goroutine替代原来的线程概念成为最小的调度单位。一旦运行goroutine时,先去当线程查找,如果线程阻塞了,则被分配到空闲的线程,如果没有空闲的线程,那么就会新建一个线程。
注意,当goroutine执行完毕后,线程不会回收推出,而是成为了空闲的线程。
一篇挺详细的博客
另一篇还不错的博客





http://my.oschina.net/nyankosama/blog/271336

最后,这里是部分leetcode solutions in Golang,有兴趣的可以去玩一玩,算法题比较有意思,语言要求也不高。

作者:暗黑破坏球嘿哈
链接:https://www.jianshu.com/p/1da03e36f382
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表