Skip to main content

golang-basic

基本概念

25 个关键词

golang-keywords

一个.go源文件是从开始的,通过package声明一个包。需要注意的是,对于main包,一个程序有且仅有一个

在 Go 语言中,main包是一个特殊的概念,它包含程序的入口点。当你运行一个 Go 程序时,实际上是在运行main包里的main函数。这是 Go 语言规定的一种结构,其原因有以下几点:

  1. 单一入口点:每个可执行的 Go 程序必须有一个且仅有一个main包,其中包含main函数。这提供了一个明确的起点,Go 运行时知道从哪里开始执行程序。
  2. 避免混淆:如果允许任何包都可以作为程序的入口,那么在构建复杂的项目时可能会引起混淆,因为编译器将不知道哪个函数是真正的入口点。通过限制入口点到main包,这种不确定性被消除了。
  3. 标准库兼容性:Go 的标准库和其他包都假设main函数是程序的起点。如果改变这一点,可能会影响到标准库的某些行为,比如初始化过程,导致不可预测的结果。
  4. 构建和运行逻辑:当你使用go rungo build命令时,这些工具会寻找main包。如果没有找到main包,go run将无法运行程序,go build将无法生成可执行文件,因为它们不知道如何构建一个可独立运行的二进制文件。
  5. 清晰的架构main包的存在有助于组织代码。它将程序的启动逻辑与其他功能代码分离,使得代码结构更加清晰,也更容易理解程序的执行流程。

因此,如果你想创建一个可独立运行的程序,你必须在你的项目中定义一个main包,且该包中必须包含一个main函数。这个函数的签名是固定的:

func main() {
// 程序的主逻辑
}

main函数是程序的入口点,没有返回值,也没有参数。当你的程序启动时,Go 运行时会直接调用这个函数。

入口

每个 go 程序都以 main 函数作为入口。定义函数使用 func 开头

权限修饰符

包里只有大写开头的函数才能被其他包导入使用

Go 语言并没有像 C++、Java 或其他一些语言中那样提供privatepublic这样的显式访问控制修饰符。相反,Go 语言使用了一种基于标识符(如变量、函数、类型等)首字母大小写的约定来控制可见性:

  • 大写字母开头的标识符:这些标识符是“公开的”(public),意味着它们可以从定义它们的包外部访问。这类似于其他语言中的public
  • 小写字母开头的标识符:这些标识符是“私有的”(private),只能在定义它们的包内部访问。这类似于其他语言中的private

例如,考虑下面的 Go 代码片段:

package mypackage

// 公开的函数
func PublicFunction() {
// ...
}

// 私有的变量
var privateVariable int

在这个例子中,PublicFunction因为首字母是大写,所以可以从其他包中访问。而privateVariable因为首字母是小写,只能在mypackage包内部访问。

此外,对于结构体内的字段和方法,同样的规则适用。如果字段或方法名以大写字母开头,则它们对外部是可见的;如果以小写字母开头,则只在定义的包内可见。

这种基于大小写的访问控制机制简化了 Go 语言的语法,并且使代码更易于阅读和理解,因为它不需要额外的修饰符来表达成员的可见性。同时,这也鼓励了模块化设计,因为包的作者可以轻松地控制哪些部分应该对用户开放,哪些部分应该保持内部使用。

数据结构(只看特有)

bool

一些编程语言使用 0 和非 0 表示。但是 go 里比如 if 语句后边只能是 bool 值或者返回 bool 值的表达式,而不像 c 一样可以使用 int 值

slice

切片比数组更加灵活,长度是可以变化的。

func testSlice() {
// 创建并且初始化一个 slice
names := []string{"zhang", "wang", "li", "zhao"}
// 打印 names, 长度和容量
fmt.Println(names, len(names), cap(names))
names2 := names[0:3] // 获取子切片 0,1,2 三个元素,注意左闭右开区间
fmt.Println(names2)
// 尝试修改一下 names2 ,注意 names 也会跟着改变么?
names[0] = "lao zhang"
fmt.Println(names, names2) // 你会发现names也变了,这里起始它们共用了底层结构,注意这个问题

// 遍历一个 slice 我们使用 for/range 语法
for idx, name := range names { // 如果没有用到下标 idx,可以写成下划线 _ 作为占位符,但是不能省略
fmt.Println(idx, name)
}

// 修改切片主要通过赋值和 append 操作。使用 append 修改切片
vals := make([]int, 0)
for i := 0; i < 3; i++ {
vals = append(vals, i)
}
fmt.Println(vals)
vals2 := []int{3, 4, 5}
newVals := append(vals, vals2...) // 可以使用省略号的方式『解包』一个 slice 来连接两个 slice
fmt.Println(newVals)
}

map

内置 map 数据结构,作为 map 的 key,必须要是支持 == 和 != 操作符的类型,常用的基础类型 int, float64, string 等都是可以作为 map 的键的,但是像切片或者不能比较的结构体无法作为键。而映射的值没有要求,任何内置或者自定义的类型都可以作为值

// 创建一个空的map
m := make(map[string]int)
m["hello"] = 1
m["world"] = 2
m["zhang"] = 3
fmt.Println(m)

// 从map中获取key为hello的value,如果取到了赋值给v,并把ok设为true(表示获取到值)
if v, ok := m["hello"]; ok {
fmt.Printf("m[%s] is %d \n", "hello", v)
}

// 如果只需要 k 或者 v 你可以使用 下划线作为占位符忽略值
for k, _ := range m {
fmt.Printf("k is %s\n", k)
}

注意:golang 中没有Set

struct

结构体是一种用户自定义的复合类型,可以包含多个不同类型的字段。结构体可以用来表示复杂的数据结构,如数据库记录、配置选项等。

type Person struct {
Name string
Age int
}

interface

接口定义了一组方法的集合,任何实现了这些方法的类型都可以满足该接口。接口在 Go 语言中用于实现多态性和抽象。

type Shape interface {
Area() float64
}

channel

通道用于在 goroutines 之间安全地通信和同步。通道可以发送和接收数据,是 Go 语言并发模型的关键部分。

ch := make(chan int) // 创建一个整数通道

pointer

虽然指针不是严格意义上的复合类型,但它可以指向其他类型的变量,并间接地访问和修改它们的值。在 Go 中,指针经常用于高效地操作数据,特别是大型结构体或数组。

var p *int // 定义一个指向整数的指针

空值

在 Go 语言中,每个类型都有一个默认的零值(zero value),也被称为“空值”。这个值在变量被声明但没有显式初始化时自动赋予变量。零值取决于数据类型:

  • 对于数值类型,零值通常是 0。
  • 对于布尔类型,零值是 false。
  • 对于字符串类型,零值是空字符串""。
  • 对于引用类型(如指针、切片、映射、通道、函数和接口),零值是 nil。

nil 是一个预定义的标识符,表示一个没有绑定到任何对象的指针或引用。例如:

var ptr *int // ptr 的值是 nil
var slice []int // slice 的值是 nil
var mapVar map[string]int // mapVar 的值是 nil

在 Go 语言中,nil 可以用来表示“空”或“无效”的引用,这与许多其他语言中的 null 关键字的作用相似。然而,Go 的 nil 与 null 有所不同,因为 Go 的类型系统更严格,nil 只能用于引用类型,而不能用于基本类型。

其他情况

在 Go 语言中,并没有像 TypeScript 那样的 any 类型,这是因为 Go 是一种静态类型语言,要求在编译时确定所有变量的类型。

然而,Go 语言中存在一种类似的机制,那就是使用 interface{}类型。interface{}是一个空接口,可以持有任何类型的值。这使得你可以在不知道确切类型的情况下处理变量,类似于 any 在 TypeScript 中的作用。例如:

var data interface{} = "Hello, World!"
fmt.Println(data) // 输出: Hello, World!

在上面的代码中,data 变量可以接受任何类型的值,这与 TypeScript 中的 any 类型相似。但是,与 any 不同的是,当你使用 interface{}时,你通常需要显式地进行类型断言或检查,以便在运行时获取变量的实际类型并使用它:

if str, ok := data.(string); ok {
fmt.Println("It's a string:", str)
} else {
fmt.Println("It's not a string")
}

流程控制

go 语言提供了三种分之方式,if/switch/select

if 语句

  • if 条件只能是一个 bool 值或者返回 bool 值得表达式,而不能是 int
  • if 后边是可以先跟一个表达式,比如:获取 map 值的 if v,ok := m[key]; ok {}

switch/case

go 里改善了 c 语言的 switch,比如我们不用每一个 case 都要加上 break

for 循环

go 只提供了一个关键词 for 来实现循环(没有 whiledo while

// 常规用法,和其他语言类似
for optionalPreStatement; booleanExpress; optionalPostStatement {
block
}

// 无限循环,block 会被一直重复执行
for {
block
}

// 实现while循环,block 一直执行直到 表达式为false
for booleanExpression {
block
}

此外 for/range 语法还支持让我去迭代字符串,数组,切片,map 或者通道(channel)

func testFor() {
intSlice := []int{3, 2, 1}
for index, item := range intSlice {
fmt.Println(index, item)
}
for index := range intSlice { // 省略 item 之后遍历的是 key,注意不像python 直接遍历值
fmt.Println(index)
}

m := map[string]string{"k1": "v1", "k2": "v2"}
for k, v := range m {
fmt.Println(k, v)
}
for k := range m {
fmt.Println(k)
}
}

go 使用 break 和 continue 分别跳出或者进入下一个循环

其他流程控制

  • goto:go 和 c 一样支持 goto 语句,用法也和其一致
  • label:当深层嵌套的 for 循环想要直接快速退出所有循环体的时候,我们可以使用 break 标签语法来实现
package main

import "fmt"

func main() {
var i, j int

// loop 标签。注意 break和continue 的标签需要在代码块前边,goto 没有这个限制
loop:
for i = 0; i < 10; i++ {
for j = 0; j < 3; j++ {
if i+j > 3 { // 如果 i + j > 3 ,跳出所有循环
break loop
}
fmt.Printf("i[%d] j[%d] i+j[%d]\n", i, j, i+j)
}
}
fmt.Println(i + j)
}

周边工具

常用命令

  1. go run xx.go:运行源码,go 允许在未编译的情况下,运行源码
  2. go build xx.go:编译源码,把源文件编译成系统级二进制文件,可直接执行:./xx

资料