【跟着咱们学Golang】基础结构

鉴于上篇文章咱们已经讲过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

上节说到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

package的存在,是为了解决文件过多而形成的絮乱等问题,太多的文件都放在项目根目录下看着老是让人以为不舒服,对开发、后期维护等都是问题。

做为代码结构化方式之一, 每一个Go程序都有package的概念。

Go语法规定

  1. 每一个Go文件的第一行(不包含注释)都必须是package的定义
  2. 可执行的程序package定义必须是main
  3. 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     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
print 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/...

相关文章
相关标签/搜索