结构体
作者: ryan 发布于: 2025/7/31 更新于: 2025/7/31 字数: 0 字 阅读: 0 分钟
Go的结构体在数据封装层面类似“类”,但通过组合替代继承、接口实现多态,以及首字母大小写控制封装,构建了一套去中心化、高内聚低耦合的轻量OOP模型。它并非“不完整的类”,而是对OOP范式的重新设计,更符合现代高并发与模块化开发需求。
结构体定义
结构体是自定义类型
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 声明
零值可用
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.字面量初始化
u2 := User{} //字段为零值
fmt.Printf("%#v\n", u2)
3.字面量初始化字段赋值
通过 field: value
语法直接为结构体的字段赋值的简洁方式。
u3 := User{id: 100}
fmt.Printf("%#v\n", u3)
u4 := User{
id: 102,score: 88.5,
addr: "beijing",name: "jack",
} //名称对应无所谓顺序
fmt.Printf("%#v\n", u4)
u5 := User{103,"ryan","beijing",92.5} //无字段名称必须按照顺序给出全部字段值
fmt.Printf("%#v\n", u5)
可见性
Go包的顶层代码中,首字母大写的标识符,跨package包可见(导出),否则只能本包内可见 导出的结构体,package内外皆可见,同时,导出的结构体中的成员(属性、方法)要在包外也可见,则也需首字母大写
属性访问
可以使用字段名称访问
u1 = User{110,"mm","beijing",82.5}
fmt.Println(u1.id,u1.name,u1.score)
属性修改
通过字段来修改
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 方法实现了更自然的面向对象调用风格,使代码更符合“对象行为”的直觉表达,而普通函数更适合通用工具操作。
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())
}
结构体指针
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).x
与 p3.x
的区别
(*p3).x
是通过地址取值找到结构体,通过结构体在取其值
p3.x
通过指针访问 x
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)
}
输出:
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
可以看出,结构体是非引用类型,使用的是值拷贝。传参或返回值如果使用结构体实例,将产生很多副本。 如何避免过多副本,如何保证函数内外使用的是同一个结构体实例呢?使用指针。
结构体的值传递与指针传递的区别
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
}
p3
、p4
、p5
均指向p1
,任一指针修改字段都会改变p1
的值。
指针就是大整数 地址结构体的指针怎么获取?
- 先创建实例,取地址
var p1 Point &p1
var p1 = Point{} &p1
//主要为了填充结构体中的值
var p1 = Point{1,2} &p1
- 用new,定义出该结构体的零值实例并返回该实例的指针
new(Point)