1、变量定义三种方法
package main import "fmt" func main(){ var a int = 10 //第一种 fmt.Println(a) b int = 10 fmt.Println(b) //第二种 c := 10 fmt.Println(c) //第三种 }
2、数据类型
布尔型:
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
数字类型:
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码
字符串类型:
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本
3、常量
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
定义方式两种:
//第一种 const b string = "abc" //第二种 const b = "abc"
4、函数
1)函数调用
package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 var ret int /* 调用函数并返回最大值 */ ret = max(a, b) fmt.Printf( "最大值是 : %d ", ret ) } /* 函数返回两个数的最大值 */ func max(num1, num2 int) int { /* 定义局部变量 */ var result int if (num1 > num2) { result = num1 } else { result = num2 } return result }
2)函数返回值
package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("Google", "Runoob") fmt.Println(a, b) }
3)闭包函数:
定义在函数内,对外部作用于有引用
func test(a int) (func()) { //var c int =100 b:=func() { fmt.Println(a) fmt.Println("我是闭包函数") } return b
5、if-else
package main import "fmt" func main(){ if a :=90;a>90{ fmt.Print("大于") }else if a==90{ fmt.Print(a) } }
6、包
//在同一个包下,变量,函数,都不能重复定义 //在包内定义的函数如果是小写字母开头,表示只能在包内部使用 //在外部包想使用,必须首字母大写
// 包的使用 package main import "mypackage" import "fmt" func main() { //想使用mypackage包下的test函数和test1函数 mypackage.Test1() fmt.Println("xxx") }
package mypackage import "fmt" //在同一个包下,变量,函数,都不能重复定义 //在包内定义的函数如果是小写字母开头,表示只能在包内部使用 //在外部包想使用,必须首字母大写 func Test1() { fmt.Println(test(1,2)) fmt.Println("xxxx") }
package mypackage //通常情况下,包名就是文件夹名,在同一个文件夹下,包名必须一致 func test(a,b int) int{ return a+b }
7、循环
package main import "fmt" func main(){ for i:=0;i<10;i++{ fmt.Println(i) } }
ps:for后面三个参数可以省略,当全部省略等同于其他语言的while循环
8、switch语句
switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。它可以被认为是替代多个 if else
子句的常用方式
-如果条件都不满足,走default默认
a:=11 switch a { case 1: fmt.Println("1") case 2: fmt.Println("2") case 10: fmt.Println("10") default: fmt.Println("不知道")
-fallthrough,穿透,无条件执行下一个case的内容
a:=10 switch a { case 1: fmt.Println("1") fmt.Println("xxxx") case 2: fmt.Println("2") case 10: fmt.Println("10") //穿透,无条件执行下一个case的内容 fallthrough case 11: fmt.Println("11") test5() fallthrough case 12: fmt.Println("12") }
9、数组
1)数组
数组是同一类型元素的集合,Go 语言中不允许混合不同类型的元素,例如包含字符串和整数的数组。
//三种一样 var a [6]int=[6]int{1,2,3} var a =[6]int{1,2,3} a :=[6]int{1,2,3}
例:
package main import ( "fmt" ) func main() { a := [3]int{12} fmt.Println(a) }
声明一个长度为 3 的数组,但只提供了一个值 12
,剩下的 2 个元素自动赋值为 0
。这个程序将输出 [12 0 0]
2)数组是值类型:即所有函数传参都是copy传参
3)数组的长度,内置函数len
4)数组大小是类型的一部分
var a [4]int=[4]int{1,2,} var b [5]int=[5]int{1,2,} 因为数组大小不一样,所以上面不是同类型
5)与或非:&&、||、!(Go语言没有and、or和not等判断字符)
6)通过range迭代,迭代可选1-2个参数,第一个为索引,第二个为迭代的值
for i,v:=range a { //for i:=range a { fmt.Println("------",i) fmt.Println(v) }
7)多维数组
var a [7][2]int a[0][1]=100 fmt.Println(a) //[[0 100] [0 0] [0 0] [0 0] [0 0] [0 0] [0 0]]
10、切片
创建 c:= [] int {6,7,8} 使用 make 创建一个切片 i := make([]int, 5, 5)
创建一个有 3 个整型元素的数组,并返回一个存储在 c 中的切片引用
ps:
1/切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中
2/切片的长度是切片中的元素数。切片的容量是从创建切片索引开始的底层数组中元素数。
3/追加切片元素append
-如果添加元素大于切片容量,则容量会翻一倍
-切片类型的零值为nil,一个nil切片的长度和容量为0
4/多维切片
package main import ( "fmt" ) func main() { pls := [][]string { {"C", "C++"}, {"JavaScript"}, {"Go", "Rust"}, } for _, v1 := range pls { for _, v2 := range v1 { fmt.Printf("%s ", v2) } fmt.Printf(" ") } } // C C++ JavaScript Go Rust
11、Maps(是引用类型:当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构)
map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值。map 的零值是 nil
创建maps:
make(map[type of key]type of value)
给map添加元素:根据key赋值(xxx[key]=value)
获取map中元素:xxx[key],如果不存在,会返回零值(对应该元素类型的零值)
删除map中元素: [delete(map, key)],次函数无返回值
获取map长度:len()
12、字符串
len 统计字节数、utf8.RuneCountInString 统计字符数
遍历字符串的好方法:for range
name :="abc老家伙" for _,v:=range name{ fmt.Println(string(v)) fmt.Printf("%T",v) fmt.Println() } # 如果用简单循环,遍历出的是字节,range遍历的是字符
13、指针
记住三点: 1)& 取地址符号
2)* 放在类型旁边,表示指向这个类型的指针
3)* 放变量旁边,表示解引用(反解)
14、结构体
结构体是用户定义的类型,表示若干个字段(Field)的集合。
1)结构体的声明
type Employee struct {
Name,gender string #同类型写一行,用逗号隔开
age,salary int
}
ps:不声明type,则创建是匿名结构体
2)访问结构体字段
点好操作符 . 用于访问结构体的字段
3)匿名字段(即创建结构体时,字段可以只有类型,而没有字段名)
type Person struct {
string
int
}
4)嵌套结构体
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
5)结构相等性
结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的
如果结构体出现不可比较类型,则不能比较,例如结构体含map类型
15、方法(类似python方法)
方法其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型
创建语法:func (a 结构体)函数名(参数)(返回值){}
package main import ( "fmt" ) type Employee struct { name string salary int currency string } /* displaySalary() 方法将 Employee 做为接收器类型 */ func (e Employee) displaySalary() { fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary) } func main() { emp1 := Employee { name: "Sam Adolf", salary: 5000, currency: "$", } emp1.displaySalary() // 调用 Employee 类型的 displaySalary() 方法
值接收器方法:在内部修改值,不会影响外部的值
指针接收器方法:在内部修改至,会改变外部的值
16、接口(一系列方法的集合)
在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。
1)定义一个鸭子类型接口
package main import "fmt" //定义一个鸭子类型接口 type Duck interface { speak() } //一个鸭子和方法 type Tduck struct { name string age int } func (a Tduck) speak(){ fmt.Println("Tduck方法",a.name) } //另一只鸭子和方法 type PDuck struct { wife,name string } func (a PDuck) speak(){ fmt.Println("PDuck",a.name) } func main(){ pD:=PDuck{name:"水鸭子"} tD:=Tduck{name:"唐老鸭"} speak(pD) speak(tD) } func speak(a Duck){ a.speak() }
2)空接口
没有包含方法的接口称为空接口。空接口表示为 interface{}
。由于空接口没有方法,因此所有类型都实现了空接口。
3)断言
//断言 func speak(p Tduck) { a:=p.(PDuck) fmt.Println(a.wife) p.speak() }
想取出类型其他的属性,需用到判断,用switch
//承接1)鸭子接口 func speak(p Duck) { switch a:=p.(type) { case PDuck: fmt.Println("唐") fmt.Println(a.wife) case Tduck: fmt.Println("普通") fmt.Println(a.name) } }
4)多接口和接口嵌套
5)接口的零值
package main import "fmt" //接口的零值 nil 接口是引用类型 type Describer interface { Describe() } func main() { var d1 Describer if d1 == nil { fmt.Println("xxxx") } }
17、异常处理
//异常处理 //defer panic recover //defer 表示延迟调用,即便程序出现严重错误,也会执行 //panic 就是python中的raise(主动抛出异常) //recover 恢复程序,继续执行
package main import "fmt" //异常处理 //defer panic recover //defer 表示延迟调用,即便程序出现严重错误,也会执行 //panic 就是python中的raise(主动抛出异常) //recover 恢复程序,继续执行 func main() { //先注册,后调用 //defer fmt.Println("xxxx") //defer fmt.Println("yyy") f1() f2() f3() } func f1() { fmt.Println("f1...") } func f2() { defer func() { if a:=recover();a!=nil{ //a 如果不等于nil,表示程序出了异常,a 就是异常信息 //a 等于nil,表示没有异常 //fmt.Println("出错了") fmt.Println(a) } //用于会被执行(相当于finally) }() fmt.Println("f2...") //var a =make([]int,3,3) //fmt.Println(a[4]) panic("你给我出去") } func f3() { fmt.Println("f3...") }
18、错误处理
package main import ( "errors" "fmt" ) //错误 func circleArea(radius int) (int, error) { if radius < 0 { return 0, errors.New("错误信息") //panic("xddd") } return 100, nil } func main() { a,_:=circleArea(-10) if err!=nil{ fmt.Println(err) } //fmt.Println(err) fmt.Println(a) _,err:=fmt.Println() if err!=nil{ fmt.Println("打印出错") } }
19、Go并发和协程
1、Go 编程语言原生支持并发。Go 使用 Go 协程(Goroutine) 和信道(Channel)来处理并发。
2、Go协程:一起并发的函数或方法,轻量级线程,而且成本很小
1)go协程会复用数量更少的os线程
2)协程之间通过信道来通信
3)启动:调用函数或方法时,在前面加上关键字go即可
package main import ( "fmt" ) func hello() { fmt.Println("Hello world goroutine") } func main() { go hello() fmt.Println("main function") }
20、信道(管道)
1、介绍
信道可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收
ps:所有信道关联了一个类型,只能运输这种类型的数据,而运输其他类型的数据都是非法的。
2、定义
chan T 表示 T 类型的信道。
信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道。
a := make(chan int)
3、信道读取值(默认是阻塞的)
data := <- a // 读取信道 a
a <- data // 写入信道 a
4、死锁现象
5、单向信道(了解)
package main import "fmt" func sendData(sendch chan<- int) { sendch <- 10 } func main() { sendch := make(chan<- int) go sendData(sendch) fmt.Println(<-sendch) }
6、关闭信道和使用for range遍历信道
1)当从信道接收数据时,接收方可以多用一个变量来检查信道是否已经关闭。
v, ok := <- ch
package main import ( "fmt" ) func producer(chnl chan int) { for i := 0; i < 10; i++ { chnl <- i } close(chnl) } func main() { ch := make(chan int) go producer(ch) for { v, ok := <-ch if ok == false { break } fmt.Println("Received ", v, ok) } }
2)也可以用range,从信道接收,只有一个变量
package main import ( "fmt" ) func producer(chnl chan int) { for i := 0; i < 10; i++ { chnl <- i } close(chnl) } func main() { ch := make(chan int) go producer(ch) for v := range ch { fmt.Println("Received ",v) } }
21、缓冲信道
信道是阻塞的,我们可以利用缓冲信道,当信道缓冲为空时,才会阻塞信道接收数据
当信道缓冲已满,会阻塞信道发送数据。
1、创建缓冲信道(capacity表示容量)
ch := make(chan type, capacity)
package main import ( "fmt" "time" ) func write(ch chan int) { for i := 0; i < 5; i++ { ch <- i fmt.Println("successfully wrote", i, "to ch") } close(ch) } func main() { ch := make(chan int, 2) go write(ch) time.Sleep(2 * time.Second) for v := range ch { fmt.Println("read value", v,"from ch") time.Sleep(2 * time.Second) } }
2、死锁
当超出了信道的容量,产生阻塞,又继续添加值时会发生死锁
程序会在运行时触发 panic错误信息
3、长度和容量
长度:即信道内含数据个数
容量:最多能放几个数据
4、Waitgroup (即等待所有go协程执行完成)
假设我们有 3 个并发执行的 Go 协程(由 Go 主协程生成)。Go 主协程需要等待这 3 个协程执行结束后,才会终止。这就可以用 WaitGroup
来实现。
ps:因为sync包下的WaitGroup,是个值类型,当参数传递是需要取地址
package main import ( "fmt" "sync" "time" ) func process(i int, wg *sync.WaitGroup) { fmt.Println("started Goroutine ", i) time.Sleep(2 * time.Second) fmt.Printf("Goroutine %d ended ", i) wg.Done() } func main() { no := 3 var wg sync.WaitGroup for i := 0; i < no; i++ { wg.Add(1) go process(i, &wg) } wg.Wait() fmt.Println("All go routines finished executing") }
//通过信道实现 func main() { var a =make(chan bool) for i:=0;i<5;i++{ go test6(a,i) } for i:=0;i<5;i++{ <-a } fmt.Println("都执行完了") } func test6(a chan bool,i int) { time.Sleep(time.Second*2) fmt.Println(i) a<-true }
22、select
数据先回来先取,同时回来时随机选取一个,
一般防止单个任务遇到阻塞情况,所以开多任务提取数据,提高性能
import ( "fmt" "time" ) func server1(ch chan string) { time.Sleep(6 * time.Second) ch <- "from server1" } func server2(ch chan string) { time.Sleep(3 * time.Second) ch <- "from server2" } func main() { output1 := make(chan string) output2 := make(chan string) go server1(output1) go server2(output2) select { case s1 := <-output1: fmt.Println(s1) case s2 := <-output2: fmt.Println(s2) } }
23、mutex
Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件
package main import ( "fmt" "sync" ) //通过锁实现 var x = 0 func increment(wg *sync.WaitGroup,m *sync.Mutex) { m.Lock() x = x + 1 m.Unlock() wg.Done() } func main() { var w sync.WaitGroup var m sync.Mutex //值类型,传递地址 for i := 0; i < 1000; i++ { w.Add(1) go increment(&w,&m) } w.Wait() fmt.Println("final value of x", x) }
也可以用信道实现
package main import ( "fmt" "sync" ) var x = 0 func increment(wg *sync.WaitGroup, ch chan bool) { ch <- true x = x + 1 <- ch wg.Done() } func main() { var w sync.WaitGroup ch := make(chan bool, 1) for i := 0; i < 1000; i++ { w.Add(1) go increment(&w, ch) } w.Wait() fmt.Println("final value of x", x) }
等待所有go协程执行完成