惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
博客园_首页
M
MIT News - Artificial intelligence
雷峰网
雷峰网
GbyAI
GbyAI
博客园 - 叶小钗
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
S
SegmentFault 最新的问题
H
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 【当耐特】
V
Visual Studio Blog
月光博客
月光博客
G
Google Developers Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
云风的 BLOG
云风的 BLOG
美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
有赞技术团队
有赞技术团队

smallyu的博客

为什么买的加密货币一定要去中心化 RISC-V 虚拟机无法代替 EVM 虚拟机 Polkadot 的 Existential Deposit 机制错在哪里 利息,套利,交易策略,金融市场 LayerZero 从来不挑战比特币的地位 默认参数引起的以太坊节点运行错误 硬件钱包与资产安全 预测市场比币圈更赌场 为什么去中心化的跨链桥不可能实现 我对于 AI 时代的答案 不要投资任何隐私币 区块链技术世界的三大真理 以太坊 AA 钱包的致命问题是什么 基于 AI 语义执行的 MCP 区块链的设计 一个集成 Geth 和 CometBFT 的兼容层 我的加密货币定投策略(二) DeFi 基础: 理解 AMM 定价机制 Go 语言 GMP 调度器的原理是什么 Web3 项目分析计划 对 Psyche Network 项目的分析 continuation 教程: 理解 CPS 如何开发一个比特币符文(Runes)协议 比特币脚本开发教程 我从王垠的计算机科学视频班学到了什么 区块链技术面试题(2025年版) Rust 语言容易让新手困惑的一个“过度优化” Solana 智能合约开发教程 (1) 尝试开发一个最小 EVM 虚拟机 基于 ZK 的链上身份系统设计 一个 Web3 打赏系统的设计
Go 语言基本语法
2019-03-15 · via smallyu的博客

2019-03-15

Go语言虽然在语言设计上不被王垠看好,但它如此简洁的代码结构确实让人着迷。

语句

Go语言语句结尾不需要;

变量和常量

使用var声明变量。当变量需要初始化时,可以使用赋值符号:=代替=以省略var关键字。

var a int
var b string

var c int = 10
var d = "golang"  // 编译器自动推断类型
d := 10

与C语言或Java不同,Go语言的类型声明在变量右侧。需要注意的是,如果程序中声明的变量未经使用,程序将无法通过编译。Go语言是一种工程化的语言,因此它的一些特性让人感觉不可理喻,但又会在实际工程中提高效益。

Go语言的变量赋值支持一些炫酷的写法,比如要交换变量x和y的值,可以使用这种违反直觉的写法:

x, y = y, x

Go语言中使用const定义常量,truefalseiota是预定义常量。其中iota稍显特殊,iota会在每一个const关键字出现时重置为0,然后在下一次const出现前,每出现一次iota,iota的值加1。

const a = iota   // 0
const b = iota   // 0
const (
  c = iota       // 0
  d = iota       // 1
)

数组和切片

声明一个元素个数为3的数组,并初始化:

array := [3]int{0, 1, 2}
array[0] = 3
fmt.Println(array)

和其他语言一样,Go语言在声明数组后并不能改变数组的大小。所以Go语言提供了像Python一样的切片。切片可以从数组中产生,也可以使用make()函数新建。

array := [3]int{0, 1, 2}
slice1 := array[:2]       // 从数组中创建

slice2 := make([]int, 3)  // 直接创建

fmt.Println(slice1)       // [0 1]
fmt.Println(slice2)       // [0 0 0]

除切片外,映射也是使用make函数创建,映射的类型全称是var myMap map[string] int,意为声明变量myMap,key为string,value为int。

流程控制

Go语言允许if-else语句的条件表达式不加小括号,当然加上也无妨。

a := 1
if a == 1 {
  print(1)
} else if (a == 2) {
  print(2)
} else {
  print(3)
}

选择语句的条件表达式同样不需要小括号,另外也不需要break,其他匹配项并不会执行,这一点和Scala相同。对选择语句的优化貌似已经是不约而同的做法。

i := 0
switch i {
case 0:
  print(0)
case 1:
  print(1)
}

循环结构的条件表达式依然不需要小括号。Go语言只支持for循环。同时对无限循环的场景也做了优化,不再需要for(;;)的写法。

for {
  print(1)
}

函数

Go语言诞生自C语言的派系,因此Go语言从一开始就不是OOP或FP的语言,没有类、对象等概念。函数是程序中的一等公民。和C语言相同,(main包下的)main函数是整个程序的入口。

func add(a int, b int) (int, int) {
  return a + b, a - b
}

func main() {
  x, y := add(1, 2)
  print(x, y)
}

Go语言的语句简洁高效,函数名后的第一个括号为入参,第二个括号是出参。函数支持多返回值。如果参数类型相同,可以将类型声明合并到一起,如(a, b int)

结构体

刚才提到Go语言没有类、对象等概念,但是Go语言有类似C语言的结构体,并且能力强大。这里定义一个Person结构体,包含两个属性name和age,并为Person添加一个方法getInfo,用于输出Person对象的信息:

type Person struct {
  name string
  age int
}

func (p Person) getInfo() {
  print(p.name, p.age)
}

func main() {
  smallyu := new(Person)
  smallyu.name = "smallyu"
  smallyu.age = 1
  smallyu.getInfo()
}

用OOP的思想理解这样的程序并不违和。除了结构体,Go语言还保留有指针的概念。Java程序员对指针可能稍感陌生,关于指针在结构体方法中的应用,可以通过一个简单的例子来了解:

type Person struct {
  name string
}

func (p Person) setName() {
  p.name = "set name"
}

func (p *Person) setName2() {
  p.name = "set name"
}

func main() {
  smallyu := &Person{"smallyu"}
  smallyu.setName()
  fmt.Println(smallyu)        // &{smallyu}

  bigyu := &Person{"bigyu"}
  bigyu.setName2()
  fmt.Println(bigyu)          // &{set name}
}

使用值类型定义的结构体方法,入参为形参;使用引用类型定义的结构体方法,入参为实参。&{}是初始化对象的方法之一,等同于new()

匿名结合

Go语言中匿名结合的概念,相当于OOP语言的继承。一个结构体可以继承另一个结构体的属性和方法,大致是这样。

type Father struct {
  name string
}

func (f Father) getName() {
  print(f.name)
}

type Son struct {
  Father
}

func main() {
  smallyu := &Son{}
  smallyu.name = "smallyu"
  smallyu.getName()       // smallyu
}

Son并没有定义name属性,也没有定义getName()方法,它们均继承自Father。

接口

Go语言的接口是非侵入式的,结构体只要实现了接口中的所有方法,程序就会认为结构体实现了该接口。

type IPerson interface {
  getName()
}

type Person struct {
  name string
}

func (p Person) getName() {
  print(p.name)
}

func main() {
  var smallyu IPerson = &Person{"smallyu"}
  smallyu.getName()
}

协程

使用协程的关键字是go,从命名就能看出协程对于Go语言的重要性、协程是轻量级的线程,启动一个协程非常简单:

func f(msg string) {
  println(msg)
}

func main() {
  f("直接调用方法")
  go f("协程调用方法")
}

运行程序,你会发现程序只打印出”直接调用方法”几个字。这种情况是不是似曾相识?go启用了另一个”线程”来打印消息,而main线程早已结束。在程序末尾加上fmt.Scanln()阻止main线程的结束,就能看到全部的打印内容。

通道

通道即协程之间相互通信的通道。

func main() {
  message := make(chan string)

  go func() {
    message <- "ping"
  }()

  msg := <-message
  println(msg)
}

make函数返回一个chan string类型的通道,在匿名函数中将字符串”ping”传入通道,之后将通道中的数据输出到变量msg,最后打印出msg的值为”ping”。

错误处理

Go语言在错误处理部分有两个函数较为常用,panic函数和defer函数。panic函数会打印错误消息,并终止整个程序的执行,类似Java的Throw Exception;defer函数会在当前上下文环境执行结束前再执行,类似try catch后的finally;panic函数虽然会终止整个程序,但不会终止defer函数的执行,可以将defer函数用于打印日志。这是一个简单的例子:

func main() {
  println("beginning")
  defer func() {
    println("defer")
  } ()
  println("middle")
  panic("panic")
  println("ending")
}

来分析一下程序的执行结果。首先beginning被打印;然后遇到defer,暂不打印;middle在defer之前被打印;遇到panic,程序将终止,打印defer和panic。

这里要注意,defer是在程序结束前执行,而不是在其他语句结束后执行,这是有区别的。就像这里,panic函数引起了当前程序的结束,所以defer会在panic函数前执行,而不是panic后。程序的执行结果如下:

beginning
middle
defer
panic: panic

goroutine 1 [running]:
main.main()
    D:/go/src/awesomeProject/main.go:12 +0x7f

其他

除此之外Go语言还有很多语言特性,也提供了非常多实用的工具包。Go语言是一种值得我们尝试去使用的语言。关于协程和通道,后续会单独探讨这一重要特性。

参考