文章

指针与结构体

指针与结构体

Go语言的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址

指针的概念

指针式存储另一个变量内存地址的变量

package main

import "fmt"

func main() {
   var a int = 10
   //b是指向了a的内存地址,&取地址符
   var b = &a
   fmt.Println(a)
   fmt.Println(&a)
   fmt.Println(b)
   //*指针
   fmt.Println(*b)
   *b = 20
   fmt.Println(a)
}

image-20221221183829473

指针的使用

指针使用流程:

  • 定义指针变量
  • 为指针变量赋值
  • 访问指针变量中指向地址的值

在指针类型前面加上*号(前缀)来获取指针所指向的内容

package main

import "fmt"

func main() {
   //声明一个变量
   var a int = 5
   fmt.Println("a变量的值:", a)
   fmt.Println("a变量的内存地址:", &a)
   //声明一个指针变量
   var p *int
   p = &a //指针变量的存储地址
   fmt.Printf("p变量储存的指针地址:%p\n", p)
   fmt.Printf("p变量地址:%p\n", &p)
   fmt.Println("*p变量的值:", *p)

   *p = 20
   fmt.Println("a变量的值:", a)
   fmt.Println("*p变量的值:", *p)

   //在定义一个指针变量ptr,来指向 指针变量 p
   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)
}

image-20221221185916332

数组指针

数组指针:首先是一个指针,一个数组的地址

指针数组:首先是一个数组,存储的数据类型是指针

package main

import "fmt"

func main() {
   //数组指针
   arr1 := [4]int{1, 2, 3, 4}
   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->地址->值%p\n", *p1)

   (*p1)[0] = 100
   fmt.Println(arr1)
   fmt.Println(*p1)
   //简化写法
   p1[1] = 200
   fmt.Println(arr1)
   fmt.Println(*p1)
   fmt.Println("======================")
   //指针数组
   a := 1
   b := 2
   c := 3
   d := 4
   arr2 := [4]*int{&a, &b, &c, &d}
   fmt.Println(arr2)
   *arr2[0] = 500
   fmt.Println(a)

   a = 100
   fmt.Println(*arr2[0])
}

image-20221221190822990

指针函数

一个函数,该函数的返回值为一个指针

package main

import "fmt"

func main() {
   ptr := f1()
   fmt.Printf("ptr的类型%T\n", ptr)
   fmt.Printf("ptr地址%p\n", &ptr)
   fmt.Println("ptr->地址->值:", *ptr)
   fmt.Println((*ptr)[0])
   fmt.Println(ptr[0])
}
func f1() *[4]int {
   arr := [4]int{1, 2, 3, 4}
   return &arr
}

image-20221221193036661

指针作为参数

package main
func main(){
  a := 10
	fmt.Println("a=", a)
	f2(&a)
	fmt.Println("a=", a)
}

func f2(ptr *int) {
   fmt.Println("ptr:", ptr)
   fmt.Println("*ptr:", *ptr)
   *ptr = 100
}

image-20221221193456294

结构体

结构体是由一系列具有相同类型或不同类型的变量数据构成的数据集合,一般用来封装一类事务的变量,结构体可以理解为一堆变量的集合。结构体表示一项纪录,比如保存人的信息,每个人都有一下属性:

  • Name:名字
  • Age:年龄
  • Sex:性别

定义结构体

结构体定义需要使用type和struct语句

struct语句定义一个新的数据类型,结构体中有一个或多个成员

定义结构体

type user struct {
   id int
   score float32
   enrollment time.Time
   name,age string
}

声明和初始化结构体

var u user                                  //声明,会用相应类型的默认值初始化struct里的每一个字段
u = user{}                                  //用相应类型的默认值初始化struct里的每一个字段
u = user{id: 3, name: "zcy"}                //赋值初始化
u = user{4, 100.0, time.Now(), "zcy", "sz"} //赋值初始化,可以不写字段名,但需要跟结构体定义里的字段顺序一致

访问与修改结构体

u.enrollment = time.Now()
   fmt.Printf("id=%d, enrollment=%v, name=%s\n,city=%s\n", u.id, u.enrollment, u.name, u.city) //访问结构体的成员变量

成员方法

// 可以把user理解为hello函数的参数,即hello(u user, man string)
func (u user) hello(man string) {
   fmt.Println("hi " + man + ", my name is " + u.name)
}
func (_ user) think(man string) {
   fmt.Println("hi " + man + ", do you know my name?")
}

为自定义类型添加方法

type UserMap map[int]User //自定义类型
//可以给自定义类型添加任意方法
func (um UserMap) GetUser(id int) User {
    return um[id]
}

结构体的可见性

  • go语言关于可见的统一规则:大写字母开头跨package也可以访问;否则只能本package内部访问。
  • 结构体名称以大写开头时,package外部可见,在此前提下,结构体中以大写开头在成员变量或成员方法在package外部也可见。

type语句设定了结构体的名称

package main

import "fmt"

// 定义一个结构体 User权限:小写私有、大写公开
type User struct {
	name string
	age  int
	sex  string
}

func main() {
	//创建一些对象
	var user1 User
	user1.name = "yama"
	user1.age = 18
	user1.sex = "男"

	fmt.Println(user1)
	fmt.Println(user1.name)
	fmt.Println(user1.age)

	user2 := User{}
	user2.name = "jiang"
	user2.age = 20
	user2.sex = "女"
	fmt.Println(user2)

	user3 := User{
		name: "zhao",
		age:  19,
		sex:  "男",
	}
	fmt.Println(user3)

	user4 := User{"zuo", 21, "男"}
	fmt.Println(user4)
}

image-20221222011719068

结构体指针

结构体是值类型的

使用**内置函数new()**创建,new的所有Type都返回指针

创建结构体指针

package main

import "fmt"

type User struct {
   Id         int
   Name, addr string
}

func main() {
   var u User
   user := &u
   fmt.Println(*user)
   user = &User{
      Id:   3,
      Name: "yama",
      addr: "sz",
   }
   fmt.Println(*user)
   user = new(User)
   fmt.Println(user)
}

image-20230313223644588

构造函数

func Newuser(id int, name string) *User {
   return &User{
      Id:   4,
      Name: "jiang",
      addr: "China",
   }
}

方法接收指针

//user传的是值,即传的是整个结构体的拷贝。在函数里修改结构体不会影响原来的结构体
func hello(u user, man string) {
    u.name = "杰克"
    fmt.Println("hi " + man + ", my name is " + u.name)
}
//传的是user指针,在函数里修改user的成员会影响原来的结构体
func hello2(u *user, man string) {
    u.name = "杰克"
    fmt.Println("hi " + man + ", my name is " + u.name)
}
//把user理解为hello()的参数,即hello(u user, man string)
func (u user) hello(man string) {
    u.name = "杰克"
    fmt.Println("hi " + man + ", my name is " + u.name)
}
//可以理解为hello2(u *user, man string)
func (u *user) hello2(man string) {
    u.name = "杰克"
    fmt.Println("hi " + man + ", my name is " + u.name)
}
package main

import "fmt"

type User struct {
   name string
   age  int
   sex  string
}

func main() {
   //结构体是值类型的,值传递
   user1 := User{"zuo", 18, "男"}
   fmt.Println(user1)
   user2 := user1
   user2.name = "jiang"
   fmt.Println(user1)
   fmt.Println(user2)

   //指针传递
   var user3 *User
   user3 = &user1
   (*user3).name = "zhao"
   fmt.Println(user1)
   //new
   user4 := new(User)
   fmt.Printf("%T\n", user4)
   (*user4).name = "sun"
   user4.age = 30
   fmt.Println(user4)
}

image-20221222012535098

匿名结构体

匿名结构体:没有名字的结构体

匿名字段:一个结构体的字段没有字段名

匿名结构体通常用于只使用一次的情况。 结构体中含有匿名成员

package main

import "fmt"

type Student struct {
   name string
   age  int
}
type Teacher struct {
   //匿名字段
   string
   int
}

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

   //匿名结构体
   s2 := struct {
      name string
      age  int
   }{
      name: "jiang",
      age:  20,
   }
   fmt.Println(s2)

   z1 := Teacher{"匿名字段", 20}
   fmt.Println(z1)
   fmt.Println(z1.string)
   fmt.Println(z1.int)
}

image-20221222014126647

结构体嵌套

一个结构体可能包含一个字段,这个字段又是一个结构体,这被称为结构体嵌套

package main

import "fmt"

type Person struct {
   name string
   age  int
   //结构体嵌套
   addr Address
}
type Address struct {
   city, state string
}

func main() {
   var person = Person{}
   person.name = "zuo"
   person.age = 19
   person.addr = Address{
      city:  "深圳",
      state: "中国",
   }
   fmt.Println(person.addr)
   fmt.Println(person.name)
}

image-20221222102354610

结构体导出

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

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

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

创建一个pojo文件夹,新建一个user.go

package pojo

// 大写字母开头就是外部可以调用的 公开
type User struct {
   Name  string
   age   int
   Money string
}

// 小写字母开头就是外部不能调用的  私有
type cat struct {
   age int
}
package main

import (
   "fmt"
   "go/yama_go/main/06_Pointers_and_Structs/pojo"
)

func main() {
   var user pojo.User
   user.Name = "zuo"
   user.Money = "1000"
   fmt.Println(user)//{zuo 0 1000}
}

深拷贝与浅拷贝

type User struct {
   Name string
}
type Vedio struct {
   Length int
   Author User
}

Go语言里的赋值都会发生值拷贝。

image-20230313225110599

type User struct {
	Name string
}
type Vedio struct {
	Length int
	Author *User
}

image-20230313225200191

  • 深拷贝,拷贝的是值,比如Vedio.Length。
  • 浅拷贝,拷贝的是指针,比如Vedio.Author。
  • 深拷贝开辟了新的内存空间,修改操作不影响原先的内存。
  • 浅拷贝指向的还是原来的内存空间,修改操作直接作用在原内存空间上。

  传slice,对sclice的3个字段进行了拷贝,拷贝的是底层数组的指针,所以修改底层数组的元素会反应到原数组上。

users := []User{{Name: "康熙"}}
func update_users(users []User) {
    users[0].Name = "光绪"
}
License:  CC BY 4.0 test