go语言学习

218 阅读7分钟

学习参考: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)
}