zoukankan      html  css  js  c++  java
  • Go语言结构

    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)
      }
      
  • 相关阅读:
    java代码split分割数字类
    P1330 封锁阳光大学
    1022 舞会2
    1626 爱在心中
    P2024 食物链(two)
    P1196 银河英雄传说
    P1892 团伙
    P1546 最短网络(最小生成树)
    烦人的幻灯片(拓扑)
    例4.15 奖金(拓扑排序)
  • 原文地址:https://www.cnblogs.com/itbsl/p/10443750.html
Copyright © 2011-2022 走看看