结构体
<h1>单元目标</h1>
<ul>
<li>结构体的定义和使用;</li>
<li>匿名结构体的定义和使用。</li>
</ul>
<h1>什么是结构体</h1>
<p><strong><font color='red'>结构体概念</font></strong>:
在Go语言中不存在<strong><font color='red'>Class类</font></strong>的概念,但是可以通过结构体struct来实现。
结构体就是一种相同类型,或者不同类型的数据构成的数据的集合。里面的每一个变量叫做成员变量。也就是结构体的字段。每一个字段拥有自己的数据类型和数值。</p>
<p>相信大家都听过这样一句话:“万物皆对象”。这句话来源于面向对象编程语言,所倡导的精神是<strong>将现实的事物“抽象”出来</strong>。必要时通过各种“组成手段”将这些抽象出的“对象”重新组合在一起,最终的效果则是将现实中的万事万物描述清楚。</p>
<p>这种“抽象”思维的优势很明显,它可以<strong>将复杂的问题简单化</strong>,更符合人类思维,可以实现<strong>从执行者到指挥者的角色转换</strong>。</p>
<p>Go语言脱胎于C语言(C语言是面向过程的),同时吸收了面向对象语言的编程思想和优势,极大地扩充了C语言中“结构体”的能力。作为开发者,不仅可以像传统C语言那样定义和使用结构体,还可以实现面向对象编程语言所具有的构造函数和方法、类型继承等。可以说:Go语言中的结构体<strong>使用简单、灵活,扩展性强</strong>。</p>
<p>当然,如果你对前面所述的一些词汇比较陌生,也没有关系,学完这3讲,相信您不仅熟悉、会用,甚至还能给别人讲清楚呢!好了,废话不多讲,我们进入正题。</p>
<h1>为何需要结构体</h1>
<p>为什么程序中需要结构体呢?在之前的各种示例中,我们使用的通常是基本数据类型。比如int、float、bool、string。但在实际项目中,所面对的情况往往会复杂很多。</p>
<p>比如,要做一个图书管理系统,每本图书都有相应的属性信息:图书名称、作者名称、出版社、分类、ISBN码等等。又或者要做一个学生管理系统,每个学生也有相应的属性信息:姓名、性别、年龄、年级、班级、学号等等。像这类可以用若干属性(这些属性还有可能是不同的数据类型)来描述的“对象”,就需要使用结构体了。</p>
<p>所以,我们便可得出结构体的定义——<strong>结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。</strong></p>
<p>>💡 <strong>提示:</strong>此处所指的“类型”,可以是基本数据类型,也可以是其它结构体。本讲仅涉及由基本数据类型构成的结构体。</p>
<p>Go语言中的结构体是一种自定义数据类型,它可以包含多个字段,并且支持面向对象编程的特性,例如封装、继承和多态。</p>
<p>结构体在Go语言中非常重要,因为它们提供了一种方便的方式来组织和处理复杂的数据。使用结构体可以将相关的数据字段组合在一起,使代码更加模块化和可读性更高。此外,结构体还可以实现接口,并且可以用作函数和方法的参数和返回值。</p>
<p>结构体的使用场景包括但不限于:定义HTTP请求和响应结构体、数据库模型、配置文件解析、并发编程中的同步机制、以及许多其他需要处理复杂数据的情况。</p>
<h1>结构体的定义</h1>
<p>在Go语言中,定义结构体的标准格式为:</p>
<pre><code class="language-go">type StructName struct {
// 属性字段
}</code></pre>
<p>>其中,开头的type表示要定义自定义的类型;StructName代表结构体的名称;struct表示结构体类型;由大括号包裹的部分是属性列表,由一个或若干个字段构成。字段的名称不允许重复。</p>
<h2>举例1:</h2>
<p>大家都玩过王者荣耀吧,我最喜欢的英雄是赵云,‘忠肝义胆’。他们都由3个属性构成:角色名、职业和性别。</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=b3ee1ca65f57a04356ab2151b08c8769&amp;file=file.png" alt="" /></p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=f632da741bfb89e637948834e574425c&amp;file=file.png" alt="" />
>💡 提示:在实际项目中使用的结构体属性大多比本例更为复杂,但无非是数量上的增加。为了讲解方便,这里仍仅使用这3个属性为例。</p>
<p>举例,若要定义用于描述游戏角色的结构体,名称为Player,则可如下实现:</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=6b7cd916fb84a2ce9b718b823a193b2c&amp;file=file.png" alt="" /></p>
<h2>结构体的使用</h2>
<pre><code class="language-go">type Player struct {
// 角色名
name string
// 角色
role string
// 性别
gender string
// 皮肤
skin string
}</code></pre>
<p>具体来说,本例定义了名为“Player”类型的数据,该数据的类型为结构体。这个结构体中由4个属性构成,分别使用4个字段来描述。包括角色名(name)、职业(role)、性别(gender)和皮肤(skin),它们都是string类型。</p>
<p>实际上,Player作为<strong>自定义类型,和基本数据类型相似,同样遵循Go语言官方建议的命名方法(驼峰式),作用域的规则限制也同样适用</strong>。</p>
<p>一旦结构体完成定义,它就已经准备就绪,随时可用了。和基本数据类型相同,使用结构体依然需要声明和初始化。</p>
<h1>结构体的声明与初始化</h1>
<p>下面的代码声明了Player类型的变量,并完成初始化。最终创建了赵云角色:</p>
<pre><code class="language-go">playerA := Player{
name: &quot;赵云&quot;,
role: &quot;战士&quot;,
gender: &quot;男&quot;,
skin:&quot;经典&quot;
}</code></pre>
<p>如上代码所示,playerA就是赵云了。</p>
<pre><code>❗️ 注意:在初始化结构体时,每个字段需要使用逗号分割开。在定义结构体时则不用。</code></pre>
<p>在初始化结构体时,可以一次性地赋值所有字段,也可以只赋值部分字段,甚至不赋值。下面几种写法都是允许的:</p>
<pre><code class="language-go">playerA := Player{
name: &quot;赵云&quot;,
gender: &quot;男&quot;,
}
playerA := Player{}</code></pre>
<p>对于本例这种一口气赋值所有字段的行为,Go语言提供了更简洁的方式,如下所示:</p>
<pre><code class="language-go">playerA := Player{
&quot;赵云&quot;,
&quot;战士&quot;,
&quot;男&quot;,
}</code></pre>
<p>注意到区别了吗?这种简洁方式省略了所有字段名。但为了确保字段的正确匹配,需要按照结构体内部定义的顺序进行赋值,且必须一次完成所有属性赋值。即定义时是角色名、职业、性别;初始化时也应是角色名、职业、性别,顺序不能颠倒,且值缺一不可。</p>
<p>当我们完成初始化后,试试使用fmt.Println()函数输出playerA的值,将得到如下结果:</p>
<p>> {赵云 战士 男}</p>
<p>下面,请各位动手练习,声明playerB变量,并使用温玉琳琅(黄忠,战士,男,经典)的角色信息初始化它。</p>
<p>下面是参考代码:</p>
<pre><code class="language-go">playerB := Player{
name: &quot;黄忠&quot;,
career: &quot;射手&quot;,
gender: &quot;男&quot;,
skin: &quot;经典&quot;,
}</code></pre>
<h3>访问和修改结构体的属性值</h3>
<p>日复一日,随着游戏不断更新,英雄推出新的皮肤。他将由战士转为战神,需要改变结构体内的职业属性值。</p>
<p>在Go语言中,改变或完善(针对初始化时未赋值的情况)结构体内属性值的方法是非常简单的,其格式为:</p>
<pre><code class="language-go">变量名.属性名=值</code></pre>
<p>举例来说,赵云对应的是playerA变量,皮肤对应的是skin字段。因此,若要更换皮肤,应如下实现:</p>
<pre><code class="language-go">playerA.skin = &quot;皇家上将&quot;</code></pre>
<p>再次向控制台输出playerA的值,可以看到如下输出:</p>
<p>> {赵云 战士 男 皇家上将}</p>
<p>可以看到,只有皮肤发生了变化,其它属性仍保持不变。</p>
<p>另一方面,如果我们想单独获取playerA变量的某个属性值,也可以使用类似的方式。举例来说,获取playerA的角色名,实现方式为:</p>
<pre><code class="language-go">fmt.Println(playerA.name)</code></pre>
<p>运行结果为:赵云</p>
<p>> </p>
<p>到此,我们已经介绍了普通结构体的定义以及结构体变量的声明、初始化和字段访问(包括取值和修改)。下面我来提个问题,大家一起思考。</p>
<p>在上一讲中,我们使用闭包实现了工厂模式。它也能定义游戏角色,本讲的结构体也能定义游戏角色。这二者之间有何区别,为何要有不同的定义方式呢?工厂模式产出的产品——游戏角色除了包含给定的值外,还有代表血量和魔法值的HP和MP。但目前为止,结构体只能做到“给什么,有什么”。这到底是怎么一回事呢?</p>
<p>闭包的目的在于起到一定的数据保护作用(对于HP、MP而言)。但是,使用结构体可以实现面向对象语言中的构造函数、方法、继承等等特性,这些却是闭包无法做到的。</p>
<h2>匿名结构体</h2>
<p>在实际开发中,还有一类情况,就是某个结构体的作用域很小,甚至只存在于某个函数内部,或是无需创建太多的该结构体变量等等。对于上述情况,Go语言允许我们使用匿名结构体简化编码,即使用匿名结构体。</p>
<pre><code>💡 提示:这是本讲第二次介绍简化编码的方式了,这便是Go语言中结构体使用简便特性的体现。大家还记得上一个简化编码是用来做什么吗?答案是——初始化结构体变量</code></pre>
<p>举例来说,还是游戏中的场景。某天,温玉琳琅来到许愿树下进行许愿,这一天是她的生日,许愿树这个植物仅在生日场景中出现。因此为了简化编码,考虑使用匿名结构体来定义和使用它。</p>
<p>使用匿名结构体的方法并不难,实际上就是省略了单独的结构体定义。而是将定义和相关变量的声明、属性赋值合三为一处理。如果我们要声明一个变量来表示许愿树,示例代码如下所示:</p>
<pre><code class="language-go">wishingTree := struct {
height float64
width float64
treeType string
}{
height: 22.5 * 100,
width: 50,
treeType: &quot;banyan&quot;}
fmt.Println(wishingTree)</code></pre>
<p>仔细阅读上述代码,wishingTree便是表示许愿树变量了,它是结构体(struct)类型。结构体内包含3个属性,分别是浮点型的高度(height)、浮点型的胸径(width)以及字符串型的树品种。这3个属性由一个大括号包裹起来。紧随其后的大括号是为属性赋值的过程,其规则依然是允许全部赋值,也允许部分赋值。简化的赋值方式同样适用,这里不再赘述。</p>
<pre><code>❗️ 注意:即使不为任何属性赋值,第二个大括号也是必不可少的,否则将引发编译时错误,程序无法被编译和运行。</code></pre>