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. 百度SEO怎么做 影响百度seo排名的因素

    现在SEO的最新算法.技巧,希望大家运用这些技巧,都像我一样能够在百度上获得客户: 第一步:了解搜索引擎的工作原理爬——抓——处——排——展 第二步:大家要建立一个整体框架,影响SEO的排名有哪些因素 ...

  2. go的语法

    概述 有接触go语言,工作中用的比较少,偶尔做个小项目,最近看到的一个项目也从go迁移到java. 但是对go还是恋恋不忘,它语法比较简洁,库也比较多,编译速度快,等等优点,让我忘不了. 对go的语法 ...

  3. Hbase的基本操作(CDH组件可用)

    Habse创建一张表:    1,创建一个命名空间NameSpace(命名空间NameSpace指的是一个表的逻辑分组 ,同一分组中的各个表有类似的用途,相当于关系型数据库中的DataBase)    ...

  4. LR回放https协议脚本失败:[GENERAL_MSG_CAT_SSL_ERROR]connect to host "XXX" failed:[10054] Connection reset by peer [MsgId:MERR-27780]

    Loadrunner默认发送是通过sockets(将http转换为sockets)发送的,而sockets默认SSL的版本为SSL2和SSL3.HTTPS协议录制的脚本以SSL3版本回放时会使sock ...

  5. css3(display)

    实现下拉 <!DOCTYPE html><html><head> <meta charset="utf-8"> <style ...

  6. [Hadoop]Hadoop章3 NameNode的ZKFC机制

    基本概念 首先我们要明确ZKFC 是什么,有什么作用: zkfc是什么? ZooKeeperFailoverController 它是什么?是Hadoop中通过ZK实现FC功能的一个实用工具. 主要作 ...

  7. Finance财务软件(引入业务系统凭证专题)

    我们通过自定义存储过程从业务系统引入凭证 我们需要以下适配 1.设置业务系统数据库链接 2.在自定义模板中设置存储过程名称及入参,这里的功能键值必须为_InterfaceExec,保留字段作为存储过程 ...

  8. AX_SysTableBrowser

    sysTableBrowser sysTableBrowser = new sysTableBrowser();  ;  sysTableBrowser.setAllowEdit(true);  sy ...

  9. sql_calc_found_rows原理

  10. 异常:No Spring WebApplicationInitializer types detected on classpath

    原因: 启动服务提供者/服务消费者-->去注册中心Zookeeper无法注册这个服务 / 在监控中心无法发现服务 异常提示:No Spring WebApplicationInitializer ...