Go语言不是一门面向对象的语言,没有对象和继承,也没有面向对象的多态、重写相关特性。

Go所拥有的是数据结构,它可以关联方法。Go也支持简单但高效的组合(Composition),请搜索面向对象和组合。

虽然Go不支持面向对象,但Go通过定义数据结构的方式,也能实现与Class相似的功能。

一个简单的例子,定义一个Animal数据结构:

type Animal struct {
name string
speak string
}

这就像是定义了一个class,有自己的属性。

在稍后,将会介绍如何向这个数据结构中添加方法,就像为类定义方法一样。不过现在,先简单介绍下数据结构。

数据结构的定义和初始化

除了int、string等内置的数据类型,我们可以定义structure来自定义数据类型。

创建数据结构最简单的方式:

bm_horse := Animal{
name:"baima",
speak:"neigh",
}

注意,上面最后一个逗号","不能省略,Go会报错,这个逗号有助于我们去扩展这个结构,所以习惯后,这是一个很好的特性。

上面bm_horse := Animal{}中,Animal就像是一个类,这个声明和赋值的操作就像创建了一个Animal类的实例,也就是对象,其中对象名为bm_horse,它是这个实例的唯一标识符。这个对象具有属性name和speak,它们是每个对象所拥有的key,且它们都有自己的值。从面向对象的角度上考虑,这其实很容易理解。

还可以根据Animal数据结构再创建另外一个实例:

hm_horse := Animal{
name:"heima",
speak:"neigh",
}

bm_horsehm_horse都是Animal的实例,根据Animal数据结构创建而来,这两个实例都拥有自己的数据结构。如下图:

从另一种角度上看,bm_horse这个名称其实是这个数据结构的一个引用。再进一步考虑,其实面向对象的类和对象也是一种数据结构,每一个对象的名称(即bm_horse)都是对这种数据结构的引用。关于这一点,在后面介绍指针的时候将非常有助于理解。

以下是两外两种有效的数据结构定义方式:

// 定义空数据结构
bm_horse := Animal{} // 或者,先定义一部分,再赋值
bm_horse := Animal {name:"baima"}
bm_horse.speak = "neigh"

此外,还可以省略数据结构中的key部分(也就是属性的名称)直接为数据结构中的属性赋值,只不过这时赋的值必须和key的顺序对应。

bm_horse := Animal{"baima","neigh"}

在数据结构的属性数量较少的时候,这种赋值方式也是不错的,但属性数量多了,不建议如此赋值,因为很容易混乱。

访问数据结构的属性

要访问一个数据结构中的属性,如下:

package main

import ("fmt")

func main(){

    type Animal struct {
name string
speak string
} bm_horse := Animal{"baima","neigh"}
fmt.Println("name:",bm_horse.name)
fmt.Println("speak:",bm_horse.speak)
}

前面说过,Animal是一个数据结构的模板(就像类一样),不是实例,bm_horse才是具体的实例,有自己的数据结构,所以,要访问自己数据结构中的数据,可以通过自己的名称来访问自己的属性:

bm_horse.name
bm_horse.speak

指针

bm_horse := Animal{}表示返回一个数据结构给bm_horse,bm_horse指向这个数据结构,也可以说bm_horse是这个数据结构的引用。

除此,还有另一种赋值方式,比较下两种赋值方式:

bm_horse := Animal{"baima","neigh"}
ref_bm_horse := &Animal{"baima","neigh"}

这两种赋值方式,有何不同?

:=操作符都声明左边的变量,并赋值变量。赋值的内容基本神似:

  • 第一种将整个数据结构赋值给变量bm_horsebm_horse从此变成Animal的实例;
  • 第二种使用了一个特殊符号&在数据结构前面,它表示返回这个数据结构的引用,也就是这个数据结构的地址,所以ref_bm_horse也指向这个数据结构。

bm_horseref_bm_horse都指向这个数据结构,有什么区别?

实际上,赋值给bm_horse的是Animal实例的地址,赋值给ref_bm_horse是一个中间的指针,这个指针里保存了Animal实例的地址。它们的关系相当于:

bm_horse -> Animal{}
ref_bm_horse -> Pointer -> Animal{}

其中Pointer在内存中占用一个长度为一个机器字长的单独数据块,64位机器上一个机器字长是8字节,所以赋值给ref_bm_horse的这个8字节长度的指针地址,这个指针地址再指向Animal{},而bm_horse则是直接指向Animal{}

如果还不明白,我打算用perl语言的语法来解释它们的区别,因为C和Go的指针太过"晦涩"。

perl中的引用

在Perl中,一个hash结构使用%符号来表示,例如:

%Animal = (
name => "baima",
speak => "neigh",
);

这里的"Animal"表示的是这个hash结构的名称,然后通过%+NAME的方式来引用这个hash数据结构。其实hash结构的名称"Animal"就是这个hash结构的一个引用,表示指向这个hash结构,只不过这个Animal是创建hash结构是就指定好的已命名的引用。

perl中还支持显式地创建一个引用。例如:

$ref_myhash = \%Animal;

%Animal表示的是hash数据结构,加上\表示这个数据结构的一个引用,这个引用指向这个hash数据结构。perl中的引用是一个变量,所以使用$ref_myhash表示。

也就是说,hash结构的名称Animal$ref_myhash是完全等价的,都是hash结构的引用,也就是指向这个数据结构,也就是指针。所以,%Animal能表示取hash结构的属性,%$ref_myhash也能表示取hash结构的属性,这种从引用取回hash数据结构的方式称为"解除引用"。

另外,$ref_myhash是一个变量类型,而%Animal是一个hash类型。

引用变量可以赋值给另一个引用变量,这样两个引用都将指向同一个数据结构:

$ref_myhash1 = $ref_myhash;

现在,$ref_myhash$ref_myhash1Animal都指向同一个数据结构。

Go中的指针:引用

总结下上面perl相关的代码:

%Animal = (
name => "baima",
speak => "neigh",
); $ref_myhash = \%Animal;
$ref_myhash1 = $ref_myhash;

%Animal是hash结构,Animal$ref_myhash$ref_myhash1都是这个hash结构的引用。

回到Go语言的数据结构:

bm_horse :=  Animal{}
hm_horse := &Animal{}

这里的Animal{}是一个数据结构,相当于perl中的hash数据结构:

(
name => "baima",
speak => "neigh",
)

bm_horse是数据结构的直接赋值对象,它直接表示数据结构,所以它等价于前面perl中的%Animal。而hm_horseAnimal{}数据结构的引用,它等价于perl中的Animal$ref_myhash$ref_myhash1

之所以Go中的指针不好理解,就是因为数据结构bm_horse和引用hm_horse都没有任何额外的标注,看上去都像是一种变量。但其实它们是两种不同的数据类型:一种是数据结构,一种是引用。

Go中的星号"*"

星号有两种用法:

  • x *int表示变量x是一个引用,这个引用指向的目标数据是int类型。更通用的形式是x *TYPE
  • *x表示x是一个引用,*x表示解除这个引用,取回x所指向的数据结构,也就是说这是 一个数据结构,只不过这个数据结构可能是内置数据类型,也可能是自定义的数据结构

x *int的x是一个指向int类型的引用,而&y返回的也是一个引用,所以&y的y如果是int类型的数据,&y可以赋值给x *int的x。

注意,x的数据类型是*int,不是int,虽然x所指向的是数据类型是int。就像前面perl中的引用只是一个变量,而其指向的却是一个hash数据结构一样。

*x代表的是数据结构自身,所以如果为其赋值(如*x = 2),则新赋的值将直接保存到x指向的数据中。

例如:

package main

import ("fmt")

func main(){
var a *int
c := 2
a = &c
d := *a
fmt.Println(*a) // 输出2
fmt.Println(d) // 输出2
}

var a *int定义了一个指向int类型的数据结构的引用。a = &c中,因为&c返回的是一个引用,指向的是数据结构c,c是int类型的数据结构,将其赋值给a,所以a也指向c这个数据结构,也就是说*a的值将等于2。所以d := *a赋值后,d自身是一个int类型的数据结构,其值为2。

package main

import "fmt"

func main() {
var i int = 10
println("i addr: ", &i) // 数据对象10的地址:0xc042064058 var ptr *int = &i
fmt.Printf("ptr=%v\n", ptr) // 0xc042064058
fmt.Printf("ptr addr: %v\n", &ptr) // 指针对象ptr的地址:0xc042084018
fmt.Printf("ptr地址: %v\n", *&ptr) // 指针对象ptr的值0xc042064058
fmt.Printf("ptr->value: %v", *ptr) // 10
}

Go函数参数传值

Go函数给参数传递值的时候是以复制的方式进行的

因为复制传值的方式,如果函数的参数是一个数据结构,将直接复制整个数据结构的副本传递给函数,这有两个问题:

  1. 函数内部无法修改传递给函数的原始数据结构,它修改的只是原始数据结构拷贝后的副本
  2. 如果传递的原始数据结构很大,完整地复制出一个副本开销并不小

例如,第一个问题:

package main

import ("fmt")

type Animal struct {
name string
weight int
} func main(){
bm_horse := Animal{
name: "baima",
weight: 60,
}
add(bm_horse)
fmt.Println(bm_horse.weight)
} func add(a Animal){
a.weight += 10
}

上面的输出结果仍然为60。add函数用于修改Animal的实例数据结构中的weight属性。当执行add(bm_horse)的时候,bm_horse传递给add()函数,但并不是直接传递给add()函数,而是复制一份bm_horse的副本赋值给add函数的参数a,所以add()中修改的a.weight的属性是bm_horse的副本,而不是直接修改的bm_horse,所以上面的输出结果仍然为60。

为了修改bm_horse所在的数据结构的值,需要使用引用(指针)的方式传值。

只需修改两个地方即可:

package main

import ("fmt")

type Animal struct {
name string
weight int
} func main(){
bm_horse := &Animal{
name: "baima",
weight: 60,
}
add(bm_horse)
fmt.Println(bm_horse.weight)
} func add(a *Animal){
a.weight += 10
}

为了修改传递给函数参数的数据结构,这个参数必须是直接指向这个数据结构的。所以使用add(a *Animal),既然a是一个Animal数据结构的一个实例的引用,所以调用add()的时候,传递给add()中的参数必须是一个Animal数据结构的引用,所以bm_horse的定义语句中使用&符号。

当调用到add(bm_horse)的时候,因为bm_horse是一个引用,所以赋值给函数参数a时,复制的是这个数据结构的引用,使得add能直接修改其外部的数据结构属性。

大多数时候,传递给函数的数据结构都是它们的引用,但极少数时候也有需求直接传递数据结构。

方法:属于数据结构的函数

可以为数据结构定义属于自己的函数。

package main
import ("fmt") type Animal struct {
name string
weight int
} func (a *Animal) add() {
a.weight += 10
} func main() {
bm_horse := &Animal{"baima",70}
bm_horse.add()
fmt.Println(bm_horse.weight) // 输出80
}

上面的add()函数定义方式func (a *Animal) add(){},它所表示的就是定义于数据结构Animal上的函数,就像类的实例方法一样,只要是属于这个数据结构的实例,都能直接调用这个函数,正如bm_horse.add()一样。

构造器

面向对象中有构造器(也称为构造方法),可以根据类构造出类的实例:对象。

Go虽然不支持面向对象,没有构造器的概念,但也具有构造器的功能,毕竟构造器只是一个方法而已。只要一个函数能够根据数据结构返回这个数据结构的一个实例对象,就可以称之为"构造器"。

例如,以下是Animal数据结构的一个构造函数:

func newAnimal(n string,w int) *Animal {
return &Animal{
name: n,
weight: w,
}
}

以下返回的是非引用类型的数据结构:

func newAnimal(n string,w int) Animal {
return Animal{
name: n,
weigth: w,
}
}

一般上面的方法类型称为工厂方法,就像工厂一样根据模板不断生成产品。但对于创建数据结构的实例来说,一般还是会采用内置的new()方式。

new函数

尽管Go没有构造器,但Go还有一个内置的new()函数用于为一个数据结构分配内存。其中new(x)等价于&x{},以下两语句等价:

bm_horse := new(Animal)
bm_horse := &Animal{}

使用哪种方式取决于自己。但如果要进行初始化赋值,一般采用第二种方法,可读性更强:

# 第一种方式
bm_horse := new(Animal)
bm_horse.name = "baima"
bm_horse.weight = 60 # 第二种方式
bm_horse := &Animal{
name: "baima",
weight: 60,
}

扩展数据结构的字段

在前面出现的数据结构中的字段数据类型都是简简单单的内置类型:string、int。但数据结构中的字段可以更复杂,例如可以是map、array等,还可以是自定义的数据类型(数据结构)。

例如,将一个指向同类型数据结构的字段添加到数据结构中:

type Animal struct {
name string
weight int
father *Animal
}

其中在此处的*Animal所表示的数据结构实例很可能是其它的Animal实例对象。

上面定义了father,还可以定义son,sister等等。

例如:

bm_horse := &Animal{
name: "baima",
weight: 60,
father: &Animal{
name: "hongma",
weight: 80,
father: nil,
},
}

composition

Go语言支持Composition(组合),它表示的是在一个数据结构中嵌套另一个数据结构的行为。

package main

import (
"fmt"
) type Animal struct {
name string
weight int
} type Horse struct {
*Animal // 注意此行
speak string
} func (a *Animal) hello() {
fmt.Println(a.name)
fmt.Println(a.weight)
//fmt.Println(a.speak)
} func main() {
bm_horse := &Horse{
Animal: &Animal{ // 注意此行
name: "baima",
weight: 60,
},
speak: "neigh",
}
bm_horse.hello()
}

上面的Horse数据结构中包含了一行*Animal,表示Animal的数据结构插入到Horse的结构中,这就像是一种面向对象的类继承。注意,没有给该字段显式命名,但可以隐式地访问Horse组合结构中的字段和函数。

另外,在构建Horse实例的时候,必须显式为其指定字段名(尽管数据结构中并没有指定其名称),且字段的名称必须和数据结构的名称完全相同。

然后调用属于Animal数据结构的hello方法,它只能访问Animal中的属性,所以无法访问speak属性。

很多人认为这种代码共享的方式比面向对象的继承更加健壮。

Go中的重载overload

例如,将上面属于Animal数据结构的hello函数重载为属于Horse数据结构的hello函数:

package main

import (
"fmt"
) type Animal struct {
name string
weight int
} type Horse struct {
*Animal // 注意此行
speak string
} func (h *Horse) hello() {
fmt.Println(h.name)
fmt.Println(h.weight)
fmt.Println(h.speak)
} func main() {
bm_horse := &Horse{
Animal: &Animal{ // 注意此行
name: "baima",
weight: 60,
},
speak: "neigh",
}
bm_horse.hello()
}

go基础系列:结构struct的更多相关文章

  1. Go基础系列:struct和嵌套struct

    struct struct定义结构,结构由字段(field)组成,每个field都有所属数据类型,在一个struct中,每个字段名都必须唯一. 说白了就是拿来存储数据的,只不过可自定义化的程度很高,用 ...

  2. Go基础系列:struct的导出和暴露问题

    struct的导出和暴露问题 关于struct的导出 struct的属性是否被导出,也遵循大小写的原则:首字母大写的被导出,首字母小写的不被导出. 所以: 如果struct名称首字母是小写的,这个st ...

  3. JVM基础系列第6讲:Java 虚拟机内存结构

    看到这里,我相信大家对于一个 Java 源文件是如何变成字节码文件,以及字节码文件的含义已经非常清楚了.那么接下来就是让 Java 虚拟机运行字节码文件,从而得出我们最终想要的结果了.在这个过程中,J ...

  4. 夯实Java基础系列5:Java文件和Java包结构

    目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...

  5. 基础系列(4)—— C#装箱和拆箱

    一 装箱和拆箱的概念 装箱是将值类型转换为引用类型 : 拆箱是将引用类型转换为值类型 : 值类型:包括原类型(Sbyte.Byte.Short.Ushort.Int.Uint.Long.Ulong.C ...

  6. 基础系列(5)—— C#控制语句

    语句是程序中最小程序指令.C#语言中可以使用多种类型的语句,每一种类型的语句又可以通过多个关键字实现.以下是C# 语言中使用的主要控制语句 类别 关键字 选择语句  if.else.switch.ca ...

  7. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  8. 带你学够浪:Go语言基础系列 - 8分钟学复合类型

    ★ 文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) " 对于一般的语言使用者来说 ,20% ...

  9. 带你学够浪:Go语言基础系列 - 10分钟学方法和接口

    文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 对于一般的语言使用者来说 ,20% 的语言特性就能够满 ...

  10. C#基础系列——小话泛型

    前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...

随机推荐

  1. appium + java + WebDriverAgent实现IOS app启动

    Appium v1.8.1 <dependency>    <groupId>io.appium</groupId>    <artifactId>ja ...

  2. Github使用:使用github用作自己的免费域名

    1.创建一个新仓库 --- 删除里面的文件 --- Git上传文件到新仓库(必须有index.html) 2. 点击新仓库的setting,下滑找到GitHub Pages ---- 点击第一行的链接 ...

  3. 哈夫曼(Huffman)树和哈夫曼编码

    一.哈夫曼(Huffman)树和哈夫曼编码 1.哈夫曼树(Huffman)又称最优二叉树,是一类带权路径长度最短的树, 常用于信息检测. 定义: 结点间的路径长度:树中一个结点到另一个结点之间分支数目 ...

  4. macOS 安装 ctags

    macOS 安装 ctags macOS 自带一个 ctags,但是不支持 -R 参数,递归产生tags文件 $ ctags -R --exclude=.git --exclude=log * cta ...

  5. kubernetes1.7.6 ha高可用部署

    写在前面:  1. 该文章部署方式为二进制部署. 2. 版本信息 k8s 1.7.6,etcd 3.2.9 3. 高可用部分 etcd做高可用集群.kube-apiserver 为无状态服务使用hap ...

  6. pm2模块编写入门

    PM2 模块 PM2模块是通过PM2来安装和管理,代码可以托管在NPM中.任何人都可以创建和发布一个PM2模块,可以是日志模块.http代理模块.负载均衡模块.DNS服务器模块或任何类型的实用程序. ...

  7. .net HttpListener 很慢

    使用   HttpListener 做的webserver ,撒逻辑没有,内网跨机器访问,都要200ms 替换方案 EvHttpSharp.dll 使用了 libevent_core,libevent ...

  8. Vue.js——快速入门

    Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API,使得我们能够快速地上手并使 ...

  9. python猜数字GUI版本V0.2

    使用类方式编写猜数字游戏GUI版本. 思路:初始化数字以及初始化wegdits,编写button click event判断代码的函数,每猜一次点击button调用一次该函数,并计算猜的次数.如果猜对 ...

  10. 6-使用requests库封装类处理get/post请求

    1.request安装 1)pip安装,直接pip install requests 2)下载离线包安装,加压后,命令行进入路径,执行python setup.py install 2.创建工程 注意 ...