go 学习笔记(五)
文章目录
前言
接触了新的语言go,记录一下学习的笔记方便日后温故知新。
一、数组
1.用来存放多个同一数据的数据类型(在Go中,数组是值类型)
2.定义和内存布局
定义
var 数组名 [数组大小]数据类型
例如:var a[2]int
初赋值:a[0]=1
内存布局
func main() {
var y1 int32
fmt.Println("int32所占字节数", unsafe.Sizeof(y1))
var y2 int
fmt.Println("int所占字节数", unsafe.Sizeof(y2))
//前置知识
var intArr [3]int
//当我们定义完数组之后,数组就已经被赋默认值了,默认零值就是0(指针:%p 表示为十六进制,并加上前导的0x)
fmt.Printf("intArr的地址=%p\n", &intArr)
//结果:intArr的地址=0xc0000ae078 (数组是基本数据类型,使用取地址符号&获取当前数组地址)
fmt.Printf("第一个元素的地址=%p\n第二个元素的地址=%p\n第三个元素的地址=%p\n",
&intArr[0], &intArr[1], &intArr[2])
//intArr的地址=0xc00000c168
//第一个元素的地址=0xc00000c168
//第二个元素的地址=0xc00000c170
//第三个元素的地址=0xc00000c178
//结论:数组在内存中是连续分布的,0xc00000c168+8=0xc00000c170(16进制,满16进一),第一个元素+int所占的字节数(int占8个字节)等于下一个元素的内存位置
//在这里int占8个字节,但如果声明为 int32则占四个字节
//数组的首个元素地址就是数组的地址
//另:当前demo中,最快的取到数组的值的方法是直接在地址上+8(int的字节数)即用地址取值是最快的
}
3.数组的地址可以通过数组名来取,使用&符
4.数组的第一个元素的地址就是数组的首地址
5.数组的各个元素之间的间隔是由数组的数据类型决定的,比如int,数组类型是值类型,默认是进行值传递,因此是值进行拷贝,数组间不会相互影响
func main() {
var intArr1 [3]string
test(intArr1)
fmt.Println("intArr1=", intArr1)
}
func test(arr [3]string) {
for i := 0; i < len(arr); i++ {
arr[i] = "测试"
}
}
//结果
intArr1= [ ]
func main() {
var intArr1 [3]string
test(&intArr1)
fmt.Println("intArr1=", intArr1)
}
//通过传递数组的地址,在具体方法中通过操作数组地址的值 & 的 * 来修改内存中的遍历
func test(arr *[3]string) {
for i := 0; i < len(arr); i++ {
*&arr[i] = fmt.Sprint("测试", i)
}
}
//结果
intArr1= [测试0 测试1 测试2]
6.遍历
for
for-range
经典遍历和增强遍历的问题
func main() {
var intArr1 [3]string
var intArr2 [3]string
var intArr3 [3]string
//当我们定义完数组之后,数组就已经被赋默认值了,默认零值就是0(指针:%p 表示为十六进制,并加上前导的0x)
//发现一个问题 如果是赋值的话经典for可以赋值但是range无法正常赋值,不知道咋回事
for i := 0; i < len(intArr1); i++ {
intArr1[i] = "测试" + string(i)
}
fmt.Println("intArr1=", intArr1)
for _, s := range intArr2 {
//使用s不行但是用角标可以,而且s取地址取数来是三个相同的地址,但是用intArr2[i]取出来的是正确的地址,就很疑惑
s = "测试"
fmt.Println(s)
}
fmt.Println("intArr2=", intArr2)
for i, s := range intArr3 {
//使用s不行但是用角标可以,而且s取地址取数来是三个相同的地址,但是用intArr2[i]取出来的是正确的地址,就很疑惑
intArr3[i] = "测试" + string(i)
fmt.Println(s)
}
fmt.Println("intArr3=", intArr3)
}
//结果
intArr1= [测试 测试 测试]
测试
测试
测试
intArr2= [ ]
intArr3= [测试 测试 测试]
func main() {
//练习题,生成五个随机数并反转打印
var intArr [5]int
//生成随机规则,为了每次生成的随机数都不一样,rand.Seed(time.Now().Unix())如果是Unix同一秒下生成的还是会生成一样的值,所以我们使用纳秒
//保证更大的随机
rand.Seed(time.Now().UnixNano())
len := len(intArr)
for i := 0; i < len; i++ {
intArr[i] = rand.Intn(100)
}
fmt.Println("intArr1=", intArr)
temp := 0
for i := 0; i < len/2; i++ {
temp = intArr[len-1-i]
intArr[len-1-i] = intArr[i]
intArr[i] = temp
}
fmt.Println("intArr1=", intArr)
}
二、slice切片(底层维护的是一个数组)
1.解决数组个数不确定的难题
2.切片是数组的一个引用,因此切片是引用类型,在传递时,遵守引用传递的机制,部分使用方式可以参考数组,切片的长度是可变的
3.定义语法
var 变量名 []类型
比如:var a []int
4.内存布局
func main() {
//声明一个数组
var intArr = [...]int{1, 2, 3, 4, 5}
//切个数组获得一个切片slice是引用到intArr数组的第2个元素到第四个元素但不包含第四个
//即从数组intArr中切取脚标1~3 包含1不包含3 ,就是说取3-1=2个
slice := intArr[1:3]
fmt.Println("slice=", slice)
//slice= [2 3]
fmt.Println("slice的cap", cap(slice))
//数组产生的切片,切片的第一个元素的地址就是数据对应的角标的地址,由此可以得出切片是数组的部分引用,切片是引用类型
//切片就是一个数据结构(struct),如果修改了切片,那么底层的数组也会发生变化,因为是数组的引用嘛
// 类似这种结构体
// type slice struct {
// ptr *[1]int
// len int
// cap int
// ...
//}
fmt.Println("intArr[1]的地址=", &intArr[1]) // intArr[1]的地址= 0xc00000c4b8
fmt.Println("slice[1]的地址=", &slice[0]) // slice[1]的地址= 0xc00000c4b8
}
5.使用方式
定义一个数组,然后通过 [:] 来引用 //slice := intArr[1:3] 或者引用整个数组 intArr[:] ,当切片之后还可以接续切片
通过make来创建一个切片 //slice := make([]int, 2, 5) // 切片类型 切片长度 切片容量 ,这样的创建切片方式不能通过操作数组的方式
来操作了,即使用make创建的切片底层维护的数组是对外不可见的
直接将数组赋给切片 //slice := []string{"marry", "tom", "jack"}
6.切片的遍历
for r
for i
三、string 和 slice 的关系
1.string底层是一个byte数组
func main() {
s := "hello,world!"
s1 := s[:]
s2 := s[:5]
fmt.Println(s1)
fmt.Println(s2)
}
//结果
hello,world!
hello
2.string是不可变的,不能通过角标修改
按照1的案例 s1[1] = “z” 是无法通过编译的 但是可以先转成切片修改后在转回去
func main() {
s := "hello,world!"
stringBytes := []byte(s)
stringBytes[2] = 'z'
s2 := string(stringBytes)
fmt.Println(s2)
}
//结果
hezlo,world!
四、map
1.k-v的数据结构
2.使用方式
func main() {
//方式一
var a map[string]string
//在使用map前需要先make给map分配数据空间
a = make(map[string]string, 10)
a["hello"] = "hi"
fmt.Println(a) // map[hello:hi]
//方式二
a1 := make(map[string]string, 10)
a1["hello a1"] = "hi a1"
fmt.Println(a1) // map[hello a1:hi a1]
//方式三
a2 := map[string]string{
"hello a2": "hi a2",
"hello a22": "hi a2",
}
fmt.Println(a2) //map[hello a2:hi a2 hello a22:hi a2]
3.crud
func main() {
a2 := map[string]string{
"hello a2": "hi a2",
"hello a22": "hi a2",
}
a2["hello a2"] = "hello a33" // update or put
delete(a2, "hello a2") //delete 如果没有删掉也不会报错
a2 = make(map[string]string) //清空集合,或者说重新分配,上一部分数据就成为垃圾等待gc
a2["hi"] = "hello"
a2["hi2"] = "hello2"
val, ok := a2["hi"] //查找
if ok {
fmt.Println(val, "***************")
}
fmt.Println(a2) //map[hello a2:hi a2 hello a22:hi a2]
}
4.遍历 map遍历不能使用fori了要用forr
func main() {
a2 := map[string]string{
"hello a2": "hi a2",
"hello a22": "hi a2",
}
for s, s2 := range a2 {
fmt.Println(s, s2)
}
}
//结果
hello a2 hi a2
hello a22 hi a2
五、map切片
1.map的个数就可以动态变化了
func main() {
//声明一个map切片
var mapDemo []map[string]string
//初始化切片 2个容量 可以放入两个map
mapDemo = make([]map[string]string, 1)
if mapDemo[0] == nil {
//如果切片第一个空间为零值,则放入一个map,这个map的大小为2
mapDemo[0] = make(map[string]string)
mapDemo[0]["hello1"] = "hi1"
mapDemo[0]["hello2"] = "hi2"
mapDemo[0]["hello3"] = "hi3"
}
newMapDemo := map[string]string{
"hello2": "hi2",
}
mapDemo = append(mapDemo, newMapDemo)
fmt.Println("mapDemo = ", mapDemo)
}
六、 map排序
1.golang中是没有针对key进行排序的,如果需要就先将key取出排序再按照取出的顺序进行遍历
func main() {
a1 := make(map[int]string, 10)
a1[1] = "a1-1"
a1[3] = "a1-3"
a1[2] = "a1-2"
var keys []int
for k, _ := range a1 {
keys = append(keys, k)
}
//排序
sort.Ints(keys) //sort.Ints 递增
//输出
for _, key := range keys {
fmt.Println(key, a1[key])
}
}
//结果
1 a1-1
2 a1-2
3 a1-3
2.使用细节
map是引用类型,遵守引用传递的机制,在一个函数接收map后修改内容,会直接修改原来的map
map在超出原始设定的容量后继续append不会panic,会继续增加k-v,是因为map是自动扩容的
map的v可以是结构体或者是map
七、面向"对象"(OOP)
1.Go没有类的概念但是有结构体
func main() {
var Garfield cat
Garfield.Name = "jack"
Garfield.Age = 8
Garfield.Color = "blue"
fmt.Println(Garfield)
}
type cat struct {
Name string
Age int
Color string
}
//结构体是自定义的数据类型,代表一类事务
//结构体变量(实例)是具体的,实际的,代表一个具体变量
2.结构是值类型
3.声明结构体
type cat struct {
Name string //字段,属性,field(可以是数组,mao,指针)
Age int
Color string
ptr *int
slice []int
fieldMap map[string]string
}
4.创建结构体变量和访问字段的四种形式
func main() {
//方式一
var Garfield cat
Garfield.Name = "jack"
Garfield.Age = 8
Garfield.Color = "blue"
fmt.Printf("Garfield的地址是:%p", &Garfield) //0xc000076480 请注意 结构是值类型
//方式二
Garfield1 := cat{
Name: "大力",
}
fmt.Println("Garfield11:", Garfield1)
//方式三
var Garfield2 *cat = new(cat)
(*Garfield2).Name = "power猫"
fmt.Println("Garfield2:", Garfield2)
//方式四
var Garfield3 *cat = &cat{}
//因为Garfield3是一个指针,因此标准的访问字段的方式是(*Garfield3).Name = "jack"
//但Go的设计者为了程序员使用方便也可以 Garfield3.Name = "jack" 底层会进行转化
(*Garfield3).Name = "jack"
fmt.Println("Garfield3:", Garfield3)
}
type cat struct {
Name string //字段,属性,field(可以是数组,mao,指针)
Age int
Color string
ptr *int
slice []int
fieldMap map[string]string
}
八、结构体类型的内存分配机制
1.结构体的所有字段在内存中是连续分布的(可以通过内存地址的加减来快速操作内存中的值)
func main() {
R1 := Rect{
LeftUp: Point{1, 2},
RightDown: Point{3, 4},
}
fmt.Printf(" R1 的LeftUp 的x的地址是 : %p\n R1 的LeftUp 的y的地址是 : %p\n R1 的RightDown 的x的地址是 : %p\n R1 的RightDown 的y的地址是 : %p\n",
&R1.LeftUp.x, &R1.LeftUp.y, &R1.RightDown.x, &R1.RightDown.y)
//R1 的LeftUp 的x的地址是 : 0xc000126060
//R1 的LeftUp 的y的地址是 : 0xc000126068
//R1 的RightDown 的x的地址是 : 0xc000126070
//R1 的RightDown 的y的地址是 : 0xc000126078
//结论,每个field的地址都是从第一个field的地址基础上加一个int(8位)的
}
type Point struct {
x int
y int
}
type Rect struct {
LeftUp, RightDown Point
}
2.两个结构体互相强转需要field完全相同(名称,类型…)
func main() {
a := Point1{}
b := Point2{}
a = Point1(b)
fmt.Println(a, b)
}
type Point1 struct {
x int
y int
}
type Point2 struct {
x int
y int
}
3.即便用type重新定义类型,两个类型也不能直接赋值,需要强转,因为golang认为用type新定义的是一个新类型
4.tag
func main() {
person := Person{
"张三", 20,
}
p, _ := json.Marshal(person) //func Marshal(v interface{}) ([]byte, error) {...} 返回的是字节切片,所以需要使用string(p)转成string
fmt.Println(string(p))
//type Person struct {
// Name string
// Age int
//}
//{"Name":"张三","Age":20}
//结构体加json可以转成首字母小写
//type Person struct {
// Name string `json:"name"`
// Age int `json:"age"`
//}
//{"name":"张三","age":20}
fmt.Println(string(p))
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
九、方法
1.定义结构体行为(方法是定义在指定的数据类型上,和指定的类型有关,因为自定义类型的都可以有方法,而不仅仅是结构体)
func main() {
person := Person{
"张三", 20,
}
fmt.Println(person.Name)
fmt.Println(person.GetName())
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
// GetName 这表示Person有一个方法GetName,和函数的区别就是(p Person),表示这个方法是结构体Person的
func (p Person) GetName() string {
p.Name = "李四" //这里修改后,调用方法可以获得 "李四",但main函数的person不会改变,除非传递地址才能够修改main函数的 "张三"
return p.Name
}
2.方法和函数的区别
//调用方式不同
// 由变量.方法名 : 方法
// 函数名(实参列表) :函数
// 对于普通函数,接收者为值类型的时候,不能将指针类型的数据直接传递,反之亦然(意思就是函数入参列表是什么类型的就要严格按照什么类型的数据传参)
// 对于方法来说,接收者为值类型的时候.可以直接使用指针类型的的变量调用方法,反过来也可以 意思是说 p.GetName() 和 (&p).GetName()的效果一样
// 但是注意,p.GetName() 和 (&p).GetName()的变量都是只拷贝,没有拷贝地址,地址还是没有改变,内存中还是两个变量,编译器会把(&p).GetName()
// 给优化成p.GetName()//反之也可,所以依然不会影响main方法的 person.name,但是如果声明方法时,不传值,传递指针,那么正常修改就可以改了,下面GetName1例子
func main() {
person := Person{
"张三", 20,
}
fmt.Println(person.Name)
fmt.Println(person.GetName1())
fmt.Println(person.Name)
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
// GetName 这表示Person有一个方法GetName,和函数的区别就是(p Person),表示这个方法是结构体Person的
func (p Person) GetName() string {
p.Name = "李四" //这里修改后,调用方法可以获得 "李四",但main函数的person不会改变,除非传递地址才能够修改main函数的 "张三"
*&p.Name = "李四"
return p.Name
}
func (p *Person) GetName1() string {
p.Name = "李四"
return p.Name
}
//对于方法来讲,入参才是是否修改内存的关键
3.仿照javaBean写一个类
//编写一个Object,给出 getter,setter,toString
func main() {
person := Person{
"张三", 20,
}
person.say()
person.SetName("李四")
person.say()
}
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (p *Person) say() {
fmt.Printf("我的名字是%v,我今年%v岁 \n", p.Name, p.Age)
}
func (p Person) GetName() string {
return p.Name
}
func (p *Person) SetName(name string) {
p.Name = name
}
十、工厂模式(类似于javaBean的构造函数)
1.解决,当结构体的声明采用首字母小写的形式又希望在其他包使用这个结构体
import (
"go_study_demo/go-study-base/bean"
)
//编写一个Object,给出 getter,setter,toString
func main() {
// Person public的结构体Person是可以被外部访问的
person := bean.Person{
Name: "张三", Age: 20,
}
person.Say()
//创建一个private的结构体 student
student := bean.NewStudent("李四", 18, 100)
student.Say()
}