美文网首页golangGo语言程序员
gogogo+无闻go编程基础笔记

gogogo+无闻go编程基础笔记

作者: 暗黑破坏球嘿哈 | 来源:发表于2016-05-24 15:54 被阅读3868次

    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程序结构

    1. package main包含main函数
      一个程序有且只有一个main包和一个main函数
    2. package 要放在非注释的第一行
    3. package 后加import,import 引入非main包
    4. 一般建议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

    _下划线,空白标识符,可以忽略

    变量类型转换

    1. (没有隐式转换)
    2. 只能在两种互相兼容的类型之间转换
    3. 类型转换格式:
      var a float32 = 1.1
      b := int(a)

    第四讲:常量和常量枚举

    常量枚举

    1. 定义常量组:如果不提供初始值,则使用上行表达式
    2. 使用相同表达式不代表具有相同值
    3. iota是常量计数器,从0起,组中每定义一个常量自动递增1
    4. 通过初始化规则和iota可以达到枚举效果
    5. 每遇到一个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循环

    1. 第一种(只写核心代码)
    a:=1
    for{
        a++
        if a > 3 {
          break
        }
      fmt.Println(a)
    }
    fmt.Println("over")
    
    1. 第二种
    a:=1
    for a <= 3{
        a++
        fmt.Println(a)
    }
    fmt.Println("over")
    
    1. 比较常见的形式
    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} 一个数组保存了两个变量的指针

    两种传递方式:

    1. 数组是值类型,传递的时候是拷贝的而不是传地址的
    2. 如果想传地址,叫引用类型,slice,可以实现,类似动态数组,引用传递

    数组之间可以 == 或者 !=(看上面,不同类型的数组不可以做任何直接比较)

    指向数组的指针(new的返回值是指针,一般不new)
    p := new([10]int)//输出也是指向数组的指针

    两种方式

    多元数组(最外层最好定义好每个数组长度,不要用...)

    a := [2][3]int {
      {1,1,1}
      {2,2,2}
    }
    

    第七讲 slice

    1. 本身不是数组,指向底层数组,可以关联底层的局部或全部
    2. 类型
    3. len()获取元素个数,cap()获取容量,容量就是后面连续的内存块还有多少就是多少,比如数组1-10,slice3-5,cap3-10
    4. 多个slice指向相同底层数组数据,改动一个,全部改动
    5. 声明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[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[]]

    sm[i],sm[i][1]可以改变slice中map值

    果然很牛逼的例子,有点绕
    应用:可以通过对mapkey排序从而达到给map排序
    把key转为int放在一个slice里然后sort.ints(s)

    作业:把map中v,k对调,
    解:for k,v := range m1{
    m2[v] = k}

    第九讲:func

    1. 不支持嵌套,重载,默认参数
    2. 支持 不需要声明原型就可以使用, 不定长度变参(参数地方可以写 ... ), 多返回值, 命名返回值参数, 匿名函数, 闭包
    3. 可以作为类型使用
    4. 关键字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
    1. 类似析构函数
    2. 函数体执行结束后,按调用顺序相反顺序执行
    3. 函数有严重错误也会执行
    4. 支持匿名函数
    5. 应用:资源清理,解锁,记录时间,文件关闭,return后修改计算结果
    6. 如果函数体内某个变量作为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[i],然后用下一个for循环输出fs这个slice的值,i为外层for循环中i的引用地址,执行完第一个for循环,i=4,所以第二个for循环输出的都是=4
    然后执行第一次第一个for循环中的第二个defer,第一个defer是值拷贝,所以值被修改了。。。其实并不是很懂┑( ̄Д  ̄)┍

    第十讲 结构struct

    1. go 中没有class,struct类似class
    2. 比较,名字等各个地方都一样才可以比较,名字不一样也不可以,名字不一样是不同类型
    3. 支持匿名,匿名结构可用于map,可以比较
    4. 允许通过指针读写结构成员
    5. 初始化:
    meStruct := new(SmallSoho) //初始化一个空的结构体,返回指针
    meStruct := &SmallSoho{} //同上
    meStruct := SmallSoho{} //返回一个空结构体
    meStruct := &SmallSoho{Name:"SmallSoho"} //也可以键值对这样来初始化
    
    struct init
    如果想让func中的修改对原数据生效,需要取地址,不然的话得到的是一个值拷贝,改变一下并不能改变原来的值

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

    1. 匿名结构
    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

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

    接口
    若某个具体类型实现了某个接口,则: 这个具体类型的值既可以当做该接口类型的值来使用, 也可以当做该具体类型的值

    注解:指针方法集,如果传进来指针,可以调用reveicer是指针和不是指针的方法,如果传进来的是非指针方法集(拷贝的)不能调用指针方法解,所以接口的receiver不做自动转换,因为是拷贝的


    connecter 嵌套接口

    十三讲reflect

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

    1. 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 通过通信来共享内存,而不是共享内存来通信

    一个基本的并发🌰

    //引入time包
    func main(){
      go Go()
      time.sleep(2*time.second)//暂停两秒
    }
    //加一个东西,使函数和main可以通信
    func Go(){
      fmt.Println("gogogo")
    }
    
    channel
    1. 阻塞同步
    2. make(c:=make(chan bool)//bool 是存储的数据类型),close
    3. 引用类型
    4. for range 可以不断操作, 这时候channel必须关的,不然死锁
    5. 单向(参数类型传递),双向
    6. 缓存大小(无缓存channal,取出在放前,有缓存channel)
    select类似switch
    1. 处理一个或多个channel的发送和接收
    2. 同时多个channel 会被随机处理
    3. 空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,有兴趣的可以去玩一玩,算法题比较有意思,语言要求也不高。:)

    相关文章

      网友评论

      • 空城新月:蛮详细的,话说有一个题外话。你们学校在教golang吗?我在例子上面看到你写了一个课堂作业。
        煎鱼不可能有BUG:@暗黑破坏球嘿哈 哪个网站或者机构的视频 还不错
        空城新月:@暗黑破坏球嘿哈 这样~
        暗黑破坏球嘿哈:@空城新月 并不是,是跟视频学的,视频里讲师布置的作业:smile:

      本文标题:gogogo+无闻go编程基础笔记

      本文链接:https://www.haomeiwen.com/subject/isadlttx.html