13、结构体

结构体

定义

使用type定义结构体,可以把结构体看做类型使用。必须指定结构体的字段(属性)名称和类型。

type User struct {
    id int
    name,addr string
    score float32
}
  • 真正的结构体是struct{}这个整体的
  • User只是一个标识符指代结构体

初始化

package main

import "fmt"

type User struct {
	id         int
	name, addr string
	score      float32
}

func main() {
	// 1 声明
	var u1 User             // 这种方式声明结构体变量很方便,所有字段都是零值
	fmt.Println(u1)         //{0   0} 因为name和addr是string 所以只占位
	fmt.Printf("%+v\n", u1) // 加上字段打印{id:0 name: addr: score:0}
	fmt.Printf("%#v\n", u1) // 加上更多信息打印 main.User{id:0, name:"", addr:"", score:0}

	//2 字面量初始化 推荐用这个
	var u2 = User{}         // 字段为零值
	fmt.Printf("%+v\n", u2) //{id:0 name: addr: score:0}

	//3 字面量初始化,field: value为字段赋值
	u3 := User{id: 102}
	fmt.Printf("%+v\n", u3) //{id:102 name: addr: score:0}

	u4 := User{id: 12, score: 99, name: "zfl", addr: "xian"} //如果按照名称对应的话,无所谓顺序
	fmt.Printf("%+v\n", u4)                                  // {id:12 name:zfl addr:xian score:99}

	u5 := User{103, "zfl", "xi'an", 99.9}//如果名称没有对应的话,必须按照结构体定义的顺序来给所有的字段值
	fmt.Printf("%+v\n", u5)	//{id:103 name:zfl addr:xi'an score:99.9}

}

可见性

  • Go包的顶层代码中,首字母大写的标识符,跨package包可见(导出),否则只能本包内可见
  • 导出的结构体,package内外皆可见,同时,导出的结构体中的成员(属性、方法)要在包外也可见,则也需首字母大写
type User struct {
	Id         int
	Name, Addr string
	Score      float32
} //所以结构体的标识符User首字母大写,结构体的成员也需要大写,才可以包外可见

通过字段名打印,修改

package main

import "fmt"

type User struct {
	id         int
	name, addr string
	score      float32
}

func main() {
	u1 := User{id: 12, score: 99, name: "zfl", addr: "xian"}
	fmt.Printf("%+v\n", u1)        //{id:12 name:zfl addr:xian score:99}
	fmt.Println(u1.id, u1.addr)    //12 xian
	fmt.Println(u1.name, u1.score) //zfl 99
	//直接修改id,通过字段名
	u1.id = 104		
	fmt.Println(u1.id) //104
}

成员方法

package main

import "fmt"

type User struct {
	id         int
	name, addr string
	score      float32
}

// u称为receiver
// 等价于 func (User) string
func (u User) getName() string {
	return u.name
}

func main() {
	u1 := User{18, "zfl", "xa", 99.9}
	fmt.Println(u1.getName())
}

指针

package main

import "fmt"

type Point struct {
	x, y int
}

func main() {
	var p1 = Point{100, 200}     //字面量初始化,一个实例
	fmt.Printf("%T %[1]v\n", p1) //main.Point {100 200}

	var p2 = &Point{5, 6}
	fmt.Printf("%T %[1]v\n", p2) //*main.Point &{5 6}

	var p3 = new(Point)
	fmt.Printf("%T %[1]v\n", p3) //*main.Point &{0 0}

	// 通过实例修改属性
	p1.x = 10
	fmt.Printf("%T, %[1]v\n", p1) //main.Point, {10 200}

	// 通过指针修改属性
	p2.x = 200
	p3.x = 300
	fmt.Printf("%T, %[1]v\n", p2) //*main.Point, &{200 6}
	fmt.Printf("%T, %[1]v\n", p3) //*main.Point, &{300 0}

	// p3.x中. 是 -> 的语法糖,更方便使用。等价于(*p3).x
	fmt.Print(*p3, (*p3).x) // {300 0} 300
}


package main

import "fmt"

type Point struct {
	x, y int
}

func test(p Point) Point {
	fmt.Printf("4 %+v %p\n", p, &p) //4 {x:10 y:20} 0xc00001a160
	return p
}
func main() {
	var p1 = Point{10, 20}
	fmt.Printf("1 %+v %p\n", p1, &p1) //1 {x:10 y:20} 0xc00001a0b0

	p2 := p1
	fmt.Printf("2 %+v %p\n", p2, &p2) //2 {x:10 y:20} 0xc00001a100

	p3 := &p1
	fmt.Printf("3 %+v %p\n", p3, p3) //3 &{x:10 y:20} 0xc00001a0b0
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")

	p4 := test(p1)
	fmt.Printf("5 %+v %p\n", p4, &p4) //5 {x:10 y:20} 0xc00001a150
}

// 1 {x:10 y:20} 0xc00001a0b0
// 2 {x:10 y:20} 0xc00001a100
// 3 &{x:10 y:20} 0xc00001a0b0
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 4 {x:10 y:20} 0xc00001a160
// 5 {x:10 y:20} 0xc00001a150

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


package main

import "fmt"

type Point struct {
	x, y int
}

func test(p *Point) *Point {
	p.x += 100
	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)
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
	p4 := test(p3)
	fmt.Printf("5 %+v %p\n", p1, &p1)
	fmt.Printf("6 %+v %p\n", p4, p4)
	p4.x += 200
	fmt.Printf("7 %+v %p\n", p1, &p1)
	fmt.Printf("8 %+v %p\n", p4, p4)
}
1 {x:10 y:20} 0xc00001a0b0
2 {x:10 y:20} 0xc00001a100
3 &{x:10 y:20} 0xc00001a0b0
​~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 &{x:110 y:20} 0xc00001a0b0
5 {x:110 y:20} 0xc00001a0b0
6 &{x:110 y:20} 0xc00001a0b0
7 {x:310 y:20} 0xc00001a0b0
8 &{x:310 y:20} 0xc00001a0b0
//说明,使用了同一个内存区域中的结构体实例,减少了拷贝。

匿名结构体

​ 匿名结构体:标识符直接使用struct部分结构体本身来作为类型,而不是使用type定义的有名字的结构体的标识符。

​ 可以使用 var 、 const 、 := 来定义匿名结构体。
​ type定义结构体的标识符,可以反复定义其结构体实例,但是匿名结构体是一次性的。

//定义一个结构体类型,指代其的标识符名称为Point
type Point struct {
	x, y int
}
//使用var定义一个变量point,后面跟类型,这个类型没有名字,只有结构体的本身
var Point struct {
	x, y int
}
package main

import "fmt"

var Point struct {
	x, y int
} // 定义Point是后面匿名结构体类型的,用零值

func main() {
   	fmt.Printf("%#v\n", Point) //struct { x int; y int }{x:0, y:0}
}


package main

import "fmt"

var message = struct {
	id   int
	data string
}{1, "ok"}

func main() {
	fmt.Println(message)         //{1 ok}
	fmt.Printf("%#v\n", message) //struct { id int; data string }{id:1, data:"ok"}
}

匿名结构体,只是为了快速方便地得到一个结构体实例,而不是使用结构体创建N个实例。

匿名成员

有时候属性名可以省略

package main

import "fmt"

type Point struct {
	x    int
	int  // 字段,匿名成员变量
	bool // 匿名,必须类型不一样才能区分
}

var p1 = Point{1, 2, false}
var p2 = Point{x: 20, int: 5, bool: false}

func main() {
	fmt.Println(p1)  //{1 2 false}
	fmt.Println(p2, p2.x, p2.int, p2.bool) //{20 5 false} 20 5 false
}
{1 2 false}
{20 5 false} 20 5 false

构造函数

​ Go语言并没有从语言层面为结构体提供什么构造器,但是有时候可以通过一个函数为结构体初始化提供属性值,从而方便得到一个结构体实例。习惯上,函数命名为 NewXxx 的形式。

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

func NewAnimal(name string, age int) Animal {
	a := Animal{name, age}
	fmt.Printf("%+v,%p\n", a, &a)
	return a
}

func main() {
	a := NewAnimal("zfl", 20)
	fmt.Printf("%+v,%p\n", a, &a)
}
{name:zfl age:20},0xc000008090
{name:zfl age:20},0xc000008078
//NewAnimal的返回值使用了值拷贝,增加了内存开销,习惯上返回值会采用指针类型,避免实例的拷贝。
package main

import "fmt"

type Animal struct {
	name string
	age  int
}

func NewAnimal(name string, age int) *Animal {
	a := Animal{name, age}
	fmt.Printf("%+v, %p\n", a, &a)
	return &a
}

func main() {
	a := NewAnimal("zfl", 20)
	fmt.Printf("%+v,%p\n", a, a)
}
{name:zfl age:20}, 0xc000008078
&{name:zfl age:20},0xc000008078

父子关系构造

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

type Cat struct {
	Animal // 匿名成员,可以使用类型名作为访问的属性名
	color  string
}

func main() {
	var cat = new(Cat) // Cat实例化,Animal同时被实例化
	fmt.Printf("%#v\n", cat)
	cat.color = "black"     // 子结构体属性
	cat.Animal.name = "Tom" // 完整属性访问
	cat.age = 20            // 简化写法,只有匿名成员才有这种效果
	fmt.Printf("%#v\n", cat)

}
&main.Cat{Animal:main.Animal{name:"", age:0}, color:""}
&main.Cat{Animal:main.Animal{name:"Tom", age:20}, color:"black"}
//使用结构体嵌套实现类似面向对象父类子类继承(派生)的效果
//子结构体使用匿名成员能简化调用父结构体成员

指针类型receiver

​ Go语言中,可以为任意类型包括结构体增加方法,形式是 func Receiver 方法名 签名 {函数体} ,这个receiver类似其他语言中的this或self。

​ receiver必须是一个类型T实例或者类型T的指针,T不能是指针或接口。

package main

import "fmt"

type Point struct {
	x, y int
}

func (p Point) getX() int {
	fmt.Println("instance")
	return p.x
}
func (p Point) getY() int {
	fmt.Println("pointer")
	return p.y
}
func main() {
	p := Point{4, 5}
	fmt.Println(p)
	fmt.Println(p.getX(), (&p).getX())
	fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
	fmt.Println(p.getY(), (&p).getY())
}
{4 5}
instance
instance
4 4
​~~~~~~~~~~~~~~~~~~~~~~~~~~~
pointer
pointer
5 5

接收器receiver可以是类型T也可以是指针*T

package main

import (
	"fmt"
)

type Point struct {
	x, y int
}

func (p Point) setX(v int) {
	fmt.Printf("1 %+v,%p\n", p, &p)
	p.x = v
	fmt.Printf("2 %+v,%p\n", p, &p)
}

func (p *Point) setY(v int) {
	fmt.Printf("3 %+v, %p\n", p, p)
	p.y = v
	fmt.Printf("4 %+v, %p\n", p, p)
}

func main() {
	p := Point{4, 5}
	fmt.Printf("5 %+v, %p\n", p, &p)
	p.setX(11) // 实例调用是值拷贝
	p.setY(22) // 看似实例调用,实则是指针,操作同一处内存
	fmt.Printf("6 %+v, %p\n", p, &p)
}
5 {x:4 y:5}, 0xc0000a6070
1 {x:4 y:5},0xc0000a60c0
2 {x:11 y:5},0xc0000a60c0

3 &{x:4 y:5}, 0xc0000a6070
4 &{x:4 y:22}, 0xc0000a6070
6 {x:4 y:22}, 0xc0000a6070

​ 非常明显,如果是非指针接收器方法调用有值拷贝,操作的是副本,而指针接收器方法调用操作的是同一个内存的同一个实例。
​ 如果是操作大内存对象时,且操作同一个实例时,一定要采用指针接收器的方法。

深浅拷贝

  • shadow copy
    影子拷贝,也叫浅拷贝。遇到引用类型数据,仅仅复制一个引用而已
  • deep copy
    深拷贝,往往会递归复制一定深度

​ 注意,深浅拷贝说的是拷贝过程中是否发生递归拷贝,也就是说如果某个值是一个地址,是只复制这个地址 ,还是复制地址指向的内容。

值拷贝是深拷贝,地址拷贝是浅拷贝,这种说法是错误的。因为地址拷贝只是拷贝了地址,因此本质上来讲也是值拷贝。

​ Go语言中,引用类型实际上拷贝的是标头值,这也是值拷贝,并没有通过标头值中对底层数据结构的指针指向的内容进行复制,这就是浅拷贝。非引用类型的复制就是值拷贝,也就是再造一个副本,这也是浅拷贝。因为你不能说对一个整数值在内存中复制出一个副本,就是深的拷贝。像整数类型这样的基本类型就是一个单独的值,没法深入拷贝,根本没法去讲深入的事儿。

​ 简单讲,大家可以用拷贝文件是否对软链接跟进来理解。直接复制软链接就是浅拷贝,钻进软链接里面复制其内容就是深拷贝。

​ 复杂数据结构,往往会有嵌套,有时嵌套很深,如果都采用深拷贝,那代价很高,所以,浅拷贝才是语言普遍采用的方案。

基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
结构体对象是结构体类型的一个具体实例。在 C 或 C++ 编程语言中,用户可以通过定义结构体类型来创建一种新的数据类型,然后通过声明该类型的变量来创建具体的结构体对象。结构体对象可以包含多个不同类型的数据成员,这些数据成员共同描述一个特定的实体[^1]。 ### 结构体对象的定义 结构体对象的定义通常包括两个步骤:首先定义结构体类型,然后声明该类型的变量(即结构体对象)。也可以在定义结构体类型的同时声明结构体对象。例如: ```c struct DATE { int year; int month; int day; } d1, d2; // 定义结构体类型的同时声明结构体对象 ``` 上面的代码定义了一个名为 `DATE` 的结构体类型,它包含三个整型成员:`year`、`month` 和 `day`。接着,`d1` 和 `d2` 是该结构体类型的两个对象[^2]。 ### 结构体对象的使用方法 一旦定义结构体对象,就可以通过点操作符(`.`)来访问其成员。例如,可以为 `d1` 的成员赋值: ```c d1.year = 2023; d1.month = 10; d1.day = 5; ``` 同样地,也可以通过点操作符来读取结构体对象的成员值: ```c printf("Date: %d-%d-%d\n", d1.year, d1.month, d1.day); ``` 此外,还可以使用结构体指针来访问结构体对象的成员。此时需要使用箭头操作符(`->`): ```c struct DATE *pd = &d1; pd->year = 2024; // 使用箭头操作符访问结构体指针的成员 ``` ### 示例代码 以下是一个完整的示例,演示了如何定义结构体类型、声明结构体对象以及访问其成员: ```c #include <stdio.h> // 定义结构体类型 struct DATE { int year; int month; int day; }; int main() { // 声明结构体对象 struct DATE d1; // 为结构体对象的成员赋值 d1.year = 2023; d1.month = 10; d1.day = 5; // 访问结构体对象的成员 printf("Date: %d-%d-%d\n", d1.year, d1.month, d1.day); return 0; } ``` ### 结构体对象的嵌套使用 结构体对象的成员也可以是另一个结构体对象。例如,在 C++ 中可以这样定义: ```cpp #include <iostream> #include <string> using namespace std; struct student { string name; int age; int score; }; struct Teacher { string code; string type; struct student stu; // 结构体嵌套结构体 }; void structNesting() { Teacher ToNi[] = { {"球球", "炼丹", {"小孟", 14, 58}}, {"冷萌", "炼器", {"萧炎", 12, 80}}, {"球球", "炼丹", {"小花", 13, 57}} }; for (auto &i : ToNi) { if (i.code == "球球") { cout << "教师 职工姓名: " << i.code << " \t教授科目: " << i.type << endl; cout << "辅导学员 姓名: " << i.stu.name << " \t年龄:" << i.stu.age << " \t考试分数: " << i.stu.score << endl; } } } int main() { structNesting(); return 0; } ``` 上述代码中,`Teacher` 结构体包含了一个 `student` 结构体对象作为其成员,从而实现了结构体的嵌套使用[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值