Golang-结构体


  Golang支持面向对象编程(OOP),但是和传统的面向对象编程(Java)又有区别,所以只能说 Golang是支持面向对象编程特性

  传统的面向对象编程是怎么样?
  拿Java作为例子,定义一个Person类,里面包含了这个Person的一些属性或特征。

public class Person(){
    private String name;
    
    private int age;
    
    private String addr;
}

然后根据这个类来创建对象来进行使用。

既然Golang是支持面向对象编程特性,那是使用什么来实现OOP呢?

那就是结构体struct

先展示一个struct的样子。

//定义一个Dog的结构体,将Dog的各个字段或属性信息,放入Dog结构体进行管理
type Dog struct {
    name string
    age int
    addr string
}

  这时候就不难发现,struct和Java的class的结构是差不多的,那golang的结构体是怎么进行管理的?

//创建一个Dog的变量
var dogo Dog
dogo.name = "小黄"
dogo.age = 2
dogo.addr = "home"

fmt.Println("狗狗的个人简历如下:")
fmt.Println("name=", dogo.name)
fmt.Println("name=", dogo.age)
fmt.Println("name=", dogo.addr)

结合上面的描述,

  1. 结构体是自定义的数据类型,代表一类事物。(Dog)
  2. 结构体变量(实例)是具体的,实际的,代表一个具体变量。(dogo)

结束了对比,现在来正式介绍Golang的结构体(struct)

声明结构体

  1. 语法结构
type 结构体名称 struct {
    field1 type //字段属性
    field2 type
    ...
}

具体例子上面有描述,这里就不赘述了。

  1. 字段属性

  字段是结构体的一个重要组成部分,一般是基本数据类型、数组,也可以是引用类型

type Person struct{
    name string
    age int
    scores [3]float64 //数组
    ptr *int //指针
    slice []int //切片
    mapp map[string]string //map
}

//如果结构体的字段类型是:指针,slice和map的零值都是nil,表示还没有分配空间
//如果要使用,就要先make,才能使用。

func main(){
    var per Person
    
    //使用slice ,一定要先用make
    per.slice = make([]int, 5)
    per.slice[0] = 3
    
    //使用map,一定要先用make
    per.mapp = make(map[string]string)
    per.mapp["key"] = "aaa"
    
    fmt.Println(per)
}
  1. 字段的独立性

  不同结构体变量(实例)的字段是独立的,互不影响,一个实例的字段改变了,不影响另外一个实例。

type Dog struct {
    name string
    age int
}


func main(){
    //不同实例的字段互不影响
    var dog1 Dog
    dog1.name = "小黄"
    dog1.age = 1
    
    dog2 := dog1 //结构体是值类型,默认是值拷贝
    dog2.name = "小白"
    
    fmt.Println("dog1=",dog1) //dog1 = {小黄 1},dog1的name没有受到影响
    fmt.Println("dog2=",dog2) //dog2 = {小白 1}
}

创建与访问

  在结构体声明后,就要去创建使用,对于结构体的创建使用主要有几个方式:
  这里取Dog的结构体为例

type Dog struct {
    name string
    age int
}
//方式一:
//这个方式就是最基础的直接声明
type doge Dog

//方式二:
doge := Dog{"小黄", 2}

//方式三:
var doge *Dog = new(Dog)

(*doge).name = "aa" //因为doge是一个指针,所以标准的赋值方式
doge.name = "bb" //不过这种方式也支持,因为在底层有对doge.name = "bb"进行处理

(*doge).age = 2
doge.age = 3
fmt.Println(*doge) //返回结构体的指针

//方式四:
var doge *Dog = &Dog{}

(*doge).name = "aa"
doge.name = "bb"

(*doge).age = 2
doge.age = 3
fmt.Println(*doge) //返回结构体的指针

方法

  在一些场景下,结构体单单只是属性是不够的,比如像Person结构体除了要有一些属性(name,age),还需要有一些行为:可以说话、奔跑这些,所以这时候结构体就要用到方法。

  golang中的方法是作用在指定的数据类型上的(和指定的数据类型绑定),所以自定义类型都可以有方法,不单单是struct。

  1. 方法的声明和调用
type C struct{
    num int
}

func (c C) test(){
    fmt.Println(c.num)
}


这段例子表示:
1. func(c C) test(){}  表示C结构体有一个方法名叫做test()
2. (c C)表示test()方法是和C类型绑定的

具体的例子说明

type Dog struct {
    name string
    age int
}

func(d Dog) say(){
    fmt.Println("say() name = ", d.name)
}

func main(){
    var d Dog
    d.name = "啊黄"
    d.say()  // 调用方法
}

上面的代码体现出来几个点:

  1. say方法和Dog类型绑定。
  2. say方法只能通过Dog类型的变量来调用,不能直接调用,也不能使用其他类型变量进行调用。
  3. func(d Dog) say(){}中的d表示哪个Dog变量调用。

方法也可以接收参数,比如

func (d Dog) say(n int){
    for i := 1; i<= n; i++{
        fmt.Println("第%d次叫",i)    
    }
}

//调用
d.say(2)

方法也可以返回参数,比如

func (d Dog) cout(n int) int {
    res := 0
    for i := 1; i<= n; i++ {
        res += 1    
    }
    return res
}

//调用
res := d.cout(5)
  1. 使用方法需要注意

(1) 结构体类型是值类型,在方法调用中,要遵守值类型的传递机制,是值拷贝传递方式

(2) 若想在方法中修改结构体变量的值,可以通过结构体的指针来处理。

//为了提高效率,通常将方法和结构体的指针类型绑定
func (d *Dog) cout() int {
    retrun 2
}

//调用
res := d.cout() //等同于res := (&d).cout() ,只是底层做了优化

(3) golang中的方法作用在指定的数据类型上的(也就是和指定的数据类型绑定),所以自定义类型都可以拥有方法,不单单是struct,还可以是int ,float64都可以拥有方法。

func (i integer) print() {
    fmt.Println("i=", i)
}

func (i *integer) change(){
    *i = *i + 1
}

func main(){
    var i integer = 10
    i.print()
    i.change()
    fmt.Println("i=", i)
}

(4) 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

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

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
}

//定义一个Student变量
stu := Student{
    name : "jam"
    age : 10
}
//如果实现了*Student 类型的String方法,就会自动调用
fmt.Println(&stu)
### Golang 中指针与结构体的使用 #### 定义结构体并创建实例 在 Go 语言中,可以通过 `struct` 关键字来定义定义数据类型。下面展示了一个简单的员工结构体定义: ```go type Employee struct { id string name string } ``` 为了初始化该结构体,有两种方式:一种是通过值接收者的方式;另一种则是通过指针接收者的方式来操作。 #### 方法绑定到结构体上 对于结构体的方法定义,可以选择是否传递其指针作为接收者参数。如果希望修改原始结构体中的字段,则应采用指针形式[^1]。 ```go // 使用指针接收者的Set方法可以直接改变原结构体内存位置上的值 func (e *Employee) SetName(name string) { e.name = name } // 值接收者版本则会作用于副本而非实际对象本身 func (e Employee) SetValueReceiverExample(newID string){ e.id = newID // 这里只会影响拷贝后的临时变量id, 不影响原来的emp1.id } ``` #### 创建结构体实例及其内存模型理解 当声明一个新的结构体变量时,默认情况下它是按值传递的。这意味着每次将其赋给新变量或将其实例传入函数调用时都会复制整个结构体的内容。然而,在某些场景下这并不是期望的行为——特别是当我们想要多个地方共享同一份数据或者需要高效地处理大型结构体的时候。这时就引入了指针的概念[^2]。 考虑如下代码片段: ```go package main import ( "fmt" ) type Employee struct{ id string name string } func main(){ p1 := &Employee{id:"007",name:"Bond"} fmt.Printf("Before modification: %v\n",*p1) var p3 = p1 // 此处p3和p1都指向相同的堆分配区域 p3.name="New Name" fmt.Printf("After modifying via p3: Original pointer p1 -> %v\n",*p1) } ``` 上述例子展示了如何利用指针对相同的数据进行间接访问以及由此带来的副作用特性。一旦两个不同的名称(比如这里的 `p1`, `p3`) 绑定了同一个地址空间内的资源之后,任何一方对该资源所做的更改都将反映在整个程序范围内可见的地方。 另外值得注意的一点是在Go里面还可以很方便地实现结构体之间的嵌套关系,从而构建更加复杂的数据结构[^3]: ```go type Address struct { city, state string } type Person struct { name string age int address Address } var person = Person{} person.name = "kuangshen" person.age = 18 person.address = Address{city: "广州", state: "中国"} fmt.Println(person.name) fmt.Println(person.age) fmt.Println(person.address.city) ``` 这段代码说明了即使在一个较大的复合型态内部仍然能够轻松管理各个组成部分间的关联性而不必担心深浅拷贝等问题所带来的困扰。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是哈猿啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值