3. Go 数据类型及数据结构

3. Go 数据类型及数据结构

前面的几节中有意无意地创建了很多变量,在变量声明过程中,除非声明过程就初始化,否则通常需要指明数据类型。不同的数据类型代表着不同的数据处理和表达方法,为了满足对现实世界的刻画要求,通常需要创建复杂的数据类型或结构,因为一个好的数据类型或结构可以恰到好处的解决所面临的问题,所以需要考虑数据结构的问题,但无论多么复杂的数据类型或结构,都是由简单的数据类型构建而来的,下面逐一进行熟悉。

Go 简单数据类型

Go 中简单数据类型大概有这么几类:

  • 布尔型:bool,表示 truefalse
  • 数值型: complex64complex128float32float64intint8int16int32int64int128unitunit8uint16uint32uint128,表示各种大小长度的数字
  • 字符串类型:string ,表示字符串
  • 指针:Pointer
  • 杂项:byte, rune

布尔型

布尔型,只有两种值,truefalse,但确实一种重要的表示状态的存在,布尔型变量主要进行逻辑运算,生成布尔型值的方式除了直接声明外,多数都是通过关系运算符获取的。

关系运算符,关系运算符用于可比较的两种类型的值进行比较,包括, ==, !=, > , >=, <, <=

  • == 检测两个值是否相等,相等返回 true,否则返回 false
  • != 检测两个值是否不相等,相等返回 false,否则返回 true
  • >, >= 比较左端和右端两个值的大小,左端大于/大于等于右端的值,返回 true,否则返回 false
  • <, <= 比较左端和右端两个值的大小,左端小于/小于等于右端的值,返回 true,否则返回 false

逻辑运行,逻辑运算包括三种,与或非,对应符号为 &&, ||, !
两个/一个布尔型变量做上述运行时,与数学上的真值表是一致的。

  • x && y 结果要为true,要求 x 为 true 并且 y 为 true,其他均为false
  • x || y 结果要为true,要求 x 为 true 或者 y 为 true,其他均为false
  • !x x为 true!x 就是 false; 反之,x为false,!x 就是 true
    逻辑运算的优先顺序是 非,与,或,但是为了自己逻辑清楚,可以加小括号表示哪些关系密不可分。

注意:Go 中,true,false 并不能和 1, 0进行默认转换,必须显式的进行转换。

func i2b(i int) bool {
   if i > 0 {
       return 1
   }
   return 0
}// int to bool
func b2i(b bool) int {
 if b {
       return 1
 }
return 0
}// bool to int

数值型

Go 中数值型有两种,一种是没有任何长度标记的 int, uint 这两种,另一种就是有长度标记的数值,例如 int8, uint16。前者的数据长度与平台相关,后者与平台无关,个人感觉是,如果你不排斥使用后者,那么你最好使用后者。

对于整型数据来说, uintx 的为无符号整型, intx 的为有符号整型,两者可以表达的数据范围是不一致的。

  • uint8: 无符号 8 位整型 (0 到 255)
  • uint16: 无符号 16 位整型 (0 到 65535)
  • uint32: 无符号 32 位整型 (0 到 4294967295)
  • uint64: 无符号 64 位整型 (0 到 18446744073709551615)
  • int8: 有符号 8 位整型 (-128 到 127)
  • int16 有符号 16 位整型 (-32768 到 32767)
  • int32: 有符号 32 位整型 (-2147483648 到 2147483647)
  • int64: 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
  • float32:32 位浮点数 (很大)
  • float64:64 位浮点数 (非常大)
  • complex64:实部和虚部都是 float32 类型的的复数
  • complex128:实部和虚部都是 float64 类型的的复数

对于数值型变量,最为常见的就是进行算术运算符,包括 +, -, *, /, %, ++, --, +=, *=, /=

  • % 整数取余,与被取模数的符号是一致的;
  • x++, x-- 自增和自减(没有++x,--x这样的东东);
  • x+=y 就是x = x+y 的简写运算,其他几个类似;

除此之外, Go 还支持位运算

& 位运算 AND
| 位运算 OR
^ 位运算 XOR
&^ 位清空 (AND NOT)
<< 左移
>> 右移

字符串

字符串就是字符的集合,更准确一点,是一个一旦创建完就不可改变的字节序列。之前提到,Go 语言字符串使用 UTF-8 编码实现,此处展开 UTF-8 不太合适,但需要知道 UTF-8 是一个变长编码,可以容纳世界上绝大多数语言及其字符。

字符串长度可以使用 len 来测量,字符串支持切片访问 string[index],字符串支持通过 + 对两个或多个字符串进行拼接。可以通过循环对一个字符串进行遍历。

package main
import "fmt"
func main() {
    b := "Hello 中国"
    fmt.Printf(b+", And have %d length.\n", len(b))
    fmt.Println(b[0:10])
    printStringHex(b)
    printStringOnebyOne(b)

}
func printStringOnebyOne(s string) {
    for index, rune := range s {
        fmt.Printf("%c\t%x\t%d\n", rune, rune, index)
    }
}
func printStringHex(s string) {
    for i := 0; i < len(s); i++ {
        fmt.Printf("%d\t%x\t%c\n", i, s[i], s[i])
    }
}
/*
Hello 中国, And have 12 length.
Hello 中�
0       48      H
1       65      e
2       6c      l
3       6c      l
4       6f      o
5       20
6       e4      ä
7       b8      ¸
8       ad      ­
9       e5      å
10      9b      �
11      bd      ½
H       48      0
e       65      1
l       6c      2
l       6c      3
o       6f      4
        20      5
中      4e2d    6
国      56fd    9
*/

注意对字符串遍历的方法,因为 UTF-8 变长编码的缘故,当处理英文字符,编码位置只有一个byte时,可以正常显示,但是对于中文,无法通过位置索引得到完整的一个字符,较好的稳妥的办法使用 range 来遍历,获取 rune。你也可能发现了,rune 是一个数据类型,它其实是 type rune int32, 是一个足够容纳编码位置的表达类型,按照如下方式访问字符串也是完全可以的。

func printChars(s string) {
    runes := []rune(s)
    for i:= 0; i < len(runes); i++ {
        fmt.Printf("%c ",runes[i])
    }
}

有一些回车符,Tab符号等不可见字符的表达需要使用转义字符,这一点同 C 语言一致。

\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\' 单引号 (只用在 '\'' 形式的rune符号面值中)
\" 双引号 (只用在 "..." 形式的字符串面值中)
\\ 反斜杠

为了处理大段文本(里面有很多换行,缩进之类的东西), Go 还支持一种字面字符串,它也是字符串,不同的是,它不用 "" 双引号表达,而是使用使用反引号代替双引号。

const GoUsage = `Go is a tool for managing Go source code.
Usage:
go command [arguments]
...`

字符串是一种最为常见的数据类型,后面还会有专门的地方涉及到它。

指针

指针是一种直接存储变量的内存地址的数据类型。一个指针对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字(如果变量有名字的话)。

Go 的指针运算

& 返回变量存储地址, 
// &a; 将给出变量的实际地址。
* 取出指针变量值,
// *a; 是一个取出该地址的值

注意 Go 的指针不允许指针的 ++,--运算。 为什么要有指针呢,因为指针方便,同时也是为了降低开销。Go 在处理简单的数据类型时,数据之间的转移和传递是通过复制做到的,就是你把一个数据传递给另一个函数,那么 Go 会帮你复制一份数据过去,当遇到该数据特别大的时候,又要频繁传递的时候,就要进行大量的复制,影响性能。但用指针就不存在该问题,因为指针只保存指向某个数据的地址,如果需要,找那个地址的数据直接去改,不要反复复制了。

func main() {
    var oneint int
    var oneptr = &oneint
    var anoptr *int = new(int) //*
    
    fmt.Println(oneint)
    oneint++
    fmt.Println(*oneptr)
    *oneptr += 2
    fmt.Println(oneint)
    fmt.Println(anoptr)
    fmt.Println(*anoptr)
    *anoptr = oneint
    fmt.Println(*anoptr)
}
/* Result:
0 oneint 的默认值
1 oneint++
3 *oneptr += 2
0xc00000a090 //anoptr 地址
0 //*anoptr 的默认值
3 // *anoptr
*/

上面*标处,可利用自动推断为 var anoptr = new(int),此处利用new分配内存空间,否则第四行输出为nil,因为所有指针的默认值为nil,就是空,没有指向任何地址。对 nil 地址操作 *anoptr = oneint 将引发panic,这是指针使用的大忌。

Go 类型之间转换

Go 有着非常严格的强类型特征。Go 没有自动类型提升或类型转换。

package main
import (  
    "fmt"
)
func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + j //不允许 int + float64
    fmt.Println(sum)
}

那该怎么办……显式转换,全部需要显式转换

package main

import (  
    "fmt"
)

func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + int(j) //转换j为整数型,当然精度就没有了
    fmt.Println(sum)
}

// 或者
func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum :=j +float64(i) // 转换I到浮点数
    fmt.Println(sum)
}

把一种类型转换为另一种类型的方法就是 T(v), T为新的类型,例如float64(i)

Go 符合数据类型

数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。

数组的声明:var arrayname [n]type // n为确定的数字,type为简单的数据类型

var stringarray [3]string = [3]string{"Are", "Good", "Yor"} 
// 最为繁重的声明方式,可以简化为 var stringarray = [3]string{"Are", "Good", "Yor"} 
for index, value := range stringarray {
    fmt.Printf("%d\t%s\n", index, value)
}
// range函数会遍历stringarray,返回两个值,第一个是索引,第二个是数值
/* Result
0       Are
1       Good
2       Yor
*/

var uintarray [5]uint
// 只声明,不初始化
uintarray[0] = 10
uintarray[4] = 100
for i, v := range uintarray {
    fmt.Printf("%d\t%d\n", i, v)
}
/*Result
0       10
1       0
2       0
3       0
4       100
*/  没有赋值的位置都是 0
floatarray := [...]float32{1:2.17,5: 3.14}
// 包括个数也自动推导
for _, v := range floatarray {
    fmt.Printf("%g\n", v)
}
// 如果对索引位置不感兴趣,就用下划线去代替一个有意义的名称,这样就会丢弃掉那个值
/* Result
0       0
1       2.17
2       0
3       0
4       0
5       3.14
*/

数组的长度是不变的,在声明就已经指定,而且 Go 认为 [3]int, [4]int 是两种不同的数据类型,无法比较。初始化过程中,可以只初始化想初始化的位置,用{index:value}来指定,如果{}给定的值数量小于[]指定的量,则认为初始化前面的几个数,后面为零。数组长度可以使用 len(array) 来获得,访问数据内的元素,可以通过 array[index] ,即数组名[索引位置]来访问,索引位置可以是单个数字,也可以是[start:end] 。数组是值类型,就是传递数组是通过传递数组的副本做到的。数组的遍历使用 for 下标索引或 range 索引都可以。

这里需要说一下下标的使用方法,一般来说 array[index] 是访问单元素, array[start:end] 是访问数组从 start开始到 end-1的那个元素,如果 start不写,默认从 0 开始,如果 end 不写,默认到 len()-1的位置,如果都不写,那就是 0:len()-1 也就是全部元素。

其实还可以创建多维数组,声明方法为 var mularray [row][vol]Type 来实现,遍历方式就是嵌套循环range,此处不再展开。

func print2darray(a [m][n]string) {
    for row, rv := range a {
        for vol, rvv := range rv {
            fmt.Printf("%d\t%d\t%s\n",row,vol,rvv)
        }
        fmt.Printf("\n")
    }
}

切片/Slice

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个切片类型一般写作[]T,其中T代表切片中元素的类型;切片的语法和数组很像,只是没有固定长度而已。切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用。一个切片有三个要素要关心,切片头(指针),长度(len),容量(cap)。

切片的创建有两种方式

  1. 从某个数组创建,s := array[start:end] 0<= start < end <= len(array)-1
  2. 不关心数组,s := make([]Type, len, cap) or s:=[]int{v1,v2,...,vn} ,也不是不想关心,是关心不到,反正能正常遍历各个值,为什么要关心数组呢。

先说第二种方式:

因为没有对照数组,单纯的说切片特性优良。当你创建一个切片后,你依然可以像数组一样,使用下标访问每个元素,使用 range 遍历整个切片。

既然说了可以变长,那就一定可以添加元素,使用 append 函数追加元素,格式为s = append(s, v1, v2, ...) or s = append(s, s1...) 就是说 append 函数可以在一个切片后面追加同类型的元素,或一个同类型的切片。当append元素的数量超过原有切片的容量时,Go 会自动扩大容量,扩容多少,由一套复杂的算法决定,可不用关心。

从一个切片上,还可以继续创建子切片,subs := s[start:end]0<= start < end <= len(array)-1 ,使用和切片相同。

理解第二种,就要回来说第一种了,有点复杂,看例子说明。

var uintarray [10]uint
uintarray[0] = 0
uintarray[2] = 100
uintarray[4] = 200
uintarray[6] = 300
uintarray[8] = 400
fmt.Println(uintarray, len(uintarray)) // [0 0 100 0 200 0 300 0 400 0] 10

uints := uintarray[1:3]
fmt.Println(uints, len(uints), cap(uints)) //[0 100] 2 9
fmt.Println(uints[0:3], len(uints[0:3]), cap(uints[0:3])) // [0 100 0] 3 9
fmt.Println(uints[1:4], len(uints[1:4]), cap(uints[1:4])) // [100 0 200] 3 8

uints[1] = 250
fmt.Println(uints[1:4], len(uints), cap(uints)) // [250 0 200] 3 8

uints[2] = 350 // panic,index out of range

uints = append(uints, 600, 700)
fmt.Println(uints, len(uints), cap(uints)) // [0 250 600 700] 4 9
fmt.Println(uintarray, len(uintarray))     // [0 0 250 600 700 0 300 0 400 0] 10
// 切片是对数组的引用,所以通过切片可以修改数组的值

uints = append(uints, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400)
fmt.Println(uints, len(uints), cap(uints)) 
// [0 250 600 700 600 700 800 900 1000 1100 1200 1300 1400] 13 18
fmt.Println(uintarray, len(uintarray))     
//[0 0 250 600 700 0 300 0 400 0] 10
// 你不是说,修改切片,数组就相应变了吗!怎么这次没有变? 因为…… 因为这次添加的数太多了,超过切片容量,Go 重新创建了一个底层数组,复制了原有切片位置的值,添加新的值后返回了新的底层数组。而原有的数组被抛弃了,但我们有一个可以访问原有数组的变量存在,所以发现数没有变化。

ints := make([]int, 3, 3)
fmt.Println(ints, len(ints), cap(ints)) // [0 0 0] 3 3

aints := []int{1, 2, 3}
fmt.Println(aints, len(aints), cap(aints)) // [1 2 3] 3 3

sumints := append(ints, aints...) 
fmt.Println(sumints, len(sumints), cap(sumints)) // [0 0 0 1 2 3] 6 6

subsumints := sumints[1:3]
fmt.Println(subsumints, len(subsumints), cap(subsumints)) //[0 0] 2 5

subsumints2 := sumints[:5]
fmt.Println(subsumints2, len(subsumints2), cap(subsumints2)) //[0 0 1 2 3] 5 5
// 创建的子切片长度比原有切片长度要大,但容量是一样

所以,一个切片一旦创建,无论何种方式创见的切片,其指针位置就确定了,指针前面的数据就永远不能访问了;在切片容量范围内,可以创建子切片,访问到由于容量所有数据;对于append 可以增加切片容量。切片对于底层数组是引用方式,修改切片就修改了底层数组,切片还有一种操作叫做 复制(copy),copy(dst, source) 从 source 复制数据到一个 dst 切片,如果想完整复制 source,则必须保证 dst 切片容量大于等于 source,否则只会复制dst容量的数据。

Map

Map有多种译名:字典,哈希表,集合......,其实都可以,因为 Map 是这样一种结构,非空的Map 里面是一系列键值对(键到值的映射),类似于一本字典;它的键是不允许重复的,所以它像一个集合;哈希表是 Map 的实现方式,它用哈希算法计算存储键,使得它可以对给定的key可以在常数时间复杂度内检索、更新或删除对应的value。由于不同的哈希算法生成的排序方式不同,所以Map是无序,更为特殊的,在 Go 中,它是故意被设计为乱序的,要求程序算法不能依赖于Map遍历的顺序。如果像排序,最好显式的获取Map的键,按照需要方式排序,然后再按照键去遍历。Map 是按照引用传递的,意思就是无论你在哪里修改一个Map,其都是对一个Map进行修改。Map 无法使用 == 比较,除非是 MapName == nil

Map 声明

var mapname map[Ktype]Vtype{Key:Value}

var monthTable map[int]string 创建了一个从数字到月份名字的Map

var monthTable map[string]int 创建了一个从月份名字到数字的Map

如果没有利用{Key:Value} 初始化map, 则声明的Map默认值为 nil。如果你想添加元素到 nil map 中,会触发运行时 panic。因此 map 必须使用 make 函数初始化。

初始化 monthTable = make(map[int]string),所以多数时候,声明和初始化是一起进行的,monthTable := make(map[int]string)

Map 的查询

Value,ok = MapName[key]

如果可以在给定的Map中查询到指定 key 的值,那么返回 value 和 true ,反之,返回值的默认零值和 false.

Map 键值的添加

MapName[key] = value

如果 Map 初始化过,那么添加就是即可,如果没有初始化,panic报错

Map 键值的删除

delete(key, MapName)

如果Map 中存在该key,则删除,如果不存在,上述操作也是安全操作,最终结果就是一定没有该键。

package main

import (
    "fmt"
)

func main() {
    var StudentScore map[string]int
    // var StudentScore map[int]string
    fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))

    StudentScore = make(map[string]int)
    fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))

    StudentScore["Xiaoming"] = 98
    StudentScore["Xiaohong"] = 99
    StudentScore["Xiaogang"] = 95
    StudentScore["Xiaohei"] = 0

    fmt.Println("Xiaoming Score:", StudentScore["Xiaoming"])
    fmt.Println(StudentScore, StudentScore == nil, len(StudentScore))

    for k, v := range StudentScore {
        fmt.Println(k, ":", v)
    }

    fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])
    fmt.Println("JackFly Score:", StudentScore["JackFly"])
    lookupScore("Xiaohei", StudentScore)
    lookupScore("JackFly", StudentScore)

    setScore("JackFly", 100, StudentScore)
    fmt.Println("JackFly Score:", StudentScore["JackFly"])
    setScore("Xiaohei", 100, StudentScore)
    fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])

    delScore("Xiaohei", StudentScore)
    setScore("Xiaohei", 99, StudentScore)
    fmt.Println("Xiaohei Score:", StudentScore["Xiaohei"])

}

func lookupScore(s string, table map[string]int) {
    Score, ok := table[s]
    if ok {
        fmt.Printf("%s Score: %d, ok Status:%t\n", s, Score, ok)
    } else {
        fmt.Printf("There is no %s Score in Table. ok Status:%t\n", s, ok)
    }
}

func setScore(s string, score int, table map[string]int) {
    if table == nil {
        table = make(map[string]int)
    }
    Score, ok := table[s]
    if ok {
        fmt.Printf("%s already had a score:%d record in table.\n", s, Score)
    } else {
        table[s] = score
        fmt.Printf("%s Score:%d had set in table successfully.\n", s, score)
    }
}

func delScore(s string, table map[string]int) {
    Score, ok := table[s]
    if ok {
        delete(table, s)
        fmt.Printf("%s score:%d had been delete in table.\n", s, Score)
    } else {
        fmt.Printf("There is no %s score record in talbe.\n", s)
    }
}
/*
map[] true 0
map[] false 0 // 初始化后就不是nil
Xiaoming Score: 98
map[Xiaohong:99 Xiaogang:95 Xiaohei:0 Xiaoming:98] false 4 // len可以给出map长度
Xiaoming : 98
Xiaohong : 99
Xiaogang : 95
Xiaohei : 0
Xiaohei Score: 0
JackFly Score: 0
Xiaohei Score: 0, ok Status:true
There is no JackFly Score in Table. ok Status:false
JackFly Score:100 had set in table successfully.
JackFly Score: 100
Xiaohei already had a score:0 record in table.
Xiaohei Score: 0
Xiaohei score:0 had been delete in table.
Xiaohei Score:99 had set in table successfully.
Xiaohei Score: 99
*/

结构体

结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的新数据类型,以满足描述算法或对象的使用描述要求,其中结构体的每个值称为结构体的成员。

结构体的定义与声明 , 结构体内成员的声明顺序是有意义的,不同顺序成员的结构体是不同的结构体。成员数据类型可以是前面的任意一种数据类型,甚至可以是另一个结构体(但不可以是自己)。

// 结构体定义
type StructName struct{
    feildName1 TypeA
    feildName2, feildName3 TypeB
    feildName3, TypeC
} // 带有成员名称(字段)的定义方式
type StructName struct{
    TypeA
    TypeB
    TypeC
} // 不带字段的定义方式
// 结构体声明
StructureV := StructureName{
    feildName1: Value1,
    feildName2: Value2,
    feildName3: Value3,// 不一定要全部赋值,如果不赋值,Go 默认初始化为0
}
StructureV := StructureName{Value1, Value2, Value3}//由此注意结构体的声明成员的的顺序很重要,

创建匿名的结构体
StructureV :=struct{
    feildName1 TypeA
    feildName2, feildName3 TypeB
    feildName3, TypeC
}{
    feildName1: Value1,
    feildName2: Value2,
    feildName3: Value3,
}

结构体成员的访问

访问结构体的成员是通过. 来操作的,即 structV.fieldNameA ,用来读写都是可以的

结构体取地址及地址访问

结构体可以取地址,结构体的成员也可以单独取地址,通常通过地址访问成员,需要先对结构体使用 * 取值运算,但 Go 会自动对结构体地址进行取值 ,简化了结构体的表达,如下:structptr := &structName{}, (*structptr).fieldName 等价于 structptr.feildName

结构体内包含另一个结构体

structName{substructName, fnc typec}时,如果另一个结构体substructName{fna typea, fnb typeb}没有名称,那么Go 会提升字段,就是将substructName 中的成员,当成自己的成员,可以通过. 直接访问。但是对于有名称的结构体,则必须使用双重点号访问子结构体的成员。structName{sub substructName, fnc type}, structName.substructName.fna

结构体虽然没有办法包含自身在内,但是可以包含自身的指针在内。

package main

import (
    "fmt"
)

type address struct {
    city, road string
    number     int
    Neighbor   *address
}

type student struct {
    Name   string
    gender bool // true for boy, false for girl
    age    int
    hight  float32
}
type studentDetail struct {
    student
    homeAddr address
}

func main() {
    xiaoming := student{"xiaoming", true, 10, 1.1}
    fmt.Println(xiaoming)
    fmt.Printf("%+v\n", xiaoming)
    printfstu(xiaoming)

    xiaominginfo := &xiaoming
    fmt.Println((*xiaominginfo).gender)
    fmt.Println(xiaominginfo.age)

    fmt.Println("A Year Later... ")
    xiaominginfo.age++
    xiaominginfo.hight = 1.2
    printfstu(xiaoming)

    xiaomingDetail := studentDetail{
        student: xiaoming,
        homeAddr: address{
            city:   "Beijing",
            road:   "ChangAn",
            number: 1,
            //Neighbor 是可以被省略的
        },
    }
    fmt.Printf("%+v\n", xiaomingDetail)
    printfstuD(&xiaomingDetail)

    xiaohongDetail := &studentDetail{student{"xiaohong", false, 10, 1.1}, address{"Beijing", "Changan", 2, &xiaomingDetail.homeAddr}} // 地址不可省略

    fmt.Println("xiaohong Age:", xiaohongDetail.age)
    fmt.Println("xiaohong home address:", xiaohongDetail.homeAddr.road, xiaohongDetail.homeAddr.number)

    fmt.Println("xiaohong Neighbor:", *xiaohongDetail.homeAddr.Neighbor)

}

func printfstu(stu student) {
    gendermap := map[bool]string{
        false: "girl",
        true:  "boy",
    }
    fmt.Printf("------------\n%s\nGender:%s\nAge:%d\nHight:%g\n", stu.Name, gendermap[stu.gender], stu.age, stu.hight)
}
func printfstuD(stuD *studentDetail) {
    gendermap := map[bool]string{
        false: "girl",
        true:  "boy",
    }
    fmt.Printf("------------\n%s\n - Gender: %s\n - Age: %d\n - Hight: %g\n - Address: %s, %s Road, No. %d\n", stuD.Name, gendermap[stuD.gender], stuD.age, stuD.hight, stuD.homeAddr.city, stuD.homeAddr.road, stuD.homeAddr.number)
}

由于这一节打印了很多不同类型的值,其格式化的方式都有所区别,可以通过这个 简单介绍 简单了解一下,后文会有机会全面理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,132评论 0 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 想必大家都听说过三十六计吧。今天我看了一本书,就是关于三十六计的。下面我就跟你们讲解。三十六计的前六计。“...
    刘子城阅读 292评论 0 0
  • 闲来无事时,喜欢追两眼电视剧。最近热播的<那年花开月圆时>自然不会错过。剧中周莹有众多男性粉丝,沈星移又英俊而倜傥...
    贪睡的木棉阅读 380评论 2 4