Go语言学习之 Day05 结构体

结构体

结构体是由一些列属性组成的复合数据类型,每个属性都具有名称、类型和值,结构体将属性组合在一起进行由程序进行处理。

自定义类型

在go语言中使用type声明一种新的类型,语法格式为:

type TypeName Formatter

Formatter 可以是任意内置类型、函数签名、结构体、接口

redefine_test.go
package day05

import "testing"

// 重定义
type Counter int

// go http
// headers key: value map
// type Header map[string][]string

func TestStructDefine(t *testing.T) {
	var i int = 10
	var count Counter
	t.Logf("%T,%v\n", count, count) // Counter

	t.Log(count)
	count += 1
	t.Log(count)
	count *= 2
	t.Log(count)

	count += Counter(i) // 左右操作数据类型必须一致
	t.Log(count)
}

redefine_method_test.go
package day05

import "testing"

type Counter int

func (c *Counter) Incr() {
	*c++
}

func (c *Counter) Decr() {
	*c--
}

func TestReMethod(t *testing.T) {
	var counter Counter

	counter.Incr()
	t.Log(counter)
	counter.Decr()
	t.Log(counter)
}

定义

结构体定义使用 struct 标识,需要指定其包含的属性(名和类型),在定义结构体时可以为结构体指定结构体名(命名结构体),用于后续声明结构体变量使用

// 定义结构体
/*
type StructName struct {
	Field1 FieldType1
	Field2 FieldType2
	...
	Fieldn FieldTypen
}

*/

// 定义结构体User => 类 => 封装

type User struct {
	id        int
	name      string
	addr      string
	tel       string
	birthday  time.Time
	createAt  time.Time
	updateAt  time.Time
	flag      bool
	status    int
	deletedAt *time.Time
}

声明

声明结构体变量只需要定义变量类型为结构体名,变量中的每个属性被初始化为对应类型的零值。也可声明结构体指针变量,此时变量被初始化为nil。

	//	 定义User 类型的变量
	var u User // 由所有属性的零值组成的一个User 类型的变量值
	t.Logf("%T\n", u)
	// 零值 => 由所有属性的零值组成的变量值
	t.Logf("%v\n", u) // 非nil
func TestStruct(t *testing.T) {
	//	指针
	var t1 *time.Time
	t.Log(t1) // 零值 nil
}

初始化

使用结构体创建的变量叫做对应结构体的实例或者对象

a) 使用结构体零值初始化结构体值对象

	var myuser = User{}
	t.Log(myuser) // {0    {0 0 <nil>} {0 0 <nil>} {0 0 <nil>} false 0 <nil>}

b)使用结构体字面量初始化结构体值对象

	// 赋值 字面量
	// 设置值 按照 结构体中属性定义的顺序设置
	// 需要给所有属性都进行赋值
	// 按照属性顺序定义的字面量
	u = User{1, "lewen", "重庆", "139xxxxxxxx", time.Now(), time.Now(), time.Now(), false, 0, nil}
	t.Logf("%#v\n", u)

	// 按照属性名称定义的字面量
	//  未指定的属性名称 对应类型的0值进行初始化
	u = User{id: 2, name: "fadewalk"}
	t.Logf("%#v\n", u)

c)使用new函数进行初始化结构体指针对象

	// new 申请空间并对元素使用0值进行初始化,取地址,赋值
	var u4 *User = new(User)
	t.Logf("u4:   %#v\n", u4)

d)使用结构体字面量初始化结构体指针对象

	u5 = &User{
		id: 10,
	}
	t.Logf("u5:   %#v\n", u5)

New函数

Go语言中常定义N(n)ew+结构体名命名的函数用于创建对应的结构体值对象或指针对象

// 类 构造器 => 创建对应类的实例(对象/变量) // go 中无构造器的说法
// 函数 N(n)ew 结构体名称 构造结构体值(值、指针) 对象

func NewUser(id int, name, addr, tel string, birthday time.Time) User {
	return User{
		id:        id,
		name:      name,
		addr:      addr,
		tel:       tel,
		birthday:  birthday,
		createAt:  time.Now(),
		updateAt:  time.Now(),
		flag:      false,
		status:    0,
		deletedAt: nil,
	}
}

属性的访问和修改

通过结构体对象名/结构体指针对象.属性名的方式来访问和修改对象的属性值

	//	属性进行访问和修改
	t.Log(u.id)
	t.Log(u.name)
	u.addr = "成都"
	t.Logf("%#v\n", u)

可以通过结构体指针对象的点操作直接对对象的属性值进行访问和修改

	// 值类型
	u2 := u
	u2.tel = "123xxxxxxxx"
	t.Logf("%#v\n", u2)
	t.Logf("%#v\n", u) // 不影响

	//	指针类型对象
	var u3 *User
	t.Logf("%T,%v\n", u3, u3)

	// u3 =
	//	指针赋值 => 取引用 &

	u3 = &u2
	t.Logf("%#v\n", u3)

	t.Log((*u3).name)
	t.Log(u3.name)
	(*u3).name = "u3"
	t.Log((*u3).name)
小结code
package play_test

import (
	"testing"
	"time"
)

// 定义结构体
/*
type StructName struct {
	Field1 FieldType1
	Field2 FieldType2
	...
	Fieldn FieldTypen
}

*/

// 定义结构体User => 类 => 封装

type User struct {
	id        int
	name      string
	addr      string
	tel       string
	birthday  time.Time
	createAt  time.Time
	updateAt  time.Time
	flag      bool
	status    int
	deletedAt *time.Time
}

// 类 构造器 => 创建对应类的实例(对象/变量) // go 中无构造器的说法
// 函数 N(n)ew 结构体名称 构造结构体值(值、指针) 对象

func NewUser(id int, name, addr, tel string, birthday time.Time) User {
	return User{
		id:        id,
		name:      name,
		addr:      addr,
		tel:       tel,
		birthday:  birthday,
		createAt:  time.Now(),
		updateAt:  time.Now(),
		flag:      false,
		status:    0,
		deletedAt: nil,
	}
}

func NewUserPointer(id int, name, addr, tel string, birthday time.Time) *User {
	return &User{
		id:        id,
		name:      name,
		addr:      addr,
		tel:       tel,
		birthday:  birthday,
		createAt:  time.Now(),
		updateAt:  time.Now(),
		flag:      false,
		status:    0,
		deletedAt: nil,
	}
}

type User2 struct {
	id              int
	name, addr, tel string
}

func TestStruct(t *testing.T) {
	//	指针
	var t1 *time.Time
	t.Log(t1) // 零值 nil
}

func TestUser(t *testing.T) {
	//	 定义User 类型的变量
	var u User // 由所有属性的零值组成的一个User 类型的变量值
	var myuser = User{}
	t.Log(myuser)
	t.Logf("%T\n", u)
	// 零值 => 由所有属性的零值组成的变量值
	t.Logf("%v\n", u) // 非nil
	t.Logf("%#v\n", u)

	// 赋值 字面量
	// 设置值 按照 结构体中属性定义的顺序设置
	// 需要给所有属性都进行赋值
	// 按照属性顺序定义的字面量
	u = User{1, "lewen", "重庆", "139xxxxxxxx", time.Now(), time.Now(), time.Now(), false, 0, nil}
	t.Logf("%#v\n", u)

	// 按照属性名称定义的字面量
	//  未指定的属性名称 对应类型的0值进行初始化
	u = User{id: 2, name: "fadewalk"}
	t.Logf("%#v\n", u)

	//	属性进行访问和修改
	t.Log(u.id)
	t.Log(u.name)
	u.addr = "成都"
	t.Logf("%#v\n", u)

	// 值类型
	u2 := u
	u2.tel = "123xxxxxxxx"
	t.Logf("%#v\n", u2)
	t.Logf("%#v\n", u) // 不影响

	//	指针类型对象
	var u3 *User
	t.Logf("%T,%v\n", u3, u3)

	// u3 =
	//	指针赋值 => 取引用 &

	u3 = &u2
	t.Logf("%#v\n", u3)

	t.Log((*u3).name)
	t.Log(u3.name)
	(*u3).name = "u3"
	t.Log((*u3).name)
	// new 申请空间并对元素使用0值进行初始化,取地址,赋值
	var u4 *User = new(User)
	t.Logf("u4:   %#v\n", u4)

	// 字面量取引用 User{}
	var u5 *User = &User{}
	t.Logf("u5:   %#v\n", u5)

	u5 = &User{
		id: 10,
	}
	t.Logf("u5:   %#v\n", u5)

	//u6 := User{}
	//t.Logf("u6:   %#v\n", u6)

}

func TestStructPointer(t *testing.T) {
	u6 := NewUser(10, "kevin", "重庆", "1231231", time.Now())
	u7 := NewUserPointer(11, "lewen", "成都", "12312312", time.Now())
	t.Logf("%T,%#v\n", u6, u6)
	t.Logf("%T,%#v\n", u7, u7)

	var user2 User2 = User2{
		1,
		"lwen", "重庆", "132423",
	}
	t.Logf("%T,%#v\n", user2, user2)
}

匿名结构体

在定义变量时将类型指定为结构体的结构,此时叫匿名结构体。匿名结构体常用于初始化一次结构体变量的场景,例如项目配置

点击查看代码
package day05

import "testing"

func TestAnyonmous(t *testing.T) {
	// 匿名结构体 => 不定义名称 => 不能再定义对应的变量 => 只需要定义该种结构的一个变量
	var user struct {
		id   int
		name string
		addr string
		tel  string
	}
	t.Logf("%T\n", user)
	t.Logf("%#v\n", user)

	user.id = 10

	t.Log(user.name)
	t.Logf("%#v\n", user)

	user = struct {
		id   int
		name string
		addr string
		tel  string
	}{id: 1, name: "lewen", addr: "深圳", tel: "1234"}
	t.Logf("%#v\n", user)

	var u2 struct {
		id   int
		name string
	} = struct {
		id   int
		name string
	}{id: 1, name: "fadewalk"}

	t.Logf("%#v\n", u2)

	/*
		var v T
		var v = T{}
	*/

	var u3 = struct {
		id   int
		name string
	}{1, "lele"}
	t.Logf("%#v\n", u3)

	u4 := struct {
		id   int
		name string
	}{1, "lele"}
	t.Logf("%#v\n", u4)

	u5 := &struct {
		id   int
		name string
	}{1, "lele"}
	t.Logf("%T\n", u5)
	t.Logf("%#v", u5)

	// 程序 => 配置
	// template => 数据(匿名结构体) => 传递给模板

	// []T => T => 结构体 => 匿名结构体

	var users = []struct {
		id   int
		name string
	}{
		{1, "lele"},
		{2, "fade"},
	}
	t.Logf("%T", users)
	t.Logf("%#v", users)
}

结构体-命名嵌入

结构体命名嵌入是指结构体中的属性对应的类型也是结构体

1)定义
2)声明和初始化
3)属性的访问和修改

点击查看代码
package day05

import "testing"

// 地址结构体
type Address struct {
	Province string
	City     string
	Street   string
}

// 定义用户信息

type User struct {
	Id    int
	Name  string
	Tel   string
	Email string
	Addr  Address // 嵌入Address结构体, 即定义Address结构体的属性,组合Address 结构体(命名嵌入)
	PAddr *Address
}

func TestCombineStruct(t *testing.T) {
	// 1. 定义
	var u User
	t.Logf("%#v\n", u) // u.Addr u.PAddr
	// 2. 赋值
	//var addr Address = Address{
	//	Province: "广东",
	//	City:     "深圳",
	//	Street:   "南山街道",
	//}
	//u = User{Id: 1, Name: "lele", Tel: "152xxxx", Email: "xxx@xx.com", Addr: addr}

	u = User{Id: 1, Name: "lele", Tel: "152xxx", Email: "xx@xx.com", Addr: Address{
		"广东", "深圳", "南山"}, PAddr: &Address{"重庆", "江北", "观音桥"}}
	t.Logf("%#v\n", u) // u.Addr

	// 3. 属性访问和修改
	t.Log(u.Id)
	t.Log(u.Addr)
	t.Logf("%T,%#v\n", u.PAddr, u.PAddr)

	u.Id = 10
	u.Addr = Address{"北京", "北京", "海淀区"}
	u.PAddr = &Address{"北京", "北京", "昌平区"}
	t.Logf("%#v\n", u)
	t.Logf("%#v\n", u.PAddr)

	t.Log(u.Addr.Street)
	t.Log(u.PAddr.Street)

	u.Addr.Street = "朝阳区"
	u.PAddr.Street = "东城区"
	t.Logf("%#v\n", u)
	t.Logf("%#v\n", u.PAddr)
}

结构体-匿名嵌入

结构体匿名嵌入是指将已定义的结构体名直接声明在新的结构体中,从而实现对以后已有类型的扩展和修改
1)定义
2)声明&初始化
在初始化匿名嵌入的结构体对象时需要遵循树状声明的结构,对于匿名嵌入的结构体可以使用结构体名来指定初始化参数
3)属性访问和修改
在访问和修改嵌入结构体的属性值时,可以通过对象名.结构体名称.属性名的方式进行访问和修改,结构体名称可以省略(匿名成员有一个隐式的名称),因此不能嵌套两个相同名称的结构体。当被嵌入结构体和嵌入结构体有相同的属性名时,在访问和修改嵌入结构体成员的属性值时不能省略结构体名称。

anyonmous_combine_test.go
package day05

import "testing"

type Address struct {
	Province string
	City     string
	Street   string
}

type User struct {
	Id      int
	Name    string
	Tel     string
	Address // 匿名嵌入 自定义属性名Address(与类型名一致), 只能被匿名嵌入一次
}
type User2 struct {
	Id     int
	Name   string
	Tel    string
	Street string
	Address
}

// 匿名嵌入两个对象,两个对象中有相同的属性名(Province)
type Company struct {
	Name     string
	Province string
}

type User3 struct {
	Id     int
	Name   string
	Tel    string
	Street string
	Address
	Company
}

func TestAnyCombine(t *testing.T) {
	// 1.定义变量
	var u User
	t.Logf("1.%#v\n", u) // u.Address
	// 2. 赋值
	u = User{
		Id:      11,
		Name:    "",
		Tel:     "",
		Address: Address{"四川", "成都", "天府新区"},
	}
	t.Logf("2.%#v\n", u)

	// 3.属性访问和修改
	t.Log("3.", u.Address)
	t.Log("4.", u.Address.Street)

	u.Address.Street = "高新区"

	t.Logf("5.%#v\n", u)

	//  当访问匿名嵌入对象的属性时可以省略嵌入对象的属性名

	t.Log("6.", u.Street)
	u.Street = "双流区"
	t.Logf("7.%#v\n", u)

	var u2 User2

	// 优先查找当前对象,若无再查找匿名嵌入对象中属性名

	t.Log("8.", u2.Street)
	u2.Street = "xxxx"
	t.Logf("9.%#v\n", u2)

	u2.Address.Street = "yyy"
	t.Log("10.", u2.Address.Street)

	t.Logf("11.%#v\n", u2)

	// 多个匿名嵌入对象具有相同属性名

	var u3 User3
	t.Logf("12. %#v\n", u3)
	//t.Log(u3.Province)  // 不允许 Address, Company 产生了二义性, go限制开发者使用全路径指名使用哪个属性

	t.Log("13.", u3.Address.Province)
	t.Log("14.", u3.Company.Province)
}

结构体-指针类型嵌入

结构体嵌入(命名&匿名)类型也可以为结构体指针
1)定义
2)声明&初始化
使用属性为指针类型底层共享数据结构,当底层数据发生变化,所有引用都会发生影响
使用属性为值类型,则在复制时发生拷贝,两者不相互影响

anyonmous_combine_pointer_test.go
package day05

import "testing"

type Address struct {
	Province string
	City     string
	Street   string
}

type User struct {
	Id       int
	Name     string
	Tel      string
	*Address // 匿名嵌入 自定义属性名Address(与类型名一致), 只能被匿名嵌入一次
}

func TestAnyComPointer(t *testing.T) {
	//1.定义变量
	var u User
	t.Logf("1.%#v\n", u) // u.Address

	// 2.赋值
	u = User{
		Id:   11,
		Name: "",
		Tel:  "",
		Address: &Address{
			Province: "四川",
			City:     "成都",
			Street:   "天府新区",
		},
	}
	t.Logf("2.%#v\n", u)
	t.Logf("3.%#v\n", u.Address)

	// 3. 属性访问和修改
	t.Log("4.", u.Address.Street)
	u.Address.Street = "金牛区"
	t.Log("5.", u.Address.Street)

	u.Street = "xxxx"
	t.Log("6.", u.Street, u.Address.Street) // xxx

	u2 := u
	u2.Address.Street = "yyyy"
	t.Log("7.", u.Address) // 7. &{四川 成都     yyyy}
}

可见性

  • 结构体首字母大写则包外可见(公开的),否者仅包内- 可访问(内部的)
  • 结构体属性名首字母大写包外可见(公开的),否者仅包内可访问(内部的)

组合:

  1. 结构体名首字母大写,属性名大写:结构体可在包外使用,且访问其大写的属性名
  2. 结构体名首字母大写,属性名小写:结构体可在包外使用,且不能访问其小写的属性名
  3. 结构体名首字母小写,属性名大写:结构体只能在包内使用,属性访问在结构体嵌入时由被嵌入结构体(外层)决定,被嵌入结构体名首字母大写时属性名包外可见,否者只能在包内使用
  4. 结构体名首字母小写,属性名小写:结构体只能在包内使用
day05/visibility/models/user.go
package models

// 包内可见(数据封装 数据对外隐藏)
type userv1 struct {
	id   int
	Name string
}

// NewUserv1 在包外 需要使用userv1类型的变量
func NewUserv1(id int, name string) userv1 {
	return userv1{id, name}
}

// SetId 在包外想要修改ID的值
func (u *userv1) SetId(id int) {
	u.id = id
}

func SetUser1Id(u userv1, id int) {
	u.id = id
}

func SetUser1IdP(u *userv1, id int) {
	u.id = id
}

func (u *userv1) setName(name string) {
	u.Name = name
}

// Userv2 包外可见
type Userv2 struct {
	id   int    // 首字母小写 包内可见
	Name string // 首字母大写 包外可见
}

type Userv3 struct {
	User userv1
	Desc string
}

/*
包外
Userv3 => 可以访问
Userv3.User => 可以访问
Userv3.User.id => 不可以访问
Userv3.User.Name => 可以访问
Userv3.Desc => 可以访问
*/

type Userv4 struct {
	userv1
	Desc string
}

/*
Userv4 => 可以访问
Userv4.userv1 => 不能访问 属性名小写
Userv4.userv1.id => 不能访问
Userv4.id => 不行
Userv4.userv1.Name => 不能访问
Userv4.Name => 可以访问
Userv4.Desc => 可以访问
*/

day05/visibility/visibility_test.go
package visibility

import (
	"testing"
	"visibility/models"
)

func TestVisibility(t *testing.T) {
	var u2 models.Userv2
	t.Logf("%T\n", u2)
	t.Logf("%#v\n", u2)
	//t.Logf(u2.id)

	u2.Name = "lele"
	t.Log(u2.Name)
	t.Logf("%#v\n", u2)

	u1 := models.NewUserv1(1, "fadewalk") // 只能使用短声明
	t.Logf("%T,%#v\n", u1, u1)
	t.Log(u1.Name)

	u1.SetId(1111)

	t.Logf("%#v\n", u1)
	//u1.setName("xxx")

	//--------------------
	//	visibility.v1

	models.SetUser1Id(u1, 999999) // 值传递
	t.Logf("%#v\n", u1)           // 语法是否报错(无问题) 能否修改成功(不能)

	models.SetUser1IdP(&u1, 8888888) // 语法是否报错(无问题) 能否修改成功(能)
	t.Logf("%#v\n", u1)              //models.userv1{id:8888888, Name:"fadewalk"}

	var u4 models.Userv4
	t.Log(u4.Name)
	u4.Name = "xxxxx"
	t.Logf("%#v\n", u4)

}

方法

方法是为特定类型定义的,只能由该类型调用的函数
1.定义
方法是添加了接收者的函数,接收者必须是自定义的类型

2.调用
调用方法通过自定义类型的对象.方法名进行调用,在调用过程中对象传递(赋值)给方法的接收者(值类型,拷贝)

3.指针接收者

a)声明
当使用结构体指针对象调用值接收者的方法时,Go编译器会自动将指针对象”解引用”为值调用方法

b)调用

当使用结构体对象调用指针接收者的方法时,Go编译器会自动将值对象”取引用”为指针调用方法

sugar_test.go
package sugar

import (
	"fmt"
	"testing"
)

type User struct {
	id int
}

func (user *User) P() {
	fmt.Println("P")
}

func (user User) V() {
	fmt.Println("V")
}

func (user *User) Id() {
	if user == nil {
		return
	}
	user.id += 1
}

func Pfunc(user *User) {
	fmt.Println("pfunc")
}

func Vfunc(user User) {
	fmt.Println("vfunc")
}

func TestSugar(t *testing.T) {
	// 只对 接收者进行语法糖处理

	u := User{}
	p := new(User)

	u.P() // 语法糖 自定取引用
	p.V() // 语法糖 自动解引用

	// 针对函数无取引用和解引用语法

	Pfunc(&u)
	Vfunc(*p)

	var pp *User // 一定要初始化
	pp.P()
	pp.Id()
}

注:取引用和解引用发生在接收者中,对于函数/方法的参数必须保持变量类型一一对应

该使用值接收者还是指针接收者,取决于是否现需要修改原始结构体
若不需要修改则使用值,若需要修改则使用指针
若存在指针接收者,则所有方法使用指针接收者

对于接收者为指针类型的方法,需要注意在运行时若接收者为nil用会发生错误

method_test.go
package method

import (
	"fmt"
	"testing"
)

type User struct {
	id    int
	name  string
	tel   string
	email string
}

// (值接收者) 定义User的方法,反回用户名
func (u User) GetName() string {
	// u: 调用者的值拷贝
	return "GetName:" + u.name
}

// (值接收者) 修改名称的方法
func (u User) SetName(name string) {
	u.name = name
}

// 指针接收者
func (u *User) PsetName(name string) {
	u.name = name
}

func (User) No() {
	fmt.Println("no")
}

func TestMethod(t *testing.T) {
	u := User{
		id:    0,
		name:  "lele",
		tel:   "",
		email: "",
	}
	t.Log(u.GetName()) //调用方法 返回值 = 对象.方法名称(参数)
	u.SetName("fadewalk")

	t.Log(u.GetName()) // GetName:lele
	// 调用PsetName (指针)

	// 语法糖 接收者 指针接收者 调用 值对象 => 自动进行取引用

	u.PsetName("fadewalk") // => 	(&u).PsetName("fadewalk")
	t.Log(u.GetName())     //GetName:fadewalk

	p := &User{
		id:    0,
		name:  "fadewalk",
		tel:   "",
		email: "",
	}
	// GetName
	// 语法糖 接收者 值接收者调用 指针对象 => 自动进行解引用
	t.Logf(p.GetName()) // (*p).GetName()

	// 语法糖在编译过程中进行转换
	p.PsetName("walk")
	t.Log(p.GetName())

	p.No()
}

method_value_test.go
package method_value

import "testing"

type User struct {
	Name string
}

func (u User) GetName() string {
	return u.Name
}

func (u *User) PGetName() string {
	return u.Name
}
func TestMethodValue(t *testing.T) {
	u := User{"xxxx"}

	method1 := u.GetName // u -> 值接收者.GetName
	t.Logf("%T\n", method1)
	t.Log(method1()) // xxxxx

	u.Name = "yyyy"
	t.Log(method1()) // xxxx

	method2 := u.PGetName
	t.Log(method2()) // yyyy

	u.Name = "zzzz"
	t.Log(method2()) //  zzzz

}

4.匿名嵌入

  • 若结构体匿名嵌入带有方法的结构体时,则在外部结构体可以调用嵌入结构体的方法,并且在调用时只有嵌入的字段会传递给嵌入结构体方法的接收者。

  • 当被嵌入结构体与嵌入结构体具有相同名称的方法时,则使用对象.方法名调用被嵌入结构体方法。若想要调用嵌入结构体方法,则使用对象.嵌入结构体名.方法

5.方法值/方法表达式

  • 方法也可以赋值给变量,存储在数组、切片、映射中,也可作为参数传递给函数或作为函数返回值进行返回
  • 方法有两种,一种时使用对象/对象指针调用的(方法值),另一种时有类型/类型指针调用的(方法表达式)
    1)方法值
    在方法表达式赋值时若方法接收者为值类型,则在赋值时会将值类型拷贝(若调用为指针则自动解引用拷贝)

2)方法表达式
方法表达式在赋值时,针对接收者为值类型的方法使用类型名或类型指针访问(go自动为指针变量生成隐式的指针类型接收者方法),针对接收者为指针类型则使用类型指针访问。同时在调用时需要传递对应的值对象或指针对象

combine_method_test.go
package combine_method

import (
	"fmt"
	"testing"
)

type Address struct {
	province string
	city     string
	street   string
}

func (address *Address) SetProvince(province string) {
	address.province = province
}

func (address *Address) GetProvince() string {
	return "Address:" + address.province
}
func (address *Address) Name() {
	fmt.Println("address")
}

type Company struct{}

func (Company) Name() {
	fmt.Println("company")
}

type User struct {
	id   int
	name string
	addr Address
	Address
	Company
}

func (u *User) GetProvince() string {
	return u.province
}

func TestComMethod(t *testing.T) {
	var u User
	u.addr.SetProvince("四川")
	t.Logf("%#v\n", u)

	// 匿名嵌入
	u.Address.SetProvince("重庆")
	t.Logf("%#v\n", u)
	u.SetProvince("广东")
	t.Logf("%#v\n", u)

	// 当前对象存在GetProvince
	t.Log(u.GetProvince())         //广东
	t.Log(u.Address.GetProvince()) //Address:广东

	// 匿名嵌入多个对象具有同名方法,需要用全路径访问
	// u.Name() => 具有二义性
	u.Address.Name()
	u.Company.Name()

}

结构体内存

sizeof_test.go
package mem

import (
	"testing"
	"unsafe"
)

type User struct {
	id    int
	name  string
	tel   string
	email string
}

func TestSize(t *testing.T) {
	var i int
	t.Log("int:", unsafe.Sizeof(i))
	var s string
	t.Log("string:", unsafe.Sizeof(s))

	var u User
	t.Log("user:", unsafe.Sizeof(u))
}

练习
作业

expr
package expr

import (
	"strings"
	"testing"
)

type User struct {
	Name string
}

func (u User) GetName() string {
	return u.Name
}

/*
func (u *User) GetName() string{
	return u.Name
}
*/

func (u User) SetName(name string) {
	u.Name = name
}

func (u *User) PsetName(name string) {
	u.Name = name
}

func TestGetName(t *testing.T) {
	// 定义 => T 类型
	// 调用 => T(值/指针)变量/对象/实例
	// 访问方法
	// T.方法名
	// 定义对象 对象.方法名

	// 方法表达式 类型
	// 值类型接收者
	f := User.GetName
	t.Logf("%T\n", f)
	t.Log(f(User{Name: "kk"}))

	t.Log(User.GetName(User{Name: "xxx"}))
}

func TestSetName(t *testing.T) {
	f2 := User.SetName
	t.Logf("%T\n", f2)
	f2(User{Name: "xxx"}, "yyy")
}

func TestPointer(t *testing.T) {
	f3 := (*User).PsetName //User.PsetName 报错了
	t.Logf("%T\n", f3)

	u := User{}
	f3(&u, "kk")
	f := User.GetName
	t.Log(f(u))

	f4 := (*User).GetName // 如果定义了值接收者方法,GO自动生成一个对应的指针接收者方法
	t.Logf("%T\n", f4)

	t.Log(f4(&u))
	t.Log(strings.Repeat("*", 10))

	u = User{Name: "fadewalk"}
	m1 := u.GetName
	t.Logf("%T\n", m1)
	t.Log(m1())

	p := &User{Name: "kevin"}
	m2 := p.GetName // 语法糖 解引用
	t.Logf("%T\n", m2)
	t.Log(m2())

	m3 := u.PsetName // 语法糖 取引用
	t.Logf("%T\n", m3)
	m3("xxx")
	t.Log(u)

	m4 := p.PsetName
	t.Logf("%T\n", m4)
	m4("yyy")
	t.Log(p)
	
}


点击查看代码
=== RUN   TestPointer
    expr_test.go:54: func(*expr.User, string)
    expr_test.go:59: kk
    expr_test.go:62: func(*expr.User) string
    expr_test.go:64: kk
    expr_test.go:65: **********
    expr_test.go:69: func() string
    expr_test.go:70: fadewalk
    expr_test.go:74: func() string
    expr_test.go:75: kevin
    expr_test.go:78: func(string)
    expr_test.go:80: {xxx}
    expr_test.go:83: func(string)
    expr_test.go:85: &{yyy}
--- PASS: TestPointer (0.00s)
PASS


进程 已完成,退出代码为 0

posted @ 2023-01-15 17:04  前海渔文乐  阅读(18)  评论(0编辑  收藏  举报