一、结构体和方法
1.Go面向对象的特性
- Go语言不是纯粹的面向对象的语言,准确是描述是,Go语言支持面向对象编程的特性.
- Go语言中没有传统的面向对象编程语言的 class ,而Go语言中的 struct 和 其他编程语言中的 class 具有同等地位,也就是说Go语言是 基于 struct 来实现 面向对象 的特性的
- 面向对象编程在Go语言中的支持设计得很具有特点,Go语言放弃了很多面向对象编程的概念,如 继承,重载, 构造函数, 析构函数 ,隐藏this指针等
- Go语言可以实现面向对象编程的特性 封装 , 继承, 多态
- Go语言仅支持封装,不支持继承和多态(在go语言中使用接口实现)
- Go语言面向对象编程的支持是语言类型系统 中天然的组成部分,整个类型系统通过接口串联,灵活度高
2.结构体(struct)
在上面说过:“Go语言中的 struct 和 其他编程语言中的 class 具有同等地位”
对结构体的定义使用如下关键字:
type 标识符 struct{
field1 type
field2 type}
例如:
// 构建一个二叉树的结构体
type Node struct{
Value int
Left, right *Node
}
那么怎么来创建Node呢?
package main
import "fmt"
// 构建一个二叉树的结构体
type Node struct{
Value int
Left, Right *Node
}
func mian(){var root Node //创建一个根节点//root := Node{}
root = Node{Value: 3}//root.left = &Node{} 没有给值时,则默认值为0//left和right在结构体中都是指针需要加&
root.Left = &Node{0, nil, nil}
root.Right = &Node{5, nil, nil}
root.Left.Right = &Node{2, nil, nil}
root.Rigth.Left = new(Node) //先new再赋值
root.Right.Left = &Node{0, nil, nil}
}
问题一:为什么这里指针可以表示为root.Right.Left
?
答:我们在c/c++中对于指针其实应该这样写:root.Right->Left
但是在Go语言中是没有->
这种写法,它确实就是可以这样:root.Right.Left
总而言之:无论是地址还是结构体本身一律使用.
来访问成员
在上面代码中我们就实现了这样的一棵树:
3.工厂函数:
Go语言中是没有构造函数的,但是Go语言本身就可以提供构造方法:
如:
root = Node{Value: 0}
root.Left = &Node{}
root.Rigth.Left = new(Node)
root.Right.Left = &Node{4, nil, nil}
但是有时候我们想要自己来构造,构造的创建就可以使用工厂函数
//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func greatNode(value int) *Node{return &Node{Value:value}
}
其实工厂函数和普通函数没有本质上的差别
4.为结构定义方法
直接看例子:我们为Node定义一个打印的方法
//(node Node)中node为接收者,print()是为node接收的
func (node Node) print(){
fmt.Println(node.value)
}
那么如何调用呢?
func mian(){var root Node //创建一个根节点//root := Node{}
root = Node{Value: 3}//root.left = &Node{} 没有给值时,则默认值为0//left和right在结构体中都是指针需要加&
root.Left = &Node{0, nil, nil}
root.Right = &Node{5, nil, nil}
root.Left.Right = &Node{2, nil, nil}
root.Rigth.Left = new(Node) //先new再赋值
root.Right.Left = &Node{0, nil, nil}//调用如下:
root.print()
}
打印值为:3
其实这里它和普通函数差不多
普通的定义如下:
func print(node Node){
fmt.Println(noe.value)
}
//调用 print(root)
值接收者 VS 指针接收者:
- 改变内容必须用指针接收者
- 结构过大也可以考虑指针接收者
- 一致性:如有指针接收者,最好使用指针接收者
- 看下面两个函数
//值接收者
func (node Node) setvalue(value int){
node.Value = value
}
//指针接收者
func (node *Node) setvalue(value int){
node.Value = value
}
第一个函数是值传递不会真正的修改node.Value的值
第二个函数是指针传参会真正的改变node.Value的值
- nil指针也可以调用方法
介绍了这些方法,现在来实现一个树的中序遍历
func (node *Node) traverse(){if node == nil{return
}
node.left.traverse()
fmt.Println(node)
node.right.traverse()
}
下面来调用:
func mian(){var root Node //创建一个根节点 //root := Node{}
root = Node{Value: 3}
//root.left = &Node{} 没有给值时,则默认值为0 //left和right在结构体中都是指针需要加&
root.Left = &Node{0, nil, nil}
root.Right = &Node{5, nil, nil}
root.Left.Right = &Node{2, nil, nil}
root.Rigth.Left = new(Node) //先new再赋值
root.Right.Left = &Node{0, nil, nil}
root.traverse()
}
输出为:
0 2 3 0 5
二、包和封装
1.封装
- 名字一般使用CamelCase
- 首字母大写:public
- 首字母小写:private
2.包
包就是每一个程序中的package
- 每一个目录只有一个包
- main包包含了可执行入口
- 为机构定义的方法必须放在同一个包内
- 可以是不同的文件
下面是一个包:
对于这个包而言它我们要做可执行程序main中使用,就应该将构造体及其方法的首字母写成大写
package tree
import "fmt"
//结构体
type Node struct{
Value int
Right, Left *Node
}
//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func GreatNode(value int) *Node{return &Node{Value:value}
}
func (node *Node)Setvalue(value int){
node.Value = value
}
//遍历树
func (node *Node) Traveser(){if node == nil {return}
node.Left.Traveser()
fmt.Println(node.Value)
node.RightTraveser()
}
在可执行的main中:
我们引入的包名叫:tree
所以需要在构造方法中都添加tree
package mian
import ("awesomeProject/tree""fmt")
func mian(){var root tree.Node //创建一个根节点 //root := tree.Node{}
root = tree.Node{Value: 3}
//root.left = &Node{} 没有给值时,则默认值为0 //left和right在结构体中都是指针需要加&
root.Left = &tree.Node{0, nil, nil}
root.Right = &tree.Node{5, nil, nil}
root.Left.Right = &tree.Node{2, nil, nil}
root.Rigth.Left = new(tree.Node) //先new再赋值
root.Right.Left = &tree.Node{0, nil, nil}
//调用tree中中序遍历方法
root.Traveser()
root.Right.Right = new(tree.Node)
//调用tree中Setvalue()方法
root.Right.Right.Stevalue(100)
fmt.Println(root.Right.Right.Value)
三、扩展已有类型
如果一个包是别人写的,那么我们想要扩展它怎么办?
在c++及Java里面我们会使用继承来扩展,相对较麻烦,所以在Go中干脆就取消了继承,在Go中扩展系统类型及别人的类型有两种方法:
- 使用组合
- 定义别名
- 使用内嵌
1.使用组合:
tree包:
package tree
import "fmt"
//结构体
type Node struct{
Value int
Right, Left *Node
}
//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func GreatNode(value int) *Node{return &Node{Value:value}
}
func (node *Node)Setvalue(value int){
node.Value = value
}
//遍历树
func (node *Node) Traveser(){if node == nil {return}
node.Left.Traveser()
fmt.Println(node.Value)
node.RightTraveser()
}
我们在别人写tree包中使用中序遍历,那么我们现在还需要使用后续遍历,而现在tree包中没有后序遍历,此时就需要我们自己来实现
现在我们在main中写一个结构体:
type mytreeNode struct{
node *tree.Node //node为tree中的Node类型
}
程序main:
package main
import ("awesomeProject/tree""fmt")
type mytreeNode struct{
node *tree.Node
}
//有接收者的后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil || myNode.node == nil {return}
//使用组合,我们构建了mytreeNode的结构体,必须使用mytreeNode{}
left := mytreeNode{myNode.node.Left}
right := mytreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
fmt.Print(myNode.node)
}
//普通后序遍历
func postOrder1(myNode *mytreeNode){if myNode == nil || myNode.node == nil{return}
left := mytreeNode{myNode.node.Left}
right := mytreeNode{myNode.node.Right}
postOrder1(&left)postOrder1(&right)
fmt.Print(myNode.node)
}
func main() {var root tree.Node
root = tree.Node{Value: 0}
root.Left = &tree.Node{1, nil, nil}
root.Right = &tree.Node{2, nil, nil}
root.Left.Left = &tree.Node{3, nil, nil}
root.Left.Right = &tree.Node{4, nil, nil}
root.Right.Left = new(tree.Node)
root.Right.Left = &tree.Node{5, nil, nil}
fmt.Println()//接收者函数
myRoot := mytreeNode{&root}
myRoot.postOrder()
//普通函数postOrder1(&mytreeNode{&root})//postOrder1(myRoot)
}
最后输出:
341520 341520
2.使用别名
例如:
现在我们另一个目录下使用别名的方式来编写一个队列(Queue)
同样,我们将队列写在包里
//定义别名
type Queue []int
完整的queue包:
package queue
type Queue []int
//加入元素
func (q *Queue) Push(v int){*q = append(*q, v)
}
//移出首元素
func (q *Queue) Pop() int {
head := (*q)[0]*q = (*q)[1:]return head
}
//判断队列是否为空
func (q *Queue)IsImpty() bool{return len((*q)) == 0
}
在main中调用包:
package main
import ("awesomeProject/tree/queue""fmt")
func main() {
q := queue.Queue{1}
q.Push(2)
q.Push(3)
q.Pop()
fmt.Println(q)
fmt.Println(q.IsImpty())
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsImpty())
最后输出为:
[2 3]
false
2
3
true
3.内嵌(emdedding)
内嵌其实也就是语法糖,它能使我们的代码量减少,什么是内嵌呢?看下面:
type mytreeNode struct{
node *tree.Node
}
type mytreeNode struct{*tree.Node //Embedding 内嵌
}
很容易看出:内嵌把node给省略了
使用内嵌后:
//后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil || myNode.node == nil {return}
left := mytreeNode{myNode.node.Left}
right := mytreeNode{myNode.node.Right}
left.postOrder()
right.postOrder()
fmt.Print(myNode.node.Value)
}
上面这段代码就会变成这样:
所以我们需要将.node
改成.Node
变成tree.Node后面的.Node; 也可以将.node直接扔掉变成:
/后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil {return}
left := mytreeNode{myNode.Left}
right := mytreeNode{myNode.Right}
left.postOrder()
right.postOrder()
fmt.Print(myNode.Value)
}
可选择:
还可以将main中的
func main() {var root tree.Node
root = tree.Node{Value: 0}
root.Left = &tree.Node{1, nil, nil}
root.Right = &tree.Node{2, nil, nil}
root.Left.Left = &tree.Node{3, nil, nil}
root.Left.Right = &tree.Node{4, nil, nil}
root.Right.Left = new(tree.Node)
root.Right.Left = &tree.Node{5, nil, nil}
改成:
func main() {//var root tree.Node// root = tree.Node{Value: 0}
root := mytreeNode{&tree.Node{Value: 0}}
root.Left = &tree.Node{1, nil, nil}
root.Right = &tree.Node{2, nil, nil}
root.Left.Left = &tree.Node{3, nil, nil}
root.Left.Right = &tree.Node{4, nil, nil}
root.Right.Left = new(tree.Node)
root.Right.Left = &tree.Node{5, nil, nil}
//这样就可直接将root值为参数了
root.postOrder()
postOrder1(&root)