2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Go语言学习笔记结构体部分

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

时间:2022-01-31 19:28:10

相关推荐

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

Go学习笔记

结构体声明方法结构体初始化练习 10.1 vcard.go:练习 10.2 personex1.go:练习 10.3 point.go:练习 10.4 rectangle.go: 结构体的标签匿名字段和内嵌结构体匿名字段内嵌结构体 结构体的方法定义方法调用方法非结构体的方法练习 10.6 employee_salary.go 外部包的方法结构体的内嵌类型练习 10.8 inheritance_car.go 多重继承练习 10.9 point_methods.go:练习 10.10 inherit_methods.go:练习 10.11 magic.go: 问题 10.2类型的 String() 方法和格式化描述符练习 10.12 type_string.go练习 10.13 celsius.go练习 10.14 days.go练习 10.15 timezones.go练习 10.16 stack_arr.go / stack_struct.go练习 10.17 main_stack.go

结构体声明方法

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

type identifier struct {field1 type1field2 type2...}

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

type T struct {a, b int}

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

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

结构体初始化

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

var t *T = new(T)

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

var t *Tt = 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 mainimport "fmt"type Address struct {name stringid int}type Birthday struct {year intmonth intday int}type Img struct {url string}type Vcard struct {namestringid intaddress *Addressbirthday *Birthdayimg*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 mainimport ("fmt""strings")type Person struct {firstName stringlastName 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 Personpers1.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 mainimport ("fmt""math")type Point2d struct {x inty int}type Polar struct {xy *Point2dz 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 *= fb.xy.y *= fb.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 mainimport "fmt"type Rectangle struct {length intheight 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 mainimport ("fmt""reflect")type TagType struct {// tagsfield1 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 mainimport "fmt"type innerS struct {in1 intin2 int}type outerS struct {b intc float32int // anonymous fieldinnerS //anonymous field}func main() {outer := new(outerS)outer.b = 6outer.c = 7.5outer.int = 60outer.in1 = 5outer.in2 = 10fmt.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 mainimport "fmt"type A struct {ax, ay int}type B struct {Abx, 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 mainimport "fmt"type TwoInts struct {a intb int}func main() {two1 := new(TwoInts)two1.a = 12two1.b = 10fmt.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 mainimport "fmt"type IntVector []intfunc (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 mainimport "fmt"type employee struct {salary int}func (a *employee) zhangxin(p float32) {res := float32(a.salary) * pa.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 mainimport ("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.first3Charsfmt.Println("First 3 chars:", m.first3Chars())}/* Output:Full time now: Mon Oct 24 15:34:54 Romance Daylight Time First 3 chars: Mon*/

结构体的内嵌类型

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

可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。

例子:

package mainimport ("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 {Pointname 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 mainimport "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 mainimport ("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 {CameraPhone}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 mainimport ("fmt""math")type Point2d struct {x inty int}type Polar struct {xy *Point2dz 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 *= fb.xy.y *= fb.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 mainimport ("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 {BaseFirstName stringLastName string}type Employee struct {Personsalary int}func main() {e := Employee{Person{Base{1}, "asda", "gs"}, 25000}fmt.Print(e.id)}

练习 10.11 magic.go:

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

package mainimport ("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 mainimport "fmt"type integer inttype 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 = 1var 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 mainimport ("fmt""strconv""strings")type T struct {a intb float32c string}func show_xiegang(a string) string {var SB strings.Builderb := []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 mainimport ("fmt""strconv")type Celsius float64func (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 mainimport ("fmt")type Day intfunc (d Day) String() string {SS := [7]string{"Monday", "Thuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}return SS[d]}type Season intconst (Monday Day = iotaThuesday Day = iota + 1Wednesday Day = iota + 2Thursday Day = iota + 3Friday Day = iota + 4Saturday Day = iota + 5Sunday 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 mainimport "fmt"type TZ intconst (Us TZ = iotaUk TZ = iota + 1Cn TZ = iota + 2As 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 mainimport ("fmt""strconv""strings")type Stack struct {num [4]intflag int}func (s *Stack) push(i int) bool {if s.flag == 4 {return false} else {s.num[s.flag] = is.flag += 1return true}}func (s *Stack) pop() (ok bool, res int) {if s.flag == 0 {ok = falsereturn} else {ok = trues.flag = s.flag - 1res = s.num[s.flag]return}}func (s *Stack) String() string {var sb strings.Builderfor 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 stackimport ("strconv""strings")type Stack struct {num [4]intflag int}func (s *Stack) Push(i int) bool {if s.flag == 4 {return false} else {s.num[s.flag] = is.flag += 1return true}}func (s *Stack) Pop() (ok bool, res int) {if s.flag == 0 {ok = falsereturn} else {ok = trues.flag = s.flag - 1res = s.num[s.flag]return}}func (s *Stack) String() string {var sb strings.Builderfor i := 0; i < s.flag; i++ {sb.WriteString("[" + strconv.Itoa(i) + ":" + strconv.Itoa(s.num[i]) + "]")}return sb.String()}

main包

package mainimport ("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)}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。