基础
- 语句分割:go可以直接用换行作为语句分割符,也可以和java一样用分号
- 代码块:go的{不能在单独行(会编译错误),java也有这样的格式化规范(只是规范,编译不会报错)
- 字符串拼接:和java一样通过+号进行
- 空格:和java类似,用于分隔标识符、关键字、运算符和表达式,以提高代码的可读性
类
go没有真正意义的类,一切皆方法,方法可以聚合在一起,可导出
关于包
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
- 通过package定义包,每个 Go 应用程序都包含一个名为 main 的包
- 通过 import导入包
- main和java的main类似
注释
- 单行注释:// 和java一样
- 多行注释:/* */ 和java一样
命名规范
- 驼峰命名
- 首字母大写代表外部可见,类似java的public关键字
- 首字母小写代表包内可见,类似java的protected
- 标识符:和java类似,第一个字符必须是字母或下划线而不能是数字
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
break:同java | default | func:定义函数 | interface:同java | select |
---|---|---|---|---|
case | defer | go | map | struct:定义结构体 |
chan | else:同java | goto | package:类似java | switch:同java |
const:定义常量 | fallthrough | if:同java | range | type |
continue | for:同java | import:同java | return:同java | var:定义变量 |
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
数据类型
-
布尔类型:var b bool = true,类似java boolean
-
整数数字类型:go有无符号类型,java没有
| 序号 | 类型和描述 |
| — | — |
| 1 | uint8
无符号 8 位整型 (0 到 255) |
| 2 | uint16
无符号 16 位整型 (0 到 65535) |
| 3 | uint32
无符号 32 位整型 (0 到 4294967295) |
| 4 | uint64
无符号 64 位整型 (0 到 18446744073709551615) |
| 5 | int8
有符号 8 位整型 (-128 到 127)
等于java的byte |
| 6 | int16
有符号 16 位整型 (-32768 到 32767)
等于java的short |
| 7 | int32
有符号 32 位整型 (-2147483648 到 2147483647)
等于java的int |
| 8 | int64
有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
等于java的long | -
浮点类型
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 等于java的float |
2 | float64 IEEE-754 64位浮点型数 等于java的double |
3 | complex64 32 位实数和虚数 java没有相应的类型 |
4 | complex128 64 位实数和虚数 java没有相应的类型 |
- 其他数字类型
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 32位处理器为32位,64位处理器为64位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
运算符
算术运算符
运算符 | 描述 | 备注 |
---|---|---|
+ | 相加 | |
- | 相减 | |
* | 相乘 | |
/ | 相除 | |
% | 求余 | |
++ | 自增 | go没有java中 ++i这种语法 同时只能作为表达式,不能赋值 a=a++编译不过 |
– | 自减 | go没有java中 --i这种语法 |
关系运算符
运算符两边的值类型要一致,不一致编译会报错(java的可以不一致)
运算符 | 描述 | 备注 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 |
逻辑运算符
和java语法一致
运算符 | 描述 | 备注 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 |
位运算符
和java语法一致
- &:且运算符,双1才1,有0则0
- |:或运算符,双0才0,有1则1
- ^:异或运算符,相同0,相异1
假定 A 为60,B 为13
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符"<<“是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<“左边的运算数的各二进位全部左移若干位,由”<<"右边的数指定移动的位数,高位丢弃,低位补0。 | A << 2 结果为 240 ,二进制为 1111 0000 |
>> | 右移运算符">>“是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数。 | A >> 2 结果为 15 ,二进制为 0000 1111 |
赋值运算符
和java语法一致
运算符 | 描述 | 备注 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | |
+= | 相加后再赋值 | |
-= | 相减后再赋值 | |
*= | 相乘后再赋值 | |
/= | 相除后再赋值 | |
%= | 求余后再赋值 | |
<<= | 左移后赋值 | |
>>= | 右移后赋值 | |
&= | 按位与后赋值 | |
^= | 按位异或后赋值 | |
|= | 按位或后赋值 |
其他运算符
与指针相关(和c类似,java无此运算符)
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低
优先级 | 运算符 |
---|---|
4 | * / % << >> & &^ |
3 | + - | ^ |
2 | == != < <= > >= |
1 | &&、|| |
可以通过()来提升优先级(建议方式)
变量
变量声明
- 声明变量的一般形式是使用 var 关键字
- var identifier type(java的是type identifier)
- 可以一次声明多个变量,var identifier1, identifier2 type
var name string ="jake"
var a,b = 1,2
- 变量只声明,不赋值,则默认为零值:零值就是变量没有做初始化时系统默认设置的值(和java类似)
- 数值类型(包括complex64/128)为 0
- 布尔类型为 false
- 字符串为 “”(空字符串)
- 以下几种类型为 nil(nil类型java的null)
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
- 声明变量时如果省略了type,则根据值进行判定
var a = true
var b = "test"
var c = 1
- 推荐方式,通过 := (本身是一个声明+赋值的关键字)可以省略var关键字, :=左侧的关键字不能是被声明过的,否则会编译错误,同时只能在函数体重存在
a := true
b := "test"
c := 1
- 因式分解关键字:一般用于全局变量声明
package main
import "fmt"
var ( // 这种写法一般用于全局变量声明
a int
b string
)
func main(){
}
- 函数体内定义的变量必须被使用,否则会报错(java不会报错),全局变量允许声明但不使用的
- 多个变量可以在同一行赋值:通常用于获取函数的多个返回值
a, b, c := 1, true, "test"
- 抛弃值:通过空白标识符进行,如值 1 在:_, b = 1, 2 中被抛弃,通常用于获取函数多个返回值的一个
常量
- 常量是一个简单值的标识符,在程序运行时,不会被修改的量(类似java的 final值)
- 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型(java的常量可以是任何类型)
- 定义格式:const identifier [type] = value
const A string = "aa"
const B = "aa"
- 常量命名规范和java类型,单词大写,下划线分割单词
-
值类型和引用类型
和java类似,
- int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值
- 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。这个内存地址称之为指针,这个指针实际上也被存在另外的某一个值中
变量作用域
Go 语言中变量可以在三个地方声明
- 函数内定义的变量称为局部变量
- 函数外定义的变量称为全局变量
- 函数定义中的变量称为形式参数
控制语句
条件语句
if语句
和java类似,不过条件不需要使用()
package main
import "fmt"
func main() {
a := 10
if a < 20 {
fmt.Printf("a 小于 20\n" )
}
fmt.Printf("a 的值为 : %d\n", a)
}
和java不同,if 条件语句还可以进行变量定义,变量的作用范围在语句块里面
package main
import "fmt"
func main() {
if i, j := 20, 30; i > 20 && j > 20 {
fmt.Println(i, j)
}
}
switch语句
语法如下,变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式
switch var1 {
case val1:
...
case val2:
...
default:
...
}
整体上看,表达力比java的强,同时不需要break语句,进入case就会break
func main() {
/* 定义局部变量 */
var grade string = "B"
var marks int = 90
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch {
case grade == "A" :
fmt.Printf("优秀!\n" )
case grade == "B", grade == "C" :
fmt.Printf("良好\n" )
case grade == "D" :
fmt.Printf("及格\n" )
case grade == "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" );
}
fmt.Printf("你的等级是 %s\n", grade );
}
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型interface{}类似java中的Object类型
package main
import "fmt"
func main() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
}
fallthrough:类似java case没有break语句,因为go不需要写break,所以强制执行下一个只能用fallthrough标识
package main
import "fmt"
func main() {
i := 6
switch {
case i == 6:
fmt.Println(6)
fallthrough
case i == 5:
fmt.Println(5)
}
}
输出:6 5,和一下java代码类似
int i = 6;
switch (i) {
case 6:
System.out.println(6);
case 5:
System.out.println(5);
break;
}
select语句
go特有,java无,用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收,后续学了通道再不出
循环语句
go的循环语句只有for,java除了for还有while
go的for循环有三种形式
- for init; condition; post { }:和java类似,init和post是可选的
- for condition { }:和java的while类似
- for { }:和java的for(;;)或while(true)类似
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:和java的foreach语法
for key, value := range oldMap {
newMap[key] = value
}
numbers := [6]int{1, 2, 3, 5}
//i是数组下标,v是值
for i,v:= range numbers {
fmt.Println(i,v)
}
以上代码中的 key 和 value 是可以省略
- 如果只想读取 key,格式为:for key := range oldMap或者for key, _ := range oldMap
- 如果只想读取 value,格式如下:for _, value := range oldMap
函数
函数语法
函数类似java的方法,定义如下
func function_name( [parameter list] ) [return_types] {
//函数体
}
package main
import "fmt"
func main() {
fmt.Println(add(1, 2))
}
func add(a, b int) int {
return a + b
}
函数可以返回多个值,java的方法只能返回一个值
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("a", "b")
fmt.Println(a, b)
}
函数参数
默认为值传递,可以通过传指针,来达到在行数中修改参数值(java无指针)
package main
import "fmt"
func main() {
x := 0
unModify(x)
fmt.Println(x)
modify(&x)
fmt.Println(x)
}
func unModify(x int) {
x++
}
func modify(x *int) {
*x++
}
//运行的输出结果为
0
1
函数用法
函数可以作为实参传递,类似java中的Function
package main
import (
"fmt"
"math"
)
func main(){
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
fmt.Println(getSquareRoot(9))
}
//结果为4
闭包:闭包是一个匿名函数,匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明,java无对应语法
package main
import "fmt"
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
//执行结果为
1
2
3
1
2
函数方法:类似java重的方法,go中没有类的概念,java中的类包含两部分,数据和方法,go语法中这两部分是分离的,数据通过结构体定义,方法通过函数方法定义,函数方法格式为
func (variable_name variable_data_type) function_name([parameter list]) [return_type]{
/* 函数体*/
}
package main
func main() {
r := Rectangle{2, 3}
println(r.test(2))
}
type Rectangle struct {
l, w int
}
func (r Rectangle) test(i int) int {
return r.l * r.w * i
}
//输出结果为12
数据结构
指针
go的指针和c语言指针类似,指针代表的就是变量的内存地址,指针通过&获取
package main
import "fmt"
func main() {
var a int = 10
fmt.Println(&a)
}
指针声明:var var_name *var-type
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
指针使用
package main
import "fmt"
func main() {
var a int = 1
var p *int = &a
*p = 2
fmt.Println(a, *p, p, &a)
}
//输出结果为:2 2 0xc0000a6058 0xc0000a6058
空指针:当一个指针被定义后没有分配到任何变量时,它的值为 nil
package main
import "fmt"
func main() {
var a *int
fmt.Println(a == nil)
}
//输出:true
空指针判断
var ptr *int
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
指向指针的指针:如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量(指针是一个地址,也需要存储的)
package main
import "fmt"
func main() {
var a int = 3
var ptr *int = &a
var pptr **int = &ptr
fmt.Println(ptr, *pptr, pptr, *ptr, **pptr)
}
//输出:0xc000018098 0xc000018098 0xc00000a028 3 3
指针参数:类似java的引用传递,通过指针参数就可以修改指针值了
package main
import "fmt"
func main() {
x, y := 1, 2
swap(&x, &y)
fmt.Println(x, y)
}
func swap(x, y *int) {
*x, *y = *y, *x
}
//输出:2 1
数组
声明数组:var variable_name [SIZE] variable_type
var arr [10] int
初始化数组
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//...也可以省略
balance := []float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//通过下标来初始化元素,将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}
多维数组:和java类似
package main
import "fmt"
func main() {
// Step 1: 创建数组
values := [][]int{}
// Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
values = append(values, row1)
values = append(values, row2)
// Step 3: 显示两行数据
fmt.Println("Row 1")
fmt.Println(values[0])
fmt.Println("Row 2")
fmt.Println(values[1])
// Step 4: 访问第一个元素
fmt.Println("第一个元素为:")
fmt.Println(values[0][0])
//二维数组初始化
a := [][]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
}
}
数组作为参数传递:数组作为参数传递,依然是值传递,这里和java不一样,java是引用传递
import "fmt"
func main() {
a := [3]int{1, 2, 3}
test(a)
fmt.Println(a)
}
func test(arr [3]int) {
arr[0] = 3
}
//输出结果为:[1 2 3]
要想在传入函数中修改数组值,需要用指针
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
test(&a)
fmt.Println(a)
}
func test(arr *[]int) {
(*arr)[0] = 3
}
//输出结果为[3 2 3]
结构体
和c的结构体类似,类似java中只有属性没有方法的类
定义格式如下
type struct_variable_type struct {
member definition
member definition
...
member definition
}
结构体使用示例代码
package main
import "fmt"
func main() {
fmt.Println(Student{"tom", 18})
fmt.Println(Student{name: "tom"})
fmt.Println(Student{age: 18})
stu := Student{"tom", 18}
fmt.Println(stu.name, stu.age)
fmt.Println(stu.Desc())
}
type Student struct {
name string
age int
}
//给结构增加函数方法
func (stu Student) Desc() string {
return fmt.Sprintf("name=%s,age=%d", stu.name, stu.age)
}
//输出为
{tom 18}
{tom 0}
{ 18}
tom 18
name=tom,age=18
结构体指针:格式如下
stu := Student{"tom", 18}
var stu_ptr *Student = &stu
结构体指针使用示例
func main() {
stu := Student{"tom", 18}
valueRef(stu)
fmt.Println(stu)
pointerRef(&stu)
fmt.Println(stu)
}
type Student struct {
name string
age int
}
func valueRef(stu Student) {
stu.name = "bob"
}
func pointerRef(stu *Student) {
stu.name = "bob"
}
//输出
{tom 18}
{bob 18}
对于结构体属性命名,首字母大小写决定了访问访问,如果要在其他包访问,属性名称首字母要大写
go语言切片(Slice)
:::info
有点类似java的ArrayList,语言切片是对数组的抽象,数组的长度不可改变,内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大
:::
切片定义
//未指定大小的数组切片
var identifier []type
//使用 make() 函数来创建切片
slice1 := make([]type, len)
//也可以指定容量,其中 capacity 为可选参数
make([]T, length, capacity)
切片初始化:
s :=[] int {1,2,3 } //cap=len=3
s := arr[:] //初始化切片 s,是数组 arr 的引用
s := arr[startIndex:endIndex] //将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片
s := arr[startIndex:] //默认 endIndex 时将表示一直到arr的最后一个元素
s := arr[:endIndex] //默认 startIndex 时将表示从 arr 的第一个元素开始
len()和cap()函数:len() 代表元素数量,cap() 底层数组中能够容纳的元素数量
详解见:https://blog.csdn.net/bibihenmuc/article/details/131135133
Map集合
数据结构和java的HashMap类似,但是在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 “”(java返回的是null)
Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量
Map定义
// 创建一个空的 Map
m := make(map[string]int)
// 创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)
// 使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
Map使用
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
// 修改键值对
m["apple"] = 5
// 获取 Map 的长度
len := len(m)
// 遍历 Map
for k, v := range m {
//
}
// 删除键值对
delete(m, "banana")
递归
语法和java类似,示例,实现斐波那契数列
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("%d ", fibonacci(i))
}
}
func fibonacci(n int) int {
if n < 2 {
return n
} else {
return fibonacci(n-2) + fibonacci(n-1)
}
}
类型转换
语言类型转换基本格式:type_name(expression),数值类型java语法可以自动转换
var a int = 10
var b float64 = float64(a)
字符串转换
var str string = "10"
var num int
num, _ = strconv.Atoi(str)
整数转换为字符串
num := 123
str := strconv.Itoa(num)
字符串转换为浮点数
str := "3.14"
num, err := strconv.ParseFloat(str, 64)
浮点数转换为字符串
num := 3.14
str := strconv.FormatFloat(num, 'f', 2, 64)
接口类型转换
接口类型转换有两种情况:类型断言和类型转换
类型断言用于将接口类型转换为指定类型,其语法为:value.(type) 或value.(T)
package main
import "fmt"
func main() {
var i interface{} = "Hello, World"//interface{} 有点像java的Object
str, ok := i.(string)
if ok {
fmt.Printf("'%s' is a string\n", str)
} else {
fmt.Println("conversion failed")
}
}
接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。go中没有java继承这种语法,继承的复用通过组合模式来实现。
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
使用示例
package main
import (
"fmt"
)
type Person interface {
Say()
}
type Teacher struct {
}
func (teacher Teacher) Say() {
fmt.Println("I'm a teacher")
}
type Stucent struct {
}
func (student Stucent) Say() {
fmt.Println("I'm a student")
}
func main() {
var p1 Person = new(Teacher)
p1.Say()
var p2 Person = new(Stucent)
p2.Say()
}
//输出
I'm a teacher
I'm a student
错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error 类型是一个接口类型,这是它的定义
type error interface {
Error() string
}
使用示例,感觉还是java thow 异常的方式比较简单
package main
import (
"errors"
"fmt"
)
func testError(i int) (int, error) {
if i == 0 {
return 0, errors.New("mock error")
} else {
return i, nil
}
}
func main() {
i, error := testError(0)
fmt.Println(i, error)
}
panic,defer 和 recover:据说可以实现类似java try catch finally的效果,后续研究了在写
go并发
go语言的并发是通过goroutine(协程) 调度额,是轻量级的线程,使用起来比java的多线程更简单
并发开启
使用示例
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(10 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")//通过go开启并发执行
go say("hello")
time.Sleep(200 * time.Millisecond)
}
//输出:输出是随机交错的
world
hello
hello
world
world
hello
hello
world
world
hello
goroutine通信
两个 goroutine 之间通过传递一个指定类型的channel来同步运行和通讯
ch := make(chan int)
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
示例
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{1, 2, 3, 4, 5}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
//输出:12 3 15
默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据,如果没被接收,发生会被阻塞,如果没有发送,接收会被阻塞
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:ch := make(chan int, 100),类似java的缓冲队列效果
通道可以通过close来关闭,通道关闭后,放数据会报错,数据取完后,会返回0值
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
v, ok := <-ch //通过ok可以判断是否取完了
fmt.Println(v, ok)
ch <- 2 //已经关闭了,再放会报错
}
//输出:
1
2
0 false
panic: send on closed channel
字符串格式化
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串
- Printf 根据格式化参数生成格式化的字符串并写入标准输出
格式化语法
格 式 | 描 述 |
---|---|
%v | 按值的本来值输出 |
%+v | 在 %v 基础上,对结构体字段名和值进行展开 |
%#v | 输出 Go 语言语法格式的值 |
%T | 输出 Go 语言语法格式的类型和值 |
%% | 输出 % 本体 |
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%d | 整型以十进制方式显示 |
%x | 整型以十六进制方式显示 |
%X | 整型以十六进制、字母大写方式显示 |
%U | Unicode 字符 |
%f | 浮点数 |
%p | 指针,十六进制方式显示 |
小结
以上为我学习的go的基础语法点以及和java语法的简单对比,整体看起来还是比较简洁的,后续继续学习高级特性会继续记录