Go语言入门
Go语言入门
1.1 helloworld示例
一个基本的helloworld示例程序如下.
// 这是该文件所在的包名package main
// 从引入fmtimport "fmt"// 函数以func声明,后接函数名func main() { // 类似于java中的System.out.println();,同样的带有输出换行功能. // 在语句后,不再需要添加分号. fmt.Println("Hello, World!")}关于这个包名,真是个大坑
-
此外,go.mod必须要放在根目录中
-
main.go,一般放在根目录中,如果是多入口的程序,会放在/cmd/app下
-
需要注意,在同一级目录下,只能有一个package声明.
-
强烈建议包名与目录名相同! 原因是
若在子目录domain中,有一个包名为package pkg2的文件,那么
在引入时,需要写 import “GoLearn/domain”,但是在使用时,需要pkg2.xxx,这很容易导致混乱,因为包名与目录名不一致,根本不知道domain里面会有一个pkg2.
-
在自己写的程序中,引用自己写的模块,需要使用 <模块名>/包名 的方式引入,具体使用方法见 1.9 函数作用域
1.2 数据类型
在go语言中,int8,int16等,可以省略为int,此时,go会根据计算机架构,自动选择整型.
func main() { // 布尔型 var boolvar bool = true
// 大类-数字类型 // 整数类型 // 默认位数跟随平台. // 支持uint无符号数 // 支持int8,int16,int32,int64等指定长度的类型,依次类推,uint同样支持. var intvar int = 2147483649 // 浮点类型,默认为float64 var float32var float32 = 100.0 var float64var float64 = 100.0 fmt.Println(boolvar) fmt.Println(intvar) fmt.Println(float32var) fmt.Println(float64var) // 其它数字类型 // byte: 类似于uint8 // rune: 类似于int32 // uint: 32或64位无符号整数 // int: 32或64位整数 // uintptr: 无符号整型,用于存放一个指针
// 字符串 var stringvar string = "good morning,Yuili" fmt.Println(stringvar)
// 虚数类型,需要使用函数complex(<实部>,<虚部>)创建,支持complex64,complex128. var complex64 complex64 = complex(1, 2) fmt.Println(complex64)}其它派生类型
指针类型 *
使用 & 取出地址
使用 *<类型> 声明一个指针类型的变量
func main() { var intvar int = 42 var intptr *int = &intvar var float64var float64 = 24.0 var float64ptr *float64 = &float64var
fmt.Printf("%d@%p\n", intvar, intptr) // 输出内容为 值@地址 fmt.Printf("%.2f@%p\n", float64var, float64ptr)
*float64ptr = 96.1 // 通过指针修改原变量 fmt.Println(float64var) // 输出: 96.1}数组类型 []
func main() { // 声明一个包含指定元素数量的数组 var arr [5]int = [5]int{1, 2, 3, 4, 5}
// 简写形式 arr2 := [3]string{"Go", "Python", "Java"}
// 让编译器推断长度,将[<长度>]换成[...]就可以 arr3 := [...]int{10, 20, 30}
fmt.Println(arr[0]) // 输出: 1 fmt.Println(len(arr)) // 输出: 5 fmt.Println(arr2) // 输出: [Go Python Java] fmt.Println(arr3) // 输出: [10 20 30]}结构体 type struct
// 声明结构体// 这个与java中的class不同,它不能存储方法.type Person struct { Name string Age int Email string}
func main() { // 为结构体字段赋值 // 方式1: 按字段顺序 p1 := Person{"Alice", 30, "alice@example.com"}
// 方式2: 指定字段名(推荐,也就是命名方式) p2 := Person{ Name: "Bob", Age: 25, }
// 方式3: 先声明再赋值,有点类似于class var p3 Person p3.Name = "Charlie"
fmt.Println(p1.Name) // 输出: Alice fmt.Println(p2) // 输出: {Bob 25 }}通道 channel
需要注意的是,若缓冲区满了或者创建的是无缓冲区的通道,那么在接收方或发送方无法接收或发送时,程序将会阻塞.
func main() { // channel: 通道 // 用于在协程之间通信使用 // 创建缓冲为 2 的 int 类型 channel bufferedChannel := make(chan int, 2)
// 发送数据到 channel bufferedChannel <- 100 bufferedChannel <- 200
// 从 channel 接收数据 // 先进先出,类似一个队列 x := <-bufferedChannel y := <-bufferedChannel
fmt.Println(x, y) // 输出: 100 200
// 无缓冲 channel unbufferedChannel := make(chan string) // 异步发送数据 go func() { unbufferedChannel <- "Hello from goroutine" }() msg := <-unbufferedChannel fmt.Println(msg)}函数 func
// 示例1: 定义一个函数类型的接口type AddFunc func(int, int) int
func main() { // 函数作为变量 // 声明函数,并赋值给变量保存起来 var add AddFunc = func(a, b int) int { return a + b } // 调用时,使用变量名作为函数名使用. result := add(3, 5) fmt.Println(result) // 输出: 8
// 示例2: 函数作为参数 // 参数1: a // 参数2: b:int // 参数3: op:func(int, int) int,是一个函数类型 operate := func(a, b int, op func(int, int) int) int { return op(a, b) }
// 使用,这里先声明一个函数,作用是将a,b相乘,并返回结果. multiply := func(a, b int) int { return a * b } // 使用operate函数,传入值a,b,以及函数multiply fmt.Println(operate(4, 5, multiply)) // 输出: 20
// 示例3: 函数作为返回值 // 函数名: makeMultiplier // 接收参数: factor int // 函数返回值类型: func(int) int makeMultiplier := func(factor int) func(int) int { // 在这里,返回了一个函数,用于计算x*factor. return func(x int) int { return x * factor } }
double := makeMultiplier(2) fmt.Println(double(7)) // 输出: 14}切片类型 slice
func main() { // 方式1: 从数组创建 arr := [5]int{10, 20, 30, 40, 50} s1 := arr[1:4] // 包含索引 1,2,3 fmt.Println(s1) // 输出: [20 30 40]
// 方式2: make 创建 s2 := make([]int, 3, 5) // 初始长度3,最大容量5 s2[0] = 1 s2[1] = 2 fmt.Println(s2) // 输出: [1 2 0]
// 方式3: 字面量 s3 := []string{"a", "b", "c"}
// 追加元素 s3 = append(s3, "d", "e") fmt.Println(s3) // 输出: [a b c d e]
// 切片复制 s4 := make([]int, len(s2)) copy(s4, s2)}接口类型
// 定义接口type Animal interface { Speak() string Move() string}
// Dog 实现 Animal 接口// Go语言的接口,是隐式实现的,不需要显式声明实现的接口// 只需要同时实现接口内的所有函数,就可以将实现赋值到接口类型的对象上,见下面的ShowAnimal函数,传入值是一个animal接口类型对象.type Dog struct { Name string}
func (d Dog) Speak() string { return "汪汪!"}
func (d Dog) Move() string { return "四条腿跑"}
// Cat 实现 Animal 接口type Cat struct { Name string}
func (c Cat) Speak() string { return "喵喵~"}
func (c Cat) Move() string { return "悄无声息地走"}
// 多态函数func ShowAnimal(a Animal) { fmt.Printf("%s, %s\n", a.Speak(), a.Move())}
func main() { // 声明两个实体类 dog := Dog{"旺财"} cat := Cat{"咪咪"} // 这里调用了多态函数,传入了两个实体类,由多态函数自动处理. ShowAnimal(dog) // 输出: 汪汪!, 四条腿跑 ShowAnimal(cat) // 输出: 喵喵~, 悄无声息地走
// 空接口: 可以存储任意类型,限一个,如基本类型,函数类型,切片类型,结构体等. // 在Go中,所有的方法都实现了空接口 var anything interface{} = "hello" anything = 42 anything = 3.14 fmt.Println(anything)}map类型
这是一个非常重要的类型
基本使用
func main() { // 方式1: make 创建 scores := make(map[string]int) scores["Alice"] = 95 scores["Bob"] = 87
// 方式2: 字面量 ages := map[string]int{ "Tom": 20, "Jerry": 18, }
// 访问 fmt.Println(scores["Alice"]) // 输出: 95
// 判断键是否存在 if score, ok := scores["Charlie"]; ok { fmt.Println("存在:", score) } else { fmt.Println("Charlie 不存在") }
// 删除 delete(ages, "Tom")
// 遍历 for name, score := range scores { fmt.Printf("%s: %d\n", name, score) }}新增Map元素的几种方式
没有单独的新增函数,若要新增函数,需要在循环中实现,利用map中键不存在,会自动新增的特性实现添加.
func main() { // 创建一个空的map scores := make(map[string]int)
// 新增元素
// 方式1: 直接赋值新增 // 当键不存在时,会自动新增元素 scores["Alice"] = 95 scores["Bob"] = 87 fmt.Println("初始map:", scores) // map[Alice:95 Bob:87]
// 方式2: 在循环中动态新增 names := []string{"Charlie", "David", "Eve"} // i: 值 // name: 键 // 循环遍历namesmap,利用键不存在时,自动新增元素的特性,将names中的元素添加到scores中 // 若已存在,那么值将会被覆盖
for i, name := range names { scores[name] = 90 + i*5 // Charlie:90, David:95, Eve:100 } fmt.Println("新增后:", scores) // map[Alice:95 Bob:87 Charlie:90 David:95 Eve:100]
// 方式3: 从另一个map复制新增 // 原理与上一个差不多 extraScores := map[string]int{ "Frank": 88, "Grace": 92, } for k, v := range extraScores { scores[k] = v // 如果key不存在就是新增 } fmt.Println("合并后:", scores) // map[Alice:95 Bob:87 ... Frank:88 Grace:92]
// 方式4: 条件新增(只有key不存在时才添加) if _, exists := scores["Henry"]; !exists { scores["Henry"] = 85 fmt.Println("新增Henry:", scores["Henry"]) // 85 }}1.3 变量声明
变量声明方式
// 声明方式一: 若活力类型,编译器会自动推断类型var <变量名> [类型] = <值>// 声明方式二: 此时编译器会自动推断类型<变量名> := <值>// 声明方式三: 一次性声明多个变量var <变量名列表> [类型] = <值列表>1.4 值类型和引用类型
int,float,string,bool类型为值引用类型,即在使用=时,会在内存中复制一份
其它复杂类型为引用类型.
1.5 常量声明
基础声明
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", area) // 面积为 : 50 fmt.Println() fmt.Println(a, b, c) // 1 false str}使用常量组
const ( a = 1 b c d)
func main() { fmt.Println(a) // b、c、d没有初始化,使用上一行(即a)的值 fmt.Println(b) // 输出1 fmt.Println(c) // 输出1 fmt.Println(d) // 输出1}一个特殊的iota 常量计数器
iota是一个特殊常量,用于生成递增的序列
基础使用
// iota 从 0 开始,每行加 1const ( a = iota // 0 b // 1,将会使用上一行的iota递增序列. c // 2)
func main() { fmt.Printf("a=%d, b=%d, c=%d\n", a, b, c) // a=0, b=1, c=2}进阶使用
iota在常量声明时,若在中间有显式赋值的变量时,将会停止使用iota,显式赋值的变量之后声明的新变量,将会使用上一行的值,也就是使用显式赋值的变量的值,等到下次使用显式赋值或使用iota时,变量才会改变值.
实际上,这是go语言的机制,参见常量组的声明
package main
import "fmt"
func main() { const ( a = iota // 0 b // 1 c // 2 d = "ha" // 独立值,iota += 1,实际值为给定的值而不是iota e // "ha",iota += 1,实际值为上一行的值而不是iota f = 100 // 独立值,iota += 1,同样的,实际值为上一行的值而不是iota g // 100,iota += 1,同上. h = iota // 7,恢复使用 iota 值,iota经过中间几个变量后,值为7 i // 8 ) fmt.Printf("a=%d, b=%d, c=%d, d=%s, e=%s, f=%d, g=%d, h=%d, i=%d\n", a, b, c, d, e, f, g, h, i)}1.6 运算符
同其它语言
1.7 程序结构
if分支结构
if <condition> { <if block>} [else { <else block>}]switch分支结构
switch var1 { case val1: ... case val2: ... default: ...}select 分支结构
这个结构只能用于通道操作,每个case上的表达式,必须是一个通道操作,要么是发送,要么是接收
不带default子句的select,一般用于同时监听多个通道,只要任意一个通道有数据,那么就可以执行下去.
select { case <- channel1: // 执行的代码 case value := <- channel2: // 执行的代码 case channel3 <- value: // 执行的代码
// 你可以定义任意数量的 case
default: // 所有通道都没有准备好,执行的代码}- 每个case都是一个通道
- 所有的channel在执行到select时,都将会被求值,此时,在本次执行select时,值已经被确定,不会再修改.
- 如果有多个case可以运行,那么select将会随机选出一个执行,其它的不会执行
- 在找不到可执行的case时,如果有default,那么将执行default,如果没有default,那么将会阻塞,此时将不再对channel或值进行求值,此时即使将通道替换为nil,也不会改变select正在监听的通道内的对象
for 循环结构
go语言中只有for循环,但有三种形式可选,可以用其中一种代替while循环.
// 方式一: 类似于其它语言的forfor init; condition; post {}
// 方式二: 类似于其它语言的whilefor condition {}
// 方式三: 类似于其它语言的for(;;)for {}示例
func main() { numbers := [6]int{1, 2, 3, 5} // 这里的key, value 类似于js的解构赋值,可以同时取出键和值. for key, value := range numbers { fmt.Printf("第 %d 位 x 的值 = %d\n", key, value) }}// 控制循环语句break: 中断本层循环,跳出本层循环continue: 中断本次循环,重新开始本层循环.// 无条件跳转语句,非常不建议使用该语句goto <label>...label: <标签名>1.8 函数
结构
func <函数名>( [参数列表] ) [返回值类型] { 函数体}函数是允许返回多个值的,如
func swap(x, y string) (string, string) { return y, x}1.9 函数作用域
- 在函数内部声明的变量,包括形式参数与返回值,都是局部变量
- 在函数外声明的变量,可以在本包内使用
- 若使用导出函数,那么可以在外部包使用
如何确定是否导出?
-
导出: 首字母大写,如 var ExportedVar int = 100
-
不导出: 首字母小写,如 var unexportedVar int = 100;
使用外部包的变量,需要注意的就是文章刚开头说的,关于包,项目文件的组织安排的问题.
package main
import ( "GoLearn/domain" // 这里的导入,需要从项目的模块名开始写. "fmt")
func main() { // 可以访问导出的内容 fmt.Println(domain.ExportedVar) // 正常访问 fmt.Println(domain.ExportedFunc()) // 正常访问
obj := domain.ExportedStruct{ Name: "张三", // 大写字段可访问 // age: 25, // 编译错误!age 未导出 }
// 以下全部编译错误 // pack2.unexportedVar // 未导出 // pack2.unexportedFunc() // 未导出
fmt.Println(obj.Name) fmt.Println(obj.Address)}package domain
// 导出(大写开头)—— 外部包可用var ExportedVar = "我是导出的变量"
func ExportedFunc() string { return "导出函数" }
type ExportedStruct struct { Name string // 导出字段 age int // 未导出字段(包外不可见) Address string // 导出字段}
// 未导出(小写开头)—— 仅限包内var unexportedVar = "私有变量"
func unexportedFunc() string { return "私有函数" }Go 拓展使用
2.1 指针
啊,指针,危险又美丽的东西
基本声明与使用
func main() { var a int var ptr *int var pptr **int // Go支持指向指针的指针,使用**就可以了
a = 3000
// 指针 ptr 地址 ptr = &a
// 指向指针 ptr 地址 pptr = &ptr
// 获取 pptr 的值 fmt.Printf("变量 a = %d\n", a) fmt.Printf("指针变量 *ptr = %d\n", *ptr) fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)}指针在数组中的应用
const max = 3
func main() { number := [max]int{5, 6, 7} var ptrs [max]*int // 声明int类型的指针数组 // 将number数组的值的地址赋给ptrs // &number: 指向了number数组,在这块内存区域中,使用for遍历 // 注意,这里使用number也是可以的,number与&number的区别在于,number是将数组复制了一份,页&number是指向数组的一个指针,使用数组复制时,是有性能开销的,但适用于单线程环境或者多线程不修改数组的环境. // i: 键 // x: 值 for i, x := range &number { // 取出x的内存地址,赋给指针数组的对应元素 ptrs[i] = &x } // 将 for i, x := range ptrs { fmt.Printf("指针数组:索引:%d 值:%d 值的内存地址:%d\n", i, *&*x, x) }}2.2 结构体
基本声明与使用
// 定义type <结构体名> struct { <成员名1> <类型> <成员名2> <类型> ...}
// 声明var <变量名> <结构体类型名>
// 使用<变量名>.<成员名>结构体同样支持指针.
2.3 切片
基本声明与使用
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!