Skip to content

结构体

作者: ryan 发布于: 2025/7/31 更新于: 2025/7/31 字数: 0 字 阅读: 0 分钟

Go的结构体在数据封装层面类似“类”,但通过组合替代继承接口实现多态,以及首字母大小写控制封装,构建了一套去中心化、高内聚低耦合的轻量OOP模型。它并非“不完整的类”,而是对OOP范式的重新设计,更符合现代高并发与模块化开发需求。

结构体定义

结构体是自定义类型

go
type User struct {  
    name,addr string  
    age       int  
    id        int  
}

User是一个标识符(标识符的本质就是一个指代) 真正的类型定义是struct{}


初始化

类型和实例

int、string 、bool结构体都是类型

1 是 int的一个实例,它是int类型

"" 是string的一个实例,它是string类型 [3]int 是类型,[3]int{}字面量表达一个数组,它是[3]int类型

结构体实例化时通过字面量初始化,如 struct{}{} 创建空结构体实例。


1.var 声明

零值可用

go
type User struct {  
    id         int  
    name, addr string  
    score      float32  
}  
  
func main() {  
 var u1 User //这种方式声明结构体变量很方便,所有字段都是零值  
 fmt.Printf("%T\n", u1)  
 fmt.Println(u1)  
   
 fmt.Printf("%v\n", u1) //默认打印  
 fmt.Printf("%+v\n",u1)  //加上字段打印  
 fmt.Printf("%#v\n",u1) //加上更多信息打印  
}

2.字面量初始化

go
u2 := User{}  //字段为零值  
fmt.Printf("%#v\n", u2)

3.字面量初始化字段赋值

通过 field: value 语法直接为结构体的字段赋值的简洁方式。

go
u3 := User{id: 100}  
fmt.Printf("%#v\n", u3)
go
u4 := User{  
    id: 102,score: 88.5,  
    addr: "beijing",name: "jack",  
} //名称对应无所谓顺序  
  
fmt.Printf("%#v\n", u4)
go
u5 := User{103,"ryan","beijing",92.5} //无字段名称必须按照顺序给出全部字段值  
fmt.Printf("%#v\n", u5)

可见性

Go包的顶层代码中,首字母大写的标识符,跨package包可见(导出),否则只能本包内可见 导出的结构体,package内外皆可见,同时,导出的结构体中的成员(属性、方法)要在包外也可见,则也需首字母大写

属性访问

可以使用字段名称访问

go
u1 = User{110,"mm","beijing",82.5}  
fmt.Println(u1.id,u1.name,u1.score)

属性修改

通过字段来修改

go
u1 = User{110, "mm", "beijing", 82.5}  
fmt.Println(u1.id, u1.name, u1.score)
u1.name = "Tom"  
u1.score = 100  
fmt.Println(u1)

成员方法

结构体的方法、属性,成为成员。方法也称为成员方法,属性也称为成员属性。

普通函数与结构体方法虽最终目标一致(操作数据),但通过 接收者机制,Go 方法实现了更自然的面向对象调用风格,使代码更符合“对象行为”的直觉表达,而普通函数更适合通用工具操作。

go
package main  
  
import "fmt"  
  
type User struct {  
    id         int  
    name, addr string  
    score      float32  
}  
  
// 一个普通的函数 ,独立的全局函数,无状态关联。
func getName(u User) string {  
    return u.name  
}  
  
// 结构体方法,本质上还是函数  
// u receiver 接收者
//(u User)声明该方法关联到 User 类型,u是接收者的副本(值接收者)
func (u User) getName() string {  
    return u.name  
}  
  
func main() {  
    var u5 = User{111, "ben", "nanjing", 98.5}  
    fmt.Printf("%T %[1]v\n", u5)  
    fmt.Println(u5.name, getName(u5))  
    fmt.Println(u5.name)  
    fmt.Println(u5.getName())  
}

结构体指针

go
package main  
  
import "fmt"  
  
type Point struct {  
    x, y int  
}  
  
func main() {  
    var p1 = Point{1, 2}   //实例
    fmt.Printf("%T %[1]v\n", p1)   //Point,{1,2}  
  
    var p2 = &Point{3, 4}   //指针
    fmt.Printf("%T %[1]v\n", p2)   //*Point,&{3,4} 
  
    var p3 = new(Point) //new 实例化一个结构体并返回  
    fmt.Printf("%T %[1]v\n", p3)  // *Point,&{0,0}
    fmt.Println("~~~~~~~~~~~~~~~~~~~~~")  

//通过实例修改属性
    p1.x = 100  
    fmt.Printf("%T %[1]v\n", p1)  //Point,{100,2}
    //通过指针修改属性
    p2.x = 200  
    p3.x = 300  
    fmt.Printf("%T %[1]v\n", p2) //*Point, &{200,4}  
    fmt.Printf("%T %[1]v\n", p3)  //*Point, &{300,0}
// p3.x中,是 -> 的语法糖,更方便使用,等价于(*p3.x)
    fmt.Print(*p3,(*p3).x) //{300 0} 300
}

go 语言为我们提供的语法糖通过指针直接就能修改结构体,不需要 c++ 的 p2 ->x

(*p3).xp3.x 的区别

(*p3).x 是通过地址取值找到结构体,通过结构体在取其值

p3.x 通过指针访问 x


go
package main  
  
import "fmt"  
  
type Point struct {  
    x, y int  
}  
  
func test(p Point) Point {  
    fmt.Printf("4 %+v %p\n", p, &p)  
    return p  
}  
  
func main() {  
    var p1 = Point{10, 20} //实例  
    fmt.Printf("1 %+v %p\n", p1, &p1)  
  
    p2 := p1  
    fmt.Printf("2 %+v %p\n", p2, &p2)  
  
    p3 := &p1  
    fmt.Printf("3 %+v %p\n", p3, p3)  
  
    p4 := test(p1)  
    fmt.Printf("5 %+v %p\n", p4, &p4)  
  
}

输出:

go
1 {x:10 y:20} 0x188c0a8
2 {x:10 y:20} 0x188c0f0
3 &{x:10 y:20} 0x188c0a8
4 {x:10 y:20} 0x188c110
5 {x:10 y:20} 0x188c108

可以看出,结构体是非引用类型,使用的是值拷贝。传参或返回值如果使用结构体实例,将产生很多副本。 如何避免过多副本,如何保证函数内外使用的是同一个结构体实例呢?使用指针。


结构体的值传递与指针传递的区别

go
type Point struct {
    x, y int
}



func test(p *Point) *Point {
    p.x += 100            // 通过指针修改原始数据的字段
    fmt.Printf("4 %+v %p\n", p, p) // 4 &{x:110 y:20} 0xc0000100c0
    return p
}


func main() {
    var p1 = Point{10, 20} // 创建 Point 实例 p1 (值类型)
    fmt.Printf("1 %+v %p\n", p1, &p1) // 1 {x:10 y:20} 0xc0000100c0

    
    p2 := p1  // 将 p1 的值复制到 p2(独立副本)
    //p2 是 p1 的完整副本,地址不同,修改 p2 不影响p1。
    fmt.Printf("2 %+v %p\n", p2, &p2) // 2 {x:10 y:20} 0xc0000100e0  
    
    p3 := &p1  // 创建指向 p1 的指针 p3
    //p3 存储p1的内存地址(0xc0000100c0),操作 p3 会直接影响 p1
    fmt.Printf("3 %+v %p\n", p3, p3) // 3 &{x:10 y:20} 0xc0000100c0


    p4 := test(p3) // 传入 p3 指针(即 p1 的地址)
    //test函数:通过指针 p 将 p1.x 从 10 改为 110。返回值 p4与 p3 指向同一地址0xc0000100c0


    p4.x += 200 // p4 指向 p1,直接修改 p1.x
    //- 通过 p4 修改 x 后,p1.x 变为 310(两者指向同一内存)。
    fmt.Printf("5 %+v %p\n", p1, &p1) // 5 {x:310 y:20} 0xc0000100c0
    fmt.Printf("6 %+v %p\n", p4, p4) // 6 &{x:310 y:20} 0xc0000100c0



     p5 := p3         // p5 也指向 p1 的地址
     p5.y = 400       // 修改 p5.y 即修改 p1.y
     fmt.Printf("7 %+v %p\n", p1, &p1) // 7 {x:310 y:400} 0xc0000100c0
     fmt.Printf("8 %+v %p\n", p5, p5) // 8 &{x:310 y:400} 0xc0000100c0
}
  • p3p4p5 均指向 p1,任一指针修改字段都会改变 p1 的值。

指针就是大整数 地址结构体的指针怎么获取?

  1. 先创建实例,取地址
go
var p1 Point   &p1
var p1 = Point{}  &p1

//主要为了填充结构体中的值
var p1 = Point{1,2}  &p1
  1. 用new,定义出该结构体的零值实例并返回该实例的指针new(Point)