第四章 面向对象
第一天: go对象的基础. 如何创建结构体,方法,构造方法(工厂函数),接收者模式 第二天: 包,如何引入外部包和系统包(定义别名或组合) 第三天: 每个目录定义一个main方法.?? ? 一. 面向对象介绍 1. go语言仅支持封装,不支持继承和多态.? 那么继承和多态所做的事情,怎么来做呢? 使用接口来实现,go语言是面向接口编程. 2. go语言只支持封装,所以,go语言没有class,只有struct ? 二. 结构体的用法 1. 结构体的创建方法 ? type TreeNode struct { Value int Left,Right *TreeNode } func main() { //创建结构体的方法 var root TreeNode root = TreeNode{Value:4} root.Left = &TreeNode{} root.Right = &TreeNode{5,nil,nil} root.Left.Right = new(TreeNode) fmt.Println(root) }
2. slice中实例化结构体的方法 func main() { .Println(root) nodes := []TreeNode{ {4,nil},{},{Value:3},{5,&root},} .Println(nodes) } 在slice中构建结构体的时候,可以省去结构体名 nodes := []TreeNode{ {3},{5,&root},} ? 3. go语言构造函数?root = TreeNode{Value:} root.Left = &TreeNode{} root.Right = &TreeNode{go语言没有构造函数的说法. 但是从上面的例子可以看出,他已经给出了各种各样的构造函数,无参的,一个参数的,多个参数的
TreeNode } func NewTreeNode(value int) *TreeNode { return &TreeNode{Value:value} }
问题: 在NewTreeNode函数里面返回了一个局部变量的地址. 这种java里是不允许的. 但在go中是允许的. 那么这个局部的TreeNode到底是放在堆里了还是放在栈里了呢? c语言,局部变量是放在栈上的,如果想要呗别人访问到就要放在堆上,结束后需要手动回收. java语言,类是放在堆上的,使用的时候new一个,用完会被自动垃圾回收 而go语言,我们不需要知道他是创建在堆上还是栈上. 这个是由go语言的编译器和运行环境来决定的. 他会判断,如果TreeNode没有取地址,他的值不需要给别人用,那就在栈上分配,如果取地址返回了,那就是要给别人用,他就在堆上分配. 在堆上分配完,会被垃圾回收 如上: 我们定义了一个这样的结构 TreeNode } func NewTreeNode(value int) *TreeNode { return &TreeNode{Value:value} } func main() { new(TreeNode) root.Left.Right = NewTreeNode(2) } ? ? ? ?4. 如何给结构体定义方法 TreeNode } func NewTreeNode(value TreeNode{Value:value} } func (node TreeNode) Print() { fmt.Println(node.Value) } 如上就定义了一个Print方法,?
? ?总结:? 1. 调用print()方法是将值拷贝一份进行打印 2. 调用setValue()方法是地址拷贝一份,给地址中的对象赋值. 4. nil指针也能调用方法 注意: 这里的重点是nil指针. 而不是nil对象 这里为什么拿出来单写呢? 是因为,他和我之前学得java是不同的. null对象调用方法,调用属性都会报错,而nil可以调用方法. 我们先来看这个demo
TreeNode{Value:value} } func (node TreeNode) Print() { fmt.Println(node.Value) } func (node *TreeNode) setValue() { node.Value = 200 } func main() { var node TreeNode fmt.Println(node) node.Print() node.setValue() node.Print() } ? 输出结果:? {0 <nil> <nil>} 0 200 这里main中的treeNode是对象,不是地址. 他在初始化的时候如果没有值,会给一个默认的值. 所以,使用它来调用,肯定都没问题. 我们这里要讨论的是空指针. 来看看空指针的情况 TreeNode } func (node *TreeNode) Print() { if node == nil { fmt.Println("node为空指针") return } .Println(node.Value) } func main() { var node *TreeNode .Println(node) node.Print() } ? 和上一个的区别是,这里的TreeNode是一个指针. 来看看结果 <nil>
node为空指针
确实,成功调用了Print方法,并且捕获到node对象是空对象 但这里需要注意,对nil对象调用属性,依然是会报错的.? TreeNode) Print() { if node == nil { fmt.Println("node为空指针") // return } .Println(node.Value) } func main() { var node *TreeNode .Println(node) node.Print() } 把return注释掉. 看结果 报了panic异常. 那么,指针接收者是不是上来都要判断这个指针是否是nil呢? 这不一定,要看使用场景. ? 5. 结构体函数的遍历 func(node *TreeNode) traveres() { nil{ return } node.Left.traveres() node.Print() node.Right.traveres() } 遍历左子树,打印出来,在遍历又子树,打印出来 结果:? 3 5 4 注意: 这里的node.Left.traveres()的写法. 我们只判断了node是否为nil. 如果在java中,我们还需要判断node.Left是否为null. 否则会抛异常,但是go不会,nil指针也可以调用方法 ? 到底应该使用值接受者还是指针接收者?
?三. 包包里面重点说明的是 1. 首字母大写表示public,首字母小写表示private 2. 包的定义: 一个目录下只能有一个包.? 比如,定义了一个文件夹叫tree. 那么他里面所有的文件的包名都是tree. 或者都是main(这样也是允许的). 不能既有tree又有main.?? 四. 如何扩展系统包或者别人定义的包?假如有一个别人写的结构体,我想用,但是还不满足我的需求,我想扩展,怎么扩展呢? 在其他语言,比如c++和java都是继承,但继承有很多不方便的地方. 所以go取消了继承. 用以下两种方法实现
1. 定义别名: 比如上面treeNode的例子. 如果我想在另外一个包里扩展,使用定义别名的方式如何实现呢? package main import ( aaa/tree" fmt ) 原来遍历方式是前序遍历. 现在想扩展一个后序遍历. 怎么办呢? 我们使用组合的方式来实现一下 第一步: 自己定义一个类型,然后引用外部类型. 引用的时候最好使用指针,不然要对原来的结构体进行一个值拷贝 第二步: 扩展自己的方法 type myTreeNode struct { node *tree.TreeNode } func (myNode *myTreeNode) postorder() { if myNode == nil || myNode.node == nil{ return } left := myTreeNode{myNode.node.Left} left.postorder() right := myTreeNode{myNode.node.Right} right.postorder() .Print(myNode.node.Value) } func main(){ var root tree.TreeNode root = tree.TreeNode{Value:tree.TreeNode{} root.Right = &tree.TreeNode{ new(tree.TreeNode) root.Right.Right = tree.NewTreeNode() root.Traveres() var node *tree.TreeNode node.Traveres() treeNode := myTreeNode{&root} treeNode.postorder() } 第一步: 先定义一个自己的类型,然后引入外部结构. 这里组好引入的是指针类型,不然对外部结构还要进行一份值拷贝 type myTreeNode struct { node *tree.TreeNode } 这样做,当前这个对象已经拥有了原来定义的TreeNode结构体. 想象一下使用的时候,传递进来了一个TreeNode类型的结构体. 然后我们对这个TreeNode结构体进行操作 ? 第二步: 实现自己的方法,后序遍历 func (myNode *myTreeNode) postorder() { 取出外部结构体,然后获取结构体的左子树. 在获取结构体的右子树,在打印出来,这样就实现了对原来结构体的调用了. ? 第三步: 调用 func main(){ tree.TreeNode node.Traveres() treeNode := myTreeNode{&root} treeNode.postorder() } 调用也很简单. 吧root传进来地址,然后调用方法即可 ? 2. 定义别名的方式实现外部结构体或系统结构体的调用 下面我们给切片定义一个别名. --- 队列 package main import type Queue [] func(q *Queue) add(v ){ *q = append(*q,v) } func(q *Queue) pop() { tail := (*q)[len(*q)-1] *q = (*q)[:len(*q)-] return tail } func(q *Queue) isEmpty() bool { return len(*q) == 0 } func main() { q := Queue{} q.add() q.add() .Println(q.pop()) .Println(q.isEmpty()) .Println(q.isEmpty()) } 第一步: 给切片定义一个别名 type Queue []int
然后对这个切片进行操作,添加一个元素 func(q *Queue) add(v func main() { q := Queue{} fmt.Printf(地址: 0x%x n",&q[]) q.add(.Println(q.isEmpty()) } 地址: 0xc000096008 地址: 0xc000096028 2 false 1 true 两次打印出来的地址是不同的. 说明他的地址变了 ? 五. 包名的定义,每一个文件夹下面只能有一个main我们用系统包来举例 ? 所以,我们在定义文件的时候,在每一个文件夹下定义一个main函数.?? ? (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |