ArcherWong博客
首页
博客
golang笔记--基本结构和基本数据类型
作者:ArcherWong
分类:golang
时间:2019-01-04 10:34:24
阅读:450
[TOC] # 1.文件名 Go 的源文件以 .go 为后缀名存储在计算机中,这些文件名均由小写字母组成,如 scanner.go 。如果文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。 # 2.Go 程序的基本结构和要素 ## 2.1包的概念、导入与可见性 ### 相关概念 每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 .go 为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。 每个 Go 应用程序都包含一个名为 main 的包,你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个**可独立执行的程序**,**每个 Go 应用程序含一个名为 main 的包**。 一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你**可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包**。如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而**不是可执行程序**。另外要注意的是,所有的**包名**都应该使用**小写字母**。 在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。在 Linux 下,标准库在 Go 根目录下的子目录 pkg\linux_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。一般情况下,标准包会存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。 属于同一个包的源文件必须全部被一起编译,一个包即是编译时的一个单元,因此根据惯例,**每个目录都只包含一个包**。 如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。 ### 如何导入包 ``` import "fmt" import "os" ``` 或: ``` import "fmt"; import "os" ``` 或 ``` import ( "fmt" "os" ) ``` 它甚至还可以更短的形式,但使用 gofmt 后将会被强制换行: ``` import ("fmt"; "os") ``` - 当你导入多个包时,**最好按照字母顺序排列包名**,这样做更加清晰易读。 - 如果包名不是以 . 或 / 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找; - 如果包名以 ./ 开头,则 Go 会在相对目录中查找; - 如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。 ### 可见性规则 当标识符(包括常量、变量、类型、函数名、结构字段等等)**以一个大写字母开头**,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);**标识符如果以小写字母开头,则对包外是不可见的**,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。 因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。 假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。 因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):两个包中的同名变量的区别在于他们的包名,例如 pack1.Thing 和 pack2.Thing。 你可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:`import fm "fmt"`。 ## 2.2函数 这是定义一个函数最简单的格式: ``` func functionName() ``` **main 函数是每一个可执行程序所必须包含的**,一般来说都是在启动后**第一个执行的函数**(**如果有 init() 函数则会先执行该函数**)。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 undefined: main.main。**main 函数既没有参数,也没有返回类型**(与 C 家族中的其它语言恰好相反)。如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误。 在程序开始执行并完成初始化后,第一个调用(程序的入口点)的函数是 main.main(),该函数一旦返回就表示程序已成功执行并立即退出。 左大括号 `{` 必须与方法的声明放在同一行,右大括号 `}` 需要被放在紧接着函数体的下一行。 如果你的函数非常简短,你也可以将它们放在同一行: ``` func Sum(a, b int) int { return a + b } ``` 因此符合规范的函数一般写成如下的形式: ``` func functionName(parameter_list) (return_value_list) { … } ``` 其中: - 参数(parameter_list) 的形式为 (param1 type1, param2 type2, …) - 返回值 (return_value_list) 的形式为 (ret1 type1, ret2 type2, …) 只有当某个函数需要**被外部包调用的时候才使用大写字母开头**,并遵循 Pascal 命名法; 否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。 ## 2.3类型 使用 var 声明的变量的值会**自动初始化为该类型的零值**。 类型可以是基本类型, - 如:int、float、bool、string; - 如:struct、array、slice、map、channel; - 只描述类型的行为的,如:interface。 **一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来**,如: ``` func FunctionName (a typea, b typeb) (t1 type1, t2 type2) ``` 示例: 函数 Atoi : ``` func Atoi(s string) (i int, err error) ``` 返回的形式: ``` return var1, var2 ``` 使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体,但是也可以定义一个已经存在的类型的别名,如: ``` type IZ int ``` 这里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。 然后我们可以使用下面的方式声明变量: ``` var a IZ = 5 ``` 这里我们可以看到 int 是变量 a 的底层类型,这也使得它们之间存在相互转换的可能。 **如果你有多个类型需要定义,可以使用因式分解关键字的方式**,例如: ``` type ( IZ int FZ float64 STR string ) ``` ## 2.4Go 程序的一般结构 总体思路如下: - 在完成包的 import 之后,开始对常量、变量和类型的定义或声明。 - 如果存在 init 函数的话,则对该函数进行定义(这是一个特殊的函数,每个含有该函数的包都会首先执行这个函数)。 - 如果当前包是 main 包,则定义 main 函数。 - 然后定义其余的函数,首先是类型的方法,接着是按照 main 函数中先后调用的顺序来定义相关函数,如果有很多函数,则可以按照字母顺序来进行排序。 示例 gotemplate.go ``` package main import ( "fmt" ) const c = "C" var v int = 5 type T struct{} func init() { // initialization of package } func main() { var a int Func1() // ... fmt.Println(a) } func (t T) Method1() { //... } func Func1() { // exported function Func1 //... } ``` Go 程序的执行(程序启动)顺序如下: - 按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程: - 如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。 - 然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。 - 在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。 # 3.常量 常量使用关键字 const 定义,用于**存储不会改变的数据**。 存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。 常量的定义格式:`const identifier [type] = value`,例如: ``` const Pi = 3.14159 ``` 在 Go 语言中,你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。 - 显式类型定义: const b string = "abc" - 隐式类型定义: const b = "abc" 常量的值必须是能够在编译时就能够确定的;你可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。 ``` 正确的做法:const c1 = 2/3 错误的做法:const c2 = getNumber() // 引发构建错误: getNumber() used as value ``` 因为在编译期间自定义函数均属于未知,因此无法用于常量的赋值,但内置函数可以使用,如:len()。 数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出: ``` const Ln2= 0.693147180559945309417232121458\ 176568075500134360255254120680009 const Log2E= 1/Ln2 // this is a precise reciprocal const Billion = 1e9 // float constant const hardEight = (1 << 100) >> 97 ``` 根据上面的例子我们可以看到,**反斜杠 \ 可以在常量表达式中作为多行的连接符使用**。 常量也允许使用并行赋值的形式: ``` const beef, two, c = "eat", 2, "veg" const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6 const ( Monday, Tuesday, Wednesday = 1, 2, 3 Thursday, Friday, Saturday = 4, 5, 6 ) ``` 常量还可以用作枚举: ``` const ( Unknown = 0 Female = 1 Male = 2 ) ``` 现在,数字 0、1 和 2 分别代表未知性别、女性和男性。 # 4.变量 ## 简介 声明变量的一般形式是使用 var 关键字:`var identifier type`。 在 Go 中,则可以很轻松地将它们都声明为指针类型: ``` var a, b *int ``` 其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。 示例: ``` var a int var b bool var str string ``` 你也可以改写成这种形式: ``` var ( a int b bool str string ) ``` **这种因式分解关键字的写法一般用于声明全局变量**。 当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate。 但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写 一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。 在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。 赋值给变量使用运算符等号 =。 示例: ``` a = 15 b = false ``` 声明与赋值(初始化)语句也可以组合起来。 示例: ``` var identifier [type] = value var a int = 15 var i = 5 var b bool = false var str string = "Go says hello to the world!" ``` Go 编译器的智商已经高到可以根据变量的值来自动推断其类型,这有点像 Ruby 和 Python 这类动态语言,只不过它们是在运行时进行推断,而 Go 是在编译时就已经完成推断过程。因此,你还可以使用下面的这些形式来声明及初始化变量: ``` var a = 15 var b = false var str = "Go says hello to the world!" ``` 或: ``` var ( a = 15 b = false str = "Go says hello to the world!" numShips = 50 city string ) ``` 变量的类型也可以在运行时实现自动推断,例如: ``` var ( HOME = os.Getenv("HOME") USER = os.Getenv("USER") GOROOT = os.Getenv("GOROOT") ) ``` **这种写法主要用于声明包级别的全局变量,当你在函数体内声明局部变量时,应使用简短声明语法 :=**,例如: ``` a := 1 ``` ## 简短形式,使用 := 赋值操作符 ``` a := 50 b := false ``` a 和 b 的类型(int 和 bool)将由编译器自动推断。 这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。 **注意事项:** - 在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 - 给相同的变量赋予一个新的值是可以的,例如:a = 20 - 在定义变量 a 之前使用它,则会得到编译错误 undefined: a - 声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,单纯地给 a 赋值也是不够的,这个值必须被使用;但是全局变量是允许声明但不使用 其他的简短形式为: 同一类型的多个变量可以声明在同一行,如: ``` var a, b, c int ``` (这是将类型写在标识符后面的一个重要原因) 多变量可以在同一行进行赋值,如: ``` a, b, c = 5, 7, "abc" ``` 上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用: ``` a, b, c := 5, 7, "abc" ``` 这被称为 并行 或 同时 赋值。 如果你想要交换两个变量的值,则可以简单地使用 ``` a, b = b, a。 ``` 空白标识符 _ 也被用于抛弃值,如值 5 在:`_, b = 5, 7 `中被抛弃。 _ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。 ## 值类型和引用类型 ### 指针 程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b0820 或 0xf84001d7f0。 ``` var i1 = 5 fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1) ``` 上面的代码片段可能输出 An integer: 5, its location in memory: 0x6b0820(这个值随着你每次运行程序而变化,指针的格式化标识符为 %p) **Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址**。这个地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 i1:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它: ``` var intP *int ``` 然后使用 ``` intP = &i1 ``` 此时 intP 指向 i1。intP 存储了 i1 的内存地址;它指向了 i1 的位置,它引用了变量 i1。  **你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容**,这里的 * 号是一个类型更改器,如 *intP,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。 当一个指针被定义后没有分配到任何变量时,它的值为 nil。 一个指针变量通常缩写为 ptr。 ### 值类型 **int、float、bool 和 string这些基本类型都属于值类型,另外,像数组和结构这些复合类型也是值类型** ▶ 使用这些类型的变量直接指向存在内存中的值:  ▶ 当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝,**修改j的时候并不会影响到i**。  ### 引用类型 **指针属于引用类型,其它的引用类型还包括 slices,maps和 channel** 更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。 ▶ 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。  这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中。 当使用赋值语句 r2 = r1 时,只有引用(地址)被复制;**如果 r2 的值被改变了,r1也会受到影响,所有引用都会指向被修改后的内容**。 ## init 函数 变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。 每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。 一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性。 **参考资料** https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
标签:
上一篇:
golang笔记--环境配置
下一篇:
golang笔记---接口(interface)
文章分类
css
elasticsearch
git
golang
guacamole
javascript
letsencrypt
linux
nginx
other
php
python
vue
web
阅读排行
centos7.3配置guacamole
golang笔记---关联数组(map)
letsencrypt证书-管理工具certbot
golang笔记---template模板语法
详解网络连接
友情链接
node文件
laravel-vue
ArcherWong的博客园