【跟着我们学Golang】基础结构
鉴于上篇文章我们已经讲过Go语言环境的安装,现在我们已经有了一个可以运行Go程序的环境,而且,我们还运行了'Hello World'跑出了我们的第一个Go程序。 这节我们就以'Hello World为例,讲解Go的基础结构,详细的解释一下Hello World中的每一行都代表了什么。
Go语言是一门静态语言,在编译前都会对代码进行严格的语法校验,如果语法错误是编译不过去的,所以基础结构是非常重要的一个环节。
类似Java中的package、class、interface、函数、常量和变量等,Go也有package、struct、interface、func、常量、变量等内容。
struct类似Java中的class,是Go中的基本结构题。 interface也类似Java中的接口,可定义函数以供其他struct或func实现(可能不太好理解,后面会讲)。
这里我们按照由外而内,从上至下的的顺序进行讲解。
GOPATH
上节说到GOPATH指定了存放项目相关的文件路径,下面包含'bin'、'pkg'、'src'三个目录。
1.src 存放项目源代码
2.pkg 存放编译后生成的文件
3.bin 存放编译后生成的可执行文件
目录结构如下
GOPATH\_ src\_ projectA\_ projectB\_ pkg\_ projectA\_ projectB\_ bin\_ commandA\_ commandB
复制代码
src目录是我们用的最多的,因为我们所有的项目都会放到这个目录中。后续项目的引用也是相对于该目录。
文件名
一个Go文件,我们对其的第一个认识,就是其的后缀,Go文件以*'.go'*结尾(Windows用户一定要文件后缀的问题),这个很容易理解。 不能以数字开头、不能包含运算符、不能使用Go的关键字等对比其他语言也非常容易理解。 有效的标识符必须以字母(可以使用任何 UTF-8 编码的字符或 _)开头加上任意个数字或字符,如:'hello_world.go'、'router.go'、'base58.go'等。
Go天然支持UTF8
不过在这里有一些细节需要注意,就是Go文件在命名的时候,跟其他语言不太一样。 Go文件都是小写字母命名(虽然大写也支持,但是大写文件名并不规范),如果需要多个单词进行拼接,那单词之间以_
下划线进行连接。 特别是编写测试用例和多平台支持时对Go文件的命名。 如:'math_test.go'、'cpu_arm.go'、'cpu_x86.go'等。
命名规范
Go可以说是非常干净整洁的代码,所以Go的命名非常简洁并有意义。虽然支持大小写混写和带下划线的命名方式,但是这样真的是非常的不规范,Go也不推荐这样做。Go更喜欢使用驼峰式的命名方式。如'BlockHeight'、'txHash'这样的定义。另外Go的定义可以直接通过'package.Xxx'这样的方式进行使用,所以也不推荐使用GetXXX这样的定义。
package
package的存在,是为了解决文件过多而造成的絮乱等问题,太多的文件都放在项目根目录下看着总是让人觉得不舒服,对开发、后期维护等都是问题。
作为代码结构化方式之一, 每个Go程序都有package的概念。
Go语法规定
- 每个Go文件的第一行(不包含注释)都必须是package的定义
- 可执行的程序package定义必须是main
- package默认采用当前文件夹的名字,不采用类似Java中package的级联定义
如下面的代码指定该文件属于learning_go这个package。
package learning_go...复制代码
Go对package的定义并不严格,在文件夹下的Go文件可以不使用文件夹名称作为package的定义(默认使用),但是同一个文件夹下的所有Go文件必须使用同一个package定义,这个是严格要求的。
tips: package的定义均应该采用小写字母,所以文件夹的定义也应该都是小写字母。
为了更好的区分不同的项目,Go定义的项目结构中都包含开源站点的信息,如github、gitee等开源站点中的所有开源Go项目都可以直接拿来使用。
Go的背后是所有开源世界
拿在GitHub下面创建的Go项目gosample项目为例。其项目路径为:"github.com/souyunkutech/gosample"。"github.com"表示其开源站点信息。"souyunkutech/gosample"就是项目的名称。 文件的路径就对应为"$GOPATH/src/github.com/souyunkutech/gosample"。
"souyunkutech"表示了gosample这个项目属于"souyunkutech"这个用户所有。
package的获取
在引用某个项目之前,需要先获取其源码,将其放置到$GOPATH/src
下,这样我们才能对其进行引用从而正确的进行编译。
Go获取源码可以手动将源码根据其相对路径放到正确的路径下,也可以通过go get path
的方式自动获取。
如获取'gosample'项目,可以通过git clone
的方式下载到$GOPATH/src/github.com/souyunkutech/
也可以通过go get github.com/souyunkutech/gosamp
的方式进行获取,go会自己把项目方到$GOPATH/src/github.com/sirupsen/
目录下。
如获取Go语言中一个非常知名的第三方日志组件logrus,可以将其通过git clone
的方式下载到$GOPATH/src/github.com/sirupsen/
也可以通过go get github.com/sirupsen/logrus
的方式进行获取。
package的引用
import关键字的作用就是package的引用。作为外部引用的最小单位,Go以package为基础,不像Java那样,以对象为基础,供其他程序进行引用,import引用了某个package那就是引用了这个package的所有(可见的)内容。
语法上需要注意的就是在引用时需要双引号包裹。没有使用过的package引用要不删除,要不定义为隐式引用。否则的话,在运行或者编译程序的时候就会报错:imported and not used:...
如HelloWorld代码中引用的'fmt'就是Go语言内建的程序package。fmt这个package下包含'doc.go'、'format.go'等(这个可以通过IDE的方式进行查看)这些Go文件中所有的(可见的)内容都可以使用。
前面说到import的引用默认都是相对于$GOPATH/src
目录的。所以我们要想使用某个开源项目,就需要使用其相对于GOPATH/src
的相对路径进行引用。(Go系统内建的项目除外)
import引用的语法有两种方式
方式1,默认的引用方式,每个引用单独占一行, 如:
import "fmt"
复制代码
方式2,通过括号将所有的引用写在一个import中,每个引用单独占一行。通过这种方式,Go在格式化的时候也会将所有的引用按照字母的顺序进行排序,所以看起来特别的清晰。如:
import ("fmt""math")
复制代码
比如,logrus项目结构是*'github.com/sirupsen/logrus'*,所以在引用这个日志组件时,就要写成下面这样
import "github.com/sirupsen/logrus"
复制代码
如果只想使用logrus项目中的某一个package的话,可以单引用该package,而不用引用logrus全项目。这也是非常的方便。 比如要使用logrus项目中'hook/syslog/syslog.go',就可以像下面这样写import
import "github.com/sirupsen/logrus/hooks/syslog"
复制代码
Go的引用还支持以文件路径的方式。如'./utils'引用当前目录下的util这个package时,就可以写成下面这样,当然按照项目路径的方式写才是最合适最规范的,并不推荐使用文件路径进行引用。
import "./utils"
复制代码
隐式引用
Go的引用有一个特别另类的支持,那就是隐式引用,需要在引用前面加上_
下划线标识。这种类型的引用一般会发生在加载数据库驱动的时候。如加载MySQL数据库驱动时。因为这些驱动在项目中并不会直接拿来使用,但不引用又不行。所以被称为隐式引用。
import _ "github.com/go-sql-driver/mysql"
复制代码
package在引用的过程需要注意不能同时引用两个相同的项目,即不管项目的站点和项目所属,只要引用的项目package名称相同,都是不被允许的,在编译时会提示'XXX redeclared as imported package name'错误。但隐式引用除外。
import "encoding/json"
import "github.com/gin-gonic/gin/json"//!!! 不允许 !!!
复制代码
但是能不能使用还要看这个package,就是这个package的可见性。
可见性
可见行可以理解为Java 中的私有和公有的意思。以首字母大写的结构体、结构字段、常量、变量、函数都是外部可见的,可以被外部包进行引用。如"fmt"中的"Println"函数,其首字母大写,就是可以被其他package引用的,"fmt"中还有"free"函数,其首字母小写,就是不能被其他package引用。
但是不管对外部package是否可见,同一个package下的所有定义,都是可见并可以引用、使用的。
函数
在Go语言中,函数的使用是最频繁的,毕竟要将代码写的清晰易懂嘛,而且好像所有的编程语言都有函数的概念(目前没听说过没有函数概念的语言)
在Go中,函数的定义支持多个参数,同样也支持多个返回值(比Java要厉害哦),多个参数和多个返回值之间使用英文逗号进行分隔。
同样与Java类似,Go的函数体使用*'{}'*大括号进行包裹,但是,Go的定义更为严格,Go要求左大括号'{'必须与函数定义同行,否则就会报错:'build-error: syntax error: unexpected semicolon or newline before {'。
- 多个参数的函数定义
func methodName(param1 type, param type2, param type3){...
}//简写方式,可以将相同类型的参数并列定义
func methodName(param1, param2 type, param3 type2) {...
}
复制代码
- 有返回值的函数定义
函数返回值的定义可以只定义返回类型,也可以直接定义返回的对象。
定义了返回的对象后,就可以在函数内部直接使用该定义,避免了在函数中多次定义变量的问题。同时,在返回的时候也可以单单使用一个'return'关键字来代替 'return flag'这样的返回语句。 需要注意返回值要不都定义返回对象,要不都不定义,Go不允许只定义部分函数返回对象。
//最简单的函数定义
func methodName(){...
}//仅定义返回的类型
func methodName() bool{...return false
}// 定义返回的对象+类型
func methodName() (flag bool){...return
}//定义多个返回类型
func methodName()(bool, int) {...return false, 0
}//定义多个返回对象+类型
func methodName()(flag bool, index int) {...return
}// !!! 不允许的定义 !!!
func methodName()(bool, index int){...return
}// !!! 不允许的定义 !!!
func methodName()
{...return
}
复制代码
在Go中有两个特别的函数,'main'函数和'init'函数。
'main'函数是程序的主入口,package main必须包含该函数,否则的话是无法运行的。在其他的package中,该函数之能是一个普通的函数。这点要特别注意。 它的定义如下,不带也不能带任何的参数和返回值
func main(){...
}
复制代码
'init'函数是package的初始化函数,会在执行'main'函数之前执行。
同一个package中可以包含多个init函数,但是多个init函数的执行顺序Go并没有给出明确的定义,而且对于后期的维护也不方便,所以一个package中尽可能的只定义一个init函数比较合适。
多个package中的init函数,按照被import的导入顺序进行执行。先导入的package,其init函数先执行。如果多个package同时引用一个package,那其也只会被导入一次,其init函数也只会执行一次。
它的定义和main函数相同,也是不带也不能带任何的参数和返回值
func init(){...
}
复制代码
数据类型
在Go中,有
- 基本类型:int(整型)、float(浮点型)、 bool(布尔型)、 string(字符串类型)
- 集合类型:array(数组)、 slice(切片)、 map(map)、 channel(通道)
- 自定义类型: struct(结构体)、func(函数)等
- 指针类型
Go的集合类型并没有Java那么复杂,什么线程安全的集合、有序的集合都没有(全都需要自己实现^_^)!
array和slice这两种集合都类似Java中的数组,他们无论在使用上都是一样的。以至于会经常忘记他们两个到底哪里不一样。其实真的是非常的细节,array是有长度的,slice是没有长度的。他们的区别就是这么小。
channel是Go中特有的一种集合,是Go实现并发的最核心的内容。与Unix中的管道也是非常的类似。
struct结构体简单理解就是对象了,可以自己定义自己需要的对象进行结构化的使用。和Java中的class不同,Java中函数是写在class中的,在Go中,struct的函数是要写在struct外的。 结构体定义需要使用type关键字结合struct来定义。struct前的字段为新的结构体的名称。内部字段可以使用大写字母开头设置成对外部可见的,也可以使用小写字母开头设置成对外部不可见。格式如下:
type Student struct {Name stringage intclassId int
}func main(){var student Student = Student{Name:"小明",age: 18, classId: 1}fmt.Printf("学生信息: 学生姓名: %s, 学生年龄: %d, 学生班级号: %d ", student.Name, student.age, student.classId)
}复制代码
针对结构体,Go还支持如下的定义
type MyInt int复制代码
这是自定义的int类型,这样做的目的是,MyInt既包含了现有int的特性,还可以在其基础上添加自己所需要的函数。这就涉及到结构体的高级语法了,后续我们会详细的介绍。
Go的作者之前设计过C语言,或许这是Go同样有指针类型的原因吧,不过讲真的,Go中的指针比C中的指针要好理解的多。在定义时简单在类型前面加上*
星号就行,使用时正常使用,不需要加星号。对其的赋值需要使用&
将其地址复制给该指针字段。
var log *logrus.Loggerfunc init(){log = logrus.New()
}func main(){log.Println("hello world")
}复制代码
类型的内容还是很多的,不同的类型无论是在定义上还是使用上等都有不同的语境,后续会专门对其进行介绍。今天先介绍一下类型的定义。
Go中,无论是常量还是变量等的定义语法都是一样的。
常量的定义使用 const 关键字,支持隐式的定义,也可以进行多个常量的同时定义。
const PI float = 3.1415926 //显式定义
const SIZE = 10 //隐式定义
//多个常量同时定义
const (LENGTH = 20WIDTH = 15HEIGHT = 20
)//另一种写法的常量同时定义
const ADDRESS, STREET = "北京市朝阳区望京SOHO", "望京街1号"
复制代码
变量的定义使用 var 关键字,同样支持隐式的定义和多个变量的同时定义
var word = "hello"
var size int = 10var (length = 20width = 15height = 20
)var address, street = "北京市朝阳区望京SOHO", "望京街1号"
复制代码
Go还支持在定义变量时把声明和赋值两步操作结合成一步来做。如下:
size := length * width * height
复制代码
这样省了定义变量这一步,代码更简洁,看着也更清晰,是比较推荐的方法。(常量不能这样用哦)
关键字及保留字
为了保证Go语言的简洁,关键字在设计时也是非常的少,只有25个。
break | case | chan | const | continue |
---|---|---|---|---|
default | defer | else | fallthrough | for |
func | go | goto | if | import |
interface | map | package | range | return |
select | struct | switch | type | var |
当然,除了关键字,Go还保留了一部分基本类型的名称、内置函数的名称作为标识符,共计36个。
append | bool | byte | cap | close | complex |
---|---|---|---|---|---|
complex64 | complex128 | copy | false | float32 | float64 |
imag | int | int8 | int16 | int32 | int64 |
iota | len | make | new | nil | panic |
println | real | recover | string | true | |
uint16 | uint32 | uint64 | uint | uint8 | uintptr |
另外,_
下划线也是一个特殊的标识符,被称为空白标识符。所以,他可以像其他标识符那样接收变量的声明和赋值。但他的作用比较特殊,用来丢弃那些不想要的赋值,所以,使用_
下划线来声明的值,在后续的代码中是无法使用的,当然也不能再付给其他值,也不能进行计算。这些变量也统一被称为匿名变量。
总结
到这里,本篇内容讲解了Go中的package、func以及类型三部分的内容。也就是这三部分内容,构成了Go语言的基础结构。到这,咱们也能对 Hello World的代码有了一个清晰的认识。也可以尝试着动手写一写简单的例子来加深印象。下面是使用变量、常量、以及函数等基础结构来实现的程序,可以参考来理解。源码可以通过'github.com/souyunkutech/gosample'获取。
//源码路径:github.com/souyunkutech/gosample/chapter3/main.go
package main //定义package为main才能执行下面的main函数,因为main函数只能在package main 中执行//简写版的import导入依赖的项目
import ("fmt" //使用其下的Println函数"os" //使用其下的Stdout常量定义"time" // 使用time包下的时间格式常量定义RFC3339"github.com/sirupsen/logrus" //日志组件"github.com/souyunkutech/gosample/util" //自己写的工具包,下面有自定义的函数统一使用
)//声明log变量是logrus.Logger的指针类型,使用时不需要带指针
var log *logrus.Logger// 初始化函数,先于main函数执行
func init() {log = logrus.New() //使用logrus包下的New()函数进行logrus组件的初始化log.Level = logrus.DebugLevel //将log变量中的Level字段设置为logrus下的DebugLevellog.Out = os.Stdoutlog.Formatter = &logrus.TextFormatter{ //因为log.Formatter被声明为指针类型,所以对其赋值也是需要使用‘&’关键字将其地址赋值给该字段TimestampFormat: time.RFC3339, //使用time包下的RFC3339常量,赋值时如果字段与大括号不在一行需要在赋值后面添加逗号,包括最后一个字段的赋值!!!}
}//定义常量PI
const PI = 3.1415926//定义Student结构体,可以统一使用该结构来生命学生信息
type Student struct {Name string //姓名对外可见(首字母大写)age int //年龄不能随便让人知道,所以对外不可见classId int //班级也是
}//main函数,程序执行的入口
func main() {var hello = "hello world" //定义hello变量,省略了其类型string的声明fmt.Println(hello) //使用fmt包下的Println函数打印hello变量//多个变量的定义和赋值,使用外部函数生成length, width, height := util.RandomShape() //使用其他package的函数//多个变量作为外部函数的参数size := util.CalSize(length, width, height)log.Infof("length=%d, width=%d, height=%d, size=%d", length, width, height, size) //使用日志组件logrus的函数进行打印长宽高和sizevar student = Student{Name: "小明", age: 18, classId: 1} //声明学生信息,最后一个字段的赋值不需要添加逗号log.Debugf("学生信息: 学生姓名: %s, 学生年龄: %d, 学生班级号: %d ", student.Name, student.age, student.classId) //使用日志组件logrus的函数进行打印学生信息
}复制代码
运行结果如下:
hello world
INFO[0000] length=10, width=15, height=20, size=3000
DEBU[0000] 学生信息: 学生姓名: 小明, 学生年龄: 18, 学生班级号: 1
复制代码
如果还有不理解的内容可以通过搜云库技术群进行讨论或者留言,我们都会进行解答。
源码可以通过'github.com/souyunkutech/gosample'获取。
微信公众号
首发微信公众号:Go技术栈,ID:GoStack
版权归作者所有,任何形式转载请联系作者。
作者:搜云库技术团队 出处:gostack.souyunku.com/2019/04/22/…
转载于:https://juejin.im/post/5cbd93825188250a8253d9dc
【跟着我们学Golang】基础结构相关推荐
- 【跟着我们学Golang】之面向对象
万物皆对象.学过Java编程的都知道Java是一门面向对象的语言,它拥有封装.继承和多态的特性.那可不可以说,拥有封装.继承和多态这一特性的语言就是面向对象的语言呢? 仔细想来,也确实是这样的,因为封 ...
- 【跟着我们学Golang】之异常处理
Java中的异常分为Error和Exception来处理,这里也以错误和异常两种,来分别讲一讲Go的异常处理. Go 语言没有类似 Java 或 .NET 中的异常处理机制,虽然可以使用 defer. ...
- 【跟着我们学Golang】Go语言全平台安装
学习Go语言的第一步当然是要先安装Go语言的环境,毕竟,没有这个环境咱们什么都做不了. 但是在安装环境之前咱们先了解一下Go涉及到的环境变量. 这里介绍几个比较常用的Go语言环境变量. GOROOT ...
- 跟着Rocskdb 学 存储引擎:读写链路的代码极致优化
文章目录 1. 读链路 1.1 FileIndexer 1.1.1 LevelDB sst查找实现 1.1.2 Rocksdb FileIndexer实现 1.2 PinnableSlice 减少内存 ...
- 通过示例学 Golang 2020 中文版【翻译完成】
原文:GolangByExample 协议:CC BY-NC-SA 4.0 阶段:机翻(1) 人最大的痛苦就是说一些自己都不相信的话.--燕京学堂鹿会 在线阅读 在线阅读(Gitee) ApacheC ...
- 用python turtle画龙猫_【跟着弹簧学画画】教你一步步画出一只超萌的龙猫来!...
原标题:[跟着弹簧学画画]教你一步步画出一只超萌的龙猫来! 龙猫,日文音译过来是豆豆龙,今天咱们就来画一个吧! 照旧先来感受下动态图↓ 1.先画耳朵,然后画出头部和身体,因为龙猫是圆滚滚的一只 画出胳 ...
- 跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)
学好路更宽,钱多少加班. --小马哥 版本修订 2021.5.19:去除目录 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述 简介 大家好,我是 ...
- 跟着CELL学作图|1.火山图
跟着CELL学作图之火山图 "实践是检验真理的唯一标准." "复现是学习R语言的最好办法." DOI: 10.1016/j.cell.2020.05.032 这 ...
- 跟着Cell学作图 | 12.韦恩图(Vennerable包)
"实践是检验真理的唯一标准." "复现是学习生信的最好办法." 2021.4.12_1 DOI: 10.1016/j.cell.2020.05.032 这篇20 ...
最新文章
- UITextField 键盘
- 用Tableau制作滚动时间轴(下)
- ​CVPR 2020雾天条件下物体检测挑战赛冠军DeepBlueAI团队技术分享
- 修改shell提示符的显示格式
- python基础内容_python基础-python介绍
- 浅谈ASP中Web页面间的数据传递
- 惊了!哆啦A梦里最能打的道具,居然真实存在!还打破了世界纪录,看完跪了....
- 第六十四期:聊聊原子变量、锁、内存屏障那点事
- Redis ZSet 的几种使用场景
- arcade 物理系统_如何使用Python和Arcade库创建2D游戏
- 华为Mate40 Pro最新渲染图曝光:双孔瀑布屏有戏 再度惊艳
- 年轻人刚入社会就进厂打工不丢人
- matlab拟合函数求系数,matlab 求拟合函数的系数
- 运动垫的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- 丙二硫醇/鸟嘌呤(BG)/Mn配合物修饰BODIPY氟化硼二吡咯荧光探针
- linux系统下如何解压RAR文件软件rarforlinux
- AD2020库安装及查找库
- 利用arcgispro将倾斜摄影三维数据OSGB转换为slpk格式
- 时速云发布微服务产品 TMF V5.6.0 全新版本,支持独立部署,简化企业微服务架构
- jionlp:一款解析地址信息的神奇 Python 库