Go是2009年开源的编程语言,Go语言具有以下特性:语法简洁、并发编程、编译迅速、数组安全、丰富的内置类型、错误处理、匿名函数和闭包、反射、函数多返回值、自动垃圾回收。
二、Go语言安装与配置Go语言支持以下系统:Linux、Mac、Windows。
安装包下载地址:https://golang.org/dl/
如果打不开可以使用这个地址:https://golang.google.cn/dl/
下面介绍Windows和Linux系统的安装:
1、Windows系统下安装:
可以直接点击.msi进行下载,比如上述图片的go1.19.1.windows-amd64.msi,下载完后打开msi,进行安装,安装目录可以使用默认,也可以自己配置,安装目录不可以有中文。然后配置系统环境变量:添加GOROOT,值为Go的安装目录,例如D:\Program Files\Go。然后可以在cmd里执行go version,如果出现类似输出:go version go1.19.1 windows/amd64,说明安装配置成功,接下来就可以进行开发运行go程序了。
2、Linux系统下的安装:
2)将下载的二进制包解压至/usr/local目录
tar -C /usr/local -xzf go1.19.1.linux-amd64.tar.gz
3)将 /usr/local/go/bin 目录添加至 PATH 环境变量:
export PATH=$PATH:/usr/local/go/bin
以上只能暂时添加 PATH,关闭终端然后下次再登录又没有了。
可以编辑 ~/.bash_profile 或者 /etc/profile,并将以下命令添加该文件的末尾,这样就可以永久生效:
export PATH=$PATH:/usr/local/go/bin
添加后需要执行:
source ~/.bash_profile
或者
source /etc/profile
三、Go语言开发结构与流程
以下程序是在windows系统的VS Code编辑器开发的,安装Go扩展可以方便开发。
Go语言程序的基本结构有以下几个组成:包声明、引入包、函数、变量、语句、表达式、注释。
以下是一个输出Hello, World!的main.go程序:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
打开命令行,在保存程序文件的目录中执行go run main.go,输出Hello, World!。
可以使用go build命令来生成二进制文件:
go build main.go
四、Go语言的基础定义
1、标识符:
Go语言标识符与其它语言类似,第一个字符必须是字母、下划线,不能是数字。
2、关键字
Go语言的25个关键字或者保留字:
var, type, switch, struct, select, return, range, package, map, interface, if, import, goto, go, func, for, fallthrough, else, default, defer, const, case, continue, chan, break.
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, print, println, real, recover, string, true, uint, uint8, uintptr.
3、注释:
// 单行注释
/*
xxxxx
多行注释
*/
五、Go语言的数据类型
有如下类型:
1、数字类型
整型 int 和浮点型 float32、float64,Go 语言支持整型、浮点型数字和支持复数。
2、布尔类型
布尔型的值只可以是常量 true 或者 false。例子:var b bool = true。
3、字符串类型:
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本。
1、声明变量与变量赋值
// 声明变量的一般形式是使用 var 关键字:
var variable type
// 也可以一次声明多个变量:
var variable1, variable2 type
// 声明变量时也可以赋值
var temp string = "ssss"
var a, b int = 1, 2
声明了变量并且赋值了,也可以不用写类型。
如果声明了变量没有赋值,那么变量的值为零值。比如var a bool的零值为false。
下面是一些类型数据的零值:
- 数值类型(包括complex64/128)为0
- 布尔类型为 false
- 字符串为 “”
- 以下几种类型零值为nil:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
声明变量更加简洁的方式:
a := 1
相当于:
var a int
a = 1
声明全局变量和函数变量:
ackage main
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)
// 这种不带声明格式的只能在函数体中出现
// c, d := 3123, "hello"
func main(){
c, d := 3123, "hello"
}
2、常量
常量是在程序运行时不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式如下:
const variable [type] = value
你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型
const a string = "abc"
const a = "abc"
多个相同类型的声明可以简写为:
const c1, c2 = value1, value2
常量可以用len()等内置函数计算表达式的值,在常量表达式中,函数必须是内置函数,否则编译不通过
ackage main
const (
a = "abc"
b = len(a)
)
func main(){
println(a, b)
}
iota是一种特殊常量,可以认为是一个可以被编译器修改的常量。iota在const关键字出现时将被重置为0,const中每新增一行常量声明,iota计数一次。
package main
import "fmt"
func main() {
const (
a = iota //0
b //1
c //2
d = "hh" //独立值,iota += 1
e //"ha" iota += 1
f = 312 //iota +=1
g //312 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i) // => 0 1 2 hh hh 312 312 7 8
}
3、变量作用域
作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。
Go语言中变量可以在三个地方声明:
- 函数内定义的变量(局部变量):作用域只在函数体内,参数和返回值变量也是局部变量。
- 函数外定义的变量(全局变量):在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。
- 函数的形式参数:形式参数可以看作函数的局部变量。
Go语言运算符与java等语言基本一样,均有:算术运算符、关系运算符、逻辑运算符、赋值运算符、位运算符。
比较例外的是,Go语言有与C/C++类似的指针相关运算符:
运算符 | 描述 | 示例 |
---|---|---|
* | 指针变量 | *a是一个指针变量 |
& | 返回变量存储地址 | &a: 变量的存储地址 |
package main
import "fmt"
func main() {
var a int = 22
var ptr *int
// ptr变量存储了a的地址
ptr = &a
fmt.Printf("a的值为 %d\n", a);
fmt.Printf("ptr的值为 %s\n", ptr);
fmt.Printf("*ptr的值为 %d\n", *ptr);
}
输出:
a的值为 22
ptr的值为 %!s(*int=0xc00000e0a8)
*ptr的值为 22
八、Go语言函数
Go语言程序至少有一个main函数。
1、函数定义
func function_name( [parameter list] ) [return_types] {
// 函数体
}
2、函数使用
以下代码定义了函数和函数调用:
package main
import "fmt"
func main() {
var a int
a = sum(1, 2)
fmt.Printf("a的值为 %d\n", a);
}
func sum(a, b int) int {
var c int
c = a + b
return c
}
返回多个值的函数:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("cc", "dd")
fmt.Println(a, b) // 输出:dd cc
}
九、Go语言条件语句
Go语言条件语句的执行逻辑与其它语言是一致的(但是Go语言多了个select语句),主要是写法不同
1、if语句
定义:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
示例:
package main
import "fmt"
func main() {
var a int = 1
if a < 10 {
// 如果条件为true时执行以下语句
fmt.Printf("a小于10\n" )
}
}
if else 语句:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
2、switch语句
定义:
switch temp {
case temp1:
//
case temp2:
//
default:
//
}
或者
switch {
case 布尔表达式1:
//
case 布尔表达式2:
//
default:
//
}
示例:
package main
import "fmt"
func main() {
var a string = ""
var num int = 10
switch num {
case 123: a = "大于10"
case 10: a = "等于10"
case 1, 2: a = "小于10"
default: a = "不知道"
}
switch {
case a == "等于10":
fmt.Printf("a等于10!\n" )
case a == "大于10", a == "小于10":
fmt.Printf("a大于或者小于10!\n" )
default:
fmt.Printf("其它情况\n" );
}
fmt.Printf("num的值为: %d, a的值为: %s", num, a );
}
输出:
a等于10!
num的值为: 10, a的值为: 等于10
fallthrough
使用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
default:
fmt.Println("默认case")
}
}
输出:
2、case条件为true
3、case条件为false
默认case
3、select语句
select语句随机执行一个可运行的case,如果没有可运行的case,将阻塞运行,直到有 case 可运行,
每个case都必须是一个通信。
定义:
select {
case a: // 通信a
//
case b: // 通信b
//
default:
//
}
十、Go语言循环语句
Go语言循环的语法有点差别,for循环的写法也有差异,没有while循环。
示例:
// 循环遍历
for i := 0; i < count; i++ {
//
}
// 类似map遍历
for _, v := range map {
//
}
//
for 布尔表达式 {
//
}
示例:
for 1 > 0 {
fmt.Printf("111")
}
go循环程序中可以用break、continue、goto语句控制循环执行过程。
十一、Go语言数组Go语言数组是具有相同唯一类型的一组长度固定的数据结构,这种类型可以是任意的原始类型例如整型、字符串,也可以是自定义类型。可以通过索引来读取或者修改数组元素。
1、一维数组
1)、数组声明:
var arr_name [size] type
例如:
var arr [3] int
2)、初始化数组
var arr = [2]float32{2.1, 3.3}
或者
arr := [2]float32{2.1, 3.3}
如果数组长度不确定,可以使用…代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var arr = [...]float32{2.1, 3.3}
或者
arr := [...]float32{2.1, 3.3}
如果设置了数组的长度,还可以通过指定数组下标来初始化元素或者修改元素值:
arr := [2]float32{2.1, 3.3}
arr[1] = 1.1
2、多维数组
1)数组声明
var arr [size1][size2]...[size_n] type
例如三维数组:
var arr [4][22][3] int
下面以二维数组举例:
二维数组本质上是由一维数组组成的。
arr := [][]int{}
// 可以使用append()函数向空的二维数组添加两行一维数组
row1 := []int{1, 2, 3}
row2 := []int{4, 5, 6}
arr = append(arr, row1)
arr = append(arr, row2)
fmt.Println("arr为:", arr)
fmt.Println("第一个元素为:%d", arr[0][0])
也可以这样初始化二维数组:
arr := [2][4]int{
{0, 1, 2, 3} ,
{4, 5, 6, 7} , // ,不能省略
}
二维数组取值赋值:
赋值:
arr[2][1] = 222
取值:
a := arr[2][1]
3、数组作为函数参数:
void fun(param [3]int)
{
//
}
或者不指定数组长度
void fun(param []int)
{
//
}
十二、Go语言结构体
结构体是由相同类型或不同类型的数据组成的数据集合。
1、定义与使用
type struct_name struct {
filed1 type
filed2 type
filed3 type
}
示例:
package main
import "fmt"
type Book struct {
name string
author string
}
func main() {
stru := Book{"编程设计", "xian"}
fmt.Println("stru为:", stru)
var book1 Book
book1.name = "js程序设计"
book1.author = "xxx"
fmt.Println("book1: ", book1)
// 也可以使用key => value格式
fmt.Println(Book{name: "编程设计", author: "xian"})
}
输出:
stru为: {编程设计 xian}
book1: {js程序设计 xxx}
{编程设计 xian}
访问结构体成员:
结构体.成员名
2、结构体作为参数传递:
package main
import "fmt"
type Book struct {
name string
author string
}
func main() {
stru := Book{"编程设计", "xian"}
fmt.Println("stru为:", stru)
getInfo(stru)
}
func getInfo( book Book ) {
fmt.Println( "Book name为: ", book.name)
}
输出:
stru为: {编程设计 xian}
Book name为: 编程设计
3、结构体指针
可以定义指向结构体的指针:
var struct_pointer *结构体名称 // 注意不是结构体变量名称
获取结构体变量地址:
struct_pointer = &str
使用指针访问结构体成员:
struct_pointer.name
十三、Go语言集合(Map)
Map是一种无序的键值对的集合。
1、定义map
// 声明变量,默认map是nil
var map_variable map[key_type]value_type
// 或者使用make函数
map_variable := make(map[key_type]value_type)
示例:
package main
import "fmt"
func main() {
var capitalMap map[string]string
capitalMap = make(map[string]string)
capitalMap["France"] = "巴黎"
capitalMap["English"] = "伦敦"
// capitalMap := map[string]string{"France": "巴黎", "English": "伦敦"}
// 使用range遍历
for k := range capitalMap {
fmt.Println(k, " => ", capitalMap[k])
}
// 或者
fmt.Println("\nk, v遍历: ")
for k, v := range capitalMap {
fmt.Println(k, " => ", v)
}
fmt.Println("\n")
// 查看元素在集合中是否存在 */
capital, ok := capitalMap["Itlay"]
if (ok) {
fmt.Println("Itlay的首都 ", capital)
} else {
fmt.Println("不存在Itlay的首都")
}
}
delete删除:
delete(capitalMap, "France")
十四、Go语言range
for range循环可以对数组、字符串、slice、map等进行循环遍历。
1、遍历map
// 遍历map
for key, value := range map {
map[key] = value
}
2、遍历数组
// 遍历数组
for i, val := range arr {
// i代表索引,val代表相应的值
}
3、遍历字符串
// 遍历字符串。i是字符的索引,char是字符(Unicode的值)本身。
for i, char := range str {
fmt.Println(i, char)
}
十五、Go语言切片
Go语言数组长度是固定的, 而切片提供了一种动态长度数组的功能。
1、创建切片与使用
可以使用make创建切片,len是数组的长度也是切片的初始长度。
var slice1 []type = make([]type, len)
或者
slice1 := make([]type, len)
// 也可以指定容量,capacity是可选参数
slice1 := make([]type, len, capacity)
切片初始化:
slice1 := []int{1,2,3 }
截取:
s := arr[startIndex:endIndex] // 左闭右开
s := arr[:endIndex]
s := arr[startIndex:]
len()方法获取切片长度,cap()测量切片容量。
切片在未初始化之前默认为nil,长度为 0:
var slice1 []int
// slice1 == nil
2、append()函数:切片追加新元素:
package main
import "fmt"
func main() {
var slice1 []int // 创建切片
fmt.Printf("长度=%d, 容量=%d, 切片=%v\n", len(slice1), cap(slice1), slice1)
// 向切片添加一个元素
slice1 = append(slice1, 33)
fmt.Printf("长度=%d, 容量=%d, 切片=%v\n", len(slice1), cap(slice1), slice1)
}
输出:
长度=0, 容量=0, 切片=[]
长度=1, 容量=1, 切片=[33]
3、copy: 拷贝切片
package main
import "fmt"
func main() {
var slice1 []int // 创建切片
slice2 := []int{32, 43} // 创建切片
// 向切片添加一个元素
slice1 = append(slice1, 33)
// slice1拷贝到slice2
copy(slice2, slice1)
fmt.Printf("长度=%d, 容量=%d, 切片=%v\n", len(slice2), cap(slice2), slice2) // => 长度=2, 容量=2, 切片=[33 43]
}
十六、Go语言接口
// 定义接口
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_name_n [return_type]
}
// 定义结构体
type struct_name struct {
variable1 type
variable2 type
...
variable_n type
}
// 实现接口方法
func (struct_variable_name struct_name) method_name1() [return_type] {
/* 方法实现 */
}
示例:
package main
import (
"fmt"
)
type Phone interface {
speak()
}
type IPhone struct {
name string
}
func (iPhone1 IPhone) speak() {
fmt.Println("iPhone1 speak")
}
func main() {
var phone Phone
phone = new(IPhone)
phone.speak()
}
十七、Go语言并发
Go语言并发:通过go关键字来开启goroutine。goroutine是轻量级线程,goroutine的调度由Golang运行时进行管理。go语句开启一个新的运行期线程,即就是goroutine,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中的所有goroutine共享同一个地址空间。
1、goroutine 语法格式:
go 函数名(参数列表)
示例:
package main
import (
"fmt"
"time"
)
func printFun(s string) {
for i := 0; i < 3; i++ {
time.Sleep(200 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go printFun("World")
printFun("Hello")
}
2、使用通道(channel)传递数据
可以使用通道(channel)用来传递数据。通道可在两个goroutine之间通过传递一个指定类型的值来通讯。操作符<-指定通道的方向:发送或接收。如果没有指定方向,说明是双向通道。
创建通道:
chan1 := make(chan int)
chan1 <- val // 把 val 发送到通道 chan1
temp := <-chan1 // 从 chan1 接收数据, 并且赋值给temp变量
示例:
package main
import "fmt"
func sum(nums []int, c chan int) {
sum := 0
for _, v := range nums {
sum += v
}
// 把sum发送到通道c
c <- sum
}
func main() {
a := []int{31 , 21, 3, 4}
chan1 := make(chan int)
go sum(a[:len(a)/2], chan1)
go sum(a[len(a)/2:], chan1)
sum1, sum2 := <-chan1, <-chan1 // 从通道c中接收两个sum结果
fmt.Println(sum1, sum2, sum1 + sum2) // => 7 52 59
}