8小时速成golang(四)语法新奇 4种变量定义方法 常量iota 函数 多返回值,值传递,引用传递
1、从一个main函数初见golang语法
package main import "fmt" func main() { /* 简单的程序 万能的hello world */ fmt.Println("Hello Go") }
终端运行
$ go run test1_hello.go
Hello Go
$
go run 表示 直接编译go语言并执行应用程序,一步完成
你也可以先编译,然后再执行
$go build test1_hello.go $./test1_hello Hello Go
- 第一行代码package main定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
- 下一行import "fmt"告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
- 下一行func main()是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
注意:这里面go语言的语法,定义函数的时候,‘{’ 必须和函数名在同一行,不能另起一行。
- 下一行 /.../ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
- 下一行fmt.Println(...)可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
2、变量的声明
声明变量的一般形式是使用 var 关键字
变量声明
第一种,指定变量类型,声明后若不赋值,使用默认值0
var v_name v_type v_name = value package main import "fmt" func main() { var a int fmt.Printf(" = %d\n", a) } $go run test.go a = 0
第二种,根据值自行判定变量类型。
var v_name = value
第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
v_name := value // 例如 var a int = 10 var b = 10 c : = 10
e.g.
package main import "fmt" func main() { //第一种 使用默认值 var a int fmt.Printf("a = %d\n", a) //第二种 省略后面的数据类型,自动匹配类型 var c = 20 fmt.Printf("c = %d\n", c) //第三种 省略var关键字 d := 3.14 fmt.Printf("d = %f\n", d)
//第四种 既有数据类型 又有数值
var b int = 10
fmt.Printf("b = %d\n", b)
}
多变量声明
package main import "fmt" var x, y int var ( //这种分解的写法,一般用于声明全局变量 a int b bool ) var c, d int = 1, 2 var e, f = 123, "liudanbing" //这种不带声明格式的只能在函数体内声明 //g, h := 123, "需要在func函数体内实现" func main() { g, h := 123, "需要在func函数体内实现" fmt.Println(x, y, a, b, c, d, e, f, g, h) //不能对g变量再次做初始化声明 //g := 400 _, value := 7, 5 //实际上7的赋值被废弃,变量 _ 不具备读特性 //fmt.Println(_) //_变量的是读不出来的 fmt.Println(value) //5 }
3、常量
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
1 | const identifier [type] = value |
你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
- 显式类型定义:
-
const b string = "abc"
- 隐式类型定义:
const b = "abc"
package main import "fmt" func main() { const LENGTH int = 10 const WIDTH int = 5 var area int const a, b, c = 1, false, "str" //多重赋值 area = LENGTH * WIDTH fmt.Printf("面积为 : %d\n", area) println(a, b, c) }
常量还可以用作枚举
const ( Unknown = 0 Female = 1 Male = 2 )
常量可以用len(), cap(), unsafe.Sizeof()常量计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package main import "unsafe" const ( a = "abc" b = len(a) c = unsafe.Sizeof(a) ) func main(){ println(a, b, c) }
输出结果为:abc, 3, 16
unsafe.Sizeof(a)输出的结果是16 。
字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
优雅的常量 iota
有些概念有名字,并且有时候我们关注这些名字,甚至(特别)是在我们代码中。
const ( CCVisa = "Visa" CCMasterCard = "MasterCard" CCAmericanExpress = "American Express" )
在其他时候,我们仅仅关注能把一个东西与其他的做区分。有些时候,有些时候一件事没有本质上的意义。比如,我们在一个数据库表中存储产品,我们可能不想以 string 存储他们的分类。我们不关注这个分类是怎样命名的,此外,该名字在市场上一直在变化。
我们仅仅关注它们是怎么彼此区分的。
const ( CategoryBooks = 0 CategoryHealth = 1 CategoryClothing = 2 )
使用 0, 1, 和 2 代替,我们也可以选择 17, 43, 和 61。这些值是任意的。
在 Go,常量有许多微妙之处。当用好了,可以使得代码非常优雅且易维护的。
自增长
在 golang 中,一个方便的习惯就是使用 iota
标示符,它简化了常量用于增长数字的定义,给以上相同的值以准确的分类。
const ( CategoryBooks = iota // 0 CategoryHealth // 1 CategoryClothing // 2 )
iota和表达式
iota可以做更多事情,而不仅仅是 increment。更精确地说,iota总是用于 increment,但是它可以用于表达式,在常量中的存储结果值
type Allergen int const ( IgEggs Allergen = 1 << iota // 1 << 0 which is 00000001 IgChocolate // 1 << 1 which is 00000010 IgNuts // 1 << 2 which is 00000100 IgStrawberries // 1 << 3 which is 00001000 IgShellfish // 1 << 4 which is 00010000 )
这个工作是因为当你在一个const
组中仅仅有一个标示符在一行的时候,它将使用增长的iota
取得前面的表达式并且再运用它,。在 Go 语言的spec中, 这就是所谓的隐性重复最后一个非空的表达式列表.
如果你对鸡蛋,巧克力和海鲜过敏,把这些 bits 翻转到 “on” 的位置(从左到右映射 bits)。然后你将得到一个 bit 值00010011
,它对应十进制的 19。
fmt.Println(IgEggs | IgChocolate | IgShellfish) // output: // 19 type ByteSize float64 const ( _ = iota // ignore first value by assigning to blank identifier KB ByteSize = 1 << (10 * iota) // 1 << (10*1) MB // 1 << (10*2) GB // 1 << (10*3) TB // 1 << (10*4) PB // 1 << (10*5) EB // 1 << (10*6) ZB // 1 << (10*7) YB // 1 << (10*8) )
当你在把两个常量定义在一行的时候会发生什么?
"<<", 右边的数指定移动的位数,KB ByteSize = 1 << (10 * iota),就是1左移10位,变成10000000000,1024
Banana 的值是什么? 2 还是 3? Durian 的值又是?
const ( Apple, Banana = iota + 1, iota + 2 Cherimoya, Durian Elderberry, Fig )
结果,
在下一行增长,而不是立即取得它的引用。
// Apple: 1 // Banana: 2 // Cherimoya: 2 // Durian: 3 // Elderberry: 3 // Fig: 4
4、函数
Go 函数可以返回多个值,例如:
package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("Mahesh", "Kumar") fmt.Println(a, b) }
以上实例执行结果为:
Kumar Mahesh
init函数与import
首先我们看一个例子:init函数:
init 函数可在package main中,可在其他package中,可在同一个package中出现多次
main函数
main 函数只能在package main中
执行顺序
golang里面有两个保留的函数: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函数。下图详细地解释了整个执行过程:
----------------------------------------------------------------------------------------------------------------------------------------------------------
先输出其他包 - 包的常量变量初始化- init函数- 再main包- main包的常量变量初始化- main包的init函数 - main函数
----------------------------------------------------------------------------------------------------------------------------------------------------------
代码结构
Lib1.go
package InitLib1 import "fmt" func init() { fmt.Println("lib1") }
Lib2.go
package InitLib2 import "fmt" func init() { fmt.Println("lib2") }
main.go
package main import ( "fmt" _ "GolangTraining/InitLib1" _ "GolangTraining/InitLib2" ) func init() { fmt.Println("libmain init") } func main() { fmt.Println("libmian main") }
代码很简单,只是一些简单的输出
lib1
lib2
libmain init
libmian main
输出的顺序与我们上面图给出的顺序是一致的
那我们现在就改动一个地方,Lib1包导入Lib2,main包不管
package InitLib1 import ( "fmt" _ "GolangTraining/InitLib2" ) func init() { fmt.Println("lib1") }
输出
lib2
lib1
libmain init
libmian main
main包以及Lib1包都导入了Lib2,但是只出现一次,并且最先输出,
说明如果一个包会被多个包同时导入,那么它只会被导入一次,而先输出lib2是因为main包中导入Lib1时,Lib1又导入了Lib2,
会首先初始化Lib2包的东西
函数参数
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数:
值传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
以下定义了 swap() 函数:
/* 定义相互交换值的函数 */ func swap(x, y int) int { var temp int temp = x /* 保存 x 的值 */ x = y /* 将 y 值赋给 x */ y = temp /* 将 temp 值赋给 y*/ return temp; }
接下来,让我们使用值传递来调用 swap() 函数:
package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值为 : %d\n", a ) fmt.Printf("交换前 b 的值为 : %d\n", b ) /* 通过调用函数来交换值 */ swap(a, b) fmt.Printf("交换后 a 的值 : %d\n", a ) fmt.Printf("交换后 b 的值 : %d\n", b ) } /* 定义相互交换值的函数 */ func swap(x, y int) int { var temp int temp = x /* 保存 x 的值 */ x = y /* 将 y 值赋给 x */ y = temp /* 将 temp 值赋给 y*/ return temp; }
以下代码执行结果为:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
引用传递(指针传递)
指针
Go 语言中指针是很容易学习的,Go 语言中使用指针可以更简单的执行一些任务。
接下来让我们来一步步学习 Go 语言指针。
我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址
以下实例演示了变量在内存中地址:
package main import "fmt" func main() { var a int = 10 fmt.Printf("变量的地址: %x\n", &a ) }
以上运行结果为
变量的地址: c00000a0b8
Process finished with the exit code 0
现在我们已经了解了什么是内存地址和如何去访问它。接下来我们将具体介绍指针。
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
引用传递指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:
/* 定义交换值函数*/ func swap(x *int, y *int) { var temp int temp = *x /* 保持 x 地址上的值 */ *x = *y /* 将 y 值赋给 x */ *y = temp /* 将 temp 值赋给 y */ }
以下我们通过使用引用传递来调用 swap() 函数:
package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int= 200 fmt.Printf("交换前,a 的值 : %d\n", a ) fmt.Printf("交换前,b 的值 : %d\n", b ) /* 调用 swap() 函数 * &a 指向 a 指针,a 变量的地址 * &b 指向 b 指针,b 变量的地址 */ swap(&a, &b) fmt.Printf("交换后,a 的值 : %d\n", a ) fmt.Printf("交换后,b 的值 : %d\n", b ) } func swap(x *int, y *int) { var temp int temp = *x /* 保存 x 地址上的值 */ *x = *y /* 将 y 值赋给 x */ *y = temp /* 将 temp 值赋给 y */ }
以上代码执行结果为:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2020-02-11 node解决通过npm无法安装forever的方法
2019-02-11 【find】Linux中find常见用法示例