初探GO语言笔记
关于GO
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go 被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
第一次接触GO之前,笔者已经有了C/C++/Java/Python等的编程经验,因此,在上手GO的基础语法时觉得没什么难度。推荐新入门GO的小伙伴可以联系一下上面几门语言最具特色的地方,开启一段GO的旅程!
GO的安装和IDE安装
这里都是傻瓜式安装,GO安装直接到官网上下载即可,一般安装时会自动将GO的环境变量加入到系统Path中,IDE推荐JBrain的GoLand IDE,和IDEA环境一样,简直不要太爽!
GO的语言结构和基础语法
一个简单的GO程序如下,一般GO的语言结构包含:包声明、引入包、函数、变量、语句、注释。
package main // 包声明
import "fmt" // 引入包
func main() { // 函数
fmt.Println("Hello, Go!")
}
要执行Go程序,首先保存文件为xxx.go,在命令行输入go xxx.go
或者直接在IDE中单击绿色小箭头即可运行。
一些基础语法:
-
无需分号结尾,跟Python一样用分行代表一个语句
-
右花括号 { 不可以单独一样,例如下面的例子是不行的:
func main() { xxx... } // error
-
标识符:按照C++/JAVA标准来命名就好
-
字符串连接:+号
-
关键字:参见 https://www.runoob.com/go/go-basic-syntax.html
GO的数据类型
常说 JAVA有八大基本数据类型(long,int,short,boolean,char,byte,float,double),GO的类型其实也类似,只是换个名字而已,参见 https://www.runoob.com/go/go-data-types.html
GO的变量、常量与运算符
变量
以一个例子来记住声明的格式:
package main // 包声明
import "fmt" // 引入包
func main() { // 函数
var a = "This is a string"
fmt.Print(a)
}
是不是有点类似于JavaScript的格式?
也可以不使用var,直接使用val := 1
即可,但注意这种写法仅能用在声明,不可用在再次赋值,否则出错。
关于值类型和引用类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值;当使用等号
=
将一个变量的值赋值给另一个变量时,如:j = i
,实际上是在内存中将 i 的值进行了拷贝。更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
常量
GO中的常量数据类型只能是数字类型、布尔类型和字符串型,一般采用const xxx=yyy
即可。
运算符
与C/JAVA一致
GO的条件与循环
条件
- if
- if-else
- if嵌套
- switch
- select(无对应的case会阻塞当前进程)
循环
- for
- for嵌套
- break,continue,goto
GO的函数与变量作用域
函数
一个GO文件必须有main()函数,一个函数一般由几个部分组成:func函数声明、函数名、参数列表、返回类型、函数体
package main // 包声明
import "fmt" // 引入包
func min(x, y int) int {
var result int
if (x > y) {
result = y
} else {
result = x
}
return result
}
func main() { // 函数
fmt.Print(min(5,3))
}
关于传参:
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
变量作用域
Go 语言中变量可以在三个地方声明:
- 函数内定义的变量称为局部变量,作用域只在函数体内
- 函数外定义的变量称为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用;Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
- 函数定义中的变量称为形式参数
GO的数据结构
数组
初始化
// method 1
var arr = [5]float32{xxx,xxx,xxx,xxx,xxx}
// method 2
arr := [5]float32{xxx,xxx,xxx,xxx,xxx}
访问
使用[]
运算符即可访问,这与C/JAVA是一致的。
指针
GO的指针用法与C一致,GO中的空指针定义为nil。
结构体
和C一样,定义字段,同时用点运算符完成赋值或计算,下面是一个例子:
package main // 包声明
import "fmt" // 引入包
type Language struct {
name string
difficulty string
}
func main() { // 函数
a := Language{"C++", "hard"}
b := Language{"JAVA", "hard"}
c := Language{"GO", "medium"}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
/*
output:
{C++ hard}
{JAVA hard}
{GO medium}
*/
切片
这里的切片和Python中的切片概念是一样的,非常简单,参见 https://www.runoob.com/go/go-slice.html 学习。
Map集合
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。通过一个实例就可以初步掌握Map的用法:
package main // 包声明
import "fmt"
func main() { // 函数
var stuinfo map[string]int
stuinfo = make(map[string]int)
stuinfo["小A"] = 1
stuinfo["小B"] = 2
stuinfo["小C"] = 3
stuinfo["小D"] = 4
for name := range stuinfo {
fmt.Println(name, "的座号是:", stuinfo[name])
}
}
/*
output:
小C 的座号是: 3
小D 的座号是: 4
小A 的座号是: 1
小B 的座号是: 2
*/
GO的接口
GO的接口和JAVA接口意义一致,都是提供了一个待实现的API,留给使用接口的struct实现并使用。下面是接口使用的一个例子:
package main // 包声明
import "fmt"
type Animal interface {
walk()
}
type Cat struct {
}
func (cat Cat) walk() {
fmt.Println("cat walk")
}
type Dog struct {
}
func (dog Dog) walk() {
fmt.Println("dog walk")
}
func main() { // 函数
var animal Animal
animal = new(Cat)
animal.walk()
animal = new(Dog)
animal.walk()
}
/*
output:
cat walk
dog walk
*/
GO的异常处理
核心在于自己实现一个带error interface的struct类型。
package main
import "fmt"
var arr = [5]int{1,2,3,4,5}
type ArrayIndexOutOfBoundException struct {
index int
}
func (exception *ArrayIndexOutOfBoundException) Error() string {
return "ArrayIndexOutOfBoundException"
}
func visit(index int) (resultCode int, errMsg string) {
if index < 5 {
return arr[index], ""
} else {
err := ArrayIndexOutOfBoundException{
index: index,
}
errMsg = err.Error()
return
}
}
func main() { // 函数
if resultCode, errMsg := visit(3); errMsg == "" {
fmt.Println(resultCode)
}
if _, errMsg := visit(10); errMsg != "" {
fmt.Println(errMsg)
}
}
/*
output:
4
ArrayIndexOutOfBoundException
*/
GO并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。同一个程序中的所有 goroutine 共享同一个地址空间。使用多线程的一个简单例子如下:
package main
import (
"fmt"
"time"
)
func thread1() {
for i := 0; i < 10; i++ {
time.Sleep(time.Millisecond * 100)
fmt.Println("in thread_1")
}
}
func thread2() {
for i := 0; i < 10; i++ {
time.Sleep(time.Millisecond * 100)
fmt.Println("in thread_2")
}
}
func main() { // 函数
go thread1()
thread2()
}
/*
output:
in thread_1
in thread_2
in thread_2
in thread_1
in thread_1
in thread_2
in thread_1
in thread_2
in thread_1
in thread_2
in thread_2
in thread_1
in thread_1
in thread_2
in thread_2
in thread_1
in thread_1
in thread_2
in thread_2
*/
通道
通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。下面是使用通道的一个例子;
package main
import "fmt"
func thread1(c chan int) {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
c <- sum
}
func thread2(c chan int) {
sum := 0
for i := 0; i < 10; i++ {
sum -= i
}
c <- sum
}
func main() { // 函数
channel := make(chan int)
go thread1(channel)
go thread2(channel)
rec1, rec2 := <-channel, <-channel
fmt.Println("result: ", rec1, rec2)
}
/*
output:
result: -45 45
*/
通道还可以设置缓冲区,可通过 make 的第二个参数指定缓冲区大小。
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
以上就是本期GO基础知识的全部内容了。诚然,GO需要我们学习的地方还有很多,后面咱们再聊!