go语言基础(四):指针、面向对象(继承)、方法

go语言基础(四):指针、面向对象(继承)、方法

1. 指针

  • 注意:无法获取常量内存地址

在这里插入图片描述

  • 指针定义的语法:var 指针变量名 *数据类型
  • 想要访问指针指向的数据,要使用解引用
  • 有关指针的概念性的东西和C语言完全相同
func main() {
	num := 10
	var p *int = &num
	fmt.Printf("%T\n", p)    // *int
	fmt.Printf("%d\n", *p)   // 10
}

2.1 使用new开辟堆空间

语法:new(数据类型),返回对应数据类型的指针

func main() {
	var p *int
	// new(数据类型) 开辟数据类型对应的内存空间,并初始化,默认初始化值为0
	p = new(int)
	p1 := new([5]int)
	fmt.Printf("%T\n", p1)   // 数组指针:*[5]int
	fmt.Println(*p)   // 0
    p = nil
}

go语言中不需要自己去释放new出来的空间,可以自己人为的将不再使用的指针置为空,有垃圾回收机制gc

  • 垃圾回收机制gc
    1. 标记清除(三色标记):使用在超过32KB大小的内存
    2. 引用计数:小于32KB的使用引用计数方式回收,如果有引用,计数+1,如果没有使用,计数-1。当引用计数为0时释放该空间

2.2 指针作为函数参数

比较经典的一个栗子,通过函数传递交换两个变量的值

func test(a *int, b *int) {
	*a, *b = *b, *a
}

func main() {
	a := 10
	b := 20
	fmt.Println(a)
	fmt.Println(b)
	test(&a, &b)
	fmt.Println(a)
	fmt.Println(b)
}

2.3数组指针

数组指针在go语言中做了优化,可以当做数组名来使用,主要用处有两个:一是在访问数组元素时,二是在获取数组元素个数时

func main() {
	arr := [5]int{1, 2, 3, 4, 5}
	p := &arr  // 数组指针 *[5]int 类型
	fmt.Printf("%T\n", p)
	// 通过指针操作数组
	fmt.Println((*p)[0])   // 打印 1

	for i:=0; i<len(p); i++ {
		// go语言中对数组指针进行了优化
		// 1.可以直接使用 指针名[index]访问数组元素
		// 2.可以直接使用 len(指针名) 来获取数组元素个数
		// fmt.Println((*p)[i]) 被优化
		fmt.Println(p[i])
	}
}
func main() {
	// 在开辟堆空间时 可以将指针当做数组名一样来操作数组
	var p *[5]int = new([5]int)
	p[0] = 123
	p[1] = 234
	p[2] = 345
	p[3] = 456
	p[4] = 567
	for i:=0; i<len(p); i++ {
		fmt.Println(p[i])
	}
}

注意:前提条件是数组指针和一个实体的数组建立了关系

  • 数组作为函数参数是值传递,所以如果函数的外部需要访问的话需要用一个函数返回值进行传出
  • 如果不使用函数的返回值传出的话,那么就可以使用函数指针,这样传递的进去的是一个地址
func BubbleSort(arr *[5]int) {    // 传递的是数组指针
	for i:=0; i<len(arr)-1; i++ {
		for j:=0; j<len(arr)-1-i; j++ {
			if arr[j] > arr[j+1] {
				arr[j], arr[j+1] = arr[j+1], arr[j]
			}
		}
	}
}

func main() {
	arr := [5]int{5, 4, 3, 2, 1}
	fmt.Println(arr)
	BubbleSort(&arr)    // 传递的是数组指针
	fmt.Println(arr)
}

2.4 指针数组

语法:var 数组名 [元素个数]*数据类型

数组中的每个元素都是一个指针。

func main() {
	a := [3]int{1, 2, 3}
	b := [3]int{4, 5, 6}
	c := [3]int{7, 8, 9}

    // 数组指针数组
	var arr [3]*[3]int = [3]*[3]int{&a, &b, &c}

	for i:=0; i<len(arr); i++ {
		fmt.Println(*arr[i])
	}

	// 打印元素的值
	for i:=0; i<len(arr); i++ {
		for j:=0; j<len(arr[i]); j++ {
			fmt.Print(arr[i][j], " ")
		}
		fmt.Println()
	}
}

2.5 结构体指针

语法:var 指针名 *结构体类型

type Student struct {
	id int
	name string
	age int
}

func main() {
	// 结构体指针变量
	var p *Student = new(Student)

	// go语言做了优化,结构体指针变量直接使用.就可以访问成员变量
	// (*p).id = 1001
	p.id = 1001
	p.name = "alin"
	p.age = 21
	fmt.Println(*p)
	// 如果直接打印结构体指针变量的话,结果前面会有一个&
	fmt.Println(p)
}

2.6 切片指针

切片指针的类型:*[]数据类型

  • 要注意在访问切片元素时不能直接.出来,要用(*p)[index]
func main() {
	slice := []int{1, 2, 3, 4, 5}
	var p *[]int = &slice
	fmt.Println(*p)

	// 切片指针在获取切片中的元素时要注意与数组区分
	// 在这里没有对它进行优化,必须要使用(*p)[index]
	fmt.Println((*p)[0])

	// fmt.Println(p[0])  // 报错
}
  • 切片在传入函数时,为了不发生错误,使用切片指针传入
    • 因为在切片扩充时,如果切片后面的内存被占用,那么将无法原地进行切片的扩充,就需要另寻空间,此时的slice变为了新的值,那么形参和实参则不相同
    • 使用切片指针会解决这个问题
func test1(slice *[]int) {
	*slice = append(*slice, 1,2,3,4,5)
}

func main() {
	slice := []int{1, 2, 3, 4, 5}
	test1(&slice)
	fmt.Println(slice)
}

2.7 指针可以使用的运算符

  • ==!=可以在指针的比较中使用。

  • >< 不能用于指针的比较中。即指针不允许使用算数运算符计算,要和C语言区分,C语言中允许使用-

2.8 多级指针

func main() {
	a := 10
	var p *int = &a     // 一级指针
	var pp **int = &p   // 二级指针
	// pp = &(&a)   // 错误,&不能连用,加括号也不行
	fmt.Println(**pp)
}

2. 面向对象:继承

2.1 给对象绑定方法(描述对象的行为)

语法:

func (对象名 类名) 函数名(函数参数) 函数返回值 {
    语句
}
  • 这是在给func后面括号中的类的对象绑定方法,所有的这个类的对象都可以通过对象名.函数名使用这个方法

2.2 匿名字段

go语言中的继承:将一个结构体作为另外一个结构体的成员,使用匿名字段,也就是没有对象名的类名

  • go语言对匿名字段进行了优化,通过子类.父类的成员变量可以访问继承过来的成员变量
  • 如果不使用匿名字段,使用实名字段给继承的父类添加了名字后,不能再使用上述的优化
type Person struct {
	id int
	name string
	sex string
}

type Student struct {
	Person // 匿名字段实现继承
	score int
}

type Teacher struct {
	Person
	subject string
}

func main() {
	var stu Student
	// 子类对象.父类.成员
	stu.Person.name = "huahua"
	stu.Person.sex = "女"
	stu.Person.id = 1001
	stu.score = 100
	// go语言做了优化,可以直接使用子类对象.父类成员名进行访问
	stu.name = "alin"
	stu.id = 1002
	stu.sex = "男"

	fmt.Println(stu)
}

2.3 同名字段

就近原则

  • 如果当同名字段比较多的时候,建议使用实名字段进行继承,因为需要多写一个类名
type Person1 struct {
	id   int    // 8
	name string //16
	sex  string //16
}

type Student1 struct {
	Person1      // 匿名字段实现继承
	name  string //16
	score int    // 8
}

func main() {
	stu := Student1{Person1{1001, "alin", "男"}, "alin666", 100}
	fmt.Println(stu)

	fmt.Println(stu.name)         // 就近原则,打印:alin666
	fmt.Println(stu.Person1.name) // 打印:alin

	fmt.Println(unsafe.Sizeof(stu))
}

2.4 指针匿名字段

匿名字段继承时使用一个指针来表示基类

type Person2 struct {
	name string
	id int
	age int
}

type Student2 struct {
	*Person2
	score int
}

func main() {
	stu := Student2{&Person2{"alin", 1001, 21}, 100}

	// 可以通过 对象名.父类成员 访问父类成员变量
	// 不必写 *(stu.Person2).name
	stu.name = "alin666"
	fmt.Println(*(stu.Person2))
}
  • 在使用指针匿名字段的时候,如果只创建了一个变量而没有对变量进行初始化的时候,不能使用对象名.父类的成员变量,因为父类的指针还是一个空指针,不能对空指针进行操作。解决办法:先对父类指针new一下再使用
type Person2 struct {
	name string
	id int
	age int
}

type Student2 struct {
	*Person2
	score int
}

func main() {
	var stu Student2
	stu.name = "alin"
	stu.id = 1001
	stu.age = 21
	stu.score = 100
	fmt.Println(stu)
}
// 报错
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x206a]

修改后:

func main() {
	var stu Student2
    // 添加如下的语句
	stu.Person2 = new(Person2)
	stu.name = "alin"
	stu.id = 1001
	stu.age = 21
	stu.score = 100
	fmt.Println(stu)
}

2.5 多重继承

  1. 嵌套继承

    type Human struct {
    	name string
    	sex string
    }
    
    type Person3 struct {
    	Human
    	age int
    }
    
    type Student3 struct {
    	Person3
    	score int
    }
    
    func main() {
    	stu := Student3{Person3{Human{"alin", "男"}, 21}, 100}
    
    	stu.name = "alin666"    // 可以直接使用对象名.成员变量进行成员变量的修改
    	fmt.Println(stu)
    
    }
    
  2. 继承多个类

    type Human1 struct {
    	name string
    	sex string
    }
    
    type Person4 struct {
    	age int
    	addr string
    }
    
    type Student4 struct {
    	Human1
    	Person4
    	score int
    }
    
    func main() {
    	stu := Student4{Human1{"alin", "男"}, Person4{21, "TYUT"}, 100}
    	stu.name = "alin666"
    	fmt.Println(stu)
    }
    
  • 尽量在程序中减少多重继承的使用,因为这样会增加程序的复杂度。可能会出现很多的同名字段,如果有很多同名字段的话,可以使用实名字段继承来避免错误的产生。

2.6 结构体的相互嵌套

结构体不允许相互嵌套,如果使用普通的匿名字段会报错。

如果打算使用的话:使用指针匿名字段。

  • 同样的,如果说结构体想要嵌套自己,也要使用指针匿名字段

3. 方法

方法指的是类中的函数

语法:func (方法的接受者) 方法名(方法的参数列表) 返回值列表 {}

type INT int    // 给int起别名为INT
// 因为绑定方法不能给普通内置类型绑定,所以给int起一个别名后可以当成创建一个对象来使用
func (a INT) add(b INT) {
	sum := a+b
	fmt.Println(sum)
}

func main() {
	// 相当于创建两个int类型的对象
	var a INT = 10
	var b INT = 20
	// 对象名.方法
	a.add(b)
}
  • 以上代码中要注意两个地方
    • 使用type给内置数据类型起别名后,可以用来当做创建对象使用
    • func绑定对象的时候,不能使用内置数据类型进行绑定,要使用一下type

3.1 类的方法

当调用类的方法的时候,函数参数的传递发生两次,第一次是将对象作为参数传递进函数,第二次是传递函数的参数列表。

type Student5 struct {
	id   int
	name string
	age  int
}

func (stu Student5) EditInfo() {
	stu.name = "alin666"
	fmt.Println("方法内:", stu)
}

func main() {
	stu := Student5{1001, "alin", 21}
	stu.EditInfo()   // 在此时发生了值传递,将stu这个对象也作为了一个函数参数传入了函数内部
	fmt.Println("方法外:", stu)
}
// 结果
方法内: {1001 alin666 21}
方法外: {1001 alin 21}

以上代码中函数内外的stu是不同的两个变量,所以修改一方的值,另一方不会发生变化。

  • 解决办法:绑定对象时使用类的指针
type Student5 struct {
	id   int
	name string
	age  int
}

func (stu *Student5) EditInfo() {
	// (*stu).name = "alin666"     //优化为下面的代码
	stu.name = "alin666"
	fmt.Println("方法内:", *stu)
}

func main() {
	stu := Student5{1001, "alin", 21}
	// (&stu).EditInfo() // 传址进入函数参数,优化为下面的代码
	stu.EditInfo()
	fmt.Println("方法外:", stu)
}
// 结果
方法内: {1001 alin666 21}
方法外: {1001 alin666 21}
  • 结论:如果在函数内部修改对象的属性在函数外部也要求修改的话,使用地址传递,加上一个*

3.2 方法继承

子类对象可以调用从父类继承的父类的方法

type Person6 struct {
	id int
	name string
	age int
}

type Student6 struct {
	Person6
	score int
}

func (per *Person6) PrintInfo() {
	fmt.Println("姓名:", per.name)
	fmt.Println("id:", per.id)
	fmt.Println("年龄:", per.age)
}

func (stu *Student6)test() {
	// 通过子类对象调用从父类继承的方法
	stu.PrintInfo()
	fmt.Println("成绩:", stu.score)
}

func main() {
	stu := Student6{Person6{1001, "alin", 21}, 100}
	stu.test()
}

3.3 方法重写

子类和父类中有相同的方法名,叫方法的重写。

父类只能调父类自己的方法,子类可以通过子类对象.方法调用子类的重写方法,也可以通过子类对象.父类名.父类方法调用父类的方法。

type Person8 struct {
	id int
	name string
	age int
}

type Student8 struct {
	Person8
	score int
}

func (per *Person8) PrintInfo() {
	fmt.Println(*per)
}

func (stu *Student8) PrintInfo() {
	fmt.Println(*stu)
}

func main() {
	stu := Student8{Person8{1001, "alin", 21}, 100}
	stu.PrintInfo()    // 调用子类重写的方法
	stu.Person8.PrintInfo()   // 调用父类的方法
}

3.4 方法值和方法表达式

函数类型可以作为一个类型定义成一个函数类型的变量,可以用于函数调用和函数参数的传递

type Person9 struct {
	id   int
	name string
	age  int
}

type Student9 struct {
	Person9
	score int
}

func test() {
	fmt.Println("hello1")
}

func (stu *Student9) test() {
	fmt.Println("hello2")
}

func main3() {
	test()
	var stu Student9
	stu.test()
}

// 函数参数是函数类型变量
func test1(f func()) {
	f()
	fmt.Println("测试通过")
}

func main() {
	var stu Student9
	// 函数类型变量
	var f func()
	// 将函数赋值给函数类型变量
	f = test
	f()
	// 将对象的方法赋值给函数类型变量
	f = stu.test
	// 通过函数类型变量调用对象方法
	f() // 匿名接收者
	// stu.f() // 报错,stu.只能用来访问对象内存在的成员变量或成员方法,其中没有f这个方法

	test1(f)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值