Go通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型。一个带属性的结构体试图表示一个现实世界中的实体。结构体是复合类型(composite types),当需要定义一个类型,它由一系列属性组成,每个属性都有自己的类型和值的时候,就应该使用结构体,它把数据聚集在一起。然后可以访问这些数据,就好像它是一个独立实体的一部分。结构体也是值类型,因此可以通过new函数来创建

组成结构体类型的那些数据称为字段(fields)。每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。

我们为什么需要结构体?

编程语言,终究是为了解决我们现实生活中的问题,生活中的事物(如人)都有属性(嘴、胳膊)和方法(行走、跳跃)。使用之前的普通类型和数组来表示这些是不方便的,结构体就像其它编程语言中的类(class),包含了一系列的属性和方法,结构体能够更好的描述事物,更好的解决问题。

tips:属性即字段

结构体定义

结构体定义的一般方式如下:

type identifier struct {
field1 type1
field2 type2
...
}

示例:

type Student struct {
Name string
Age int
Score float64
}

type T struct{a,b int}也是合法的语法,它更适用于简单的结构体。

结构体的字段可以是任何类型,甚至是结构体本身,也可以是函数或者接口。可以声明结构体类型的一个变量,然后像下面这样给它的字段赋值:

var s T
s.a = 6
s.b = 8

数组可以看作是一种结构体类型,不过它使用下标而不是具名的字段。

创建结构体实例

创建结构体实例有两种方式,一种是普通方式(var t T)创建,另一种是使用new()方法来创建对应结构体的实例。

普通方式创建结构体实例

声明var t T会给t分配内存,并零值化内存,这个时候t的类型是T。语法如下

var t T

示例:

package main

import "fmt"

type Person struct {
Name string
age int
} func main() { var p Person p = Person{Name:"黄忠", age:35} fmt.Println(p)
}

new()创建结构体实例

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针: var t *T = new(T) ,如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。

var t *T = new(T)

写这条语句的惯用方法是:t := new(T),变量 t 是一个指向 T 的指针(t的类型是*T),此时结构体字段的值是它们所属类型的零值。

示例:

package main

import "fmt"

type Person struct {
Name string
age int
} func main() { var p *Person = new(Person) (*p).Name = "黄忠" //也可以简写成 p.Name = "黄忠",编译器在编译时会优化自动帮我们加上
(*p).age = 34 //也可以简写成 p.age = 34 fmt.Println(*p)
}

在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值)。在首次使用结构体里的引用字段的时候一定要先make()或者使用字面量的方式初始化才能再使用。

例如:

package main

import "fmt"

type Person struct {
Name string
Age int
Scores [5]float64
ptr *int //指针
slice []int //切片
mp map[string]string //map
} func main() { var p Person p.Name = "黄忠"
p.Age = 34
p.Scores[0] = 12 //使用引用类型字段,要先初始化内存,才能使用,或者使用字面量方式初始化
p.ptr = new(int)
*p.ptr = 4 p.slice = []int{1, 2, 3, 4} p.mp = make(map[string]string)
p.mp["test"] = "hello" fmt.Println(p) }

结构体实例初始化

初始化实例的常规方式如下:

package main

import "fmt"

type Person struct {
Name string
age int
} func main() { var p3 Person
p3.Name = "狄仁杰"
p3.age = 34
fmt.Println(p3)
}

上面的代码中使用了(.语法)来给属性/字段赋值,在Go语言中这叫选择器(selector),无论变量是一个结构体类型还是一个结构体类型指针,都使用同样的 选择器符(selector-notation) 来引用结构体的字段/属性。(注:字段即属性)

还有更简单的方式就是使用混合字面量语法(composite literal syntax)

时间间隔(开始和结束时间以秒为单位)是使用结构体的一个典型例子:

type Interval struct {
start int
end int
}
intr := Interval{0, 3} //(A)
intr := Interval{end:5, start:1} //(B)
intr := Interval{end:5} //(C)

在(A)中,值必须以字段在结构体定义时的顺序给出,& 不是必须的。(B)显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像(C)中那样。

示例:

package main

import "fmt"

type Person struct {
Name string
age int
} func main() { //方法一:混合字面量方法,
var p Person
p = Person{Name:"黄忠", age:35} //方法2:混合字面量方法(和上面的不同点是没有指定属性名,这里值的顺序必须按照属性顺序来写)
var p2 Person
p2 = Person{"小乔", 16} fmt.Println(p)
fmt.Println(p2) }

聊聊给结构体字段赋值

来看看下面的代码:

package main

import "fmt"

type Person struct {
Name string
Age int
} func main() { var p *Person = new(Person) //对指针有了解的人都知道这里本来应该写成(*p).Name = "黄忠" (*p).Age = 34
//但是为什么写成p.Name和p.Age也可以呢?
//这是因为编译器在内部帮我们做了优化,在编译时会自动帮我们加上,让我们在书写代码的时候更加的方便快捷
//但是我们应当知道,这里本应该写成(*p).Name = "黄忠" (*p).Age = 34
//因为.运算符比*运算符优先级高,所以把*p用括号括起来
p.Name = "黄忠"
p.Age = 34 fmt.Println(*p) }

结构体类型实例和指向它的指针内存布局

说明了结构体类型实例和一个指向它的指针的内存布局:

type Point struct {
x int
y int
}

使用new初始化:

作为结构体字面量初始化:

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。下面的例子清晰地说明了这些情况:

type Rect1 struct{Min, Max Point}
type Rect2 struct{Min, Max *Point}

结构体的方法

方法与函数及其类似,本质上是一样的,只不过方法比函数多了接收者。也就是下图中的(1)

示例:

定义了一个Person结构体,并且Person结构体定义了一个OutputName的方法,该方法属于Person结构体,该方法只能通过Person结构体的实例来调用,

package main

import "fmt"

type Person struct {
Name string
Age int
} func (p Person) OutputName() {
fmt.Println(p.Name)
}
func main() { var test Person
test.Name = "黄忠"
test.OutputName()
}

面向对象

组合(继承)

Go语言实现继承的方式和其它大多数编程语言不太一样,Go原因是通过组合来实现的。比如一个人具有姓名、年龄等属性,而学生不仅具有该人类的各项属性,而且还有分数,年级等额外属性,此时就可以在学生结构体中组合人结构体来简化代码,代码如下:

package main

import "fmt"

type Person struct {
Name string //姓名
Age int //年龄
} type Student struct {
Person
score float64 //分数
grade int //年级
} func main() { var stu Student stu.Name = "黄忠" //stu.Person.Name = "黄忠"
stu.Age = 34 //stu.Person.Age = 34
stu.score = 99
stu.grade = 11 fmt.Println(stu) //输出: {{黄忠 34} 99 11}
}

对上面代码的小结:

(1) 当我们直接通过stu访问字段或方式时,执行流程如下,比如stu.Name

(2) 编译器会先看stu对应的类型有没有Name,如果有,则直接调用Student类型的Name字段

(3) 如果没有就去Student中嵌入的匿名结构体Person中有没有声明Name字段,如果有就调用,没有没有就继续查找…如果找不到就报错

(4) 当结构体匿名结构体有相同的字段或者方法时,编译器采用就近访问原则,如果希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分,如stu.Person.Name

(5) 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。

(6) 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

import "fmt"

type Person struct {
Name string //姓名
Age int //年龄
} type Student struct {
person Person //有名结构体
score float64 //分数
grade int //年级
} func main() { var stu Student stu.person.Name = "黄忠"
stu.person.Age = 34
stu.score = 99
stu.grade = 11 fmt.Println(stu) //输出: {{黄忠 34} 99 11}
}

继承带来的便利

代码的复用性提高了

代码的扩展性和维护性提高了

结构体使用注意事项

  1. 结构体是用户单独定义的类型,和其它类型进行转换时,需要有完全相同的字段(名字、个数和类型),示例如下:

    package main
    
    import "fmt"
    
    type A struct {
    Num int
    } type B struct {
    Num int
    } func main() { var a A
    var b B b.Num = 3
    a = A(b) fmt.Println(a, b)
    }
  2. 结构体是值类型,在方法调用中遵守值类型的传递机制,是值拷贝传递方式

  3. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

    package main
    
    import "fmt"
    
    type Person struct {
    Name string
    Age int
    } func (p *Person) modifyName() {
    p.Name = "小乔"
    }
    func main() { var test Person
    test.Name = "黄忠"
    test.modifyName() fmt.Println(test.Name) //输出小乔
    }
  4. Go中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型都可以有方法, 而不仅仅是struct,比如int, float64等都可以有方法

    package main
    
    import "fmt"
    
    type Integer int
    
    func (i Integer) print() {
    fmt.Println("i = ", i)
    } func main() { var i Integer = 4
    i.print()
    }
  5. 方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

  6. 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

    package main
    
    import "fmt"
    
    type Student struct {
    Name string
    Age int
    }
    //给*Student实现方法String()
    func (stu *Student) String() string {
    str := fmt.Sprintf("Name = [%v], Age = [%v]", stu.Name, stu.Age)
    return str
    } func main() { stu := Student{
    Name: "Jane",
    Age : 30,
    } //如果实现了*Student类型的String方法,就会自动调用
    fmt.Println(&stu)
    }

Go语言结构的更多相关文章

  1. 漫谈C语言结构体struct、公用体union空间占用

    先用代码说话: #include<stdio.h> union union_data0{ int a ;//本身占用4个字节 char b ;//本身占用1个字节 int c ; }; u ...

  2. 解析C语言结构体对齐(内存对齐问题)

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

  3. 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符

    [源码下载] 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 结构体 ...

  4. (转)PHP的语言结构和函数的区别

    相信大家经常看到对比一些PHP应用中,说用isset() 替换 strlen(),isset比strlen执行速度快等. 例子: if ( isset($user) ) { //do some thi ...

  5. php入门 数据类型 运算符 语言结构语句 函数 类与面向对象

    php PHP-enabled web pages are treated just like regular HTML pages and you can create and edit them ...

  6. Go语言结构体(struct)

    Go 语言结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图 ...

  7. C语言结构体定义的几种方法

    什么是结构体? 在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类.结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据 ...

  8. 对嵌入式开发C语言结构体的一点总结

    今天冬至居然不上班,公司的良心啊!这回有心情写博客和日志了,好了,废话不多说.直接看下文: 鉴于嵌入式开发过程中,C语言结构体的使用当然是必不可少.话说,基础什么的比你会更牛逼的算法更重要,基础不牢, ...

  9. C语言结构体变量私有化

    操作系统 : CentOS7.3.1611_x64 gcc版本 :4.8.5 问题描述 C语言结构体定义中的变量默认是公有(Public)属性,如果实现成员变量的私有(Private)化? 解决方案 ...

  10. 在C语言结构体中添加成员函数

    我们在使用C语言的结构体时,经常都是只定义几个成员变量,而学过面向对象的人应该知道,我们定义类时,不只是定义了成员变量,还定义了成员方法,而类的结构和结构体非常的相似,所以,为什么不想想如何在C语言结 ...

随机推荐

  1. &#65279导致网页顶部空白一行的解决办法【实测有效】

    我用织梦做的网站,出现了&#65279导致网页顶部空白一行的问题.通过搜索,了解到这原来是UTF-8的BOM的问题. &#65279导致网页顶部空白一行原因 这个问题只会在chrome ...

  2. 天然气管道Gaz[POI2007]

    --BZOJ1034 题目描述 Mary试图控制成都的天然气市场.专家已经标示出了最好的天然气井和中转站在成都的地图.现在需要将中转 站和天然气井连接起来.每个中转站必须被连接到正好一个钻油井,反之亦 ...

  3. tomcat设置开机自动启动

    windows下: 1 命令cmd 进入    磁盘: 回车 2 tomcat\bin目录   回车 3  service.bat install   (注:tomcat一定要有service.bat ...

  4. jquery操作select下拉框的多种方法(选中,取值,赋值等)

    Query获取Select选择的Text和Value: 语法解释: 1. $("#select_id").change(function(){//code...}); //为Sel ...

  5. jquery 3.4.0

    jQuery 3.4.0 的更新内容主要是错误修复和功能改进,列举部分如下: 针对 .width 和 .height 的性能改进 支持 nonce 和 nomodule 针对 Radio 元素新增事件 ...

  6. license

    http://139.199.89.239:1008/4571ab86-eb0d-4d2b-999e-37406bb8ba38 _~~~ rO0ABXNyAChjb20uemVyb3R1cm5hcm9 ...

  7. C语言的数据类型的本质和提高学习

    一.数据类型的概念 类型是对数据的抽象 类型是相同的数据有相同的表示形式.存储格式以及相关的操作 程序中使用的数据必定属于某一种数据类型 ​ 1.算术类型: 包括三种类型:整数类型.浮点类型,枚举型. ...

  8. Spring流行的十大理由

    Spring大概是每个JAVA程序员都听过的框架,但是它为什么能这么流行? 听到咕泡学院的Tom老师的公开课,下面是他总结的阿里为什么选择Spring的十大理由,我觉得这也是Spring能流行的原因: ...

  9. NotePad++ 添加HEX-Editor插件

    步骤: 一.下载插件 https://github.com/chcg/NPP_HexEdit/releases 二.选择插件版本 Notepad 官网客服提示:32bit Notepad++可以使用常 ...

  10. Automated generation of test oracles using a model-driven approach

    一.基本信息 标题:Automated generation of test oracles using a model-driven approach 时间:2013 出版源:Information ...