【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
本专栏文章持续更新,新增内容使用蓝色表示。
食用指南
本文适合有 C++ 基础的朋友,想要快速上手 Go 语言。可直接复制代码在 IDE 中查看,代码中包含详细注释和注意事项。
Go 的环境搭建请参考以下文章:
【Go】C++ 转 Go 第(一)天:环境搭建 Windows + VSCode 远程连接 Linux -CSDN博客
变量声明与使用
Go 语言对变量的要求非常严格,float32 和 float64 是不同的类型,且没有自动类型转换机制。
C++ vs Go 变量声明对比
特性 | C++ | Go |
---|---|---|
类型声明 | int a = 5; | var a int = 5 |
类型推导 | auto b = "hello"; (C++14+) | b := "hello" |
多变量声明 | int x=1, y=2; | x, y := 1, 2 |
默认值 | 未初始化是未定义行为 | 自动零值初始化 |
注意事项:
Go 中没有 char 类型,使用 byte(ASCII) 和 rune(Unicode)。
变量声明后必须使用,否则编译报错。
:=简短声明只能在函数内部使用。
var.go
// 声明变量
package mainimport ("fmt"
)// 二、全局变量声明
var e int
var f float64 = 9
var g = "q"// var h:=1 // := 只能用在函数体内使用func main() {// 一、局部变量声明// 默认值是0var a intfmt.Println("a =", a)// go 中没有 char 字符类型,但是有byte (ASCII) / rune (Unicode)// 带初始化值var b int = 8fmt.Println("b =", b)// 自动匹配,相当于auto// 1. varvar c = "var c"// 使用类型推导可以使用 %v 占位符fmt.Printf("c = %v \t", c) // 非格式化输出,带换行符fmt.Printf("type : %T \n", c) // 格式化输出// 2. :=d := 5.5fmt.Printf("d = %v \t", d)fmt.Printf("type : %T \n", d)fmt.Println("e =", e)fmt.Println("f =", f)fmt.Println("g =", g)// fmt.Println("h =", h)// 三、多个变量声明// 1. 单行// 1) 同一种数据类型var a1, a2 int = 56, 78fmt.Println("a1 =", a1, ",", "a2 =", a2)// 2) 不同数据类型(自动匹配)var b1, b2 = "hello", 8.9fmt.Printf("b1 = %v \t", b1)fmt.Printf("type : %T \n", b1)fmt.Printf("b2 = %v \t", b2)fmt.Printf("type : %T \n", b2)// 2. 多行var (c1 int = 1c2 bool = false)fmt.Println("c1 =", c1, ",", "c2 =", c2)}
执行结果
常量与iota枚举
iota 是 Go 语言的常量计数器,只能在 const 内部使用。
iota 特性:
每遇到一个 const 关键字,iota 重置为0
const 声明块中每新增一行,iota 自增1
支持在表达式中使用 iota
const.go
package mainimport ("fmt"
)// 二、const 定义枚举类型
const (apple = 0banana = 1
)// 可在 const 中添加关键字 iota(只能在const内部使用),第一行的 iota 的默认值是0,之后每行 iota 加1。
// 读法: i o ta
const (_ = iota // 跳过 0monday // 1tuesday // 2wednesday // 3
)const (cat = iota * 2 // 0 * 2dog // 1 * 2panda // 2 * 2
)const (a, b = iota + 1, iota + 2 // iota = 0, a = iota + 1 = 1, b = iota + 2 = 2a1, b1 // iota = 1, a1 = iota + 1 = 2, b1 = iota + 2 = 3a2, b2 // iota = 2, a2 = iota + 1 = 3, b2 = iota + 2 = 4c, d = iota * 2, iota * 3 // iota = 3, c = iota * 2 = 6, d = iota * 3 = 9c1, d1c2, d2
)func main() {// 一、常量const x string = "xian"fmt.Println("x =", x)// x = "xining" // 只读fmt.Printf("apple=%d, banana=%d \n", apple, banana)fmt.Printf("monday=%d, tuesday=%d, wednesday=%d\n", monday, tuesday, wednesday)fmt.Printf("cat=%d, dog=%d, panda=%d\n\n", cat, dog, panda)fmt.Printf("a=%d, b=%d \n", a, b)fmt.Printf("a1=%d, b1=%d \n", a1, b1)fmt.Printf("a2=%d, b2=%d \n\n", a2, b2)fmt.Printf("c=%d, d=%d \n", c, d)fmt.Printf("c1=%d, d1=%d \n", c1, d1)fmt.Printf("c2=%d, d2=%d \n", c2, d2)
}
执行结果
函数
特性
-
参数顺序为参数名在前,类型在后。
-
Go 的返回值中没有 void,不需要返回值时直接省略返回类型。
-
Go 语言支持多返回值。
-
Go 中只有值传递,但可以传递指针来修改原值。
func.go
package mainimport "fmt"// 形参的变量名和类型和C++的相反,末尾的是返回值类型。没有返回值,末尾可以不写
// 注意:go中没有void关键字,不需要返回值时直接省略// go中只有值传递,但是传递的值不同,结果也不同// 一、返回单个匿名返回值
func test(a int, b int) bool {fmt.Println("-------test-------")fmt.Println("a =", a, "b =", b)c := truereturn c
}// 二、返回多个匿名返回值
func test1(a int, b string) (int, string) {fmt.Println("-------test1------")fmt.Println("a =", a, "b =", b)return a, b
}// 三、返回多个有名返回值
// 1. 多个返回值类型不同
func test2(a int, b string) (e float64, f string) {fmt.Println("-------test2------")fmt.Println("a =", a, "b =", b)// 这两个变量也属于函数形参,在未赋值之前使用不会报错,前者默认为0,后者默认为空// 防止不可控的野指针、野地址的出现fmt.Println("e =", e, "f =", f)// go没有隐式类型转换,比如将float64改为float32或者int都会报错e = 17.3f = "world"return
}// 2. 多个返回值类型相同
func test3(a int, b string) (e, f int) {fmt.Println("-------test3------")fmt.Println("a =", a, "b =", b)return 6, 7
}// 传递指针,函数内更改会影响原来的值
func change(n *int) {*n = 99
}func main() {c := test(1, 2)// 注意go中定义的变量必须要使用,否则会报错fmt.Println("c =", c)// 多返回值时, 可以使用下划线 _ 忽略不需要的返回值_, d := test1(3, "hello")fmt.Println("d =", d)e, f := test2(4, "world")fmt.Println("e =", e, "f =", f)e1, f1 := test3(5, "word")fmt.Println("e1 =", e1, "f1 =", f1)fmt.Println("\n======传指针=======")// 使用时传地址num := 7fmt.Println("未调用之前:", num)change(&num)fmt.Println("调用之后:", num)}
执行结果
init 函数与包导入
目录结构如下所示:
导入方式说明
-
标准导入:通过包名调用导出函数
-
匿名导入:只执行包的init函数,不直接使用包中其他函数
-
别名导入:为长包名设置简短别名
-
导入点:将包中全部方法导入当前作用域(谨慎使用)
lib1/lib1.go
package lib1import "fmt"func init() {fmt.Println("-----lib1.init()-----")
}// lib1包提供的API
// 以下函数名大写表示对外开放
// 小写则表示当前函数只能再包内调用
func Lib1Test() {fmt.Println("-----lib1Test()-----")
}
lib2/lib2.go
package lib2import "fmt"func init() {fmt.Println("-----lib2.init()-----")
}// lib2包提供的API
// 以下函数名大写表示对外开放
// 小写则表示当前函数只能再包内调用
func Lib2Test() {fmt.Println("-----lib2Test()-----")
}
main.go
package main// 暂时采用这种写法
// 导包时会调用对应的 init 函数
import (// 从 GOPATH 开始的相对路径// 1. 标准导入"GoLangStudy/5_initStudy/lib1""GoLangStudy/5_initStudy/lib2"// 2. 匿名导入(_+空格)// go 对语法要求严格,导包不使用,会报错// 如果只执行包的 init() 函数,不直接使用包中的其他函数,可通过匿名导包实现//_ "GoLangStudy/5_initStudy/lib2"// 3. 别名导入// 包名很长的时候可以使用// mylib2 "GoLangStudy/5_initStudy/lib2"// 4. 导入点(谨慎使用)// 将包中的全部方法导到当前包的作用域中,可直接调用API// 不同包可能会出现同名函数,不推荐生产上使用// . "GoLangStudy/5_initStudy/lib2"
)func main() {// 1. 通过导包,可以调用 lib1 的函数。lib1.Lib1Test()lib2.Lib2Test()// 3. 别名调用方式// mylib2.Lib2Test()// 4. 导入点// Lib2Test()
}
执行结果
如有问题或建议,欢迎在评论区中留言~