最近做一个小程序后端项目,准备用go语言编写,所以学习了一下go语言基础,做了一些笔记,备忘,也供大家参考。
参考资料:Go语言教程|菜鸟教程
作者水平有限,有任何问题可以在文章下方给我留言,谢谢!
package main
import "fmt"
func main() {
/* 这是我的第一个简单的程序 */
fmt.Println("hello "+"World!")
}
让我们来看下以上程序的各个部分:
第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包。
如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
下一行 /…/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
在终端cd到.go文件所在目录
1、输入命令 go run helloworld.go 并按回车执行代码
$ go run helloworld.go
hello world!
2、我们还可以使用 go build 命令来生成二进制文件
$go build helloworld.go
$ls
helloworld helloworld.go
$./helloworld
hello world!
值得注意的是:
{不能单独放在一行,否则代码在运行时会产生错误
。
声明变量的一般形式是使用 var 关键字:
var identifier type
实例
package main
import "fmt"
func main() {
//声明单个变量并赋值
var a string = "syb"
fmt.Println(a)
}
运行结果
syb
若不指定类型,则根据值自行判定变量类型
var v_name = value
实例
package main
import "fmt"
func main() {
var d = true
fmt.Println(d)
}
运行结果
true
v_name := value
intVal := 1 相等于:
var intVal int
intVal =1
//以下是声明多个变量并赋值的4种写法
var a, b int = 1, 2 //第1种
var a, b int //第2种
a, b = 1, 2
var a, b = 1, 2 //第3种
a, b := 1, 2 //第4种
常量是一个在程序运行时,不会被修改的量。
常量的定义格式:
const identifier [type] = value
//多常量声明
const c_name1, c_name2 [type] = value1, value2
你**可以省略[type]**来让编译器自动根据变量的值来确定变量的类型。
iota是一个特殊常量,只能在常量表达式中使用。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),之后const 中每新增一行将使 iota 自增1(iota 可理解为 const 语句块中的行索引)。
示例
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
运行结果
0 1 2 ha ha 100 100 7 8
if…else…语句很简单不再赘述。
Go中的switch语句不同于C/C++/Java中的switch语句。
Go中的switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。
switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。
示例
package main
import "fmt"
func main() {
var grade string
var marks int = 90
//switch语句第1种写法
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
//switch语句第2种写法
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 );
}
运行结果
优秀!
你的等级是 A
Type switch可用于判断某个变量的类型
实例:
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("未知型")
}
}
运行结果:
x 的类型 :<nil>
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
实例:
package main
import "fmt"
func main() {
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true:
fmt.Println("2、case 条件语句为 true")
fallthrough
case false:
fmt.Println("3、case 条件语句为 false")
fallthrough
case true:
fmt.Println("4、case 条件语句为 true")
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
运行结果:
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true
1、select 语句随机执行一个可运行的 case。
2、每个case都必须是一个通信
3、如果没有 case 可运行,且没有default语句,它将阻塞,直到有 case 可运行。
4、如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
实例:
package main
import "fmt"
func main() {
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
运行结果:
no communication
Go语言的for循环有3种形式
实例:
package main
import "fmt"
func main() {
sum := 0
for i := 0; i <= 10; i++ {
sum += i
}
fmt.Println(sum)
}
运行结果:
55
实例:
package main
import "fmt"
func main() {
sum := 0
// 这样写也可以,更像 While 语句形式
for sum <= 10{
sum += sum
}
fmt.Println(sum)
}
运行结果:
16
这种格式的循环可以对字符串、数组等进行迭代输出元素。
实例:
package main
import "fmt"
func main() {
str := []string{"google", "qpple"}
for i, s := range str {
fmt.Println(i, s)
}
num := [6]int{1, 2, 3, 5}
for i,x:= range num {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
运行结果:
0 google
1 qpple
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0
break语句和continue语句比较简单,在此不再赘述。
goto语句我自己没用过,记录一下。
实例:
在变量 a 等于 15 的时候跳过本次循环并回到循环的开始语句 LOOP 处
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 10
/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}
运行结果:
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19
//返回类型可省略
func function_name( [parameter list] ) [return_types] {
//函数体
}
实例:
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
Go函数可返回多个值。
实例:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "apple")
fmt.Println(a, b)
}
运行结果:
apple Google
匿名函数是指不需要定义函数名的一种函数实现方式。
在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。
匿名函数由一个不带函数名的函数声明和函数体组成。
匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
实例:
package main
import (
"fmt"
"math"
)
func main() {
getSqrt := func(a float64) float64 {
return math.Sqrt(a)
}
fmt.Println(getSqrt(4))
}
运行结果:
2
用的较少,先跳过
Go 语言中同时有函数和方法。
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。
语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
实例:
package main
import (
"fmt"
)
// 定义结构体
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
// 该方法属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
运行结果:
圆的面积 = 314
声明一个数组:
//声明一个整型数组num
var num [10] float32
声明数组并初始化:
var str1 = [3]string{"C", "go", "C++"}
//或者
str2 := [3]string{"Python", "Java", "JavaScript"}
如果数组长度不确定,可以使用 … 代替数组的长度,也可以直接空掉,编译器会根据元素个数自行推断数组的长度:
var array1 = [...]float32{2000.0, 2.7, 12.0, 3.0}
var array2 = []float32{2000.0, 2.7, 12.0, 3.0}
//或者
array2 := [...]float32{2002.0, 2.72, 12.2, 3.3}
array2 := []float32{2002.0, 2.72, 12.2, 3.3}
//声明一个3维的整型数组
var threedim [3][3][3] int
与C++的指针基本一致。
实例:
package main
import "fmt"
func main() {
var a int= 20
//声明整型指针变量ip
var ip *int
//用&取变量a的地址
ip = &a
fmt.Printf("a 变量的地址是: %x\n", &a )
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
运行结果:
a 变量的地址是: c00000a118
ip 变量储存的指针地址: c00000a118
*ip 变量的值: 20
nil,与C++中的NULL概念一致。
//整型指针数组的声明
var ptr [5]*int
//双重指针的声明格式
var ptr **int;
实例:
package main
import "fmt"
//使用type xxx struct语句定义结构体
type Books struct {
title string
author string
subject string
bookId int
}
func main() {
//创建结构体的两种写法
book1 := Books{"goNote","syb","go",1}
book2 := Books{title:"skyBook", author: "me",bookId: 0}
//访问结构体成员
fmt.Println(book1.title)
fmt.Println(book2)
}
Go 语言的切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用。
Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
切片声明后未初始化则为空切片,为nil,长度为0.
使用 make() 函数来创建切片:
var slice1 []type = make([]type, len, [cap])
//也可以简写为
slice1 := make([]type, len, [cap])
make()函数中的,len是数组的长度,cap是切片的容量(最大长度),cap是可选参数。
s := []int{1,2,3}
//此时cap=len=3
//将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:endIndex]
//startIndex默认指向数组的第一个元素
//endIndex - 1默认指向数组的最后一个元素
s1 := s[startIndex:endIndex]
切片是可索引的。
切片可以由 len() 方法获取长度。
切片可以由**cap()**方法获取容量,即最大长度。
实例:
package main
import "fmt"
func main() {
num1 := [6]int{1, 2, 3, 4, 5, 6}
//创建一个num2的切片
var num2 = make([]int,3,5)
//初始化切片
num2 = num1[1:4]
printSlice(num2)
}
//打印切片信息函数
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果:
len=3 cap=5 slice=[2 3 4]
下面的代码演示了拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
实例:
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
// 向切片添加一个元素
numbers = append(numbers, 1)
printSlice(numbers)
// 同时添加多个元素
numbers = append(numbers, 2,3,4)
printSlice(numbers)
// 创建切片 numbers1 ,容量为原切片的两倍
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
// 拷贝 numbers 的内容到 numbers1
copy(numbers1,numbers)
printSlice(numbers1)
}
//打印切片信息函数
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果:
len=0 cap=0 slice=[]
len=1 cap=1 slice=[1]
len=4 cap=4 slice=[1 2 3 4]
len=4 cap=8 slice=[1 2 3 4]
Map 是一种无序的键值对的集合。
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。
不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
//声明一个map
var map_name map[key_type]value_type
仅声明而未进行初始化的map,为nil。
使用make()函数初始化Map
//使用内建函数make()
map_name := make(map[key_type]value_type)
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。
delete(map_name, key)
实例:
package main
import "fmt"
func main() {
// 创建Map集合
var countryCapitalMap map[string]string
// 初始化Map集合
countryCapitalMap = make(map[string]string)
// 向Map插入key - value对,各个国家对应的首都
countryCapitalMap [ "Chinese" ] = "北京"
countryCapitalMap [ "Italy" ] = "罗马"
countryCapitalMap [ "Japan" ] = "东京"
countryCapitalMap [ "India" ] = "新德里"
// 使用键输出地图值
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [country])
}
// 查看元素在集合中是否存在
capital, ok := countryCapitalMap [ "American" ]
fmt.Println(ok)
if (ok) {
fmt.Println("American 的首都是", capital)
} else {
fmt.Println("American 的首都不存在")
}
}
运行结果:
Italy 首都是 罗马
Japan 首都是 东京
India 首都是 新德里
Chinese 首都是 北京
false
American 的首都不存在
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
实例:
package main
import "fmt"
func main() {
//这是我们使用range去求一个slice的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range也可以用在map的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//range也可以用来枚举字符串。第一个参数是字符的索引,第二个是字符的ASCII码。
for i, c := range "go" {
fmt.Println(i, c)
}
}
运行结果:
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111
Go 语言类型转换基本格式如下:
type_name(expression)
//type_name 为类型
//expression 为需要转换的表达式
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
实例:
package main
import (
"fmt"
)
// 定义Phone接口
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()
}
运行结果:
I am Nokia, I can call you!
I am iPhone, I can call you!
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,下面是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
}
实例:
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 error 接口
func (de *DivideError) Error() string {
strFormat := `
无法完成除法运算, 除数不能为0.
被除数: %d
除数: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 int 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("错误信息: ", errorMsg)
}
}
运行结果:
100/10 = 10
错误信息:
无法完成除法运算, 除数不能为0.
被除数: 100
除数: 0
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
//例如:
go f(x, y, z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。
同一个程序中的所有 goroutine 共享同一个地址空间。
通道(channel)是用来传递数据的一个数据结构。
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int, [缓冲区大小(可选)])
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
// 把 sum 发送到通道 c
c <- sum
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
// 从通道 c 中接收
x, y := <-c, <-c
fmt.Println(x, y, x+y)
}
运行结果:
-5 17 12
默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:
1、如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;
2、如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。
3、接收方在有值可以接收之前会一直阻塞。
Go 通过 range 关键字来实现遍历通道读取到的数据,类似于与数组或切片。格式如下:
//用range遍历通道返回一个通道中的值和一个布尔值,布尔值表示通道是否还能接收到数据
v, ok := <-ch
通道可以使用 close() 函数来关闭。
实例:
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
运行结果:
0
1
1
2
3
5
8
13
21
34