Golang学习笔记(05-1-结构体定义)

108 阅读16分钟

1. 自定义类型和类型别名

1.1. 自定义类型

Go语言中除了基本的数据类型之外,还可以自定义数据类型,最常用的是就是结构体,除此之外还可以基于基本数据类型来实现自定义类型,如: type newType type 。基于基本数据类型创造自定义类型的目的在于,部分场景中,需要对已有类型添加新的方法,此时需要使用到自定义类型。

package main

import "fmt"

type MyInt int32

func main() {
	var a MyInt = 133
	fmt.Printf("%T %#v %d\n", a, a, a) // main.MyInt 133 133, main包中MyInt类型
}

1.2. 类型别名

在Go中最常见的类型别名就是 rune 和 byte ,分别为 int32 和 uint8 的别名。别名只是在代码中用于更好的区分不同数据类型,一旦编译完毕后,就直接映射为原始的数据类型。

package main

import "fmt"

type testInt = uint64

func main()  {
	var (
		a testInt = 65539
		b rune = '渡'
		c rune = '#'
	)
	fmt.Printf("a --> Type:%T Value:%v\n", a, a)
	fmt.Printf("b --> Type:%T Value:%v\n", b, b)
	fmt.Printf("c --> Type:%T Value:%v\n", c, c)
}
[root@heyingsheng 01-type]# go run 01.go
a --> Type:uint64 Value:65539
b --> Type:int32 Value:28193
c --> Type:int32 Value:35
# runebyte 类型
[root@heyingsheng 01-type]# grep -E "rune|byte" /mnt/c/Program\ Files/Go/src/builtin/builtin.go | grep -v '//'
type byte = uint8
type rune = int32

1.3. 类型别名和自定义类型的区别

  • 自定义类型编译完毕后,仍然是自定义类型;类型别名编译完毕后,编译完毕后变成原始的数据类型
  • 类型别名是方便写代码时区分,自定义类型是方便扩展功能,如方法

2. 定义结构体

GoLang中支持面向对象编程,但是并不是通过Class方式来实现的,而是通过结构体(struct)来实现的,GoLang的面向对象思维的编程方式比较简单,去掉了传统OOP语言中的继承、方法重载、构造函数、析构函数以及this指针等特性。GoLang中仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式不同而言。

2.1. 结构体的定义

2.1.1. 定义结构体

结构体的名称就是一种数据类型,其它内部的字段可以具有不同的数据类型,这样就实现了一个实例的不同属性。注意:

  • 同一个包中结构体数据类型不能重名
  • 同一个结构体中字段不能重名
  • 不能指定字段的默认值
  • 结构体默认是值类型,指针类型的结构体需要单独创建
type Person struct {
	name string
	gender string
	age  uint8
	hobby []string
}

2.1.2. 字段简写

多个同类型的字段可以写在一行,格式: field1, field2, field3 Type 

type service struct {
	name, logDir, dataDir string
	port []uint16
}

2.1.3. 匿名结构体

上述两种方式定义的结构体都有一个类型名称,可以被不同的实例所引用,参考 2.2. 结构体的初始化 。如果一个结构体只是使用一次,那么可以不定义结构体名称,直接完成定义。

package main

func main()  {
	var redis struct{
		name string
		port uint16
		dataDir string
	}
	redis.name = "Redis"
	redis.port = 6379
    // 直接赋值
    var nginx = struct{
        name string
        port []uint16
    }{"nginx", []uint16{80,8080,8081}}
}

2.1.4. 结构体的匿名字段

匿名字段指没有声明字段名称,指声明了字段类型的结构体,这种结构体的字段名称与字段类型一致。因此同一个类型只能定义一个,否则就出现了结构体中字段名称冲突。 了解即可,不建议使用! 

type student struct {
    string
    uint8
}
var stu01 student
stu01.string = "张三"

2.2. 结构体的实例化

将结构体作为数据类型赋值给一个变量,称为结构体的实例化,实例化后的变量被称为(结构体的)实例、(结构体的)对象:

  • 实例化有两大类:以定义变量的形式实例化结构体,使用构造函数来实例化
  • 通过变量形式实例化有三种:
    • 使用 var.field = value 格式直接对字段赋值,常用
    • 使用 key/value 方式来赋值,与map 类型类似,常用
    • 使用 value 直接赋值,这种方式要求按顺序对所有字段赋值
  • 字段的默认值为对应数据类型的零值
    • 字符串、数字、数组字段为默认的零,可以直接拿来赋值
    • 切片、map和指针需要分配内存之后才能使用

2.2.1. 以定义变量的形式实例化

package main

import (
	"fmt"
	"strings"
)

type Person struct {
	name string
	gender string
	age  uint8
	hobby []string
}

func main()  {
	// 结构体的默认值
	var p0 Person
	fmt.Printf("p0: %v %#v\n", p0, p0)
	fmt.Println(strings.Repeat("-",30))
	// 先实例化,再赋值,未赋值的使用字段零值
	var p1 Person
	p1.name = "张三"
	p1.age = 18
	p1.hobby = []string{"篮球","乒乓球","足够"}
	fmt.Printf("p1: %v\n", p1)
	fmt.Println(strings.Repeat("-",30))
	// 使用key/value来实例化
	p2 := Person{
		name:   "李四",
		gender: "女",
		age:    24,
		hobby:  []string{"旅游","购物"},
	}
	fmt.Printf("p2: %v\n", p2)
	fmt.Println(strings.Repeat("-",30))
	// 直接使用value来实例化
	p3 := Person{
		"王五",
		"男",
		35,
		[]string{"游泳","钓鱼"},
	}
	fmt.Printf("p3: %v\n", p3)
}
[root@heyingsheng 02-struct]# go run 03.go
p0: {  0 []} main.Person{name:"", gender:"", age:0x0, hobby:[]string(nil)}
------------------------------
p1: {张三  18 [篮球 乒乓球 足够]}
------------------------------
p2: {李四 女 24 [旅游 购物]}
------------------------------
p3: {王五 男 35 [游泳 钓鱼]}

2.2.2. 结构体的指针

package main

import "fmt"

type Service struct {
	Name string
	Port []uint16
}
func main()  {
	// 使用new定义结构体的指针类型的变量
	sshd := new(Service)
	sshd.Port = []uint16{22} //语法糖,是 (*sshd).port 的简写
	sshd.Name = "SSHD"
	fmt.Printf("sshd 类型:%T; 值:%v; 16进制:%p; 结构体的值:%v\n", sshd, sshd, sshd, *sshd)
	// 直接使用结构体指针进行定义
	nginx := &Service{}
	nginx.Name = "Nginx"
	nginx.Port = []uint16{80, 8080}
	fmt.Printf("nginx 类型:%T; 值:%v; 16进制:%p; 结构体的值:%v\n", nginx, nginx, nginx, *nginx)
}
[root@heyingsheng 02-struct]# go run 05.go
sshd 类型:*main.Service; 值:&{SSHD [22]}; 16进制:0xc000066330; 结构体的值:{SSHD [22]}
nginx 类型:*main.Service; 值:&{Nginx [80 8080]}; 16进制:0xc0000663c0; 结构体的值:{Nginx [80 8080]}

2.2.3. 使用构造函数来实例化

构造函数类似于Python中 __init__() 函数,是为了更方便的完成结构体的实例化。需要注意的是:

  • 构造函数名称约定俗成用new开头
  • 构造函数分两类,返回结构体的构造函数和返回结构体指针的构造函数
    • 返回结构体的构造函数,会将函数内生成的结构体赋值给接收变量,内存的开销较大
    • 返回结构体指针的构造函数,只需要对返回的指针进行拷贝,内存的开销较小,推荐使用
package main

import "fmt"

type service struct {
	name string
	port []uint16
	dataDir string
	logDir string
	man string
}

func NewService(name string, port []uint16, dataDir string, logDir string, man string) service {
	return service{
		name:    name,
		port:    port,
		dataDir: dataDir,
		logDir:  logDir,
		man:     man,
	}
}

func NewServicePointer(name string, port []uint16, dataDir string, logDir string, man string) *service {
	return &service{
		name:    name,
		port:    port,
		dataDir: dataDir,
		logDir:  logDir,
		man:     man,
	}
}

func main()  {
	nginx := NewService("Nginx",[]uint16{80,8080},"/data/nginx/html","/data/nginx/logs","")
	sshd := NewServicePointer("sshd",[]uint16{22},"","/var/log/","")
	fmt.Printf("nginx --> value:%v; type:%T\n", nginx, nginx)
	fmt.Printf("sshd --> value:%v; type:%T ;dstvalue:%v\n", sshd, sshd, *sshd)
}
[root@heyingsheng day04]# go run 05-structfunc/main.go
nginx --> value:{Nginx [80 8080] /data/nginx/html /data/nginx/logs }; type:main.service
sshd --> value:&{sshd [22]  /var/log/ }; type:*main.service ;dstvalue:{sshd [22]  /var/log/ }

2.3. 结构体值的访问和修改

2.3.1. 结构体的访问

package main

import "fmt"

type service struct {
	name string
	port []uint16
	dataDir string
	logDir string
	man string
}

func main()  {
	mysql := service{
		name:    "MySQL",
		port:    []uint16{3306},
		dataDir: "/data/mysql/db",
		logDir:  "/data/mysql/logs",
		man:     "",
	}
	fmt.Println(mysql.name,mysql.dataDir)
}

2.3.2. 结构体数据修改

对于结构体的数据修改有两种方式:一个是通过 name.field = value ,一种是通过函数修改。

  • 通过函数修改时,函数传参传的是实参的副本,因此在函数想要修改结构体的值,必须要要采用指针的方式。
  • go语言中通过结构体指针操作结构体数据时,可以省略 * ,这是go的语法糖
package main

import (
	"fmt"
	"strings"
)

type service struct {
	name string
	port []uint16
	dataDir string
	logDir string
	man string
}

func f0(s service) {
	fmt.Printf("函数内结构体内存地址:%p\n", &s)
	s.dataDir = "/data/mysql/data"
}

func f1(s *service)  {
	//(*s).dataDir = "/data/mysql/data"
	s.dataDir = "/data/mysql/data"  // 这种写法是go的语法糖,本质就是上一行的写法
}

func main()  {
	mysql := service{
		name:    "MySQL",
		port:    []uint16{3306},
		dataDir: "/data/mysql/db",
		logDir:  "/data/mysql/logs",
		man:     "",
	}
	mysql.dataDir = "/data/mysql/database"
	fmt.Println(mysql.name,mysql.dataDir)
	fmt.Println(strings.Repeat("-",30))
	fmt.Printf("实例化的mysql内存地址:%p\n", &mysql)
	f0(mysql)
	fmt.Println(mysql.name,mysql.dataDir)
	fmt.Println(strings.Repeat("-",30))
	f1(&mysql)
	fmt.Println(mysql.name,mysql.dataDir)
}
[root@heyingsheng day04]# go run 03-accessstruct/main.go
MySQL /data/mysql/database
------------------------------
实例化的mysql内存地址:0xc00001a0c0
函数内结构体内存地址:0xc00001a120
MySQL /data/mysql/database
------------------------------
MySQL /data/mysql/data

2.4. 嵌套结构体

2.4.1. 嵌套结构体的定义

嵌套结构体类似于Json,用于存储复杂数据类型,比如存储Kubernetes中的资源配置清单,如下案例:

package main

import "fmt"
// 定义k8s资源清单metadata字段
type metadata struct {
	name string
	namespace string
}
// 定义k8s资源清单顶层字段
type topInfo struct {
	aipVersion string
	kind string
	metadata
}
// 定义pod
type pod struct {
	info topInfo
	spec struct{
		containers []string // 仅做一个示范而已,不操作更加复杂结构
	}
}
// 定义service
type service struct {
	topInfo
	spec struct{
		clusterIP string
	}
}

func main()  {
	var nginxPod = pod{
		info: topInfo{
			aipVersion: "v1",
			kind:       "Pod",
			metadata: metadata{
				name:      "nginx",
				namespace: "default",
			},
		},
		spec: struct {
			containers []string
		}{[]string{"nginx"}},
	}
	fmt.Println(nginxPod.info.aipVersion, nginxPod.info.namespace,nginxPod.spec.containers)
	nginxSVC := service{
		topInfo: topInfo{
			aipVersion:"v1",
			kind:"Service",
			metadata: metadata{
				name:      "nginx",
				namespace: "default",
			},
		},
		spec: struct {
			clusterIP string
		}{"192.168.0.112"},
	}
	fmt.Println(nginxSVC.aipVersion, nginxSVC.namespace,nginxSVC.spec.clusterIP)
}
[root@heyingsheng day04]# go run 08-nested/main.go
v1 default [nginx]
v1 default 192.168.0.112
通过上面的案例可以分析得到:
1.  实现结构体嵌套有两种方式:
    (1) 将每一层的结构拆开定义(line5-14)。这种方式定义繁琐,赋值时方便(line32-39)
    (2) 直接在一个结构体中,使用struct来定义嵌套的结构体(line18-20),定义方便,赋值繁琐(line54-56)
2.  嵌套结构体访问
    (1) 如果嵌套的为匿名结构体(line13,line24),在查询值的时候可以省略中间字段,如nginxSVC.namespace
    (2) 如果嵌套的为命名结构体(line17),在查询的时候就得写全路径,如nginxPod.info.aipVersion
    (3) 当多个嵌套结构体中存在相同名称字段,则不适合使用匿名结构体

2.4.2. 结构体转json

使用 json.Marshal(name) 可以将结构体转换为字符切片,转为string后就是json字符串。需要注意的是结构体字段名称首字母需要大写,以下为简单演示。

package main

import (
	"encoding/json"
	"fmt"
)

type service struct {
	// 只有字段名称首字母大写才能被其它包读取到,此时json字符串中的字段名都为大写字母开头
	// 如果需要指定在json中该字段显示的名称,则需要使用tag指定
	Name    string   `json:"name"`
	Port    []uint16 `json:"port"`
	DataDir string   `json:"data_dir"`
	LogDir  string   `json:"log_dir"`
	Man     string   `json:"man"`
}

func main() {
	nginx := service{
		Name:    "Nginx",
		Port:    []uint16{80, 8080, 8081},
		DataDir: "/data/nginx/www",
		LogDir:  "/data/nginx/logs",
		Man:     "",
	}
	ret, err := json.Marshal(nginx)
	if err == nil {
		fmt.Printf("type: %T; value:%v\n", string(ret), string(ret))
	}
}
[root@heyingsheng day04]# go run 10-json/main.go
type: string; value:{"name":"Nginx","port":[80,8080,8081],"data_dir":"/data/nginx/www","log_dir":"/data/nginx/logs","man":""}
[root@heyingsheng day04]# echo '{"name":"Nginx","port":[80,8080,8081],"data_dir":"/data/nginx/www","log_dir":"/data/nginx/logs","man":""}'|python3 -m json.tool
{
    "name": "Nginx",
    "port": [
        80,
        8080,
        8081
    ],
    "data_dir": "/data/nginx/www",
    "log_dir": "/data/nginx/logs",
    "man": ""
}

2.4.3. json转结构体

json转结构体,需要先定义好接收json准换后的结构体变量,通过 json.Unmarshal([]byte, &name) 可以将字符切片转为结构体。需要注意的是:

  • json.Unmarshal()接收的是字符切片和结构体变量的指针
  • 必须要先定义好结构体和结构体变量
package main

import (
	"encoding/json"
	"fmt"
)

type service struct {
	// 只有字段名称首字母大写才能被其它包读取到,此时json字符串中的字段名都为大写字母开头
	// 如果需要指定在json中该字段显示的名称,则需要使用tag指定
	Name    string   `json:"name"`
	Port    []uint16 `json:"port"`
	DataDir string   `json:"data_dir"`
	LogDir  string   `json:"log_dir"`
	Man     string   `json:"man"`
}

func main() {
	var ret service
	jsonStr := `{"name":"MySQL","port":[3306],"data_dir":"/data/mysql/data","log_dir":"/data/mysql/logs","man":"db"}`
	err := json.Unmarshal([]byte(jsonStr), &ret)
	if err == nil {
		fmt.Printf("type: %T; value:%v\n", ret, ret)
	}
}
[root@heyingsheng day04]# go run 10-json/main.go
type: main.service; value:{MySQL [3306] /data/mysql/data /data/mysql/logs db}

2.5. 注意事项

2.5.1. 结构体的修改

package main

import "fmt"

type Person struct {
	Name string
	Age  uint8
}

func f0() {
	p1 := Person{Name: "Tom", Age: 18}
	p2 := p1
	p2.Name = "Stone" // 结构体是指针类型,因此p2是p1的副本,因此修改p2的属性不会影响p1
	fmt.Println("f0:", p1)
	fmt.Println("f0:", p2)
}

func f1() {
	p1 := &Person{Name: "Tom~", Age: 18}
	p2 := p1
	p2.Name = "Stone~" // p1是结构体指针,因此p2中存储的是p1的地址,而p1的地址又指向了结构体实例
	fmt.Println("f1:", *p1)
	fmt.Println("f1:", *p2)
}

func main() {
	f0()
	f1()
}
[root@heyingsheng 13-notice]# go run 01.go
f0: {Tom 18}
f0: {Stone 18}
f1: {Stone~ 18}
f1: {Stone~ 18}

2.5.2. 内存地址的连续性

package main

import "fmt"

type S21 struct {
	N1, N2 int8
	N3, N4 int64
}

type S22 struct {
	x, y int8
}

type S23 struct {
	m, n S22
}

type S24 struct {
	m, n *S22
}

func f21() {
	// N1和N2是连续的,地址相差为1。N3和N4是连续的。N2和N3之所以中间有中断是为了方便寻址,而做的优化
	s := S21{N1: 100, N2: 120, N3: 0, N4: 1}
	fmt.Printf("s地址:%p, s.N1地址:%p, s.N2地址:%p, s.N3地址:%p, s.N4地址:%p\n", &s, &s.N1, &s.N2, &s.N3, &s.N4)
	// s23中所有属性的地址都是连续的
	e := S23{m:S22{1,2}, n:S22{3,4}}
	fmt.Printf("e地址:%p, e.m.x地址:%p, e.m.y地址:%p, e.n.x地址:%p, e.n.y地址:%p\n",
		&e, &e.m.x, &e.m.y, &e.n.x, &e.n.y)
	// s24中m和n的地址连续,但是m和n指针对应的s22的地址就不一定连续了
	f := S24{m:&S22{x:1,y:2},n:&S22{x:3,y:4}}
	fmt.Printf("f地址:%p, f.m地址:%p, f.n地址:%p\n",&f, &f.m, &f.n)
	fmt.Printf("f地址:%p, f.m.x地址:%p, f.m.y地址:%p, f.n.x地址:%p, f.n.y地址:%p\n",
		&f, &f.m.x, &f.m.y, &f.n.x, &f.n.y)
}

func main() {
	f21()
}
[root@heyingsheng 13-notice]# go run 02.go
s地址:0xc00000c380, s.N1地址:0xc00000c380, s.N2地址:0xc00000c381, s.N3地址:0xc00000c388, s.N4地址:0xc00000c390
e地址:0xc0000100a8, e.m.x地址:0xc0000100a8, e.m.y地址:0xc0000100a9, e.n.x地址:0xc0000100aa, e.n.y地址:0xc0000100ab
f地址:0xc0000461f0, f.m地址:0xc0000461f0, f.n地址:0xc0000461f8
f地址:0xc0000461f0, f.m.x地址:0xc0000100ac, f.m.y地址:0xc0000100ad, f.n.x地址:0xc0000100ae, f.n.y地址:0xc0000100af

2.5.3. 结构体之间类型转换

两个定义完全相同的结构体仍然是不同的数据类型,不能直接做类型转换。当且仅当在两个结构体字段完全相同(字段名称、类型、个数相同)的情况下实现类型强制转换!

package main

import "fmt"

type S31 struct {
	Name string
	Age int
}

type S32 struct {
	Name string
	Age int
}

func main()  {
	s1 := S31{Name:"Tom", Age:18}
	s2 := S32{}
	//s2 = s1   // 直接直接转换报错: cannot use s1 (type S31) as type S32 in assignment
	s2 = S32(s1)
	fmt.Printf("s1类型:%T, s2类型:%T\n", s1, s2)
	fmt.Printf("s1的值:%v, s2的值:%v\n", s1, s2)
}
[root@heyingsheng 13-notice]# go run 03.go
s1类型:main.S31, s2类型:main.S32
s1的值:{Tom 18}, s2的值:{Tom 18}

3. 方法

方法是作用于特定数据类型变量的一种函数,本质是函数,只是相对于普通函数多了接收者信息。方法相当于Python中实例的方法,结构体的方法在编程中使用的非常多。格式如下:

func (接收者 接收者类型)函数名(参数)(返回值){
    函数代码块
}
# 接收者类型:当前方法作用的数据类型,比如 service 结构体。也可以是数据类型的指针
# 接收者: 接收者相当于Py中方法的self参数,表示调用这个方法的实例。在go中使用类型名称的小写,如service类型使用s
# 函数名、参数、返回值与普通函数定义的规则一致

在方法中,采用 接收者.字段 可以实现调用实例的属性
# 指针类型接收者和值类型接收者区别
1. 指针类型接收者方法会用在较为复杂的数据类中,降低内存拷贝带来的资源消耗
2. 当需要修改实例的属性时,需要通过指针类型的方法
3. 一般非特殊场景中,建议使用指针类型的方法

# 语法糖
如果指针类型的变量调用值类型接收者方法时,方法会自动对指针求值!

3.1. 值类型接收者

3.1.1. 代码展示

package main

import "fmt"

type service struct {
	name string
	port []uint16
	man string
}

func newService(name string,port []uint16,man string) *service {
	return &service{
		name: name,
		port: port,
		man:  man,
	}
}

func (s service)startService() {
	s.port = []uint16{3366}
	fmt.Printf("startService: %T %p\n", s, &s)
	fmt.Printf("startService: service %s is started!Listen port %v\n", s.name, s.port)
}

func main()  {
	mysql := newService("MySQL", []uint16{3306},"")
	fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
	mysql.startService()
	fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
}
[root@heyingsheng day04]# go run 06-method/main.go  // 端口没有发生变化
*main.service 0xc00004e040 &{MySQL [3306] }
startService: main.service 0xc00004e0c0
startService: service MySQL is started!Listen port [3366]
*main.service 0xc00004e040 &{MySQL [3306] }

3.1.2. 执行步骤分析

image.png

3.2. 指针类型接收者

3.2.1. 代码展示

package main

import "fmt"

type service struct {
	name string
	port []uint16
	man string
}

func newService(name string,port []uint16,man string) *service {
	return &service{
		name: name,
		port: port,
		man:  man,
	}
}

func (s *service)stopService()  {
	s.port = []uint16{3377}
	fmt.Printf("stopService: %T %p\n", s, s)
	fmt.Printf("stopService: service %s is stop!Release port %v\n", s.name, s.port)
}

func main()  {
	mysql := newService("MySQL", []uint16{3306},"")
	fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
	mysql.stopService()
	fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
}
[root@heyingsheng day04]# go run 06-method/main.go
*main.service 0xc00004e040 &{MySQL [3306] }
stopService: *main.service 0xc00004e040
stopService: service MySQL is stop!Release port [3377]
*main.service 0xc00004e040 &{MySQL [3377] }

3.2.2. 执行步骤分析

image.png

3.3. 特殊方法

3.3.1. String()

当一个数据类型实现了 String() 方法后,在使用 fmt.Println() 时,会自动调用该方法,类似于 Python 中的 __str__()

package main

import "fmt"

type Student struct {
	Name string
	Age int
}

func (s *Student)String() string {
	ret := fmt.Sprintf("value:(Name:[%v], Age:[%v]); pointer:[%p]", s.Name, s.Age, s)
	return ret
}

func main()  {
	s1 := Student{Name:"Tom", Age:20}
	fmt.Println(&s1)
}
[root@heyingsheng 13-notice]# go run 04.go
value:(Name:[Tom], Age:[20]); pointer:[0xc0000044a0]

3.4. 给内置类型添加方法

自定义内置类型的用处之一在于为内置类型添加方法,如基于 string 类型自定义一个myStr类型,目的在于添加一些自定义方法,因为在其它包中是无法直接给内置类型添加方法的。

package main

import "fmt"

type myStr string

func (m myStr)repeat(n int) myStr {
	var tmp myStr
	for i:=1;i<=n;i++{
		tmp += m
	}
	return tmp
}

func main()  {
	s0 := myStr("s")
	s0 = s0.repeat(30)
	fmt.Printf("tye:%T value:%v\n",s0,s0)
}
[root@heyingsheng day04]# go run 07-method/main.go
type:main.myStr value:ssssssssssssssssssssssssssssss

3.5. 方法和函数调用时的区别

3.5.1. 调用方式的区别

函数调用方式: 函数名(实参)
方法调用方式: 变量名.方法名(实参)

3.5.2. 方法中的语法糖

// 定义方法和结构体如下:
type Student struct { }
func (s Student)f1() { }
func (s *Student)f2() { }

s1 := Student{}
// 结构体实例 s1 可以直接调用 f1 和 f2,结构体指针 &s1 可以调用 f1 和 f2
s1.f1()
s1.f2()
(&s1).f1()
(&s1).f2()