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不是相同的类型,即使它们可能在特定架构上具有相同的大小。
整型的使用
- golang整数类型分为有符号和无符号
- golang默认整型是int型
package main
import "fmt"
func main() {
var n1 = 100
fmt.Printf("n1的类型:%T \n", n1)
}
输出结果
n1的类型:int
- 查看变量的字节大小,和数据类型
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]
}
数组使用步骤:
- 声明数组
- 给数组元素赋值
- 使用数组
- 数组索引从0开始,且不得越界否则panic
- Go数组是值类型,变量传递默认是值传递,因此会进行值拷贝
- 修改原本的数组,可以使用引用传递(指针),避免数据复制
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切片
- Go语言切片(Slice)
- 切片是
可动态变化
的序列,是对数组的引用
,引用类型
,遵循引用传递的机制 - 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. 创建切片的方式
- 定义切片,然后引用已经创建好的数组,数组可见
- 内置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)
}
- 定义切片直接对应数组,如同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. 结构体细节
- 结构体字段在内存中是连续的
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)
}
- 结构体由用户自定义,可以类型转换,但必须完全相同字段、个数、类型
- 对结构体进行重新定义(重新type),效果同于结构体
别名
- 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. 结构体内存分布原理图