Go语言基础六:结构体和方法
作者:互联网
结构体
结构体是一个由用户定义的复合类型,它由一系列属性组成,每个属性都有自己的类型和值。Go语言中数组可以存储同一类型的数据,但在结构体中用户可以为不同项定义不同(或相同)的数据类型。结构体是值类型,因此可以通过new()
函数来创建。
定义结构体
结构体的定义需要使用type
和struct
语句。struct
语句定义一个新的数据类型,type
语句用来设定结构体的名称。
type identifier struct {
member1 type1
member2 type2
}
//当然如果有属性的类型时相同的你也可以下面的方式简写
type identifier struct {
//member1和member2就是同一种数据类型
member1, member2 type1
member3 type2
}
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正的分配内存。因此必须在定义结构体并实例化以后才能使用结构体的字段。
声明一个变量
当完成了对结构体的定义之后,就可以使用如下的方式来声明一个结构体变量。
var_name := struct_identifier {value1,value2,value3...}
或
var_name := struct_identifier {key1:value1,key2:value2,key3:value3...}
访问结构体的成员
结构体.成员名
结构体的零值
当定义一个机构体以后,并且没有显示地使用任何值初始化时,结构体的每一个字段都默认分配为零值。
下面我们定义一一个学生的结构体,其包含的属性为姓名和年龄。初始化s1
并赋值,初始化s2
但不赋值。
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
s1 := Student{"zs", 12}
var s2 Student
fmt.Println(s1)
fmt.Printf("name=%s,age=%d", s2.name, s2.age)
}
输出结果:
{zs 12}
name=,age=0
Program exited.
指向结构体的指针
Go语言可以使用&
对结构体进行取地址操作,来创建指向结构体的指针。
var1 := &struct_type{}
还可以使用new()
函数来创建结构体指针,以结构体类型为参数,返回一个指向结构体的指针,并且为其指向的结构体变量分配零值。
var1 := new(struct_type)
示例:
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
//s1 := &Student{"zs", 12} 你也可以使用这种方式创建结构体指针
s1 := new(Student)
s1.name = "zs"
s1.age = 12
//s1就是一个指向结构体Student的指针,其类型为 *Student
fmt.Printf("name=%s,age=%d", s1.name, s1.age)
}
在上述代码中,尽管s1
的类型为*Student
,但是我们仍然可以使用s1.name
来访问name
字段,而不是显式地取消引用指针(*s1).name
来访问该字段。
匿名结构体
当然你也可以在不创建新数据类型的情况下声明结构。这些类型的结构称为匿名结构。匿名结构体没有类型名称,无需通过type
关键字定义就可以直接使用。
var_name := struct{
member1 type1
member2 type2
}{key1:value1,key2:value2}
在上述代码中,这个结构体就称为匿名结构体,因为它只创建了一个新的结构体变量var_name
并没有像命名结构体那样定义任何新的结构体类型。
匿名字段
可以使用仅包含类型而没有字段名称的字段创建结构。这些类型的字段称为匿名字段。
下面的代码中创建了一个结构体Student
,其中有两个匿名字段string和int
type Student struct{
string
int
}
匿名字段没有明确的名称,默认情况下匿名字段的名称为其类型。例如在上面Student
的情况下,虽然字段是匿名的,但默认情况采用字段数据类型名做为其字段名称。所以字段名称为int和string
嵌套结构体
结构体的一个字段可能是另一个结构体,这类型的结构体就是嵌套结构体。
例如
package main
import "fmt"
type Address struct {
city string
}
type Student struct {
name string
age int
address Address
}
func main() {
s := Student{
name: "zs",
age: 12,
address:Address{
city:"bj",
},
}
fmt.Printf("name=%s,age=%d,city=%s",s.name,s.age,s.address.city)
}
当这个结构体字段是一个匿名字段时,其中的字段被称为提升字段。因为它们可以被直接访问到,就像它们属于持有匿名结构字段的结构一样。通过下面的例子更好的理解这个定义。
package main
import "fmt"
type Address struct {
city string
}
type Student struct {
name string
age int
Address
}
func main() {
s := Student{
name: "zs",
age: 12,
Address:Address{
city:"bj",
},
}
fmt.Printf("name=%s,age=%d,city=%s",s.name,s.age,s.city)
}
在上述代码中,Student
结构体有一个匿名字段Address
,它是一个机构体。那么Address
结构体的city
字段就被称为提升字段。因为它们可以被直接访问,就好它直接被定义在Student
结构当中一样。所以在代码中我们用s.city
直接访问该字段。
结构体的比较
结构体是值变量,如果结构体内的每个字段都是可以比较的,那么这个结构体就是可以比较的。如果两个结构变量的对应的字段相等,则两个结构变量相等。
方法
方法(method)就是在func
关键字和方法名之间具有一个特殊的接收器类型的函数。这个接收器可以是结构体或非结构体。
在调用方法时,
方法的定义
func(t Type) methodName(paramter list){
}
上述代码片段中,创建了一个名为methodName
的方法,且接收器类型为Type
的方法。t
被称为接收者,可以在方法中访问t
。
方法和函数的区别
Go语言不是一种纯面向对象的语言,它不支持类。因此Go语言中的方法实现了一种类似于类行为的一种方式。方法允许对类型相关的逻辑行为进行分组,类似于类。
同名的方法可以定义在不同的类型(接收器)上,而不允许从在同名的函数。
在调用方法时,可以使用类型的实例值或者指针,编译器会自动根据接收器类型自动在值类型和指针类型之间转换。但是对于函数,你只能传递参数对应的类型,参数是值类型就是值类型,参数是指针类型就必须值指针类型。
示例:
package main
import "fmt"
type Rectangle struct {
length,width int
}
func (r Rectangle) Area() int {
return r.length * r.width
}
func main() {
r := Rectangle{10,5}
r2 := &Rectangle{10,5}
fmt.Printf("Area of rectangle %d\n", r.Area())
fmt.Printf("Area of rectangle %d\n", r2.Area())
}
接收者类型问题
在方法中既可以接受值类型也可以接受指针类型。这两者之间的区别在与,指针类型接收器的方法内部所作的修改对调用者是可见的,而值类型则不然。
示例:
package main
import "fmt"
type Rectangle struct {
length, width int
}
func (r Rectangle) modify(len int) {
r.length = len
}
func (r *Rectangle) modifys(len int) {
r.length = len
}
func main() {
r := Rectangle{10, 5}
r.modify(1)
fmt.Printf("修改没有发生%d\n", r.length)
r.modifys(1)
fmt.Printf("修改已经发生%d\n", r.length)
}
何时使用值类型接收者,何时使用指针类型接收者?
粗暴的结论:如果你不知道怎么选择,那就使用指针。但有时候,使用值接收者会更合理,尤其是效率考虑,比如:不需要修改的小 struct、基础数据类型。以下是一些有用的指导方针:
- 如果接收者是 map、func 或 chan,不用使用指针。如果是 slice,并且方法不会 reslice 或 从分配 slice,不要使用指针;
- 如果方法需要修改接收者,必须使用指针;
- 如果接收者是包含了 sync.Mutex 或类似的同步字段的结构体(struct),接收者必须使用指针,避免拷贝;
- 如果接收者是一个大的结构体或数组,使用指针会更高效。但多大是大?如果所有元素(struct 的字段或数组元素)作为方法的参数传递认为太大,那么作为接收者也是太大。(粗暴一些,所有元素内存超过指针大小,可以考虑使用指针);
- 如果接收者是结构体、数组或 slice,同时,它们的元素是指针,且指向的数据要改变,那么使用指针接收者会更合理;(有点绕,那就总原则:用指针没错);
- 如果接收者是小的数组,或小的没有可变字段或指针的结构体,或者结构体字段只是简单的基础类型,值接收者会更合理;值接收者能减少垃圾回收的压力,一般会优先分配在栈上(记住是一般,因为有可能逃逸);但除非有性能测试验证,否则别因为可以介绍垃圾回收压力,就选择值接收者;
最后再强调一下,如果你拿不定主意,那就用指针接收者。
标签:struct,接收者,指针,name,Student,Go,结构,方法,语言 来源: https://www.cnblogs.com/newbe3three/p/15703795.html