GoLang 程序结构

2019-10-06 19:38:06   最后更新: 2019-11-05 15:17:10   访问数量:69




上一篇文章中,我们从一个 hello world 程序着手,看到了 golang 程序的基本结构以及程序中的 for、if、switch

GoLang 从安装到 hello world -- 循环判断与分支

 

本文我们来进一步详细学习一些 golang 的语法细节

 

 

golang 的名称包括函数名、变量名、常量名、类型名、语句标签、包名,他们都遵循相同的规则:

  1. 以字母或下划线开头
  2. 只包含任意数量的字符、数字和下划线
  3. 区分大小写
  4. 通常使用驼峰式命名

 

不能作为名称的关键字

以下关键字不能作为名称:

 

 

作用域与导入导出

golang 通过块来组织程序,循环或函数体等由大括号围起来的语句序列组成语法块,块将声明包围起来,决定了声明的可见性,当程序用到一个变量时,编译器首先在最内层语法块内查找变量的声明,如果没有找到,进而会去外一层语法块查找,因此,如果内层和外层声明了同名变量,那么内层的声明会覆盖外层的声明

因此函数中声明的实体只在函数内局部有效,下面即将介绍的“逃逸”是一种特殊情况,如果声明在包内源文件的最外层,则对整个包内所有源文件可见

实体首字母的大小写决定了他是否可以被跨包使用,如果首字母大写,则可以被其他包通过 import 后使用,否则只能在当前包内使用

包名总是由小写字母组成

 

golang 中函数定义的方式是:

func func_name([var_name var_type, ...]) [return_type] {

func_body

}

 

下面是一个函数调用的实际例子:

package main import "fmt" func main() { const freezingF, boilingF = 32.0, 212.0 fmt.Printf("%gF = %gC\n", freezingF, fToC(freezingF)) fmt.Printf("%gF = %gC\n", boilingF, fToC(boilingF)) } func fToC(f float64) float64 { return (f - 32) * 5 / 9 }

 

 

上面的例子实现了一个从华氏度转换为摄氏度的温度转换函数

此前我们已经看到,如果没有参数或返回值时,相应的类型声明部分可以省去

 

上一篇文章中我们已经介绍了 golang 中如何声明变量,通常使用 var 关键字:

var name type = expression

 

类型和表达式可以省略一个,但不能同时省略,因为 golang 中所有变量都拥有固定且不可更改的类型

golang 中不存在未经初始化的变量,所有变量均会在定义时进行初始化,如果没有给出初始化表达式,则会初始化为该类型对应的 0 值(0、""、false、nil)

golang 也支持同时初始化多个变量:

var i, j, k int var b, f, s = true, 23.5, "hello world"

 

 

短声明

函数局部通常使用短声明:

name := expression

 

例如:

i := 100 i, j := 0, 1

 

 

和 C 语言一样,golang 中也有指针,它存储的是变量的地址

通过 * 操作符作用于指针可以获取变量的值

通过 & 操作符作用于变量可以获取变量的地址

通过 * 操作符作用于类型

例如:

var p *int var x = 1 fmp.Println(*p) p = &x *p = 2 fmp.Println(x)

 

 

执行代码输出了:

1

2

 

与 C 语言不同,golang 中返回指向函数局部变量的指针是安全的,指向局部变量的指针一旦被返回和使用,该局部变量不会随着函数退出而销毁,这种情况被称为“逃逸”:

func getPtr() { var dummy int return &dummy }

 

 

逃逸的原理是通过在堆上开辟一块新的空间,然后将原本在栈上的变量赋值到新的空间并返回来实现的,因此具有额外的性能消耗

 

new 函数

另一种创建变量的方式是 new 函数,他返回指向所生成变量的指针:

ptr := new(int)

 

 

相当于:

var ptr *int var dummy int ptr = &dummy

 

 

golang 的 GC 机制是一个比较深入的话题,不在本文介绍范围内,后面我们会有单独的文章来介绍

变量的生命周期和 GC 机制有着一定的关系,基本思路是只有变量变得不可访问时才会被销毁,例如上面讲述指针时讲到可以通过返回局部变量的地址让局部变量在外部继续存活

和其他语言不同,golang 中你不需要关心变量是否创建在堆上还是栈上,这也与是否是通过 new 函数创建的无关

 

golang 变量具有严格的类型,只有类型完全相同的变量才可以进行比较和赋值操作

type 关键字可以用来声明定义一个新的命名类型:

type name underlying-type

 

例如下面的代码创建了摄氏度和华氏度两种新的类型及他们间的转换函数:

package main import "fmt" type Celsius float64 type Fahrenheit float64 func fToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) } func cToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }

 

 

虽然 Celsius 与 Fahrenheit 都是通过 float64 类型生成的新类型,但这两者并不是相同的类型,因此 Celsius 类型的变量与 Fahrenheit 类型的变量是不能进行比较和算数运算,但他们可以进行强制类型转换,因为他们有着相同的底层类型

虽然 Celsius 是一种新的类型,但它支持的操作与他的底层类型 float64 是完全一致的

 

声明类型的显示方式

和面向对象编程语言中对象的 toString 方法类似,golang 中定义的新类型也可以指定其转换为 string 类型的方法:

func (c Celsius) String() string { return fmt.Sprintf("%gC", c) } c := Celsius(212.0) fmt.Println(c)

 

 

打印出了 212C

在 func 关键字与函数名之间加入 Celsius 类型参数 c,表示将该方法绑定到 Celsius 类型中

 

包级别变量初始化

包的初始化从初始化包级别的变量开始,这些变量按照声明顺序初始化,但会根据依赖的顺序进行

例如对于下面包中的三个变量:

var a = b + c var b = f() var c = 1 func f() int { return c + 1 }

 

 

上面的程序中,按照声明顺序进行初始化时,a、b 均依赖变量 c,因此编译器首先进行 c 的初始化工作,由于 a 的初始化依赖变量 b 的初始化,所以接下来会进行 b 的初始化工作,最后进行 a 的初始化工作

 

init 方法调用

golang 中任何文件都可以包含任意多个 init 方法,用于初始化工作:

func init() { /* 初始化方法体 */ }

 

 

init 方法不能被显式调用,每当编译器完成所有包级别变量的初始化工作后,就会自动按顺序执行所有的 init 方法,整个过程是按照包的导入顺序进行的

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

 

《Go 程序设计语言》

 






读书笔记      技术贴      变量      指针      作用域      golang      go      程序结构      声明     


京ICP备15018585号