文章目录
  1. 1. 基本类型
  2. 2. 类型转换
  3. 3. 类型断言
  4. 4. 结构
  5. 5. 初始化
  6. 6. 组合vs继承
  7. 7. 案例分析

Go内置类型实战,包括类型转换,类型断言,结构类型,结构继承等内容。

基本类型

Go语言主要有如下内置基本类型,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bool
string

Numeric types:

uint either 32 or 64 bits
int same size as uint
uintptr an unsigned integer large enough to store the uninterpreted bits of
a pointer value
uint8 the set of all unsigned 8-bit integers (0 to 255)
uint16 the set of all unsigned 16-bit integers (0 to 65535)
uint32 the set of all unsigned 32-bit integers (0 to 4294967295)
uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8 the set of all signed 8-bit integers (-128 to 127)
int16 the set of all signed 16-bit integers (-32768 to 32767)
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers
(-9223372036854775808 to 9223372036854775807)

float32 the set of all IEEE-754 32-bit floating-point numbers
float64 the set of all IEEE-754 64-bit floating-point numbers

complex64 the set of all complex numbers with float32 real and imaginary parts
complex128 the set of all complex numbers with float64 real and imaginary parts

byte alias for uint8
rune alias for int32 (represents a Unicode code point)

内置类型基本使用示例,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
"math/cmplx"
)

var (
goIsFun bool = true
maxInt uint64 = 1<<64 - 1
complex complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
const f = "%T(%v)\n"
fmt.Printf(f, goIsFun, goIsFun)
fmt.Printf(f, maxInt, maxInt)
fmt.Printf(f, complex, complex)
}

输出结果:
1
2
3
bool(true)
uint64(18446744073709551615)
complex128((2+3i))

类型转换

T(v)表示将v的类型转换为T类型,一些数值类型转换如下,

1
2
3
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

简化一下,
1
2
3
i := 42
f := float64(i)
u := uint(f)

Go语言中不同类型之间需要显示转换才能相互赋值,如传入的参数类型与函数接收的参数不同时,需要手动进行类型转换才能传入。
译者注: Go语言中除了byteuint8之间以及runeint32之间可以相互进行赋值操作外,其它不同类型直接都需要显示转换才能进行相互赋值操作。 如,
1
2
3
4
5
6
var i byte = 1
var u uint8
u = i // 可以直接赋值,不需显示转换 ,同样rune 与 int32 之间也是一样的。

var i int = 1
var f float64 = float64(i) //这里需要显示将 int 转换float64 才能赋值成功!

类型断言

如果想判断一个变量的数据类型,或将当前类型(如 interface{})作相应的类型判断,可以借助类型断言来解决。类型断言试图将当前变量转换为指定的数据类型,并返回转换之后相应的指针对象(如果转换成功)及是否转换成功的标志值。下面示例中,timeMap函数接受一个变量,并断言这个变量类型是否为map[string]interface{}类型,如果是则为其初始化一个keyupdated_at,valuetime.Now()的值对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"time"
)

func timeMap(y interface{}) {
z, ok := y.(map[string]interface{})
if ok {
z["updated_at"] = time.Now()
}
}

func main() {
foo := map[string]interface{}{
"Matt": 42,
}
timeMap(foo)
fmt.Println(foo)
}

Go语言并不对空接口interface{}进行类型断言,如果函数内部如对不同类型参数会作出不同的处理,则通常需借助类型断言来完成,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Stringer interface {
String() string
}

type fakeString struct {
content string
}

func (s *fakeString) String() string {
return s.content
}

func printString(value interface{}) {
switch str := value.(type) {
case string:
fmt.Println(str)
case Stringer:
fmt.Println(str.String())
}
}

如果给printString传入一个fakeString,因为fakeString实现了Stringer方法,那么printString中会处理Stringer这个case,如果直接传入一个字符串给printString,则显然会处理string这个case 从而直接输出传入的字符串。
示例代码
类型断言也可以用于判断具体的error类型,如:
1
2
3
4
5
6
7
if err != nil {
if msqlerr, ok := err.(*mysql.MySQLError); ok && msqlerr.Number == 1062 {
log.Println("We got a MySQL duplicate :(")
} else {
return err
}
}

结构

struct是多个字段或属性的集合,用户也可以自定义一个像structinterface这样的数据类型。如果你学过面向对象编程,你可以将struct理解为一个轻量级的class支持字段属性的集合,但不支持继承。struct中默认提供getset方法,需要注意的是只有首字母大写的struct变量才能被包访问。struct可以通过Name:后跟初始值来初始化struct,这样初始化时可以不按struct中字段声明的顺序进行初始化,如果在struct变量前加上&符号,返回的是一个指向这个struct的指针对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"time"
)

type Bootcamp struct {
// Latitude of the event
Lat float64
// Longitude of the event
Lon float64
// Date of the event
Date time.Time
}

func main() {
fmt.Println(Bootcamp{
Lat: 34.012836,
Lon: -118.495338,
Date: time.Now(),
})
}

声明并初始化struct对象,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type Point struct {
X, Y int
}

var (
p = Point{1, 2} // 初始化一个Point对象
q = &Point{1, 2} // 初始化一个Point指针对象
r = Point{X: 1} // Y值默认为0
s = Point{} // X和Y的值都默认为0
)

func main() {
fmt.Println(p, q, r, s)
}

示例代码

可通过.符号访问struct中的成员变量,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"time"
)

type Bootcamp struct {
Lat, Lon float64
Date time.Time
}

func main() {
event := Bootcamp{
Lat: 34.012836,
Lon: -118.495338,
}
event.Date = time.Now()
fmt.Printf("Event on %s, location (%f, %f)",event.Date, event.Lat, event.Lon)
}

示例代码

初始化

Go支持用new表达式初始化变量,分配一个类型零值并返回指向这个类型的指针给变量。

1
x := new(int)

一种常见的初始化一个包含struct或引用变量的方法是创建一个struct字段。另一种方法是通过创建构造函数来完成初始化操作。这两种方法是当需要自定义初始化字段值常用的两种方法。下面用new方法和用空struct字段初始化一个struct是等价的,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

type Bootcamp struct {
Lat float64
Lon float64
}

func main() {
x := new(Bootcamp)
y := &Bootcamp{}
fmt.Println(*x == *y)
}

上面xy的初始化操作是等价的;后面要接触的slicesmapschannels结构初始化一般需要自定义初始化字段如长度,容量等,所以这些结构通常用make关键字来初始化操作。

组合vs继承

Go不支持面向对象编程中的继承操作,但是可以使用组合和接口来完成继承的操作。Go支持OOP中的组合(或者说绑定)操作。下面是一个关于地址操作的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type User struct {
Id int
Name string
Location string
}

type Player struct {
User
GameId int
}

func main() {
p := Player{}
p.Id = 42
p.Name = "Matt"
p.Location = "LA"
p.GameId = 90404
fmt.Printf("%+v", p)
}

示例代码
上面地址的案例是一个经典的OOP例子,现在考虑到Player struct和Userstruct有相同的字段,但是Player还有一个自己的GameId字段,用OOP思想则声明PlayerUserstruct,会存在重复声明相同的字段,但是在Go中可以通过组合struct来简化这样的情况,
1
2
3
4
5
6
7
8
9
type User struct {
Id int
Name, Location string
}

type Player struct {
User
GameId int
}

现在可通过两种方法来初始化一个Player结构。第一种使用.来设置struct字段,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type User struct {
Id int
Name, Location string
}

type Player struct {
User
GameId int
}

func main() {
p := Player{}
p.Id = 42
p.Name = "Matt"
p.Location = "LA"
p.GameId = 90404
fmt.Printf("%+v", p)
}

另一种初始化方法是使用struct字段初始化语法Name:来初始化,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type User struct {
Id int
Name, Location string
}

type Player struct {
User
GameId int
}

func main() {
p := Player{
User{Id: 42, Name: "Matt", Location: "LA"},90404,}
fmt.Printf("Id: %d, Name: %s, Location: %s, Game id: %d\n", p.Id, p.Name, p.Location, p.GameId)
// Directly set a field define on the Player struct
p.Id = 11
fmt.Printf("%+v", p)
}

当需要调用匿名结构中的字段时,不能直接引用相应的字段,而是需要通过当前结构对象来调用,如User作为匿名结构嵌入在Player中,所以通过Player对象能调用到User中到字段,但是不能直接通过User中到字段名去调用,示例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type User struct {
Id int
Name, Location string
}

func (u *User) Greetings() string {
return fmt.Sprintf("Hi %s from %s",u.Name, u.Location)
}

type Player struct {
User
GameId int
}

func main() {
p := Player{}
p.Id = 42
p.Name = "Matt"
p.Location = "LA"
fmt.Println(p.Greetings())
}

通过当前结构体对象引用嵌入到匿名结构体中的字段对构建数据结构非常有效,当嵌入对匿名结构实现了某个接口,那当前结构也就自动实现了这个接口了。下面来看另一个示例,Job结构中嵌入了Logger结构,那相当于Job也实现了Logger实现的了日志接口,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"log"
"os"
)

type Job struct {
Command string
Logger *log.Logger
}

func main() {
job := &Job{"demo", log.New(os.Stderr, "Job: ", log.Ldate)}
// 也可这样初始化job
// job := &Job{Command: "demo",
// Logger: log.New(os.Stderr, "Job: ", log.Ldate)}
job.Logger.Print("test")
}

Job结构中有个字段Logger是一个指向log.Logger类型的指针,在初始化值之后,则Job对象就可以通过这样来调用log.Logger实现的Print函数了,job.Logger.Print()。既然Logger本身是一个指向log.Logger类型的指针,那我们直接在Job结构中嵌入一个log.Logger指针对象,那Job结构对象不就可以直接调用job.Logger实现的Print方法? 答案是可以的, 如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"log"
"os"
)

type Job struct {
Command string
*log.Logger //嵌入匿名指针类型
}

func main() {
job := &Job{"demo", log.New(os.Stderr, "Job: ", log.Ldate)}
job.Print("starting now...") //直接调用log.Logger实现的Print()方法
}

注意在使用log.Logger之前需要初始化,如果匿名结构实现了某个接口,也相当于使嵌入这个匿名结构的当前结构也实现了这个接口,非常方便高效。

案例分析

Go中参数是默认是值传递,所以从上述User/Player示例中,你可能注意到在Player结构中嵌入User结构指针比直接嵌入User结构对象更好。确实如果嵌入到结构体比较简单时,使用哪种方法都差不多,如果像现实中User结构其实非常复杂,那选择传入引用则是更好到选择。修改上述代码如:

1
2
3
4
5
6
7
8
9
10
11
12
13
type User struct {
Id int
Name, Location string
}

func (u *User) Greetings() string {
return fmt.Sprintf("Hi %s from %s",u.Name, u.Location)
}

type Player struct {
*User
GameId int
}

上述将代码修改为嵌入User指针,那在调用User中字段之后,需要对User先初始化,详情看示例代码

  • Question: 为User结构指针类型定义一个Greetings方法,如何直接去调用它呢?
  • Solution:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package main

    import "fmt"

    type User struct {
    Id int
    Name, Location string
    }

    func (u *User) Greetings() string {
    return fmt.Sprintf("Hi %s from %s",u.Name, u.Location)
    }

    type Player struct {
    *User
    GameId int
    }

    func NewPlayer(id int, name, location string, gameId int) *Player {
    return &Player{
    User: &User{id, name, location},
    GameId: gameId,
    }
    }

    func main() {
    p := NewPlayer(42, "Matt", "LA", 90404)
    fmt.Println(p.Greetings())
    }
    上述案例通过NewPlayer方法在使用User中的Greetings前,先初始化了User结构指针,如果在使用前不初始化,则调用Greetings时,是用一个nil ptr去调用Greetings,显然这样调用不成功,所以使用前需要先初始化,得到返回指向这个匿名对象的地址指针对象,才能进一步通过这个指针对象去调用匿名结构中的字段属性。

《GO BOOTCAMP》第三章翻译完成,原著第三章出处:Chapter 3 Types

作者署名:朴实的一线攻城狮
本文标题:[译]GO BOOTCAMP 第三章:内置类型实战
本文出处:http://researchlab.github.io/2016/01/17/go-types/
版权声明:本文由Lee Hong创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处, 否则保留追究法律责任的权利。

@一线攻城狮

关注微信公众号 @一线攻城狮

总访问:
总访客: