GO的优缺点:
此处引用华为云开发者联盟的一篇文章:
GO语言的亮点很明显:
-
GoDoc。 GoDoc的静态语言分析能力很强大,可以直接从代码和注释生成漂亮的文档。这一点区别于其他的类似工具如JavaDoc, PHPDoc或者JSDoc。这些工具需要添加额外的注解,比较麻烦。
-
GoFmt。代码格式化一直是程序员编码的痛点,主要的困境在于没有统一的标准,Go通过内置的GoFmt工具来解决这个问题。
-
GoLint。代码语法提示也在Go中通过GoLint工具进行了统一。
-
测试框架内置。这一点区别于其他的流行语言如Java, C#, Javascript,他们需要选择测试框架进行测试代码编写。而Go语言直接内置了测试框架,可以程序员快速生成测试框架代码,省时,省力。
-
GoRoutines的并行化处理能力。Go对于并行化的支持做得非常彻底。直接把繁琐的线程创建封装起来,程序员无需担心线程创建中可能遭遇的硬件资源不足的问题。
-
使用Interface支持多态。在Go语言中省去了面向对象编程中父类继承的特征。在使用多态的地方使用Interface的模式实现多态,这样把代码结构线性化、平行化,从而降低了代码的复杂度。
GO语言的缺点:
1.异常和错误处理啰嗦。如果你习惯了使用异常处理,那么你就会很讨厌Go里面的错误处理方式。
-
你定义一个Go函数,返回一个错误;
-
调用这个函数时,你要判断返回错误是否为空;
-
-
-
然后判断是哪种错误
-
根据哪种错误进行相应的处理
-
-
-
这样的处理实在是啰嗦
2.空值判断。
-
在Go中指针类型可以是空,如果一个函数返回指针,在Go中,你需要进行上述第一条所说的啰嗦的错误处理,然后才可以使用指针。否则,如果这个指针为空,你使用它的话,会Crash。
3.作用域的限定比较另类。
-
大写字母开头可以全局访问,小写字母开头只能在当前文件可见。
-
Go语言假定每个程序员都清楚the whole picture, 这在实际工作场景中是不现实的。
-
为了践行防御性编程理念,在Go中,不同程序员不得不创建大量的文件和目录。这样也不利于管理。
4.Go语言缺乏不可改写机制。
-
这是可能是因为它强调性能高于潜在的bug的规避。
5.Go语言缺乏泛型支持,我们不得不对潜在的数据类型进行转换。
6.Go语言缺乏继承机制,这导致共性功能的代码重用很难。
7.Go语言没有枚举类型,类似的功能不得不使用const,很不方便。
Go语言的应用场景主要集中在后端应用层和工具类开发,用来写单体服务,微服务以及工具。Go语言相对比较年轻,需要经过长时间的洗礼,大量的项目开发验证。目前来看,还无法与Java, C#这类语言的生态进行竞争。
综上,使用哪种语言是一种选择,适宜、简洁、高效、安全是核心。
作者:华为云开发者联盟 链接:为什么 Go 语言在某些方面的性能还不如 Java? - 知乎 来源:知乎
package main
import "fmt"
func main() {fmt.Println("hello world")
}
包的概念
-
每个 Go 文件都属于且仅属于一个包。一个包可以由许多以
.go
为扩展名的源文件组成。 -
你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:
package main
。package main
表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为main
的包。 -
一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。另外要注意的是,所有的包名都应该使用小写字母。
-
如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
导包格式:
import "fmt"
import "os"
import "fmt"; import "os"
import ("fmt"; "os")
可见性规则:
当标识符以一个大写字母开头则等同于java中的public,以小写字母开头则等同于private
因此,在导入一个外部包后,能够且只能够访问该包中导出的对象。假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。
也可以对包名进行重新设置,如:import fm "fmt"
。
package main
import fm "fmt"
func main() {fm.Println("hello world")
}
注意事项:
-
如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如
imported and not used: os
,这正是遵循了 Go 的格言:“没有不必要的代码!“。
函数
func functionName();
在 ()
中写入 0 个或多个函数的参数(使用逗号 ,
分隔),每个参数的名称后面必须紧跟着该参数的类型。
main函数:
-
main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init () 函数则会先执行该函数)。
-
main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)
函数体:
-
函数里的代码(函数体)使用大括号 {} 括起来。
-
左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会出现错误提示。
-
右大括号 } 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行:
特别注意: Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发像上面这样的错误
符合规范的函数一般写成如下的形式:
func functionName(parameter_list) (return_value_list) {…
}
程序正常退出的代码为 0 即 Program exited with code 0
;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。
package main
import "fmt"
func main() {fmt.Println("hello world")//result := Sum(1, 2)fmt.Println("Sum result:", Sum(1, 3))fmt.Println("____________________")fmt.Println(swap("hello", "world"))
}
func swap(x, y string) (string, string) {return y, x
}
func Sum(a, b int) int {return a + b
}
类型在变量声明之后
注释
单行注释是最常见的注释形式,你可以在任何地方使用以 //
开头的单行注释。多行注释也叫块注释,均已以 /*
开头,并以 */
结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
规范:
-
在首行的简要注释之后可以用成段的注释来进行更详细的说明,而不必拥挤在一起。
-
在多段注释之间应以空行分隔加以区分。
类型
-
可以包含数据的变量(或常量),可以使用不同的数据类型或类型来保存数据。
-
使用 var 声明的变量的值会自动初始化为该类型的零值。
-
类型可以是基本类型,如:int、float、bool、string;
-
结构化的(复合的),如:struct、array、slice、map、channel;(nil 为默认值,在 java 中为null)
-
只描述类型的行为的,如:interface。
注意 Go 语言中不存在类型继承。
函数也可以是一个确定的类型,就是以函数作为返回类型。这种类型的声明要写在函数名和可选的参数列表之后,例如:
func FunctionName (a typea, b typeb) typeFunc
你可以在函数体中的某处返回使用类型为 typeFunc 的变量 var:
return var
一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
实例:
package main
import "fmt"
// 声明一个函数类型
type Operator func(int, int) int
// 定义一个加法函数
func Add(a, b int) int {return a + b
}
// 定义一个减法函数
func Subtract(a, b int) int {return a - b
}
func main() {// 使用函数类型作为变量类型var op Operator
// 将加法函数赋值给函数变量op = Addresult := op(5, 3)fmt.Println("5 + 3 =", result)
// 将减法函数赋值给函数变量op = Subtractresult = op(5, 3)fmt.Println("5 - 3 =", result)
}
package main
import ("fmt"
)
const c = "C"
var v int = 5
type T struct{}
func init() { // initialization of package
}
func main() {var a intFunc1()// ...fmt.Println(a)
}
func (t T) Method1() {//...
}
func Func1() { // exported function Func1//...
}
Go 程序的执行(程序启动)顺序如下:
-
按顺序导入所有被 main 包引用的其它包,然后在每个包中执行如下流程:
-
如果该包又导入了其它的包,则从第一步开始递归执行,但是每个包只会被导入一次。
-
然后以相反的顺序在每个包中初始化常量和变量,如果该包含有 init 函数的话,则调用该函数。
-
在完成这一切之后,main 也执行同样的过程,最后调用 main 函数开始执行程序。
类型转换
Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
valueOfTypeB = typeB(valueOfTypeA)
类型 B 的值 = 类型 B (类型 A 的值)
package main
import ("fmt""reflect"
)
func main() {a := 5.0b := int(a)fmt.Println("the type of a is: ", reflect.TypeOf(a))fmt.Println("the type of a is: ", reflect.TypeOf(b))
}
赋值:
在Go语言中,赋值操作使用 =
运算符。赋值操作符将右侧表达式的值赋给左侧的变量。下面是一些关于赋值的基本用法:
单个变量赋值
package main
import "fmt"
func main() {// 单个变量赋值var a inta = 10
fmt.Println(a)
}
在这个例子中,我们声明了一个整数变量 a
,然后将值 10
赋给变量 a
。
多个变量同时赋值
package main
import "fmt"
func main() {// 多个变量同时赋值var b, c intb, c = 20, 30
fmt.Println(b, c)
}
在这个例子中,我们声明了两个整数变量 b
和 c
,然后使用逗号分隔的方式同时给它们赋值。
简短声明并赋值
package main
import "fmt"
func main() {// 简短声明并赋值d := 40
fmt.Println(d)
}
在这个例子中,我们使用 :=
运算符进行了简短声明并赋值操作,声明了一个整数变量 d
并赋值为 40
。
注意:
-
如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明
-
函数外的每个语句都必须以关键字开始(
var
,func
等等),因此:=
结构不能在函数外使用。
匿名变量
在Go语言中,可以使用下划线 _
来表示匿名变量,用于占位,表示不关心这个值。
package main
import "fmt"
func main() {_, e := 50, 60
fmt.Println(e)
}
在这个例子中,我们使用匿名变量 _
来占位,不关心第一个赋值的值,只关心第二个值 e
。
如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a。
(在 Go 语言中,这样省去了使用交换变量的必要)
空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
命名返回值
Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
返回值的名称应当具有一定的意义,它可以作为文档使用。
没有参数的 return
语句返回已命名的返回值。也就是 直接
返回。
直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。
package main
import "fmt"
func split(sum int) (x, y int) {x = sum * 4 / 9y = sum - xreturn
}
func main() {fmt.Println(split(17))
}
变量
声明变量的一般形式是使用 var
关键字:var identifier type
。
var
语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
就像在这个例子中看到的一样,var
语句可以出现在包或函数级别。
package main
import "fmt"
var c, python, java bool
func main() {var i intfmt.Println(i, c, python, java)
}
是为了避免像 C 语言中那样含糊不清的声明形式,例如:int* a, b;
。在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在 Go 中,则可以很轻松地将它们都声明为指针类型:
var a, b *int
两种写法:
-
var a int var b bool var str string
-
var (a intb boolstr string )
这种因式分解关键字的写法一般用于声明全局变量。
注:变量的命名规则遵循骆驼命名法。如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写
格式化输出:
格式化字符串可以含有一个或多个的格式化标识符,例如:%..,其中 .. 可以被不同类型所对应的标识符替换,如 %s 代表字符串标识符、%v 代表使用类型的默认输出格式的标识符。这些标识符所对应的值从格式化字符串后的第一个逗号开始按照相同顺序添加,如果参数超过 1 个则同样需要使用逗号分隔
init 函数
变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。(类似java中的构造函数?)
每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。
基础类型:
布尔类型 bool
var b bool = ture
布尔型的值只可以是常量 true 或者 false。
两个类型相同的值可以使用相等 ==
或者不等 !=
运算符来进行比较并获得一个布尔型的值。
规范:对于布尔值而言,好的命名能够很好地提升代码的可读性。例如以 is 或者 Is 开头的 isSorted、isFinished、isVisible,使用这样的命名能够在阅读代码的获得阅读正常语句一样的良好体验
整型和浮点类型
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的:
int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。 uintptr 的长度被设定为足够存放一个指针即可。 Go 语言中没有 float 类型。(Go 语言中只有 float32 和 float64)没有 double 类型。
-
整数:
-
int8(-128 -> 127)
-
-
int16(-32768 -> 32767)
-
int32(-2,147,483,648 -> 2,147,483,647)
-
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
-
无符号整数:
-
uint8(0 -> 255)
-
-
uint16(0 -> 65,535)
-
uint32(0 -> 4,294,967,295)
-
uint64(0 -> 18,446,744,073,709,551,615)
-
浮点型(IEEE-754 标准):
-
float32(+- 1e-45 -> +- 3.4 * 1e38)
-
float64(+- 5 1e-324 -> 107 1e308)
-
int 型是计算最快的一种类型。
-
注意:
-
整型的零值为 0,浮点型的零值为 0.0。
-
float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。(尽可能地使用 float64,因为
math
包中所有有关数学运算的函数都会要求接收这个类型)
你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
复数
Go 拥有以下复数类型:
-
complex64 (32 位实数和虚数)
-
complex128 (64 位实数和虚数)
复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1。
函数 real(c)
和 imag(c)
可以分别获得相应的实数和虚数部分。
在使用格式化说明符时,可以使用 %v
来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f
。
字符串
字符串是 UTF-8 字符的一个序列,由于该编码对占用字节长度的不定性,Go 中的字符串也可能根据需要占用 1 至 4 个字节。(java始终为2字节)
字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。(字符串0值是 " ")
和 C/C++ 不一样,Go 中的字符串是根据长度限定,而非特殊字符 \0
。(和redis sds很像嘛,哈哈)
一般的比较运算符(==、!=、<、<=、>=、>)通过在内存中按字节比较来实现字符串的对比。你可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)。
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:
-
字符串 str 的第 1 个字节:str[0]
-
第 i 个字节:str[i - 1]
-
最后 1 个字节:str[len(str)-1]
注意:
-
这种转换方案只对纯 ASCII 码的字符串有效。
-
获取字符串中某个字节的地址的行为是非法的,例如:&str[i]。
字符串拼接符 +
两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。
s2 追加在 s1 尾部并生成一个新的字符串 s。
字符串api:4.7. strings 和 strconv 包 | 第四章. 基本结构和基本数据类型 |《Go 入门指南》| Go 技术论坛 (learnku.com)
时间和日期
time
包为我们提供了一个数据类型 time.Time
(作为值使用)以及显示和测量时间和日期的功能函数。
当前时间可以使用 time.Now() 获取,或者使用 t.Day()、t.Minute() 等等来获取时间的一部分;你甚至可以自定义时间格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 将会输出 21.07.2011。
常量
常量的声明与变量类似,只不过是使用 const
关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 :=
语法声明。
指针
程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b0820 或 0xf84001d7f0。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
例:
package main
import "fmt"
func main() {var a intfmt.Println("the address of a is: ", &a)
}
一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。
你可以在指针类型前面加上 * 号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
一个指针变量通常缩写为 ptr。
位运算
位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。
%b
是用于表示位的格式化标识符。
二元运算符
与java类似,主要注意一点:位清除 &^
:将指定位置上的值设置为 0。
运算符
同时,带有 ++ 和 -- 的只能作为语句,而非表达式,因此 n = i++ 这种写法是无效的,其它像 f(i++) 或者 a[i]=b[i++] 这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。
格式化说明
在格式化字符串里:
-
%d 用于格式化整数(%x 和 %X 用于格式化 16 进制表示的数字),
-
%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),
-
%0d 用于规定输出定长的整数,其中开头的数字 0 是必须的。
-
%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f。
-
%c
用于表示字符;当和字符配合使用时,%v
或%d
会输出用于表示该字符的整数; -
%U
输出格式为 U+hhhh 的字符串 -
%T 返回值的type。
-
%p
指针的格式化标识符 -
%c 格式化输出char
学习参考资料:
《Go 入门指南》 | Go 技术论坛 (learnku.com)
Go 语言之旅