概述
go的出现主要是在保证c++或java等性能的前提下提升了开发效率,这里可以参考 What is the purpose of the project?。
go的语法主要来自于c family,在此基础上做了大量简化,比如没有class,简单的并发api(goroutine,channel),也因此go的用法和常见语言有比较大的差别,下面以nodejs端的js为例,通过对比对go做一下基本了解。
与js的对比
解释和编译
js是一种解释型语言,为了加快运行效率,node.js中的v8引擎会进行 just-in-time (JIT) 编译,go是一种编译型语言。
类型
由于类型系统的相关概念有争议(弱类型、强类型、动态类型、静态类型语言的区别是什么?),因此这里不会按此分类。
js本身不会有静态类型检查,因此才有了带类型的ts;类型之间可以各种隐式转换,因此也有了大部分语言没有的三个等号的判等===
。
go虽然可以声明时不带类型,但自身包含静态类型检查,不能隐式类型转换,可以使用type_name(expression)
进行强制转换
面向对象
js支持以原型继承为基础的面向对象,并由class语法糖,go中为了简化使用,不包含class等继承方式,不算是一个真正意义上的面向对象语言(Is Go an object-oriented language?)
多线程
js的官方规范ecmascript没涉及线程相关的概念,但无论是node.js还是浏览器传统上是以单线程为基础的异步事件驱动,但是后来html规范推出了web worker,node.js推出了worker_threads,因此也为多线程提供了原生的支持,而go更是以轻量级线程goroutine和消息传递方式channel而擅长高并发。
语法
js的语法和c语言很相似,go的语法自带lint功能,比如不能存在引入但不用的包。
包管理
js有生态繁荣的npm,它是世界上最大的software registry,且yarn和npm包管理工具十分方便,go使用go modules,使用可参考这里,没有深度使用,这里不多展开。
结构
一个基础的go代码,比如
package main
import "fmt"
func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!")
}
包含以下部分
- 包声明 声明当前的包名
- 包引入
- 启动函数,main函数,类似于c语言的main函数。
数据类型
包括基本数据类型和派生类型,其中前者包括
- 布尔型
- 数字
- 字符串 后者包括
- 指针
- 数组
- 切片
- 结构
- 函数
- 接口
- map
- channel
布尔型
布尔类型(bool)只有true和false两个值
数字类型
数字类型在go中分的很细,处理传统的int,还有长度的整数和浮点数以及复数。
字符串
字符串(string)类型就是字符组成的数组,即[]rune
或[]byte
用双引号,其中rune和byte表示单个字符,用单引号。
指针
go中的指针是阉割版的,可以使用&
取址,声明指针,指针赋值,除了不能进行运算其他和c语言类似
数组
go中的数组和c语言类似,用来存放定长数量的某种类型,可以用...
表示数组长度,编译器根据元素个数推论,但数组名是值类型,不是指针
var variable_name [SIZE] variable_type
切片
就是动态数组,可以用未指定长度的数组定义
var identifier []type
或者用make
var slice1 []type = make([]type, len)
结构
还是类似于c语言,用struct关键字
type struct_variable_type struct {
member definition
member definition
...
member definition
}
结构体指针同样使用点来访问成员
结构可以直接初始化,或者使用new,比如
type user struct {
id int `1123`
}
b := user{}
b.id = 222
c := new(user)
c.id = 333
函数
函数使用func关键字定义,参数列表包括参数类型、顺序和个数,函数名和参数列表构成了函数签名。
func function_name( [parameter list] ) [return_types] {
函数体
}
比如
func swap(x, y string) (string, string) {
return y, x
}
接口
c++等面向对象的语言使用class封装数据和方法,go中可以使用结构、接口和函数和创建一个自定义的数据结构。
接口类型可以定义一组方法,用interface关键字,其他类型只要有这些方法就相当于实现了该接口。
结构可以定义一组数据,然后使用函数实现接口的对应方法,完成数据的封装。比如
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
map
map就是字典,存储无序键值对
channel
使用chan关键字,用于两个goroutine之间传递消息。
变量和常量
变量
变量声明方法有好多
- 使用var,其中类型在变量名后面,如果没有类型,编译器会根据后面的初始值推断
var v_name v_type
v_name = value
如果有类型没有初始值,会初始为零值,零值对于不同类型有不同含义,对于数值初始为0,对于布尔值初始化为false,对于字符串初始化为"",其他一般初始化为nil
- 省略var,使用
:=
根据后面的初始值判断类型,用在函数内
v_name := value
- 多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
常量
常量使用const,只能是基本类型,iota是一个特殊常量
作用域
主要包含函数作用域和全局作用域
语句
条件语句
if语句,除了小括号可以省略基本和js一样
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
switch,每个case后默认带break,如果不需要可以使用fallthrough
switch var1 {
case val1:
...
case val2:
...
default:
...
}
select语句是通信中使用的switch
循环语句
for循环可以使用range迭代数组(array)、切片(slice)、通道(channel)或集合(map),有三种形式
第一种和js中的for一样
for init; condition; post { }
第二种和js中的while一样
for condition { }
第三种就是无限循环类似于for { }
for { }
使用range时,以字符串为例
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.Println(i, s)
}
其中i代表index,s代表value
完结