博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang学习笔记
阅读量:6669 次
发布时间:2019-06-25

本文共 12478 字,大约阅读时间需要 41 分钟。

一、基础

1. Hello World程序

demo:

package mainimport "fmt" // 注释//注释func main() {    fmt.Printf("Hello World\n")}

执行:

go run demo.go

编译成可执行文件

go build demo.go

2. 声明和赋值

func main() {    var a int    var b string = "aaaa"    var (        c int        d bool    )    conse i = 10    e := 15    a = 1    b = "hello"    c = 2    d = false    f,g:=12,13    if (a + c + e < 100 && d) {        fmt.Printf("Hello World\n")    } else {        fmt.Printf(b)    }}
  • 变量的类型在变量名后面,所以不能同时声明和赋值
  • 在2.4后,支持a:=1这种类型,类似于动态类型的声明了,这时会自动识别变量的类型
  • 可以在var里面声明多个变量
  • 声明了的变量一定要用,不然编译会错误
  • const定义常量,类似var,而已可以定义多个

字符串转换

func main() {    var s string ="aaaaaa"    sb:=[]byte(s)    s1:=string(sb)    fmt.Printf("%c\n",sb[0])    fmt.Printf("%s\n",s1)    fmt.Printf("%s\n",s)}

直接访问字符串的下标是不可以的,需要先转换为byte类型,通过string函数转换回来。

其他操作符

Precedence Operator(s)

Highest :

* / % << >> & &^+ - | ^== != < <= > >=<-&&

Lowest

||

3. 控制结构

3.1. if

func main() {    if a:=1;a<0{        fmt.Printf("a is less 0")    }else{        fmt.Printf("a is bigger 0")    }}
  • and &&
  • or ||
  • un !=
  • ==
  • <= >=

    2. for

    for有三种形式
  • for init;condition;post {} 类似C的for
  • for condition {} 类似C的while
  • for {} 类似C的for(;;) 死循环

golang中没有do和while

func main() {    for i := 0; i < 10; i++ {        fmt.Printf("i:%d\n", i)    }    j := 0    for j < 10 {        fmt.Printf("j:%d\n", j)        j++    }    k := 0    for {        fmt.Printf("k:%d\n", k)        k++        if k > 9 {            break        }    }}

rang可以方便地遍历对象

l := []string{"a", "b", "c"}for k, v := range l {    fmt.Printf("pos:%d,value:%s\n", k, v)}

switch

func main() {    i := 0    switch i {    case -1, 0:        fmt.Printf("is -1 or 0")    case 1:        fmt.Printf("is 1")    default:        fmt.Printf("default")    }}
  • case中逗号表示或

4. 预定义的函数

Table 2.3. Go 中的预定义函数close new panic complexclosed make recover reallen append print imagcap copy println
  • len 和cap 可用于不同的类型,len 用于返回字符串、slice 和数组的长度。参阅”array、slices和map” 小节了解更多关于slice、数组和函数cap 的详细信息。
  • new 用于各种类型的内存分配。参阅”用new 分配内存” 在59 页。
  • make 用于内建类型(map、slice 和channel)的内存分配。参阅”用make 分配内存” 在59
    页。
  • copy 用于复制slice。append 用于追加slice。参阅本章的”slice”。
  • panic 和recover 用于异常处理机制。参阅”恐慌(Panic)和恢复(Recover)” 在37 页了
    解更多信息。
  • print 和println 是底层打印函数,可以在不引入fmt 包的情况下使用。它们主要用于调
    试。
  • complex、real和imag 全部用于处理复数。有了之前给的简单的例子,不用再进一步讨论
    复数了。

5. array,slice和map

array就是Python的数组

map就是Python的字典

5.1 array

array使用[n]定义,其中n是数组的大小,type是元素的类型。n是可选的。

数组的定义和使用。

l1:=[]string{"a","b"}var l2 [2]intl2[0]=1l2[1]=2var l3 [2][3]intl3[0][0]=1print(l1[0])print(l2[0])print(l3[0][0])

当传递一个array给函数的时候,函数得到的是一个array的副本,即传值。

5.2 slice(切片)

slice和array类似,不同的是slice是array的一个指针,所以修改slice,是会影响array的,而且传递一个slice给函数的时候,传递的是指针,所以是传址。

l1:=[]string{"a","b","c","d"}s1:=l1[1:2]s2:=l1[:] //类似l1[0:4]s3:=l1[:2] //类似l1[0:2]print(s1[0])print(s2[0])print(s3[0])

append用户向切片中添加元素,返回新的切片,新的切片的内存地址可能和之前的不一样。

l1:=[]string{"a","b","c","d"}s2:=append(l1,"e","f")print(s2[4])print(s2[5])

5.3 map

map的定义:map[<from type>]<to type>

var map1 = make(map[string]int)map2:=map[string]int{    "k1":11,"k2":12,}print(map2)map1["k1"] = 12v, ok := map1["k1"] //12 trueprint(v, ok,"\n")v1, ok1 := map1["k2"] //0 falseprint(v1, ok1,"\n")//map1["k2"] = 0, false //删除,不知道为什么测试失败

遍历:

map2:=map[string]int{    "k1":11,"k2":12,}for k,v:=range map2{    print(k,v,"\n")}

二、函数

func (p mytype) funcname(q int) (r,s int) { return 0,0 }
  1. 保留字func 用于定义一个函数;
  2. 函数可以定义用于特定的类型,这类函数更加通俗的称呼是method。这部分称
    作receiver 而它是可选的。它将在6 章使用;
  3. funcname是你函数的名字;
  4. int 类型的变量q 是输入参数。参数用pass-by-value 方式传递,意味着它们会被复
    制;
  5. 变量r 和s 是这个函数的named return parameters。在Go 的函数中可以返回多个
    值。参阅”多个返回值” 在32。如果想要返回无命名的参数,只需要提供类型:(int,
    int)。如果只有一个返回值,可以省略圆括号。如果函数是一个子过程,并且没有任
    何返回值,也可以省略这些内容;
  6. 这是函数体,注意return 是一个语句,所以包裹参数的括号是可选的。

DEMO:

func add(a int,b int) (int,int,int){    return a+b,a,b}func main() {    a,b,c:=add(12,13)    print(a,b,c)}

函数的参数都是传值的形式。

1. 命名返回的参数

func add(a int,b int) (int){    sum:=a+b    return sum}
func add(a int,b int) (sum int){    sum=a+b    return}

在定义函数的返回类型的时候,加上类型对应的变量名,然后在函数体中,return后面不带参数,这样go就会找到函数体中的变量sum,然后返回。注意,由于定义函数的时候已经定义了sum变量,所以后面修改的时候不需要加冒号。

2. 定义函数退出前执行的函数

例如在打开文件的时候,每一次返回都需要关闭文件描述符,这样会有大量代码重复,在go中,可以定义函数退出前执行的函数。

func test(a int) (sum int){    defer print("test done")    if a<0{        return -1    }else{        return 1    }}

这样无论a是大于还是小于0,都会输出文字。

3.可变参数

类似于Python的*args

func test(args ...int) (int) {    for i, v := range args {        print(i, v, "\n")    }    return 1}func main() {    test(1, 2, 3, 4, 5)}

4. 快速定义函数

类似于Python的lambda

add_one := func(i int) int {    return 1 + i}print(add_one(2))

5.函数作为参数

func test(i int, fun func(int) int) (int) {    i++    return fun(i)}func main() {    add_one := func(i int) int {        return 1 + i    }    print(test(2,add_one))}

最后的值是4,

6.恐慌和恢复

go中没有异常的处理,只有恐慌和恢复

func thrownPanic(fun func() int) (b bool) {    defer func() {        if r := recover(); r != nil {            b = true        }    }()    fun()    return}func main() {    add_one := func() int {        a := []int{1, 2, 3}        print(a[0])        return 1    }    print(thrownPanic(add_one))}

在thrownPanic中,会调用fun,然后在函数结束前执行defer的函数,如果fun中产生了异常,r会为非nil,这样返回true,否则返回false

这样外层的函数就能知道调用fun是否产生了异常。

"runtime/debug"    "reflect"    "fmt")func test_func(){    defer func(){        if err:=recover();err!=nil{            fmt.Println("Panic ",err,reflect.TypeOf(err))            debug.PrintStack()        }    }()    list:=[]int{1}    println(list[1])}func main(){    test_func()

程序在执行println(list[1])的时候,会产生恐慌,也就是异常,但是程序不会立刻退出,还会执行defer的函数,这时,通过revocer函数,可以catch住这个异常,然后把异常信息打印出来,这样程序可以继续正常运行,其实跟try exept差不多。

三、包

目录结构

/    test.go    /util        util1.go        util2.go

util1.go:

package utilfunc add(a int, b int) int {    //私有函数,只能在包内被调用    return a + b}func Add(a int, b int) int {    //公有函数,可以在其他包中调用    return a + b}

test.go

package mainimport "./util" // 注释//注释func main() {    print(util.Add(12,13))}

有多个地方用到util这个名字:

  1. test.go中的import
  2. test.go中调用Add时的前缀
  3. utils1.go中的package 名字
  4. utils1.go的文件名

其中1,2,3需要一样,4可以不一样

在包中,变量或者函数名,根据首字母是否大写来判断该变量或函数是否公有的

在一个包中,也就是文件夹,不同的文件中的变量或函数名不能重复。

别名

import u "./util" // 注释//注释func main() {    print(u.Add(12,13))}

导入util并设置别名为u

package mainimport . "./util" // 注释//注释func main() {    print(Add(12,13))}

别名设置为点,就不需要名字了

导入路径

上面的导入方法是相对路径导入,即在util前面加上./

还有绝对路径的导入

import   "shorturl/model"       //加载GOROOT/src/shorturl/model模块

包的文档

每个包都应该包含文档,如果包中有多个文件,文档可以在任意一个。格式:

/*The regexp package implements a simple library forregular expressions.The syntax of the regular expressions accepted is:regexp:concatenation '|' concatenation*/package regexp

单元测试

在util目录下面创建文件util_test.go:

package utilimport "testing"func TestAdd(t *testing.T){    if Add(12,13)!=24 {        t.Log("test Add 12 13 fail")        t.Fail()    }}

然后cd到util目录,执行go test,这样go就会调用所有*_test.go文件里面的Test*函数。在函数里面,如果测试失败,就调用t.Fail()

常用的包

标准的Go 代码库中包含了大量的包,并且在安装Go 的时候多数会伴随一起安装。浏

览$GOROOT/src/pkg 目录并且查看那些包会非常有启发。无法对每个包就加以解说,不过下
面的这些值得讨论:b

  • fmt
    包fmt 实现了格式化的I/O 函数,这与C 的printf 和scanf 类似。格式化短语派生于C
    。一些短语(%-序列)这样使用:
    %v
    默认格式的值。当打印结构时,加号(%+v)会增加字段名;
    %#v
    Go 样式的值表达;
    %T
    带有类型的Go 样式的值表达;
  • io
    这个包提供了原始的I/O 操作界面。它主要的任务是对os 包这样的原始的I/O 进行封
    装,增加一些其他相关,使其具有抽象功能用在公共的接口上。
  • bufio
    这个包实现了缓冲的I/O。它封装于io.Reader 和io.Writer 对象,创建了另一个对象
    (Reader 和Writer)在提供缓冲的同时实现了一些文本I/O 的功能。
  • sort
    sort 包提供了对数组和用户定义集合的原始的排序功能。
    b描述来自包的godoc。额外的解释用斜体。
    54 Chapter 4: 包
  • strconv
    strconv 包提供了将字符串转换成基本数据类型,或者从基本数据类型转换为字符串
    的功能。
  • os
    os 包提供了与平台无关的操作系统功能接口。其设计是Unix 形式的。
  • sync
    sync 包提供了基本的同步原语,例如互斥锁。
  • flag
    flag 包实现了命令行解析。参阅”命令行参数” 在第92 页。
  • json
    json 包实现了编码与解码RFC 4627 [22] 定义的JSON 对象。
  • template
    数据驱动的模板,用于生成文本输出,例如HTML。
    将模板关联到某个数据结构上进行解析。模板内容指向数据结构的元素(通常结构的
    字段或者map 的键)控制解析并且决定某个值会被显示。模板扫描结构以便解析,而
    “游标”@ 决定了当前位置在结构中的值。
  • http
    http 实现了HTTP 请求、响应和URL 的解析,并且提供了可扩展的HTTP 服务和基本
    的HTTP 客户端。
  • unsafe
    unsafe 包包含了Go 程序中数据类型上所有不安全的操作。通常无须使用这个。
  • reflect
    reflect 包实现了运行时反射,允许程序通过抽象类型操作对象。通常用于处理静态类
    型interface{} 的值,并且通过Typeof 解析出其动态类型信息,通常会返回一个有接
    口类型Type 的对象。包含一个指向类型的指针,StructType、IntType 等等,描述
    了底层类型的详细信息。可以用于类型转换或者类型赋值。参阅6,第”自省和反射”
    节。
  • exec
    exec 包执行外部命令。

四、进阶

1. 指针

go中也有指针,但是和C有区别,不能进行指针运算。

var p *int; //p=nil//*p = 8; //这样会报错,因为p还没有分配内存var i int;p = &i; //令p的值等于i的内存值*p = 8; //相当于修改i的值print(p, &i, i)//看到,p和*i是一样的,i=8
  • 在类型的前面加星号,表示定义一个该类型的指针,定义之后,没有为指针分配内存,指针为nil

2. 内存分配

go中有两种方法可以分配内存:new和make

2.1 new

new是声明一个变量,返回变量的指针。

var p1 *int; //p=nilp2 := new(int)var p3 intprint(p1,"\n")print(*p2,"\n")print(p3,"\n")

p1是一个指针,但是它还没有初始化

p2也是一个指针,它已经初始化了,初始化值为0
p3是一个变量,已经初始化,初始化值为0

2.2 make

make只能声明slice,map和channel。返回值,而不是指针。

也可以使用new来声明指针,然后使用make来初始化:

p2 := new([]int)//(*p2)[0]=1 //这样是不行的,因为p2还没有初始化*p2=make([]int,11)(*p2)[0]=1print((*p2)[0])

make([]int,11)声明了一个长度为11的切片slice

2.3 结构定义

go的结构和C的结构类似,然后使用new来定义

type Person struct {    name string    age int}p1:=new(Person)p1.name="kevin"p1.age=23println(p1.name)println(p1.age)

定义结构的方法:

type Person struct {    name string    age  int}func (p *Person) SetAge(v int) int {    p.age = v    return v}func main() {    p1 := new(Person)    p1.SetAge(12)    println(p1.age)}

五、接口

六、并发

1. goroutine

go中一个很重要的概念是goroutine,协程的英文是coroutine,第一个字母不同,即goroutine类似于协程,但是又有所不同,是go特殊的概念。

goroutine的特点:

  • goroutine 并行执行的,有着相同地址空间的函数。
  • 轻量的
  • 初始化的代价很低

一个并发的DEMO:

package mainimport "time"//注释func f(name string) {    for i := 0; i < 10; i++ {        println(name, i)        time.Sleep(1 * 1e9)    }}func main() {    go f("f 1")    go f("f 2")    time.Sleep(15 * 1e9)}

类似于线程,然后启动的方法也比较方便,只需要在前面加一个go的关键字。

1e9是一个内部的常量,是秒的意思

2. channel

goroutine之间通过channel来通讯,channel类似于队列,即Python中的Queue。

定义channel的时候,需要指定channel接受的类型,可以为int,string,和interface等。

var c chan int;  //定义一个接受int类型的channelc = make(chan int)c<-1//向c中put一个对象item:=<- c //从c中取一个对象

所以上面的并发程序可以改为:

func f(name string) {    for i := 0; i < 10; i++ {        println(name, add(i))        time.Sleep(1 * 1e9)    }    c <- 1//向c中put一个对象}func main() {    c = make(chan int)    go f("f 1")    go f("f 2")    <-c    <-c}

在goroutine中,可以put,也可以get。

2.1 缓冲

上面的channel是无缓冲的,也就是put完之后,goroutine就会阻塞,直到有goroutine取走。

定义有缓冲的channel:

ch:=make(chan int,1)

1就是缓冲的数量

获取的时候,也是阻塞的,可以使用非阻塞的方法:

v,ok:=<-c

如果有值,ok为true,否则为false

3.并发问题

尽管是叫并发,但是同一时刻,只有一个goroutine在执行,也就是占用CPU,类似Python的线程和协程。

可以通过runtime.GOMAXPROCS(n)来设置同一个时刻运行的goroutine的数量,也可以修改环境变量GOMAXPROCS。

七、通讯

1. 文件

读取文件

import "os"func main() {    buf := make([]byte, 1024)    f ,_:= os.Open("./test.data")    defer f.Close()    for {        n, _ := f.Read(buf)        if n == 0 {            break        }        os.stdout.write(buf[0:n]) //必须使用write,如果使用println,会输出切片的内存地址    }}

通过bufio读取文件

import "os"import "bufio"//注释func main() {    buf := make([]byte, 1024)    f, _ := os.Open("/etc/passwd")    defer f.Close()    r := bufio.NewReader(f)    w := bufio.NewWriter(os.Stdout)    defer w.Flush()    for {        n, _ := r.Read(buf)        if n == 0 { break }        w.Write(buf[0:n])    }}

创建目录

if _, e := os.Stat("name"); e != nil {    os.Mkdir("name", 0755)} else {    // error}

2. 命令行参数

import "os"import "flag"import "fmt"//注释func main() {    dnssec := flag.Bool("dnssec", false, "Request DNSSEC records")    port := flag.String("port", "53", "Set the query port")    flag.Usage = func() {        fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] [name ...]\n", os.Args[0])        flag.PrintDefaults()    }    flag.Parse()    println(*dnssec,*port)}

3. 网络

八、性能测试

go的特点就是高性能和高并发

测试用例:

从1加到1000,执行一百万次,计算需要的时间。

使用linux的time命令来进行计时。

1. go

sum.go

package mainfunc sum(num int)int{    sum:=0    for i:=1;i<=num;i++{        sum+=i    }    return sum}func main(){    sum_:=0    for j:=0;j<1000000;j++{        sum_+=sum(1000)    }    println(sum_)    println(sum(1000))}

编译:

go build sum.go

执行

time ./sum

结果:

real    0m0.464suser    0m0.460ssys     0m0.001s

2. C

sum.c

#include 
long int sum(int num){ long int sum=0; int i =0; for( i=1;i<=num;i++){ sum=sum+i; }; return sum;}int main(){ int i ; long int sum_=0; for (i=0;i<1000000;i++){ sum_+=sum(1000); } printf("%ld\n",sum(1000)); printf("%ld\n",sum_);// printf(sum_); return 0;};

编译:

gcc sum.c  -fPIC -shared -o sum.so

执行

time ./sumc

结果:

real    0m2.874suser    0m2.856ssys     0m0.000s

3. Python

test_sum.py

def sum(num):    s = 0    for i in xrange(1,num+1):        s += i    return sdef main():    sum_=0    for i in range(1000000):        sum_+=sum(1000)    print sum_if __name__ == '__main__':    main()

执行

time python test_sum.py

结果

real    0m35.146suser    0m34.814ssys     0m0.125s
  1. 在Python中调用C
    test_sum_c.py
from ctypes import cdllc_lib = cdll.LoadLibrary('./sum.so')if __name__ == '__main__':    c_lib.main()

执行

time python test_sum_c.py

结果

real    0m2.899suser    0m2.874ssys     0m0.006s

5. 比较

语言|go|C|Python|Python调用c

---|
用时:|0.464s|2.874s|35.146s|2.899s

  • go竟然比c还快,而且快很多
  • Python非常慢

博文为作者原创,未经允许,禁止转载。

你可能感兴趣的文章
Asp.net mvc 5 CRUD代码自动生成工具- vs.net 2013 Saffolding功能扩展
查看>>
优秀团队建设--美国式团队(ppt)
查看>>
HTML5 ArrayBuffer:类型化数组 (二)
查看>>
tail 命令(转)
查看>>
Linux中zip压缩和unzip解压缩命令详解
查看>>
DevExpress学习03——label控件的背景色问题
查看>>
微信全球MBA创新大赛麻省理工学院的WeChat Care团队夺魁
查看>>
properties 中文乱码问题的解决
查看>>
2015第15周一
查看>>
腾讯一面总结(转)
查看>>
Loadrunner参数化如何在记事本中将参数值显示超过100个参数值
查看>>
更改默认alert框体
查看>>
[Shell学习笔记] 命令行下的高级网络工具cURL命令
查看>>
同步、异步、阻塞和非阻塞区别
查看>>
get改造成post请求
查看>>
linux文件分割(将大的日志文件分割成小的)
查看>>
使用LNMP常见问题解答
查看>>
python网络爬虫进入(一)——简单的博客爬行动物
查看>>
好玩的SQL
查看>>
TFS Express backup and restore
查看>>