go语言学习笔记-基础知识-3
相关文档
1、简介
1.1 什么是GO
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。
1.2 目前的编程语言排名
数据来自HELLOGITHUB
1.3 语言结构
语言结构包含:包声明、引入包、函数、变量、语句 & 表达式、注释等模块。
1.4 语言特点
1.4.1 、每行程序结束后分号(;)可有可无,且不鼓励用。
1.4.2 、大括号 { 不能够换行放置。
1.4.3、没有用到的变量编译会报错。
1.4.4、 If判断语句小括号可有可无。
1.4.5、For循环语句不能加小括号。
2、关键字、标识符、数据类型、变量、常量等
2.1 关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字
2.2 预定义标识符
2.3 数据类型(这边简单和java做了个对比)
2.4、变量
2.4.1、变量的命名规范
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
2.4.2、变量的声明方式
//第一种,指定变量类型,如果没有初始化,则变量默认为零值。
var a1 int8//声明一个变量
var a2, a3 string//多个变量
//第二种,根据值自行判定变量类型。
var a4 = 1
var a5 , a6 = 1 , “hallo”
//第三种,省略 var使用 := 格式:
intVal :=1
2.4.3、 变量作用域
Go 语言中变量可以在三个地方声明:
函数内定义的变量称为局部变量
函数外定义的变量称为全局变量
函数定义中的变量称为形式参数
2.4.4 、初始化变量 new和make的区别
1) new可以分配任意类型的数据,new初始化变量返回的是变量的地址,new并不常用,实际编码过程中我们通常都是采用短语句声明以及结构体的字面量达到我们的目的,如:u:=user{}
2) make 的作用是为 slice,map 或 chan 初始化并返回引用(T)
2.5、 值类型和引用类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值
你可以通过 &i 来获取变量 i 的内存地址
2.6 、常量
2.6.1、 const
//显式类型定义:
const b string = "abc"
//隐式类型定义:
const b = "abc"
//常量不能被修改
2.6.2、特殊的常量 iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
func f3() {
const (
a0 = 0;
a1= 1
a2 = iota
a3 = iota
a4
a5
)
fmt.Println(a0, a1, a2, a3, a4, a5)
}
//输出结果为 0 1 2 3 4 5
3、运算符
3.1、算数运算符
3.2、关系运算符
3.3、逻辑运算符
3.4、位运算符
3.5、赋值运算符
3.6、其他运算符
3.7、运算符优先级(建议用小括号区分优先级)
4 条件语句&循环语句
4.1、 If条件语句
//注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。如:1>2?1:2
//if条件语句
if(true){
}
//if else 条件语句
if(true){
}else {
}
//if 嵌套条件语句
if(true){
if(true){
}
}
//switch条件语句
switch 1 {
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
default:
fmt.Println(0)
}
4.2、for循环语句
//第一种
for i:=0;i<10 ;i++ {
fmt.Println("第一种 i=",i)
}
//第二种、相当于java中的while
var flag=true
i2:=0
for flag {
i2++
fmt.Println("第二种...i2=",i2)
if i2==9 {
break
}
}
//第三种、使用range关键字取值
var arr1 =[]int{1,2,3,4,5}
for k,v:= range arr1 {
fmt.Printf("第三种 k=%v,v=%v",k,v)
}
4.3、break的使用
//1、用于循环语句中跳出循环,并开始执行循环之后的语句。
//2、break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
//3、在多重循环中,可以用标号 label 标出想 break 的循环。
for i := 1; i<=3;i++ {
fmt.Println("111")
break
fmt.Println("222")
}
//打印结果
111
4.4、continue
Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。
for i := 1; i < 5; i++ {
if i==2 {
continue
}
fmt.Println(i)
}
//打印结果,2 不会打印
1 3 4
4.5、goto关键字
Go 语言的 goto 语句可以无条件地转移到过程中指定的行。
for i := 1; i < 5; i++ {
if i==2 {
goto re
}
fmt.Println("contineu i=",i)
}
re:
//当 i=2 是会调出循环并从 re: 开始执行代码
5、函数
函数是基本的代码块,用于执行一个任务。
1)、Go 语言最少有个 main() 函数。
2)、你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
3)、函数声明告诉了编译器函数的名称,返回类型,和参数。
4)、Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。
//函数实例代码
//main函数
func main() {
i, s := ff(1, "22")
fmt.Println(i,s)
}
//普通函数
func ff(a1 int ,a2 string)(int ,string) {
fmt.Println("++++++++++++++++++++++++++ 15 ")
return a1,a2
}
5.1、值传递
1)、传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
2)、默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
//值传递,在函数 ff1() 给a1,a2重新赋值,实际上并没有a1,a2的值并没有改变
func main() {
var a1, a2 = 1, 2
fmt.Println(a1, a2)
ff1(a1, a2)
fmt.Println(a1, a2)
}
func ff1(a1 int, a2 int) {
fmt.Println("++++++++++++++++++++++++++ 15 ")
a2 = 1
a1 = 2
}
5.2、引用传递
//引用传递
func ff2_3() {
s1 := student{"zz"}
fmt.Println(s1)//打印 zz
ff2_2(&s1)
fmt.Println(s1)//打印 222
}
func ff2_2(s1 *student) {
s1.name="222"
}
//student 为结构体
type student struct {
name string
}
5.3、函数作为实参
Go 语言可以很灵活的创建函数,并作为另外一个函数的实参
//函数
func main() {
ff3_2(ff3_1)
}
func ff3_1(a int) {
b := a * 10
fmt.Println(b)
}
//函数作为实参
func ff3_2(f func(int)) {
a2 := 2
f(a2)
}
5.4、 闭包
1)、可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。
2)、也可以理解成在一个函数中访问另一个函数的局部变量
func main() {
i := ff4()
i2 := i(10)
fmt.Println(i2)
}
//闭包
func ff4() func(int) int{
a1:=1
return func(v int) int{
return a1+v
}
}
6 结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
6.1 结构体的定义和使用
func main() {
//初始化结构体 stud
var ss stud=stud{"zz"}
//访问结构体
var name =ss.name
fmt.Println(ss)
}
//定义结构体 stud
type stud struct {
name string
}
6.2、结构体指针
1)、你可以定义指向结构体的指针类似于其他指针变量
2)、如果想在函数里面改变结果体数据内容,需要传入指针
//
func main() {
s1 := student{"zz"}
ff2_2(&s1)
fmt.Println(s1)
}
func ff2_2(s1 *student) {
s1.name="222"
}
type student struct {
name string
}
7 方法
在 Go 中,类型可以定义接收此类型的函数,即方法,方法和函数只差了一个,那就是方法在 func 和标识符之间多了一个参数。
//函数
func main() {
ff5()
}
func ff5() {
var s ss;
s.set("aaaaa")
get := s.get()
fmt.Println(get)
}
//方法 结构体 ss 的 get 方法
func (s ss) get() string {
return s.name
}
//方法 结构体 ss 的 set 方法
func (s *ss) set(a string) {
s.name=a
}
type ss struct {
name string
}
8 数组
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型
8.1、数组
//初始化一维数组
func ff7() {
var arr1 =[4]int{1,2,3,4}
fmt.Println(arr1)
for v :=range arr1 {
fmt.Println(v)
}
v1:=arr1[0]
fmt.Println(v1)
}
8.2、切片
1)、Go 语言切片是对数组的抽象。
2)、Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
8.2.1、区分数组和切片
//指定元素个数为数组 “... 标识根据 后面定义的元素个数自动计算数组长度
a:=[5] int {1,2,3,4,5}
c := [...]int{3, 5, 7}
我个人总结的区分方式
1)、使用编辑器区分
创建数组
创建切片
2)、数组没有append方法,数组编译会报错
8.2.2、切片的自动扩容如何体现
//通过代码测试切片自动扩容
s:=[]int{1}
fmt.Println("长度=",len(s),"容量=",cap(s))
s=append(s, 1)
fmt.Println("长度=",len(s),"容量=",cap(s))
s=append(s, 1)
fmt.Println("长度=",len(s),"容量=",cap(s))
结果如图:
9、指针
一个指针变量指向了一个值的内存地址
9.1 指针声明和使用
//指针声明和使用
func ff9() {
a:=1;
var b float32 =8787872.298989
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
fmt.Println(ip,fp)
ip=&a
fp=&b
fmt.Println(ip)
fmt.Println(fp)
fmt.Println(*ip)
fmt.Println(*fp)
}
9.2、指针数组
func ff10() {
var p =[3]*int{}
a1:=1
a2:=2
a3:=3
p[0]=&a1
p[1]=&a2
p[2]=&a3
for i := 0; i<3;i++ {
fmt.Println(p[i])
fmt.Println(*p[i])
}
}
9.3、指向指针的指针
func ff11() {
var p *int
var pp **int
a1:=1;
p=&a1
pp=&p
fmt.Println(p)
fmt.Println(pp)
fmt.Println(*p)
fmt.Println(*pp)
fmt.Println(**pp)
}
9.4、指针作为函数参数
//指针作为函数参数
func ff12() {
a1:=1
a2:=2
ff12_1(&a1,&a2)
fmt.Println(a1,a2)
}
func ff12_1(a1 ,a2 *int) {
temp:=*a2
*a2=*a1
*a1=temp
}
10、范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
10.1、使用range
func ff15() {
q:=[]int{1,2,3}
for k,v:=range q{
fmt.Println(k,v)
}
}
11、Map
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
11.1、 定义 Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
func ff16(){ //初始化map,注意不初始化的map不能赋值 var m1 map[int]int= map[int]int{} var m2 map[string]string=make(map[string]string) m1[1]=1 m1[2]=2 fmt.Println(m1) fmt.Println(m2) //遍历map for k,v:=range m1{ fmt.Println(k,v) } //取值并验证是否存在 i,ok := m1[1] if ok { fmt.Println(i) } //删除元素 delete(m1, 1)}
12、递归函数
递归,就是在运行的过程中调用自己。
//递归函数
func ff17(i int) int {
i++
if i==100 {
return i
}else {
return ff17(i)
}
}
13、类型转换
类型转换用于将一种数据类型的变量转换为另外一种类型的变量
//类型转换
func ff18() {
a1 := 1;
a2 := 2
f1 := float32(a1) + float32(a2)
fmt.Println(f1)
fmt.Println(reflect.TypeOf(a1))
fmt.Println(reflect.TypeOf(a2))
fmt.Println(reflect.TypeOf(f1))
//s1:=""+a1
s1:=""+strconv.Itoa(a1)
fmt.Println(s1)
//f2:=a1/f1
f2:=float32(a1)/f1
fmt.Println(f2)
}
14、接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
接口中的方法都得实现
//接口
func ff19() {
var jk jiekou
jk=aaa{}
jk.call()
jk=bbb{}
jk.call()
}
type jiekou interface {
call()
}
type aaa struct {
}
func (a aaa) call() {
fmt.Println("aaaa")
}
type bbb struct {
}
func (b bbb) call() {
fmt.Println("bbbb")
}
15并发
15.1、 goroutine
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
//并发-goroutine
func ff20() {
go ff20_1()
go func() {
for i:=0;i<10;i++ {
fmt.Println("2")
}
}()
}
//TODO
func ff20_1() {
for i:=0;i<10;i++ {
fmt.Println("1")
}
}
15.2、 通道Chan
15.2.1、chan 可以理解为队列,遵循先进先出的规则。
// 声明不带缓冲的通道
ch1 := make(chan string)
// 声明带10个缓冲的通道
ch2 := make(chan string, 10)
// 声明只读通道
ch3 := make(<-chan string)
// 声明只写通道
ch4 := make(chan<- string)
//写入 chan
ch1 := make(chan string, 10)
ch1 <- "a"
// 读取 chan
val, ok := <- ch1
// 或
val := <- ch1
//4 Close
//1、close 以后不能再写入,写入会出现 panic
//2、重复 close 会出现 panic
//3、只读的 chan 不能 close
//4、close 以后还可以读取数据
//5、带缓冲的通道,如果长度等于缓冲长度时,再进就会阻塞。
15.2.2 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片(注意:range会一直读取导致线程阻塞)
//通道
func ff21() {
var c1 =make(chan int)
go func() {
c1<-1
c1<-2
c1<-3
}()
var i=1
for k:=range c1 {
fmt.Println(k)
i++
if i==4{
close(c1)
}
}
}
15.3.Select
换一种理解方式
select就是用来监听和channel有关的IO操作,当 IO 操作发生时,触发相应的动作。
1、每个 case 都必须是一个通信
2、所有 channel 表达式都会被求值
3、所有被发送的表达式都会被求值
4、如果任意某个通信可以进行,它就执行,其他被忽略。
5、如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
如果有 default 子句,则执行该语句。
如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
16、错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型
16.1 、panic
抛出异常,后续代码不会执行
16.2 、defer
defer语句延迟执行一个函数,该函数被推迟到当包含它的程序返回时(包含它的函数 执行了return语句/运行到函数结尾自动返回/对应的goroutine panic)执行。defer的函数的任何返回值都会被丢弃。 在声明时不会立即执行,而是在函数 return 后,再按照 FILO (先进后出)的原则依次执行每一个 defer.
16.3、 Recover
recover只能在defer语句中使用, 直接调用recover是无效的.recover()方法有一个返回值,如果recover()捕获到panic,则返回panic对象,否则返回nil。类似php的try{} catch() {}
//异常处理
func f17() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}else {
fmt.Println(err)
}
}()
fmt.Println(1)
//a1:=1/0
//fmt.Println(a1)
//panic("抛出异常")
fmt.Println(2)
}
完!