第7章 使用结构体和指针
7.1 结构体是什么
结构体是一系列具有指定数据类型的数据字段,它能够让您通过单个变量引用一系列相关的值。通过使用结构体,可在单个变量中存储众多类型不同的数据字段。通过使用结构体,可提高模块化程度,还能够让您创建并传递复杂的数据结构。
package main
import (
"fmt"
"reflect"
)
type Movie struct {
Name string
Rating float32
}
func main() {
m := Movie{
Name: "cityzen",
//注意,此处结尾必须有逗号,不然报错!
Rating: 10,
}
fmt.Println(m.Name);
}
- 关键字type指定一种新类型。
- 将新类型的名称指定为Movie。
- 类型名右边是数据类型,这里为结构体。
- 在大括号内,使用名称和类型指定了一系列数据字段。请注意,此时没有给数据字段赋值。可将结构体视为模板。
- 在main函数中,使用简短变量赋值声明并初始化了变量m,给数据字段指定的值为相应的数据类型。
- 使用点表示法访问数据字段并将其打印到控制台。
7.2 创建结构体
声明结构体后,就可通过多种方式创建它。假设您已经声明了一个结构体,那么就可直接声明这种类型的变量。
要调试或查看结构体的值,可使用fmt包将结构体的字段名和值打印出来。为此,可使用占位符%+v。
type Movie struct {
Name string
Rating float32
}
func main() {
var m Movie
m.Name = "Sun"
m.Rating = 9.2
fmt.Printf("%+v", m);
}
输出
{Name:Sun Rating:9.2}
也可使用关键字new来创建结构体实例
var q = new(Movie)
7.3 嵌套结构体
时候,数据结构需要包含多个层级。此时,虽然可选择使用诸如切片等数据类型,但有时候需要的数据结构更复杂。为建立较复杂的数据结构,在一个结构体中嵌套另一个结构体的方式很有用。
type Hero struct {
Name string
Age int
Adr Address
}
type Address struct{
Street string
City string
}
7.4 自定义结构体数据字段的默认值
创建结构体时,如果没有给其数据字段指定值,它们将被设为Go语言中的的零值。Go语言没有提供自定义默认值的内置方法,但可使用构造函数来实现这个目标。构造函数创建结构体,并将没有指定值的数据字段设置为默认值。
type Hero struct {
Name string
Age int
}
func NewHero() Hero{
h := Hero{
Name : "yx",
Age : 0,
}
return h;
}
func main() {
var h = NewHero();
fmt.Printf("%+v\n", h);
}
这里不直接创建结构体Hero,而是使用函数NewHero来创建,从而让字段包含自定义的默认值。请注意,这只是一种技巧,而并非Go语言规范的组成部分。
7.5 比较结构体
对结构体进行比较,要先看它们的类型和值是否相同。对于类型相同的结构体,可使用相等性运算符来比较。要判断两个结构体是否相等,可使用==;要判断它们是否不等,可使用!=。
不能对两个类型不同的结构体进行比较,否则将导致编译错误。
7.6 理解公有和私有值
要导出结构体及其字段,结构体及其字段的名称都必须以大写字母打头。
说明:
- 这里的导出,指的是不同包(package)之间的导出。
- 大写的结构体或者字段可以在包外被导出并引用。小写结构体及属性字段只能在本包内引用。
7.7 区分指针引用和值引用
将指向结构体的变量赋给另一个变量时,被称为赋值。
a := b
赋值后,a与b相同,但它是b的副本,而不是指向b的引用。修改b不会影响a,反之亦然。
要修改原始结构体实例包含的值,必须使用指针。指针是指向内存地址的引用,因此使用它操作的不是结构体的副本而是其本身。要获得指针,可在变量名前加上&。
package main
import (
"fmt"
)
type Movie struct {
Name string
Rating float32
}
func main() {
var m Movie
m.Name = "Sun"
m.Rating = 9.2
var mcopy *Movie = &m;
mcopy.Name = "lunar"
fmt.Printf("%+v\n%+v\n", m, *mcopy);
fmt.Printf("%p\n%p\n", &m, mcopy);
}
输出
{Name:lunar Rating:9.2}
{Name:lunar Rating:9.2}
0xc00000a080
0xc00000a080
mcopy的值,即是m的地址,也就是&m
第8章创建方法和接口
8.1 使用方法
方法类似于函数,但有一点不同:在关键字func后面添加了另一个参数部分,用于接受单个参数。下面的示例给结构体Movie添加了一个方法。
type Movie struct {
Name string
Rating float32
}
func (m *Movie) summary() string{
//code
}
在方法声明中,关键字func后面多了一个参数——接收者。严格地说,方法接收者是一种类型,这里是指向结构体Movie的指针。接下来是方法名、参数以及返回类型。
package main
import (
"fmt"
"strconv"
)
type Movie struct {
Name string
Rating float64
}
func (m *Movie) summary() string{
r := strconv.FormatFloat(m.Rating, 'f', 1, 64)
return m.Name + ", " + r
}
func main() {
var m = Movie{"Summer", 6.6}
fmt.Println(m.summary());
}
8.2 创建方法集
方法集是可对特定数据类型进行调用的一组方法。方法集可包含的方法数量不受限制,这是一种封装功能和创建库代码的有效方式。
type Math struct {
Num int
}
func (m *Math) Square() int{
return m.Num * m.Num
}
func (m *Math) Cub() int{
return m.Num * m.Num * m.Num
}
8.3 使用方法和指针
方法是一个接受被称为接收者的特殊参数的函数,接收者可以是指针,也可以是值。
指针和值之间的差别很微妙,但选择使用指针还是值这一点很简单:如果需要修改原始结构体,就使用指针;如果需要操作结构体,但不想修改原始结构体,就使用值。
8.4 使用接口
在Go语言中,接口指定了一个方法集,这是实现模块化的强大方式。您可将接口视为方法集的蓝本,它描述了方法集中的所有方法,但没有实现它们。
下面的接口描述了开关机器人的方式。
type Robot interface{
PowerOn() error
}
那么如何使用接口呢?接口是方法集的蓝本,要使用接口,必须先实现它。如果代码满足了接口的要求,就实现了接口。
可以像下面这样来实现接口Robot。
type T850 struct{
Name string
}
func (a *T850) PowerOn() error{
return nil
}
接口也是一种类型,可作为参数传递给函数,因此可编写可重用于多个接口实现的函数。例如,编写一个可用于启动任何机器人的函数。
func Boot(r Robot) error{
return r.PowerOn()
}
这个函数将接口Robot的实现作为参数,并返回调用方法PowerOn的结果。这个函数可用于启动任何机器人,而不管其方法PowerOn是如何实现的。
下面是一个完整的使用接口Robot的示例。
package main
import (
"fmt"
"errors"
)
type Robot interface{
PowerOn() error
}
type T850 struct{
Name string
}
func (a *T850) PowerOn() error{
return nil
}
type R2D2 struct{
Broken bool
}
func (r *R2D2) PowerOn() error{
if r.Broken{
return errors.New("R2D2 is broken")
}else{
return nil
}
}
func Boot(r Robot) error{
return r.PowerOn()
}
func main() {
var t = T850{"The xb"}
var r = R2D2{true}
fmt.Println(Boot(&t))
fmt.Println(Boot(&r))
}
8.6 问与答
问:函数和方法有何不同?
答:严格地说,方法和函数的唯一差别在于,方法多了一个指定接收者的参数,这让您能够对数据类型调用方法,从而提高代码重用性和模块化程度。
第9章使用字符串
9.1 创建字符串字面量
Go语言支持两种创建字符串字面量的方式。解释型字符串字面量是用双引号括起的字符,如"hello"。一种创建字符串的简单方式是使用解释型字符串字面量。
var str = "this is a demo"
除换行符和未转义的双引号外,解释型字符串字面量可包含其他任何字符。对于前面有反斜杠(\)的字符,将像它们出现在rune字面量中那样进行解读。
9.2 理解rune字面量
通过使用rune字面量,可将解释型字符串字面量分成多行,还可在其中包含制表符和其他格式选项。
var str = "this\tis\na\tdemo!"
输出
this is
a demo!
原始字符串字面量用反引号括起,如'hello'。不同于解释型字符串,原始字符串中的反斜杠没有特殊含义,Go按原样解释这种字符串。
str := `this is
a demo!`
9.3 拼接字符串
要拼接(合并)字符串,可将运算符+用于字符串变量。还可使用复合赋值运算符+=来拼接字符串。
9.3.1 使用缓冲区拼接字符串
对于简单而少量的拼接,使用运算符+和+=的效果虽然很好,但随着拼接操作次数的增加,这种做法的效率并不高。如果需要在循环中拼接字符串,则使用空的字节缓冲区来拼接的效率更高。
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
for i:=0; i<500; i++{
buffer.WriteString("z")
}
fmt.Println(buffer.String())
}
9.3.2 理解字符串是什么
要更深入地理解字符串以及如何操作它们,必须首先知道Go语言中的字符串实际上是只读的字节切片。要获悉字符串包含多少个字节,可使用Go语言的内置函数len。
由于Go字符串为字节切片,因此可输出字符串中特定位置的字节值。
s := "hello";
fmt.Println(len(s))
fmt.Println(s[1])
结果为
5
101
输出101,是因为通过索引访问字符串时,访问的是字节而不是字符,因此显示的是以十进制表示的字节值。
在Go语言中,可使用格式设置将十进制值转换为字符和二进制表示。
s := "hello";
fmt.Printf("%q", s[1]);//h
fmt.Printf("%b", s[1]);//1100101
9.3.3 处理字符串
给字符串变量赋值后,就可使用标准库中的strings包提供的任何方法。
1.将字符串转换为小写
ToLower()
strings.ToLower("YX IS YX");
2.在字符串中查找子串
Index()
处理字符串时,另一个常见的任务是在字符串中查找子串。方法Index提供了这样的功能,它接受的第二个参数是要查找的子串。如果找到,就返回第一个子串的索引号;如果没有找到,就返回-1。
strings.Index("qq input is good", "input")//3
3.删除字符串中的空格
TrimSpace()
删除开头和末尾的空格
strings.TrimSpace(" input ")//input