go语言的基本语法
学了go语言但是一直没整理。。。那怎么证明我学了?如果学了之后忘了怎么复习?遂诞生这几篇,当作Linux中间的小插曲
整理一下go语言的基本语法:
package main
import (
"bufio"
"fmt"
"os"
)
在使用对应函数或接口的时候,要通过import导入对应的包,就像C语言中的头文件一样
变量声明及赋值:
1.正经的声明赋值使用关键字var:
var 变量名,数据类型
变量名=赋值
两行分别实现声明和赋值,也可以这样:
var 变量名 数据类型 =赋值
还可以批量声明赋值:
var (
变量名1 变量类型1
变量名2 变量类型2
)
2.类型推断,不写明什么类型,后面编译的时候go会自己猜
var 变量名=赋值
3.简短声明,冒号左边至少有一个为赋值的新变量
sum :=100
定义全局变量不能简短声明,在go里面变量定义了就要用,不然会报错
go是静态语言(得显式的说明变量类型)和强类型语言(不会根据运算的语境切换数据的类型)
所以我们使用的时候如果不是对应类型的数据做运算的时候需要强制转换,定义、赋值、运算要保持一致:
内建变量类型
- bool、string
- (u)int、(u)int8、(u)int16、(u)int32、(u)int64、uintptr,u开头为无符号整型,范围没有负数部分
- byte、rune
- float32、float64、complex64、complex128
运算符:
转化为二进制,按位操作
按位&:对应位置都为1才为1,有一个0就为0
按位|:对应位置都为0才为0,有一个1就为1
异或^:
1.a^b按位运算,相同为1,不同为0
2.^a按位取反
1->0,0->1
go语言特有:位清空&^
a&^b对于b上的数值,如果为0,则取a对应位上的数值
如果为1,则结果位就取0
">>"和"<<":
条件判断:
if做条件判断,可以在{}前声明变量,这个变量只作用于该if
if res := 9; res > 0 { //if外面访问不了if里面的res,只作用于当前的if
fmt.Println("res>0")
}
switch也可以这么用:
func main() {
switch num := 16; num {
case 2:
fmt.Println("2进制")
case 8:
fmt.Println("8进制")
case 10:
fmt.Println("10进制")
case 16:
fmt.Println("16进制")
default:
fmt.Println("内容不正确")
}
fmt.Println("程序结束")
}
default放哪里都行,只有当case一个都执行不了的时候,才能这么用
func main() {
month := 5
switch month {
case 1, 3, 5, 7, 8, 10, 12:
fmt.Println("31天")
case 2:
fmt.Println("28或29天")
default:
fmt.Println("30天")
}
fmt.Println("程序结束")
}
case条件支持多个值,每个值使用逗号分开
但是一般情况只会执行一个case,如果需要继续执行下面的分支,需要fallthrough:
fallthrough,满足条件后向下穿透,无条件执行下一个case的语句,放在case里面的最后一行
func main() {
switch num := 1; num {
case 1:
fmt.Println("1")
fallthrough
case 2:
fmt.Println("2")
case 3:
fmt.Println("3")
fallthrough
case 4:
fmt.Println("4")
default:
fmt.Println("不是1,2,3,4")
}
fmt.Println("程序结束")
}
for的写法:
标准写法
for 表达式1;表达式2;表达式3{
}
其他写法:
1.省略表达式1和3
for (;)表达式2(;)(while)分号可不写
2.省略所有表达式
for(
}
相当于(while(1))死循环
遍历数组:
arr1 := [5]int{1, 2, 3, 4, 5}
for index, value := range arr1 {
fmt.Printf("下标是%d,数值是%d", index, value)
}
数组、切片和映射
数组的数据类型为值类型,声明:
[长度]类型
值类型:array,int,float,string都是值类型
数组可以值传递,可值传递的数据类型为存储数值本身
传递的数值为备份(副本)
数组打印:
arr1 := [5]int{1, 2, 3, 4, 5}
for index, value := range arr1 {
fmt.Printf("下标是%d,数值是%d", index, value)
}
引用类型:传递地址,如slice(切片),map(映射)
silce声明:
var 名字 []
像vector,动态数组, 左闭右开
array := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //设定初始数组
ss1 := array[1:9] //1-9,len:8,cap:9
ss2 := array[:4] //0-4,len:4,10
fmt.Printf("%p,%d,%d\n", ss1, len(ss1), cap(ss1)) //检查容量
fmt.Printf("%p,%d,%d\n", ss2, len(ss2), cap(ss2))
ss1和ss2是依托于array产生的切片,array是切片的底层数组
更改数组内容直接:
array[4] = 100
更改切片内容同时也会更改底层数组的内容
ss2 = append(ss2, 50, 50, 50, 50) //ss2的后面改变,底层数组也被改变
fmt.Println(ss2)
fmt.Println(array) //底层数组也被更改
除了这种明着写的切片的底层数组,用make创建切片也会有自己的底层数组
哪怕长度和容量为0,也有底层数组
切片是引用类型,数组是值类型,所以我们直接打印一个切片的地址是指向他的底层数组的地址,对它再取地址,才是切片的地址
make函数:
make函数可以创建引用类型的数据,除了slice还有别的
使用格式:
make(type,len,cap)类型,长度,容量
切片一旦扩容,指向一个新的数组地址,容量成倍增长,最大容量为1024
创建map:
var map3 = map[string]int{"GO": 98, "Python": 79, "Html": 93}
map:映射,键值对的集合,引用类型
存储特点:存储的是无序的键值对
键不能重复,与value值一一对应,否则新的会覆盖旧的
每次创建一个map要:
1.map创建
2.判断map是否为空(map为空不能用,所以很重要)
3.存键值对到map中
4.获取数据
每种数据类型未赋值的初始值:
int:0
float:0.0->0
string:""
array:[00000]slice:nil(空,但是因为有底层数组,所以未赋值也可直接使用
map:nil(空,但是不可以直接使用
把map比喻为盘子上放着饺子,那么:
var map1 map[int]string //[key的类型]值的类型,没有盘子没有饺子
var map2 = make(map[int]string) //有盘子没有饺子
var map3 = map[string]int{"GO": 98, "Python": 79, "Html": 93} //有盘子,也装了饺子
//map1[1] = "hello"会报错
打印他们三个的结果:
//流程大概是这样:
var map1 map[int]string //定义map
if map1 == nil {//检查是否为空
map1 = make(map[int]string)
fmt.Println(map1 == nil)
}
map1[3] = "xixixi"//输入数据
map1[4] = ""
fmt.Println(map1[3])//获取数据
fmt.Println(map1[2])
我们在上面的代码中打印了map1[2],但是我们没输这个键值对,会打印什么?
当然是打印“空”了
我们可以用ok-idiom(惯用法)来测试自己的键值对是否存在:
v4, ok := map1[4]
if ok == true {
fmt.Println("v4==", v4)
}
打印出来也是空格
删除数据,函数delete
delete(map1, 4)
fmt.Println(map1)
删除后的结果:
遍历map:
map4 := make(map[int]string)
map4[1] = "今天是5.27号"
map4[2] = "我的自行车被学校拖走了"
map4[3] = "我的雪顶咖啡化完了"
map4[4] = "风是透明的河流"
map4[5] = "水是流动的山川"
for k, v := range map4 {
fmt.Println(k, v) //map为无序存储,所以每次打印顺序不一样
}
如何有序打印:
如果你的键值对中的k是有序的,可以按这个顺序打印:
for i := 1; i <= len(map4); i++ {
fmt.Println(i, "-->", map4[i])
} //如果k没有顺序就无法这样打印
可以利用map和silce的结合:
1.获取所有的key,->切片/数组存储
2.进行排序
3.遍历key,->map[key]
key := make([]int, 0, len(map4))
fmt.Println(key)
for k := range map4 {
key = append(key, k)
}
fmt.Println(key) //每次顺序都不一样
//排序
sort.Ints(key)
fmt.Println(key)
for _, keys := range key {
fmt.Println(keys, map4[keys])
}
如果你有很多个map,可以用数组/切片来把这些map串起来,比如1对应的就是k值和k值映射的值,2对应自己的k值......以此类推
//map和slice结合使用,创建map->存入信息到map里->把map存入到slice里->遍历输出
map5 := make(map[string]string)
map5["name"] = "艾美莉卡"
map5["age"] = "244"
map6 := make(map[string]string)
map6["name"] = "西安邮电大学"
map6["age"] = "74"
map7 := make(map[string]string)
map7["name"] = "荷叶饭"
map7["age"] = "18"
str := make([]map[string]string, 0, 3)
str = append(str, map5)
str = append(str, map6)
str = append(str, map7)
for i, v := range str {
fmt.Printf("第%d个人的姓名为%s\n", i+1, v["name"])
fmt.Printf("年龄为%s\n", v["age"])
fmt.Println()
}
silen里套map,map里也可以套map:
map8 := make(map[string]map[string]string) //k为string,v为map[string][string]
m1 := make(map[string]string)
m1["name"] = "Go"
m1["age"] = "15"
map8["message"] = m1
fmt.Println(map8)
string:
定义字符串:
s1 := "hello world"
s2 := "hello 狗屎"
字符串长度:
fmt.Println(len(s1), len(s2))
获取某个字符:
fmt.Println(s1[0])
fmt.Printf("%c\n", s1[0])
字符串遍历:
1.
for i := 0; i < len(s2); i++ {
fmt.Printf("%c\n", s2[i])
} //后面乱码是因为中文是三个字节
2.
for i, v := range s2 {
fmt.Printf("%d %c\n", i, v) //没有乱码但是下标顺序不连续
}
字符串其实就是一个byte的切片:
slice1 := []byte{65, 66, 67, 68, 69}
s3 := string(slice1) //根据一个字节切片,构建字符串
fmt.Println(s3)
反过来根据字符串构造byte数组:
s4:="abcde"
slice2:=[]byte(s4)
fmt.Println(slice2) //打印的是数值
字符串不能随意修改:
//s3[2]="ww",报错,字符串不可修改
字符串相关的函数:
string.Contains、string.ContainsAny、string.Count
str := "世界是一个大妓院aaa"
fmt.Println(strings.Contains(str, "妓院")) //如果含有则true
fmt.Println(strings.ContainsAny(str, "妓院abc")) //含有一部分则true
fmt.Println(strings.ContainsAny(str, "狗屎")) //false,包含其一即为true,都不包含则false
fmt.Println(strings.Count(str, "a")) //统计a在str出现的次数,3
string.HasPrefix(以某个字符开头)、string.HasSuffix(以某个字符结尾)
str1 := "你的肛门比较松弛"
if strings.HasPrefix(str1, "你") { //如果str1是以“你”开头
fmt.Println("没错,", str1)
} else {
fmt.Println("不,他的肛门比较松弛")
}
if strings.HasSuffix(str1, "松弛") { //若以“松弛”为结尾
fmt.Println(str1)
}
string.Index(第一次出现某个字符)、LastIndex(最后出现某个字符)、IndexAny(出现某个字符串中的一个字符):
没有返回-1
str := "世界是一个大妓院aaa"
fmt.Println(strings.Index(str, "a")) //第一次出现"a"的下标,24
fmt.Println(strings.LastIndex(str, "a")) //最后一次出现“a"的下标,26
fmt.Println(strings.Index(str, "b")) //没有的话返回-1,-1
fmt.Println(strings.IndexAny(str, "abc")) //出现”abc”任意一个排在前面的字符字符则返回下标,24
Join(将字符串数组以某个字符的形式连接起来):
str2 := []string{"abc", "def", "ghi", "jkl"}
fmt.Println(strings.Join(str2, "+")) //拼接一个字符串数组中的字符
Split(分散字符串)Repeat(复制某个字符串几次)Replace(把字符串中的某个字符替换为目标字符串,替换n次,如果要全部替换则n置为负数)
str3 := "原神,是一款由\"米哈游\"前面忘了,后面忘了"
fmt.Println(strings.Split(str3, ",")) //从逗号处断开
fmt.Println(strings.Repeat(str1, 5)) //自己拼接自己拼接了5次
fmt.Println(strings.Replace(str, "世界", "肛门", 1)) //把str中的“世界”替换为“肛门”,替换一次
//如果要全部替换则n取负数
fmt.Println(strings.Replace(str, "a", "去死", -1))
ToLwer(大写变小写)ToUpper(小写换大写)
str4 := "abcdefg"
str5 := "ABCDEFG"
fmt.Println(strings.ToLower(str5)) //大写字母全变成小写,ABCDEFG->abcdefg
fmt.Println(strings.ToUpper(str4)) //小写字母全变成大写,abcdefg->ABCDEFG
截取字符串:
其他语言:substring(start,end)截取子串
go没有substring,但是我们的字符串本质上是个byte切片,我们可以从切片截取(左闭右开):
str := "世界是一个大妓院aaa"
fmt.Println(str[0:6]) //截取str的前六个字节,“世界”
strconv包的使用:
按住ctrl可以查看我们函数的源代码:
strconv.ParseBool将字符串转换为布尔值(bool)
strconv.ParseBool支持以下几种字符串输入,并且不区分大小写:
"1"
,"t"
,"T"
,"true"
,"TRUE"
,"True"
:会被解析为true
"0"
,"f"
,"F"
,"false"
,"FALSE"
,"False"
:会被解析为false
sss1 := "true"
b1, err := strconv.ParseBool(sss1)
if err == nil {
fmt.Println(err)
}
fmt.Printf("%T,%t\n", b1, b1) //bool,true
strconv.ParseInt,将字符串转为int(不同进制)
sss2 := "100"
b2, err := strconv.ParseInt(sss2, 10, 64) //(字符串,进制,最大位数)
if err == nil {
fmt.Println(err)
}
fmt.Printf("%T,%d\n", b2, b2) //int64,100
itoa:字符串->int(10进制)
itoa:int->字符串
//itoa(),atoi()
i3, err := strconv.Atoi("-42") //字符串转为int
fmt.Printf("%T,%d\n", i3, i3) //int,-42
i4 := strconv.Itoa(42)
fmt.Printf("%T,%s\n", i4, i4) //string,42
函数和指针:
首先go语言支持多个返回值,一个函数可以返回多个值:
//函数
func rectang(len, wid float64) (float64, float64) {
perimeter := 2 * (len + wid)
area := len * wid
return perimeter, area
}
//主函数部分
res1, res2 := rectang(5, 3)
fmt.Println(res1, res2)
执行结果
如果你不像要其中一个返回值,也可以拿下划线代替
但是只要你定义了返回值,你就必须得返回
如果你的函数有返回值,并且包含了if分支,那么每个分支下都要有一个return语句来返回值(return来返回整个函数,break来中断这个分支)
defer(延迟)函数:使对应的语句暂停,放到最后执行
func fun1(a int) {
fmt.Println(a)
}
fun1(1111111)
defer fun1(2222222)
fun1(3333333)
defer fun1(4444444)
不加defer应为
1111111
2222222
3333333
4444444
加了之后为
1111111
3333333
4444444
2222222
因为defer置后了两个函数的调用,采用栈的结构打印
先被defer的语句后执行,后被defer的语句先执行(后进先出)
defer函数:在return之前,所有的defer函数会执行完毕
当执行外围函数中的return语句时,只有其中所有的延迟函数都执行完毕后
外围函数才会真正返回。
当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数;外围函数的恐慌不会影响内部的defer,得里面的defer执行完了,这个执行恐慌才会向调用处显现
函数的本质:
fmt.Printf("%T\n", fun1) //func(int)
fmt.Println(fun1) //0xe4b3c0,函数名对应的函数体地址
c := fun1 //c被赋值为赋值为fun1的地址
fmt.Println(c) //0x8cb400,所以“c"可以被当作函数调用
c(6)
fmt.Println(c) //6
匿名函数:
匿名函数:没有名字,直接在匿名函数后面加上“()"代表调用
定义一个匿名函数后匿名函数直接被调用,一般只能调用一次
也可以把这个匿名函数赋值给一个变量,这样就可以多次调用
go语言支持函数式编程:
1.将匿名函数作为另一个函数的参数,回调参数
2.将匿名函数作为另一个函数的返回值,可以形成闭包结构
匿名函数实例:
func() {
fmt.Println("我是一个匿名函数")
}() //直接在匿名函数后面加上“()"代表调用
即使写一个新的一模一样结构的函数,也不是同一个函数,会开辟新的一块内存
把匿名函数赋值给变量b
b := func() {
fmt.Println("我也是一个匿名函数")
}
b() //执行b
匿名函数也可以传递:
ans2 := func(a, b int) int {
return a + b
} //将匿名函数的地址给ans2
fmt.Println(ans2) //0x2cb7a0
高阶函数:
go语言函数为一种数据类型,支持高阶函数:函数把另一个函数作为参数
fun1(),fun2()
将fun1函数作为了fun2()这个函数的参数
fun2:高阶函数
fun1:回调函数
函数是一种数据类型,是什么类型主要取决于参数列表和返回值列表的类型:
// 函数的类型:func(参数列表的类型)(返回值列表的类型)
func add(a, b int) int {
return a + b
}
func oper(a, b int, fun func(int, int) int) int {//把一个fun类型的func (int,int)参数
fmt.Println(a, b, fun)
res := fun(a, b)
return res
}
//打印函数类型
fmt.Printf("%T\n", add) //func(int, int) int
fmt.Printf("%T\n", oper) //func(int, int, func(int, int) int) int
我们调用一下oper这个高阶函数:
ans11 := add(12, 4)
fmt.Println(ans11) //16
ans22 := oper(10, 9, add)
fmt.Println(ans22) //10 9 0x28b800,fun(10,9)==19
相当于在oper里调用执行了add
来聊聊我们刚才提到的闭包:
闭包(closure):
一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量),
并且该外层函数的返回值就是该内层函数,这个内层函数和外层函数的局部变量,统称为闭包结构
闭包导致的变量逃逸:
当外层函数的局部变量被内层函数使用时,外层函数的返回值为该内层函数,且该内层函数还会用到该局部变量
则外层函数的局部变量不被销毁
闭包的结构:
- 函数本身:闭包是一种函数,可以被调用和执行。
- 函数的自由变量:这些是在函数定义时,函数外部的变量,但在函数内部被引用。这些变量没有在函数内部定义,称为“自由变量”。
- 捕获的环境:闭包可以记住它定义时的上下文环境,也就是说即使函数的执行环境已经离开了当前作用域,闭包依然可以访问并使用这些自由变量。
eg:
func increment() func() int { //外层函数
i := 0 //1.定义了一个局部变量
//2.定义了一个匿名函数
fun1 := func() int { //内层函数
i++
return i
}
return fun1 //3.返回该匿名函数
}
来调用一下这个闭包:
ans111 := increment() //res111:=fun=内部的匿名函数
v1 := ans111()
fmt.Println(v1) //1
v2 := ans111()
fmt.Println(v2) //2,第二次调用i不会被销毁
按理来说你第一次调用这个ans111的时候,打印的v1=1很正常,因为你的i在闭包里设置的变量就是1
但是你第二次调用时,按理来说应该还是1啊?怎么就是2了?
局部变量的生命周期会发生变化,正常的局部变量会随着函数的调用创建,函数的结束销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用
也就是说别的函数之所以销毁是因为没人用里面的变量了,才销毁;而闭包结构因为里面的内层函数还用,所以就没有销毁。再次调用会有一个新的i,但是这个新的i继承上次的值,也就是1
(每新调用一次外层函数,就会有一个新的i与之对应,但旧的仍继承)
指针
指针储存的是另一个变量的地址:
a := 10
fmt.Println(a)
var p *int
p = &a //p等于a的地址,*p等于p的指的地址的值
fmt.Println(*p) //10
fmt.Println(p) //0xc00000a0c8
fmt.Printf("%T,%p,%p\n", p, p, &p) //*int,0xc00000a0c8,0xc000058030,p的类型,p指的地址,p自己的地址
这是指针的指针:
var p1 **int
p1 = &p //p1是p的指针,存的是
fmt.Println(*p1) //0xc00000a0c8
fmt.Println(p1) //0xc000058030
数组指针:指向一个数组的指针
指针数组:首先是一个数组,存储的类型是指针
数组指针:
//1.创建普通数组
arr1 := [4]int{1, 2, 3, 4}
fmt.Println(arr1)
//2.创建数组指针
var p2 *[4]int
p2 = &arr1
fmt.Println(p2) //&[1 2 3 4]
fmt.Printf("%T,%p,%p\n", p2, p2, &p2) //*[4]int,0xc000014200,0xc000058038,类型,数组arr1的地址,p2指针自己的地址
//3.根据数组指针,操作数组
(*p2)[1] = 100 //&[1 100 3 4]
p2[0] = 200 //简化写法
fmt.Println(p2) //&[200,2,3,4]
指针数组:
b := 1
c := 2
d := 3
e := 4
arr2 := [4]int{b, c, d, e}
arr3 := [4]*int{&b, &c, &d, &e}
fmt.Println(arr2) //[1 2 3 4]
fmt.Println(arr3) //[0xc00000a148 0xc00000a150 0xc00000a158 0xc00000a160]
*arr3[0] = 100
fmt.Println(b) //100
这么写肯定是错的:
fmt.Println(*(arr3))不能这么写,不能直接对指针数组解引用,应该对数组的每个元素进行解引用
只能遍历指针数组中的每个指针然后对指针解引用:
for i, v := range arr3 {
fmt.Printf("Element %d: %d\n", i, *v)
}
函数指针:
函数指针:指向函数地址的指针
go语言中函数为值类型,可以代替传统意义上的“函数指针”,因为函数变量已经可以直接表示指向函数的操作。(虽然go也可以用指针做中间人来使一个函数等于另一个函数但往往没必要)
func fun5() {
fmt.Println("fun1()")
}//函数部分
var f func()
f = fun5 //函数是一个指针,
f() //f()==fun1(),也可以写成*f(),但go中可以舍去*
func fun3() *[4]int {
arr := [4]int{5, 6, 7, 8}
return &arr
}
func fun2() [4]int {
arr := [4]int{1, 2, 3, 4}
return arr
}
arr4 := fun2()
fmt.Printf("arr4:类型%T,地址%p,数值%v\n", arr4, &arr4, arr4) //arr4:类型[4]int,地址0xc0000142a0,数值[1 2 3 4]
//函数内的arr被销毁
arr5 := fun3()
fmt.Printf("arr5:类型%T,地址%p,存的地址:%p,数值%v\n", arr5, &arr5, arr5, arr5) //arr5:类型*[4]int,地址0xc000058040,存的地址:0xc000014300,数值&[5 6 7 8]
//函数内的arr没有被销毁,因为arr5==fun3()返回值的地址
指针作为参数:
参数传递:值传递,引用传递
func fun6(num int) { //值传递
num = 100
fmt.Println("num在函数中被修改")
}
func fun7(p1 *int) { //引用传递
fmt.Println("fun7()函数中,p1:", *p1) //fun7()函数中,p1: 10
*p1 = 200 //p1在函数结束后会被销毁,但是因为p1指的地址为输入进来的实参的值,所以被传入的实参(g)的值被指针改变,销毁指针p1的内存地址,不销毁实参的地址
fmt.Println("fun7()函数中,p1:", *p1) //fun7()函数中,p1: 200
}
func funexe1(p1 *[4]int) {
fmt.Println("函数中的p1", p1) //&[1 2 3 4]
fmt.Println("函数中的p1", *p1) //[1 2 3 4]
p1[0] = 200
}
func funexe2(arr [4]int) {
fmt.Println("函数中的arr", &arr) //&[200 2 3 4]
fmt.Println("函数中的arr", arr) //[200 2 3 4]
arr[1] = 99
}
//上面是函数部分
g := 10
fmt.Println("fun6()函数调用前,g:", g) //fun6()函数调用前,g: 10
fun6(g)
fmt.Println("fun6()函数调用后g:", g) //fun6()函数调用后g: 10,为什么调用后还是10?因为他是值类型,在函数运行时变成100,函数结束后100就被销毁了
fun7(&g)
fmt.Println("fun7()函数调用后,g:", g) //fun7()函数调用后,g: 200
arrexe := [4]int{1, 2, 3, 4}
funexe1(&arrexe)//传的是地址
fmt.Println(arrexe)//200,2,3,4
funexe2(arr1)//函数内部为200,99,3,4
fmt.Println(arr1)//函数结束后销毁,还是200,3,4,5