目录
前言——碎碎念
环境安装
配置环境变量
变量的定义
数据类型
数字型
字符与字符串
数据类型转换
运算符
算术运算符
关系运算符
逻辑运算符
位运算符(二进制)
赋值运算符
其他运算符(指针)
键盘的输入与输出(交互)
流程控制
选择结构
循环结构
函数
函数的定义
函数的声明和调用
可变参数
参数传递
值传递
引用传递
递归函数
延迟函数
函数的类型
匿名函数
回调函数
闭包
教程
前言——碎碎念
大学时期的我一直纠结于编程语言的选择,直到我关注的一个博主 “英雄哪里出来” 他解答过这个问题。
原话我记不清了大概意思是不管哪种语言都会有 “很厉害或者很菜的人” ,最重要的是先学好一门语言(不管哪种),然后拥有能快速入门其他语言并应用起来的能力。
最近空闲时间我就看了Go的基础语法(先简单入了个门,Go也应用于游戏服务端,万一以后会用到呢)。
环境安装
地址:Go安装包下载
下载完是一个安装包直接安装,使用安装包安装在系统变量的环境变量Path中会自动把Go环境配置好
用户变量的Path可能会自动配置到C盘,可以自己改一下
安装完成后测试
配置环境变量
打开高级系统设置——》环境变量
在自己设置的GOPATH目录里新建三个文件夹
打开命令行窗口查看有没有配置好
开发工具可以下载GoLand或者VSCode(我用的VSCode)
【go】一、go语言vscode开发环境_vscode 运行go-CSDN博客
变量的定义
有些变量名重复我都注释掉了,可以打开注释自己练习一下
package mainimport "fmt"//全局变量
var name string = "李四"func main(){//fmt.Println("hello world!") //输出语句// var name string = "www" //定义变量 // var age int = 23// fmt.Println(name)// fmt.Println(age)// var (// name1 string// age1 int// addr string// )// fmt.Println(name1,age1,addr)// name1 = "111"// age1 = 24// addr = "222"// fmt.Println(name1,age1,addr)// // := 自动推导数据类型// name3 := "www"// age3 := 23// fmt.Println(name3,age3)// fmt.Printf("%T,%T",name3,age3) //打印什么类型// var num int// num=100// fmt.Printf("num:%d,内存地址:%p",num,&num) //取地址符号 & num:100,内存地址:0xc00000a0b8/*变量交换*/// var a int = 100// var b int = 200// b,a = a,b //a,b的值互换// fmt.Println(a,b) //200 100// a,b := test()// fmt.Println(a,b)// c,_ := test() // _(匿名变量) 当不用第二个返回值时用 _ 接收 // fmt.Println(c) /*局部变量*/// var name string = "李五"// fmt.Println(name) //优先打印局部变量/*常量*/// const URL string = "www.baidu.com" //显示定义(明确什么类型)// const URL2 = "www.baidu.com" //隐式定义(不明确类型)// const a,b,c = 3.14,"www",false/*iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota是go语言的常量计数器iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。iota 可以被用作枚举值*/// const(// d = iota// e = iota// f = iota// )// fmt.Println(URL,URL2)// fmt.Println(a,b,c,d,e,f) //3.14 www false 0 1 2// const(// a = iota //0// b //1// c //2// d = "hahaha" //hahaha iota 3// e //hahaha iota 4// f = 100 //100 iota 5// g //100 iota 6// h = iota //iota 7// i //iota 8// )// fmt.Println(a,b,c,d,e,f,g,h,i) //0 1 2 hahaha hahaha 100 100 7 8
}func test()(int,int){return 100,200
}
数据类型
数字型
package mainimport "fmt"func main(){//定义一个整型var age int = 18fmt.Printf("%T,%d\n",age,age)//定义一个浮点型//默认保留六位小数var money float64 = 3.14fmt.Printf("%T,%f\n",money,money)/*int,18float64,3.140000*/var num1 float32 = -123.0000901var num2 float64 = -123.0000901fmt.Println("num1 = ",num1,"num2 =",num2)//num1 = -123.00009 num2 = -123.0000901 float32内存空间不够精度丢失
}
字符与字符串
package mainimport "fmt"func main(){var str string str = "hello,www"fmt.Printf("%T,%s\n",str,str)//单引号v1 := 'A'v2 := "A"fmt.Printf("%T,%s\n",v1,v1)fmt.Printf("%T,%d\n",v1,v1)fmt.Printf("%T,%s\n",v2,v2)/*string,hello,wwwint32,%!s(int32=65)int32,65string,A*///所有的中国字的编码表: GBK//全世界的编码表: Unicode编码表//字符串拼接fmt.Println("hello" + ",www")//转义字符fmt.Println("hello\",www") //hello",www
}
package mainimport "fmt"func main(){str := "hello,www"fmt.Println(str)// 获取字符串的长度 lenfmt.Println("字符串的长度为:",len(str)) //字符串的长度为: 9// 获取指定的字节fmt.Println("字节打印:",str[0]) //输出结果为104对应'h'的ascii码fmt.Printf("%c",str[0]) //h// forfor i := 0; i < len(str); i++{fmt.Printf("%c",str[i]) }// for range 循环,遍历数组、切片...for i,v := range str{fmt.Print(i) //i表示下标fmt.Printf("%c",v) //v对应的字符}//0h1e2l3l4o5,6w7w8w
}
数据类型转换
在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显示的声明。
package mainimport "fmt"func main(){a := 3 //intb := 5.0 //float64fmt.Printf("%T\n",a)fmt.Printf("%T\n",b)//将int类型的a转换为 float64 类型c := float64(a)d := int(b)fmt.Printf("%T\n",c)
}
运算符
算术运算符
package mainimport "fmt"func main(){var a int = 10var b int = 3//var c int//+ - * / % ++ --fmt.Println(a+b)fmt.Println(a-b)fmt.Println(a*b)fmt.Println(a%b)a++ // a = a+1fmt.Println(a)a-- // a = a-1fmt.Println(a)
}13
7
30
1
11
10
关系运算符
package mainimport "fmt"func main(){var a int = 11var b int = 10//关系运算符fmt.Println(a==b)fmt.Println(a!=b)fmt.Println(a>b)fmt.Println(a<b)fmt.Println(a>=b)fmt.Println(a<=b)//判断ifif a>b{}
}
false
true
true
false
true
false
逻辑运算符
package mainimport "fmt"func main(){var a bool = truevar b bool = false// 逻辑 && 与if a==true && b==true{fmt.Println(a && b)}fmt.Println(a && b)// 逻辑 || 或 都为假才会返回假fmt.Println(a || b)//逻辑 ! 非fmt.Println(!a)
}
位运算符(二进制)
package mainimport "fmt"func main(){// 二进制 0 1 逢二进一// 位运算: 二进制上的 0 false 1 true// & 我和你 都为1 结果才为1// | 我或者你 有一个为1 结果才为1// ^ 不同为 1,相同为0// 60 二进制: 0011 1100// 13 二进制: 0000 1101//------------------------------// & : 0000 1100 // | : 0011 1101// ^ : 0011 0001// >>: 1111 0000var a uint = 60var b uint = 13// 位运算var c uint = 0c = a&b // 位运算符&fmt.Println("%d,二进制%b",c,c) //12,二进制 1100c = a|b // 位运算符|fmt.Println("%d,二进制%b",c,c) //61,二进制 111101c = a^bfmt.Println("%d,二进制%b",c,c) //49,二进制 110001c = a << 2fmt.Println("%d,二进制%b",c,c) //240 二进制 1111 0000c = a >> 2fmt.Println("%d,二进制%b",c,c) //15 二进制 0000 1111
}
赋值运算符
package mainimport "fmt"func main(){var a int = 21var b int// =赋值b = a // b = 21fmt.Println(b)// += b = a+b = 42b += afmt.Println(b)
}
其他运算符(指针)
键盘的输入与输出(交互)
package mainimport "fmt"func main(){var x intvar y float64// 定义了两个变量,想用键盘来录入这两个变量// fmt.Println() 打印并换行// fmt.Printf() 格式化输出// fmt.Print() 打印输出// fmt.Scanln() 换行接收输入// fmt.Scanf() 格式化输入// fmt.Scan() 接收输入fmt.Println("请输入两个数:1、整数,2、浮点数:")// 变量取地址 &变量 通过指针地址来操作变量fmt.Scanln(&x,&y) // 接收输入 Scan fmt.Scanln(&x,&y)fmt.Println("x:",x)fmt.Println("y:",y)
}
流程控制
程序的流程控制结构一共有三种:顺序结构,选择结构,循环结构
顺序结构:从上到下,逐行执行。默认的逻辑
选择结构
:条件满足某些代码才会执行
- if
var a int = 15
if a>20 {fmt.Println("a>20")
}
else if a>10{fmt.Println("a>10")
}
else{fmt.Println("a")
}
- switch
package mainimport "fmt"func main(){var score int = 60switch score {case 90:fmt.Println("A")case 80:fmt.Println("B")case 50,60,70:fmt.Println("C")default:fmt.Println("D")}switch {case false:fmt.Println("false")case true:fmt.Println("true")default:fmt.Println("其他")}
}
fallthrough 贯穿;直通
switch 默认情况下匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough穿透case,使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
package mainimport "fmt"func main(){a := falseswitch a {case false:fmt.Println("false")fallthrough //case穿透,不管下个条件满不满足都会执行(只判断fallthrough的下一个条件)case true:fmt.Println("true")default:fmt.Println("其他")}//输出结果//false//true
}
- select
循环结构
:条件满足某些代码会被反复执行0-N次
- for
count := 5
// for 条件的起始值;循环条件;控制变量自增或自减
for i := 0; i < count; i++ {fmt.Println("----",i);
}//break 跳出整个循环
//continue 跳过本次循环
打印方阵练习:
count := 5
for i := 0; i < count; i++ {for j := 0;j < count;j++{fmt.Print("* ")}fmt.Println()
}
打印九九乘法表:
package mainimport "fmt"func main(){count := 9for i := 1; i <= count; i++ {for j := 1;j <= i;j++{fmt.Print(j,"*",i,"=",j*i," ")}fmt.Println()}
}
函数
函数的定义
- 函数是基本的代码块,用于执行一个任务
- Go语言最少有个main()函数
- 可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务
- 函数声明告诉了编译器函数的名称,返回类型,和参数
函数的声明和调用
package mainimport "fmt"//func 函数名 (参数,参数...) (返回值类型){ 函数体 return 返回结果}func main(){printinfo()c := add(1,2)myprint(c)x,y := swap("aaa","bbb") //不想用y可以用匿名变量只接收一个返回值 x,_ := swap("aaa","bbb")fmt.Println(x,y)
}//无参无返回值函数
func printinfo(){fmt.Println("printinfo")
}//有一个参数的函数
func myprint(num int){fmt.Println(num)
}//有两个参数一个返回值的函数
func add(a,b int)(int){c := a+breturn c
}//多个返回值的函数
func swap(x,y string)(string,string){return y,x
}// max 两个数字比大小
// 形式参数:定义函数时,用来接收外部传入数据的参数,就是形式参数
// 实际参数:调用函数时,传给形参的实际数据叫做实际参数
func max(num1,num2 int) int{var result int if num1 > num2 {result = num1} else {result = num2}//函数定义上有返回值,函数中必须有returnreturn result
}
可变参数
注意事项:
- 如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在列表的最后
- 一个函数的参数列表中最多只能有一个可变参数
package mainimport "fmt"func main(){getSum(1,2,3,4,5,6,7)
}// ...可变参数
func getSum(nums ...int){sum:=0for i := 0; i < len(nums); i++ {fmt.Println(nums[i])sum += nums[i]}fmt.Println("sum:",sum)
}// 1
// 2
// 3
// 4
// 5
// 6
// 7
// sum: 28
参数传递
按照数据的存储特点分:
- 值类型的数据:操作的是数据本身、int、string、bool、float64、array...
- 引用类型的数据:操作的是数据的地址slice、map、chan...
值传递
package mainimport "fmt"func main(){//值传递:传递的是数据的副本,修改数据对原始数据无影响//值传递类型:基础类型、array、struct(结构体)//定义一个数组 [个数]类型arr := [4]int{1,2,3,4}fmt.Println(arr)//传递:arr2的数据是复制arr来的的,arr2并不影响arrupdate(arr)fmt.Println("调用后的数据",arr)
}func update(arr2 [4]int){fmt.Println("arr2接收的数据:",arr2)arr2[0] = 100fmt.Println("arr2修改后的数据:",arr2)
}// [1 2 3 4]
// arr2接收的数据: [1 2 3 4]
// arr2修改后的数据: [100 2 3 4]
// 调用后的数据 [1 2 3 4]
引用传递
变量在内存中是存放在一定的地址上的,修改变量实际是修改变量地址的内存
package mainimport "fmt"func main(){//引用传递:传递的是数据的地址,导致多个变量指向同一块内存//切片,可以扩容的数组s1 := []int{1,2,3,4}fmt.Println("默认的数据",s1)update2(s1)fmt.Println("调用后的数据",s1)
}func update2(s2 []int){fmt.Println("传递的数据",s2)s2[0] = 100fmt.Println("修改后的数据",s2)
}// 默认的数据 [1 2 3 4]
// 传递的数据 [1 2 3 4]
// 修改后的数据 [100 2 3 4]
// 调用后的数据 [100 2 3 4]
递归函数
定义:一个函数自己调用自己
注意:递归函数要有一个出口,逐渐向出口靠近,没有出口就会形成死循环
package mainimport "fmt"
//求和:1,2,3,4,5
func main(){sum := getSum(5)fmt.Println(sum)
}func getSum(n int) int{if n == 1 {return 1}return getSum(n - 1) + n
}//15
延迟函数
defer:推迟、延迟
在go语言中,使用defer关键字来延迟一个函数或者方法的执行
package main
import "fmt"
func main(){f("1")fmt.Println("2")defer f("3") //会被延迟到最后执行fmt.Println("4")
}func f(s string){fmt.Println(s)
}// 1
// 2
// 4
// 3
defer函数或者方法:一个函数或方法的执行被延迟了
- 你可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回,特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题
- 如果有很多调用 defer,那么 defer 是采用 后进先出(栈) 模式。
package main
import "fmt"
func main(){f("1")fmt.Println("2")defer f("3") //会被延迟到最后执行fmt.Println("4")defer f("5") //会被延迟到最后执行fmt.Println("6")defer f("7") //会被延迟到最后执行fmt.Println("8")
}func f(s string){fmt.Println(s)
}// 1
// 2
// 4
// 6
// 8
// 7
// 5
// 3
package main
import "fmt"
func main(){a := 10fmt.Println("a=",a)defer f(a)a++fmt.Println("end a=",a)
}func f(s int){fmt.Println("函数里面的a:",s) //a在执行defer的时候就已经传递进来了
}// a= 10
// end a= 11
// 函数里面的a: 10
defer的用法:
- 对象.close()临时文件的删除
文件.open()
defer.close()
读或写
- go语言中关于异常的处理,使用panic()和recover()
panic函数用于引发恐慌,导致程序中断执行
recover函数用于恢复程序的执行,recover()语法上要求必须在defer中执行
函数的类型
package main
import "fmt"
func main(){//f() 如果加了括号就成了函数的调用//f 如果不加括号,函数就是一个变量fmt.Printf("%T",f) //func() 本身就是一个数据类型fmt.Printf("%T",10)fmt.Printf("%T","hello")fmt.Printf("%T",f1) // func(int, int)fmt.Printf("%T",f2) // func(int, int) int//定义函数类型的变量var f5 func(int, int)f5 = f1//给变量赋值然后尝试调用它f5(1,2) // 1 2//打印 f1 和 f5 的地址发现是一样的,f1 和 f5 指向同一块地址,是引用类型fmt.Println(f5) // 0x71b1a0 fmt.Println(f1) // 0x71b1a0
}
func f(){}func f1(a,b int){fmt.Println(a,b)
}func f2(a,b int) int {return 0
}// func()
// int
// string
// func(int, int)
//func(int, int) int
//1 2
// 0x71b1a0
// 0x71b1a0
函数在Go语言中是复合类型,可以看做是一种特殊的变量
函数名(): 调用返回结果
函数名(): 指向函数体的内存地址,一种特殊类型的指针变量
匿名函数
package main
import "fmt"//匿名函数
func main(){f() f1 := f // 函数本身也是一个变量f1()//匿名函数f2 := func(){fmt.Println("我是f2函数")}f2()//匿名函数自己调用自己func(){fmt.Println("我是f3函数")}() //加()相当于直接调用函数//匿名函数可以传递参数func(a,b int){fmt.Println(a,b)fmt.Println("我是f4函数")}(1,2) //加()相当于直接调用函数//匿名函数可以有返回值r1 := func(a,b int) int {fmt.Println("我是f5函数")return a+b}(1,2) //加()相当于直接调用函数fmt.Println(r1)
}func f(){fmt.Println("我是f函数")
}// 我是f函数
// 我是f函数
// 我是f2函数
// 我是f3函数
// 1 2
// 我是f4函数
// 我是f5函数
// 3
回调函数
高阶函数: 根据go语言的数据类型的特点,可以将一个函数作为另外一个函数的参数。
fun1(),fun2()
将 fun1 函数作为 fun2 这个函数的参数
fun2函数: 就叫做高阶函数,接收了一个函数作为参数的函数
fun1函数: 就叫做回调函数,作为另外一个函数的参数
package main
import "fmt"func main(){r1 := add(1,2)fmt.Println(r1)r2 := oper(3,4,add)fmt.Println(r2)r3 := oper(8,4,sub)fmt.Println(r3)r4 := oper(8,4,func(a int,b int) int{if b == 0{fmt.Println("除数不能为0")return 0}return a/b})fmt.Println(r4)
}//高阶函数: 可以接收一个函数作为参数
func oper(a,b int,fun func(int,int) int) int{r := fun(a,b)return r
}func add(a,b int) int{return a+b
}func sub(a,b int) int{return a-b
}// 3
// 7
// 4
// 2
闭包
package main
import "fmt"/*
一个外层函数中,有内层函效,该内层函数中,会操作外层函数的局部变量。
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。
*//*
局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的
结束而销毁,但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用。
*/
func main(){r1 := increment()fmt.Println(r1)v1 := r1()fmt.Println(v1)v2 := r1()fmt.Println(v2)fmt.Println(r1())fmt.Println(r1())fmt.Println(r1())//定义r2r2 := increment()v3 := r2()fmt.Println(v3)fmt.Println(r1())fmt.Println(r2())
}// 自增
func increment() func() int {// 局部变量ii := 0// 定义一个匿名函数,给变量自增并返回fun := func() int { // 内层函数,没有执行的i++return i}return fun
}// 0xa398a0
// 1
// 2
// 3
// 4
// 5
// 1
// 6
// 2
教程
遇见狂神说:狂神聊Go001:Go语言的发展史_哔哩哔哩_bilibili