Go数据类型

1. 第三章 Go变量

变量是对内存中数据存储空间的表示,如同门牌号对应着房间,同样的,变量名字对应变量的值。

变量:本质就是一块内存空间。用于存储某个数值。该数值在运行时可以改变。

变量使用步骤

1.声明变量,定义变量
2.给与变量赋值
3.使用变量

实际案例

package main

import "fmt"

func main() {
    var name string               //声明变量名 name
    name = "超哥"                   //给与变量赋值
    fmt.Println("name的值是:", name) //使用变量
}

结果

name的值是: 超哥

变量使用过程

代码读取
变量加载到内存  name 指向 内存地址的值
1)变量含有名字,变量类型

1.1.1. 变量定义方式

变量作用域

如同天气预报,局部地区有雨,全国地区艳阳高照

局部变量:函数内部定义的变量,指的是该变量的作用范围
全局变量:函数外部,整体作用域

1)定义变量与类型,不赋值,含有默认值

语法:var 语句定义变量的列表,类型在后面,可以定义局部变量也可以,也可全局变量

var name string

var age int

声明多个变量

var num,num2 int

package main

import "fmt"

func main() {
    var age int
    fmt.Println("age的值是:", age)

    var name string
    fmt.Println("name的值是:", name)

    var salary float64
    fmt.Println("salary的值是:", salary)

}

结果

age的值是: 0
name的值是: 
salary的值是: 0

2)编译器类型推导,自行判断变量类型

var num = 10.1

一次性定义多个变量

var age,age2 = 10,11

package main

import "fmt"

func main() {
    var num, num1 = 10, 11
    fmt.Println(num, num1)
}

3)短声明,省略var,只能用在函数内部

package main

import "fmt"

func main() {
    name := "超哥"
    fmt.Println("name的值是:", name)

    //上述段声明等于如下方式
    var name2 string
    name2 = "超哥"
    fmt.Println(name2)
}

4)多变量声明

golang支持一次性声明多个变量

支持平行赋值

多个局部变量

作用域只在函数体内,参数和返回值也是局部变量

package main

import "fmt"

func main() {
    //一次性声明多个变量,int默认值
    var n1, n2, n3 int
    fmt.Println(n1, n2, n3)

    //声明多个变量,且赋值
    //平行赋值
    var c1, c2, c3 = "chaoge", 18, 99.99
    fmt.Println(c1, c2, c3)

    //短声明多个变量
    a1, a2, a3 := "yu", 17, 100.0
    fmt.Println(a1, a2, a3)
}

5)一次性声明多个全局变量

package main

import "fmt"
//声明全局变量方式1
var n1 = 100
var n2 = 200
var n3 = 300
//声明全局变量方式2
var (
    d1, d2, d3 = 1, 2, 3
)
//声明全局变量方式3
var (
    c1 = 100
    c2 = 200
    c3 = 300
)

func main() {
    fmt.Println("这里是函数体内")
}

6)特殊变量,占位符 "_"

Go编译器要求变量必须被使用,"_"是一个只写的变量,不能读,等于舍弃,常用于接收函数的返回值

package main

import "fmt"

func Person(a1 int, n1 string) (int, string) {
    return a1, n1
}

func main() {
    //丢弃变量
    n1,n2,_:=1,2,3

    _, name := Person(18, "好嗨哦")
    fmt.Println(name)
}

7)常见数据类型变量默认值

默认值,某个变量没有给定具体的数值,称为默认值

package main

import "fmt"

func main() {
    // 只声明变量,不赋值,只有默认值
    var age int
    var name string
    var gender bool
    var salary float64
    fmt.Println("age默认值 :", age)
    fmt.Println("name默认值 :", name)
    fmt.Println("gender默认值 :", gender)
    fmt.Println("salary默认值 :", salary)
}

输出结果

age默认值 : 0
name默认值 : 
gender默认值 : false
salary默认值 : 0

2. Go常量

常量使用关键字 const 定义,用于存储不会改变的数据。常量不能被重新赋予任何值。 存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 常量的定义格式:const identifier [type] = value

常量代表只读的,不可修改的值,用const关键字定义。

如同用常量定义 "π"之类的常数。

常量如同变量一样,可以批量声明,或者一组相关的常量。

常量的计算都在编译期间完成,并非运行期间!减少运行时的工作。

未使用的常量不会引发编译错误。(这点和变量不一样哦~)

常量习惯用法,所有的字母全大写,代表变量可导出,私有则常量以小写开头即可

package main

import (
    "fmt"
    "unsafe"
)

//常量定义且赋值
const World string = "世界"

//多常量初始化
const x, y int = 1, 2

//常量类型推断,字符串类型
const s1 = "Hello golang"

//常量组
const (
    e       = 2.71828182845904523536028747135266249775724709369995957496696763
    pi      = 3.14159265358979323846264338327950288419716939937510582097494459
    b  bool = true
)

//常量组,可以除了第一个外其他的常量右边的初始化表达式可以省略
//如果省略初始化表达式,默认使用前面常量的表达式
//与上一个常量相同
const (
    c1=1
    c2
    c3
    c4="c44444"
    c5
)
/*
输出结果
1
1
1
c44444
c44444
 */

 //常量也可以定义函数的返回值
 const (
     f1="abc"  //长度为3的字符串类型
     f2=len(f1)//返回长度的函数结果
     f3=unsafe.Sizeof(f2)//返回f2所占用的内存大小
 /*
 输出结果
 abc
 3
 8
  */

func main() {
    fmt.Println(f1)
    fmt.Println(f2)
    fmt.Println(f3)
}

3. Go常量之iota常量生成器

iota用于生成一组相似规则初始化的常量,在const常量声明的语句中,第一个常量所在行,iota为0,之后每一个常量声明加一。

例如time包的例子,一周7天,每天可以定义为常量,1~6,周日为0,这种类型也称为枚举

package main

import (
    "fmt"
)

const (
    Sunday = iota
    Monday //通常省略后续行表达式
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

func main() {
    fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}

如果iota表达式被打断,需要显示恢复

package main

import (
    "fmt"
)

const (
    A = iota //初始0
    B        // +1
    C = "c"  //iota枚举被打断 ,为  c
    D        // c,与上  相同。
    E = iota // 4,显式恢复。注意计数包含了 C、D 两个,此时为4 。
    F        // 恢复iota 加一,此时为5
)

func main() {
    fmt.Println(A, B, C, D, E, F)
}

输出结果

0 1 c c 4 5




1. 3.2 Go整数类型

Go语言的数值类型包含不同大小的整数型、浮点数和负数,每种数值类型都有大小范围以及正负符号。

官方文档解释数据类型

int类型中哪些支持负数

有符号(负号):int8 int16 int32 int64
无符号(负号):uint8 uint16 uint32 uint64
浮点类型的值有float32和float64(没有 float 类型)

byte和rune特殊类型是别名

byte就是unit8的别名,代表了ASCII码的一个字符。
rune就是int32的别名,代表了UTF-8字符。
int和uint取决于操作系统(32位机器上就是32字节,64位机器上就是64字节)

uint是32字节或者64字节
int和uint是一样的大小
为了避免可移植性问题,除了byte(它是uint8的别名)和rune(它是int32的别名)之外,所有数字类型都是不同的。 在表达式或赋值中混合使用不同的数字类型时,需要转换。例如,int32和int不是相同的类型,即使它们可能在特定架构上具有相同的大小。

整型的使用

  1. golang整数类型分为有符号和无符号
  2. golang默认整型是int型
package main

import "fmt"

func main() {
    var n1 = 100
    fmt.Printf("n1的类型:%T \n", n1)
}

输出结果

n1的类型:int
  1. 查看变量的字节大小,和数据类型
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var n2 int64 = 100
    //unsafe包底下的Sizeof函数,返回变量占用字节数
    fmt.Printf("n2的类型是:%T,占用的字节数是%d", n2, unsafe.Sizeof(n2))
}

输出结果

n2的类型是:int64,占用的字节数是8

1.1. 数字类型

序号类型和描述
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

 

 

1. 3.3 Go浮点型

Go 语言提供了两种精度的浮点数,float32 和 float64,编译器默认声明为float64

小数类型就是存放小数的,如1.2 0.005 -2.32

package main

import "fmt"

func main() {
   var price float32 = 100.02
   fmt.Printf("price类型是:%T,值是%v", price, price)//%T 类型  %v 默认值
}

1.1.1. 浮点数形式

浮点数=符号位+指数位+位数位
package main

import "fmt"

func main() {
    var price float32 = 11.22 //正数符号
    fmt.Println("price=", price)
    var num1 float32 = -3.4 //负数符号
    var num2 float64 = -8.23
    fmt.Println("num1=", num1, "num2=", num2)

    //尾数可能丢失,精度缺损
    var num3 float32 = -123.11111111105//精度丢失了    
    var num4 float64 = -123.11111111105//float64的精度高于float32
    fmt.Println("num3=", num3, "num4=", num4)
    //输出结果
    //num3= -123.111115 num4= -123.11111111105
}


1. 3.4 Go字符型

Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 来保存。

普通字符串就是一串固定长度的字符连接起来的字符序列。

Go 的字符串是由单个字节连接起来的。

也 就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的。

Go的字符用单引号表示

Go的字符串用双引号表示

package main

import "fmt"

func main() {
    var c1 byte = 'a'
    var c2 byte = '2' //字符的2

    //直接输出byte的值,也就是输出对应的字符的码值
    fmt.Println("c1=", c1)
    fmt.Println("c2=", c2)

    //输出字符的值,需要格式化输出
    fmt.Printf("c1值=%c  c2值=%c\n", c1, c2)
}

Go变量保存的byte 对应码值ASCII表,范围在[0-1,a-z,A-Z...]

如果保存的字符对应码大于255,应该使用int而不是byte,否则overflows byte异常

var c3 int = '皮' //正确
var c4 byte = '皮' //overflows byte 报错

Go语言默认字符编码UTF-8,统一规定

Go字符的本质是一个整数,直接打印是UTF-8编码的码值

给与变量赋值整数,按%c格式化输出,得到的是unicode字符

var c4 int = 22269
fmt.Printf("c4=%c\n", c4)
//输出结果c4=国

Go语言允许使用转义符号"\"

Go语言字符类型允许计算,相当于整数运算,因为字符拥有对应的Unicode码

 

 

1. 3.5 Go布尔型

一个布尔类型的值只有两种:true 和 false。

if 和 for 语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var b = true
    fmt.Println("b=", b)
    fmt.Println("b字节数=", unsafe.Sizeof(b))
}

输出结果

b= true
b字节数= 1






1. 3.6 Go String型

Unicode是一种字符集,code point
UTF8是unicode的存储实现,转换为字节序列的规则
go的rune类型 可以取出字符串里的unicode

字符串是一个不可改变的字节序列。

Go string通常是用来包含人类可读的文本。

文本字符串通常被解释为采用 UTF8 编码的 Unicode 码点。

Go的字符串由单个字节连接起来。

package main

func main() {
    var city string = "我爱北京天安门"
    println(city)
    // city[0]='1' 错误,go的字符串不可变
}

Go的字符串用双引号识别,识别转义字符"\n \t"

package main

import "fmt"

func main() {
    //识别转义符
    var story string = "妖怪\n放了我师父"
    fmt.Println(story)

    //反引号,以原生形式输出,包括特殊字符,防止注入攻击
    story2 := `
你看这个灯,它又大又亮
你好
我是银角大王吧,你吃了\n吗?
`
    fmt.Println(story2)
}

Go字符串拼接

package main

import "fmt"

func main() {
    //字符串拼接,识别空格
    str3 := "你好" + "  我是孙悟空"
    fmt.Println(str3)
}

Go可以用索引取出某字节

package main

import "fmt"

func main() {
    str3 := "hello world"
    //索引取出值的码值,格式化输出
    fmt.Printf("str3=%c\n",str3[1])
    //输出str3的长度
    fmt.Println(len(str3))
}

Go多行字符串拼接

package main

import "fmt"

func main() {
    //注意 +   加号 写在上一行
    myname := "wo" + "shi" + "bei" + "jing" +
        "sha" + "he" + "yu"
    fmt.Println(myname)
}

Go遍历字符串

package main

import "fmt"

func main() {
    myname := "hello world"
    for _, ret := range myname {
        fmt.Printf("ret=%c\n", ret)
    }
}

Go修改字符串的方法

package main

import "fmt"

func main() {
    myname := "hello world"
    m1 := []rune(myname) //转化为[]int32的切片,rune是int32的别名
    m1[4] = '皮'//修改索引对应的值
    myname = string(m1)//类型强转,rune转为string
    fmt.Println(myname)
}

1.1. 字符串处理strings包

官网模块

https://golang.org/pkg/strings/

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "hello world"
    //判断是不是以某个字符串开头,返回布尔值
    res0 := strings.HasPrefix(str, "http://")
    res1 := strings.HasPrefix(str, "hello")
    fmt.Printf("res0 is %v\n", res0)
    fmt.Printf("res1 is %v\n", res1)

    //判断是不是以某个字符串结尾
    res3 := strings.HasSuffix(str, "http://")
    res4 := strings.HasSuffix(str, "world")
    fmt.Printf("res3 is %v\n", res3)
    fmt.Printf("res4 is %v\n", res4)

    //判断字符在字符串中首次出现的索引位置,没有返回-1
    res5 := strings.Index(str, "o")
    res6 := strings.Index(str, "x")
    fmt.Printf("res5 is %v\n", res5)
    fmt.Printf("res6 is %v\n", res6)

    //返回字符最后一次出现的索引位置,没有返回-1
    res7 := strings.LastIndex(str, "o")
    res8 := strings.LastIndex(str, "x")
    fmt.Printf("res7 is %v\n", res7)
    fmt.Printf("res8 is %v\n", res8)

    //字符串替换
    res9 := strings.Replace(str, "world", "golang", 2)
    res10 := strings.Replace(str, "world", "golang", 1)
    //trings.Replace("原字符串", "被替换的内容", "替换的内容", 替换次数)
    //原字符串中有2个world,才能替换2次
    fmt.Printf("res9 is %v\n", res9)
    fmt.Printf("res10 is %v\n", res10)

    //求字符在字符串中出现的次数,不存在返回0次
    countTime0 := strings.Count(str, "h")
    countTime1 := strings.Count(str, "x")
    fmt.Printf("countTime0 is %v\n", countTime0)
    fmt.Printf("countTime1 is %v\n", countTime1)

    //重复几次字符串
    res11 := strings.Repeat(str, 0)
    res12 := strings.Repeat(str, 1)
    res13 := strings.Repeat(str, 2)
    // strings.Repeat("原字符串", 重复次数)
    fmt.Printf("res11 is %v\n", res11)
    fmt.Printf("res12 is %v\n", res12)
    fmt.Printf("res13 is %v\n", res13)

    //字符串改大写
    res14 := strings.ToUpper(str)
    fmt.Printf("res14 is %v\n", res14)

    //字符串改小写
    res15 := strings.ToLower(str)
    fmt.Printf("res15 is %v\n", res15)

    //去除首尾的空格
    res16 := strings.TrimSpace(str)
    fmt.Printf("res16 is %v\n", res16)

    //去除首尾指定的字符,遍历l、d、e然后去除
    res17 := strings.Trim(str, "ld")
    fmt.Printf("res17 is %v\n", res17)

    //去除开头指定的字符
    res18 := strings.TrimLeft(str, "he")
    fmt.Printf("res18 is %v\n", res18)

    //去除结尾指定的字符,遍历d、l、r
    res19 := strings.TrimRight(str, "dlr")
    fmt.Printf("res19 is %v\n", res19)

    //用指定的字符串将string类型的切片元素结合
    str1 := []string{"hello", "world", "hello", "golang"}
    res20 := strings.Join(str1, "+")
    fmt.Printf("res20 is %v\n", res20)
}

2. Go基本数据类型转化

Golang在不同的数据类型之间赋值时需要显示转化,无法自动转换。

兼容类型之间可以转换,语法Type(value)

语法:

T(v)  将v转化为T类型
T  数据类型 如 int32 int64 float32
V  需转化的变量

类型转化注意点:

1)Go中数据类型的转换可以从 数值范围大 > 数值范围小,反之也可,注意别溢出

2)被转化的变量存储的数值,变量本身的数据类型没变化

package main

import "fmt"

func main() {
    var num1 int32 = 100
    var num2 float32 = float32(num1)    //num2强转为浮点型
    fmt.Printf("num1=%v  num2=%v\n", num1, num2)     //%v 值的默认格式
    fmt.Printf("num1类型是:%T", num1)//本身的类型 没有变化
}

3)不同类型变量之间的计算

package main

import "fmt"

func main() {
    var n1 int32 = 12
    var n2 int64 = 10

    //n3:=n1+n2 //不同类型之间无法计算,需要强转
    n3 := int64(n1) + n2
    fmt.Println(n3)
}

3. Go基本数据类型与string转化

开发中经常将数据类型转成string

方法1:

fmt.Sprintf("%参数",表达式)

package main

import "fmt"

func main() {
    var num1 int = 66
    var num2 float64 = 25.25
    var b bool = true
    var myChar byte = 'c'

    //%q 单引号
    //%d 十进制表示
    str1 := fmt.Sprintf("%d", num1)
    fmt.Printf("str1 type %T str=%q\n", str1, str1)

    //%f 有小数点
    str2 := fmt.Sprintf("%f", num2)
    fmt.Printf("str2 type %T str2=%q\n", str2, str2)

    //%t 布尔值
    str3 := fmt.Sprintf("%t", b)
    fmt.Printf("str3 type %T str3=%q\n", str3, str3)

    //%c Unicode码对应的字符
    str4 := fmt.Sprintf("%c", myChar)
    fmt.Printf("str4 type %T str4=%q\n", str4, str4)
}

方法2:

Strconv包

包strconv实现基本数据类型转化字符串类型

func Itoa(i int) string            //int转化string
func FormatBool(b bool) string        //bool转string
func FormatFloat(f float64, fmt byte, prec, bitSize int) string    //float转string
func FormatInt(i int64, base int) string    //int64转化string
package main

import (
    "fmt"
    "strconv"
)

func main() {
    var num1 int = 99
    var num2 float64 = 66.66
    var b1 bool = true

    str1 := strconv.FormatInt(int64(num1), 10)
    fmt.Printf("str1类型是%T str1=%q\n", str1, str1)

    //参数解释
    // f 格式
    // 10 小数位保留10位
    // 64  表示float64
    //浮点数转stirng
    str2 := strconv.FormatFloat(num2, 'f', 10, 64)
    fmt.Printf("str2类型是%T str2=%q\n", str2, str2)
    //bool转string
    str3 := strconv.FormatBool(b1)
    fmt.Printf("str3类型是%T str3=%q\n", str3, str3)

    //Itoa,将int转为string
    var num3 int64 = 1123
    str4 := strconv.Itoa(int(num3))//必须强转int()
    fmt.Printf("str4类型是%T str4=%q\n", str4, str4)

    //将int64转为stirng
    var n1 int64 = 99
    s1 := strconv.FormatInt(n1, 10)
    fmt.Printf("%T %#v\n", s1, s1)
}

3.1.1. string类型转基本数据类型

官方函数介绍

https://golang.org/pkg/strconv/

package main

import (
    "fmt"
    "strconv"
)

func main() {
    /*
        ParseBool,ParseFloat,ParseInt和ParseUint将字符串转换为值
        如转换失败,返回新类型默认值
    */
    var str1 string = "true"
    var b1 bool
    /*
        strconv.ParseBool(str1)函数返回2个值(value bool,err error)
    */
    b1, _ = strconv.ParseBool(str1)
    fmt.Printf("b 值类型= %T  b值=%v\n", b1, b1)

    var str2 string = "1234"
    var num1 int64
    //func ParseInt(s string,base int,bitSize int)(i int64,err error)
    num1, _ = strconv.ParseInt(str2, 10, 64)
    fmt.Printf("num1类型:%T num2值:%v\n", num1, num1)

    var str3 string = "123.456"
    var float1 float64
    //func ParseFloat(s string,bitSize int)(float64,error)
    float1, _ = strconv.ParseFloat(str3, 64)
    fmt.Printf("float1类型:%T  float1值:%v", float1, float1)
}

3.2. 非安全转换字符串

要修改字符串,都要将其转化为可变类型[]rune或[]byte,待完成后再转换回来。

不管如何转换,都需重新分配内存,并且复制数据。

非安全方式转换

func toString(bs []byte)string{
  return *(*string)(unsafe.Pointer(&bs))
}
func main(){
  bs:=[]byte("hello,world!")
  s:=toString(bs)
    printDataPointer("bs: %x\n",&bs)
    printDataPointer("s: %x\n",&s)
}

#结果
bs:c82003fec8
s:c82003fec8

解释:

[]byte和string头部结构,部分相同,以非安全的指针类型转换来实现`变更`,从而避免了底层数组复制。
在很多web Framework中都有此类做法,在高并发下,此法能有效改善性能。
unsafe需谨慎,有一定风险。

3.3. 动态构建字符串

用加法操作符拼接字符串时,每次都需重新分配内存,构建超大字符串时,性能极差。

改进思路预分配足够内存空间。

使用strings.Join函数,它会统计所有参数长度,一次性完成内存分配。

bytes.Buffer也是类似操作。

少量字符串格式化拼接,使用fmt.Sprintf方法

 

1. 3.7 Go指针

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。

Go 语言中使用&作符放在变量前面对变量进行“取地址”操作。

1.指针默认值nil
2.通过&(取地值符)取变量地址
3.通过*(取值符)透过指针访问目标值

格式:

age := 18
ptr:=&age  //age是变量名,&age是取age的内存地址,地址交给ptr变量接收,ptr类型是*int

首先基本数据类型中,如name="yugo" ,变量name存的值是yugo

1)基本数据类型,变量存的是,称为类型

2)通过&符号获取变量地址,例如&name

3)指针类型的变量,存的是另一个变量的内存地址,这个地址指向的空间存的是

4)获取指针类型指向的,使用*,例如*ptr,使用*ptr获取ptr指向的值

package main

import (
    "fmt"
)

func main() {
    var name string = "yugo"
    //查看name变量的内存地址,通过&name取地址
    fmt.Printf("name的内存地址:%v\n", &name)

    //指针变量,存的是内存地址
    //ptr指针变量指向变量name的内存地址
    var ptr *string = &name
    fmt.Printf("指针变量ptr的内存地址:%v\n", ptr)

    //获取指针变量的值,用*ptr
    //*ptr表示读取指针指向变量的值
    fmt.Printf("指针变量ptr指向的值是:%v\n", *ptr)
}

5)值类型(int、float、bool、string、array、struct)都有对应指针类型

比如*int*string等等

6)使用指针类型交换变量的值

//想要交换ab的值
func exchange(a, b *int) {
    *a, *b = *b, *a
}

func main() {
    a, b := 3, 4
    exchange(&a, &b)
    fmt.Println(a, b)
}

7)使用new()函数创建指针

func main() {
    s1 := new(string)                                  //new函数返回指针类型*string,用s1指针变量存储
    fmt.Printf("s1类型:%T  s1指针变量存储的内存地址是:%v\n", s1, s1) //s1类型*string,值是空字符串的内存地址
    //s1="哈哈" 错误写法,s1存储的内存地址
    *s1 = "马云说我不爱钱" //*s1取内存地址指向的值
    fmt.Println(*s1)
}
 

1. 3.8 Go Array数组

数组是固定长度的特定类型元素组成的序列

一个数组由零或多个相同类型元素组成。

数组的长度是固定,因此Go更常用Slice(切片,动态增长或收缩序列)。

数组是值类型,用索引下标访问每个元素,范围是0~len(数组)-1,访问越界会panic异常

注意:赋值传参复制整个数组而不是指针

//接收数组参数
func test(x [2]int){
  fmt.Printf("x: %p,%v\n",&x,x)
}

//对数组赋值语句
func main(){
  a:=[2]int{10,20}
  var b [2]int//初始化数组b
  b=a//a的值复制拷贝给b
  fmt.Printf("a: %p,%v\n",&a,a)
  fmt.Printf("b: %p,%v\n",&b,b)

  test(a)
}

实例

package main

import (
    "fmt"
)

func main() {
    /*
        定义数组
        var 数组名 [数组大小]数据类型
        var a1 [5]int

        定义数组后,5个元素都有默认值 0

        数组赋值方式
        a[0]=1
        a[1]=2

        数组的第一个元素的地址,就是数组的首地址
        数组各个元素地址间隔根据数组的数据类型决定,int64 8字节  int32 4字节
    */
    var intArr [5]int
    fmt.Println("intArr默认值是:", intArr)
    intArr[0] = 1
    intArr[1] = 2
    intArr[2] = 3
    fmt.Println("intArr赋值后的值是:", intArr)
    fmt.Printf("intArr数组地址是=%p\n", &intArr)
    fmt.Printf("intArr数组第一个元素地址是=%p\n", &intArr[0])
    fmt.Printf("intArr数组第二个元素地址是=%p\n", &intArr[1])
    fmt.Printf("intArr数组第三个元素地址是=%p\n", &intArr[2])

    //(全局声明)
    //声明赋值方式    一
    var a1 [5]string = [5]string{"大猫", "二狗"}
    //自动类型推导,未赋值的有默认值
    var a2 = [5]int{1, 2, 3}
    //自动判断数组长度    二
    var a3 = [...]int{1, 2, 3, 4, 5}
    //指定索引赋值元素    三
    var a4 = [...]string{3: "狗蛋", 6: "猫屎"}
    //结构体类型数组    四
    var a5 = [...]struct {
        name string
        age  int
    }{
        {"王麻子", 10},
        {"吕秀才", 29},
    }
    fmt.Println(a1)
    fmt.Println(a2)
    fmt.Println(a3)
    fmt.Println(a4)
    fmt.Println(a5)
}

运行结果

GOROOT=/usr/local/go #gosetup
GOPATH=/Users/yuchao/go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/dd/1j1pbw895772hqp2d2gfg00c0000gn/T/___go_build_main_go /Users/yuchao/go/src/gostudy/gobook/main.go #gosetup
/private/var/folders/dd/1j1pbw895772hqp2d2gfg00c0000gn/T/___go_build_main_go #gosetup
intArr默认值是: [0 0 0 0 0]
intArr赋值后的值是: [1 2 3 0 0]
intArr数组地址是=0xc42001c090
intArr数组第一个元素地址是=0xc42001c090
intArr数组第二个元素地址是=0xc42001c098
intArr数组第三个元素地址是=0xc42001c0a0
[大猫 二狗   ]
[1 2 3 0 0]
[1 2 3 4 5]
[   狗蛋   猫屎]
[{王麻子 10} {吕秀才 29}]

Process finished with exit code 0

1.1. 遍历数组

package main

import "fmt"

func main() {
    var a1 = [...]int{1, 2, 3, 4, 5, 6}
    //通过索引取值
    for i := 0; i < len(a1); i++ {
        fmt.Println(a1[i])
    }

    //for循环遍历数组,索引和值,index可以省略用占位符_
    for index, value := range a1 {
        fmt.Println(index, value)
    }

}

1.2. 数组使用细节

package main

import "fmt"

func main() {
    //数组是多个相同类型数据的组合,且长度固定,无法扩容
    var a1 [3]int
    a1[0] = 1
    a1[1] = 11
    //必须赋值int类型数据,否则报错
    //a1[2] = 111.1

    //不得超出索引
    //a1[3]=111
    fmt.Println(a1)//有默认值[1 11 0]
}

数组使用步骤:

  1. 声明数组
  2. 给数组元素赋值
  3. 使用数组
  4. 数组索引从0开始,且不得越界否则panic
  5. Go数组是值类型,变量传递默认是值传递,因此会进行值拷贝
  6. 修改原本的数组,可以使用引用传递(指针),避免数据复制
package main

import (
    "fmt"
)

//函数接收值类型,默认有值拷贝
func test(arr [3]int) {
    arr[0] = 66
}

//函数修改原本数组,需要使用引用传递
func test2(arr *[3]int) {
    (*arr)[0] = 66 //可以缩写arr[0]=66 编译器自动识别,arr是指针类型
}

func main() {
    //声明arr数组,需要考虑传递函数参数时,数组的长度一致性
    arr := [3]int{11, 22, 33}
    //test函数不会修改数组
    test(arr)
    fmt.Println(arr)
    //test2修改了数组
    test2(&arr)
    fmt.Println(arr)
}

1.3. 指针数组,数组指针

指针数组: 元素为指针类型的数组

func ptrArray() {
    //定义数组,元素类型是*int,
    var ptrArray [5]*int
    fmt.Println(ptrArray) //[<nil> <nil> <nil> <nil> <nil>]
    //需要传入地址
    a1 := [5]int{1, 2, 3, 4, 5}
    for i := 0; i < len(a1); i++ {
        ptrArray[i] = &a1[i] //循环写入内存地址
    }
    fmt.Println(ptrArray)
    //输出指针数组的值
    for i := 0; i < len(ptrArray); i++ {
        fmt.Println(*ptrArray[i])
    }
}

func main() {
    ptrArray()
}

数组指针: 获取变量数组变量的地址

func main() {
    //数组是值类型,可以用new函数创建    数组指针,类型是*[5]int
    //数组指针,指向数组的地址
    //数组中存的是值类型
    var array1 = new([5]int)
    array1[0] = 66
    array1[1] = 77
    fmt.Println(reflect.TypeOf(array1))
    fmt.Println(array1[0])
}
 

1. 3.9 Go Slice切片

  1. Go语言切片(Slice)
  2. 切片是可动态变化的序列,是对数组的引用引用类型,遵循引用传递的机制
  3. slice类型写作[ ]T,T是slice元素类型,var s1 []int,s1就是切片变量
package main

import "fmt"

func main() {
   //创建一个数组
   var array1 [5]int = [...]int{11, 22, 33, 44, 55}
   /*
      创建切片,通过对数组的索引切片
      s1 是切片名
      array1[1:3]代表slice引用数组区间,索引1到索引3的值,注意取头不取尾,
   */
   s1 := array1[1:4]
   fmt.Println(array1)
   fmt.Println(s1)
   fmt.Println(len(s1))
   fmt.Println(cap(s1))
}

运行结果

[11 22 33 44 55]     //原本数组
[22 33 44]            //切片的值
3                    //切片元素长度
4                    //切片容量

2. 切片原理

slice是一个轻量级数据结构,提供访问数组子序列元素的功能。

slice由三个部分构成,指针、长度、容量

指针:指针指向slice第一个元素对应的数组元素的地址。

长度:slice元素的数量,不得超过容量。

容量:slice开始的位置到底层数据的结尾

package main

import "fmt"

func main() {
//创建数组,Months月份,1月份到12月份
months:=[...]string{"","January","February","March","April","May","June","July","August","September","October","November","December"}
//创建切片,对数组的引用
s1:=months[4:7]//[April May June]
s2:=months[6:9]//[June July August]
fmt.Println(s1)
fmt.Println(s2)

//指针:指针指向slice`第一个元素`对应的`数组元素`的地址。
fmt.Printf("slice第一个元素地址%p\n",&s1[0])
fmt.Printf("对应数组元素的地址%p\n",&months[4])
}

对切片读写

package main

import (
    "fmt"
)

func main() {
    //创建数组data
    data := [...]int{0, 1, 2, 3, 4, 5}
    //切片s [2,3]
    s := data[2:4]
    //切片读写操作目标是底层数组data
    s[0] += 100
    s[1] += 200
    fmt.Println(s)
    fmt.Println(data)
}

运行结果

[102 203]
[0 1 102 203 4 5]

2.1. 创建切片的方式

  1. 定义切片,然后引用已经创建好的数组,数组可见
  2. 内置make函数创建切片,底层数组看不见,只能通过slice访问元素

make创建切片内存分配图

package main

import (
"fmt"
)
/*
内置make函数,参数(类型,len,cap),注意cap大于len,容量可以省略,默认等于长度
切片有默认值
 */
var slice0 []int = make([]int, 10)
var slice1 = make([]int, 10)
var slice2 = make([]int, 10, 10)

func main() {
    fmt.Printf("make全局slice0 :%v\n", slice0)
    fmt.Printf("make全局slice1 :%v\n", slice1)
    fmt.Printf("make全局slice2 :%v\n", slice2)
    fmt.Println("--------------------------------------")
    slice3 := make([]int, 10)
    slice4 := make([]int, 10)
    slice5 := make([]int, 10, 10)
    slice5[0] = 11
    slice5[1] = 22
    fmt.Printf("make局部slice3 :%v\n", slice3)
    fmt.Printf("make局部slice4 :%v\n", slice4)
    fmt.Printf("make局部slice5 :%v\n", slice5)
}
  1. 定义切片直接对应数组,如同make方式
package main

import "fmt"

func main() {
    //第三种方式,原理类似make,数组看不见,由make维护
    var s1 []int = []int{1, 2, 3, 4, 5}
    fmt.Println(s1)
    fmt.Println(len(s1))
    fmt.Println(cap(s1))
}

4.遍历切片

package main

import "fmt"

func main() {
    var arr [5]int = [...]int{11, 22, 33, 44, 55}
    s1 := arr[1:4]
    //for循环遍历
    for i := 0; i < len(s1); i++ {
        fmt.Printf("s1[%v]=%v\n", i, s1[i])
    }
    fmt.Println()

    //for range方式遍历切片
    for i, v := range s1 {
        fmt.Printf("索引i=%v 值v=%v\n", i, v)
    }
}

5.切片案例

package main

import "fmt"

func main() {
    var array1 = [...]int{11, 22, 33, 44}
    slice1 := array1[1:4] //11,22,33
    slice2 := array1[1:]  //22,33,44
    slice3 := array1[:]   //11,22,33,44
    slice4 := slice3[:2]   //slice4=[11,22] 切片再切片
    fmt.Println(slice1)
    fmt.Println(slice2)
    fmt.Println(slice3)
    fmt.Println(slice4)
}

6.cap是内置函数,统计切片容量,最大存放多少元素

7.切片扩容,append内置函数,向尾部添加数据,返回新的slice对象

package main

import "fmt"

func main() {
    //创建切片
    var slice1 []int = []int{100, 200, 300}
    fmt.Printf("slice1容量=%v 长度=%v\n", cap(slice1), len(slice1))
    //给切片追加新元素
    //容量扩容机制是2倍扩容
    slice1 = append(slice1, 400)
    fmt.Printf("slice1扩容后容量=%v 长度=%v\n", cap(slice1), len(slice1))
    fmt.Println(slice1)

    //切片扩容切片,slice1... 语法糖代表展开切片元素
    slice1=append(slice1,slice1...)
    fmt.Println(slice1)
}
/*
append原理就是对底层数组扩容,go会创建新的数组,将原本元素拷贝到新的数组中
slice重新引用新的数组
这个数组不可见
 */

8.切片拷贝

package main

import "fmt"

func main() {
    //创建切片
    var slice1 []int = []int{11, 22, 33, 44}
    //make创建切片,长度是10
    var slice2 = make([]int, 10)
    copy(slice2, slice1) //把slice1的值拷贝给slice2
    fmt.Println(slice1)  //[11 22 33 44]
    fmt.Println(slice2)  //[11 22 33 44 0 0 0 0 0 0]
    //slice1和slice2数据独立,互不影响
    slice1[0] = 123 
    fmt.Println(slice1)
    fmt.Println(slice2)
}

9.全切片表达式

array[x:y:z]

x切片内容 [x:y]

Y切片长度: y-x

Z切片容量:z-x

package main

import (
    "fmt"
)

//官网资料
// https://golang.google.cn/ref/spec#Slice_expressions
func main() {
    //10:2代表索引10的元素是2
    data := [...]int{0, 1, 2, 3, 4, 10: 2}
    fmt.Println(data)
    s := data[1:2:3] //data[start:end:数字-start]  这个s容量是3-1=2
    fmt.Printf("扩容前s的容量是:%v\n", cap(s))
    s = append(s, 100, 200)            // 一次 append 两个值,超出 s.cap 限制。
    fmt.Println(s, data)               // 重新分配底层数组,与原数组无关。
    fmt.Printf("扩容后s的容量=%v\n", cap(s)) //二倍扩容
    fmt.Println(&s[0], &data[0])       // 比对底层数组起始指针。
}

3. string和slice的联系

1)string底层就是byte数组,因此string同样可以进行切片处理

package main

import "fmt"

func main() {
    str1 := "yugo niubi"
    //对str1进行切片
    s1 := str1[:4]
    fmt.Println(s1)//yugo
}

2)string修改的两种方式

package main

import (
    "fmt"
)

func main() {
    str1 := "yugo niubi"
    //string是不可变的,也无法通过切片修改值
    //str1[0] = 's'  编译器失败

    //修改string的方法,需要string转化为[]byte,修改后转为string
    arr1 := []byte(str1) //类型强转
    arr1[0] = 'g'
    str1 = string(arr1)
    fmt.Printf("str1=%v\n", str1)

    //[]byte只能处理英文和数字,不能处理汉字,汉字3个字节,会出现乱码
    //将string转为[]rune,按字符处理,兼容汉字
    arr2 := []rune(str1)
    arr2[0] = '于'
    str1 = string(arr2)
    fmt.Printf("str1=%v\n", str1)
}
 

1. 3.10 Go Map哈希表

map是key-value类型数据结构,读作(哈希表、字典),是一堆未排序的键值对集合。

map是引用类型,使用make函数或者初始化表达式创建。

map的key必须是支持相等运算符==!=的类型,如int、bool、channel、string、pointer、array、sruct、interface。

通常map的key是int、string

map的value可以是任意类型,没有限制,通常是int、float、string、struct

2. map声明

package main

import "fmt"

func main() {
    /*
        map声明语法
        var 变量名  map[keytype]valuetype

        var m1 map[string]string
        var m2 map[int]string
        var m3 map[int]map[string]string//map的value又是map

        注意map声明不会分配内存,必须make初始化才可以使用
    */

    //声明map方式一
    //make函数可以合理申请一大块内存,避免后续扩张时性能问题
    var m1 map[string]string
    //标注map的初始容量为10
    m1 = make(map[string]string, 10)
    m1["一号"] = "大狗子"
    m1["二号"] = "二狗子"
    fmt.Println(m1)

    //声明方式二
    m2 := make(map[string]string)
    m2["男"] = "小黑"
    m2["女"] = "小女"
    fmt.Println(m2)

    //声明方式三
    m3 := map[string]string{
        "坦克": "德玛西亚",
        "射手": "伊泽瑞尔",
        "辅助": "星女",
    }
    m3["打野"] = "赵信"
    fmt.Println(m3)
}

3. map增删改查

访问不存在的键值,默认返回零值,不会引发错误。

推荐使用ok-idiom模式,如: val, ok := m1["k4"]

package main

import "fmt"

func main() {
    m1 := map[string]string{"k1": "v1", "k2": "v2"}
    fmt.Printf("m1值:%v\n", m1)

    //map插入值
    m1["k3"] = "v3"
    fmt.Printf("插入后m1值:%v\n", m1)

    //map修改值
    m1["k1"] = "v11111"
    fmt.Printf("修改k1值后:%v\n", m1)

    //map查找值
    val, ok := m1["k4"]
    if ok {
        fmt.Printf("k4的值是:%v\n", val)
    }

    //长度: 获取键值对数量
    m1Len := len(m1)
    fmt.Printf("m1长度:%v\n", m1Len)

    //判断key是否存在
    if val, ok := m1["k4"]; !ok {
        fmt.Printf("此key不存在\n")
    } else {
        fmt.Printf("此key的值:%v\n", val)
    }

    //删除map的key,如key不存在,delete不进行操作
    //delete函数按指定的键,将元素从映射中删除
    //一次性删除所有key可以遍历下,逐个删除
    //也可以重新make新map,让原本map被gc回收
    if _, ok := m1["k3"]; ok {
        delete(m1, "k3")
        fmt.Printf("已删除m1中的k3\n")
    } else {
        fmt.Printf("无法删除,此key不存在")
    }
    fmt.Printf("此时m1的值:%v\n", m1)
}

4. map遍历

使用for-range结构遍历

package main

import "fmt"

func main() {
    //循环性生成10个key的map
    m1 := make(map[int]int)
    for i := 0; i < 10; i++ {
        m1[i] = i + 1
        fmt.Printf("m1的key:%v value:%v\n", i, m1[i])
    }
    fmt.Println(m1)
fmt.Println("--分割线--")
    //循环遍历map的值
    for k, v := range m1 {
        fmt.Printf("m1的key:%v m1的值%v\n", k, v)
    }
}

遍历复杂map

map的value又是map

package main

import "fmt"

func main() {
    //make初始化第一层map,分配内存
    stuMap := make(map[string]map[string]string)
    //第二层map初始化
    stuMap["stu01"] = make(map[string]string)
    stuMap["stu01"]["名字"] = "大狗子"
    stuMap["stu01"]["年纪"] = "18"

    //切记,map必须make后方可使用
    stuMap["stu02"] = make(map[string]string)
    stuMap["stu02"]["名字"] = "二麻子"
    stuMap["stu02"]["年纪"] = "17"

    fmt.Println(stuMap)

    //取出所有学生的信息
    for k, v := range stuMap {
        fmt.Printf("k值是学生:%v  v值是学生信息:%v\n", k, v)
        //k1是键,v1是值
        for k1, v1 := range v {
            fmt.Printf("\tk1:%v v1:%v\n", k1, v1)
        }
        fmt.Println()
    }
}

5. map切片

声明一个切片(slice),并且这个切片的类型是map,这就被称作slice of map,map切片,这样map的个数就可以动态变化了。

package main

import "fmt"

func main() {
    //声明map切片,
    // 默认值        [map[] map[] map[] map[] map[]]
    sliceMap := make([]map[string]string, 5)
    for i := 0; i < 5; i++ {
        //map必须初始化再用,遍历初始化
        sliceMap[i] = make(map[string]string)
    }
    sliceMap[0]["名字"] = "张飞"
    sliceMap[1]["性别"] = "不男不女"
    sliceMap[2]["体重"] = "三百斤"
    fmt.Println(sliceMap)
    fmt.Printf("容量:%v,长度:%v\n", cap(sliceMap), len(sliceMap))

    //动态扩容map切片,用append函数
    newSliceMap := map[string]string{
        "姓名": "狗子",
        "爱好": "吃包子",
    }
    //append函数进行切片扩容,动态增加
    sliceMap = append(sliceMap, newSliceMap)
    fmt.Println(sliceMap)
    fmt.Printf("容量:%v,长度:%v\n", cap(sliceMap), len(sliceMap))
}

6. map排序

map默认是无序的,每次遍历都是不同的结果

package main

import "fmt"

func main() {
    //无须的map
    m1 := make(map[int]int)
    for i := 0; i < 10; i++ {
        m1[i] = i + 1
    }
    //遍历结果无序
    for k, v := range m1 {
        fmt.Println(k, v)
    }
}

通过索引排序取值

package main

import "fmt"

func main() {
    m1 := map[int]string{
        1: "红孩儿",
        2: "孙悟空",
        3: "银角大王",
        4: "金角大王",
    }
    //无序
    // for k, v := range m1 {
    //     fmt.Println(k, v)
    // }
    //通过索引,有序取值
    for i := 1; i <= len(m1); i++ {
        fmt.Println(i, m1[i])
    }
}

sort包排序

golang没有针对map的key排序的方法。

必须先对key排序,然后根据key值就可以输出排序后的结果

调用sort包函数进行排序

sort.Strings

sort.Ints

package main

import (
    "fmt"
    "sort"
)

func main() {
    //定义一个m map变量
    m := map[string]string{"q": "q", "w": "w", "e": "e", "r": "r", "t": "t", "y": "y"}
    fmt.Println(m)
    //定义一个 string类型切片
    var slice []string
    //循环遍历map,取出所有的key和value
    for k, _ := range m {
        //循环将key添加到切片中
        slice = append(slice, k)
    }
    fmt.Printf("切片slice值 : %v\n", slice)
    //调用排序包,对切片进行排序,按照字母顺序排序
    sort.Strings(slice[:])
    fmt.Printf("排序后 切片slice值 : %v\n", slice)
    for _, v := range slice {
        fmt.Printf("排序后 m[%v]=%v\n", v, m[v])
    }
}

7. map使用细节

1)map是引用类型,遵循引用类型传递的机制,在函数接收map参数,对map修改是直接操作原本的map。

package main

import "fmt"

func modify(m map[string]string) {
    m["名字"] = "狗子"

}
func main() {
    //map是引用类型,遵循引用引用类型传递
    m1 := make(map[string]string)
    m1["名字"] = "傻子"
    m1["年纪"] = "十八"
    fmt.Println(m1)
    modify(m1) //直接对m1进行修改,说明是引用类型
    fmt.Println(m1)
}

2)map可以自动扩容,动态增长。

package main

import "fmt"

func main() {
    //初始化m1,限制容量3
    m1 := make(map[int]int, 3)
    for i := 0; i < 10; i++ {
        m1[i] = i + i
    }
    fmt.Println(m1)
    fmt.Printf("m1元素个数:%v", len(m1))
}

3)map的value也可以是struct类型,适合更复杂的数据

package main

import "fmt"

type Stu struct {
    Name    string
    Age     int
    Address string
}

func main() {
    //map的value可以为更复杂的struct结构体类型
    //map的key是学号
    //map的value是结构体{姓名、年纪、住址}
    students := make(map[int]Stu, 10)
    //初始化结构体,不需要填写key,顺序value即可
    stu1 := Stu{"alex", 1000, "沙河"}
    stu2 := Stu{"武沛奇", 999, "于辛庄"}
    students[1] = stu1
    students[2] = stu2
    fmt.Println(students)
    //遍历map,取出学生信息
    for k, v := range students {
        fmt.Printf("学生编号%v\n", k)
        fmt.Printf("学生姓名%v\n", v.Name)
        fmt.Printf("学生年纪%v\n", v.Age)
        fmt.Printf("学生住址%v\n", v.Address)
        fmt.Println("--------")
    }
}

4)函数len返回键值对数量,cap不适用map类型。

5)因内存访问安全和哈希算法等因素,字典设计是not addressable,不得直接修改value成员(struct或array)

6)mymap["age"]+=1此代码报错

7)mymap["age"]++ 相当于mymap["age"]=mymap["age"]+1 ,此代码正确,mymap["age"]返回的是指针



1. 3.11 Go Struct结构体

Golang支持OOP面向对象编程。

Go的结构体struct如同python的class

Go基于struct实现OOP特性,只有组合composition这个特性。

2. 结构体概念

1)将一类事务特性提取出一个新的数据类型,就是结构体。

2)通过结构体可以创建多个实例。

3)可以是Student结构体、可以是Animal、Person结构体。

3. 结构体特点

1)struct用于定义复杂数据结构

2)struct可以包含多个字段

3)struct可以定义方法(注意不是函数,是golang的method)

4)struct可以是值类型

5)struct类型可以嵌套

6)Go没有class,只有struct类型

7)结构体是自定义类型,不得与其他类型强转

8)可以为struct每一个字段添加tag,这个tag可以反射机制获取,场景如json序列化和反序列化。

4. 结构体定义

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}
func main() {
    //声明方式
    p1 := Person{"小黑", 18} //有序赋值,并且必须包含所有字段,否则报错
    p2 := Person{Age: 18}  //关键词赋值,未赋值字段有空值
    fmt.Println(p1)
    fmt.Println(p2)
}

练习struct

package main

import "fmt"

//声明结构体名称Stu
type Stu struct {
    Name    string //结构体字段
    Age     int    //如未赋值有默认空值
    Address string
    Score   int
}

//结构体可以定义复杂的类型
type Person struct {
    Name  string
    Age   int
    Score [5]float64        //容量为5的数组
    prt   *int              //指针类型
    slice []int             //int类型切片
    map1  map[string]string //map类型字段
    //slice和map默认值是nil,必须make初始化才可使用
}

//结构体是值类型,不同结构体实例之间互不影响
type Monster struct {
    Name string
    Age  int
}

func main() {
    //声明结构体类型变量
    var stu1 Stu
    //结构体可以通过 . 的方式赋值,声明赋值方式一
    stu1.Name = "小黑"
    stu1.Age = 18
    stu1.Address = "沙河"
    stu1.Score = 100
    fmt.Printf("stu1的名字=%v 年纪=%v 住址=%v 分数=%v\n", stu1.Name, stu1.Age, stu1.Address, stu1.Score)

    //声明赋值方式二
    monster1 := Monster{"红孩儿", 18}
    monster2 := Monster{"女妖怪", 999}
    //两个结构体实例,内存地址不一样,确保独立
    fmt.Printf("monster1地址:%p\n", &monster1)
    fmt.Printf("monster2地址:%p\n", &monster2)

    //声明方式三
    //用来分配内存,主要用来分配值类型,比如int、struct。返回指向类型的 指针
    //此时m1是一个指针
    var m1 *Monster = new(Monster)
    //给m1赋值
    (*m1).Name = "孙悟空" //编译器自动识别 同于 m1.Name="孙悟空"
    (*m1).Age = 9999   //同上
    fmt.Println(*m1)   //此时m1是指针变量,加上*取值

    //声明方式四
    m2 := &Monster{
        "猪八戒",
        888,
    }
    fmt.Println(*m2)
    //第三、第四种返回结构体指针,go编译器自动识别,简化程序员心智负担,建议用1、2方法
}

4.1.1. 匿名结构体

没有名字的结构体

package main

import "fmt"

func main() {
    //匿名函数
    func() {
        fmt.Println("我是匿名函数")
    }()
    //匿名结构体
    p1 := struct {
        name string
        age  int
    }{
        name: "张三",
        age:  18,
    }
    fmt.Println(p1)
}

4.1.2. 匿名字段

package main

import "fmt"

func main() {
    type student struct {
        string //匿名字段,类型当做字段名
        int
    }
    s1 := student{
        "吴亦凡",
        18,
    }
    fmt.Println(s1.string, s1.int)
}

4.1.3. 结构体嵌套

面向对象:聚合关系

​ 一个类作为另一个类的属性

package main

import "fmt"

type Book struct {
    bookName string
    price    float64
    author   string
}

type Person struct {
    name string
    age  int
    book Book //继承Book结构体的字段 ,模拟聚合关系
}

func main() {
    //先定义好的book对象
    b1 := Book{"如何找到女朋友", 999.999, "alex金角大王"}
    p1 := Person{"武沛奇", 26, b1} //b1就是Book结构体类型,武沛奇买了一本书
    fmt.Printf("姓名:%s,年纪:%d,书名:%s,价格:%.2f,书的作者:%s\n", p1.name, p1.age, p1.book.bookName, p1.book.price, p1.book.author)

    //声明初始化book对象,一行搞定
    p2 := Person{"萧峰", 25, Book{"如何找到男朋友", 3.58, "超哥著作"}}
    fmt.Printf("姓名:%s,年纪:%d,书名:%s,价格:%.2f,书的作者:%s\n", p2.name, p2.age, p2.book.bookName, p2.book.price, p2.book.author)
}

结构体嵌套练习

学生与书架

package main

import "fmt"

type Book struct {
    bookName string
    price    float64
}
type Student struct {
    name  string
    age   int
    books []*Book
}

func main() {
    b1 := Book{"霸道总裁爱上我", 120.22}
    b2 := Book{"斗破苍穹", 12.5}
    b3 := Book{"我和师姐的故事", 15.5}
    //定义书架,默认没有书,可以容纳10本书
    //用Book就是值拷贝,*Book就是放入书的内存地址
    bookSlice := make([]*Book, 0, 10)
    //注意需要传入地址
    bookSlice = append(bookSlice, &b1, &b2, &b3)
    //创建一个学生
    s1 := Student{"小猪沛奇", 3, bookSlice}
    fmt.Printf("姓名:%s,年纪:%d\n", s1.name, s1.age)
    //查看书架上书的信息
    for i := 0; i < len(s1.books); i++ {
        book := s1.books[i]
        fmt.Printf("\t第%d本书,书名:%s,书价格:%.2f\n", i+1, (*book).bookName, book.price)
    }

    //创建图书方式二
    //初始化创建时候,必须对切片分配内存
    s2 := Student{"特斯拉车主alex", 46, make([]*Book, 0, 10)}
    //放入书架的书,放入书的内存地址
    s2.books = append(s2.books, &Book{"斗罗大陆", 55.3}, &Book{"python入门到放弃", 1.28}, &Book{"王思聪与三个网红的一天", 999999.99})
    fmt.Printf("学生名:%s,学习年龄:%d\n", s2.name, s2.age)
    //输入所有s2学生看的书
    for k, v := range s2.books {
        fmt.Printf("\t第%d本书,书名:%s,价格:%.2f\n", k+1, v.bookName, v.price)
    }
}

4.1.4. 面向对象:继承关系

​ 一个类作为另一个类的子类:子类,父类

继承:面向对象的第二个特征,用于描述两个类的关系

​ 子类,派生类,subClass继承父类(超类,基类,superClass)

​ 子类可以直接访问父类已有的属性和方法

​ 子类可以新增自己的属性和方法

​ 子类也可以重写父类已有的方法

通过匿名字段的方式,进行嵌套,实现继承关系

package main

import "fmt"

//1.定义父类
type Person struct {
    name string
    age  int
}

//2定义子类,匿名字段,Person即是
type Son struct {
    Person        //模拟继承结构,继承父类的name,age属性
    school string //子类的新增属性
}

func main() {
    //父类
    p1 := Person{"李靖", 999}
    fmt.Println(p1.name, p1.age)
    //子类赋值方式一,子类直接访问父类属性
    var s2 Son
    s2.name = "娜扎"
    s2.age = 666
    s2.school = "神仙学校"
    fmt.Println(s2, s2.name, s2.age, s2.school)
    //创建方式二,简写方式
    s3 := Son{Person{"木吒", 667}, "神仙学校"}
    fmt.Println(s3, s3.name, s3.age, s3.school)

    //创建方式三,基于key-value写
    s4 := Son{Person: Person{name: "金吒", age: 668}, school: "神仙学校"}
    fmt.Println(s4, s4.name, s4.age, s4.school)
}

4.1.5. 结构体细节

  1. 结构体字段在内存中是连续的
package main

import "fmt"

type Test struct {
    A int32
    B int32
    C int32
    D int32
}

func main() {
    var t Test
    fmt.Printf("a addr:%p\n", &t.A)
    fmt.Printf("b addr:%p\n", &t.B)
    fmt.Printf("c addr:%p\n", &t.C)
    fmt.Printf("d addr:%p\n", &t.D)
}
  1. 结构体由用户自定义,可以类型转换,但必须完全相同字段、个数、类型
  2. 对结构体进行重新定义(重新type),效果同于结构体别名
  3. struct每个字段,可以写一个tag,这个tab可以通过反射获取,用在序列化,反序列化
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    UserName string  `json:"姓名"` //反引号括起来的就是struct tag
    Sex      string  `json:"性别"`
    Score    float32 `json:"成绩"`
    Age      int32   `json:"年纪"`
}

func main() {
    user := &User{
        UserName: "user01",
        Sex:      "男",
        Score:    99.2,
        Age:      18,
    }
    //将user变量序列化为json格式字符串
    data, _ := json.Marshal(user)
    fmt.Printf("json str:%s\n", string(data))
}

4.2. 结构体内存分配

先看代码

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    //p1有自己的结构体内存地址,
    var p1 Person
    p1.Age = 10
    p1.Name = "王大锤"

    //定义P2 指针类型,指向p1的内存地址
    var p2 *Person = &p1
    //两种形式一样,go编译器自动识别
    fmt.Println((*p2).Age)
    fmt.Println(p2.Age)
    //修改p2的结构体值,也就是修改了p1的结构体值
    p2.Name = "葫芦娃"
    fmt.Printf("输出结果  p2.Name=%v p1.Name=%v\n", p2.Name, p1.Name)
    fmt.Printf("输出结果(*p2).Name=%v p1.Name=%v\n", (*p2).Name, p1.Name)

    //查看p1和p2的内存地址
    fmt.Printf("p1内存地址%p\n", &p1)
    //p2是指针变量,自己也有一块内存地址,p2的值指向
    fmt.Printf("p2内存地址%p p2的值是%v\n", &p2, p2)
}

4.3. 结构体内存分布原理图

 

 

 

 

 

 

 

 

 

 

 

 

 







posted @ 2019-06-26 14:53  AnthonyWang  阅读(127)  评论(0)    收藏  举报