版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
go语言学习笔记(初级)
最近一直在学习g。语言,因此打算学习的时候能够记录
一下笔记。我这个人之前是从来没有记录笔记的习惯,
一直以来都是靠强大的记忆力去把一些要点记住。
读书的时候因为一直都是有一个很安静和很专心的环境,
因此很多事情都能记得很清楚,思考的很透彻。但是随着
年纪不断增加,也算是经历了很多的事情,加上工作有时会让人
特别烦闷,很难把心好好静下来去学习,去思考大自然的终极
奥秘,因此需要记录一些东西,这些东西一方面可以作为一种自我激励
的机制,另一方面,也算是自己成长的轨迹吧。
一.顺序编程
1.变量
g。语言变量定义的关键字是var。类型放在变量名后:
varvlint
varv2string
varv3[10]int〃数组
varv4[]int〃切片
varv5struct{〃结构体
fint
}
varv6*int〃指针
varv7map[string]int//map
varv8func(aint)int〃函数
每一行不需要以分号作为结尾。var
关键字还有一种是把多个变量的申明放在一起,
var(
vlstring
v2int
)
2.变量初始化
有人说,变量初始化有什么好提的,那么简单。是的,但是这里面确实
还是有一些值得注意的点。
varaint=10〃完整定义
vara=10〃自动推断是int型
a:二10〃自动推断是int型,申明并未该变量赋值
第三种初始化方式无疑是最简单的。
但是要注意的是,这里面第三种方式是和特别的,比如
varaint
a:=10
等价于
varaint
varaint
a=10
这时候就会报一个重复定义的错误。
3.变量赋值
变量赋值和我们通常的语言基本是一致的,但是多了多重赋值功能。
1,J=J,1
这就直接实现了两个变量的交换。
4.匿名变量
g。语言的函数是多返回值的,因此可能有些值并没有被用到,这时我
们就需要一个占位符去忽略这些返回值。
funcGetName()(firstName,lastName,nickNamestring){
return〃May〃,〃Chan〃,〃ChibiMarukoz/
}
nickName:=GetName()
5.定义常量
通过const关键字,可以用来定义常量。
constPifloat64=3.1415926
constzero=0.0〃自动推断类型
const(〃多定义
sizeint64=10
hello=-1
)
constu,vfloat32=0.0,3.0〃多重赋值
consta,b,c=1,2,"hello”〃自动推断类型
常量的定义也可以跟一个表达式,但是这个表达式应该是编译的时候
就可以求值的.
constmask=1<<3〃正常
constHome=os.GetEnv("HOME")〃错误,运行时才能确定
6.预定义常量
这里要讲一个很有意思的东西,叫做iota.
这个东西每一个const出现的位置被置为0,没出现一个iota出现,都自
增1,到写一个const出现的时候,又置为0.
const(
cl=iota//0
c2=iota//I
c3=iota//2
)
constx=iota//x==0(因为iota又被重设为0了)
consty=iota//y二二0(同上)
如果两个const赋值表达式是一样的,可以省略后面的赋值表达式.
const(
cl=iota//0
c2//I
c3//3
)
const(
a=1<<iota//a==1(iota在每个const开头被重设为0)
b//b==2
c//c==4
6.枚举
const(
Sunday=iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays//这个常量没有导出
)
大写字母开头的包外可见,小写字母开头的包外不可见.
7.类型
・整型
int8,uint8,int16,uint16,int32,uint32,int64,uint64,int,uint,uintptr
不同类型不能相互比较.
・浮点类型
float32,float64
涉及的一些运算比较简单,我们不做细讲.
・字符串类型
下面我们展示一个完整的go语言程序,也是以hello
world为主题,毕竟helloworld是一个万斤油的主题.
packagemain
importz/fmtzz〃引入依赖包
funcmain(){
fmt.Printin(/zhello,world!/z)
}
这基本上是一个最简单的程序了,但是对于我们的学习非常有用,用这个
模板可以写出非常好的东西出来.
字符串串本身非常简单,主要就是一些字符串操作,比如取特定位置的
字符等.
packagemain
import〃引入依赖包
funcmain(){
varstrstring="hello,world!〃
fmt.Println(str)
ch:=str[0]〃取某个特定位置的字符
fmt.Printf(〃%c\n〃,ch)
length:=len(str)
fmt.Printin(length)//len用来获取长度
str=〃你好,世界〃
ch=str[0]
fmt.Printf(〃%c\n〃,ch)
length=len(str)
fmt.Printin(length)
)
输出结果为:
hello,world!
h
12
9
13
这正好说明□和len都不能处理中文.
字符串连接也是用+.
字符串的遍历:
packagemain
import〃引入依赖包
funcmain(){
varstrstring="hello,world!〃
n:=len(str)
fori:=0;i<n;i++{
ch:=str[i]
fmt.Printf(〃%c\n〃,ch)
}
}
输出结果:
h
e
1
1
o
w
0
r
1
d
I
对于中文,结果是乱码,因为是一个字节一个字节输出的,但是默认是
UTF8编码,一个中文对应3个字节.
这里我们要提到一个range的关键字,它可以把字符串按键值对的方式
返回.
packagemain
import〃引入依赖包
funcmain(){
varstrstring=z/hello,world!你好,世界!〃
forch:=rangestr{
fmt.Printf(〃%c\n〃,ch)
}
)
输出结果为:
h
e
1
1
o
w
o
r
1
d
I
你
好
世
界
!
事实上,字符类型有两种,一种就是byte(uint8),另一种是rune.第一
种遍历字符串ch是byte,而第二种是rune.
・数组
数组这种类型是非常常见的
[32]byte
[2*N]struct{x,yint32}
[1000]*float64
[3][5]int
[2][2][2]float64
数组的遍历和字符串一样,这里不再重复.
数组是值类型,在赋值时会拷贝一份.
・数组切片
数组切片的概念比较复杂,它有点类似于C++中vector的概念,但又不
完全一样.
我们这里详细提几点.
1.切片的创建
切片有两种创建方式,一种是基于数组创建,另一种是用make
创建.
packagemain
import〃fmt〃〃引入依赖包
funcmain(){
//从数组创建
varmyArray[10]int=[10]int{1,2,3,4,5,6,7,8,9,10)
varsa[]int=myArray[5:]
fore:=rangesa{
fmt.Println(e)
)
fmt.Printin(len(sa))
fmt.Printin(cap(sa))
〃从make创建
varmySlice2[]int=make([]int,5,10)
fore:=rangemySlice2{
fmt.Println(e)
}
fmt.Printin(len(mySlice2))
fmt.Printin(cap(mySlice2))
〃赋值
varmySlice3[]int=[]int{1,2,3)
fore:=rangemySlice2{
fmt.Println(e)
slice是引用类型.
packagemain
import/zfmt,z〃引入依赖包
functest(a[10]int){
a[0]=10
}
funcprintArray(a[10]int){
fore:=rangea{
fmt.Println(e)
}
)
funcmain(){
varmyArray[10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
printArray(myArray)
test(myArray)
printArray(myArray)
)
输出结果:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
我们发现数组确实是按照值来传递.那么如果是slice呢,会发生
什么?
packagemain
import〃引入依赖包
functest(a[]int){
a[0]=10
}
funcprintArray(a[]int){
fore:=rangea{
fmt.Println(e)
}
)
funcmain(){
varmyArray[]int=[]int{1,2,3,4,5,6,7,8,9,10}
printArray(myArray)
test(myArray)
printArray(myArray)
输出结果:
1
2
3
4
5
6
7
8
9
10
10
2
3
4
5
6
7
8
9
10
确实是按照引用来传递的.
append函数可以往切片尾部增加元素.
mySlice=append(mySlice,1,2,3)
mySlice=append(mySlice,mySlice2...)
…表示把一个slice拆成元素来处理.
packagemain
import〃fnit〃〃引入依赖包
funcmain(){
varslicel[]int=make([]int,5,10)
varslice2[]int=[]int{1,2,3)
fmt.Println(slicel)
fmt.Printf(〃%p\n〃,slicel)
slicel=append(slicel,slice2...)
fmt.Println(slicel)
fmt.Printf(〃%p\n〃,slicel)
slicel=append(slicel,slice2...)
fmt.Printin(slicel)
fmt.Printf(〃%p\n〃,slicel)
}
输出结果:
[00000]
0xc820012190
[00000123]
0xc820012190
[00000123123]
0xc82005e000
在这里我们看到,slice的地址是所随着内内存的改变而变化的,因
此是需要仔细思考的.我个人不觉得
go语言这种特性有什么好的,反正也是奇葩极了.不过slice还提
供copy,也算是一些弥补吧.
map
go语言中,map使用非常简单.基本上看代码就会了.
packagemain
importz/fmt/z〃引入依赖包
〃定义一个Person的结构体
typePersonstruct{
namestring
ageint
)
funcmain(){
vardiemap[string]Person=make(map[string]Person,100)//初始化
map
dic[〃1234〃]=Person{name:z,lileiz/,age:100)
dic[〃12345〃]=Person{name:zzhanmeimei/z,age:20}
die[〃123456〃]=Person{name:/zdagong/z,age:30}
fmt.Println(dic)
〃删除dagong
delete(die,“123456")
fmt.Println(dic)
〃查找某个key
value,ok:=dic]〃123456〃]
ifok{
fmt.Printin(value)
}
value,ok=dic]〃1234〃]
ifok{
fmt.Printin(value)
}
fork,v:=rangedie{
fmt.Println(k+〃:〃+v.name)
输出结果为:
map[12345:{hanmeimei20}123456:{dagong30}1234:{lilei100}]
map[1234:{lilei100}12345:{hanmeimei20}]
{lilei100)
12345:hanmeimei
1234:lilei
map很简单吧.数据结构我们讲完了,接下来可以看看代码的程序控制
了.
8.程序控制
程序控制本人只提一些关键性的东西,不会啰嗦太多.
・switch语句
switch语句不需要在每个case地下写break,默认就是执行break.如果
要执行多个case,在case最后加入fallthrough.
条件表达式不限制为常量或者整数.单个case自然可以有多个结果可以
选.
packagemain
import〃引入依赖包
functest(aint){
switch{
casea<0:
fmt.Printin("hello")
caseEL——10:
fallthrough
casea>10&&a<100:
fmt.Printin("world")
default:
fmt.Println(/,nimazz)
)
}
funcmain(){
test(-1)
test(10)
test(100)
}
・循环
go语言的循环比较特别,它用一个for就把for和while的活都干了.
packagemain
import"fmt〃〃引入依赖包
funcmain(){
sum:=0
fori:=0;i<=100;i++{
sum+=i
}
fmt.Println(sum)
sum=0
i:=0
for(i<=100){
sum+=i
i++
)
fmt.Println(sum)
}
break还支持break到指定的label处.
forj:=0;j<5;j++{
fori:=0;i<10;i++{
ifi>5{
breakJLoop
}
fmt.Println(i)
}
)
JLoop:
//...
・函数
函数是一个非常重要的概念,也很简单.go的函数以func关键字定义,
支持不定参数和多返回值.函数名首字符的大小写是很有讲究的:
小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他
包使用。这个规则也适用于类型和变量的可见性。
packagemain
import〃引入依赖包
//...int是不定参数,实际上就是一个slice,a,b是多返回值
funcSumAndAverage(sample...int)(a,bfloat64){
a,b=0,0
ford:=rangesample{
a+=float64(d)
)
iflen(sample)==0{
b=0
}else{
b=a/float64(len(sample))
}
returna,b
}
funcmain(){
a,b:=SumAndAverage(1,2,3)
fmt.Println(a,b)
}
很简单吧.注意,如果是函数里面调了其他函数,那么这个sample怎
么传给其他喊函数呢?
sample...//...表示是拆成一个个元素传递
匿名函数的概念也很简单,只要看代码就会明白.
packagemain
import/zfmtz/〃引入依赖包
funcmain(){
varmyFuncfunc(...int)(float64,float64)=func(sample...int)(a,b
float64){
a,b=0,0
ford:=rangesample{
a+=float64(d)
}
iflen(sample)二二0{
b=0
}else{
b=a/float64(len(sample))
}
returna,b
}
a,b:=myFunc(1,2,3)
fmt.Println(a,b)
)
下面是关于闭包的概念.这个概念在许式伟的书中被诠释的非常好:
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不
在这个代码块内或者任何全局上下文中定义,
而是在定义代码块的环境中定义。
要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以
及它们引用的对象没有被释放)为自由变量提供绑定定的计算环境(作
用域)。
闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存
在.*
我们来看来两个闭包的例子.
packagemain
importz/fmtzz〃引入依赖包
functest(iint)func(){
returnfunc(){
fmt.Println(10+i)
fmt.Printf(〃%p\n〃,&i)
)
}
funcmain(){
a:=test(1);
b:=test(2)
a()
b()
)
输出结果:
11
0xc82000a288
12
0xc82000a2c0
我们从这个结果中发现,i的地址是会变的,因为是作为一个局部变量传
进去的.
packagemain
import/zfmtz/〃引入依赖包
functest(xint)func(int)int(
returnfunc(yint)int{
fmt.Printf(/z%p\n/z,&x)
returnx+y
funcmain(){
a:=test(1);
fmt.Printin(a(10))
fmt.Printin(a(20))
}
输出结果:
0xc82000a288
11
0xc82000a288
21
因为X只传入了一次,因此没有改变.
packagemain
import(
〃fmt〃
)
funcmain(){
varjint=5
a:=func()(func()){
variint=10
returnfunc(){
fmt.Printf(/zi,j:%d,%d\n〃,i,j)
}
)()
a()
j*=2
a()
此时输出:
i,j:10,5
i,j:10,10
二.面向对象编程
这里我们先提值语义和引用语义的概念.
b=a
b.Mofify()
如果b改变,a不发生改变,就是值语义.如果b改变,a也发生改变,就
是引用语义.
g。语言大多数类型都是值语义,比如:
基本类型:byte,int,float32,float64,string
复合类型:struct,array,pointer
也有引用语义的,比如:
slice,map,channel,interface.
这是我们要牢记的.
我们的笔记整体式按照许式伟的书来安排,但是由于许的书提纲性很
强,内容上不是很详细,基本上会重新整理补充一些东西进去.
・结构体
结构体是用struct来申明的,不多废话,直接上代码.
packagemain
import(
〃fmt〃
)
〃申明一个结构体
typePersonstruct{
Namestring
Ageint
}
funcmain(){
〃结构体的初始化方式
//I.直接赋值
varpPerson
p.Name="dingding”
p.Age=10
fmt.Println(p)
〃2,顺序赋值
pl:=Person{"dingding”,10}
fmt.Println(pl)
//3.keyvalue赋值
p2:=Person{Name:z,dingdingz/,Age:10}
fmt.Println(p2)
〃4.指针赋值
p3:=&Person{Name:z/dingding/z,Age:10}
fmt.Println(p3)
p4:=new(Person)
fmt.Println(p4)
fmt.Println(,z--------------------------〃)
a:=p
a.Name="dongdong”
b:=p3
b.Name="dongdong”
fmt.Println(p)
fmt.Println(p3)
)
输出结果:
{dingding10)
(dingding10}
{dingding10)
&{dingding10}
&{0}
(dingding10}
&{dongdong10}
这说明,struct确实是值语义.
下面讨论一下结构体的组合问题.这点许的书中并没有过多涉及,但是
还是很有必要的,因为在实际场合中用的会很多.
packagemain
import(
〃fmt〃
〃申明一个结构体
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一个结构体
typeEmployeestruct{
PersonHuman//匿名字段Human
Specialitystring
Phonestring//雇员的phone字段
)
funcmain(){
e:=Employee{
Person:Human{
Name:〃dingding”,
Age:11,
Phone:〃6666666”,
},
Speciality:/zaaa/z,
Phone:〃77777777〃,
}
fmt.Println(e.Phone)
}
这段代码看上去非常ok,但是如果我们稍微改一下代码呢?比如把
Person改成匿名结构,会有些好玩的现象.
packagemain
import(
〃fmt〃
)
〃申明一个结构体
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一个结构体
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
//Phonestring//雇员的phone字段
}
funcmain(){
e:=Employee{
Human{"dingding”,11,”6666666〃},
〃〃
〃Phone:"77777777”,
)
fmt.Printin(e.Phone)
}
此时输出的是6666666,因为相当于它把Human的字段Phone继承下
来了.
如果Employee里也定义Phone字段呢?
packagemain
import(
〃fmt〃
)
〃申明一个结构体
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一个结构体
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
Phonestring//雇员的phone字段
)
funcmain(){
e:=Employee{
Human{/zdingding/z,11,“6666666〃},
〃〃
“77777777”,
}
fmt.Printin(e.Phone)
}
此时输出的时77777777,因为相当于是覆盖.那么怎么输出6666666
呢?
packagemain
import(
〃fmt〃
)
〃申明一个结构体
typeHumanstruct{
Namestring
Ageint
Phonestring
}
//再申明一个结构体
typeEmployeestruct{
Human//匿名字段Human
Specialitystring
Phonestring//雇员的phone字段
}
funcmain(){
e:=Employee{
Human{"dingding”,11,”6666666〃},
〃〃
“77777777”,
)
fmt.Printin(e.Phone)
fmt.Printin(e.Human.Phone)
)
输出结果:
77777777
6666666
所以,匿名结构体的组合相当于有继承的功能.
・为类型添加方法
这个概念和java或者是C++非常不一样,它的理念是把似乎是把方法
绑定到特定类型上去.
这个概念已经不仅仅是对象的概念了,事实上,
我也不知道google这帮人脑子是怎么想的,这种搓劣的复古风格,
也是让我打开眼界,我个人觉得,g。虽然仗着google的名气,似乎显得
很厉害,但是,
比起java和C++,简直就是个玩具,说的不好听一点,
比起scala这样的一出生就是个大胖子,go更像是个缺胳膊少腿的畸形
儿.
好了,不说了,直接上代码.
packagemain
import(
〃fmt〃
)
//go绑定方法必须是本包内的,int不是main包内定义的.
〃因此需要type一下,Integer就是本包内定义的类型
typeIntegerint
〃为int绑定一个Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
funcmain(){
varaInteger=10
a.PrintlnO
}
结果输出10,如果是如下呢?
packagemain
import(
〃fmt〃
)
//go绑定方法必须是本包内的,int不是main包内定义的.
〃因此需要type一下,Integer就是本包内定义的类型
typeIntegerint
〃为int绑定〜个Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
}
funcmain(){
a:=10
a.Printin()
}
输出结果:
#command-line-arguments
./main,go:18:a.Printinundefined(typeinthasnofieldormethod
Printin)
因为a:=10,go会把a推断为int,但int并没绑上Printin方法.
注意:
〃为int绑定一个Print方法
func(iInteger)PrintlnO{
fmt.Println(i)
)
这里的i并不是引用类型,因此对i的修改不会反应到a上去.
packagemain
import(
〃fmt〃
)
//go绑定方法必须是本包内的,int不是main包内定义的.
〃因此需要type一下,Integer就是本包内定义的类型
typeIntegerint
〃为int绑定一个Print方法
func(iInteger)Add(){
i+=2
}
funcmain(){
varaInteger=10
a.Add()
fmt.Println(a)
输出10.
如果我们用引用呢?
packagemain
import(
〃fmt〃
)
//go绑定方法必须是本包内的,int不是main包内定义的.
〃因此需要type一下,Integer就是本包内定义的类型
typeIntegerint
〃为int绑定〜个Print方法
func(this*Integer)Add(){
*this+=2
)
funcmain(){
varaInteger=10
a.Add()
fmt.Println(a)
)
这时输出12.我们发现,这个this就像是我们C++里面的this指针一样.
不过也傻13复古的多.
我们在看一个例子,
packagemain
import(
〃fmt〃
//go绑定方法必须是本包内的,int不是main包内定义的.
〃因此需要type一下,Integer就是本包内定义的类型
typeIntegerint
〃为int绑定一个Print方法
func(this*Integer)Add(){
fmt.Printin(this)
*this+=2
}
funcmain(){
varbInteger=10
vara*Integer=&b
a.Add()
fmt.Println(a)
)
输出结果:
0xc82000a288
0xc82000a288
我们发现,
〃为int绑定一个Print方法
func(this^Integer)Add(){
fmt.Printin(this)
*this+=2
)
该方法用指针调用和用值去调用,效果是一样的.this都是指向原来对
象的指针而已.
下面这个例子来自于许的书中,
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
)
funcmain(){
varaInteger=1
varbLessAdder=a
}
输出:
#command-1ine-arguments
./main,go:20:cannotusea(typeInteger)astypeLessAdderinassignment:
IntegerdoesnotimplementLessAdder(Addmethodhaspointerreceiver)
这个例子似乎有点奇怪,为什么呢?
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a*Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
}
typeLessinterface{
Less(bInteger)bool
}
typeAdderinterface{
Add(bInteger)
}
funcmain(){
varaInteger=1
varbAdder=a
b.Add(10)
}
我们可以看得更清楚:
./main,go:28:cannotusea(typeInteger)astypeAdderinassignment:
IntegerdoesnotimplementAdder(Addmethodhaspointerreceiver)
但如果是:
packagemain
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
typeLessAdderinterface{
Less(bInteger)bool
Add(bInteger)
)
typeLessinterface{
Less(bInteger)bool
}
typeAdderinterface{
Add(bInteger)
}
funcmain(){
varaInteger=1
varbInteger=a
b.Add(10)
}
就没有任何问题.对比起来,就是这两行代码:
varbInteger=a
varbAdder=a
我们接下去会娓娓道来其中的奥妙.
packagemain
import(
〃fmt〃
)
〃定义对象People、Teacher和Student
typePeoplestruct{
Namestring
)
typeTeacherstruct{
People
Departmentstring
}
typeStudentstruct{
People
Schoolstring
}
〃对象方法实现
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\n\p.Name)
}
func(t*Teacher)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
func(s*Student)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
}
func(s*Student)Study(){
fmt.Printf(/zrmlearningGolangin%s.\n/z,s.School)
}
〃定义接口Speaker和Learner
typeSpeakerinterface{
SayHi()
}
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化对象
people:=People{〃张三〃}
//teacher:=&Teacher{People{〃关B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},"YaleUniversity”}
varspeakerSpeaker〃定义Speaker接口类型的变量
speaker=people
speaker.SayHi()
}
这时就会出现上面我们提到的错误.尽管如果我们这么去调用:
packagemain
import(
〃fmt〃
)
〃定义对象People>Teacher和Student
typePeoplestruct{
Namestring
)
typeTeacherstruct{
People
Departmentstring
)
typeStudentstruct{
People
Schoolstring
)
//对象方法实现
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\nz/,p.Name)
}
func(t*Teacher)SayHi(){
fmt.Printf(/zHi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
)
func(s*Student)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
func(s*Student)Study(){
fmt.Printf(/?rmlearningGolangin%s・\n〃,s.School)
}
〃定义接口Speaker和Learner
typeSpeakerinterface{
SayHi()
)
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化对象
people:=People{〃张三〃}
//teacher:=&Teacher{People{〃关B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},“YaleUniversity7/)
//varspeackerSpeaker〃定义Speaker接口类型的变量
//speacker=people
people.SayHi()
)
或者
packagemain
import(
〃fmt〃
〃定义对象People、Teacher和Student
typePeoplestruct{
Namestring
}
typeTeacherstruct{
People
Departmentstring
}
typeStudentstruct{
People
Schoolstring
}
〃对象方法实现
func(p*People)SayHi(){
fmt.Printf(Z/Hi,I'm%s.Nicetomeetyou!\n/z,p.Name)
}
func(t^Teacher)SayHi(){
fmt.Printf(Z/Hi,mynameis%s.I'mworkingin%s.\n〃,t.Name,
t.Department)
}
func(s^Student)SayHi()
fmt.Printf(zzHi,mynameis%s.rmstudyingin%s.\n/z,s.Name,s.School)
func(s*Student)Study(){
fmt.Printf(/?rmlearningGolangin%s・\n〃,s.School)
〃定义接口Speaker和Learner
typeSpeakerinterface{
SayHi()
}
typeLearnerinterface{
SayHi()
Study()
}
funcmain(){
〃初始化对象
people:=People{〃张三〃}
//teacher:=&Teacher{People{〃关B智〃},“ComputerScience”}
//student:=&Student{People{〃李明〃},“YaleUniversity”}
varspeackerSpeaker〃定义Speaker接口类型的变量
speacker=fepeople
speacker.SayHi()
}
这样就都没有任何问题,这就是说什么呢?这说明对于对象的方法,无
论接受者是对象还是对象指针,都没
任何问题.但是如果是借口,如果接口中存在某个方法,绑定的接收者是
对象指针,那么这个接口
也只能被该对象指针赋值.如此奇葩的设计,我只能说,g。的设计者真
是个脑残.
・继承
好了,下面正式讲继承的语法,话说那玩意儿的真的算不上继承,比
C++的继承真的时不知道low到哪里去了.但是我也不知道为啥这是go
爱好者们最爱标榜的东西,
有时我想想,程序员也真是单纯的人,一点点的蛊惑,就会让他们激动
不已,
感觉就要去参加革命了似的.
g。的继承非常简陋,就是一个匿名结构组合的问题.不废话,直接上代
码.
packagemain
import(
〃fmt〃
〃基类
typeBasestruct{
Namestring
〃绑定Say方法
func(b*Base)Say()
fmt.Println(b.Name)
〃绑定ok方法
func(b*Base)0k()
fmt.Println(/zokz/)
//Foo有个匿名结构Base
typeFoostruct{
Base
Namestring
)
//重写Say方法
func(f*Foo)Say(){
f.Base.Say()
fmt.Printin(f.Name)
}
funcmain(){
varf*Foo=&F00{Base{"father"},〃sun〃}
f.0k()
f.Say()
)
输出结果:
ok
father
sun
ok,下面我们看看多继承二义性的问题.
packagemain
import(
〃fmt〃
//father
typeFatherstruct{
)
func(f*Father)Say(){
fmt.Printin("father")
}
//mother
typeMotherstruct{
}
func(f"Mother)Say(){
fmt.Printin("mother")
}
//sun
typeSunstruct{
Father
Mother
}
funcmain(){
vars*Sun=new(Sun)
s.Say()
}
输出:
#command-1ine-arguments
./main,go:32:ambiguousselectors.Say
果然展现了二义性.消歧义方式也是土的掉渣:
packagemain
import(
〃fmt〃
)
//father
typeFatherstruct{
}
func(f*Father)Say(){
fmt.Printin("father")
}
//mother
typeMotherstruct{
)
func(f*Mother)Say(){
fmt.Printin("mother")
}
//sun
typeSunstruct{
Father
Mother
}
funcmain(){
vars*Sun=new(Sun)
s.Father.Say()
s.Mother.Say()
}
我也是醉了.
此外,我们也会看到还有一种用法,
packagemain
import(
)
//基类
typeBasestruct{
Namestring
}
//绑定Say方法
func(b*Base)Say(){
fmt.Printin(b.Name)
}
//绑定ok方法
func(b*Base)0k(){
fmt.Println(〃ok〃)
}
//Foo有个匿名结构Base
typeFoostruct{
*Base〃base是个指针
Namestring
〃重写Say方法
func(f*Foo)Say(){
f.Base.Say()
fmt.Println(f.Name)
}
funcmain(){
varf*Foo=&F00{&Base{"father"},〃sun〃}
f.Ok()
f.Say()
}
这里Foo的Base是一个指针类型,因此我们传入的也必须是个指针,
我们可以看到这种用法.
.接口
这个我不想吐槽什么,前面已经吐槽过了,但是这种设计,确实也是诡
异之极.
g。的借口是非侵入式的,只要实现了接口定义的方法,就等于实现了
该接口.换句话说,接口的实现和定义式可以分离
不相关的.
接口的例子还是之前那个:
packagemain
import(
〃fmt〃
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
)
func(a^Integer)Add(bInteger){
*a+=b
}
//定义接口
typeLessAdderinterface{
Less(bInteger)bool〃函数声明
Add(bInteger)〃函数声明
}
funcmain(){
varaInteger=10
varbLessAdder二&a〃道理我们前面提到过了,Add接收者是个对象指针
fmt.Printin(b.Less(5))
b.Add(20)
fmt.Println(a)
}
输出:
false
30
只要两个接口拥
有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相
互赋值。
接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B
的方法列表的子集,
那么接口B可以赋值给接口Ao
几个接口也可以组合出一个接口.
typeReadWriterinterface{
Reader
Writer
}
等价于:
typeReadWriterinterface{
Read(p[]byte)(nint,errerror)
Write(p[]byte)(nint,errerror)
}
・Any类型
由于Go语言中任何对象实例都满足接口interface。,所以interface。
看起来是任何对象的Any类型
varvlinterface{}=1
varv2interface{}=〃abc〃
varv3interface{}=&v2
varv4interface{}=struct{Xint}{1}
varv5interface{}=fcstruct{Xint}{1}
funcPrintf(fmtstring,args...interface{})
funcPrintin(args...interface{})
・接口转换和类型查询
接口转换
packagemain
import(
〃fmt〃
)
typeIntegerint
func(aInteger)Less(bInteger)bool{
returna<b
}
func(a*Integer)Add(bInteger){
*a+二b
)
〃定义接口
typeLessAdderinterface{
Less(bInteger)bool〃函数声明
Add(bInteger)//函数声明
)
〃定义接口
typeAdderinterface{
Add(bInteger)〃函数声明
}
funcmain(){
varaInteger=10
varbLessAdder=&a〃道理我们前面提到过了,Add接收者是个对象指针
ifc,ok=b.(Adder);ok{
c.Add(10)
fmt.Println(a)
//fmt.Println(c.Less(100))//报错,c.Lessundefined(typeAdderhas
nofieldormethodLess)
)
)
类型查询
packagemain
import(
"fmt"
)
funcmain(){
i〃〃
b:=a
varainterface{}=b
switcha.(type){
caseint:
fmt.Printin(z/intz,)
casefloat32:
fmt.Printin(/zfloat32z/)
caseint32:
fmt.Printin(,zint32z/)
casefloat64:
fmt.Printin(zzfloat64/z)
casebool:
fmt.Printin(〃bool〃)
casestring:
fmt.Printin("string")
default:
fmt.Println(/Zok/Z)
・补充
我们补充一些defer-panic-recover的案列.
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
}
funcmain(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
}
)()
f()
fmt.Println(〃b〃)
)
输出结果:
a
b
如果我们在f中panic呢?这会发生什么呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
)
)()
f()
fmt.Println(/Zb/Z)
}
输出:
a
error!
我们发现,b没有输出.panic会抛出一个异常,由recover去捕获.f抛出
异常后,事实上,
main剩下的部分都不会执行,但是因为我们defer了,
defer是一定会执行的,因此我们在defer中捕获了panic抛出的
异常.这就是为什么b没有输出.似乎和trycatch很像.如果我们希望
把b也输出,但也能捕获异常呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
func(){
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
}
)()
f()
)()
fmt.Println(〃b〃)
)
输出结果:
a
error!
b
如果是这样呢?
packagemain
import(
〃fmt〃
)
funcf(){
fmt.Println(〃a〃)
panic("error!〃)
}
funcmain(){
func(){
f()
deferfunc(){
iferr:=recover();err!=nil{
fmt.Println(err)
)
}()
)()
fmt.Println(〃b〃)
}
此时,在定义defer之前,f已经panic了,没有recover去捕获,这个
panic会一直抛出.
直到被go虚拟机捕获.
输出:
a
panic:error!
goroutine1[running]:
main,f()
/Users/fengyan/code/go/test/main,go:9+0xlle
main.main,fund()
/Users/fengyan/code/go/test/main,go:14+0x18
main,main()
/Users/fe
go里面有个东西很好玩,nil类似于java的null,那么java如果对null
调用方法,会直接抛出一个空指针异常.
那么go会怎么样呢?
packagemain
funcmain(){
nil.Printin(〃a〃)
}
输出结果:
#command-line-arguments
./main,go:4:useofuntypednil
看来还是不行的.
所以调用前我们还是要进行空的判断.
三.go并发编程
并发不是
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- GB/T 23356-2024卷烟烟气气相中一氧化碳的测定非散射红外法
- 小学数学计算教学的实践与反思
- 城乡给排水工程建设安全生产责任保险事故预防服务指南征
- 《安全检测技术》练习题集
- 质量工程师理论知识题库及答案(1-342道)
- 【初中物理】2024-2025学年苏科版物理八年级上学期知识清单
- 新疆镀锌厂钢材热镀锌项目环境影响报告书
- 深圳-PEP-2024年10版小学4年级下册英语第4单元寒假试卷
- 2024年01版小学四年级英语第5单元期末试卷
- 小企业会计准则建筑业的账务处理-记账实操
- GB 30603-2014食品安全国家标准食品添加剂乙酸钠
- 2023届台州一模考试试卷
- 大学物理 电阻电路等效变换
- 国企职务犯罪预防课件
- 介入手术跟台课件
- 医学高级职称评审答辩报告PPT模板
- 《王戎不取道旁李》课堂作业设计
- 签收回执表(最新版)
- 中国气血健康白皮书
- 统编版语文5年级(上)期中单元复习课件
- 驾校大学招生策划书
评论
0/150
提交评论