zoukankan      html  css  js  c++  java
  • go基础系列:简介

    1.Go简介

    Go语言是编译型、静态类型的类C的语言,并带有GC(垃圾收集器,garbage collection)。这意味着什么?

    另外,Go是一种非常严格的语言,它几乎总是要求我们"以标准答案去答题",在其它语言可以容忍的不规范编码方式在Go语言中几乎都会抛异常。例如导入了包却没有使用这个包,Go不会去编译它并报错。再例如,定义了一个变量但从来没用过,也会报错。

    初学Go的时候,这可能是件无比的苦恼事情,但习惯了之后,编写出来的程序自然是无比规范的。这也正是Go和不少语言的区别:其它语言编码、调试阶段可能很快,但维护和优化阶段可能会非常长;而Go的编码周期可能稍长,但编码完成后几乎都是足够优化的,维护和优化周期足够短。

    编译型

    编译表示的是将你所写的源代码转换为低层次的语言,例如汇编语言(go采用此底层语言),或者其它中间的语言(如Java、C#编译成字节码)。

    编译型语言可能不太友好,因为编译的过程速度很慢。如果一个程序的编译过程就需要花几分钟甚至几小时,那么程序的版本迭代可能会很难进行下去。编译速度是Go语言的一个主要设计目标,值得庆幸的是,Go的编译速度很快,即便对于习惯于使用解释型语言的人来说,它也还是快。

    编译型语言虽然编译过程慢,但这类语言在运行阶段可能会更快,而且运行时不再需要加载额外的依赖。

    静态类型

    静态语言意味着变量必须要指定数据类型(int,string,bool,[]byte等)。虽然必须指定数据类型,但除了在声明变量的时候显式指定数据类型,也可以让Go自己去推断数据类型(稍后有示例)。

    对于习惯于使用动态型语言的人来说,可能会感觉静态型语言很笨重,事实确实如此。但静态有静态的好处,特别是配合编译操作的时候。

    关于静态和动态数据类型,要说的内容其实很多很多,毕竟对于一门语言来说,数据类型牵一发而动全身,无论是静态、还是动态型语言,都因此而衍生出无数的优、缺点。

    类C型的语言

    当我们说一门语言是类C型(C-like)的语言时,意味着这门语言里有一些语法和特性和C语言是类似的。例如,&&表示布尔的AND,==表示等值比较,数组索引从0开始计算,{...}表示一段代码块,也表示它属于一个作用域范围,等等。

    类C型语言也意味着每行的语句要使用分号";"结束,条件表达式要使用括号包围。但Go语言不采用这两种方式,尽管还是可以使用括号包围条件表达式以改变优先级。例如:

    if name == "malongshuai" {
        print("name rigth!")
    }
    

    一个更复杂一点的条件表达式,使用括号改变优先级:

    if (name == "longshuai" && age > 23) || (name == "xiaofang" && age < 22) {
        print("yeyeye!!!")
    }
    

    GC

    每当创建一个变量后,这个变量都会有其生命周期。例如,函数内部的本地变量将在函数退出的时候消逝。对于非函数内部的变量生命周期,无论是对程序员还是对编译器来说,变量的生命周期都没有那么显而易见。

    没有garbage collection,意味着要让程序员自己来决定变量所占用内存的释放,这是很艰巨的任务,而且很容易出错导致程序崩溃。

    带有GC的语言可以对变量进行跟踪,并且在它们不再被需要的时候自动释放它们。虽然GC带来了一点点的负载,会影响一点点的性能,但对于现在高性能的计算机来说,这点影响相比它带来的优点而言,完全可以将其无视。

    尝试写一个简单的Go程序

    按照国际管理,每一门语言总是以hello world开篇。这里就算了,因为我有我的惯例。

    先安装Go,so easy...

    目前还没有必要涉及Go的工作空间,所以随意找个地方创建一个test.go文件,内容如下:

    package main
    
    func main() {
        println("Let's Go")
    }
    

    然后运行:

    go run test.go
    

    显然,它将输出Let's Go。但是Go的编译过程呢?go run命令同时进行了编译和运行两个过程:它将使用一个临时目录保存构建的程序,然后执行它,最后自动清理构建出来的临时程序。

    可以使用go run --work查看下具体情况:

    $ go run --work test.go
    WORK=/tmp/go-build267589647
    Let's Go
    

    构建的临时目录位于/tmp/go-buildXXXX中(我这是Linux),在此目录下会有一个二进制程序(对于Windows则是.exe文件):

    $ tree /tmp/go-build267589647/
    /tmp/go-build267589647/
    ├── command-line-arguments
    │   └── _obj
    │       └── exe
    │           └── test    # 这是可执行二进制程序
    └── command-line-arguments.a
    

    那个test文件就是编译后得到的二进制程序,可以直接用来执行:

    $ /tmp/go-build267589647/command-line-arguments/_obj/exe/test 
    Let's Go
    

    如果要显式编译,使用go build命令:

    go build test.go
    

    它将在当前目录下生成一个名为test的二进制文件,可以直接拿来运行,就像前面/tmp中的一样。

    $ ./tese
    Let's Go
    

    在开发阶段,用go build还是用go run,随意即可。但在部署的时候,一般先go build,再go run。

    main包和main函数

    在上面的代码中,声明了这个包的名称为main,然后创建一个函数,并在此函数中使用println输出了一个字符串,但是go run如何知道要去执行什么?在Go中,程序的入口是main包中的main函数,这两名称都是固定的。

    对于一个从没编程过的人,可能不理解程序的入口。它表示程序从此处开始执行,函数main中可能会包含很多其它函数的调用,这些函数可能放在其它文件(包)中。通过一次次、一层层的调用,从而将整个程序的所有代码、逻辑都连接在一起并运行。

    如果你愿意,可以试着修改一下package后面的main关键字,然后go rungo build都运行一下。再试着修改一下func mainmain关键字,go rungo build再运行一下。

    关于包的内容,后面再做介绍。目前来说,需要理解的只是些基础,对于基础阶段来说,我们将总是在main包中写代码。

    import

    Go有一些内置的函数,例如上面的println,内置函数无需额外的引用就可直接调用。但内置函数毕竟很少,所以得从已经写好的Go标准库和其它第三方库中找出一些工具来使用。

    在Go中,import关键字用于定义要导入到当前文件的包名,导入某个包后,这个包中的属性就能在当前文件中去访问,例如调用属于这个包的函数。

    例如,将前面的代码改改:

    package main
    
    import (
        "fmt"
        "os"
    )
    
    func main (){
        if len(os.Args) != 2{
            os.Exit(1)
        }
        fmt.Println("Arg0: ",os.Args[0])
        fmt.Println("Arg1: ",os.Args[1])
    }
    

    执行一下:

    $ go run test.go
    exit status 1
    
    $ go run test.go a b
    exit status 1
    
    $ go run test.go a 
    Arg0:  /tmp/go-build730099388/command-line-arguments/_obj/exe/test
    Arg1:  a
    

    上面的import导入了两个标准包:fmtos,还使用了另一个内置函数len()

    len()函数返回字符串的长度、字典的元素个数以及数组的元素个数。上面使用len()判断了该Go程序的参数个数必须为2,否则就以状态码1退出该程序。看上面的运行结果,好像只有给一个参数的时候才是正确的,这是因为第一个参数(Args[0])代表的总是当前正在运行的程序名称,正如上面结果所显示的那样。

    你可能还注意到了fmt.Println,前缀fmt正好是导入的一个包名,这表示使用fmt包中的Println函数。

    本文的开头就说过了,Go是一门非常严格的语言,如果这里导入了fmt包,但却没有使用它,它将报错。

    # command-line-arguments
    ./test.go:4:5: imported and not used: "fmt"
    

    关于Go文档

    关于fmt的Println函数详细用法,可去参考Go的官方文档:https://golang.org/pkg/fmt/#Println。当然,现阶段去看官方手册,那是在找死。

    还可以使用go doc命令去查找各帮助文档。

    例如,查看fmt包的帮助文档:

    go doc fmt
    

    查看fmt.Println函数的用法:

    go doc fmt.Println
    

    完整用法:

    go doc
    go doc <pkg>
    go doc <sym>[.<method>]
    go doc [<pkg>].<sym>[.<method>]
    go doc <pkg> <sym>[.<method>]
    

    此外,还可以构建本地的网页版官方手册,在断网的时候可以访问:

    godoc -http=:6060
    

    然后就可以在浏览器中通过http://localhost:6060/访问官方手册。

    变量和变量声明

    很多语言中,要为变量赋值只需一个语句:

    x=10
    

    这个语句中实际上包含了两个过程:变量的声明和变量的赋值。声明一般也被称为"定义"

    在Go中,必须先声明变量,再赋值或使用变量。最复杂的声明+赋值操作为:

    package main
    
    import ( "fmt" )
    
    func main(){
        var x int
        x=10
        fmt.Println("x =",x)
    }
    

    此处声明了一个变量x,其数据类型为int。默认情况下,Go在变量的声明期间会为其做初始化赋值:int类型初始化赋值为0,booleans初始化赋值为false,strings初始化赋值为"",等等。

    可以将声明和赋值操作合并:

    var x int = 10
    

    还有一种更方便的声明+赋值方式:

    x := 10
    

    通过这种变量的定义方式,还可以将函数执行结果(返回值)赋值给变量。例如:

    func main() {
        x := getAdd(10)
    }
    
    func getAdd(x int) int {
        return x+1
    }
    

    :=在Go中属于类型推断操作,它包含了变量声明和变量赋值两个过程。

    需要注意的是,变量声明之后不能再次声明(除非在不同的作用域),之后只能使用=进行赋值。例如,执行下面的代码将报错:

    package main
    
    import ("fmt")
    
    func main(){
    	x:=10
    	fmt.Println("x =",x)
    	x:=11
    	fmt.Println("x =",x)
    }
    

    错误如下:

    # command-line-arguments
    .	est.go:8:3: no new variables on left side of :=
    

    报错信息很明显,:=左边没有新变量。

    如果仔细看上面的报错信息,会发现no new variables是一个复数。实际上,Go允许我们使用:=一次性声明、赋值多个变量,而且只要左边有任何一个新变量,语法就是正确的。

    func main(){
        name,age := "longshuai",23
        fmt.Println("name:",name,"age:",age)
        
        // name重新赋值,因为有一个新变量weight
        weight,name := 90,"malongshuai"
        fmt.Println("name:",name,"weight:",weight)
    }
    

    需要注意,name第二次被:=赋值,Go第一次推断出该变量的数据类型之后,就不允许:=再改变它的数据类型,因为只有第一次:=对name进行声明,之后所有的:=对name都只是简单的赋值操作。

    例如,下面将报错:

    weight,name := 90,80
    

    错误信息:

    .	est.go:11:14: cannot use 80 (type int) as type string in assignment
    

    另外,变量声明之后必须使用,否则会报错,因为Go对规范的要求非常严格。例如,下面定义了weight但却没使用:

    weight,name := 90,"malongshuai"
    fmt.Println("name:",name)
    

    错误信息:

    .	est.go:11:2: weight declared and not used
    

    可以一次性声明、声明并赋值多个变量:

    var x, y, z int
    
    x, y, z := 10, 20, 30
    
    var (
    	x = 10
    	y = 20
    	z = 30
    )
    

    函数定义

    Go的函数允许有多个返回值。例如:

    // 该函数有一个参数,没有返回值
    func log(message string){
        ...CODE...
    }
    
    // 该函数有两个参数,一个返回值,返回值的类型为int
    func add(a int,b int) int {
        ...CODE...
    }
    
    // 该函数一个参数,两个返回值,分别是int和bool
    func power(name string) (int,bool) {
        ...CODE...
    }
    

    既然函数可以有返回值,就可以将其返回值赋值给变量:

    value, exists := power("malongshuai")
    if exists == false {
        ...CODE...
    }
    

    有些时候,我们可能并不需要所有的返回值。例如,我们只想要取得power()的第二个返回值。这时可以将不想要的返回值丢给特殊符号下划线_,它表示丢弃这部分结果。

    _,exists := power("longshuai")
    if exists == false {
        ...CODE...
    }
    

    如果函数的参数类型相同,则不同的参数可以共享数据类型。

    func (a,b int) int {
        ...CODE...
    }
    
  • 相关阅读:
    三数之和
    罗马数字与整数
    Oracle 开启或关闭归档
    Oracle RMAN scripts to delete archivelog
    Oracle check TBS usage
    Oracle kill locked sessions
    场景9 深入RAC运行原理
    场景7 Data Guard
    场景4 Data Warehouse Management 数据仓库
    场景5 Performance Management
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/9832577.html
Copyright © 2011-2022 走看看