文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/

Go学习群:415660935

结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合。这些数据称为结构体的成员。Go语言的结构体和其他语言的类有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础的特性。这也体现了Go语言的简洁性。这章内容主要实现了Go语言的面向对象编程。

6.1.1 结构体定义

定义一个结构体相当于定义了一个新的类型。定义结构体的一般形式:

type 类型名称 struct {

成员列表

}

例如,定义一个类型Person

type Person struct {

name string

age  int

}

另外需要注意的是,结构体成员的输入顺序也有重要的意义。如果成员的顺序不同,意味着定义了不同的结构体类型。一个命令为S的结构体类型将不能再包含S类型的成员,因为一个聚合的值不能包含它自身(该限制同样适应于数组)但是S类型的结构体可以包含*S指针类型的成员,这可以创建递归的数据结构,例如链表和树结构。如果结构体没有任何成员的话,就是空结构体,写作struct{}.它的大小为0,也不包含任何信息。

6.1.2结构体初始化

结构体变量的的初始化可以按顺序直接初始化,也可以按照字段:值的方式初始化,这种方式的初始化对于字段的初始化的顺序可以是任意的。另外,也可以先定义结构体变量,随后赋值。

p1 := Person{"张三", 20} // 1 按顺序初始化

fmt.Println(p1)

p2 := Person{age: 18, name: "李四"} // 2采用字段:值的方式初始化,可以任意顺序

fmt.Println(p2)

p3 := Person{}// 3 未显示初始化,其成员默认被初始化为零值

fmt.Println(p3)

p3.name = "王五" //给每个成员赋值

p3.age = 19

fmt.Println(p3)

6.1.3 匿名组合实现继承

1 匿名组合的实现

只声明一个成员对应的数据类型而不指明成员的名字,这类成员就叫匿名成员。通过匿名成员实现的匿名组合,可以完成继承的功能。下面定义一个Student类,继承Person类的功能。匿名组合类型相当于以其类型名称(去掉包名部分)作为成员变量的名字。

type Person struct {

name string

age  int

}

type Student struct {

Person

major string

}

func main() {

s1 := Student{Person{"张三", 18}, "计算机应用"}

fmt.Println(s1)

fmt.Println(s1.Person)

fmt.Println("姓名:", s1.Person.name, "年龄:", s1.Person.age,

"专业:", s1.major)

fmt.Println("姓名:", s1.name, "年龄:", s1.age, "专业:", s1.major)

}

//打印结果:

{{张三 18} 计算机应用}

{张三 18}

姓名: 张三 年龄: 18 专业: 计算机应用

姓名: 张三 年龄: 18 专业: 计算机应用

2匿名组合的重名问题 

以下的示例中,Base1、Base2和Child名称相同。另外,匿名组合中Base2使用了指针类型。

type Base1 struct {

name string

}

type Base2 struct {

name string

}

type Child struct {

Base1

*Base2

name string

}

func main() {

c := Child{Base1{"base1"}, &Base2{"base2"}, "child"}

fmt.Println(c)

fmt.Println(c.name)

fmt.Println(c.Base1.name)

fmt.Println(c.Base2.name)

}

//打印结果:

{{base1} 0xc0420082c0 child}

child

base1

base2

6.1.4 匿名结构

func main() {

a := &struct {

Name string

Age  int

}{

Name: "zhangsan",

Age:  18,

}

fmt.Println(a)

b := struct{}{}

fmt.Println(b)

}

//打印结果:

&{zhangsan 18}

{}

6.1.5 结构体比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的。可比较的结构体类型和其它可比较的类型一样,可以用于map的key类型。

6.2 方法

方法就是与某个类型(也可以是内置类型)关联的函数。理所当然我们可以给一个具体的结构体类型添加方法,使得它更像面向对象中的类。

6.2.1 方法声明

在函数声明时,在其名字之前放上一个变量,即是一个方法。相当于为这种类型定义了一个独占的方法。方法可以被声明到任意类型,只要这个类型本身不是一个指针或接口。只有类型和指向它们的指针才可以是接收器。如果一个类型名本身是一个指针的话,是不允许出现在接收器中的。

方法声明的语法格式如下:

func  (变量名 类型)方法名称( [形参列表] ) [返回值列表]{

方法体

}

func  (变量名 *类型)方法名称( [形参列表] ) [返回值列表]{

方法体

}

接下来定义了一个新类型Integer,它和int没有本质不同,只是增加了个新方法Less()。

type Integer int

func (a Integer) Less(b Integer) bool {

return a < b

}

func main() {

var i Integer = 2

fmt.Println(i.Less(3)) //调用对象i的Less方法

}

在上面的例子中我们把Integer看着面向对象编程中的类,i是Integer类型的对象,Less()是对象的方法。如果需要修改对象i的值,这就需要使用指针作为接收器。

type Integer int

func (a *Integer) Add(b Integer) {

*a += b

}

func main() {

var i Integer = 2

i.Add(3)

fmt.Println(i) // 5

}

不管你的方法的接收者是指针类型还是非指针类型,都可以通过指针/非指针类型进行调用,编译器会自动做类型转换。在实际的项目开发中,一般会约定如果某个类型有一个指针作为接收器的方法,那么所有该类型的所有方法都必须有一个指针接收器。

在声明一个方法的接收者该是指针还是非指针类型时,你需要考虑两方面:

1>对象本身是否特别大,如果声明为非指针变量时,调用会产生一次拷贝;

2>如果使用指针类型,需要注意这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。

6.2.2 方法值和方法表达式

某个对象的方法称为方法值,某个类型的方法称为方法表达式。

type Integer int

func (a Integer) Add(b Integer) Integer {

return a + b

}

func main() {

var i Integer = 2

add := i.Add         //方法值:将对象i的方法作为值赋值给add变量

fmt.Println(add(3))

add2 := Integer.Add //方法表达式:将类型Integer的方法赋值给add2变量,add2的第一个参数作为接收器

fmt.Println(add2(i, 3))

}

方法表达式可以根据变量来决定调用同一类型的不同方法。

type Integer int

func (a Integer) Add(b Integer) Integer {

return a + b

}

func (a Integer) Sub(b Integer) Integer {

return a - b

}

func Operator(i, j Integer, sel bool) Integer {

var op func(a, b Integer) Integer

if sel {

op = Integer.Add

else {

op = Integer.Sub

}

return op(i, j)

}

func main() {

fmt.Println(Operator(10, 5, true))   //15

fmt.Println(Operator(10, 5, false))  //5

}

6.2.3 可见性

Go语言只有一种控制可见性的手段:首字母大写可见,小写字母则不可见。Go语言中符号的可访问性是包一级的而不是类型一级的。

package main

import (

"fmt"

)

type Person struct {

Name string

age  int

}

func (p Person) Show() {

fmt.Println("Name:", p.Name, "age:", p.age)

}

func (p *Person) setAge(age int) {

p.age = age

}

func main() {

p := Person{"张三", 18}

p.Show()     //包外也可见

p.age = 19   //包内可见,包外不可见

p.setAge(20) //包内可见,包外不可见

p.Show()

}

6.3 接口

6.3.1接口类型

接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起。很多面向对象的语言都有类似的接口概念,但是Go语言中接口类型的独特之处在于它是满足隐式实现的。另外,一个接口也可以嵌入其它接口,即接口组合。

6.3.2 实现接口的条件

一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。

// 1 定义Phone接口

type Phone interface {

Call()

}

//  定义DumbPhone类型,并实现了Phone接口

type DumbPhone struct { //非智能手机

Name string

}

func (dp DumbPhone) Call() {

fmt.Println("使用" + dp.Name + "非智能机打电话!")

}

// 2 定义Camera接口

type Camera interface {

TakePicture()

}

// 定义DigitalCamera类型,并Camera接口

type DigitalCamera struct { //数码相机

Name string

}

func (d DigitalCamera) TakePicture() {

fmt.Println("使用" + d.Name + "数码相机照相!")

}

// 3 定义PhoneCamera接口,嵌入了Phone和Camera接口

type PhoneCamera interface {

Phone

Camera

}

type CameraPhone interface {//与PhoneCamera接口等价,虽然顺序不同

TakePicture()

Call()

}

// 3 定义SmartPhone类型,并实现了PhoneCamera接口

type SmartPhone struct { // 智能手机

Name string

}

func (sp SmartPhone) Call() {

fmt.Println("使用" + sp.Name + "智能机手机打电话!")

}

func (sp SmartPhone) TakePicture() {

fmt.Println("使用" + sp.Name + "智能手机照相!")

}

6.3.3 接口赋值

1 将一个对象赋值给一个接口

func main() {

var p Phone

p = &DumbPhone{Name: "诺基亚"}

p.Call()

p = &SmartPhone{Name: "华为"}

p.Call()

var c Camera

c = &DigitalCamera{Name: "佳能"}

c.TakePicture()

c = &SmartPhone{Name: "华为"}

c.TakePicture()

var pc PhoneCamera

pc = &SmartPhone{Name: "华为"}

pc.Call()

pc.TakePicture()

}

2 将一个接口赋值给另一个接口

在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么这两个接口是等价的,可以相互赋值。 接口PhoneCamera与接口CameraPhone等价。

var pc PhoneCamera

pc = &SmartPhone{Name: "华为"}

pc.Call()

pc.TakePicture()

var cp CameraPhone

cp = pc          //将一个接口赋值给另一个等价接口

cp.Call()

cp.TakePicture()

接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。 接口Phone是接口PhoneCamera的子集,接口Camera也是接口PhoneCamera的子集。所以可以有如下的赋值:

var pc PhoneCamera

pc = &SmartPhone{Name: "华为"}

var p Phone

var c Camera

p = pc                //父集赋值给子集

p.Call()

c = pc                //父集赋值给子集

c.TakePicture()

6.3.4 接口查询

接口查询语法类似x.(T),x表示一个接口,T表示另一个接口类型。用来判断接口x操作的对象的是否实现了另一个接口类型。

var p Phone

p = &SmartPhone{Name: "华为"}

p.Call()

if pc, ok := p.(PhoneCamera); ok {

pc.TakePicture()

}

6.3.5 空接口

由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可
以指向任何对象的类型。一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。

6.3.6 类型查询

在Go语言中,还可以更加直截了当地询问接口指向的对象实例的类型。

//伪代码如下

var v1 interface{} = ...

switch v := v1.(type) {

case int: // 现在v的类型是int

case string: // 现在v的类型是string

...

Go语言 6 结构体、方法和接口的更多相关文章

  1. Go语言 - 结构体 | 方法

    自定义类型和类型别名 自定义类型 在Go语言中有一些基本的数据类型,如string.整型.浮点型.布尔等数据类型, Go语言中可以使用type关键字来定义自定义类型. 自定义类型是定义了一个全新的类型 ...

  2. go语言学习-结构体

    结构体 go语言中的结构体,是一种复合类型,有一组属性构成,这些属性被称为字段.结构体也是值类型,可以使用new来创建. 定义: type name struct { field1 type1 fie ...

  3. Go语言中结构体的使用-第2部分OOP

    1 概述 结构体的基本语法请参见:Go语言中结构体的使用-第1部分结构体.结构体除了是一个复合数据之外,还用来做面向对象编程.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性. ...

  4. C语言的结构体和C++结构体的区别

    关于C++中声明结构体中需要使用构造器创建实例对象的语法: <C++的结构体构造方法的基本概念:结构体的构造方法需要和结构体的名字相同,并且无返回值,也不要void关键字,这样的方法就是构造器的 ...

  5. C语言中结构体对齐问题

    C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...

  6. 将c语言的结构体定义变成对应的golang语言的结构体定义,并将golang语言结构体变量的指针传递给c语言,cast C struct to Go struct

    https://groups.google.com/forum/#!topic/golang-nuts/JkvR4dQy9t4 https://golang.org/misc/cgo/gmp/gmp. ...

  7. [日常] Go语言圣经--结构体,JSON习题

    Go语言圣经-结构体 1.结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体 2.通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行 ...

  8. Go语言中结构体的使用-第1部分结构体

    1 概述 结构体是由成员构成的复合类型.Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性.结构体成员,也可称之为成员变量,字段,属性.属性要满足唯一性.结构体的概念在软件工程上 ...

  9. C语言链表结构体(学习笔记)

    #include <stdio.h> #define LENTEST 100 // 采取逐步删除的方法求的素数 //先假设1-100都是素数,然后剔除2的倍数, //3的倍数,直到剔除所有 ...

随机推荐

  1. luogu 1344 追查坏牛奶(最小割)

    第一问求最小割. 第二问求割边最小的最小割. 我们直接求出第二问就可以求出第一问了. 对于求割边最小,如果我们可以把每条边都附加一个1的权值,那么求最小割是不是会优先选择1最少的边呢. 但是如果直接把 ...

  2. CodeChef KnightMov

    码死了...考试的时候基本上是写一会儿思考一会儿人生....考完了调了调...最后400行+....不应该这么长的....以后重写一下再补题解..... 也许这就是蒟蒻吧.jpg 安利cstdio博客 ...

  3. 【loj6041】「雅礼集训 2017 Day7」事情的相似度 后缀自动机+STL-set+启发式合并+离线+扫描线+树状数组

    题目描述 给你一个长度为 $n$ 的01串,$m$ 次询问,每次询问给出 $l$ .$r$ ,求从 $[l,r]$ 中选出两个不同的前缀的最长公共后缀长度的最大值. $n,m\le 10^5$ 题解 ...

  4. 【bzoj5157】[Tjoi2014]上升子序列 树状数组

    题目描述 求一个数列本质不同的至少含有两个元素的上升子序列数目模10^9+7的结果. 题解 树状数组 傻逼题,离散化后直接使用树状数组统计即可.由于要求本质不同,因此一个数要减去它前一次出现时的贡献( ...

  5. 【明哥报错簿】tomcat 安装时出现 Failed to install Tomcat7 service

    安装tomcat时提示 Failed to install Tomcat7 service 应该是卸载时直接删除目录导致的. Failed to install Tomcat7 service Che ...

  6. Django之CSS,JS静态文件的配置

    一. 专门创建一个目录放静态文件,即CSS,JS等. 1)先把jquery.min拿过来. 2)新建一个CSS文件放入样式 3)在login.html中引入.css文件 在login.html中引入. ...

  7. 【CodeChef-SPCLN】Cleaning the Space

    https://odzkskevi.qnssl.com/7dfb262544887eff6fb35bfb444759d6?v=1502084197 做法是类似于最大割之类的东西,把每个碎片按照按钮拆点 ...

  8. 【JQuery】数据

    一.前言        接着前一章的内容,继续本章的学习 二.内容 queue 显示或操作在匹配元素上执行的函数队列 .queue(queueName) 操作在匹配元素上执行的函数队列 .queue( ...

  9. Redis的List链表类型命令

    List是一个链表结构,主要功能是push.pop.获取一个范围的所有值等等,操作中key理解为链表的名字.list类型其实就是一个每个子元素都是string类型的双向链表.我们可以通过push.po ...

  10. Codeforces 576C. Points on Plane(构造)

    将点先按x轴排序,把矩形竖着划分成$10^3$个块,每个块内点按y轴排序,然后蛇形走位上去. 这样一个点到下一个点的横坐标最多跨越$10^3$,一共$10^6$个点,总共$10^9$,一个块内最多走$ ...