学习参考:www.runoob.com/go/go-tutor…
go语言学习
一、基础教程
1、go语言用途
应用于搭载巨型中央服务器,开发效率高,支持海量并行。
2、第一个Go程序
hello.go
package main
import "fmt"
func main(){
fmt.PrintIn("Hello,World!")
}
运行: go run
$ go run hello.go
生成二进制文件:go build
$ go build hello.go
$ ./hello
二、语言结构
1、实例
Go包含:包声明、引入包、函数、变量、语句&表达式、注释
package main//包名,第一行指明,每个go应用都包含一个main包
import "fmt"//告诉编译器使用fmt包,fmt实现I/O函数
func main(){//程序开始执行函数,如果有init()则会先执行init
fmt.PrintIn("Hello,World!")//自动加换行符
//如果标识符以大写字母开头类pubilc,可以被外部包所使用,小写开头类protected,外部不可用,内部可见可用
}
//注意函数花括号的左括号不能在单独行上
三、基础语法
1、行分割
不需要像C++加分号来分割每行,但如果一行写多句,则需要人为加逗号分隔,不建议一行写多句。
fmt.PrintIn("Hello World!")
2、字符串
go字符串可以 + 号连接
fmt.PrintIN("Google"+"Runoob")
3、格式化字符串
Sprintf:格式化字符串并返回,也就是要返回等号左边变量
Printf:格式化字符串并写入标准输出
例子:
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.PrintIn(target_url)
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var fmt.Printf(url,stockcode,enddate)
四、数据类型
1、数字类型
浮点型:complex64、complex128实数和虚数
byte:类uint8
rune:类int32
uintptr:无符号整型,存放一个指针
2、派生类型
1、Channel类型
2、切片类型
3、接口类型interface
五、变量
1、变量声明
var
(1)指定变量类型
var a, c int = 1,2
var a string = "Runoob"
未初始化,设定为零值(系统默认设置值)
(2)自行判断
var d=true
(3):=
已经声明不能再使用:=,:=直接使用
只能在函数体中出现
intVal := 1
//相当于
var intVal int
intVal = 1
2、多变量声明
//1、非全局变量
//1.1、指定变量类型
var vname1, vame2, vame3 type
vname1, vname2, vname3 = v1, v2, v3
//1.2、自行判断
var vname1, vname2, vname3 = v1, v2, v3
//1.3、:=
vname1, vname2, vname3 := v1, v2, v3
//2、全局变量
var(
vname1 v_type1
vname2 v_type2
)
3、注意
1、普通变量未被使用会报错,全局变量不会
2、多变量可以同一行赋值(并行赋值/同时赋值)
var a, b int
var c string
a, b, c = 5, 7, "abc"
可用于一个函数多个返回值
val, err = Func1(var1)
3、空白标识符用于抛弃值
_实际上是一个只写变量,不能得到
_, b = 5, 7
//5被抛弃
六、常量
1、定义
定义格式
const identifier [type] = value
显式类型定义:
const b string = "abc"
隐式类型定义:
const b = "abc"
可以合并简写:
const c_name1, c_name2 = value1, value2
常量还可以用作枚举
const(
Unkown = 0
Female = 1
Male = 2
)
注意,常量可以用len(),cap(),Sizeof()等内置函数计算表达式,必须是内置函数
const(
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
2、itoa
1、iota,特殊常量,可以被编译器修改
在const关键字出现时,被重置为0,const中每新增一行常量声明将使itoa计数一次
const(
a = itoa
b = itoa
c = itoa
)
此处a = 0, b = 1,c = 2,也可以简化为
const(
a = itoa
b
c
)
2、实际使用示例
示例1:
const(
a = iota //0
b //1
c //2
d = "ha" //ha itoa = 3
e //ha itoa = 4
f = 100 //100 itoa = 5
g //100 itoa = 6
h = itoa //7
i //8
)
示例2:
const(
i = 1 << itoa //1 << 0 -> 1
j = 3 << itoa //3 << 1 -> 6
k //3 << 2 -> 12
l //3 << 3 -> 24
)
注:<< n == * ( 2^n )
七、条件语句
1、if
if a < 20 {
fmt.Printf("a小于20\n")
}
相较于C++没有括号
2、else if
if a < 20 {
fmt.Printf("a小于20\n");
} else {
fmt.Printf("a不小于20\n");
}
3、switch
1、普通示例:
switch var1 {
case val1:
case val2:
default:
}
2、Type Switch
数据的类型:
switch x.(type) {
case type:
case type:
default:
}
3、fallthrough
强制执行后面的case语句,不会判断下一条case是否为true
func main() {
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true://进入
fmt.Println("2、case 条件语句为 true")//打印语句1
fallthrough//强制下一个case
case false://强制进入
fmt.Println("3、case 条件语句为 false")//打印语句2
fallthrough//强制下一个case
case true://强制进入
fmt.Println("4、case 条件语句为 true")//打印语句3
//结束
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
4、select
select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
select {
case <- channel1:
case value := <- channel2:
case channel3 <- value:
default:
}
select语法:
1.case必须是一个通道
2.所有channel表达式都会被求值
3.所有被发送的表达式都会被求值
4.多个case,select随机公平选出执行
5.没有可运行default,如果没有default,阻塞,不会重新对channel/表达式求值
func main(){
c1 := make(chan string)
c2 := make(chan string)
go func(){//协程1
time.Sleep(1 * time.Second)
c1 <- "one"//通道c1
}
go func(){//协程2
time.Sleep(2 * time.Second)
c2 <- "two"//通道c2
}
for(int i := 0;i < 2; i++ ){//2秒
select{
case msg1 := <- c1:
fmt.PrintIn("received", msg1)
case msg2 := <-c2:
fmt.PrintIn("received", msg2)
}
//结果:received one received two
}
}
八、循环语句
1、for语句
1、类c++for语句
for init; condition; post { }
2、类c++while语句
for condition { }
3、类c++for(;;)语句
for { }
4、对slice、map、数组、字符串迭代循环
for key, value := range oldMap {
newMap[key] = value
}
5、只读取key
for key := range oldMap
for key,_ := range oldMap
6、只读取value
for _,value := range oldMap
7、for-each range循环
对字符串、数组、切片等迭代输出
func main(){
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.PrintIn(i,s)
}
numbers := [6]int{1,2,3,5}
for i, x := range numbers {
fmt.PrintIn("第 %d 位 x 的值 = %d\n", i, x)
}
//输出结果:0 google 1 runoob 0 1 ,1 2,2 3,3 5,4 0,5 0
}
8、for循环的range格式可以省略key和value
func main() {
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
for key, value := range map1 {
fmt.Printf("key is: %d - value is: %f\n", key, value)
}
for key :=range map1 {
fmt.Printf("key is %d\n", key)
}
for _,value := range map1 {
fmt.Printf("value is %f\n", value)
}
}
2、循环控制语句
1、break
2、continue
3、goto 跳到指定行,不主张使用goto,易造成流程混乱
3、无限循环
for true {
fmt.Printf("这是无限循环\n");
}
九、函数
1、定义
func function_name([parament list]) [return_types]{ }
示例:
func max(num1, num2 int) int {
……
}
2、函数返回多个值
func swap(x, y string) (string, string) {
return y,x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.PrintIn(a, b)//Runoob Google
}
3、传参
值传参、引用传参:Go默认值传递
4、函数用法
1、go语言函数可以作为实参
func main(){
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
fmt.PrintIn(getSquareRoot(9))
}
2、闭包
闭包是匿名函数,可在动态编程中使用
匿名函数是一个内联语句,优点是直接使用函数内变量,不必声明
func getSequence() func() int{
i := 0
return func() int {
i += 1
return i
}
}
func main(){
nextNumber := getSequence()//调用函数
fmt.PrintIn(nextNumber())//1
fmt.PrintIn(nextNumber())//2
fmt.PrintIn(nextNumber())//3
nextNumber1 := getSequence()//调用函数
fmt.PrintIn(nextNumber1())//1
}
3、方法
包含了接收者的函数,接收者可以是命名类型或者结构体类型的值或指针。所有给定类型的方法属于该类型的方法集。
func (variable_name varible_data_type) function_name() [return_type]{ }
type Circle struct {
return float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.PrintIn("圆的面积 = ", c1.getArea())//314
}
//下面属于Circle类型对象中的方法
func (c Circle) getArea() float64 {
return 3.14*c.radius*c.radius
}
十、变量作用域
局部变量、全局变量、形式参数
1、局部变量
作用域只在函数体内,参数和返回值变量也是局部变量
2、全部变量
1、函数体外声明变量,可以在整个包甚至外部包(被导出后)使用
2、全局变量、局部变量名称可以相同,但素局部变量会有被优先考虑
3、形式参数
形式参数会作为函数的局部变量来使用
十一、数组
1、声明数组
var variable_name [size] variable_type
//例如:
var balance [10] float32
2、初始化数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//字面量初始化:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//数组长度不确定:用...代替长度,编译器会根据元素个数自行推断数组长度
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//根据下标来初始化
balance := [5]float32{1:2.0, 3:7.0}
3、访问数组元素
var salary float32 = balance[9]
4、多维数组
(1)初始化方式
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
var threedim [5][10][4]int
(2)初始化二维数组
a := [3][4]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},//注意这里的逗号,因为最后的}不能单独一行
}
//或写为
a := [3][4]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}}//两个}放到一起
(3)创建各个维度元素数量不一致的多维数组
animals := [][]string{}
row1 := []string{"fish", "shark", "eel"}
row2 := []string{"bird"}
row3 := []string{"lizard","salamander"}
animals = append(animals, row1)
animals = append(animals, row2)
animals = append(animals, row3)
5、向函数传递数组
(1)、形参设定数组大小
void myFunction(param [10]int)
(2)、形参未设定数组大小
void myFunction(param []int)
十二、指针
1、声明指针
var var_name *var-type
var ip *int
var fp *float32
2、空指针
没有被分配是为空nil,和其他语言的null一样,指代零值或者空值
3、指针数组
//使用示例:
a := []int{10,100,200}
var i int
var ptr[MAX]*int
for i = 0; i < MAX; i++ {
ptr[i] = &a[i]
}
for i = 0;i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, *ptr[i])
}//a[0] = 10 a[1] = 100 a[2] = 200
4、指向指针的指针
var a int
var ptr *int
var pptr **int
a = 3000
ptr = &a
pptr = &ptr
5、指针作为函数参数
func swap(x *int, y *int)
十三、结构体
1、定义结构体
定义结构体需要type和struct语句:
type struct_variable_type struct {
memeber definition
...
}
声明结构体:
variable_name := structure_variable_type {value1, value2...valueN}
variable_name := structure_variable_type {key1: value1, key2: value2..., keyN:valueN}
2、访问结构体成员
使用结构体成员需要.号操作符
3、结构体作为函数参数
和其他类型区别不大
type Books struct{
...
}
func printBook( book Books){
...
}
4、结构体指针
type Books struct{
title string
...
}
func printfBook(book *Books){//传入结构体指针
...
}
var book Books
book1.title = "1"
printfBook(&book1)//传入地址
十四、切片Slice
与数组相对切片的长度是不固定的,可以追加元素
1、定义切片
var identifier []type //不需要指定长度
//也可以使用make创建
var slice1 []type = make([]type, len)
slice := make([]type, len)
//也可以指定容量
make([]T, length, capacity)//capacity是可选参数
2、切片初始化
//直接初始化切片
s := int {1, 2, 3}//cap=len=3
//数组的引用初始化
s := arr[:]
//数组部分引用,创建新切片,范围为startIndex到endIndex-1
s := arr[startIndex:endIndex]
//范围startIndex到arr最后一个元素
s := arr[startIndex:]
//范围为arr第一个元素到endIndex
s := arr[:endIndex]
//另一个切片来初始化
s1 := s[startIndex:endIndex]
//内置函数make初始化
s := make([]int, len, cap)
3、len()、cap()函数
len()获取长度
cap()测量切片最长可到达多少
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
//切片这里用的是%v
4、空切片nil
切片未初始化前默认为nil,长度为0
5、切片截取
设置上下限来截取[lower-bound:upper-bound]
number := []int{0, 1, 2, 3, 4, 5}
//1~4
number[1:4]
//默认下限是0,0~3
number[:3]
//默认上限是len(s),4~5
number[4:]
6、append()和copy()函数
如果想增加切分的容量,必须创建新的更大的切片并把原分片内容都拷贝过来
var number []int
//允许追加空切片
number = append(number, 0)
//添加一个元素
number = append(number, 1)
//添加多个元素
number = append(number, 2, 3, 4)
//创建新切片number1,是原切片的两倍容量
number1 := make([]int, len(number), (cap(number))*2)
//拷贝number内容到number1
copy(number1,number)
十五、范围Range
range关键字用于for循环中迭代数组array、切片slice、通道channel、集合map的元素,在数组、切片中返回元素索引和索引对应的值,在集合中返回key-value对
1、用于数组、切片
for key, value := range oldMap {
newMap[key] = value
}
for key := range oldMap
for _, value := range oldMap
2、用于map的键值对上
kvs := map[string]string{"a":"apple", "b":"banana"}
for k, v :=range kvs {}
3、用于枚举字符串
for i, c := range "go"{}
十六、Map集合
Map是无序键值对的集合,最重要的一点是通过key来快速检索数据。
如果键不存在,返回该类型的零值。
Map是引用类型,对Map的修改会影响所有引用它的变量。
1、定义Map
使用内建函数make或者使用map关键字来定义Map:
键值对达到容量,Map会自动扩容
map_variable := make(map[KeyType]ValueType, initialCapacity)
//make创建
//创建一个空的Map
m := make(map[string]int)
//创建一个初始初始容量为10的Map
m := make(map[string]int, 10)
//字面量创建
m := map[string]int{
"apple":1,
"banaa":2
}
其他Map操作:
获取键值对:
如果键不存在,ok的值为false,v2的值为该类型的零值
v1 := m["apple"]
v2, ok := m["pear"]
修改元素:
m["apple"] = 5
获取Map的长度:
len := len(m)
遍历Map:
for k,v := range m {}
删除键值对:
delete(m,"banana")
2、delete()函数
删除集合元素
delete(Map, "France")
十七、递归函数
在运行的过程中调用自己
func res(){
res()
}
1、阶乘
func Factorial(n uint64)(result uint64){
if(n > 0) {
result = n * Factorial(n-1)
return result
}
return 1
}
2、斐波那契数列
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
十八、类型转换
基本格式:
type_name(expression)
1、数值类型转换
将整型转换为浮点型
var a int = 10
var b float64 = float64(a)
2、字符串类型转换
字符串转整型:strconv.Atoi(str) 整型转字符串:strconv.Itoa(num)
var str string = "10"
var num int
num, _ = strconv.Atoi(str)
//strconv.Atoi返回两个值,第一个是转化后的整型值,第二个是可能发生的错误,_空白标识符可以忽略这个错误
strNew := strconv.Itoa(num)
字符串转浮点型:strconv.ParseFloat(str, 64) 浮点型转字符串:strconv.FormatFloat(num, 'f', 2, 64)
str := "3.14"
num, err := strconv.ParseFloat(str, 64)
strNew := strconv,FormatFloat(num,'f', 2, 64)
3、接口类型转换
接口类型转换有两种i情况:类型断言、类型转换
(1)类型断言
value是接口类型的变量,type/T是要转成的类型
value.(type) 或者 value.(T)
如果类型断言成功会返回转后值和判断是否成功的布尔值,表示是否成功
var i interface{} = "Hello World"
str, ok := i.(string)
(2)类型转换
T是目标接口类型,value是要转换的值
T(value)
需要保证要转换的值和目标接口类型之间是兼容的,否则编译器会报错
十九、接口
接口把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
使得不同类型绑定到一组公共方法上,实现多态和灵活设计
Go语言中的接口是隐式实现的,一个类型定义了一个接口定义的所有方法,那么它就自动地实现了该接口。
接口使用模板:
//定义接口
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
...
method_namen [return_type]
}
//定义结构体
type struct_name struct {
...
}
//实现接口方法
func (struct_name_variable struct_name) method_name1() [return_type] {
...
}
实现实例1:
定义了一个接口Phone,接口内有一个方法call(),main函数定义一个Phone类型变量,赋值为NokiaPhone、IPhone,可以调用call()方法
type Phone interface {
call()
}
type NokiaPhone struct {
}
type IPhone struct {
}
func (nokiaPhone NokiaPhone) call(){
fmt.PrintIn("Nokia call")
}
func (iPhone IPhone) {
fmt.PrintIn("IPhone call")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phonr.call()
phone = new(IPhone)
phonr.call()
}
实现实例2:
接口变量可以存储任何实现了该接口地类型的值
type Shape interface {
area() float64
}
type Rectangle struct {
width float64
height float64
}
func (r Rectangle) area() float64 {
return r.width * r.height
}
type Circle struct {
radius float64
}
func (c Circle) area() float64 {
return 3.14 * c.radius *c.radius
}
func main() {
var s Shape
s = Rectangle{width: 10, height: 5}
fmt.Printf("矩形面积:%f\n",s.area())
s = Circle{radius: 3}
fmt.Printf("圆形面积:%f\n",s.area())
}
二十、错误处理
通过内置错误接口提供非常简单的错误处理机制
erro是接口类型,其定义:
type error interface {
Error() string
}
实例:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("nagative number")
}
}
result, err := Sqrt(-1)
if err != nil {
fmt.PrintIn(err)
}
二十一、并发
1、go并发
go语言支持并发,我们只需要通过go关键字来开启goroutine即可
goroutine是轻量级线程,是由Golang运行时进行管理的
语法格式:
go 函数名( 参数列表 )
go f(x, y, z)
f(x, y, z)
同一个程序中所有goroutine共享同一个地址空间
实例:
func say(s string) {
for i := 0;i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.PrintIn(S)
}
}
func main() {
go say("world")
say("Hello")
}
//输出hello和world没有固定先后顺序,因为它们是两个goroutine在执行
2、通道channel
通道是用于来传递数据的一个数据结构
通道可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<-用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道
ch <- v //把v发送到通道ch
v := <- ch //从ch接收数据,并把值赋给v
声明通道,使用chan关键字,通道在使用前必须先创建:
ch := make(chan int)
注意:默认情况下,通道不带缓冲区,有发送端发送数据,同时必须有接收端来接收数据
实例:
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum
}
func main(){
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s/2)], c) //-5
go sum(s[len(s/2), c]) //17
x, y := <-c, <-c //从通道c中接收
fmt.PrintIn(x, y, x+y)
}
(1)通道缓冲区
通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小
ch := make(chan int, 100)
带缓冲区的通道允许发送端和接收端之间是异步的,不需要接收端立刻获取数据
func main() {
ch := make(chan int, 2)
//带缓冲区可以同时发送两个数据
ch <- 1
ch <- 2
fmt.PrintIn(<-ch)
fmt.PrintIn(<-ch)
}
(2)遍历通道与关闭通道
通过range关键字来实现遍历读取,类数组和切片
v, ok := <-ch
如果通道接收不到数据后,可以通过调用close()来关闭
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)
}