摘要

在上一篇文章中,我们聊了聊Golang中的一些基础的语法,如变量的定义、条件语句、循环语句等等。他们和其他语言很相似,我们只需要看一看它们之间的区别,就差不多可以掌握了,所以作者称它们为“基础语法”。在这篇文章中,我们将聊一聊Golang的一些语言特性,这也是Golang和其他语言差别比较大的地方。除此之外,还有一部分内容是关于Golang的并发,这一部分将在下一篇文章中介绍。

1 结构体

在Java中,我们已经体会过了面向对象的方便之处。我们只需要将现实中的模型抽象出来,就成为了一个类,类里面定义了描述这个类的一些属性。

而在Golang中,则没有对象这一说法,因为Golang是一个面向过程的语言。但是,我们又知道面向对象在开发中的便捷性,所以我们在Golang中有了结构体这一类型。

结构体是复合类型,当需要定义类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。
组成结构体类型的那些数据成为字段(fields)。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。

我们可以近似的认为,一个结构体就是一个类,结构体内部的字段,就是类的属性。

注意,在结构体中也遵循用大小写来设置公有或私有的规则。如果这个结构体名字的第一个字母是大写,则可以被其他包访问,否则,只能在包内进行访问。而结构体内的字段也一样,也是遵循一样的大小写确定可用性的规则。

1.1 定义

对于结构体,他的定义方式如下:

type 结构体名 struct {字段1 类型字段2 类型
}

1.2 声明

对于结构体的声明和初始化,有以下几种形式:

使用var关键字

var s T
s.a = 1
s.b = 2

注意,在使用了var关键字之后不需要初始化,这和其他的语言有些不同。Golang会自动分配内存空间,并将该内存空间设置为默认的值,我们只需要按需进行赋值即可。

使用new函数

type people struct {name stringage int
}func main() {ming := new(people)ming.name = "xiao ming"ming.age = 18
}

使用字面量

type people struct {name stringage int
}func main() {ming := &people{"xiao ming", 18}
}

1.3 区别

上面我们提到了几种结构体的声明的方法,但其实这几种是有些区别的。

先说结论,第一种使用var声明的方式,返回的是该实例的结构类型,而第二第三种,返回的是一个指向这个结构类型的一个指针,是地址。

注意,这一部分作者可以保证是观点是正确的。但是作者的解释其实有些问题。这是因为作者能力有限,还没开始研究Golang的源码,所以不能很好的解释“返回的是实例的结构类型”这一句话。在作者的理解中,返回类型有两种,一种是具体的数值,一种是指向这个数值的指针。

所以,对于第二第三种返回指针的声明形式,在我们需要修改他的值的时候,其实应该使用的方式是:

(*ming).name = "xiao wang"

也就是说,对于指针类型的数值,应该要先用*取值,然后再修改。

但是,在Golang中,可以省略这一步骤,直接使用ming.name = “xiao wang”。尽管如此,我们应该知道这一行为的原因,分清楚自己所操作的对象究竟是什么类型,掌握这点对下面方法这一章节至关重要。

2 方法

在上一节的内容中,我们也提到了面向对象的优势,而Golang又是一种面向过程的语言。在上一章节中,提到了用结构体实现了对象这一概念。在这一章中,提到的是对象对应的方法。

在Go语言中有一个概念,它和方法有着同样的名字,并且大体上意思相同,Go的 方法是作用在接收器(receiver)上的一个函数,接收器是某种类型的变量,因此方法是一种特殊类型的函数。

说白了,方法就是函数,只不过是一种比较特殊的函数。

我们都知道,在Golang中,定义一个函数是这样的:

func 函数名(args) 返回类型

而在此基础上,在func和函数名之间,加上接受者的类型,就可以定义一个方法。

type Vertex struct {X, Y float64
}func (v Vertex) Abs() float64 {return math.Sqrt(v.X*v.X + v.Y*v.Y)
}func main() {v := Vertex{3, 4}fmt.Println(v.Abs())
}

可以看到,我们定义了一个Vertex为接收者的方法。也就是说,这个方法,仅仅可以被Vertex的结构体数值调用。

注意,接受者有两种类型,即指针接收者和非指针接受者。

我们来看下面的代码:

type Vertex struct {X, Y float64
}func (v Vertex) test1(){v.X++;v.Y++;
}func (v *Vertex) test2(){v.X++;v.Y++;
}

在这里我们定义了两个方法,test1和test2,他们唯一的区别就是方法名前面的接收者不同,一个是指针类型的,一个是值类型的。

并且,执行这两个方法,也需要定义不同的结构体类型。

v1 := Vertex{1, 1}
v2 := &Vertex{1, 1}v1.test1()
v2.test2()fmt.Println(v1)
fmt.Println(v2)

执行之后我们可以查看结果:

{1 1}
&{2 2}

也就是说,只有指针接收者类型的方法,才能修改这个接收器的成员值,非指针接收者,方法修改的只是这个传入的指针接收者的一个拷贝。

那么为什么会这样,我们同样拿代码说话:

type Vertex struct {X, Y float64
}func (v Vertex) test1(){fmt.Printf("在方法中的v的地址为:%p\n", &v)v.X++;v.Y++;
}func main()  {v1 := Vertex{1, 1}fmt.Printf("自己定义的v1内存地址为:%p\n", &v1)v1.test1()
}

在上述的代码中,我定义了一个非指针类型接收者的方法,然后打印方法外的v1和方法内的v的内存地址,结果如下:

自己定义的v1内存地址为:0xc00000a0e0
在方法中的v的地址为:0xc00000a100

我们可以看出,这两个结构体数值的内存地址是不一样的。也就是说,就算我们修改了方法内的数值,对方法外的原变量也不能起到任何的作用,因为我们修改的只是一个结构体数值的拷贝,没有真正的修改的他本来的值。

但是,如果使用的是指针接收者,他们的内存地址就是一样的了,下面看代码:

type Vertex struct {X, Y float64
}func (v *Vertex) test2(){fmt.Printf("在方法中的v的地址为:%p\n", v)v.X++;v.Y++;
}func main()  {v1 := &Vertex{1, 1}fmt.Printf("自己定义的v1内存地址为:%p\n", v1)v1.test2()
}

执行之后的结果为:

自己定义的v1内存地址为:0xc00000a0e0
在方法中的v的地址为:0xc00000a0e0

所以我们可以知道,使用指针接收器是可以直接拿到原数据所在的内存地址,也就是说可以直接修改原来的数值。这也和Java中的对象调用方法更加相似;而对于非指针,它是拷贝原来的数据。至于使用哪一种,需要按照实际的业务来处理。

但是,如果是一个大对象,如果也采用拷贝的方式,将会耗费大量的内存,降低效率。

还有一点需要补充说明:不管是指针接收者还是非指针接收者,他在接受一个对象的时候,会自动将这个对象转换为这个方法所需要的类型。也就是说,如果我现在有一个非指针类型的对象,去调用一个指针接收者的方法,那么这个对象将会自动被取地址然后再被调用。

换句话说,方法的调用类型不重要,重要的是方法是怎么定义的。

3 接口

在聊接口怎么用之前,我们先来聊聊接口的作用。

在作者看来,接口是一种规范,一种约定。举个例子:一个商品只要是符合某种种类的约定,遵循某种种类的规范,那么就可以认为这个商品是属于这个种类的,他会具有这个种类应有的一切功能。这样做的目的是为了把生产这个商品的生产者和使用这个商品的消费者分开。用编程里面的术语来讲,我们可以把实现和调用解耦。

下面举个鸭子模型的例子,来自于知乎,可以说特别的形象生动了。注意,在这里先不研究语法,语法的问题我们后面会提到,你只需要跟随作者的思路去思考:

首先我们定义一个规范,也就是说定义一个接口:

type Duck interface {Quack()   // 鸭子叫DuckGo()  // 鸭子走
}

这个接口是鸭子的行为,我们认为,作为一只鸭子,它需要会叫,会走。然后我们再定义一只鸡:

type Chicken struct {
}

假设这只鸡特别厉害,它也会像鸭子那样叫,也会像鸭子那样走路,那么我们定义一下这只鸡的行为:

func (c Chicken) Quack() {fmt.Println("嘎嘎")
}func (c Chicken) DuckGo() {fmt.Println("大摇大摆的走")
}

注意,这里只是实现了 Duck 接口方法,并没有将鸡类型和鸭子接口显式绑定。这是一种非侵入式的设计。

然后我们让这只鸡,去叫,去像鸭子那样走路:

func main() {c := Chicken{}var d Duckd = cd.Quack()d.DuckGo()
}

执行之后我们可以得到结果:

嘎嘎
大摇大摆的走

也就是说,这只鸡,他能做到鸭子能做的所有事情,那么我们可以认为,这只鸡,他就是一个鸭子。

这里牵涉到了一个概念,任何类型的数据,它只要实现了一个接口中方法集,那么他就属于这个接口类型。所以,当我们在实现一个接口的时候,需要实现这个接口下的所有方法,否则编译将不能通过。

理解了接口是什么之后,我们再来聊聊语法,首先是如何定义一个接口:

type 接口名 interface {方法1(参数) 返回类型方法2(参数) 返回类型 ...
}

这一部分和结构体的定义很相似,但是里面的元素换成了函数,但是这个函数不需要func关键字。这里和Java中的接口相似,不需要访问修饰符,只需要函数名参数返回类型。

定义完接口之后,我们需要定义方法去实现这些接口。注意,这里新定义方法的方法名,参数,返回类型,必须和接口中所定义的完全一致。

其次,这里的方法中的接收者,就是调用这个方法的对象类型。换句话说,这个方法想要被哪类对象执行,接收者就是谁。

还有最重要的一点,如果要实现某个接口,必须要实现这个接口的全部方法。

在调用接口的时候,我们需要先声明这个接口类型的变量,如我们上面定义了一个Duck接口,就应该声明一个Duck类型的变量。

var d Duck

然后我们把实现了这个方法的接收器对象赋值给这个变量d:

d := Chicken{}

随后,我们就可以用这个变量d,是调用那些方法了。

Golang入门(3):一天学完GO的进阶语法相关推荐

  1. Golang入门(2):一天学完GO的基本语法

    摘要 在配置好环境之后,要研究的就是这个语言的语法了.在这篇文章中,作者希望可以简单的介绍一下Golang的各种语法,并与C和Java作一些简单的对比以加深记忆.因为这篇文章只是入门Golang的第二 ...

  2. Flask全套知识点从入门到精通,学完可直接做项目

    目录 Flask入门 运行方式 URL与函数的映射(动态路由) PostMan的使用 查询参数的获取 上传文件 其它参数 url_for 函数 响应-重定向 响应-响应内容 响应-自定义响应 Flas ...

  3. 刚开始接触Java,学完基础语法之后,应该学什么?

    学完Java寄出语法之后,应该学什么?这是很多初学者在入门学习Java的过程当中比较常见的一个问题,在这里我给大家分享一个系统的Java学习路线: 1.JavaSE:Java基础,既然是基础,那肯定是 ...

  4. 学完java基础语法之后用来练习的不依赖框架的小项目

    刚学完一门语言基础语法之后,一般都需要写一些小项目来检验我们的学习效果,将所学的基础语法串联起来,同时也熟悉一下用这门语言做项目的大概流程.但是此时学习的项目不能太复杂,因此此时才刚学完基础语法,太复 ...

  5. Python入门教程:很多人推荐学 Python 入 IT ,如果学完 Python 找不到工作怎么办...

    Python入门教程:很多人推荐学 Python 入 IT ,但是如果学完 Python 找不到工作怎么办,这也是很多人担心的问题. 很多人推荐通过学习 Python 入行 IT 一是因为 Pytho ...

  6. 学完python基础开始学爬虫_零基础入门Python爬虫不知道怎么学?这是入门的完整教程...

    这是一个适用于小白的Python爬虫免费教学课程,只有7节,让零基础的你初步了解爬虫,跟着课程内容能自己爬取资源.看着文章,打开电脑动手实践,平均45分钟就能学完一节,如果你愿意,今天内你就可以迈入爬 ...

  7. 3分钟入门python_3分钟学完Python,直接从入门到精通「史上最强干货库」

    作为帅气小编,我已经把python一些模块的甩在这儿了qwq,只要你拿到这些干货,包你玩转python,直接冲向"大佬"的段位,如果已经学了C或者C++或者说如果你需要你的一段关键 ...

  8. 想轻松入门Python编程,必须看这10个经典案例,学完就能找到工作

    一直以来,Python都是一门很简单的编程语言,其实无论你有没有基础,学起来都不难. 但,必须有方法,而最好的方法其实就是学+练,即:基本常识+这10经典案例. 而同时有着系统的Python基础知识点 ...

  9. python入门先学什么-所以学完 Python 入门课的孩子到底能干啥?

    原标题:所以学完 Python 入门课的孩子到底能干啥? 画个四色螺旋线 ▼ 绘制一个五彩橡皮筋球 ▼ 弄一朵同心花瓣 ▼ 螺旋花瓣 ▼ 随机万花筒 ▼ 还可以搞些表情包 ▼ 以上是学完Python ...

最新文章

  1. python得到一个excel的全部sheet标签值
  2. POJ 1671 第二类斯特林数
  3. 声呐图像数据集_MaskedFace-Net 口罩人脸基准数据集,13万+图像数据
  4. C# 依据KeyEventArgs与组合键字符串相互转换
  5. Kubernetes控制器--副本集ReplicaSet
  6. 12499元!“不知名”折叠手机2分钟售罄,网友:备货就10台...
  7. 记mysql 启动不了了的解决方法
  8. manjaro中文输入法已安装但切换不了解决方法
  9. OnScrollListener
  10. 数字图像处理复习记录(一)图像平滑、图像锐化、间隔检测
  11. 「堡垒之夜」母公司Epic元宇宙蓝图:颠覆Facebook的社交媒体,拆除苹果的高墙花园...
  12. Mysql创建锁芯_A级锁规格释疑
  13. Galaxy 平台下 LEfSe 安装与使用教程
  14. Latex 中文配置解析
  15. uniapp app真机测试
  16. 鸭梨山大,格力战双11有何苦衷?
  17. 关于一个简单函数方程问题的深入探究
  18. 【开源电机驱动】速度环控制
  19. 如何在家赚钱,盘点5个方法,让你足不出户也能挣钱
  20. 51单片机STC 89C52RC进阶 – 自制8x8点阵、点亮单颗LED、显示点阵汉字

热门文章

  1. 让DEM数据更有表现力
  2. [Python] Django+Apache 报 [wsgi:error]问题解决
  3. Kafka MirrorMaker2.0 (异地双活/跨数据中心容灾/跨集群容灾)
  4. Git生成patch及打patch到源代码
  5. Idea 版本控制冲突解决
  6. c++语言中for循环语句,C++ 循环
  7. HDOJ-1014 Uniform Generator
  8. Ansible结合跳板机控制远程服务器
  9. 马哥-Linux云计算架构班学习计划
  10. Posftix邮箱服务