首页 文章资讯内容详情

Golang学习笔记

2026-06-01 4 花语

本文内容纲要:

-一、基础 -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.py

fromctypesimportcdll 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