Go面向对象中仅仅支持封装,不支持继承和多态;
Go语言没有class,只有struct;
在结构体struct中无论地址还是结构本身,一律使用 . 来访问成员;
1. 方法一:只定义,初始化默认值空值
type treeNode struct {
value int
left, right *treeNode
}
func main(){
var node treeNode
fmt.Println(node)
var node2 *treeNode
fmt.Println(node2)
}
{
0 <nil> <nil>}
<nil>
注
通过var定义结构体变量,会给结构体中每个成员变量开空间,并赋值为默认值;
通过var定义结构体指针 即如上述指针node2,没有指向一个实际存在的内存。这样的指针变量只会占用 1 个指针的存储空间,也就是一个机器字的内存大小。结构体中的变量并没有分配具体的空间,这就是nil结构体指针。
2. 方法二:边定义边初始化
var 结构体变量名 结构体类型名 = { 值 }
结构体变量名 := 结构体类型名 { 值 }
type treeNode struct {
value int
left, right *treeNode
}
func main(){
root := treeNode{
value: 3} // 边定义边初始化自己值
fmt.Println(root)
root.left = &treeNode{
}
root.right = &treeNode{
5, nil, nil}
fmt.Println(root)
root1 := treeNode{
} // 效果同方法一
var root2 treeNode= treeNode{
5, nil, nil} // 对每个成员变量赋值,要么全不赋值,要么全赋值(包括赋值默认值)
fmt.Println(root1, root2)
}
{
3 <nil> <nil>}
{
3 0xc00009c060 0xc00009c080}
{
0 <nil> <nil>} {
5 <nil> <nil>}
3. 使用内建函数new()
type treeNode struct {
value int
left, right *treeNode
}
func main(){
root3 := new(treeNode) //内建函数new()创建 空的treeNode
fmt.Println(root3)
}
&{
0 <nil> <nil>}
4. 使用工厂函数构建Struct
// 自定义工厂函数
func createNode(value int) *treeNode {
// 返回是指针,也可以定义返回类型为结构体类型,而非结构体类型 针
return &treeNode{
value: value} // 返回局部变量地址
// 这里注意
// 在C++中 函数返回局部变量没有什么问题,但返回局部变量的地址会出现错误
// 返回局部变量地址后,函数退栈之后,局部变量消失, 指针将指向未知区域,所以出现问题
// 但在Go中函数返回局部变量地址没有问题
}
func main() {
root := treeNode{
value : 5}
root.right = createNode(3)
root.left = createNode(4)
fmt.Println(root, root.right, root.left)
}
{
5 0xc00009c200 0xc00009c1e0} &{
3 <nil> <nil>} &{
4 <nil> <nil>}
注
go语言中的每一个变量,它的生命周期和引用它的周期一样长。该语言的语义和实现时选择的存储位置,是没有关系的;
关于Golang 中的变量是否分配在堆上还是栈上,变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系;
如果可能,Golang 编译器会将函数的局部变量分配到函数栈帧(stack frame)上;
如果编译器不能确保变量在函数 return 之后不再被引用,编译器就会将变量分配到堆上;
如果一个局部变量非常大,那么它也应该被分配到堆上而不是栈上。
5. 此外可以将struct作为切片Slice的内建类型
type treeNode struct {
value int
left, right *treeNode
}
func main(){
// 在slice中定义
fmt.Println("-------define struct Slice-------")
nodes := []treeNode {
{
value: 3},
{
},
{
6, nil, &root},
}
fmt.Println(nodes)
}
-------define struct Slice-------
[{
3 <nil> <nil>} {
0 <nil> <nil>} {
6 <nil> 0xc00009c020}]
1. 关于普通函数和接收者函数
显示定义和命名方法接收者(在其他语言中可以使用this、self指针定义方法,go中没有,定义方法必须取名);
定义结构体的方法常用接收者函数,如下例中print1,将参数写在方法前,使用 结构体名 . 方法名 进行调用。
type treeNode struct {
value int
left, right *treeNode
}
// 输出节点的value值
func (node treeNode) print1() {
// 配套使用方法是 node.print1()
// print1()的接收者为node treeNode,可以把这种方法使用时类似java类中的内部函数
fmt.Println(node.value)
}
// 输出节点的value值
func print2(node treeNode) {
// 配套使用方法是 print2(node)
// 不同函数方法,node treeNode作为值传递的参数送入函数中并输出
fmt.Println(node.value)
}
func main() {
node := treeNode{
value : 5}
node.print1()
print2(node)
}
5
5
2. 关于值接收者和指针接收者
值接收者和指针接收者是指接收者的类型不同,一个是结构体,另外一个是结构体指针
可以使用指针作为方法接收者,只有使用指针才可以改变结果内容;
值接收者是go语言特有;
type treeNode struct {
value int
left, right *treeNode
}
// 输出节点的value值
// 值接收者
func (node treeNode) print() {
// 配套使用方法是 node.print()
// print()的接收者为node treeNode,可以把这种方法使用时类似java类中的内部函数
fmt.Println(node.value)
}
// 修改节点的value值
// 指针接收者
func (node *treeNode) setValue(value int) {
// go中函数参数一般是值传递,这里需要通过指针进行指针传递
node.value = value
}
// 修改节点的value值
// 值接收者
func (node treeNode) setValue_non(value int) {
// go中函数参数一般是值传递,这里需要通过指针进行指针传递
node.value = value
}
func main() {
node := treeNode{
value : 5}
node.left = &treeNode{
value: 4} // 将地址赋值给指针
node.left.print()
node.left.setValue_non(6)
node.left.print()
node.left.setValue(9)
node.left.print()
}
4
4
9
上述的案例中print()和setValue_non()是值接受者,而setValue()是指针接收者;
在关于修改node节点中value的值情况中,值接受者setValue_non()无法修改,指针接收者setValue()能修改;
此外关于值接收者和指针接收者,其实Go在编译的时候有一个隐式转换,将其转换为正确的接收者类型;
上述中root.left是指针,setValue()作为其的指针接收者,收到是其的地址;
print()和setValue_non()作为其的值接收者,Go会自动获取指针指向地址空间的值。收到是隐式转化后的值传递的参数;
Go的隐式转化保证了值\指针接受者均可接受值\指针;
使用指针接收者的情况:
要改变内容必须使用指针接收者;
结构体过大也考虑使用指针接收者;
一致性:建议如果编写代码中有使用指针接收者,最后都使用指针接收者;
3. 关于nil结构体指针调用方法
nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量,并非是关键字;
nil是一个合法的接收者,可以调用结构体方法,类似函数允许nil指针作为实参,方法的接收者允许是nil指针;
nil 结构体是指结构体指针变量没有指向一个实际存在的内存。这样的指针变量只会占用 1 个指针的存储空间,也就是一个机器字的内存大小。
上述的nil特征让我们无需在每次调用方法前判断指针是否为nil;
但在一些结构体中方法,需要输出或修改结构体成员变量时,需要判断接收者是否为nil,如果是nil,报错并return,由于nil中结构体成员变量未分配空间,不存在。如果在方法中没有判断接收者是否为nil,代码在运行时直接panic出错;
type treeNode struct {
value int
left, right *treeNode
}
// 输出节点的value值
// 值接收者
func (node treeNode) print() {
// 配套使用方法是 node.print()
fmt.Println(node.value)
}
// 修改节点的value值
// 指针接收者
func (node *treeNode) setValue(value int) {
if node == nil {
fmt.Println("setting value to nil node. Ignored!")
return // 这里return必须有,其作用是当node == nil时,终止函数运行,直接返回,避免执行node.value = value
// 若执行node.value = value,由于node是nil,不存在value变量,运行报错
}
node.value = value
}
func main() {
var node *treeNode
fmt.Println(node)
// node.print() // 运行报错,不存在value变量,这里需不要考虑node是否为指针,go会隐式转化
// 这里如果定义为结构体变量var node treeNode,而非是结构体指针变量var node *treeNode,运行node.print()不会出错
// 是因为结构体变量定义结果是{0 },存在value成员变量并且赋值为0,而定义结构体指针变量结果是,仅仅给指针开辟内存,不存在value等变量
// 为nil
node.setValue(99)
// node.print() //运行报错
// 实例化后
node = &treeNode{
66,nil,nil}
// node = &treeNode{value: 66} // 效果相同
node.setValue(999)
node.print()
}
<nil>
setting value to nil node. Ignored!
999
4. 二叉树中序变量例子
type treeNode struct {
value int
left, right *treeNode
}
// 输出节点的value值
// 值接收者
func (node treeNode) print() {
// 配套使用方法是 node.print()
fmt.Println(node.value)
}
// 自定义工厂函数
func createNode(value int) *treeNode {
return &treeNode{
value: value} // 返回局部变量地址
// 这里注意
// 在C++中 函数返回局部变量没有什么问题,但返回局部变量的地址会出现错误
// 返回局部变量地址后,函数退栈之后,局部变量消失, 指针将指向未知区域,所以出现问题
// 但在Go中函数返回局部变量地址没有问题
}
// 中序遍历
func (node *treeNode) traverse(){
if node == nil {
return
}
node.left.traverse()
node.print()
node.right.traverse()
}
func main() {
root := treeNode{
0, nil,nil}
root.left = createNode(1)
root.right = createNode(2)
root.left.left = createNode(3)
root.left.right = &treeNode{
value: 4}
root.right.left = &treeNode{
5, nil,nil}
root.right.right = createNode(6)
root.traverse()
}
3
1
4
0
5
2
6
每个文件夹(目录)一个包;
一个文件夹内也只能有一个包,但是一个文件夹可以有多个文件;
文件名可以和包名不同;
特殊的包:main包,其包含可执行入口;
如果文件中包含main()函数,则这文件只能在main包中(package main),否则将在当前文件夹(目录)下创建一个文件夹,创建新的go文件包含mian()函数,将文件夹放入main包中;
为结构体定义的方法必须放在同一个包内,可以是在不同文件中;
引用其他包的结构体或方法时,是通过包名.结构体名/方法名 来实现引用;
目标是对结构体的方法进行封装;
封装函数的名字一般使用CamelCase,即骆驼拼写法,单词首字母大写,多个单词拼接在一起;
首字母大写表示函数、结构体、常量、变量等所有定义是public
首字母小写表示函数、结构体、常量、变量等所有定义是private
package tree
import "fmt"
type Node struct {
Value int
Left, Right *Node
}
// 输出节点的Value值
// 值接收者
func (node Node) Print() {
// 配套使用方法是 node.Print()
fmt.Println(node.Value)
}
// 自定义工厂函数
func CreateNode(Value int) *Node {
return &Node{
Value: Value} // 返回局部变量地址
// 这里注意
// 在C++中 函数返回局部变量没有什么问题,但返回局部变量的地址会出现错误
// 返回局部变量地址后,函数退栈之后,局部变量消失, 指针将指向未知区域,所以出现问题
// 但在Go中函数返回局部变量地址没有问题
}
package tree
// 中序遍历
func (node *Node) Traverse(){
if node == nil {
return
}
node.Left.Traverse()
node.Print()
node.Right.Traverse()
}
package main
import "xxx/tree" // Node所在的绝对路径
func main() {
root := tree.Node{
0, nil,nil}
root.Left = tree.CreateNode(1)
root.Right = tree.CreateNode(2)
root.Left.Left = tree.CreateNode(3)
root.Left.Right = &tree.Node{
Value: 4}
root.Right.Left = &tree.Node{
5, nil,nil}
root.Right.Right = tree.CreateNode(6)
fmt.Println("------inOrder------")
root.Traverse()
}
------inOrder------
3
1
4
0
5
2
6
使用组合方法在封装和包的例子的基础上将Node结构体组合一起,在这里只有Node一个 已知的类型。
工作内容:定义一个新的结构体组合其他类型,定义后序遍历的方法
type myNode struct {
// 只用组合方法扩充系统类型或别类型
node *tree.Node
}
func (myyNode *myNode) postOrder() {
if myyNode == nil || myyNode.node == nil {
return
}
left := myNode{
myyNode.node.Left}
right := myNode{
myyNode.node.Right}
left.postOrder()
right.postOrder()
myyNode.node.Print()
}
// 在main()方法中的末尾添加
fmt.Println("------postOrder------")
node := myNode{
&root}
node.postOrder()
------postOrder------
3
4
1
5
6
2
0
GOROOT就是go的安装路径;
GOPATH是作为编译后二进制的存放目的地和import包时的搜索路径(也就是你的工作目录, 你可以在src下创建你自己的go源文件和包);
GOPATH是一个环境变量,可以指向多个目录;
在unix,linux系统GOPATH默认在~/go,在windows系统中默认在%USERPROFILE
官方推荐:所有项目和第三方库都放在同一GOPATH下;
但可以将每个项目放在不同的GOPATH下;
关于获取Go的第三方库:使用go get命令,对于无法下载的包可以在github上下载或者通过 gopm工具下载;
安装gopm
go get github.com/gpmgo/gopm
go build来编译
go install 产生pkg文件和可执行文件
go run直接编译 运行
GOPATH目录下有三个文件夹,分别是src,pkg,bin;
src文件夹下存在各种项目;
pkg文件夹是src中各种项目build的中间文件;
bin文件夹下存放是项目的可执行文件;