Golang学习笔记

https://www.cnblogs.com/Xjng/p/5913954.html

一、基础

1. Hello World程序

demo:

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

执行:
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
")
    } 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
",sb[0])
    fmt.Printf("%s
",s1)
    fmt.Printf("%s
",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
", i)
    }
    j := 0
    for j < 10 {
        fmt.Printf("j:%d
", j)
        j++
    }
    k := 0
    for {
        fmt.Printf("k:%d
", k)
        k++
        if k > 9 {
            break
        }
    }
}

rang可以方便地遍历对象

l := []string{"a", "b", "c"}
for k, v := range l {
    fmt.Printf("pos:%d,value:%s
", 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 complex
closed make recover real
len append print imag
cap 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]int
l2[0]=1
l2[1]=2
var l3 [2][3]int
l3[0][0]=1
print(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"] = 12
v, ok := map1["k1"] //12 true
print(v, ok,"
")
v1, ok1 := map1["k2"] //0 false
print(v1, ok1,"
")
//map1["k2"] = 0, false //删除,不知道为什么测试失败

遍历:

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

二、函数

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, "
")
    }
    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 util

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

test.go

package main
import "./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 main
import . "./util" // 注释
//注释
func main() {
    print(Add(12,13))
}

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

导入路径

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

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

包的文档

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

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

单元测试

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

package util
import "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=nil
p2 := new(int)
var p3 int
print(p1,"
")
print(*p2,"
")
print(p3,"
")

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]=1
print((*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=23
println(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 main

import "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类型的channel
c = 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 ...]
", os.Args[0])
        flag.PrintDefaults()
    }
    flag.Parse()
    println(*dnssec,*port)
}

3. 网络

八、性能测试

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

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

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

1. go

sum.go

package main
func 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.464s
user    0m0.460s
sys     0m0.001s

2. C

sum.c

#include <stdio.h>
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
",sum(1000));
    printf("%ld
",sum_);
//    printf(sum_);
    return 0;

};

编译:

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

执行

time ./sumc 

结果:

real    0m2.874s
user    0m2.856s
sys     0m0.000s

3. Python

test_sum.py

def sum(num):
    s = 0
    for i in xrange(1,num+1):
        s += i
    return s

def main():
    sum_=0
    for i in range(1000000):
        sum_+=sum(1000)
    print sum_

if __name__ == '__main__':
    main()

执行

time python test_sum.py

结果

real    0m35.146s
user    0m34.814s
sys     0m0.125s
  1. 在Python中调用C
    test_sum_c.py
from ctypes import cdll
c_lib = cdll.LoadLibrary('./sum.so')


if __name__ == '__main__':
    c_lib.main()

执行

time python test_sum_c.py

结果

real    0m2.899s
user    0m2.874s
sys     0m0.006s

5. 比较

语言|go|C|Python|Python调用c
---|
用时:|0.464s|2.874s|35.146s|2.899s

    • go竟然比c还快,而且快很多
    • Python非常慢
原文地址:https://www.cnblogs.com/rxbook/p/10638179.html