Golang-基本结构
- Golang
- 2023-03-25
- 780热度
- 0评论
任何一个编程语言都有自己的基础架构,只有在了解了编程语言的基础架构后才能开始进行一些简单的编程的逻辑,然后组合简单结构变为复杂的数据结构,其实编程很多的时候都是一种艺术,写出的代码也是一种艺术,给人赏心悦目的感觉。
命名
命名在所有的编程语言中都是有规则可寻的,也是需要遵守的,只有我们有了好的命名习惯才可以写出好的代码,例如我们在生活中对建筑的命名也是希望可以表达这个建筑的含义和作用。在Go语言中也是一样的,Go语言的函数名,变量名,常量名,类型名和包的命名也是都遵循这一规则的:一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:Car和car是两个不同的名字。
通常我们在Go语言编程中推荐的命名方式是驼峰命名例如:ReadAll,不推荐下划线命名。
关键字
Go语言中也有类似java的关键字,且关键字不能用于自定义名字,只能在特定语法结构中使用。
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
除此之外Go语言中还有30多个预定义的名字,比如int和ture等
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64 bool byte rune string error
内建函数: make len cap new append copy close delete complex real imag panic recover
常量
在Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、浮点型和复数类型)、布尔类型、字符串类型等。
下面我们使用const 关键字来定义常量:
package main
import "fmt"
import "math"
// "const" 关键字用来定义常量
const s string = "appropriate"
func main() {
fmt.Println(s)
// "const"关键字可以出现在任何"var"关键字出现的地方
// 区别是常量必须有初始值
const n = 20
// 常量表达式可以执行任意精度数学计算
const d = 3e20 / n
fmt.Println(d)
// 数值型常量没有具体类型,除非指定一个类型
// 比如显式类型转换
fmt.Println(int64(d))
// 数值型常量可以在程序的逻辑上下文中获取类型
// 比如变量赋值或者函数调用。
// 例如,对于math包中的Sin函数,它需要一个float64类型的变量
fmt.Println(math.Sin(n))
}
输出的结果为:
appropriate
6e+11
600000000000
-0.28470407323754404
变量
通常用var声明语句可以创建一个特定类型的变量,然后给变量附加一个名字,并且设置变量的初始值。
Go的基本数据类型
* bool
* string
* int int8 int16 int32 int64
* uint uint8 uint16 uint32 uint64 uintptr
* byte // uint8 的别名
* rune // int32 的别名 代表一个Unicode码
* float32 float64
* complex64 complex128
变量的声明和初始化值
变量的声明的语法一般是var 变量名字 类型 = 表达式
通常情况下“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。
1.数值类型变量对应的零值是0
2.布尔类型变量对应的零值是false
3.字符串类型对应的零值是空字符串
4.接口或引用类型(包括slice、map、chan和函数)变量对应的零值是nil
5.数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值。
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。
通常我们在编程的过程中,也用简短声明变量。它以“名字 := 表达式
”形式声明变量,变量的类型根据表达式来自动推导。
destination := 12
result := rand.Float64() * 3.0
- 因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。
- var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。
- 对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。
-
而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。
- 函数的参数变量和返回值变量都是局部变量。
- 它们在函数每次被调用的时候创建。
赋值
使用赋值语句可以更新一个变量的值,最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。
x = 1 // 命令变量的赋值
*p = true // 通过指针间接赋值
person.name = "keke" // 结构体字段赋值
count[n] = count[n] * scale // 数组、slice或map的元素赋值
//说明:在实际的使用中,前提条件是以上变量是需要预先定义好了的
数值变量也可以支持++递增和—递减语句。
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
//不可以使用++v或--v这种形式,否则会报错
类型
变量或表达式的类型定义了对应存储值的属性特征,类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用。
//自定义类型名称
type 类型名字 底层类型
type Precision float64 //精确度
包和文件
Go语言中的包和其他语言的库或模块的概念类似,目的都是为了支持模块化、封装、单独编译和代码重用。一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中。在Go语言中包还可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息。在Go语言中,一个简单的规则是:如果一个名字是大写字母开头的,那么该名字是导出的。
包的初始化过程
如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译。
对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,还有一些没有初始化表达式的,例如某些表格数据初始化并不是一个简单的赋值过程。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作。每个文件都可以包含多个init初始化函数,例如func init() { }
。
这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。 每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了m包,那么在p包初始化的时候可以认为m包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依赖的包都已经完成初始化工作了。
概述:init函数是对包的初始化(包级别的初始化),能够对无法使用初始化表达式初始化的变量进行初始化,在有初始化表达式的变量之后执行,优先于main函数。
init函数会在main函数执行前被Golang隐式自动调用,且我们不能主动调用init函数,原因是因为它可以有多个。
init函数是可选的
init函数的定义没有入参也没有出参
一个源文件可以有多个init函数,执行顺序取决于出现的顺序,如果一个包中有多个.go文件,那么执行顺序是首先按照文件名排序再依次执行
如果一个包导入了其他的包,则按照导入顺序先执行其他包中的init函数
不允许主动调用init函数,是允许声明多个init函数的原因
import导入包的用法
import "github.com/tidwall/gjson" //通过包名gjson调用导出接口
import json "github.com/tidwall/gjson" //通过别名json调用gjson
import . "github.com/tidwall/gjson" //.符号表示,对包gjson的导出接口的调用直接省略包名
import _ "github.com/tidwall/gjson" //_ 仅仅会初始化gjson,如初始化全局变量,调用init函数
作用域
作用域和生命周期的区别
一个声明语句将程序中的实体和一个名字关联,比如一个函数或一个变量。声明语句的作用域是指源代码中可以有效使用这个名字的范围。 不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
可以理解为作用域是指我声明了一个变量,我可以在什么地方调用这个变量,比如包级别声明变量那就是整个包的任何地方,函数中定义的变量那就是只能在函数内调用,函数外无法调用。
语法块
语法块是由花括弧所包含的一系列语句,就像函数体或循环体花括弧对应的语法块那样。语法块内部声明的名字是无法被外部语法块访问的。语法决定了内部声明的名字的作用域范围。我们可以这样理解,语法块可以包含其他类似组批量声明等没有用花括弧包含的代码,我们称之为语法块。有一个语法块为整个源代码,称为全局语法块;然后是每个包的包语法决;每个for、if和switch语句的语法决;每个 switch或select的分支也有独立的语法决;当然也包括显式书写的语法块(花括弧包含的语句)。
声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量,比如int、len和true 等是在全局作用域的,因此可以在整个程序中直接使用。任何在函数外部(也就是包级语法域)声明的名字可以在同一个包的任何源文件中访问的。对于导入的包,例如tempconv导入的fmt包,则是对应源文件级的作用域,因此只能在当前的文件中访问导入的fmt包,当前包的其它源文件无法访问在当前源文件导入的包。
在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。
if f, err := os.Open(fname); err != nil { // compile error: unused: f
return err
}
f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f
变量f的作用域只有在if语句内,因此后面的语句将无法引入它,这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示,具体错误信息依赖编译器的实现。