【Go基础】结构体
1. 结构体引入
Golang也支持面向对象编程,但是和传统的面向对象有区别,并不是像Java、C++那样纯粹的面向对象语言,而是通过特别的手段实现面向对象特点。
Golang没有类(Class)的概念,但是提供了结构体(struct),和其他编程语言中的类(class)有同等地位,可以理解为 :Go基于struct"拐弯抹角"的实现了面向对象的特点。
Golang面向对象编程非常简洁,去掉了传统OOP语言的 方法重载、构造函数、和析构函数、this、super一系列东西,但是保留了封装、继承、多态的特性,只是和其他语言的实现方式不同。
2. 结构体的定义
使用 type
和 struct
定义一个结构体
type People struct {
}
People就成了一个结构体,当然可以为People添加一些字段
type People struct {
name string
age string
addr string
}
定义之后如何声明呢 ?
有以下几种方式 :
-
使用
var 变量名 结构体类型
var people People people.name = "小明" people.age = 18 fmt.Println(people) // {小明 18}
-
变量名 := new(结构体类型)
new返回的指向特定类型的指针,所以这种方式返回的是 指向一个People结构体 的指针
people := new(People) people.name = "小明" people.age = 18 fmt.Println(people) // &{小明 18}
为什么people是指针,还能使用
.
来访问结构体中的变量呢?使用(*people).name 、(*people).age
当然可以访问,只是这种不好写,Go为我们进行了优化。如果第一种方式和这种方式让你选一种,你用哪一个呢?当一个结构体中的字段特别多时,直接创建该结构体会占用大量空间,但是指针不一样,指针固定大小。所以以上两种第二种常用。不过还有其他方式。
-
变量名 := 结构体类型{字段1:值, 字段1:值,}
使用这种方式跟第一种差不多,都是直接开辟空间,不过这种方式可以直接对属性赋值
(左边用
var
还是:=
都一样)people := People{ name: "小明", age: 18, } fmt.Printf("people的类型为: %T\n", people) // main.People // 这样也一样 var people1 People = People{ name: "小明", age: 18, }
这可不是json,字段名不加双引号,最后一个字段后要要加逗号。
-
变量名 := &结构体类型{字段1:值, 字段2:值,}
这种方式和第二种像,返回的是一个指向结构体的指针。
people := &People { name: "小明", age: 18, } fmt.Printf("people的类型为: %T\n", people) // *main.People
这四种方式选择哪一种,这要看习惯与需求,熟练用其中一种,可以看懂其他的方式就行。
初始化时可以直接列出全部的字段值,这种方式的缺点是 :无法指定字段,且一次性需要给全部字段赋值,如果后续由增加或删除,会出问题。
type People struct {
name string
age int
}
func main() {
people := &People {
"小明",
18,
}
}
匿名字段 :没有名字的字段。
type People struct {
string
int
}
func main() {
people := &People {
"小明",
18
}
}
赋值时需要严格按照字段类型与顺序,一般不用匿名字段,因为可读性太差,且如果出现多个string int类型,可读性更差
3. 结构体之间的转换
当两个不同类型的结构体中,字段数量、字段名、字段顺序、字段类型全部相同时,这两个字段可以相互转换。
type People struct {
name string
age int
height float32
}
type Monkey struct {
name string
age int
height float32
}
func main() {
// 声明一个People类型的结构体
people := &People{
name: "小明",
age: 18,
height: 180.4,
}
// 将上述结构体变量转换为 *Monkey 类型
monkey := (*Monkey)(people)
fmt.Println(monkey)
// 打印结果: &{小明 18 180.4}
}
两个结构体是否能相互转换 :
字段数、字段名、字段顺序、字段类型
全部相同,缺一个就无法编译。
4. 结构体嵌套
说人话,结构体里还有结构体。
type Address struct {
province string
city string
}
type People struct {
id int
name string
age int
address Address
}
func main() {
people := &People{
id: 1,
name: "小明",
age: 18,
address: Address{
province: "河北",
city: "衡水",
},
}
fmt.Println(people)
}
需要注意的是,People中的address字段如果是指针,在赋初值时就申请指针,是结构体就直接赋值结构体。
可以直接people.address.city访问嵌套结构体中的值.
fmt.Println(people.address.city)
这个经常使用,因为仅仅使用一个结构体无法描述出一个复杂的类型,就可以分为多个字段。
结构体中也可以有结构体数组,不过很少用这种方式一个一个new,都是从前端传来的json直接转换。
package main
import (
"fmt"
"strconv"
)
type Phone struct {
name string
price int
}
func NewPhone(name string, price int) Phone {
return Phone{
name: name,
price: price,
}
}
type People struct {
name string
phones []Phone
}
func NewPeople(name string, phones []Phone) People {
return People{
name: name,
phones: phones,
}
}
func main() {
// 创建一个People数组
people := NewPeople("小明", make([]Phone, 5, 200))
// 循环给people数组中的值赋值
for i := 0; i < 5; i++ {
phoneName := "小米" + strconv.Itoa(i)
phone := NewPhone(phoneName, 1000*(i+1))
//
people.phones[i] = phone
// 如果在初始化切片时指定长度为0或者小于5,使用append()扩容
// people.phones = append(people.phones, phone)
}
fmt.Println(people)
}
5. 方法的引入
方法是作用在指定的数据类型上、和指定的数据类型绑定,因此自定义类型都可以有方法,通常给结构体定义一些方法,以便外部调用,就像Java中的setter/getter方法、C++中的构造函数、析构函数一样。当然,Go语言中的方法并没有放在结构体中,而是和结构体分离。
形式:
func (变量名 : 自定义类型) 方法名(参数列表) (返回值列表) {
// 方法体
}
第一个括号中的变量不需要传入,而是==“谁调用,这个变量就是谁”==
当然可以不使用第一个变量。
给People定义一个初始化方法:
type People struct {
name string
age int
}
func newPeople(name string, age int) (*People) {
return &People {
people.name = name
people.age = age
}
}
func main() {
people := newPeople("小明", 18)
}
这个newPeople方法接收两个变量,name和age,通过这两个变量创造一个Person对象并返回,以后再也不用频繁的写初始化方法了。
以下是使用第一个变量的例子 :
func (people *People) SetAge(age int) {
people.age = age
}
这个方法的调用者必须是People类型的指针,否则会报错。
people := newPeople("小明", 18)
fmt.Println(people) // &{小明 18}
people.SetAge(10)
fmt.Println(people.age) // 10
就是方法的特殊性,让我们可以对结构体进行各种操作。你可以单独定义一个文件,在这个文件中完成对结构体的封装。
方法和函数的区别 :
- 方法被调用者影响。
- 函数与没有具体的调用者。