go语言学习笔记_第1页
go语言学习笔记_第2页
go语言学习笔记_第3页
go语言学习笔记_第4页
go语言学习笔记_第5页
已阅读5页,还剩66页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论