Go语言学习笔记结构体部分

结构体声明方法

结构体定义的一般方式如下:

type identifier struct {
    field1 type1
    field2 type2
    ...
}

它更适用于简单的结构体:

type T struct {a, b int}

结构体里的字段都有 名字,像 field1、field2 等,如果字段在代码中从来也不会被用到,那么可以命名它为 _。

结构体的字段可以是任何类型,甚至是结构体本身

结构体初始化

使用 new() 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:

var t *T = new(T)

如果需要可以把这条语句放在不同的行(比如定义是包范围的,但是分配却没有必要在开始就做)。

var t *T
t = new(T)

写这条语句的惯用方法如下,变量 t 是一个指向 T 的指针,此时结构体字段的值是它们所属类型的零值。

t := new(T)

声明 var t T 也会给 t 分配内存,并零值化内存,但是这个时候 t 是类型 T 。在这两种方式中,t 通常被称做类型 T 的一个实例 (instance) 或对象 (object)。

练习 10.1 vcard.go:

定义结构体 Address 和 VCard,后者包含一个人的名字、地址编号、出生日期和图像,试着选择正确的数据类型。构建一个自己的 vcard 并打印它的内容。

提示:
VCard 必须包含住址,它应该以值类型还是以指针类型放在 VCard 中呢?
第二种会好点,因为它占用内存少。包含一个名字和两个指向地址的指针的 Address 结构体可以使用 %v 打印:
{Kersschot 0x126d2b80 0x126d2be0}

package main

import "fmt"

type Address struct {
	name string
	id   int
}
type Birthday struct {
	year  int
	month int
	day   int
}
type Img struct {
	url string
}
type Vcard struct {
	name     string
	id       int
	address  *Address
	birthday *Birthday
	img      *Img
}

func main() {
	ad := Address{"陕西省西安市未央区汉城街道长安大学", 1}
	bir := Birthday{1999, 5, 1}
	img := Img{"asdasdasdsa"}
	v := Vcard{"sam", 1, &ad, &bir, &img}
	fmt.Printf("%v\n", v)
	fmt.Printf("%v\n", ad)
	fmt.Printf("%v\n", bir)
	fmt.Printf("%v\n", img)
}

结果:在这里插入图片描述

练习 10.2 personex1.go:

修改 personex1.go,使它的参数 upPerson 不是一个指针,解释下二者的区别。

package main

import (
	"fmt"
	"strings"
)

type Person struct {
	firstName string
	lastName  string
}

func upPerson(p *Person) {
	p.firstName = strings.ToUpper(p.firstName)
	p.lastName = strings.ToUpper(p.lastName)
}

func main() {
	// 1- struct as a value type:
	var pers1 Person
	pers1.firstName = "Chris"
	pers1.lastName = "Woodward"
	upPerson(&pers1)
	fmt.Printf("The name of the person is %s %s\n", pers1.firstName, pers1.lastName)
	// 2 - struct as a pointer:
	pers2 := new(Person)
	pers2.firstName = "Chris"
	pers2.lastName = "Woodward"
	upPerson(pers2)
	fmt.Printf("The name of the person is %s %s\n", pers2.firstName, pers2.lastName)
	// 3 - struct as a literal:
	pers3 := &Person{"Chris", "Woodward"}
	upPerson(pers3)
	fmt.Printf("The name of the person is %s %s\n", pers3.firstName, pers3.lastName)

}

结果:
在这里插入图片描述

练习 10.3 point.go:

使用坐标 X、Y 定义一个二维 Point 结构体。同样地,对一个三维点使用它的极坐标定义一个 Polar 结构体。实现一个 Abs() 方法来计算一个 Point 表示的向量的长度,实现一个 Scale() 方法,它将点的坐标乘以一个尺度因子(提示:使用 math 包里的 Sqrt() 函数)(function Scale that multiplies the coordinates of a point with a scale factor)。

package main

import (
	"fmt"
	"math"
)

type Point2d struct {
	x int
	y int
}
type Polar struct {
	xy *Point2d
	z  int
}

func Abs(a Point2d) (res float64) {
	res = math.Sqrt(float64(a.x*a.x + a.y*a.y))
	return
}
func Scale(b *Polar, f int) {
	b.xy.x *= f
	b.xy.y *= f
	b.z *= f
}
func main() {
	p1 := Point2d{3, 5}
	p2 := Polar{&p1, 4}
	fmt.Printf("%v\n", p1)
	fmt.Printf("%v\n", p2)
	fmt.Print(Abs(p1), "\n")
	Scale(&p2, 2)
	fmt.Printf("%v\n", p1)
	fmt.Printf("%v\n", p2)
	fmt.Print(Abs(p1), "\n")
}

在这里插入图片描述

练习 10.4 rectangle.go:

定义一个 Rectangle 结构体,它的长和宽是 int 类型,并定义方法 Area() 和 Perimeter(),然后进行测试。

package main

import "fmt"

type Rectangle struct {
	length int
	height int
}

func Area(a Rectangle) int {
	return a.length * a.height
}
func Perimeter(a Rectangle) int {
	return a.length*2 + a.height*2
}
func main() {
	rect := Rectangle{10, 5}
	fmt.Printf("%v\n", rect)
	fmt.Print(Area(rect), "\n")
	fmt.Print(Perimeter(rect), "\n")

}

在这里插入图片描述

结构体的标签

例子

package main

import (
	"fmt"
	"reflect"
)

type TagType struct { // tags
	field1 bool   "An important answer"
	field2 string "The name of the thing"
	field3 int    "How much there are"
}

func main() {
	tt := TagType{true, "Barak Obama", 1}
	for i := 0; i < 3; i++ {
		refTag(tt, i)
	}
}

func refTag(tt TagType, ix int) {
	ttType := reflect.TypeOf(tt)
	ixField := ttType.Field(ix)
	fmt.Printf("%v\n", ixField.Tag)
}

在这里插入图片描述

匿名字段和内嵌结构体

匿名字段

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。

package main

import "fmt"

type innerS struct {
	in1 int
	in2 int
}

type outerS struct {
	b    int
	c    float32
	int  // anonymous field
	innerS //anonymous field
}

func main() {
	outer := new(outerS)
	outer.b = 6
	outer.c = 7.5
	outer.int = 60
	outer.in1 = 5
	outer.in2 = 10

	fmt.Printf("outer.b is: %d\n", outer.b)
	fmt.Printf("outer.c is: %f\n", outer.c)
	fmt.Printf("outer.int is: %d\n", outer.int)
	fmt.Printf("outer.in1 is: %d\n", outer.in1)
	fmt.Printf("outer.in2 is: %d\n", outer.in2)

	// 使用结构体字面量
	outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
	fmt.Println("outer2 is:", outer2)
}
内嵌结构体

同样地结构体也是一种数据类型,所以它也可以作为一个匿名字段来使用,如同上面例子中那样。外层结构体通过 outer.in1 直接进入内层结构体的字段,内嵌结构体甚至可以来自其他包。内层结构体被简单的插入或者内嵌进外层结构体。这个简单的“继承”机制提供了一种方式,使得可以从另外一个或一些类型继承部分或全部实现。

package main

import "fmt"

type A struct {
	ax, ay int
}

type B struct {
	A
	bx, by float32
}

func main() {
	b := B{A{1, 2}, 3.0, 4.0}
	fmt.Println(b.ax, b.ay, b.bx, b.by)
	fmt.Println(b.A)
}

结构体的方法

定义方法
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
调用方法
recv.Method1()

例子:

package main

import "fmt"

type TwoInts struct {
	a int
	b int
}

func main() {
	two1 := new(TwoInts)
	two1.a = 12
	two1.b = 10

	fmt.Printf("The sum is: %d\n", two1.AddThem())
	fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))

	two2 := TwoInts{3, 4}
	fmt.Printf("The sum is: %d\n", two2.AddThem())
}

func (tn *TwoInts) AddThem() int {
	return tn.a + tn.b
}

func (tn *TwoInts) AddToParam(param int) int {
	return tn.a + tn.b + param
}
非结构体的方法
package main

import "fmt"

type IntVector []int

func (v IntVector) Sum() (s int) {
	for _, x := range v {
		s += x
	}
	return
}

func main() {
	fmt.Println(IntVector{1, 2, 3}.Sum()) // 输出是6
}
练习 10.6 employee_salary.go

定义结构体 employee,它有一个 salary 字段,给这个结构体定义一个方法 giveRaise 来按照指定的百分比增加薪水。

package main

import "fmt"

type employee struct { 
	salary int
}

func (a *employee) zhangxin(p float32) {
	res := float32(a.salary) * p
	a.salary = int(res)
}
func main() {
	p := employee{25000}
	fmt.Printf("%v\n", p)
	p.zhangxin(1.1)
	fmt.Printf("%v\n", p)

}

在这里插入图片描述

外部包的方法

有一个间接的方式:可以先定义该类型(比如:int 或 float32(64))的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。

package main

import (
	"fmt"
	"time"
)

type myTime struct {
	time.Time //anonymous field
}

func (t myTime) first3Chars() string {
	return t.Time.String()[0:3]
}
func main() {
	m := myTime{time.Now()}
	// 调用匿名 Time 上的 String 方法
	fmt.Println("Full time now:", m.String())
	// 调用 myTime.first3Chars
	fmt.Println("First 3 chars:", m.first3Chars())
}

/* Output:
Full time now: Mon Oct 24 15:34:54 Romance Daylight Time 2011
First 3 chars: Mon
*/
结构体的内嵌类型

内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。当然类型可以有只作用于本身实例而不作用于内嵌“父”类型上的方法。
可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。
例子:

package main

import (
	"fmt"
	"math"
)

type Point struct {
	x, y float64
}

func (p *Point) Abs() float64 {
	return math.Sqrt(p.x*p.x + p.y*p.y)
}

type NamedPoint struct {
	Point
	name string
}

func (n *NamedPoint) Abs() float64 {
	return n.Point.Abs() * 100.
}

func main() {
	n := &NamedPoint{Point{3, 4}, "Pythagoras"}
	fmt.Println(n.Abs()) // 打印 5
}
练习 10.8 inheritance_car.go

创建一个上面 Car 和 Engine 可运行的例子,并且给 Car 类型一个 wheelCount 字段和一个 numberOfWheels() 方法。

创建一个 Mercedes 类型,它内嵌 Car,并新建 Mercedes 的一个实例,然后调用它的方法。

然后仅在 Mercedes 类型上创建方法 sayHiToMerkel() 并调用它。

package main

import "fmt"

type Car struct {
	wheelCount int
}

func (c *Car) numberOfWheels() {

	fmt.Print("有四个轮子\n")

}

type Mercedes struct {
	Car
}

func (m Mercedes) sayHiToMercedes() {
	fmt.Print("hello\n")
}

func main() {
	m := Mercedes{Car{4}}
	m.numberOfWheels()
	m.sayHiToMercedes()
}

在这里插入图片描述

多重继承

多重继承指的是类型获得多个父类型行为的能力,它在传统的面向对象语言中通常是不被实现的(C++ 和 Python 例外)。因为在类继承层次中,多重继承会给编译器引入额外的复杂度。但是在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。

作为一个例子,假设有一个类型 CameraPhone,通过它可以 Call(),也可以 TakeAPicture(),但是第一个方法属于类型 Phone,第二个方法属于类型 Camera。

只要嵌入这两个类型就可以解决这个问题,如下所示

package main

import (
	"fmt"
)

type Camera struct{}

func (c *Camera) TakeAPicture() string {
	return "Click"
}

type Phone struct{}

func (p *Phone) Call() string {
	return "Ring Ring"
}

type CameraPhone struct {
	Camera
	Phone
}

func main() {
	cp := new(CameraPhone)
	fmt.Println("Our new CameraPhone exhibits multiple behaviors...")
	fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
	fmt.Println("It works like a Phone too: ", cp.Call())
}
练习 10.9 point_methods.go:

从 point.go 开始(第 10.1 节的练习):使用方法来实现 Abs() 和 Scale()函数,Point 作为方法的接收者类型。也为 Point3 和 Polar 实现 Abs() 方法。完成了 point.go 中同样的事情,只是这次通过方法。

package main

import (
	"fmt"
	"math"
)

type Point2d struct {
	x int
	y int
}
type Polar struct {
	xy *Point2d
	z  int
}

func (a Point2d) Abs() (res float64) {
	res = math.Sqrt(float64(a.x*a.x + a.y*a.y))
	return
}
func (b *Polar) Scale(f int) {
	b.xy.x *= f
	b.xy.y *= f
	b.z *= f
}
func main() {
	p1 := Point2d{3, 5}
	p2 := Polar{&p1, 4}
	fmt.Printf("%v\n", p1)
	fmt.Printf("%v\n", p2)
	fmt.Print(p1.Abs(), "\n")
	p2.Scale(2)
	fmt.Printf("%v\n", p1)
	fmt.Printf("%v\n", p2)
	fmt.Print(p1.Abs(), "\n")
}
练习 10.10 inherit_methods.go:

定义一个结构体类型 Base,它包含一个字段 id,方法 Id() 返回 id,方法 SetId() 修改 id。结构体类型 Person 包含 Base,及 FirstName 和 LastName 字段。结构体类型 Employee 包含一个 Person 和 salary 字段。

创建一个 employee 实例,然后显示它的 id。

package main

import (
	"fmt"
)

type Base struct {
	id int
}

func (b *Base) getId() int {
	return b.id
}
func (b *Base) setId(a int) {
	b.id = a
}

type Person struct {
	Base
	FirstName string
	LastName  string
}
type Employee struct {
	Person
	salary int
}

func main() {
	e := Employee{Person{Base{1}, "asda", "gs"}, 25000}
	fmt.Print(e.id)
}

在这里插入图片描述

练习 10.11 magic.go:

首先预测一下下面程序的结果,然后动手实验下:

package main

import (
	"fmt"
)

type Base struct{}

func (Base) Magic() {
	fmt.Println("base magic")
}

func (self Base) MoreMagic() {
	self.Magic()
	self.Magic()
}

type Voodoo struct {
	Base
}

func (Voodoo) Magic() {
	fmt.Println("voodoo magic")
}

func main() {
	v := new(Voodoo)
	v.Magic()
	v.MoreMagic()
}

在这里插入图片描述

问题 10.2

a)假设定义: type Integer int,完成 get() 方法的方法体: func (p Integer) get() int { … }。

b)定义: func f(i int) {}; var v Integer ,如何就 v 作为参数调用f?

c)假设 Integer 定义为 type Integer struct {n int},完成 get() 方法的方法体:func (p Integer) get() int { … }。

d)对于新定义的 Integer,和 b)中同样的问题。

package main

import "fmt"

type integer int
type integer1 struct {
	n int
}

func (p integer) Get() int {
	return int(p)
}
func (p integer1) Get1() int {
	return p.n
}
func f(i int) {
	fmt.Print(i, "\n")
}
func main() {
	var v integer = 1
	var v1 integer1 = integer1{2}
	f(v.Get())
	f(v1.Get1())
}
类型的 String() 方法和格式化描述符

当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。
注意:
不要在 String() 方法里面调用涉及 String() 方法的方法,它会导致意料之外的错误,比如下面的例子,它导致了一个无限递归调用(TT.String() 调用 fmt.Sprintf,而 fmt.Sprintf 又会反过来调用 TT.String()),很快就会导致内存溢出:

练习 10.12 type_string.go

给定结构体类型 T:

type T struct {
a int
b float32
c string
}
值 t: t := &T{7, -2.35, “abc\tdef”}。给 T 定义 String(),使得 fmt.Printf(“%v\n”, t) 输出:7 / -2.350000 / “abc\tdef”。

package main

import (
	"fmt"
	"strconv"
	"strings"
)

type T struct {
	a int
	b float32
	c string
}

func show_xiegang(a string) string {
	var SB strings.Builder
	b := []rune(a)
	for i, v := range b {
		if v == 9 {
			SB.WriteString(a[:i])
			SB.WriteString("\\t")
			SB.WriteString(a[i+1:])
		}
	}
	return SB.String()
}
func (t *T) String() string {
	return strconv.Itoa(t.a) + "/" + strconv.FormatFloat(float64(t.b), 'f', 6, 64) + "/\"" + show_xiegang(t.c) + "\""
}
func main() {
	t := &T{7, -2.35, "abc\tdef"}
	fmt.Printf("%v", t)

}

结果:
在这里插入图片描述

练习 10.13 celsius.go

为 float64 定义一个别名类型 Celsius,并给它定义 String(),它输出一个十进制数和 °C 表示的温度值。

package main

import (
	"fmt"
	"strconv"
)

type Celsius float64

func (c Celsius) String() string {
	return strconv.FormatFloat(float64(c), 'f', 0, 64) + "°C"
}

func main() {
	c := Celsius(125.546132165)
	fmt.Printf("%v", c)

}

结果:
在这里插入图片描述

练习 10.14 days.go

为 int 定义一个别名类型 Day,定义一个字符串数组它包含一周七天的名字,为类型 Day 定义 String() 方法,它输出星期几的名字。使用 iota 定义一个枚举常量用于表示一周的中每天(MO、TU…)。

package main

import (
	"fmt"
)

type Day int

func (d Day) String() string {
	SS := [7]string{"Monday", "Thuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
	return SS[d]
}

type Season int

const (
	Monday    Day = iota
	Thuesday  Day = iota + 1
	Wednesday Day = iota + 2
	Thursday  Day = iota + 3
	Friday    Day = iota + 4
	Saturday  Day = iota + 5
	Sunday    Day = iota + 6
)

func main() {
	M := Day(Monday)
	fmt.Printf("%v", M)

}

在这里插入图片描述

练习 10.15 timezones.go

为 int 定义别名类型 TZ,定义一些常量表示时区,比如 UTC,定义一个 map,它将时区的缩写映射为它的全称,比如:UTC -> “Universal Greenwich time”。为类型 TZ 定义 String() 方法,它输出时区的全称。

package main

import "fmt"

type TZ int

const (
	Us TZ = iota
	Uk TZ = iota + 1
	Cn TZ = iota + 2
	As TZ = iota + 3
)

func (z TZ) String() string {
	allname := [4]string{"美国", "英国", "中国", "澳大利亚"}
	return allname[z]
}
func main() {
	name := TZ(Us)
	fmt.Printf("%v", name)
}

在这里插入图片描述

练习 10.16 stack_arr.go / stack_struct.go

实现栈 (stack) 数据结构:

在这里插入图片描述

它的格子包含数据,比如整数 i、j、k 和 l 等等,格子从底部(索引 0)至顶部(索引 n)来索引。这个例子中假定 n = 3,那么一共有 4 个格子。

一个新栈中所有格子的值都是 0。

将一个新值放到栈的最顶部一个空(包括零)的格子中,这叫做 push。

获取栈的最顶部一个非空(非零)的格子的值,这叫做 pop。 现在可以理解为什么栈是一个后进先出 (LIFO) 的结构了吧。

为栈定义一个 Stack 类型,并为它定义 Push 和 Pop 方法,再为它定义 String() 方法(用于调试)输出栈的内容,比如:[0:i] [1:j] [2:k] [3:l]。

1)stack_arr.go:使用长度为 4 的 int 数组作为底层数据结构。

2) stack_struct.go:使用包含一个索引和一个 int 数组的结构体作为底层数据结构,索引表示第一个空闲的位置。

3)使用常量 LIMIT 代替上面表示元素个数的 4 重新实现上面的 1)和 2),使它们更具有一般性。

ackage main

import (
	"fmt"
	"strconv"
	"strings"
)

type Stack struct {
	num  [4]int
	flag int
}

func (s *Stack) push(i int) bool {
	if s.flag == 4 {
		return false
	} else {
		s.num[s.flag] = i
		s.flag += 1
		return true
	}
}
func (s *Stack) pop() (ok bool, res int) {
	if s.flag == 0 {
		ok = false
		return
	} else {
		ok = true
		s.flag = s.flag - 1
		res = s.num[s.flag]
		return
	}
}
func (s *Stack) String() string {
	var sb strings.Builder
	for i := 0; i < s.flag; i++ {
		sb.WriteString("[" + strconv.Itoa(i) + ":" + strconv.Itoa(s.num[i]) + "]")
	}
	return sb.String()
}
func main() {
	s := &Stack{num: [4]int{}, flag: 0}
	fmt.Printf("%v\n", s)
	s.push(1)
	fmt.Printf("%v\n", s)
	s.push(2)
	fmt.Printf("%v\n", s)
	s.push(3)
	fmt.Printf("%v\n", s)
	s.push(4)
	fmt.Printf("%v\n", s)
	s.pop()
	fmt.Printf("%v\n", s)
	s.pop()
	fmt.Printf("%v\n", s)
	s.pop()
	fmt.Printf("%v\n", s)
	s.pop()
	fmt.Printf("%v\n", s)
}

结果:
在这里插入图片描述

练习 10.17 main_stack.go

从练习 10.16 开始(它基于结构体实现了一个栈结构),为栈的实现 (stack_struct.go) 创建一个单独的包 stack,并从 main 包 main.stack.go 中调用它。
stack包

package stack

import (
	"strconv"
	"strings"
)

type Stack struct {
	num  [4]int
	flag int
}

func (s *Stack) Push(i int) bool {
	if s.flag == 4 {
		return false
	} else {
		s.num[s.flag] = i
		s.flag += 1
		return true
	}
}
func (s *Stack) Pop() (ok bool, res int) {
	if s.flag == 0 {
		ok = false
		return
	} else {
		ok = true
		s.flag = s.flag - 1
		res = s.num[s.flag]
		return
	}
}
func (s *Stack) String() string {
	var sb strings.Builder
	for i := 0; i < s.flag; i++ {
		sb.WriteString("[" + strconv.Itoa(i) + ":" + strconv.Itoa(s.num[i]) + "]")
	}
	return sb.String()
}

main包

package main

import (
	"awesomeProject/stack"
	"fmt"
)

func main() {
	s := &stack.Stack{}
	fmt.Printf("%v\n", s)
	s.Push(1)
	fmt.Printf("%v\n", s)
	s.Push(2)
	fmt.Printf("%v\n", s)
	s.Push(3)
	fmt.Printf("%v\n", s)
	s.Push(4)
	fmt.Printf("%v\n", s)
	s.Pop()
	fmt.Printf("%v\n", s)
	s.Pop()
	fmt.Printf("%v\n", s)
	s.Pop()
	fmt.Printf("%v\n", s)
	s.Pop()
	fmt.Printf("%v\n", s)
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值