Go学习笔记
1、什么是Go语言?
Go语言全称Golang,是Google于2009年推出的一种高级编程语言,是一种静态语言:
静态语言
- 通过编译器(Compiler)将源代码翻译成机器码,之后才执行。程序被编译后无论是程序中的数据类型还是程序结构都不可以被改变;
- 静态语言的安全性和性能很好,例如C、C++、Go,但是C和C++的开发速度慢,维护成本高。
动态语言
- 一般不需要用编译器将源代码翻译为机器码,在运行时逐行翻译。程序运行过程中可以动态修改程序中的数据类型和程序结构。
- 开发速度快,维护成本低,但性能、安全性略低。
2、与C的对比
1)源文件
C:
- .h:头文件,存放代码声明
- .c:源文件,存放代码实现
Go:
- .go:源文件,存放代码实现
2)代码管理
C:
- 使用某一函数时,通过include导入对应的.h文件;
- 通过extern和static实现函数与变量是否公开;
Go:
- 使用某个函数,通过import导入对应的包;
- 通过变量与函数名称大小写实现是否公开;
3)关键字(下表加粗为二者共有)
C:
- 32个关键字
Go:
- 25个关键字
4)数据类型
C:
- 基本类型:shot、int、long、float、double、char
- 构造类型:struct、union、enum、数组
- 指针:*
- 空:void
Go:
占用内存空间
C:
Go:
Go通过Sizeof计算变量占用的内存空间:
1、import "unsafe" 2、unsafe.Sizeof()
5)常量变量
C定义常量变量:
类型 变量名 = 值;
const 类型 变量名 = 值;
Go定义常量变量:
var 变量名 类型 = 值;
const 变量名 类型 = 值;
6)注释
C和Go在注释方面写法相同:
单行 // 多行 /* ... */
7)运算符
Go没有前置运算符(++、--只能写成a++、a--的形式),++和--被视为语句要单独成行。
算术运算符
关系运算符
逻辑运算符
位运算
赋值运算符
8)流程控制
Go没有while和do while,其他(if、swithch、for……)和C相同
所有左大括号都要和关键字在同一行
①if
if 条件表达式{ 语句块 } if 条件表达式{ 语句块 }else{ 语句块 } if 条件表达式{ 语句块 }else if 条件表达式{ 语句块 } ... else{ 语句块 }
②switch
switch 变量{ case 值1: 语句块 case 值2: 语句块 ... default: 语句块 } switch 赋值;表达式{ case 表达式1,表达式2: 语句块 case 表达式3,表达式4: 语句块 ... default: 语句块 }
例子
//常规用法 switch num { case 1: fmt.Println("星期一") ... case 7: fmt.Println("星期日”) default: fmt.Println("other...") } //一次匹配多值 switch num{ case 1, 2 , 3 , 4, 5: fmt.Println("工作日") case 6,7: fmt.Println("非工作日”) default: fmt.Println("other...") } //case穿透 switch num{ case 1: fallthrough case 2: fallthrough case 3: fallthrough case 4: fallthrough case 5: fmt.PrintIn("工作日")//case 1 2 3 4一直穿透到没有falltrough处 case 6: fallthrough case 7: fmt.Println("非工作日”) default: fmt.Println("other...") }
说明
- case后不仅可以放常量1,2,3,4,也可以放变量与表达式
- case后的值可以不加,也可以有多个,此时只要一个命中就执行
- case后没有break
- case语句块最后加fallthrough,可以实现case穿透(一次命中,同时执行之后的所有case)
③for
for 初始化表达式;循环条件;循环后变化 { 循环体 }
for 索引,值 := range 可遍历类型{
对索引、值的相关操作
}
for i:=;i<10;i++{ fmt.Println(i) i++ } arr := [3]int{1,3,5} for i,v := range arr{ fmt.Println(i,v) }
跳转
return、break、continue、goto
9)函数
10)延迟调用defer
常用于释放资源、解除锁定、错误处理
作用方式:无论在何处注册defer,它都会在所属函数执行完毕之后才会执行,并且如果注册了多个defer,会按照后进先出的原则执行。
基于这种特性,可以在资源打开的同时,就用defer来声明它的关闭,因为无论如何defer都会在所属函数执行完毕后才会执行
11)init函数
golang中存在两个保留的函数:
- init函数:应用于所有的package
- main函数:只能应用于main包
这两个函数在定义时不能有参数、返回值。
init函数的作用:处理当前文件的初始化操作,存放一些准备工作。
说明
- go会自动调用main、init,因此我们不能手动调用它们。
- main包必须包含一个main(),但每个包中的init()都是可选的
- 一个包中可以写任意多个init(),但是最好还是只写一个init
- 执行顺序:main包→常量→全局变量→init()→main()→Exit
执行顺序:const→var global...→init()→main()→exit
- 多个包代码的执行顺序
12)数组
13)map
14)struct
Go教程105页
方法
func (接收者 接收者类型)函数名(参数列表)(返回值列表){
函数体;
return 返回值;
}
15)接口
GoLang:接口16)面向对象:类-对象
17)异常
18)字符串
GoLang:字符串3、Go语言SDK安装
1)何为SDK
软件开发包(Software Development Kit,SDK)是一些软件工程师为特定软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合。
如果没有SDK,虽然可以写Go代码,但是无法编译执行。
2)如何安装?
最新版本一般不太稳定,这里选择上一版本(X86是32位OS,X86_64是64位OS)
②双击安装
③修改环境变量
新建两个环境变量GOROOT(Go的安装目录)和GOPATH(将来开发的Go项目的文件夹),路径不能有中文
再看看GoBin是否加入到了Path目录下
这样Go的环境变量配置完毕。
④检查是否安装成功
go env
4、GoLang开发工具
常用的有:vim、VSCode、GoLand、各种在线编辑器
GoLand安装(待完成)
5、Go程序
1)一个Go程序的运行过程
①编写
package main import "fmt" func main() { fmt.Println("Hello,world"); }
②命名为hello.go。
③进入该文件所在目录下,输入指令go run hello.go即可运行。
2)Go语言程序组成
- Go由众多函数组成;
- 程序运行时自动寻找并调用main函数;
- 如果一个程序没有main函数,那它不具备运行能力,大概率作为功能插件引入别的程序中;
- 一个Go程序有且仅有一个main函数
以上规则和C相同
标准Go项目目录格式
目录结构
|…项目目录
……|……src目录
…………|…………main目录
…………|…………其他目录
……|……bin目录
……|……pkg目录
目录 |
说明 |
项目目录 | 项目所在地,GOPATH指向的目录 |
|
专门用于存放源码文件,下有main目录和其他目录 |
|
存放package main包相关源码 |
|
存放除main包外的源码 |
|
存放编译后可执行程序 |
|
存放编译后的.a文件 |
go命令行(cmd中输入指令)
指令用法:go 指令 |
说明 |
version | 当前go版本 |
env | 当前go环境变量 |
fmt |
格式化代码 将指定文件中的凌乱代码按照go语言规范格式化 |
run 命令文件 |
编译并运行 存放于main包中的main函数文件,称为命令文件 其他包中的文件,称之为源码文件 |
build |
编译检查 对于非命令文件,只会编译检查,不会生成任何文件 对于目录文件,除了编译检查,还会在当前目录生成可执行文件 如果只想编译某个文件,可以在命令后指定文件名称 go build 文件名 |
install |
安装程序 对于非命令文件,执行编译检查,生成.a包,存入$GOPATH/pkg目录 对于命令文件,执行编译检查,生成可执行文件,存入$GOPATH/bin目录 |
3)main函数
C:
int main(int argc , const char * argv[]){ return 0; }
Go:
package main func main(){ }
首行package会告诉系统该程序所属哪个包
这里的包的概念和Java中的包概念类似,是一批文件的共同存放处,因此包是实际存在的目录(main包除外,main函数必须存放在main包中,但main包可以不是一个实际存在的目录)。
例如有两个自写的与计算相关的函数存于包calculate中,那calculate就一定是这两个函数的所在目录,在另一个函数中要调用这两个函数,就要用import导入包calculate:
如果一次性导入多个包,还要把不同的包写入同一个括号()中,并加双引号分行排布
import ( "fmt" "xxx/calculate" )
传入并获取命令行参数
①os.Args
Go中main函数没有形参,因此不能通过main函数获取命令行参数,因此要想获取命令行参数必须导入os包,通过os包的Args获取。最后,无论传入什么类型,最后得到的都是字符串。
用法:
import ( "fmt" "os" ) func main(){ num:=len(os.Args) for i:=0;i<num;i++ { fmt.Println(os.Args[i]) } }
传入
main.exe 参数1 参数2
②flag包
③对比
os:
- 如果用户没传递参数,会报错
- 需要严格按照顺序传递,否则会造成数据混乱
- 不能指定参数名称
- 获取到的都是字符串
flag:
- 用户可以不传递参数
- 不用严格按照代码中的顺序传递
- 可以指定参数名‘可以获取到指定类型’
4)普通函数
C:
返回值类型 函数名(参数列表){ 语句; return 返回值; }
Go:
func 函数名(参数列表)(返回值列表) { 语句; return 返回值; }
除了语法上的不同,还要注意Go中首行左大括号{必须放在首行。
调用
调用前:
- C:用#include导入.h文件
- Go:用import导入函数所在的包
调用:
- C:直接用函数(参数)的形式
- Go:还要写上包名,包名.函数(参数)的形式
package main import ( "fmt" "lesson_1/calculate ) func main() { res := calculate.Sum(2,3) // 使用包名.函数名称调用函数 fmt.Println("res1 = ", res) }
此外,语句末尾不用写分号(编译器会自己加,写上也不会错)
5)Go编码风格
- .go文件后缀
- 包名一般为文件所在目录
- 包名小写
- 语句末尾不加分号
- 一行写一条语句
- 函数左大括号{与函数名在同一行
- 尽量用单行注释
- 变量用驼峰命名法:sayHello而非say_hello
- 引入的变量、包,下文一定要有用到,不然会报错
6)关键字
C中32个关键字
Go中25个关键字
30个预定义标识符
7)类型
说明 |
类型 |
占用内存 |
本质如何用C实现,对应C中哪种类型 |
||
32位 |
64位 |
||||
基本类型 | 有符号整型 | int(默认整) | 4 | 8 |
根据机器位数自动调整 32位就是int32,64位就是int64 |
int8 | 1 | 1 | char | ||
int16 | 2 | 2 | short | ||
int32 | 4 | 4 | int | ||
int64 | 8 | 8 | long long int | ||
无符号整型 | uint | 同intx | |||
uint8 | |||||
uint16 | |||||
uint32 | |||||
uint64 | |||||
uintptr | 4 | 8 | 同int | ||
浮点 | float32 | 4 | 4 | float | |
float64 | 8 | 8 | double | ||
字符 | byte | 1 | 1 | char | |
rune | 4 | 4 | int | ||
布尔 | true | 1 | 1 | char | |
false | 1 | 1 | char | ||
字符串string | //// | ||||
非基本类型/派生类型 | 指针Pointer | ||||
数组 | |||||
切片slice | |||||
Map | |||||
结构体struct | |||||
管道channel | |||||
函数 | |||||
接口interface |
Go是用C编写的,上文的本质就是在C的实现代码中这些类型是如何定义的(go\src\runtime\runtime.h中)
计算变量的内存空间:
import ( ... "unsafe" ) func main(){ ... fmt.Println("Size of int is "+unsafe.Sizeof(int(0)))
}
8)变量
定义
C:
类型 变量;
类型 变量1,变量2;
int num1; num1=10; int num2=10; int num3,num4; num3=10; num4=10;
Go:
var 变量 类型 = 值 //标准格式 var 变量 = 值 //自动推测类型 变量 := 值 //简短模式
//一次多个
//连续型
var 变量1,变量2 类型 = 值1,值2
var 变量1,变量2 = 值1,值2
变量1,变量2 := 值1,值2
//变量组
var(
变量1 类型
变量2 类型
)
var(
变量1 类型=值1
变量2 类型=值2
)
var(
变量1=值1
变量2=值2
)
var(
变量1,变量2=值1,值2
变量3,变量4=值3,值4
)
var num1 int num1 = 10 var num2 int = 20 var num3 = 30 num4 := 40
一次多个
var num1,num2 int num1=10 num2=10 var num3,num4 int=30,40 var num5,num6=50,60 num7,num8:=70,80
关于简短模式:=
-
变量 := 值等价于
var 变量 类型 变量 = 值
- :=不是赋值号,是定义(参考上一条),不能连续对同一个变量使用!
num:=10 num:=20//报错
- 只能用于定义局部变量,不能用于全局变量(不在任何函数中定义的变量)
- 不能用于变量组
- 接第2条,当多个变量中存在未定义变量(至少存在一个未定义变量),此时不论其他变量有无定义,均可顺利进行,对于已定义变量,:=相当于赋值操作(仅适用于多变量简短定义时):
num1:=10 num1,num2:=20,30//此时不会报错,因为num2未定义,对于num1来说此时:=相当于赋值号
num3,num4:=30,40
num3,num4:=50,60//出错,因为num3、num4均定义过
全局变量
定义:函数之外
作用域:从定义开始到其所在代码块结束
生命周期:程序一启动就会为其分配空间,直到程序结束
变量默认值
所有整型:0
浮点:0.0
bool:false
string:""
派生类型:nil
array与struct:内部数据的默认值
var intV int var floatV float var boolV bool var stringV string var pointerV *int var funcV func(int,int) int var interfaceV interface{} var sliceV []int var channelV chan int var mapV map[String]String var errorV error var arrayV [3]int type Person struct{ name string age int }
fmt.Println("array=",arrayV) //[0,0,0]
fmt.Println("struct=",structV)//{"" 0}
强制类型转换
C中:
int a = (int)10.5
Go中:
num0:=10.5 num1:=int(num0) //10
数值类型与String之间互相转换
必须通过相关函数转化,不能用类型(数值)的形式强制转换,下边这种写法就是错的:
var str1,str2,str3 string; num1:=65 num2:=3.14 str3="97" //错误写法 str1=string(num1)//会被转换为ASCII str2=string(num2) num3:=int(str3)//会报错cannot conver str to int
正确转化
①数值→string:strconv.FormatXxx()
用法:
整型:strconv.FormatInt(数值,进制)——这里的数值必须是int64,如果不是还需要强制类型转化
浮点:strconv.FormatFloat(数值,格式,小数位,float类型):
- 数值:必须是float64类型;
- 格式:f-小数,e-指数
- 小数位:转换后保留多少位小数,传入-1则按照指定类型的默认有效位转换
- float类型:32或64,代表float32与float64
num1:=10 str1:=strconv.FormatInt(int64(num1),10)//十进制 str2:=strconv.FormatInt(int64(num2),2)//二进制 fmt.Println(str1) //10 fmt.Println(str2) //1010
num2:=3.123456789123456789 str2:=strconv.FormatFloat(num2,'f',-1,64)//3.1234567 str3:=strconv.FormatFloat(num2,'f',2,64)//3.12 num3:=1012.0 str4:=strconv.FormatFloat(num3,'e',3,64)//1.012e+03 num4:=true str5:=strconv.FormatBool(num4)//"true"
注意:使用前要先把strconv库给import
②string→数值:strconv.ParseXxx()
该函数会返回两个值:①转换后的结果;②错误。成功转换后(没有超范围,转换合法)err为nil,转换失败err不为nul
用法:
整型:strconv.ParseInt(数据,进制,位数)
- 数值:需要转化为整型的string
- 进制:多少进制
- 位数:转化成int8、int16……
//成功转换的案例 str1:="123" num1,err1:=strconv.ParseInt(str1,10,8)//将123转化为10进制int8 if err != nil{ fmt.Println(err1) } fmt.Println(num1) //失败的案例 str2:="150" num2,err2=strconv.ParseInt(str2,10,8)//由于int8的范围为-128~127,所以这里超出了范围,因此err不为nil
浮点:strconv.ParseFloat(数据,位数):
- 数据:以字符形式写出来的浮点小数
- 位数:该项只能写32和64,代表转化成float32还是float64
str3:="3.12345678901234567890" num3,err3:=strconv.ParseFloat(str3) if err!=nil{ fmt.Println(err) } fmt.Println(num3)
数值↔字符串(快速转化):Itoa与Atoi
num1:=32 str1:=strconv.Itoa(int(num1))//只接受int类型 str2:="666" num2,err:=strconv.Atoi(str2) if err!=nil{ fmt.Println(err) } fmt.Println(num2)
数值→字符串:格式化fmt.Sprintf("占位符",数值变量)
num1:=110 num2:=3.14 num3:=true str1:=fmt.Sprintf("%d",num1) str2:=fmt.Sprintf("%f",num2) str3:=fmt.Sprintf("%t",num3)
注意
- String→数值:strconv.ParseXxx。会返回两个值:转化后的值、err。其中err会在转化成功时返回nil,其他时候返回非nil。
- 如果ParseXxx遇上完全不能转化的,如将"abc"转为int,那么除了err不为nil外,返回的数值也将为该类型的默认值。
9)常量(成功赋值后不能改变)
①自定义常量:
单个
const 常量 类型 = 值 const 常量 = 值
多个
const 常量1,常量2 类型 = 值1,值2
//常量组 const( 常量1 = 值1 常量2 = 值2 ) const( 常量1,常量2 = 值1,值2 常量3 = 值3 )
说明:
- 局部变量、包在定义/导入后没有使用,那么编译器会报错,但是常量不会
- 常量组中,如果上一行有值,下一行没有值,那么下一行的值就会用上一行的值:
const( num1=666 num2 //用上一行的值,即666 )
const(
num1,num2=100,200
num3,num4//和上一行一样,变量个数也必须一样
)
②枚举常量
例子:
const( male=iota female=iota yao=iota ) //相同效果 const( male=iota female yao ) fmt.Println(male)//0 fmt.Println(female)//1 fmt.Println(yao)//2
说明:
- 在同一个常量组中,iota从0开始递增,每行递增1
- 只要上一行出现了iota,那么后续行会自动递增;
- 如果中间某行出现了具体数值,截断了上一行的iota,那么后续数值不会自动递增
const ( male=iota //iota从0开始 female=666//此处中断,如果后续没有iota,那么所有未赋值常量都等于female yao //同female666 ) / const ( male=iota //iota从0开始 female=666//此处中断,但后续有iota,续上iota的地方,相当于前边都有iota的值,并不会从赋值处递增 yao=iota //2 )
10)输出:fmt.PrintXxx
Printf:格式化输出,可以用占位符;
Println:标准输出,用于实现字符串拼接式输出
Print:其它效果同Println
指定输出到某处:Fprintxxx(效果同上,只是多了能指定输出的位置)
Fprintf
Fprintln
Fprint
将输出结果返回为字符串,不显式输出:str:=Sprintxxx
Sprintf
Sprintln
Sprint
11)输入:fmt.Scanfxxx
从标准输入(键盘)读取
Scanf:格式化输入,一次一行
Scan:一次一行,但是不能指定格式化字符串
Scanln:只能输入一行
指定输入位置
Fscanf
Fscanln
Fscan
从字符串中读入
Sscanf
Sscanln
Sscan
12)运算符
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性