指针
指针的概念
Go语言中指针简单易学,使用指针可以更简单的执行一些操作。
变量是一种占位符,底层指向的是一个内存地址。
&取地址符,对于变量a,&a取出这个变量的地址。
指针的使用
定义指针变量
为指针变量赋值 &
指针变量前加上*,来访问指针变量中地址所指向的值
package main
import "fmt"
func main() {
var a int = 10
// 声明指针变量,其实就是一个特殊的变量,指向a
// 定义变量格式 var ptr *类型
var p *int
p = &a
fmt.Printf("a变量的地址:%p\n", &a) // 0xc00001c0a8
fmt.Printf("p变量存储的地址:%p\n", p) // 0xc00001c0a8
fmt.Printf("p变量自己的地址:%p\n", &p) // 0xc00000a028
fmt.Println("p变量存储的指针地址指向的值:", *p) // 10
*p = 20
fmt.Println(a) // 20 通过指针变量b,改变了a的值
a = 40
fmt.Println(*p) // 40
// 指针的嵌套,指针指向指针,*int就是这个指针对应的类型
var ptr **int
ptr = &p
fmt.Printf("ptr变量存储的指针地址:%p\n", ptr) // 0xc00000a028
fmt.Printf("ptr变量自己的地址:%p\n", &ptr) // 0xc00000a038
fmt.Printf("*ptr变量存储的指针地址:%p\n", *ptr) // 0xc00001c0a8
fmt.Printf("*ptr变量存储的地址指向的值:%d\n", **ptr) // 40
// 使用指针嵌套,修改变量a的值就有了无数种方式
**ptr = 50
fmt.Println(a) // 50
}
数组指针
数组指针:指向数组的指针
指针数组:保存指针的数组
package main
import "fmt"
// 数组指针
func main() {
// 创建一个数组
arr1 := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr1) // [1 2 3 4 5]
fmt.Printf("arr1指向的地址:%p\n", &arr1) // 0xc00000e390
// 创建一个指针,指向这个数组的地址,通过指针来操作数组
var p1 *[5]int
p1 = &arr1
fmt.Printf("p1指向的地址:%p\n", p1) // 0xc00000e390
fmt.Printf("p1自己的地址:%p\n", &p1) // 0xc00000a030
fmt.Printf("p1指向的地址的值:%d\n", *p1) // [1 2 3 4 5]
// 操作数组指针,修改数组
(*p1)[0] = 100 // 原生写法
fmt.Println(arr1) // [100 2 3 4 5]
fmt.Println(*p1) // [100 2 3 4 5]
// 语法糖 可以直接用p1来操控数组
// p1 == arr1
p1[0] = 100
fmt.Println(*p1) // [100 2 3 4 5]
fmt.Println(arr1) // [100 2 3 4 5]
}
package main
import "fmt"
func main() {
a := 1
b := 2
c := 3
d := 4
e := 5
// 创建一个指针数组
arr1 := [5]*int{&a, &b, &c, &d, &e}
fmt.Println(arr1) // [0xc000186018 0xc000186030 0xc000186038 0xc000186040 0xc000186048]
// 通过指针修改a的值
*arr1[0] = 10
fmt.Println(a) // 10
}
指针函数
需要是一个函数,这个函数的返回值是一个指针。
package main
import "fmt"
// 指针函数 指针可以用作函数的返回值
func main() {
p1 := fun()
fmt.Printf("%p\n", p1)
fmt.Printf("p1变量的类型:%T\n", p1) // *[5]int
fmt.Printf("p1变量的地址:%p\n", &p1) // 0xc00000a028
fmt.Printf("p1变量存储的地址指向的值:%d\n", *p1) // [1 2 3 4 5]
fmt.Println((*p1)[0]) // 1
p1[0] = 100
fmt.Println(p1[0]) // 100
}
// 调用该函数,返回的是一个指针
func fun() *[5]int {
arr := [5]int{1, 2, 3, 4, 5}
return &arr
}
指针作为函数参数(常用)
package main
import "fmt"
func main() {
a := 10
fmt.Println("a变量指向的地址:", &a) // 0xc0000a6058
fun1(&a)
fmt.Println(a) // 20
fmt.Println("a变量指向的地址:", &a) // 0xc0000a6058
}
func fun1(ptr *int) {
fmt.Println("ptr 存储的地址:", ptr) // 0xc0000a6058
fmt.Println("ptr 自己的地址:", &ptr) // 0xc0000ca020
fmt.Println("ptr 存储的地址指向的值:", *ptr) // 10
*ptr = 20
}
补充:如果没有任何东西在指向变量,变量会被销毁,垃圾回收(GC)。
结构体
变量:定义名字(解决生活中的问题)
判断:分支:true false(解决生活中的问题)
循环:多次操作for(解决生活中的问题)
面向对象编程思维OOP:将世界上的所有物体,抽象成一个一个的类(属性、方法)
用一个变量定义一个人的信息:map[string]string slice
人:属性(name、sex、age)动作(eat() work() sleep())
结构体定义
package main
import "fmt"
// 定义结构体 type 结构体名 struct
type User struct {
name string
age int
sex string
}
func main() {
// 创建结构体对象的方式一
var user1 User
// 定义了结构体对象,但不赋值,默认是结构体的零值 {"" 0 ""}
fmt.Println(user1) // { 0 }
user1.name = "张三"
user1.age = 18
user1.sex = "男"
fmt.Println(user1) // {张三 18 男}
// 获取名字
fmt.Println(user1.name) // 张三
// 创建结构体对象的方式二
user2 := User{}
user2.name = "李四"
user2.age = 20
user2.sex = "女"
fmt.Println(user2) // {李四 20 女}
// 创建结构体对象的方式三
user3 := User{
name: "王五",
age: 40,
sex: "男",
}
fmt.Println(user3) // {王五 40 男}
// 创建结构体对象的方式四(这种不声明属性的方式,赋值时需要参数一一匹配)
user4 := User{"小明", 25, "男"}
fmt.Println(user4) // {小明 25 男}
}
结构体指针
package main
import "fmt"
// 定义结构体
type User2 struct {
name string
age int
sex string
}
func main() {
user1 := User2{"张三", 18, "男"}
fmt.Println(user1) // {张三 18 男}
// 结构体类型 包.struct名
fmt.Printf("%T, %p\n", user1, &user1) // main.User2, 0xc00007e4b0
// 结构体是值类型的
user2 := user1
fmt.Println(user2) // {张三 18 男}
fmt.Printf("%T, %p\n", user2, &user2) // main.User2, 0xc00007e540
user2.name = "李四"
fmt.Println(user1) // {张三 18 男}
fmt.Println(user2) // {李四 18 男}
fmt.Println("=========================")
// 可以用指针解决值传递的问题
var user_ptr *User2
user_ptr = &user1
// *user_ptr 等价于 user1
fmt.Println(*user_ptr) // {张三 18 男}
(*user_ptr).name = "王五"
fmt.Println(user1) // {王五 18 男}
// 语法糖
user_ptr.name = "王五111"
fmt.Println(user1) // {王五111 18 男}
// 利用内置函数 new 创建对象。
// new 关键字创建的对象,返回指针,而不是返回结构体对象
// 通过这种方式创建的结构体对象更加灵活,突破了结构体值类型的限制
user3 := new(User2)
fmt.Println(user3) // &{ 0 }
(*user3).name = "小明"
user3.age = 25
user3.sex = "男"
fmt.Println(user3) // &{小明 25 男}
updateUser(user3)
fmt.Println(user3) // &{小明 20 男}
}
func updateUser(user *User2) {
user.age = 20
}
匿名结构体
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
s1 := Student{"张三", 18}
fmt.Println(s1) // {张三 18}
// 匿名结构体 没有名字的结构体,直接可以在函数内部创建出来,创建后就需要赋值使用
s2 := struct {
name string
age int
}{
"李四",
20,
}
fmt.Println(s2.name, s2.age) // 李四 20
// 匿名字段
t1 := Teacher{"李四", 25}
fmt.Println(t1) // {李四 25}
// 如何打印这个匿名字段,默认使用数据类型当做字段名称
fmt.Println(t1.string) // 李四
fmt.Println(t1.int) // 25
}
// 结构体中的匿名字段,没有名字的字段,这时属性类型不能重复
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 = "张三"
person.age = 18
person.address = Address{
city: "安庆",
state: "中国",
}
fmt.Println(person.name) // 张三
fmt.Println(person.age) // 18
fmt.Println(person.address) // {安庆 中国}
fmt.Println(person.address.city) // 安庆
}
结构体导出
public 公开的,所有地方都可以使用;private 私有的,只能自己使用
如果结构体名称首字母小写,则结构体不能被导出使用。即使结构体成员字段名首字母大写,也不会被导出。
如果结构体名称首字母大写,则结构体可以被导出使用。但只对成员字段首字母大写导出使用,对于首字母小写的成员字段不能被导出使用。
如果存在嵌套结构体,即使嵌套在内层的结构体名称首字母小写,外部也能访问到其中首字母大写的成员字段。
结论:结构体名字、属性名字。如果是大写字母,可以被导出使用;如果是小写字母,不可以被导出使用。