Go学习——7.结构体

结构体

1.创建结构体

命名结构体(声明一个新的类型)
  • 定义
type Employee struct {
  firstName, lastName string
  age, salary int
}
  • 使用(2种)
    • 第一种:属性顺序可以乱
    emp1 := Employee{
      firstName: "Sam",
      age: 18,
      salary: 100,
      lastName: "Anderson"
    }
    
    • 第二种:属性可以不写,但是值顺序不能乱
    emp2 := Employee{"Sam", "Anderson", 10, 100}
    
匿名结构体(不声明一个新的类型)

没有声明一个新类型,这种类型的结构体被称为匿名结构体

  • 定义1:这种结构体var后面的变量就代表了这个结构体
var employee struct {
  firstName, lastName string
  age, salary int
}
  • 定义1的使用
employee.firstName = "Sam"
// 没有被赋值的属性都是当前类型的零值(默认值),注意字符串的零值是空字符串
fmt.Println(employee); // {Sam "" 0 0}
  • 定义2和使用

直接定义并赋值

emp3 := struct {
    firstName, lastName string
    age, salary         int
}{
    firstName: "Andreah",
    lastName:  "Nikola",
    age:       31,
    salary:    5000,
}

2.结构体的零值

  • 当定义了一个结构体,没有进行初始化(或者部分字段没有进行初始化),结构体没有初始化的字段都会赋予零值(默认值)
  • 举例: 如1中的定义1的使用

3.访问结构体中的单个字段

type Employee struct {
  firstname, lastname string
  age, salary int
}
emp4 := Employee{"Andreah", "Nikola", 31, 5000}
firstname := emp4.firstname
fmt.Println(firstname) // "Andreah"

3.给结构体字段赋值

type Employee struct {
  firstname, lastname string
  age, salary int
}
var emp5 Employee // 先定义不赋值
emp5.firstname = "Andreah"
emp5.lastname = "Nikola"
emp5.age = 31
emp5.salary = 5000
fmt.Println(emp5) // {Andreah Nikola 31 5000}

4.指向结构体的指针

  • 引例一:
type Employee struct {
  firstname, lastname string
  age, salary int
}

emp6 := &Employee{"Andreah", "Nikola", 31, 1000}
firstname := (*emp6).firstname
lastname := (*emp6).lastname
age := (*emp6).age
salary := (*emp6).salary

fmt.Println(firstname, lastname, age, salary) // Andreah Nikola 31 1000

  • 引例二:用*emp6可以访问属性,用emp6也可以直接访问属性
type Employee struct {
  firstname, lastname string
  age, salary int
}

emp6 := &Employee{"Andreah", "Nikola", 31, 1000}
firstname := emp6.firstname
lastname := emp6.lastname
age := emp6.age
salary := emp6.salary

fmt.Println(firstname, lastname, age, salary) // Andreah Nikola 31 1000
  • 总结:emp6与*emp6一样,都可以访问其下面的属性值
  • 为什么???

5.结构体的匿名字段

  • 匿名字段:只指定类型,不指定字段名。
  • 匿名字段其实默认字段名就是类型。
type Employee struct {
  int
  string
}
emp7 := Employee{31, "Andreah"}
fmt.Println(emp7) // {31 Andreah}

// 匿名字段其实默认字段名就是类型
emp7.int = 18
fmt.Println(emp7) // {18 Andreah}

6.嵌套结构体

结构体的字段仍然是结构体的结构体,叫做嵌套结构体

  • 嵌套结构体取值跟js嵌套对象类似
type Address struct {
  city string
}
type Person struct {
  name string
  age int
  address Address
}
p := Person{
  "Sam",
  18,
  Address{"纽约"}} // 这里最后一个}不要单独一行,会编译不过,编译器希望最后一个是Person的}结尾

fmt.Println(p) // {Sam 18 {纽约}}
fmt.Println(p.age) // 18
fmt.Println(p.address.city) // 纽约
p.address.city = "巴黎"
fmt.Println(p.address.city) // 巴黎

7.promoted fields提升字段

  • 结构体A中有一个匿名字段,类型是一个结构体B,那么访问结构体中的结构体的字段propC,可以直接访问用A.propC,也就是忽略匿名结构体B
type Address struct {
  city string
}
type Person struct{
  name string
  age int
  Address
}

var p1 Person
p1.Address.city = "纽约"
fmt.Println(p1) // { "" 0 {纽约}}
p1.city = "巴黎"
fmt.Println(p1) // { "" 0 {巴黎}}

8.导出结构体和字段

  • 如果一个结构体类型是大写字母开头,那么这个结构体就是导出类型

  • 如果结构体字段是大写字母开头,那么这个结构体字段也是导出类型

  • 举例:

    • 假设有如下目录结构
      src
        structs
                computer
                        spec.go
                main.go
    
    • computer下的spec.go
      package computer
    
      type Spec struct{
        Maker string // 导出类型
        model string // 非导出类型
        Price int // 导出类型
      }
    
    • main.go
      package main
    
      import (
        "fmt",
        "structs/computer"
      )
    
      func main(){
        var spec computer.Spec
        spec.Maker = "apple"
        spec.Price = 5000
        fmt.Printlb(spec) // {apple  50000}
      }
    
    • 如果使用没导出的类型,会报错

    9.结构体的相等性

    • 如果两个结构体以下全部相等,则这两个结构体相等
      • 均是值类型
      • 其中的字段可比
      • 字段值都相等
    • 举例:
    type name struct {
      firstName string
      lastName string
    }
    
    
    func main() {
        name1 := name{"Steve", "Jobs"}
        name2 := name{"Steve", "Jobs"}
        if name1 == name2 {
            fmt.Println("name1 and name2 are equal") // 打印这个
        } else {
            fmt.Println("name1 and name2 are not equal")
        }
    
        name3 := name{firstName:"Steve", lastName:"Jobs"}
        name4 := name{}
        name4.firstName = "Steve"
        if name3 == name4 {
            fmt.Println("name3 and name4 are equal") 
        } else {
            fmt.Println("name3 and name4 are not equal") // 打印这个
        }
    }
    
    • 如果含有不可比较的字段,比如map类型的字段,则不可以进行比较!!! 注意是不可以比较,一比较就报错
    package main
    
    import (
        "fmt"
    )
    
    type image struct {
        data map[int]int
    }
    
    func main() {
        image1 := image{data: map[int]int{
            0: 155,
        }}
        image2 := image{data: map[int]int{
            0: 155,
        }}
        if image1 == image2 { // 报错
            fmt.Println("image1 and image2 are equal")
        }
    }
    

以下为补充

面向对象

  • go语言仅支持封装,不支持继承和多态
  • go没有class,只有struct

结构体的定义

type treeNode struct {
    value int
    left, right *treeNode
}

// go没有构造函数,但是可以通过工厂函数去控制,一般返回一个局部变量的地址
func createNode(value int) *treeNode {
    return &treeNode{value: value}
}
func main() {
    var root treeNode

    root = treeNode{value: 3}
    root.left = &treeNode{}
    root.right = &treeNode{5, nil, nil}
    root.right.left = new(treeNode)// root.right是地址,这个地址对应是treeNode解构,所以地址中含有value属性和left属性和right属性,那么继续访问left属性还是使用点,这样的话就代表访问了root下的right中的left
    root.left.right = createNode(2)// 调用了工厂函数

}

给结构体定义方法

type treeNode struct {
    value int
    left, right *treeNode
}

/*
详解如下:
    (node treeNode)称之为接受者
    (node treeNode)的意思就是,为treeNode定义了一个方法print
    同时,将结构体变量node传入print函数,这样print函数就能访问到node下的属性
*/
func (node treeNode) print() {
    fmt.Println(node.value)
}

/*
    注意:要改值的话,接受者需要是指针,要找到这个地址才可以修改值,否则是改不掉的。在结构体添加方法中需要特别注意!!!
*/
func (node *treeNode) setValue(val int) {
    if node == nil {
        fmt.Println("node is nil")
        return
    }
	node.value = val
}
// go没有构造函数,但是可以通过工厂函数去控制,一般返回一个局部变量的地址
func createNode(value int) *treeNode {
	return &treeNode{value: value}
}
func main() {
	var root treeNode

	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{5, nil, nil}
	root.right.left = new(treeNode)// root.right是地址,这个地址对应是treeNode解构,所以地址中含有value属性和left属性和right属性,那么继续访问left属性还是使用点,这样的话就代表访问了root下的right中的left
	root.left.right = createNode(2)// 调用了工厂函数

    /*
        这里的root就是接受者,接受者调用print方法
    */
	root.print()
	root.right.left.setValue(4)
	root.right.left.print() // 4
	fmt.Println(root.right.left) // {4 <nil> <nil>}

	root.print()
	root.setValue(100)

	pRoot := &root // 获取root的地址
	pRoot.print() // 打印100,地址也可以调用结构体的方法,因为会通过地址找到该结构体,然后调用方法
	pRoot.setValue(200)
	pRoot.print() // 打印200
}

练习题

创建如下解构的树,然后遍历打印出来

    5
    |
   ---
  4   3
  |   |
 --   --
|       |
2       1
  • 代码:
package main
import (
	"fmt"
)
type treeNode struct{
	value int
	left, right *treeNode
}
func (node treeNode) print(){
	fmt.Println(node.value)
}
func (node *treeNode) traverse(){// 中序遍历
	if node == nil {
		return
	}
	node.left.traverse()
	node.print()
	node.right.traverse()
}
func main(){
	
	// 构建tree
	node := treeNode{value: 5}
	node.left = &treeNode{value: 4}
	node.left.left = &treeNode{value: 2}
	node.right = &treeNode{value: 3}
	node.right.right = &treeNode{value: 1}

	// 遍历树
	node.traverse()// 先后打印的结果为2 4 5 3 1也就是最左边列先打印,依次向右,并且是从上到下打印的

}

注意

  1. new(结构体) 就是获取该结构体所有字段为默认值时的地址,等同于&结构体
  2. go没有class,只有struct,所以go语言没有构造函数这种说法
  3. 结构体添加方法,如果设置值的话,接受者必须是指针,也就是地址
    • 只有使用指针才可以改变结构体的内容
  4. 地址也可以调用结构体的方法,因为go语言会通过地址找到结构体,然后调用结构体的方法
  5. nil指针也可以调用方法,但是nil调用下面的属性会报错,比如nil.value是不行的
  6. 值接受者VS指针接受者
    • 要改变内容必须使用指针接受者
    • 结构过大也考虑使用指针接受者
    • 一致性:如有指针接受者,最好都是指针接受者(这是建议)
  7. 值接受者是go语言特有的
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值