Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易,具有以下特性
下载地址All releases - The Go Programming Language (google.cn)
选择稳定版本
配置两个环境变量,两个变量不要指向同一个目录
然后再path环境变量中新增两行
在控制台输出go version,提示版本信息就安装成功了
我使用了goland作为编译器,下载后开箱即用,创建一个go工程,命名为hello,在根目录下新建一个main包,然后建立helloworld.go,内容如下
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
运行结果为
在c,java等语言中,我们用分号标识一个可执行语句,但是在go中,我们使用换行符分割语句,如下
package main
import "fmt"
func main() {
fmt.Println("不需要分号")
fmt.Println("使用换行符分割可执行语句")
}
示例代码:
package main
import "fmt"
func main() {
// 行注释
fmt.Println("不需要分号")
/*
段落注释
*/
fmt.Println("使用换行符分割可执行语句")
}
go的标识符只能以字母或下划线开头
使用var声明一个变量,变量名称只能是字母或者下划线开头
示例代码如下
package main
import "fmt"
func main() {
// 声明变量并给定值
var name = "name"
// 声明变量不设置值
var name1 string
// 一次性声明多个变量
var age, sex = 18, "男"
// 使用:=声明变量
name2 := "name2"
fmt.Println(name)
fmt.Println(name1)
fmt.Println(name2)
fmt.Println(age)
fmt.Println(sex)
}
运行结果为
全局变量和局部变量可以同名,但是函数内的变量会被优先使用
常量使用const标识
const 常量名 类型 = 值
类型可以省略,编译器会通过值来判断变量类型
iota是一个计数器,只能在常量表达式中使用,把第一个常量设置为iota后,后面的常量就会递增
const (
a = iota
b = iota
c = iota
)
上述代码等价于
const (
a = iota
b
c
)
a=0,b=1,c=2
使用下划线可以跳过这次递增的赋值
const (
a = iota
_
c
d
)
a=0 c=2 d=3
const (
a, b = iota, iota
_, _
c, d
e, f
)
a=b=0 c=d=2 e=f=3
package main
import "fmt"
func main() {
if true {
fmt.Println("if")
}
}
package main
import "fmt"
func main() {
var a int = 2
if a == 1 {
fmt.Println(1)
} else if a == 2 {
fmt.Println(2)
} else {
fmt.Println(3)
}
}
go的switch不需要break来结束判断,遇到符合条件的语句块执行了就直接结束了,下述代码输出结果为1
package main
import "fmt"
func main() {
var a int = 1
switch a {
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
default:
fmt.Println("default")
}
}
fallthrough会强制执行后面的case内的代码块,不会判断条件是否为true,下述代码执行结果为1 2
package main
import "fmt"
func main() {
var a int = 1
switch a {
case 1:
fmt.Println(1)
fallthrough
case 2:
fmt.Println(2)
default:
fmt.Println("default")
}
}
for 初始化值;条件;复制表达式
package main
import (
"fmt"
)
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
goto示例:
package main
import "fmt"
func main() {
var i = 0
LOOP:
for i < 5 {
fmt.Println(i)
if i == 0 {
i += 1
goto LOOP
}
i++
}
}
在i=0时,跳转到for循环执行输出结果0 1 2 3 4
函数定义: func 函数名([参数1 类型,参数2 类型......]) [返回值类型]
函数是可以有多个返回值的,示例如下
package main
import "fmt"
func main() {
var x, y string = swap("1", "2")
fmt.Println(x + y)
}
func swap(x, y string) (string, string) {
return y, x
}
值传递示例如下,输出结果为100 200 100 200
package main
import "fmt"
func main() {
var a int = 100
var b int = 200
fmt.Printf("交换前a的值为%d\n", a)
fmt.Printf("交换前b的值为%d\n", b)
swap(a, b)
fmt.Printf("交换后a的值为%d\n", a)
fmt.Printf("交换后b的值为%d\n", b)
}
func swap(x, y int) {
var temp int
temp = x
x = y
y = temp
}
引用传递示例如下,输出结果为100 200 200 100
package main
import "fmt"
func main() {
var a int = 100
var b int = 200
fmt.Printf("交换前a的值为%d\n", a)
fmt.Printf("交换前b的值为%d\n", b)
swap(&a, &b)
fmt.Printf("交换后a的值为%d\n", a)
fmt.Printf("交换后b的值为%d\n", b)
}
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
定义一个变量,并声明为一个函数,示例如下
package main
import "fmt"
func main() {
var sumFunc = func(a, b int) int {
return a + b
}
fmt.Println(sumFunc(1, 2))
}
匿名函数在go语言也成为闭包
示例代码如下,输出结果5 5
package main
import "fmt"
func main() {
// 定义并调用一个匿名函数
var add = func(a, b int) int {
return a + b
}
fmt.Println(add(2, 3))
//将参数声明为一个匿名函数
var myFunc = func(operation func(a, b int) int, x, y int) int {
return operation(x, y)
}
fmt.Println(myFunc(add, 2, 3))
}
var 数组名 [数组大小]数据类型
package main
import "fmt"
func main() {
// 初始化一个素组
var num [5]int
fmt.Println(num)
// 用列表初始化数组
var num1 = [5]int{1, 2, 3, 4, 5}
fmt.Println(num1)
// 数组长度不确定
var num2 = [...]int{1, 2, 3, 4, 5}
fmt.Println(num2)
// 数组遍历
var index int
for index = 0; index < 5; index++ {
fmt.Printf("数组下标%d,值为%d\n", index, num2[index])
}
}
var 数组名称 [size][size]...[size]数组类型
示例如下
package main
import (
"fmt"
)
func main() {
// 创建一个二维数组并初始化值
var arr = [2][2]int{
{1, 2},
{3, 4},
}
fmt.Println(arr)
// 创建空的二位数组并添加值
var arr1 [][]int
arrRow := []int{1, 2}
arrRow1 := []int{1, 2, 3, 4}
arr1 = append(arr1, arrRow)
arr1 = append(arr1, arrRow1)
fmt.Println(arr1)
}
指针指向变量得内存地址,通过var 变量名称 变量类型声明一个变量,通过&操作符可以访问一个变量的地址,通过 *变量名称可以访问指针的值
示例代码如下
package main
import "fmt"
func main() {
var a int = 20 // 声明变量
var pointer *int // 声明指针
pointer = &a // 让指针指向变量得实际地址
fmt.Printf("变量a得实际地址为%d\n", &a)
fmt.Printf("指针指向的变量的地址%d\n", pointer)
fmt.Printf("指针自己的地址%d\n", &pointer)
fmt.Printf("指针变量指向的地址存储的值%d\n", *pointer)
}
运行结果如下,内存由程序分配,每次运行结果可能会不同,但第一项和第二项值相同
在go语言中,空指针用nil标识
package main
import "fmt"
func main() {
//declare()
var pointer *int
//var pointer *int = nil 等同于上述语句
if pointer == nil {
fmt.Println("指针pointer为空")
}
}
func declare() {
var a int = 20 // 声明变量
var pointer *int // 声明指针
pointer = &a // 让指针指向变量得实际地址
fmt.Printf("变量a得实际地址为%d\n", &a)
fmt.Printf("指针指向的变量的地址%d\n", pointer)
fmt.Printf("指针自己的地址%d\n", &pointer)
fmt.Printf("指针变量指向的地址存储的值%d\n", *pointer)
}
指针数组的每一个元素都指向了一个值
示例代码如下
package main
import "fmt"
func main() {
var intArray = [3]int{1, 2, 3}
var index int
var arrayPointer [3]*int
for index = 0; index < 3; index++ {
// 整数地址复制给指针数组
arrayPointer[index] = &intArray[index]
}
for index = 0; index < 3; index++ {
fmt.Printf("arrayPoint[%d]=%d\n", index, *arrayPointer[index])
}
}
运行结果为
arrayPoint[0]=1
arrayPoint[1]=2
arrayPoint[2]=3
指针是可以指向指针的,通过**符号声明
示例如下
package main
import "fmt"
func main() {
var point *int
var pointToPoint **int
var num int = 1
point = &num
pointToPoint = &point
fmt.Printf("变量a=%d\n", num)
fmt.Printf("指针point值为%d\n", *point)
fmt.Printf("指针pointToPoint的值为%d\n", **pointToPoint)
}
输出结果为
指针可以作为函数参数,在函数中修改了指针的值,函数外的指针值会跟着一起变动
示例代码如下
package main
import "fmt"
func main() {
var a int = 1
var b int = 2
fmt.Printf("交换前a=%d,b=%d\n", a, b)
swap(&a, &b)
fmt.Printf("交换后a=%d,b=%d", a, b)
}
func swap(x *int, y *int) {
*x, *y = *y, *x
/*
上述代码等同于
var temp int
temp = *x
*x = *y
*y = temp
*/
}
运行结果为
结构体是由一系列数据构成的数据集合
结构体通过type struct定义
type 结构体名称 struct{
属性名 类型
......
}
声明方式如下
变量名称:=结构体名称{value1,...}
变量名称:=结构体名称{属性名:value1....}
示例代码如下
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var person Person = Person{"问", 10}
var person1 Person = Person{name: "龙", age: 10}
var person2 Person = Person{name: "坤"}
fmt.Println(person)
fmt.Println(person1)
fmt.Println(person2)
}
输出结果为
结构体属性通过结构体变量.属性名可以直接访问
代码示例如下
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var person Person
person.name = "龙"
person.age = 1
fmt.Printf("person.name=%s\n", person.name)
fmt.Printf("person.age=%d", person.age)
}
输出结果为
结构体可以作为函数参数,结构体指针存储的也是结构体的地址
代码示例如下
package main
import "fmt"
type Person struct {
name string
age int
}
func main() {
var person Person = Person{"问", 10}
var person1 *Person = &person
printAttr(person, person1)
}
func printAttr(person Person, person1 *Person) {
fmt.Printf("person.name=%s\n", person.name)
fmt.Printf("person.age=%d\n", person.age)
fmt.Printf("person1.name=%s\n", person1.name)
fmt.Printf("person1.age=%d", person1.age)
}
输出结果为
go语言中我们可以定义数组,但是数组长度是不能修改的,而切片可以,切片是数组的抽象,切片的长度是不固定的
可以通过数组创建,也可以通过make关键字创建
示例如下
package main
import "fmt"
func main() {
// 使用未指定大小的数组来定义切片
var slice []int
// 通过make创建数组切片,make([]类型,数组长度(切片初始大小))
var slice1 []int = make([]int, 5)
fmt.Println(slice)
fmt.Println(slice1)
}
示例代码如下
package main
import "fmt"
func main() {
// 创建一个数组
var arr = []int{1, 2, 3}
// 初始化切片为数组的引用,等同于var slice = arr[:endIndex]
var slice = arr[:]
// 截取数组从startIndex到~ndIndex-1下的元素作为一个新的切片
var slice1 = arr[0:2]
// 截取数组从startIndex到最后一个元素作为一个新的切片
var slice2 = arr[2:]
// 也可以从一个切片创建另一个切片
var slice3 = slice[0:2]
fmt.Println(slice)
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
}
运行结果为
示例代码如下,输出结构为3 5
func main() {
// make第一个参数为类型,第二个参数为切片长度,第三个参数为切片容量
var arr = make([]int, 3, 5)
fmt.Println(len(arr))
fmt.Println(cap(arr))
}
如下,实现一个切片扩容方法
package main
import "fmt"
func main() {
var numbers []int
// 向切片添加一个数字
numbers = append(numbers, 0)
fmt.Printf("len=%d cap=%d numbers=%v\n", len(numbers), cap(numbers), numbers)
// 创建一个切片,在老切片的基础上扩容
var numbers1 = make([]int, len(numbers), cap(numbers)*2)
// 将numbers切片复制到numbers1切片
copy(numbers1, numbers)
fmt.Printf("len=%d cap=%d numbers=%v\n", len(numbers1), cap(numbers1), numbers1)
}
运行结果如下
map是一个无序的键值对集合,map是一个引用类型,将map赋值给其他变量,修改了map的值会影响所有引用他的变量
创建语法为
var 变量名称=nake(map[键类型值类型]值类型,初始容量)
map中的键值对容量达到初始容量时会自动扩容,初始容量可以不指定
示例代码如下
package main
import "fmt"
func main() {
// 初始化一个map
var myMap = make(map[string]string)
// 新增一个key
myMap["龙"] = "帅"
// 获取元素
var myValue = myMap["龙"]
fmt.Printf("key=龙value=%s\n", myValue)
var myValue1, ok = myMap["龙1"]
if ok {
fmt.Printf("key=龙1value=%s", myValue1)
} else {
fmt.Printf("数据不存在\n")
}
// 删除元素
delete(myMap, "龙")
var myValue2, ok1 = myMap["龙"]
if ok1 {
fmt.Printf("key=龙value=%s\n", myValue2)
} else {
fmt.Printf("数据已删除不存在")
}
}
range用于迭代数组、map、切片或channel中的元素,在数组和切片中,返回元素的索引和对应的值,在集合中返回key-value
代码示例
package main
import "fmt"
func main() {
var arr = []int{0, 1, 2}
for index, value := range arr {
fmt.Printf("下标=%d,值=%d\n", index, value)
}
/*
上述代码等同于
var index, value int
for index, value = range arr {
fmt.Printf("下标=%d,值=%d", index, value)
}
*/
}
package main
import "fmt"
func main() {
var myMap = make(map[string]string)
myMap["问"] = "问"
myMap["尤"] = "尤"
myMap["龙"] = "龙"
// 遍历key value
for key, value := range myMap {
fmt.Printf("key=%s,value=%s\n", key, value)
}
// 只遍历key
for key := range myMap {
fmt.Printf("key=%s\n", key)
}
// 只遍历value
for _, value := range myMap {
fmt.Printf("value=%s\n", value)
}
}
接口定义规则如下:
// 定义接口
type 接口名称 interface{方法名称() 返回值类型}
// 定义结构体
type 结构体名称 struct{}
// 实现接口的方法
func (结构体变量名 结构体名称) 方法名 返回值类型
示例代码如下
package main
import "fmt"
// Phone 接口
type Phone interface {
call()
}
// Xiaomi 结构体
type Xiaomi struct {
}
func (xiaomi Xiaomi) call() {
fmt.Println("我是小米")
}
// Huawei 结构图
type Huawei struct {
}
func (huawei Huawei) call() {
fmt.Println("我是华为")
}
func main() {
var xiaomi = new(Xiaomi)
xiaomi.call()
var huawei = new(Huawei)
huawei.call()
}
再来一个带返回值的示例
package main
import "fmt"
type Shape interface {
area() float64
}
type Circle struct {
radius float64
}
func (circle Circle) area() float64 {
return 3.14 * circle.radius * circle.radius
}
type Rectangle struct {
width float64
height float64
}
func (rectangle Rectangle) area() float64 {
return rectangle.width * rectangle.height
}
func main() {
var shape Shape
shape = Circle{1.0}
var circleArea = shape.area()
shape = Rectangle{1.0, 2.0}
var rectangleArea = shape.area()
fmt.Printf("圆形面积%f\n", circleArea)
fmt.Printf("矩形面积%f", rectangleArea)
}
类型转换表达式为
类型名称(值或表达式)
代码示例如下,只能从低精度转为高精度
package main
import "fmt"
func main() {
var a int = 10
var b int = 5
var mean float32
mean = float32(a) / float32(b)
fmt.Println(mean)
}
代码示例
package main
import (
"fmt"
"strconv"
)
func main() {
stringToInt()
intToString()
stringToFloat()
floatToString()
}
func floatToString() {
var num float64 = 3.14
var str = strconv.FormatFloat(num, 'f', 2, 64)
fmt.Println(str)
}
func stringToFloat() {
var str string = "3.14"
var num, err = strconv.ParseFloat(str, 64)
if err != nil {
fmt.Println("类型转换报错", err)
} else {
fmt.Println(num)
}
}
func intToString() {
var a int = 10
var str string = strconv.Itoa(a)
fmt.Println(str)
}
func stringToInt() {
// 字符串转数组
var str = "10 ..."
var intValue, err = strconv.Atoi(str)
if err != nil {
fmt.Println("转换错误", err)
} else {
fmt.Printf("转换的值为%d", intValue)
}
}
输出结果为
类型断言用于将接口类型转为指定类型,通过下述形式操作
value.(type)或value.(T)
value是接口类型的变量,type(T)是要转换的类型
下述代码中,建立一个变量originType类型为接口,值为hello world,然后通过接口类型转换,将origintType转换成一个字符串并返回
package main
import "fmt"
func main() {
var originType interface{} = "hello world"
var str, ok = originType.(string)
if ok {
fmt.Println(str)
} else {
fmt.Printf("转换失败")
}
}
类型转换将一个接口类型的值转换成另一个接口类型,语法为T(value)
示例代码如下
package main
import "fmt"
type Write interface {
write(data string) (int, error)
}
type StringWriter struct {
str string
}
// 这里需要修改传递的参数内部的值,所以传递指针类型
func (sw *StringWriter) write(data string) (int, error) {
sw.str = data
return len(data), nil
}
/*
type FloatWriter struct{}
*/
func main() {
var w Write = &StringWriter{}
var strLen, err = w.write("你好")
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("输入文本长度为%d", strLen)
}
var sw *StringWriter = w.(*StringWriter)
/*
转换的值和目标目标接口必须是兼容的,否则会报错
var sw *StringWriter = w.(*FloatWriter)
*/
fmt.Println(sw.str)
}
go有一个内置的error接口
type error interface {
Error() string
}
在函数中可以在返回值中返回错误信息,示例如下
package main
import (
"errors"
"fmt"
)
func main() {
var a, err = getData(1)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(a)
}
}
func getData(a int) (int, error) {
if a == 0 {
return 0, nil
}
return a, errors.New("数字故为0,请输入0")
}
package main
import "fmt"
type CustomError struct {
errorInfo string
}
func (ce *CustomError) Error() string {
return "异常内容为---" + ce.errorInfo
}
func myFunc(data int) (ret int, errorMsg error) {
if data == 0 {
return 0, nil
}
return 0, &CustomError{"这是一个自定义异常"}
}
func main() {
_, error := myFunc(1)
if error != nil {
var err = error.(*CustomError)
fmt.Println(err.errorInfo)
fmt.Println(err.Error())
}
}
运行结果如下
go语言通过goroutine开启并发支持,goroutine是轻量级线程,调度由golang的运行时进行管理
语法 go 函数名(参数)
代码示例如下,go语句开启了一个新的线程,同一个程序中的所有goroutine共享同一个地址空间
package main
import (
"fmt"
"time"
)
func sayHello(s string) {
for i := 0; i < 5; i++ {
time.Sleep(10 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
/*
sayHello("hello") 语句前不加go会按顺序执行,先输出五个hello,在输出五个world
*/
go sayHello("hello")
sayHello("world")
}
输出结果为五个hello和五个world交替输出
通道是用来传递数据的一个数据结构,可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯操作符<-用于指定通道的方向,发送或接受。如果未指定,则默认是一个双向通道,通道默认是没有缓冲区的,发送端发送数据后,必须要有接收端接收数据
ch<-v 把v发送到通道ch
v:=<-ch 从ch接受数据并把值赋给v
使用chan关键字声明一个通道,通道必须在使用前创建
ch:=make(chan int)
示例代码如下
如果不带缓冲区,发送方会阻塞直到接收方接受了值,如下代码会阻塞直到数据被接受,所以下述代码中虽然开启了两个线程,但是都会阻塞等待通道值被接收
package main
import "fmt"
func sum(data []int, c chan int) {
var sum = 0
for _, value := range data {
sum += value
}
// 把sum发送到通道c
c <- sum
}
func main() {
var array []int = []int{1, 2, 3, 4, 5}
var c chan int = make(chan int)
go sum(array[:len(array)/2], c)
go sum(array[len(array)/2:], c)
x, y := <-c, <-c
fmt.Printf("x+y=%d", x+y)
}
如果通道不带缓冲,发送方会阻塞直到接收方从通道接受了值,如果通道带缓冲,发送方会阻塞,直到自己的值被拷贝到缓冲区;如果缓冲区满了,则需要等待直到接收方获取值,接收方在获取值之前会一直阻塞
ch:=make(chan int,100)
示例代码如下
package main
import "fmt"
func main() {
s := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
s <- 1
s <- 2
// 获取数据
fmt.Println(<-s)
fmt.Println(<-s)
}
使用range遍历通道,close关闭通道,示例代码如下
package main
import "fmt"
func main() {
var c = make(chan int, 10)
go myChannel(c)
for i := range c {
fmt.Printf("从通道读取数据%d\n", i)
}
}
func myChannel(c chan int) {
for i := 0; i < 10; i++ {
fmt.Printf("发送数据%d至通道\n", i)
c <- i
}
// 发送完成
fmt.Println("关闭通道")
close(c)
}
输出结果为