学习Go语言基础类语法_Day03结构体

1、前言

go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承、组合等特性。

2、结构体的定义

结构体的定义和类型定义类似,只不过多了一个struct关键字,语法结构如下:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

type:结构体定义关键字

struct_variable_type:结构体类型名称

struct:结构体定义关键字

member definition;:成员定义

3、基本实例1:

下面我们定义一个人的结构体Person

type Person struct {
    id    int
    name  string
    age   int
    email string
}

以上我们定义一个Person结构体,有四个成员,来描述一个Person的信息。

形同类型的可以合并到一行,例如:

type Person struct {
    id, age     int
    name, email string
}

4、声明结构体变量

声明一个结构体变量和声明一个普通变量相同,例如:

var tom Person
fmt.Printf("tom: %v\n", tom)
kite := Person{}
fmt.Printf("kite: %v\n", kite)

也行结果

tom: {0 0  }
kite: {0 0  }

结构体成员,在没有赋值之前都是零值。

5、访问结构体

可以使用点运算符(.),来访问结构体成员,例如:

package main

import "fmt"

func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	var tom Person
	tom.id = 1
	tom.name = "tom"
	tom.age = 20
	tom.email = "tom@gmail.com"
	fmt.Printf("tom: %v\n", tom)
}

运行结果如下

tom: {1 20 tom tom@gmail.com}

6、匿名结构体

如果结构体是临时使用,可以不用起名字,直接使用,例如:

package main

import "fmt"

func main() {
	var dog struct {
		id   int
		name string
	}
	dog.id = 1
	dog.name = "花花"
	fmt.Printf("dog: %v\n", dog)
}

7、结构体的初始化

未初始化的结构体,成员都是零值 int 0 float 0.0 bool false string nil nil

7.1、未初始化实例:

package main

import "fmt"

func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	var tom Person
	fmt.Printf("tom: %v\n", tom)
}

运行结果

tom: {0 0 "" ""}

7.2、使用键值对对结构体进行初始化

package main

import "fmt"

func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	kite := Person{
		id:    1,
		name:  "kite",
		age:   20,
		email: "kite@gmail.com",
	}
	fmt.Printf("kite: %v\n", kite)
}

运行结果

kite: {1 20 kite kite@gmail.com}

7.3、使用值的列表初始化

实例

package main

import "fmt"

func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	kite := Person{
		1,
		20,
		"kite",
		"kite@gmail.com",
	}
	fmt.Printf("kite: %v\n", kite)
}

运行结果

kite: {1 20 kite kite@gmail.com}

注意:

  1. 必须初始化结构体的所有字段。
  2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  3. 该方式不能和键值初始化方式混用。

7.4、部分成员初始化

用不到的成员,可以不进行初始化

package main

import "fmt"

func main() {
	type Person struct {
		id, age     int
		name, email string
	}

	kite := Person{
		id:   1,
		name: "kite",
	}
	fmt.Printf("kite: %v\n", kite)
}

运行结果

kite: {1 0 kite "" }

8、结构体指针

结构体指针和普通的变量指针相同

8.1、普通变量的指针

package main

import "fmt"

func main() {
	var name string
	name = "tom"
    // 声明 p_name为 指针类型变量
	var p_name *string
    // &name 取name地址
	p_name = &name
	fmt.Printf("name: %v\n", name)
    // 输出指针地址
	fmt.Printf("p_name: %v\n", p_name)
    // 输出指针指向的内容值
	fmt.Printf("*p_name: %v\n", *p_name)
}

运行结果

name: tom
p_name: 0xc00010e120
*p_name: tom

8.2、结构体指针

package main

import "fmt"

func main() {
	type Person struct {
		id   int
		name string
	}

	var tom = Person{1, "tom"}

	// var p_person *Person
	// p_person = &tom
	p_person := &tom
    
	fmt.Printf("tom: %v\n", tom)
	fmt.Printf("p_person: %p\n", p_person)
	fmt.Printf("*p_person: %v\n", *p_person)
}

运行结果

tom: {1 tom}
p_person: 0xc000004078
*p_person: {1 tom}

8.3、使用new关键字创建结构体指针

使用new关键字对结构体进行实例化,得到的是结构体的地址,例如:

package main

import "fmt"

func main() {
	type Person struct {
		id   int
		name string
	}

	var p_person = new(Person)
	fmt.Printf("p_person: %T\n", p_person)
}

运行结果

p_person: *main.Person

从运行结果,我们发现p_person为指针类型

8.4、访问结构体指针

访问结构体指针成员,也使用点运算符(.),例如:

package main

import "fmt"

func main() {
	type Person struct {
		id   int
		name string
	}

	var p_person = new(Person)
	fmt.Printf("p_person: %T\n", p_person)

	p_person.id = 1
	p_person.name = "tom"
	fmt.Printf("*p_person: %v\n", *p_person)//输出指针的值需要加`*`来取值
}

运行结果

p_person: *main.Person
*p_person: {1 tom}

9、结构体作为函数参数

go结构体可以像普通变量一样,作为函数的参数,传递给函数,这里分为两种情况:

  1. 直接传递结构体,这是是一个副本(拷贝),在函数内部不会改变外面结构体内容。
  2. 传递结构体指针,这时在函数内部,能够改变外部结构体内容。

9.1、直接传递结构体

package main

import "fmt"

type Person struct {
	id   int
	name string
}

func showPerson(person Person) {
	person.id = 1
	person.name = "kite"
	fmt.Printf("person: %v\n", person)
}

func main() {
	person := Person{1, "tom"}
	fmt.Printf("person: %v\n", person)
	fmt.Println("----------------")
	showPerson(person)
	fmt.Println("----------------")
	fmt.Printf("person: %v\n", person)
}

运行结果

person: {1 tom}
----------------
person: {1 kite}
----------------
person: {1 tom}

从运行结果可以看出,函数内部改变了结构体内容,函数外面并没有被改变。

9.2、传递结构体指针

package main

import "fmt"

type Person struct {
	id   int
	name string
}

func showPerson(person *Person) {
	person.id = 1
	person.name = "kite"
	fmt.Printf("per: %v\n", person)
}

func main() {
	tom := Person{1, "tom"}

	fmt.Printf("per: %v\n", tom)
	fmt.Println("----------------")
	showPerson(&tom)
	fmt.Println("----------------")
	fmt.Printf("per: %v\n", tom)
}

运行结果

person: {1 tom}
----------------
person: &{1 kite}
----------------
person: {1 kite}

从运行结果,我们可以看到,调用函数后,参数被改变了。

Caidd123见解:

主函数这块

func main() {
	tom := Person{1, "tom"}

	fmt.Printf("per: %v\n", tom)
	fmt.Println("----------------")
	showPerson(&tom)
	fmt.Println("----------------")
	fmt.Printf("per: %v\n", tom)
}

还可以这么写

func main() {

	tom := Person{1, "tom"}

	per := &tom

	fmt.Printf("per: %v\n", per)

	fmt.Println("----------------")

	showPerson(per)

	fmt.Println("----------------")

	fmt.Printf("per: %v\n", per)

}

所以Caidd123认为,只要是值类型前面加上& 那么他就是指针了

10、嵌套结构体

go语言没有面向对象编程思想,也没有继承关系,但是可以通过结构体嵌套来实现这种效果。

下面通过实例演示如何实现结构体嵌套,加入有一个人Person结构体,这个人还养了一个宠物Dog结构体。

下面我们来看一下:

Dog结构体

type Dog struct {
	name  string
	color string
	age   int
}

Person结构体

type person struct {
	dog  Dog
	name string
	age  int
}

访问它们

package main

import "fmt"

type Dog struct {
	name  string
	color string
	age   int
}

type person struct {
	dog  Dog
	name string
	age  int
}

func main() {
	var tom person
	tom.dog.name = "花花"
	tom.dog.color = "黑白花"
	tom.dog.age = 2

	tom.name = "tom"
	tom.age = 20

	fmt.Printf("tom: %v\n", tom)
}

运行结果

tom: {{花花 黑白花 2} tom 20}

Caidd123理解:

这个结构体吧,就是一个类型 比如 int string func 等等

所以我们声明变量 也可以声明结构体类型的

结构体定义就是里面是变量对应类型,故也可以使用结构体类型,从而一直套娃下去

遇到什么类型的变量就使用该类型设定的一些东西来使用,同样的遇到了结构体依然可以这么做,从而一直. 下去 per.name.age.opg.xxxx.xxx. ……

11、方法

go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。我们也可以声明一些方法,属于某个结构体。

11.1、语法

Go中的方法,是一种特殊的函数,定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。

通俗的讲,方法就是有接收者的函数。

语法格式如下:

type mytype struct{}

func (recv mytype) my_method(para) return_type {}
func (recv *mytype) my_method(para) return_type {}

mytype:定义一个结构体

recv:接受该方法的结构体(receiver)

my_method:方法名称

para:参数列表

return_type:返回值类型

从语法格式可以看出,一个方法和一个函数非常相似,多了一个接受类型。

11.2、基本实例1:

package main

import "fmt"

type Person struct {
	name string
}
//   接收者是Person
func (per Person) eat() {
	fmt.Println(per.name + " eating....")
}

func (per Person) sleep() {
	fmt.Println(per.name + " sleep....")
}

func main() {
	var per Person
	per.name = "tom"
	per.eat()
	per.sleep()
}

运行结果

tom eating....
tom sleep....

Caidd123理解:

首先呢我们有一个结构体的类型,里面可以有好多东西

然后我们可以创造一个函数,这个函数恰巧满足我们结构体里的一些东西

所以我们就把我们这个函数让我们的这个结构体来接收,并起一个名字来方便使用

type Person struct {
	name string
}
//   接收者是Person
func (per Person) eat() {
	fmt.Println(per.name + " eating....")
}

因为接收者是结构体嘛,所以我们依然可以用. 来调用访问具体的函数也就是具体的方法

11.3、注意事项

  1. 方法的receiver type并非一定要是struct类型,type定义的类型别名、slice、map、channel、func类型等都可以。
  2. struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
  3. 方法有两种接收类型:(T Type)(T *Type),它们之间有区别。
  4. 方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
  5. 如果receiver是一个指针类型,则会自动解除引用。
  6. 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。

11.4、方法接收者类型

11.4.1、值类型结构体和指针类型结构体

实例

package main

import "fmt"

type Person struct {
	name string
}

func main() {
	p1 := Person{name: "tom"}
	fmt.Printf("p1: %T\n", p1)
	p2 := &Person{name: "tom"}
	fmt.Printf("p2: %T\n", p2)
}

运行结果

p1: main.Person
p2: *main.Person

从运行结果,我们可以看出p1是值类型,p2是指针类型。

下面看一个传参结构体的例子

package main

import "fmt"

type Person struct {
	name string
}

func showPerson(per Person) {
	fmt.Printf("per: %p\n", &per)
	per.name = "kite"
	fmt.Printf("per: %v\n", per)
}

func showPerson2(per *Person) {
	fmt.Printf("per: %p\n", per)
	per.name = "kite"
	fmt.Printf("per: %v\n", per)
}

func main() {
	p1 := Person{name: "tom"}
	fmt.Printf("p1: %p\n", &p1)
	showPerson(p1)
	fmt.Printf("p1: %v\n", p1)
	fmt.Println("---------------")
	p2 := &Person{name: "tom"}
	fmt.Printf("p2: %p\n", p2)
	showPerson2(p2)
	fmt.Printf("p2: %v\n", p2)
}

运行结果

p1: 0xc000046240
per: 0xc000046250
per: {kite}
p1: {tom}
---------------
p2: 0xc000046280
per: 0xc000046280
per: &{kite}
p2: &{kite}

从运行结果,我们看到p1是值传递,拷贝了副本,地址发生了改变,而p2是指针类型,地址没有改变。

11.4.2、方法的值类型和指针类型接收者

值类型和指针类型接收者,本质上和函数传参道理相同。

实例

package main

import "fmt"

type Person struct {
	name string
}

func (per Person) showPerson() {
	fmt.Printf("per: %p\n", &per)
	per.name = "kite"
	fmt.Printf("per: %v\n", per)
}

func (per *Person) showPerson2() {
	fmt.Printf("per: %p\n", per)
	per.name = "kite"
	fmt.Printf("per: %v\n", per)
}

func main() {
	p1 := Person{name: "tom"}
	fmt.Printf("p1: %p\n", &p1)
	p1.showPerson()
	fmt.Printf("p1: %v\n", p1)
	fmt.Println("---------------")
	p2 := &Person{name: "tom"}
	fmt.Printf("p2: %p\n", p2)
	p2.showPerson2()
	fmt.Printf("p2: %v\n", p2)
}

运行结果

p1: 0xc000046240
per: 0xc000046250
per: {kite}
p1: {tom}
---------------
p2: 0xc000046280
per: 0xc000046280
per: &{kite}
p2: &{kite}

从运行结果,我们看到p1是值传递,拷贝了副本,地址发生了改变,而p2是指针类型,地址没有改变。

12、继承

golang本质上没有oop的概念,也没有继承的概念,但是可以通过结构体嵌套实现这个特性。

例如

package main

import "fmt"

type Animal struct {
	name string
	age  int
}

func (a Animal) eat() {
	fmt.Println("eat...")
}

func (a Animal) sleep() {
	fmt.Println("sleep")
}

type Dog struct {
	Animal
}

type Cat struct {
	Animal
}

func main() {
	dog := Dog{
		Animal{
			name: "dog",
			age:  2,
		},
	}

	cat := Cat{
		Animal{name: "cat",
			age: 3},
	}

	dog.eat()
	dog.sleep()

	cat.eat()
	cat.sleep()

}
posted @   Caidd123  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示