Go语言入门

3937 字
20 分钟
Go语言入门

Go语言入门#

1.1 helloworld示例#

一个基本的helloworld示例程序如下.

// 这是该文件所在的包名
package main
// 从引入fmt
import "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 开始,每行加 1
const (
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循环.

// 方式一: 类似于其它语言的for
for init; condition; post {}
// 方式二: 类似于其它语言的while
for 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;

使用外部包的变量,需要注意的就是文章刚开头说的,关于包,项目文件的组织安排的问题.

GoLearn/main.go
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)
}
GoLearn/domain/pack2.go
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 切片#

基本声明与使用

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Go语言入门
https://kulve.tech/posts/go语言入门/
作者
Kulve
发布于
2026-06-09
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Kulve
Hello, I'm Kulve.
公告
欢迎来到我的博客!这是一则示例公告。正在建设与测试此网站。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
5
分类
4
标签
13
总字数
3,472
运行时长
0
最后活动
0 天前

文章目录