您的位置:首页 > 编程语言 > Go语言

Golang学习笔记

2016-09-27 19:11 591 查看

一、基础

1. Hello World程序

demo:

package main
import "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 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是数组的大小,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,"\n")
v1, ok1 := map1["k2"] //0 false
print(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 }


保留字func 用于定义一个函数;

函数可以定义用于特定的类型,这类函数更加通俗的称呼是method。这部分称

作receiver 而它是可选的。它将在6 章使用;

funcname是你函数的名字;

int 类型的变量q 是输入参数。参数用pass-by-value 方式传递,意味着它们会被复

制;

变量r 和s 是这个函数的named return parameters。在Go 的函数中可以返回多个

值。参阅”多个返回值” 在32。如果想要返回无命名的参数,只需要提供类型:(int,

int)。如果只有一个返回值,可以省略圆括号。如果函数是一个子过程,并且没有任

何返回值,也可以省略这些内容;

这是函数体,注意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 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这个名字:

test.go中的import

test.go中调用Add时的前缀

utils1.go中的package 名字

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,"\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]=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 ...]\n", 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\n",sum(1000));
printf("%ld\n",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


在Python中调用C

test_sum_c.py

```python

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非常慢

博文为作者原创,未经允许,禁止转载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: