指针与结构体

指针

Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。

变量是一种占位符,底层指向是一个内存地址。

&取地址符,拿到一个变量 a,&a取出这个变量的地址。

image-20230216200546322

指针的概念

&取地址符

b内存地址 0x11111111111,   值:500                var b int = 500
a指针变量,指向了一个内存地址 0x11111111111

变量a持有了变量b的地址。这个就是a指向了b
package main

import "fmt"

// 指针
func main() {

   var a int = 10
   // b存储的是a的地址,b也有自己的地址
   var b = &a

   fmt.Println("a变量的地址:", &a) // 0xc00000e0a8
   fmt.Println("b存储的地址:", b)  // 0xc00000e0a8-->10
   fmt.Println("b自己的地址:", &b) // 0xc00000a028

   // &  *
   // b 指向的地址 ,  * 取出 b变量所指向的地址 中的值。
   fmt.Println("b变量指向的地址中的值:", *b)

   *b = 20
   fmt.Println(a) // a 变量20,通过指针变量b,操控了a的值。
}

image-20230216201732959

指针如何使用

package main

import "fmt"

// 指针的使用
/*
1、定义指针变量
2、为指针变量赋值  &
3、访问指针变量中地址所指向的值   *
* :在指针类型前面加上 * 号,就是来获取指针所指向的地址的值

*/
func main() {

   // 声明 普通变量
   var a int = 10
   fmt.Printf("a 变量的值:%d\n", a)   // 10
   fmt.Printf("a 变量的地址:%p\n", &a) // 0x

   // 声明 指针变量,指向a, 指针其实就是一个特殊的变量而已。,ptr命名  p
   // 定义变量格式  var ptr *类型
   var p *int
   p = &a // 指针变量赋值

   fmt.Printf("p 变量存储的指针地址:%p\n", p)     // a 的地址
   fmt.Printf("p 变量自己的地址:%p\n", &p)      // p 变量自己的地址
   fmt.Printf("p 变量存储的指针地址指向的值%d\n", *p) // a的值

   // 改变a的值   *p 和 a 其实是同一个地址
   a = 20
   fmt.Printf("a:%d\n", a)
   fmt.Printf("*p的值:%d\n", *p)

   // 通过指针改变a的值
   *p = 40
   fmt.Printf("a:%d\n", a)
   fmt.Printf("*p的值:%d\n", *p)

   // 指针的套娃,指针指向指针 , 指针类型 第一个*指针类型, *int是这个指针对应的类型
   // 如何理解多个符号,第一个取出来后,后面就是它的类型 *(*(int))
   var ptr **int
   ptr = &p
   //
   fmt.Printf("ptr变量存储的指针的地址:%p\n", ptr)
   fmt.Printf("ptr变量自己的地址:%p\n", &ptr)
   fmt.Printf("*ptr变量存储的地址:%p\n", *ptr)
   fmt.Printf("*ptr变量存储的地址中的值:%d\n", **ptr)
   // 修改变量a就有了无数种方式
   **ptr = 1111
   fmt.Println(a)

}

数组指针

  • 数组指针,首先应该是一个指针,指向了一个数组
package main

import "fmt"

// 数组指针
func main() {

   // 创建数组,值传递。fun
   arr1 := [4]int{1, 2, 3, 4}
   fmt.Println("arr1:", arr1)
   fmt.Printf("arr1指向的地址:%p\n", &arr1)

   // 创建一个指针,指向这个数组的地址,通过指针来操作数组
   var p1 *[4]int
   p1 = &arr1
   fmt.Printf("p1指向的地址: %p\n", p1)
   fmt.Printf("p1自己的地址: %p\n", &p1)
   fmt.Println("p1指向的地址的值: ", *p1)

   // 操作数组指针 来修改数组
   (*p1)[0] = 100 // 原生写法
   fmt.Println("arr1:", arr1)
   fmt.Println("p1指向的地址的值: ", *p1)

   // 语法糖:由于p1指向了arr1这个数组,所以可以直接用p1来操控数组
   // 指向了谁,这个指针就可以代表谁。
   // p1 = arr1
   p1[0] = 200 // 在程序中,我们更多时候是这样在使用指针的
   fmt.Println("arr1:", arr1)
   fmt.Println("p1指向的地址的值: ", *p1)

}
  • 指针数组,首先应该是一个数组,保存是指针
package main

import "fmt"

func main() {

   a := 1
   b := 2
   c := 3
   d := 4

   // 创建一个指针数组
   arr1 := [4]*int{&a, &b, &c, &d}
   fmt.Println(arr1)

   // 通过指针修改a的值
   // arr1[0] 0xc00000e0a8
   *arr1[0] = 100
   fmt.Println(a)

   a = 200
   fmt.Println(*arr1[0])
}

指针函数

首先需要是一个函数,这个函数的返回值是一个指针。

package main

import "fmt"

// 指针函数, 指针是可以用作函数的返回值
func main() {
   // 调用了这个函数后,可以得到一个指针类型的变量。
   ptr := f5()

   fmt.Println("ptr:", ptr)
   fmt.Printf("ptr类型:%T\n", ptr)
   fmt.Println("ptr的地址:\n", &ptr)
   fmt.Println("ptr地址中的值:\n", *ptr)

   // 使用
   fmt.Println((*ptr)[0])
   ptr[0] = 10
   fmt.Println(ptr[0])

}

// 调用该函数后返回一个指针
func f5() *[4]int {
   arr := [4]int{1, 2, 3, 4}
   return &arr
}

指针作为函数参数

package main

import "fmt"

func main() {
   // 值传递
   a := 10
   fmt.Println("a:", a)  // 10
   fmt.Println("a addr:", &a) // 0xc00000e0a8

   f6(&a)

   fmt.Println("a:", a) // 20
   fmt.Println("a addr:", &a) // 0xc00000e0a8
}

// 指针当做函数的参数
func f6(ptr *int) {
   fmt.Println("ptr:", ptr)  // 0xc00000e0a8
   fmt.Println("ptr 指针的地址中的值", *ptr) // a = 10
   *ptr = 20 
}

补充:什么时候定义的变量会销毁,没有任何东西再指向它的时候,垃圾回收(GC)会回收。

结构体

变量:解决生活中问题(定义名字)

判断:解决生活中问题(分支:true false)

循环:解决生活中问题(循环:多次操作 for)

面向对象编程思维 OOP:将世界上的所有东西,抽象成一个个的类(属性、方法)

我想用一个变量来定义一个人的信息: map[string]string slice(,)

人:属性(name、sex、age) 动作( 吃 eat() 工作 work() sleep() )

定义一个结构体

定义变量 var

定义结构体 type User struct {}

package main

import "fmt"

// 定一个结构体 type User struct
type User struct {
   name string
   age  int
   sex  string
}

func main() {
   // 通过结构体创建对象, 以前的类型都是用的基本类型,自己定义类型了type,Struct
   // 定义了结构体对象,不赋值,默认都是这个结构体的零值 {"",0,""}
   var user1 User
   fmt.Println("user:", user1)
   // 给结构体对象赋值。 xxx.属性 = 值
   user1.name = "张三"
   user1.age = 1
   user1.sex = "女"
   fmt.Println("user:", user1)

   // 获取这个人的名字
   fmt.Println("user:", user1.name)

   // 创建对象的方式二
   user2 := User{}
   user2.name = "qinjiang"
   user2.age = 27
   user2.sex = "男"
   fmt.Println("user:", user2)
   fmt.Println("user:", user2.name)

   //     // 创建对象的方式三
   user3 := User{
      name: "feige",
      age:  35,
      sex:  "男",
   }
   fmt.Println("user:", user3)
   fmt.Println("user:", user3.name)

   // 创建对象的方式四,这种不声明属性的方式,需要参数一一匹配
   user4 := User{"guoguo", 18, "女"}
   fmt.Println("user:", user4)
}

结构体指针

package main

import "fmt"

// 定一个结构体 type User struct
type User2 struct {
   name string
   age  int
   sex  string
}

func main() {

   // 结构体类型    包.struct名
   user1 := User2{"kuangshen", 18, "男"}
   fmt.Println(user1)
   fmt.Printf("%T,%p\n", user1, &user1) // main.User2,0xc00007e4b0

   // 结构体是值类型的
   user2 := user1
   fmt.Println(user2)
   fmt.Printf("%T,%p\n", user2, &user2) // main.User2,0xc00007e540

   user2.name = "tywin"
   fmt.Println(user1)
   fmt.Println(user2)
   fmt.Println("========================")
   // 指针解决值传递的问题
   var user_ptr *User2
   user_ptr = &user1
   // *user_ptr 等价于 user1
   fmt.Println(*user_ptr)
   (*user_ptr).name = "qinjiang"
   fmt.Println(user1)
   // 语法糖
   user_ptr.name = "qinjiang222222222"
   fmt.Println(user1)

   // 内置函数 new 创建对象。  new 关键字创建的对象,都返回指针,而不是结构体对象。
   // func new(Type) *Type
   // 通过这种方式创建的结构体对象更加灵活,突破了结构体是值类型的限制。
   user3 := new(User2)
   fmt.Println(user3)

   (*user3).name = "小红"
   user3.sex = "女"
   user3.age = 18
   fmt.Println(user3)

   updateUser(user3)
   fmt.Println(user3)
}
func updateUser(user *User2) {
   user.age = 100
}

匿名结构体

package main

import "fmt"

// 匿名结构体:没有名字的结构体
type Student struct {
   name string
   age  int
}

func main() {

   s1 := Student{"kuangshen", 18}
   fmt.Println(s1.name, s1.age)

   // 匿名结构体,直接可以在函数内部创建出来,创建后就需要赋值使用
   s2 := struct {
      name string
      age  int
   }{
      name: "张三",
      age:  3,
   }
   fmt.Println(s2.name, s2.age)

   // 匿名字段
   t1 := Teacher{"qinjiang", 18}
   fmt.Println(t1)
   // 如何打印这个匿名字段,默认使用数据类型当做字段名称
   fmt.Println(t1.string)
   fmt.Println(t1.int)

}

// 结构体中的匿名字段,没有名字的字段,这个时候属性类型不能重复
type Teacher struct {
   string
   int
}

结构体嵌套

复杂对象构建,可以这么定义

package main

import "fmt"

// 一个结构体可能包含一个字段,而这个字段又是一个结构体:结构体嵌套
type Person struct {
   name    string
   age     int
   address Address
}
type Address struct {
   city, state string
}
// 结构体是可以嵌套:我们就可以定义很多复杂的对象,来进行拼接了,构成一个更大的对象
func main() {

   var person = Person{}
   person.name = "kuangshen"
   person.age = 18
   person.address = Address{
      city:  "广州",
      state: "中国",
   }
   fmt.Println(person.name)
   fmt.Println(person.age)
   fmt.Println(person.address)
   fmt.Println(person.address.city)

}

结构体导出

public 公开的,所有地方都可以使用 、priavte私有的,只能自己使用

结构体:结构体名字,属性名字 。大写字母,可以导出使用,小写字母,不能导出使用。

如果结构体名称首字母小写,则结构体不会被导出。这时,即使结构体成员字段名首字母大写,也不会被导出。

如果结构体名称首字母大写,则结构体可被导出,但只会导出大写首字母的成员字段,那些小写首字母的成员字段不会被导出。

如果存在嵌套结构体,即使嵌套在内层的结构体名称首字母小写,外部也能访问到其中首字母大写的成员字段。

image-20230216220213474

将我讲的所有demo自己手动写一遍,并理解

预习 面向对象