鉴于上篇文章咱们已经讲过Go语言环境的安装,如今咱们已经有了一个能够运行Go程序的环境,并且,咱们还运行了'Hello World'跑出了咱们的第一个Go程序。
这节咱们就以'Hello World为例,讲解Go的基础结构,详细的解释一下Hello World中的每一行都表明了什么。mysql
Go语言是一门静态语言,在编译前都会对代码进行严格的语法校验,若是语法错误是编译不过去的,因此基础结构是很是重要的一个环节。git
<!--more-->github
相似Java中的package、class、interface、函数、常量和变量等,Go也有package、struct、interface、func、常量、变量等内容。golang
struct相似Java中的class,是Go中的基本结构题。
interface也相似Java中的接口,可定义函数以供其余struct或func实现(可能不太好理解,后面会讲)。sql
这里咱们按照由外而内,从上至下的的顺序进行讲解。数据库
上节说到GOPATH指定了存放项目相关的文件路径,下面包含'bin'、'pkg'、'src'三个目录。编程
1.src 存放项目源代码json
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的存在,是为了解决文件过多而形成的絮乱等问题,太多的文件都放在项目根目录下看着老是让人以为不舒服,对开发、后期维护等都是问题。
做为代码结构化方式之一, 每一个Go程序都有package的概念。
Go语法规定
以下面的代码指定该文件属于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"这个用户全部。
在引用某个项目以前,须要先获取其源码,将其放置到$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
的方式进行获取。
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中,有
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 string age int classId 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.Logger func init(){ log = logrus.New() } func main(){ log.Println("hello world") }
类型的内容仍是不少的,不一样的类型不管是在定义上仍是使用上等都有不一样的语境,后续会专门对其进行介绍。今天先介绍一下类型的定义。
Go中,不管是常量仍是变量等的定义语法都是同样的。
常量的定义使用 const 关键字,支持隐式的定义,也能够进行多个常量的同时定义。
const PI float = 3.1415926 //显式定义 const SIZE = 10 //隐式定义 //多个常量同时定义 const ( LENGTH = 20 WIDTH = 15 HEIGHT = 20 ) //另外一种写法的常量同时定义 const ADDRESS, STREET = "北京市朝阳区望京SOHO", "望京街1号"
变量的定义使用 var 关键字,一样支持隐式的定义和多个变量的同时定义
var word = "hello" var size int = 10 var ( length = 20 width = 15 height = 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下的DebugLevel log.Out = os.Stdout log.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的函数进行打印长宽高和size var 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
版权归做者全部,任何形式转载请联系做者。
做者:搜云库技术团队
出处:https://gostack.souyunku.com/...