Golang学习笔记
本文内容纲要:
-一、基础
-1.HelloWorld程序
-2.声明和赋值
-3.控制结构
-4.预定义的函数
-5.array,slice和map
-二、函数
-三、包
-四、进阶
-1.指针
-2.内存分配
-2.3结构定义
-五、接口
-六、并发
-1.goroutine
-2.channel
-3.并发问题
-七、通讯
-1.文件
-2.命令行参数
-3.网络
-八、性能测试
-1.go
-2.C
-3.Python
-5.比较
一、基础
1.HelloWorld程序
demo:
packagemain
import"fmt"//注释
//注释
funcmain(){
fmt.Printf("HelloWorld\n")
}
执行:
gorundemo.go
编译成可执行文件
gobuilddemo.go
2.声明和赋值
funcmain(){
varaint
varbstring="aaaa"
var(
cint
dbool
)
consei=10
e:=15
a=1
b="hello"
c=2
d=false
f,g:=12,13
if(a+c+e<100&&d){
fmt.Printf("HelloWorld\n")
}else{
fmt.Printf(b)
}
}
- 变量的类型在变量名后面,所以不能同时声明和赋值
- 在2.4后,支持a:=1这种类型,类似于动态类型的声明了,这时会自动识别变量的类型
- 可以在var里面声明多个变量
- 声明了的变量一定要用,不然编译会错误
- const定义常量,类似var,而已可以定义多个
字符串转换
funcmain(){
varsstring="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函数转换回来。
其他操作符
PrecedenceOperator(s)
Highest:
*/%<<>>&&^
+-|^
==!=<<=>>=
<-
&&
Lowest
||
3.控制结构
3.1.if
funcmain(){
ifa:=1;a<0{
fmt.Printf("aisless0")
}else{
fmt.Printf("aisbigger0")
}
}
- and&&
- or||
- un!=
- ==
- <=>=
2.for
for有三种形式
- forinit;condition;post{}类似C的for
- forcondition{}类似C的while
- for{}类似C的for(;?死循环
golang中没有do和while
funcmain(){
fori:=0;i<10;i++{
fmt.Printf("i:%d\n",i)
}
j:=0
forj<10{
fmt.Printf("j:%d\n",j)
j++
}
k:=0
for{
fmt.Printf("k:%d\n",k)
k++
ifk>9{
break
}
}
}
rang可以方便地遍历对象
l:=[]string{"a","b","c"}
fork,v:=rangel{
fmt.Printf("pos:%d,value:%s\n",k,v)
}
switch
funcmain(){
i:=0
switchi{
case-1,0:
fmt.Printf("is-1or0")
case1:
fmt.Printf("is1")
default:
fmt.Printf("default")
}
}
- case中逗号表示或
4.预定义的函数
Table2.3.Go中的预定义函数
closenewpaniccomplex
closedmakerecoverreal
lenappendprintimag
capcopyprintln
- 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.1array
array使用[n]定义,其中n是数组的大小,type是元素的类型。n是可选的。
数组的定义和使用。
l1:=[]string{"a","b"}
varl2[2]int
l2[0]=1
l2[1]=2
varl3[2][3]int
l3[0][0]=1
print(l1[0])
print(l2[0])
print(l3[0][0])
当传递一个array给函数的时候,函数得到的是一个array的副本,即传值。
5.2slice(切片)
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.3map
map的定义:map[<fromtype>]<totype>
varmap1=make(map[string]int)
map2:=map[string]int{
"k1":11,"k2":12,
}
print(map2)
map1["k1"]=12
v,ok:=map1["k1"]//12true
print(v,ok,"\n")
v1,ok1:=map1["k2"]//0false
print(v1,ok1,"\n")
//map1["k2"]=0,false//删除,不知道为什么测试失败
遍历:
map2:=map[string]int{
"k1":11,"k2":12,
}
fork,v:=rangemap2{
print(k,v,"\n")
}
二、函数
func(pmytype)funcname(qint)(r,sint){return0,0}
- 保留字func用于定义一个函数;
- 函数可以定义用于特定的类型,这类函数更加通俗的称呼是method。这部分称
作receiver而它是可选的。它将在6章使用; - funcname是你函数的名字;
- int类型的变量q是输入参数。参数用pass-by-value方式传递,意味着它们会被复
制; - 变量r和s是这个函数的namedreturnparameters。在Go的函数中可以返回多个
值。参阅”多个返回值”在32。如果想要返回无命名的参数,只需要提供类型:(int,
int)。如果只有一个返回值,可以省略圆括号。如果函数是一个子过程,并且没有任
何返回值,也可以省略这些内容; - 这是函数体,注意return是一个语句,所以包裹参数的括号是可选的。
DEMO:
funcadd(aint,bint)(int,int,int){
returna+b,a,b
}
funcmain(){
a,b,c:=add(12,13)
print(a,b,c)
}
函数的参数都是传值的形式。
1.命名返回的参数
funcadd(aint,bint)(int){
sum:=a+b
returnsum
}
funcadd(aint,bint)(sumint){
sum=a+b
return
}
在定义函数的返回类型的时候,加上类型对应的变量名,然后在函数体中,return后面不带参数,这样go就会找到函数体中的变量sum,然后返回。注意,由于定义函数的时候已经定义了sum变量,所以后面修改的时候不需要加冒号。
2.定义函数退出前执行的函数
例如在打开文件的时候,每一次返回都需要关闭文件描述符,这样会有大量代码重复,在go中,可以定义函数退出前执行的函数。
functest(aint)(sumint){
deferprint("testdone")
ifa<0{
return-1
}else{
return1
}
}
这样无论a是大于还是小于0,都会输出文字。
3.可变参数
类似于Python的*args
functest(args...int)(int){
fori,v:=rangeargs{
print(i,v,"\n")
}
return1
}
funcmain(){
test(1,2,3,4,5)
}
4.快速定义函数
类似于Python的lambda
add_one:=func(iint)int{
return1+i
}
print(add_one(2))
5.函数作为参数
functest(iint,funfunc(int)int)(int){
i++
returnfun(i)
}
funcmain(){
add_one:=func(iint)int{
return1+i
}
print(test(2,add_one))
}
最后的值是4,
6.恐慌和恢复
go中没有异常的处理,只有恐慌和恢复
functhrownPanic(funfunc()int)(bbool){
deferfunc(){
ifr:=recover();r!=nil{
b=true
}
}()
fun()
return
}
funcmain(){
add_one:=func()int{
a:=[]int{1,2,3}
print(a[0])
return1
}
print(thrownPanic(add_one))
}
在thrownPanic中,会调用fun,然后在函数结束前执行defer的函数,如果fun中产生了异常,r会为非nil,这样返回true,否则返回false
这样外层的函数就能知道调用fun是否产生了异常。
"runtime/debug"
"reflect"
"fmt"
)
functest_func(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println("Panic",err,reflect.TypeOf(err))
debug.PrintStack()
}
}()
list:=[]int{1}
println(list[1])
}
funcmain(){
test_func()
程序在执行println(list[1])
的时候,会产生恐慌,也就是异常,但是程序不会立刻退出,还会执行defer的函数,这时,通过revocer函数,可以catch住这个异常,然后把异常信息打印出来,这样程序可以继续正常运行,其实跟tryexept差不多。
三、包
包说明
目录结构
/
test.go
/util
util1.go
util2.go
util1.go:
packageutil
funcadd(aint,bint)int{
//私有函数,只能在包内被调用
returna+b
}
funcAdd(aint,bint)int{
//公有函数,可以在其他包中调用
returna+b
}
test.go
packagemain
import"./util"//注释
//注释
funcmain(){
print(util.Add(12,13))
}
有多个地方用到util这个名字:
- test.go中的import
- test.go中调用Add时的前缀
- utils1.go中的package名字
- utils1.go的文件名
其中1,2,3需要一样,4可以不一样
在包中,变量或者函数名,根据首字母是否大写来判断该变量或函数是否公有的
在一个包中,也就是文件夹,不同的文件中的变量或函数名不能重复。
别名
importu"./util"//注释
//注释
funcmain(){
print(u.Add(12,13))
}
导入util并设置别名为u
packagemain
import."./util"//注释
//注释
funcmain(){
print(Add(12,13))
}
别名设置为点,就不需要名字了
导入路径
上面的导入方法是相对路径导入,即在util前面加上./
还有绝对路径的导入
import"shorturl/model"//加载GOROOT/src/shorturl/model模块
包的文档
每个包都应该包含文档,如果包中有多个文件,文档可以在任意一个。格式:
/*
Theregexppackageimplementsasimplelibraryfor
regularexpressions.
Thesyntaxoftheregularexpressionsacceptedis:
regexp:
concatenation'|'concatenation
*/
packageregexp
单元测试
在util目录下面创建文件util_test.go:
packageutil
import"testing"
funcTestAdd(t*testing.T){
ifAdd(12,13)!=24{
t.Log("testAdd1213fail")
t.Fail()
}
}
然后cd到util目录,执行gotest
,这样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。额外的解释用斜体。
54Chapter4:包 - strconv
strconv包提供了将字符串转换成基本数据类型,或者从基本数据类型转换为字符串
的功能。 - os
os包提供了与平台无关的操作系统功能接口。其设计是Unix形式的。 - sync
sync包提供了基本的同步原语,例如互斥锁。 - flag
flag包实现了命令行解析。参阅”命令行参数”在第92页。 - json
json包实现了编码与解码RFC4627[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有区别,不能进行指针运算。
varp*int;//p=nil
//*p=8;//这样会报错,因为p还没有分配内存
variint;
p=&i;//令p的值等于i的内存值
*p=8;//相当于修改i的值
print(p,&i,i)//看到,p和*i是一样的,i=8
- 在类型的前面加星号,表示定义一个该类型的指针,定义之后,没有为指针分配内存,指针为nil
2.内存分配
go中有两种方法可以分配内存:new和make
2.1new
new是声明一个变量,返回变量的指针。
varp1*int;//p=nil
p2:=new(int)
varp3int
print(p1,"\n")
print(*p2,"\n")
print(p3,"\n")
p1是一个指针,但是它还没有初始化
p2也是一个指针,它已经初始化了,初始化值为0
p3是一个变量,已经初始化,初始化值为0
2.2make
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来定义
typePersonstruct{
namestring
ageint
}
p1:=new(Person)
p1.name="kevin"
p1.age=23
println(p1.name)
println(p1.age)
定义结构的方法:
typePersonstruct{
namestring
ageint
}
func(p*Person)SetAge(vint)int{
p.age=v
returnv
}
funcmain(){
p1:=new(Person)
p1.SetAge(12)
println(p1.age)
}
五、接口
六、并发
1.goroutine
go中一个很重要的概念是goroutine,协程的英文是coroutine,第一个字母不同,即goroutine类似于协程,但是又有所不同,是go特殊的概念。
goroutine的特点:
- goroutine并行执行的,有着相同地址空间的函数。
- 轻量的
- 初始化的代价很低
一个并发的DEMO:
packagemain
import"time"
//注释
funcf(namestring){
fori:=0;i<10;i++{
println(name,i)
time.Sleep(1*1e9)
}
}
funcmain(){
gof("f1")
gof("f2")
time.Sleep(15*1e9)
}
类似于线程,然后启动的方法也比较方便,只需要在前面加一个go
的关键字。
1e9是一个内部的常量,是秒的意思
2.channel
goroutine之间通过channel来通讯,channel类似于队列,即Python中的Queue。
定义channel的时候,需要指定channel接受的类型,可以为int,string,和interface等。
varcchanint;//定义一个接受int类型的channel
c=make(chanint)
c<-1//向c中put一个对象
item:=<-c//从c中取一个对象
所以上面的并发程序可以改为:
funcf(namestring){
fori:=0;i<10;i++{
println(name,add(i))
time.Sleep(1*1e9)
}
c<-1//向c中put一个对象
}
funcmain(){
c=make(chanint)
gof("f1")
gof("f2")
<-c
<-c
}
在goroutine中,可以put,也可以get。
2.1缓冲
上面的channel是无缓冲的,也就是put完之后,goroutine就会阻塞,直到有goroutine取走。
定义有缓冲的channel:
ch:=make(chanint,1)
1就是缓冲的数量
获取的时候,也是阻塞的,可以使用非阻塞的方法:
v,ok:=<-c
如果有值,ok为true,否则为false
3.并发问题
尽管是叫并发,但是同一时刻,只有一个goroutine在执行,也就是占用CPU,类似Python的线程和协程。
可以通过runtime.GOMAXPROCS(n)
来设置同一个时刻运行的goroutine的数量,也可以修改环境变量GOMAXPROCS。
七、通讯
1.文件
读取文件
import"os"
funcmain(){
buf:=make([]byte,1024)
f,_:=os.Open("./test.data")
deferf.Close()
for{
n,_:=f.Read(buf)
ifn==0{
break
}
os.stdout.write(buf[0:n])//必须使用write,如果使用println,会输出切片的内存地址
}
}
通过bufio读取文件
import"os"
import"bufio"
//注释
funcmain(){
buf:=make([]byte,1024)
f,_:=os.Open("/etc/passwd")
deferf.Close()
r:=bufio.NewReader(f)
w:=bufio.NewWriter(os.Stdout)
deferw.Flush()
for{
n,_:=r.Read(buf)
ifn==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"
//注释
funcmain(){
dnssec:=flag.Bool("dnssec",false,"RequestDNSSECrecords")
port:=flag.String("port","53","Setthequeryport")
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
packagemain
funcsum(numint)int{
sum:=0
fori:=1;i<=num;i++{
sum+=i
}
returnsum
}
funcmain(){
sum_:=0
forj:=0;j<1000000;j++{
sum_+=sum(1000)
}
println(sum_)
println(sum(1000))
}
编译:
gobuildsum.go
执行
time./sum
结果:
real0m0.464s
user0m0.460s
sys0m0.001s
2.C
sum.c
#include<stdio.h>
longintsum(intnum){
longintsum=0;
inti=0;
for(i=1;i<=num;i++){
sum=sum+i;
};
returnsum;
}
intmain(){
inti;
longintsum_=0;
for(i=0;i<1000000;i++){
sum_+=sum(1000);
}
printf("%ld\n",sum(1000));
printf("%ld\n",sum_);
//printf(sum_);
return0;
};
编译:
gccsum.c-fPIC-shared-osum.so
执行
time./sumc
结果:
real0m2.874s
user0m2.856s
sys0m0.000s
3.Python
test_sum.py
defsum(num):
s=0
foriinxrange(1,num+1):
s+=i
returns
defmain():
sum_=0
foriinrange(1000000):
sum_+=sum(1000)
printsum_
if__name__=='__main__':
main()
执行
timepythontest_sum.py
结果
real0m35.146s
user0m34.814s
sys0m0.125s
-
在Python中调用C
test_sum_c.pyfromctypesimportcdll c_lib=cdll.LoadLibrary('./sum.so')
ifname=='main': c_lib.main()
执行
timepythontest_sum_c.py
结果
real0m2.899s
user0m2.874s
sys0m0.006s
5.比较
语言|go|C|Python|Python调用c
---|
用时:|0.464s|2.874s|35.146s|2.899s
- go竟然比c还快,而且快很多
- Python非常慢
博文为作者原创,未经允许,禁止转载。
本文内容总结:一、基础,1.HelloWorld程序,2.声明和赋值,3.控制结构,4.预定义的函数,5.array,slice和map,二、函数,三、包,四、进阶,1.指针,2.内存分配,2.3结构定义,五、接口,六、并发,1.goroutine,2.channel,3.并发问题,七、通讯,1.文件,2.命令行参数,3.网络,八、性能测试,1.go,2.C,3.Python,5.比较,
原文链接:https://www.cnblogs.com/Xjng/p/5913954.html