全栈开发知识库

Golang变量及函数(二)

命名规范

Go语言中,任何标识符(变量、常量、函数、自定义类型接口等)都应该满足以下规律:

  • 连续的字符或数字组成
  • 以字符或者下划线开头
  • 不能和Go关键字冲突

Go关键字:

break default func interface select case defer go map struct
chan else goto package switch const fallthrough if range type
continue for import return var

基本数据类型

bool  // 一个字节,值是true或者false

string // 字符串由1个或者多个字符组成,字符串都隐藏了一个结束符`\0`,也就是说字符串是由字符和'\0`组成的。

int int8 int16 int32 int64 // 带符号的

uint uint8 uint16 uint32 uint64 uintptr // 无符号的

byte // 占用8位,一个字节,相当于uint8

rune // int32相同

float32 float64

complex64 complex128 // 复数带有float32实部和虚部  复数带有float64实部和虚部

int、uint和uintptr(无符号整数用于存储指针值的未解释位)类型在32位的系统上一般是32位,而在64位系统上是64位。 当你需要使用一个整数类型时,应该首选int,仅当有特别的理由才使用定长整数类型或者无符号整数类型。

引用(结构化)类型

  • slice
  • map
  • channel
  • struct
  • array

结构化的类型没有真正的值,它使用nil作为默认值(在Objective-C中是nil,在Java中是null,在C和C++中是NULL或0)。 值得注意的是,Go语言中不存在类型继承。

接口类型

  • interface

函数类型

  • func

其他类型

  • arry
  • struct
  • string

变量

var语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。就像在这个例子中看到的一样,var语句可以定义在包或函数级别。变量在定义时没有明确的初始化时会赋值为零值 。

package main

import "fmt"

var c, python, java bool // boolean 类型

func main() {
    var i int
    fmt.Println(i, c, python, java) // 打印出来都是默认的值0 false false false
}

变量定义可以包含初始值,每个变量对应一个。如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。

package main

import "fmt"

var i, j int = 1, 2

func main() {
    var c, python, java = true, false, "no!"
    fmt.Println(i, j, c, python, java)
}
  • =是赋值,在使用之前必须先用var将变量进行声明
  • :=是声明变量并赋值,并且系统会自动推断类型,不需要使用var关键字声明,:=结构不能使用在函数外。

下面的这几种写法都是声明变量并赋值:

var a
a = 100
var b = 100
var c int = 100
d := 100

常量

常量的定义与变量类似,只不过不是使用var而是使用const关键字。

常量可以是字符、字符串、布尔或数字类型的值。常量不能使用:=语法定义。

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

如果有多个敞亮也可以一起声明:

const (
    Name = "a"
    Age = 18
)

类型转换

Go的在不同类型之间的项目赋值时需要显式转换。

var i int = 42
var f float64 = float64(i) // 将int转换为float64
var u uint = uint(f) // 将float64转换为unit

类型声明

变量或表达式的类型定义了对应存储值的属性特征,例如数值在内存的存储大小(或者是元素的bit个数),它们在内部是如何表达的, 是否支持一些操作符,以及它们自己关联的方法集等。在任何程序中都会存在一些变量有着相同的内部结构,但是却表示完全不同的概念。 例如,一个int类型的变量可以用来表示一个循环的迭代索引、或者一个时间戳、或者一个文件描述符、或者一个月份; 一个float64类型的变量可以用来表示每秒移动几米的速度、或者是不同温度单位下的温度; 一个字符串可以用来表示一个密码或者一个颜色的名称。

一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型, 这样即使它们底层类型相同也是不兼容的:

type 类型名字 底层类型

类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用。

package tempconv

import "fmt"

type Celsius float64    // 摄氏温度
type Fahrenheit float64 // 华氏温度

const (
    AbsoluteZeroC Celsius = -273.15 // 绝对零度
    FreezingC     Celsius = 0       // 结冰点温度
    BoilingC      Celsius = 100     // 沸水温度
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

函数

不支持嵌套、重载和默认参数。 函数的声明是通过func关键字:

func 函数名(参数列表) (返回值列表) {
    // 函数体
}

如:

func add(x int, y int) int {
    return x + y
}

注意上面的参数类型是在变量名之后的。

这个例子中函数名称是小写开头的add,所以它的作用域只属于所声明的包内使用,不能被其他包使用,如果我们把函数名以大写字母开头,该函数的作用域就大了,可以被其他包调用。这也是Go语言中大小写的用处。 因为在Go语言中不像Java那样有专门的关键字来声明作用域如:private、protect、public等,它是通过首字母的大小写来表示的。

当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略, 上面的例子也可以写成:

func add(x, y int) int {
    return x + y
}

函数可以返回任意数量的返回值,上面的add函数返回了一个int类型的返回值。 下面:

package main
import "fmt"
func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world") // 注意了这里用的是 := 这是为什么? 为什么不用=
    fmt.Println(a, b) // 打印出来的结果是world hellow
}

Go推荐给返回值起一个变量名:

func func01() (result int) {
    result = 666
    // 有返回值的函数,必须通过return返回
    return
}
  • 函数类型:函数也是一种数据类型,可以通过type给一个函数类型起名
func Add(a, b int) int {
    return a + b
}

func Minus(a, b int) int {
    return a - b
}

// FuncType是一个函数类型,注意后面的函数是没有名字并且没有{}
type FuncType func(int, int) int

func main() {
    var result int

    var fTest FuncType
    fTest = Add
    // 等价于Add(10, 20),执行结果是30
    result = fTest(10, 20) 

    fTest = Minus
    // 等价于Minus,执行结果是5
    result = fTest(10, 5)
}

  • 回调函数

如果函数有一个参数是函数类型,这个函数就是回调函数

还是继续用上面函数类型的例子:

func Calc(a, b int, fTest FuncType) (result int) {
    result = fTest(a, b)
    return
}

func main() {
    // 注意函数类型传递的是函数名字,没有()等
    a := Calc(1, 1, Add) // 或Calc(1, 1, Minus)
    fmt.Println("a = ", a)
}

可变参数的函数可以用任何数量的参数来调用。 例如,fmt.Println()就是一个常见的可变函数。

可以使用...XXX来表示接受任意数量的XXX类型的参数。

func sum(nums ...int) {
    fmt.Print(nums, " ")
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}
func test(args ...int) {
    // 把全部元素传递给sum的nums参数
    sum(args...) 
    // 把前面0~2的元素(不包括元素2)传递过去
    sum(args[:2]...)
    // 从第二个元素开始(包括本身),把后面的元素传递过去
    sum(args[2:]...)
}

注意:我们平时在java开发中,大多把函数和方法理解成一个意思,但是在go中方法是方法,函数是函数,他俩是完全不一样的。所以以后一定要注意。

上面使用了_这是什么?

空白符

空白符用来匹配一些不需要的值,然后丢弃掉。

下面的例子中,ThreeValues是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 i1与f1。第二个返回值赋给了空白符_然后自动丢弃掉。

package main

import "fmt"

func main() {
    var i1 int
    var f1 float32
    i1, _, f1 = ThreeValues()
    fmt.Printf("The int: %d, the float: %f \n", i1, f1)
}

func ThreeValues() (int, int, float32) {
    return 5, 6, 7.5
}

输出结果:

The int: 5, the float: 7.500000 

匿名函数及闭包

Go语言支持匿名函数,即函数可以像普通变量一样被传递或使用。匿名函数可赋值给变量,作为结构字段.

匿名函数可以直接访问外部的变量。

package main

import "fmt"

func main() {
    // 1
    var v func(a int) int
    // 匿名函数匿名函数就是没有函数名字
    v = func(a int) int {
        return a * a
    }
    fmt.Println(v(6))

    // 2
    fn := func() {
        fmt.Println("Hello World")
    }
    // 匿名函数的调用
    fn()
}

闭包

Go语言支持匿名函数,可以形成闭包。匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必声明。 说道这里,我还是一脸懵逼,没明白闭包到底有什么用,所以直接上例子吧:

package main

import "fmt"

func f(i int) func() int {
    return func() int {
        i++
        return i
    }
}

func main() {
    c1 := f(0)
    c2 := f(0)
    fmt.Println(c1()) // 1
    fmt.Println(c2()) // 1
}

函数f返回了一个函数,返回的这个函数,返回的这个函数就是一个闭包。这个函数中本身是没有定义变量i的,而是引用了它所在的环境(函数f)中的变量i。

c1跟c2引用的是不同的环境,在调用i++时修改的不是同一个i,因此两次的输出都是1。函数f每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却是引用不同的环境。

以下实例中,我们创建了函数intSeq(),返回另外一个函数。该函数的目的是在闭包中递增i变量.

package main

import "fmt"

// This function `intSeq` returns another function, which
// we define anonymously in the body of `intSeq`. The
// returned function _closes over_ the variable `i` to
// form a closure.
func intSeq() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}

func main() {

    // We call `intSeq`, assigning the result (a function)
    // to `nextInt`. This function value captures its
    // own `i` value, which will be updated each time
    // we call `nextInt`.
    nextInt := intSeq()

    // See the effect of the closure by calling `nextInt`
    // a few times.
    fmt.Println(nextInt())
    fmt.Println(nextInt())
    fmt.Println(nextInt())

    // To confirm that the state is unique to that
    // particular function, create and test a new one.
    newInts := intSeq()
    fmt.Println(newInts())
}

执行结果是:

1
2
3
1

所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只要有闭包还在使用它,这些变量就还会存在。

内置函数

Go语言拥有一些不需要进行导入操作就可以使用的内置函数。它们有时可以针对不同的类型进行操作,例如:len、cap和append, 或必须用于系统级的操作,例如:panic。因此,它们需要直接获得编译器的支持。

以下是一个简单的列表:

  • close:用于管道通信
  • len、cap:len用于返回某个类型的长度或数量(字符串、数组、切片、map和管道)cap是容量的意思, 用于返回某个类型的最大容量(只能用于切片和map)
  • new、make:new和make均是用于分配内存:new用于值类型和用户定义的类型,如自定义结构, make用于内置引用类型(切片、map和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。 new(T)分配类型T的零值并返回其地址,也就是指向类型T的指针它也可以被用于基本类型v := new(int)。 make(T)返回类型T的初始化之后的值,因此它比new进行更多的工作,new()是一个函数,不要忘记它的括号
  • copy、append:用于复制和连接切片
  • panic、recover: 两者均用于错误处理机制
  • print、println:底层打印函数,在部署环境中建议使用fmt包
  • complex、real imag:用于创建和操作复数

main函数和init函数

Go里面有两个保留的函数:

  • init函数(能够应用于所有的package中)
  • main函数(只能应用于package main中)

这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。

Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。

程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。

下图详细地解释了整个执行过程:

Image

import导入包

讲到这里顺便讲一下import导包。我们上面频繁用到了improt "fmt"。 上面这个fmt是Go语言的标准库,其实是去GOROOT环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块:

  • 相对路径
    import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import

  • 绝对路径
    import “shorturl/model” //加载gopath/src/shorturl/model模块

上面展示了一些import常用的几种方式,但是还有一些特殊的import:

  • 点操作
    go import( . "fmt" ) 这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world")可以省略的写成Println("hello world")

  • 别名操作
    别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字 go import( f "fmt" ) 别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")

  • _操作 这个操作经常是让很多人费解的一个操作符 go import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" ) _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。

ioat

package main

import "fmt"

func main() {
    const (
        a = iota
        b = iota
        c = iota
    )

    fmt.Printf("a = %d, b = %d, c + %d \n", a, b, c)
    // iota:常量自动生成器,每个一行,自动累加1
    // iota用于给常量复制使用

    // iota遇到const会充值为0
    const d = iota
    fmt.Println("d = ", d)

    // 可以只写一个iota
    const (
        a1 = iota
        b1
        c1
    )
    fmt.Printf("a1 = %d, b1 = %d, c1 + %d \n", a1, b1, c1)

    //  如果是同一行,值都一样
    const (
        i          = iota
        j1, j2, j3 = iota, iota, iota
        k          = iota
    )
    fmt.Printf("i = %d, j1 = %d, j2 + %d, j3 + %d, k + %d \n", i, j1, j2, j3, k)
}

执行结果:

a = 0, b = 1, c + 2 
d =  0
a1 = 0, b1 = 1, c1 + 2 
i = 0, j1 = 1, j2 + 1, j3 + 1, k + 2 

  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记