Go教程(八)结构体

无论是数组,切片还是映射,它们的值都只能保存同一种类型.

如一个切片变量的所有元素只能是int,而不是有string,这个和动态语言Python,Javascript不同.

一个映射变量的所有键只能是string,而不是一些是int.

在实际项目过程中,肯定是会遇到这种组合类的变量的,比如学生信息

name := "张三"
age := 18
sex := 1
address :="大铁岭"

Go里面有一个叫struct的结构体可以把多种不同类型的变量联合起来

struct类型

struct是一个由不同类型的值构成的结构体.它的定义如下

struct {                // 关键字
    field1 string       // 前端是变量名后面是变量类型
    field2 int
}

让我们来定义一个学生类型的struct

var Student struct {
    name    string
    age     int
    address string
}
func Test() {
  fmt.Printf("%#v", Student)
  // struct { name string; age int; address string }{name:"", age:0, address:""}
}

从打印信息我们可以看到一个struct的初始化字面量是各个类型自身的字面量

struct成员字段访问

我们可以用.来访问struct结构的成员和赋值

  Student.address = "大铁岭"
  Student.age = 18
  Student.name = "张三"
  fmt.Printf("%#v \n", Student)
  /*
  struct { name string; age int; address string }{name:"张三", age:18, address:"大铁岭"}
  */

以上我们把一个学生的基本信息都放到一起了,这样很方便.

定义类型和struct

到目前为止struct给我们带来了前所未有的方便,但是也引入了一个问题,第二个学生叫李四,它怎么办呢?

var Student1 struct {
    name    string
    age     int
    address string
}
tudent.address = "大铁岭"
Student.age = 18
Student.name = "李四"

以上就是能想到的方法,就像一个int类型,如果需要另一个的话,就再次声明一个

类型定义允许用户创建自己的新类型.而且可以基于基础类型来创建新的定义类型.

为了定义一个类型.需要使用type关键字,后面跟新类型名,然后是你希望基于的基础类型,你需要使用struct关键字,后面跟着以{}包裹的一组字段定义,就像你声明struct那样.

就像变量,类型定义可以被放在一个函数中.但是把它的作用域限定在该函数块中,意味着你不能在函数外面使用.所以类型定义经常放在包级别中的.

type MyStduent struct {
  name    string
  age     int
  address string
}

type MyTeacher struct {
  name    string
  age     int
  address string
  course  string
}

func Test(){
  var stu1 MyStduent
  stu1.address = "大铁岭"
  stu1.age = 18
  stu1.name = "李四"
  fmt.Printf("%#v \n", stu1)

  var te1 MyTeacher
  te1.address = "大铁岭"
  te1.age = 18
  te1.name = "王二"
  fmt.Printf("%#v \n", te1)
}
//以下是输出
structure.MyStduent{name:"李四", age:18, address:"大铁岭"}
structure.MyTeacher{name:"王二", age:18, address:"大铁岭", course:""}

当这些变量被声明后,我们可以访问和对这些字段进行赋值就和之前一样.

在函数中使用定义类型

已定义的类型可以被导出以及在项目的任何地方使用.也可以作为参数传递给函数

func ShowInfo(s MyStduent) {
  fmt.Println("student's address = ", s.address)
  fmt.Println("student's age = ", s.age)
  fmt.Println("student's name = ", s.name)
}

func Test(){
  var stu1 MyStduent
  stu1.address = "大铁岭"
  stu1.age = 18
  stu1.name = "李四"
  fmt.Printf("%#v \n", stu1)

  ShowInfo(stu1)
}

输出结果如下

student's address =  大铁岭
student's age =  18
student's name =  李四

函数的返回值也可以是已定义类型

type MyStduent struct {
  name    string
  age     int
  address string
}
func NewStudent(name string, age int, address string) MyStduent {
  var s MyStduent
  s.name = name
  s.age = age
  s.address = address
  return s
}
func ShowInfo(s MyStduent) {
  fmt.Println("student's address = ", s.address)
  fmt.Println("student's age = ", s.age)
  fmt.Println("student's name = ", s.name)
}
func Test() {
  ss := NewStudent("张四", 18, "大铁岭")
  ShowInfo(ss)
}

输出如下

student's address =  大铁岭
student's age =  18
student's name =  张四

使用函数修改struct

不止一次的提到Golang是一个值传递的语言,所以要修改struct的内容,必须借助指针的力量,对于struct也是一样的,当我们传递给ShowInfo一个struct时,函数内部实际上是接收到了一个拷贝,而原始不会改变.

要修改struct也是一样的,我们需要修改参数是一个指针,而不是一个对象.

func ModifyAge(s *MyStduent, age int) {
  s.age = age // 直接使用.运算符访问指针指向的值
}

func Test() {
  ss := NewStudent("张四", 18, "大铁岭")
  ShowInfo(ss)
  
  ModifyAge(&ss, 10)
  ShowInfo(ss)
}

显示如下

student's address =  大铁岭
student's age =  18
student's name =  张四
student's address =  大铁岭
student's age =  10
student's name =  张四

通过指针访问struct的字段

查看指针相关的知识,我们知道,对于基础类型的指针,是通过来定义的,要获取指针变量引用的值,需要通过在变量前面加来获取,否则指针变量代表的是不可读的内存地址.

对于struct而言,也有指针定义,我们来看一下以下的代码

  p := &ss      // 创建一个指向ss struct的指针
  ShowInfo(*p)  // 传入指针引用内容
  fmt.Println("addr ", (*p).address)  // 打印指针的address字段
  fmt.Println("addr ", *&p.address)   // 打印指针的address字段
  fmt.Println("addr ", p.address)     // 打印指针的address字段

输出如下

addr  大铁岭
addr  大铁岭
addr  大铁岭

*p.address Go认为address必须是一个指针,Go编译器认为*p.address= *(p.address)这种形式.但address不是地址,所以会报错,要想纠正有2种写法

  1. (*p).address 此时 *p是指针
  2. *&p.address 此时 &(p.address)确实是一个指针,*再去获取指针的内容
  3. p.address 直接使用,点运算符允许通过struct的指针直接访问字段

使用指针来传递大型struct

如果我们的struct有非常多的字段,那要传递一个struct系统内部会拷贝一个struct对象出来.这样做的后果就是会大大的增加程序的内存使用以及减慢程序运行速度.

所以我们的建议是任何时候都传递一个struct的指针给函数,以减少不必要的开销.

让struct在另一个包中使用

如果要想把我们的struct给程序的其他部分使用(因为不想在每个文件中都定义同样的一个student struct),我们需要遵守一些Go的规则

  1. 如果一个类型希望在它被定义的包之外被访问,它必须是被导出的,它的名字必须以大写字母开始.
  2. 只有首字母大写的struct才能被导出给程序的其他文件使用
  3. 只有首字母大写的function才能被导出给程序的其他文件调用
  4. 需要创建一个文件夹,文件夹的命名就是包的名字(如:structure)
  5. 在创建的文件夹里面新建一个.go的文件,然后把package写成structure
  6. 在需要引用这个struct的go文件中写 import “lesson2/structure”
  7. struct中首字母大写字段才能被导出给其他go文件访问
  8. var s strcture.Student

这样就完成了对struct的封装,即使一个struct类型被从包中导出,如果它的字段没有首字母大写的话,也是不会被导出的.

go moudle中我们知道了如何引用自己写的包,现在我们把这个Student导出来,以下是完整的代码

structure/a.go

package structure

// 被导出的MyStudent结构体
// 但是字段没被导出
type MyStduent struct {
  name    string  
  age     int
  address string
}
// 被导出的MyStudentE结构体
// 所有字段均已导出
type MyStduentE struct {
  Name    string
  Age     int
  Address string
}

func ShowInfoE(s *MyStduentE) {
  fmt.Println("student's address = ", s.Address)
  fmt.Println("student's age = ", s.Age)
  fmt.Println("student's name = ", s.Name)
}

main.go

package main

import (
  "fmt"
  "lesson2/structure"
)

func main() {
  var s structure.MyStduentE
  fmt.Printf("s.name = %#v \n", s.Name)
  var s1 structure.MyStduent
  // fmt.Printf("%#v", s1.name)
  fmt.Printf("s1 = %#v \n", s1)
}

以上的代码会造成一个问题

在这里插入图片描述

IDE提示s1.name未定义,因为它没被导出所致,而s的所有字段访问正常

我们把s1.name注释掉,直接用s1是可以的

以下是输出结果

s.name = ""
s1 = structure.MyStduent{name:"", age:0, address:""}

struct字面量

struct也可以使用{}字面量初始化进行短变量声明

main.go

s2 := structure.MyStduentE{Name: "王五", Age: 18, Address: "辽宁"}
structure.ShowInfoE(&s2)

这样就可以正常的显示出所赋值的MyStudentE对象

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值