golangGo语言程序员

gogogo+无闻go编程基础笔记

2016-05-24  本文已影响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 基本类型

类型零值:某种类型的默认值,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,有兴趣的可以去玩一玩,算法题比较有意思,语言要求也不高。:)

上一篇下一篇

猜你喜欢

热点阅读