Golang学习编
1.环境安装配置
Visual Studio Code下载地址:https://code.visualstudio.com/download
安装后切换中文:
1.ctrl+shift+p
2.再搜索Configure Display Language
3.显示中文插件点击Install
搭建Go开发环境,安装和配置SDK
SDK下载地址:https://golang.google.cn/dl
按系统选择
下载解压后看到的Go目录就是SDK
校验GO的SDK是否安装成功
配置Golang的环境变量:
环境变量 | 值 |
---|---|
GOROOT | D:\GO\go |
GOPATH | D:\GO\go\bin |
上面是新建环境变量,下面是编辑环境变量 | 空 |
Path | %GOROOT%\bin |
GOPATH为项目存放的路径,即工作目录
校验环境变量是否配置好
重现打开cmd,不在D:\GO\go\bin路径下面输入go version。显示版本号即配置成功
Windows开发步骤:
1)安装windows版本VScode.
2)将Go代码编写到扩展名为hello.go的文件中
[说明:源码在工作目录src/go_code下,如:goproject/src/go_code]
3)通过go build命令对该go文件进行编译,生成.exe文件
4)在dos命令下执行.exe文件就可以看到运行效果
5)注意:go run命令能直接运行hello.go程序
代码:
对上图的说明:
1.go文件的后缀为.go
2.package main 表示该hello.go文件所在的包是main,在go中,每个文件必须归属一个包
3.import “fmt” 表示引入一个包,包名fmt,引入该包后才能使用fmt包的函数,比如fmt.Println
4.func main func表示一个关键字,表示一个函数 main为函数名,是个主函数,为程序的入口
5.fmt.Println(“hello world”) 表示fmt包的函数Println输出"hello world"
Golang执行流程分析
1.go文件-------->(go build编译)编译成.exe可执行文件-------->(运行)结果
2.go文件-------->(go run编译运行)结果
两种执行的区别
第1种 编译成可执行文件,没有开发环境也可以运行。并且可执行文件内存大
第2种 依赖开发环境才可运行,文件内存小
编译和运行说明
1)有了go源文件,通过编译器将其编译成机器可以识别的二进制文件
2)在该源文件的目录底下,可通过go build对hello.go文件进行编译。可指定生成可执行文件名,在windows下必须是.exe后缀
3)如果程序没有错误,是没有任何提示,会在当前目录底下生成一个可执行文件,该文件是二进制,也是可执行的程序
4)如果程序有错误,编译时会在错误那行报错。精准到第X行报错
2.Go程序开发注意事项
1.Go源文件以go为后缀名
2.Go应用程序的执行入口是main()函数
3.Go语言严格区分大小写
4.Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每个语句后自动加分号),这也体现Golang的简洁性
5.Go编译器是一行行编译的,因此我们一行写一条语句,不能将多个语句写在同一行。否则报错
6.Go语言定义的变量或者import的包没有使用,代码不能编译通过
7.大括号是成对出现的,缺一不可
3.Golang常用的转义字符
1)\t,一个制表位,实现对齐功能
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("大家\t晚上好")
}
输出:大家 晚上好
2)\n,换行符
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("大家\n晚上好")
}
输出:
大家
晚上好
3)\\,一个\
当你想输出一个文件路径,需要这样输出
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("D:\\goproject\\src\\go_code\\chapter02\\escaptechar")
}
输出:D:\goproject\src\go_code\chapter02\escaptechar
4)\",一个"
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("她说:\"她很开心\"")
}
输出:她说:“她很开心”
5)\r,一个回车,fmt.Println("大家\r晚上好")
//从当前行的最前面开始输出,覆盖掉以前内容
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("大家哈哈哈哈哈\r晚上好")
}
输出:晚上好哈哈哈哈
课堂练习,请使用一句输出语句,达到输出如下图形的效果
源码
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//演示转义字符的使用
fmt.Println("姓名\t年龄\t籍贯\t住址\nJohn\t12\t河北\t北京")
}
Golang开发常见问题和解决方法
找不到文件
D:\goproject\src\go_code\chapter02\escaptechar>go build apple.go
no required module provides package apple.go: go.mod file not found in current directory or any parent directory; see 'go help modules'
D:\goproject\src\go_code\chapter02\escaptechar>go run apple.go
CreateFile apple.go: The system cannot find the file specified.
D:\goproject\src\go_code\chapter02\escaptechar>apple.exe
'apple.exe' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
解决方法
源文件不存在或者写错,当前路径错误
注释的两种方式:
1.//行注释
2./* */块注释
源代码示范注释使用
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//行注释
fmt.Println("不想学习")
/*块注释
fmt.Println("想碎觉")
*/
}
输出:不想学习
注意:块注释不允许有块注释嵌套
规范的代码风格
1)使用一次tab操作,实现整体向右边移动,用shift+tab整体往左移 2)或者使用gofmt来进行格式化
格式化前的代码
package main
import "fmt"//fmt包中提供格式化、输出和输入的函数
func main(){
//不想学习
fmt.Println("不想学习")
//不想碎觉
fmt.Println("不想碎觉")
//不想打工
fmt.Println("不想打工")
}
格式化后的输出内容:
D:\goproject\src\go_code\chapter02\escaptechar>gofmt main.go
package main
import "fmt" //fmt包中提供格式化、输出和输入的函数
func main() {
//不想学习
fmt.Println("不想学习")
//不想碎觉
fmt.Println("不想碎觉")
//不想打工
fmt.Println("不想打工")
}
格式化更改源文件代码,重现打开源文件代码是格式化后的效果
D:\goproject\src\go_code\chapter02\escaptechar>gofmt -w main.go
3)运算符两边习惯性加一个空格
package main
import "fmt" //fmt包中提供格式化、输出和输入的函数
func main() {
//不想学习
fmt.Println("不想学习")
var num = 2 + 4 * 2
fmt.Println(num)
}
4)Go语言代码风格
错误的编写格式
func main()
{
fmt.Println("不想学习")
}
正确的编写格式
func main(){
fmt.Println("不想学习")
}
Go语言设计思想:一个问题只有一个解决方法
5)行长约定,一行不超过80个字符,超过的使用换行展示,尽量体现代码格式化优雅
源代码示范
package main
import "fmt" //fmt包中提供格式化、输出和输入的函数
func main() {
//不想学习
fmt.Println("这是很长的一段话有多长我们数一数\n",
"一二三四五六七八九十一二三四五六七八九十\n",
"一二三四五六七八九十一二三四五六七八九十\n",
"一二三四五六七八九十一二三四五六七八九十\n",
"一二三四五六七八九十\n",)
}
输出内容:
D:\goproject\src\go_code\chapter02\escaptechar>go run main.go
这是很长的一段话有多长我们数一数
一二三四五六七八九十一二三四五六七八九十
一二三四五六七八九十一二三四五六七八九十
一二三四五六七八九十一二三四五六七八九十
一二三四五六七八九十
4.Golang官方编程指南
Golang官方网站 https://golang.org/#
具体使用说明查看 bilibili网站【尚硅谷】Golang入门到实战教程丨一套精通GO语言,P24课节
Golang标准库API文档
1)API是Golang提供的基本编程接口
2)Go语言提供了大量的标准库,因此谷歌公司也为这些标准库提供了相应的API文档,用于告诉开发者如何使用这些标准库,以及标准库包含的方法。
3)Golang中文网,在线标准文档:https://studygolang.com/pkgdoc
5.Dos(Disk Operating System磁盘操作系统)的常用命令
1.dir查看当前所在路径底下的目录文件
2.C盘切换到D盘,输入d:
3.绝对路径:从当前盘的最上面开始定位,比如C:D找到对应的文件目录,比如D:\testb
相对路径:从当前位置开始定位,去找对应的目录。比如testb
4.返回上一步,cd ..
5.回到根目录,cd \
6.目录操作
1.新建目录,
md testfile
2.新建多个目录,md testfile1 testfile2
3.删除空目录,rd testfile
4.删除目录底下的子目录和文件不带询问,rd /q/s testfile
5.删除目录底下的子目录和文件带询问,rd /s testfile
7.文件操作
1.输出hello内容到新建的test.txt文件(绝对路径),
echo hello >d:\testa\test.txt
输出hello内容到新建的test.txt文件(相对路径),echo hello >test.txt
2.复制test.txt文件到指定路径,copy test.txt D:\testb\testbb
复制test.txt文件到指定路径并生成指定的文件名,copy test.txt D:\testb\testbb\test2.txt
3.移动test.txt文件到指定路径,move test.txt D:\testb\testbb
4.删除test.txt文件,del test.txt
删除所有.txt后缀的文件,del *.txt
8.其他指令
清屏,cls
退出,exit
6.变量(程序的基本组成单位)
变量使用的基本步骤:1.声明变量 2.变量赋值 3.使用变量
package main
import "fmt"
func main(){
var pro int //声明变量
pro = 10 //变量赋值
fmt.Println(pro) //使用变量
}
输出:10
变量使用的注意事项
1.变量表示内存中的一个存储区域
2.该区域有自己的名称(变量名)和类型(数据类型)
3.Golang使用的三种方式
1)指定变量类型,声明后若不赋值,使用默认值
package main
import "fmt"
func main(){
var pro int //声明变量
fmt.Println(pro) //使用变量
}
输出:0
2)根据值自行判定变量类型(类型推倒)
package main
import "fmt"
func main(){
var pro = 10 //变量赋值
fmt.Println(pro) //使用变量
}
输出:10
3)省略var,注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
package main
import "fmt"
func main(){
name :="zhangsan"
fmt.Println(name) //使用变量
}
输出:zhangsan
4.多变量声明
在编程中,有时我们需要一次性声明多个变量,Golang提供这种语法
package main
import "fmt"
func main(){
//方法1:声明,赋值,使用
var a,b,c int
a=0
b=1
c=2
//方法2:声明赋值,使用
var e,f,g int=3,4,5
//方法3:类型推倒,使用
h,i,j := 6,"不想上班",7
fmt.Println(a,b,c,e,f,g,h,i,j) //使用变量
}
输出:0 1 2 3 4 5 6 不想上班 7
全局变量声明赋值
package main
import "fmt"
//全局变量方法1:
var a = 100
var b = 200
var c = 300
//全局变量方法2:
var (
d=400
e=500
f=600
)
func main(){
fmt.Println(a,b,c,d,e,f) //使用变量
}
输出:100 200 300 400 500 600
5.该区域的数据值可以在同一类型范围内不断变化
package main
import "fmt"
func main(){
//该区域的数据值可以在同一类型范围内不断变化
var i int
i=1
i=2
fmt.Println(i) //使用变量
i=3.11//执行会报错,强制类型转换
}
输出:2
6.变量在同一作用域(在一个函数或者代码块)不能重名
package main
import "fmt"
func main(){
var i int
i=1
fmt.Println(i)
//下面两种情况,变量在同一作用域重名,会报错的
// var i int=2
// i :=3
}
7.变量=变量名+值+数据类型。变量三要素
8.Golang的变量如果没有赋予初始值,编译器会使用默认值。比如int类型的默认值为0,string的默认值为空串。小数默认为0
程序中的+号使用
1.当左右两边都是数值,会做加法运算
2.当左右两边都是字符串,则做字符串拼接。
7.数据类型
1.基本数据类型:数值型(整点类型,浮点类型)、字符型、布尔型、字符串型
2.浮生/复杂数据类型:指针、数组、结构体、管道、函数、切片、接口、map
整型的类型:
有符号:int8、int16、int32、int64
无符号:uint8、uint16、uint32、uint64
rune(与int32一样),byte(与uint8一样)
整型的使用细节:
1.Golang各整数类型的区分:有符号和无符号,int和uint大小和系统有关
2.Golang的整数默认声明是int
3.如何在程序查看变量的字节大小和数据类型
输出变量的字节大小%d
输出变量的数据类型%T
package main
import (
"fmt"
"unsafe"
)
func main(){
var test int64 = 10
fmt.Printf("test的类型是:%T,test的字节数是:%d",test,unsafe.Sizeof(test))
}
输出:test的类型是:int64,test的字节数是:8
4.Golang程序中整型变量在使用时,遵守保小不保大的原则,即:在保证程序正确运行下,尽量使用占用空间小的数据类型
5.bit是计算机最小的存储单位,byte是计算机最小的存储单元。1byte字节=8bit位
小数类型/浮点型
使用演示:
package main
import (
"fmt"
)
func main(){
var test float32 = 10.3
fmt.Println(test)
}
输出:10.3
浮点型分类,单精度float32(4字节),双精度float64(8字节)
浮点数=符号位+指数位+尾数位
尾数部分可能丢失,精度损失。
float64精度要比float32的更准确。
浮点型使用细节:
1.Golang的浮点类型有固定的范围和字节长度,不受具体OS(操作系统)的影响
2.Golang的浮点型默认声明为float4类型
3.浮点常量有两种表示形式
十进制形式:5.12 .512(必须有小数点)
科学计数法形式:5.1234e2=5.12*10的2次方 5.12E*2=5.12/10的2次方
字符类型(char)
Golang中没有专门的字符了下,如果要存储单个字符(字母),一般有byte来报错。
字符串就是一串固定长度的字符连接起来的字符序列,Go的字符串是由单个字节连接起来的。也就是说对于传统的字符串是有字符组成的。而Go的字符串不太,它是由字节组成的。
[官方将string归属到基本数据类型:https://tour.go-zh.org/basics/11]
1.当我们直接输出byte值是输出对应的字符ASCII码值
package main
import (
"fmt"
)
func main(){
var test byte = 'a'
fmt.Println(test)
}
输出:97
2.若我们需要输出对应的字符,需要使用格式化输出
package main
import (
"fmt"
)
func main(){
var test byte = 'a'
fmt.Printf("%c",test)
}
输出:a
3.若我们需要输出对应的文字,使用int
package main
import (
"fmt"
)
func main(){
var test int = '北'
fmt.Printf("test的码值为%d;\ntest的值为%c;",test,test)
}
输出:
test的码值为21271;
test的值为北
结论:
1.如果我们保存的字符在ASCII表的,比如[0-9,a-z,A-Z]可以保存到byte
2.如果我们保存的字符对应值大于255,这时我们可以考虑使用int类型
3.如果我们需要按照字符的方式输出,需要格式化输出fmt.printf(%c,test)
字符类型使用细节
1.字符常量是用单括号括起来的单个字符,例如 var test1 byte=‘a’、var test2 int=‘中’、var test3 byte=9
2. Go中允许使用转义字符’‘来将其后的字符转变成特殊字符型常量,例如 var test char=’\n’
3. Go语言的字符使用UTF-8编码,英文等于1个字节,汉字等于3个字节
4. 在Go中字符的本质是个整数,直接输出,是该字符对应的UTF-8编码的码值
5. 可以直接给某个变量赋一个数字,然后按格式化输出%c,会输出该数字对应的Unicode字符
符类型是可以运算的,相当于一个整数,它都有对应的Unicode码
package main
import (
"fmt"
)
func main(){
var test int = 20013
fmt.Printf("test的码值为%d;\ntest的值为%c;",test,test)
}
输出:
test的码值为20013;
test的值为中;
- 字符类型是可以运算的,相当于一个整数,它都有对应的Unicode码
package main
import (
"fmt"
)
func main(){
var test = 10+'a'
fmt.Println(test)
}
输出:107
字符类型本质探讨
存储:字符>>字符对应的码值>二进制>>存储
读取:二进制>>字符对应的码值>>字符>>读取
布尔类型
1.布尔类型也叫bool类型,只允许取值true或者false
2.bool类型占一个字节
3.boolean类型适用于逻辑运算,一般用于程序流程控制
使用方法
package main
import (
"fmt"
)
func main(){
var test = true
fmt.Println(test)
}
输出:true
注意事项:
1.bool类型只能去true或者false
package main
import (
"fmt"
"unsafe"
)
func main(){
var test = true
fmt.Println(unsafe.Sizeof(test))
test=1 //强制类型转换
}
会报错:.\main.go:9:7: cannot use 1 (untyped int constant) as bool value in assignment
2.不可0或1代替true和false
字符串类型(string)
字符串就是一串固定长度的字符连接起来的字符序列,Go的字符串是由单个字节连接起来的。Go语言的字符字节是由UTF-8编码标识unicode文本
String基本使用
package main
import (
"fmt"
)
func main(){
var test string="北京长城 10086 abcd"
fmt.Println(test)
}
输出:北京长城 10086 abcd
注意事项和使用细节
1.Go语言的字符串字节使用UTF-编码标识Unicode文本,这样Golang统一使用UTF-8编码。不会出现乱码问题
2.字符串一旦赋值了就不能改
3.字符串的两种表现形式
双引号:会识别转义字符
反引号:以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击,输出源代码等效果
反引号使用演示:
package main
import (
"fmt"
)
func main(){
var test string=`
package main
import (
"fmt"
)
func main(){
var test string="北京长城 10086 abcd"
fmt.Println(test)
}`//``为反引号
fmt.Println(test)
}
输出:
package main
import (
"fmt"
)
func main(){
var test string="北京长城 10086 abcd"
fmt.Println(test)
}
4.字符串的拼接方式
package main
import (
"fmt"
)
func main(){
var test ="hello"+"xiaoming"
fmt.Println(test)
}
输出:helloxiaoming
5.拼接内容太多,需要分行。并且+号留在上面一行最后面
package main
import (
"fmt"
)
func main(){
var test ="hello"+"xiaoming"+"hello"+
"xiaoming"+"helloaa"+"xiaoming"+"hellobb"+
"xiaoming"+"hellocc"+"xiaoming"
fmt.Println(test)
}
输出:helloxiaominghelloxiaominghelloaaxiaominghellobbxiaominghelloccxiaoming
数据类型默认值
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0 |
字符串 | “” |
布尔类型 | false |
代码示例格式化输出
package main
import (
"fmt"
)
func main(){
var a int
var b float32
var c float64
var d bool
var e string
fmt.Printf("a=%d,b=%f,c=%f,d=%v,e=%v",a,b,c,d,e)
//%v为变量的值输出
}
输出:a=0,b=0.000000,c=0.000000,d=false,e=
8.Golang基本数据类型的转换
Golang和java/c不同,Go在不同类型的变量之间赋值就可以显式转换,也就是说Golang中数据类型不能自动转换
基本语法:
表达式:T(v)将值v转换成类型T
T就是数据类型
v就是要转换的变量
用法示例:
package main
import (
"fmt"
)
func main(){
var a int = 100
var b float32 =float32(a)
var c int8=int8(a)
var d int64=int64(a)
fmt.Printf("a的类型为%T,a的值为%v,\nb的类型为%T,b的值为%v,\nc的类型为%T,C的值为%v,\nD的类型为%T,D的值为%v",a,a,b,b,c,c,d,d)
}
输出:
a的类型为int,a的值为100,
b的类型为float32,b的值为100,
c的类型为int8,C的值为100,
D的类型为int64,D的值为100
细节说明:
1)Go中数据类型的转换是可以从表示范围小–>变成表示范围大,也可以表示范围大–>表示范围小
2)被转换的是变量存储的数据类型(即值),变量本身的数据类型没有变化
3)在转换中,比如将int64转换成int8,编译时不会报错,只是转换的结果按溢出处理,和我们希望的结果不一样
基本数据类型和String类型的转换
在程序开发中,我们经常需要将基本数据类型转换成string类型。或者将string类型转换成基本数据类型
基本类型转String类型
方式1:fmt.Sprintf(“%参数”,表达式)
1)参数需要和表达式的数据类型相匹配
2)fmt.Sprintf()会返回转换后的字符串
3)案例演示
package main
import (
"fmt"
)
func main(){
var a int = 100
var b float64 =8.88
var c bool = true
var d byte ='k'
var e string
e=fmt.Sprintf("%d",a)
fmt.Printf("d的类型为%T,d的值为%v,d的值为%q\n",e,e,e)
e=fmt.Sprintf("%f",b)
fmt.Printf("d的类型为%T,d的值为%v,d的值为%q\n",e,e,e)
e=fmt.Sprintf("%t",c)
fmt.Printf("d的类型为%T,d的值为%v,d的值为%q\n",e,e,e)
e=fmt.Sprintf("%c",d)
fmt.Printf("d的类型为%T,d的值为%v,d的值为%q\n",e,e,e)
}
输出:
d的类型为string,d的值为100,d的值为"100"
d的类型为string,d的值为8.880000,d的值为"8.880000"
d的类型为string,d的值为true,d的值为"true"
d的类型为string,d的值为k,d的值为"k"
方式二:使用strconv包的函数
案例演示
package main
import (
"fmt"
"strconv"
)
func main(){
var a int = 99
var b float64=9.9
var c bool=true
var str string
//int类型转字符串
str=strconv.FormatInt(int64(a),10)
fmt.Printf("str的类型为%T,值为%q\n",str,str)
str=strconv.Itoa(a)
fmt.Printf("str的类型为%T,值为%q\n",str,str)//a只接受int类型,若a为int64需要转成int
//float类型转字符串
str=strconv.FormatFloat(b,'f',10,64)
//说明strconv.FormatFloat(变量名,格式,小数位保留X位,小数的精度是X位)
fmt.Printf("str的类型为%T,值为%q\n",str,str)
//bool类型转字符串
str=strconv.FormatBool(c)
fmt.Printf("str的类型为%T,值为%q\n",str,str)
}
输出:
str的类型为string,值为"99"
str的类型为string,值为"99"
str的类型为string,值为"9.9000000000"
str的类型为string,值为"true"
String类型转换基本数据类型
1)使用strconv包
案例演示
package main
import (
"fmt"
"strconv"
)
func main(){
//字符串类型转bool
var str string="true"
var a bool
//a,_说明:strconv.ParseBool函数会返回两个值(value bool,error)
//因为我只想获取value bool不想获取error,所以我使用_忽略
a,_=strconv.ParseBool(str)
fmt.Printf("a的类型为%T,值为%v\n",a,a)
//字符串类型转int
var str2 string="1234567"
var b int64
var c int
b,_=strconv.ParseInt(str2,10,64)
c=int(b)
//strconv.ParseInt(变量名,X进制,指定的整数类型),0(int)、8(int8)...
fmt.Printf("b的类型为%T,值为%v\n",b,b)
fmt.Printf("c的类型为%T,值为%v\n",c,c)
//字符串类型转float
var str3 string="16.66"
var d float64
d,_=strconv.ParseFloat(str3,64)
fmt.Printf("d的类型为%T,值为%v",d,d)
}
输出:
a的类型为bool,值为true
b的类型为int64,值为1234567
c的类型为int,值为1234567
d的类型为float64,值为16.66
注意事项:
在将String转换成基本数据类型,需要确保String类型能够转换成有效数据,比如能将"123"转成一个整数,但不是将"hello"转换成整数,否则golang会转成默认值
9.指针
基本介绍
1)基本数据类型:变量存的是值,也叫值类型
2)获取变量的地址,用&,比如:var num int,取num的地址&num
3)指针类型,变量存的是一个地址,这个地址指向的空间存的才是值,比如var ptr *int=&ptr
4)获取指针类型所指向的值,使用 *,比如 var *ptr int,使用 *ptr获取ptr所指向的值
举例说明
package main
import (
"fmt"
)
//演示golang的指针类型
func main(){
//基本数据类型在内存布局
var a int=10
fmt.Println("a的地址=",&a)
var b *int=&a
//b是个指针变量,b的类型是*int,b本身的值是&a
fmt.Printf("b的类型是%T,值是%v\n",b,b)
fmt.Printf("b的地址是%v\n",&b)
fmt.Printf("b所指向的值是%v\n",*b)
}
输出:
a的地址= 0xc00000c098
b的类型是*int,值是0xc00000c098
b的地址是0xc00000a030
b所指向的值是10
案例分析
声明一个int变量num,将num的地址赋值指向给指针ptr,通过ptr去修改num的值
package main
import (
"fmt"
)
//演示golang的指针类型
func main(){
//基本数据类型在内存布局
var num int =10
fmt.Println("num的地址是",&num)
var ptr *int
ptr=&num
*ptr=11//这里修改,会修改到num变化
fmt.Printf("num的值是%v",num)
}
输出:
num的地址是 0xc000102058
num的值是11
常见的值类型和引用类型
值类型:int系列、float系列、bool、char、string、数组和结构体struct
引用类型:指针、slice切片、map、管道chan、interface等都是引用类型
10.标识符的命名规则
标识符概念
1)Golang对各种变量、方法、函数等命名时所使用的字符序列称为标识符
2)凡是自己可以起名字的都称为标识符
标识符的命名规则
1)由26个字母大小写0-9,_组成
2)数字不可以开头
3)Golang中严格区分大小写
4)标识符不能包含空格
5)下划线本身在Go中是个特殊的字符,称为空标识符。可以代表任何其他的标识符、但是它对应的值会被忽略,所以它不能作为占位符使用,不能作为标识符使用
6)不能以系统保留关键字作为标识符,比如break,if等等
标识符的注意事项
1)包名:保持package的名字和目录保持一致,尽量使用有意义的包名,剪短,有意义,不要和标准库冲突
2)变量名、函数名、常量名尽量使用驼峰法
3)如果变量名、函数名、常量名首字母为大写则可以给其他包使用,如果首字母小写,则只能在本包使用。
案例示范,使用其他包
输出:宋江
注意事项:
因为新版的go不支持老版本的环境配置了,若出现is not in GOROOT。可以输入命令go env确认GO111MODULE为""或者on,需要输入命令修改go env -w GO111MODULE=off
并重启cmd,执行go run main.go
11.运算符介绍
运算符是一种特殊符号,用于表示数据的运算、赋值和比较等
1)算术运算符
2)赋值运算符
3)比较运算符
4)逻辑运算符
5)位运算符
6)其他运算符
1)算术运算符
细节说明:
1)对于"/"号,它的整数除和小数除是有区别的,整数除只保留整数部分,舍弃小数部分。
2)当一个数取模时,可以等价a%b=a-a/b*b
3)Golang的自增自减,只能当做一个独立语言使用,不能这样使用:
b:=a++或者b:=a–
4)Golang的++和–只能写在变量后面
5)Golang的设计者去掉java/c中的自增自减容易混淆的写法,让golang更加简洁统一
2)比较运算符
比较运算符结果都是bool型,也就是要么true,要么是false
关系运算符通常在if结构条件或者循环条件使用较多
细节说明:
1)比较运算符的结果都是bool类型
2)比较运算符的表达式,我们称为关系表达式:a>b
3)比较运算符"==“不能写成”="
3)逻辑运算符
用于连接多个条件(一般来讲是关系表达式),最终结果也是bool型
细节说明:
1)&& 也叫短路与,如果第一个条件为false,第二个条件就不会进行判断,最终结果为false
2)|| 也叫短路或,如果第一个条件为true,第二个条件就不会进行判断,最终结果为true
4)赋值运算符
赋值运算符是将某个运算后的值,赋给指定的变量
运算符优先级
1)运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序,如下表,上一行运算符总优先于下一行。
2)只有单目运算符,赋值运算符是从右往左运算的。
3)大致的顺序整理
1.括号,++,–
2.单目运算
3.算术运算
4.移位运算
5.比较运算符
6.位运算符
7.逻辑运算符
8.赋值运算符
9.逗号
12键盘输入语句
介绍
在编程中需要接收用户输入的数据,就可以使用键盘输入语句来获取。ImputDemo.go
步骤:
1)导入fmt包
2)调用fmt.Scanln()或者fmt.Scanf()
案例演示:
从控制台接收用户信息(姓名,年龄,薪水,性别)
1)使用fmt.Scanln()
package main
import "fmt"
func main() {
var name string
var age int
var sal float32
var isPass bool
fmt.Println("请输入你的名字")
fmt.Scanln(&name)
fmt.Println("请输入你的年龄")
fmt.Scanln(&age)
fmt.Println("请输入你的薪水")
fmt.Scanln(&sal)
fmt.Println("请问你是学生吗")
fmt.Scanln(&isPass)
fmt.Printf("你的名字是%v,年龄是%v,薪水是%v,是否是学生 %v", name, age, sal, isPass)
}
输出:
请输入你的名字
西瓜
请输入你的年龄
18
请输入你的薪水
1234.56
请问你是学生吗
true
你的名字是西瓜,年龄是18,薪水是1234.56,是否是学生 true
2)使用fmt.Scanf()
import "fmt"
func main() {
var name string
var age int
var sal float32
var isPass bool
fmt.Println("请输入你的名字,年龄,薪水,是否是学生 请用空格隔开")
fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
fmt.Printf("你的名字是%v \n你的年龄是%v \n你的薪水是%v \n是否是学生%v", name, age, sal, isPass)
}
输出:
请输入你的名字,年龄,薪水,是否是学生 请用空格隔开
西瓜 18 99.99 true
你的名字是西瓜
你的年龄是18
你的薪水是99.99
是否是学生true
13.进制
进制介绍
对于整数,有四种表示方式
1)二进制:0,1,满2进1
2)十进制:0-9,满10进1
3)八进制:0-7,满8进1,以数字0开头显示
4)十六进制:0-9及A-F,满16进1,以0x或者0X开头表示。此处A-F不区分大小写
如0x21AF+1=0X21B0**
案例输出:
package main
import "fmt"
func main() {
//二进制输出
var a int = 5
fmt.Printf("%b \n", a)
//八进制转换十进制
var b int = 011
fmt.Println("b=", b)
//十六进制转换十进制
var c int = 0x11
fmt.Println("c=", c)
}
输出:
101
b= 9
c= 17
进制的转换
进制转换的介绍
第一组:(其他进制转十进制)
1)二进制转十进制
规则:从最低位开始(最右边的数值)将每个位上的数取出来,乘以2的(位数-1)次方,然后求和。
案例:请将二进制1011转成十进制的数
1011=11+12+022+1222=1+2+0+8=11
2)八进制转十进制
规则:从最低位开始(最右边的数值)将每个位上的数取出来,乘以8的(位数-1)次方,然后求和。
案例:请将八进制0123转成十进制的数
0123=31+28+188+0888=3+16+64+0=83
3)十六进制转十进制
规则:从最低位开始(最右边的数值)将每个位上的数取出来,乘以16的(位数-1)次方,然后求和。
案例:请将十六进制0x34A转成十进制的数
034A=101+416+316*16=10+64+768=842
第二组:(十进制转其他进制)
1)十进制转二进制
规则:该除数不断除以二,直到商为0为止。然后将每步得到的余数倒过来,就是对应的二进制
案例:请将56转成二进制
结果为11100
2)十进制转八进制
规则:该除数不断除以八,直到商为0为止。然后将每步得到的余数倒过来,就是对应的二进制
案例:请将156转成八进制
结果为234
3)十进制转十六进制
规则:该除数不断除以八,直到商为0为止。然后将每步得到的余数倒过来,就是对应的二进制
案例:请将356转成十六进制
结果为164
第三组:(二进制转其他进制)
1)二进制转八进制
规则:将二进制转每三位一组(从低位开始组成),转成对应的八进制即可。
案例:请将二进制11010101转成八进制
结果为:0325
2)二进制转十六进制
规则:将二进制转每四位一组(从低位开始组成),转成对应的十六进制即可。
案例:请将二进制11010101转成八进制
结果为0xD5
第四组:(其他进制转二进制)
1)八进制转二进制
规则:将八进制每一位,转成对应的一个3位的二进制即可
案例:请将0237转成二进制
结果为:10011111
2)十六进制转二进制
规则:将十进制每一位,转成对应的一个3位的二进制即可
案例:0x237转成二进制
结果为:1000110111
14.位运算
原码、反码、补码
对于有符号而言:
1)二进制最高位是符号位:0表示正数,1表示负数
2)正数的原码、反码和补码都一样
3)负数的反码=它的原码符号位不变,其他位取反(0>1,1>0)
-1==>原码10000001、反码11111110
4)负数的补码=它的反码+1
-1的补码=11111111
5)0的反码,补码都是0
6)在计算机运行的时候,都是以补码方式来运算
位运算符和移位运算符
位运算符
分别是按位与、按位或、按位异或。它的运算规则是
按位与&:两位全为1,结果才为1,否则为0
案例:2&3
2的补码是00000010
3的补码是00000011
两位全为1,结果才为1,否则为0。所以2&3结果为2
按位或|:两位有一个为1,结果为1,否则为0
案例:2|3
2的补码是00000010
3的补码是00000011
两位有一个为1,结果为1,否则为0。所以2|3结果为3
按位异^:两位一个为0,一个为1,结果为1,否则为0
案例:2^3
2的补码是00000010
3的补码是00000011
两位一个为0,一个为1,结果为1,否则为0。所以2^3结果为1
移位运算符
右移和左移的运算规则>>、<<
右移运算符>>:低位溢位,符号位不变,并用符号位补溢出的高低
左移运算符>>:符号位不变,低位补0
15.程序流程控制介绍
在程序中,程序运行的流程控制决定程序是如何执行的。主要有三大流程控制语句
1)顺序控制
程序从上往下逐行地执行,中间没有任何判断和跳转
2)分支控制
程序有选择的执行,分支控制有三种
- 单分支
// 基本语法
if 条件表达式{
执行代码块
}
//说明:当条件表达式为true时,就会执行{}的代码
- 双分支
// 基本语法
if 条件表达式{
执行代码块1
}else{
执行代码块2
}
//说明:当条件表达式成立,会执行代码块1,否则执行代码块2。
- 多分支
// 基本语法
if 条件表达式1{
执行代码块1
}else if 条件表达式2{
执行代码块2
}
else{
执行代码块N
}
//说明:先判断条件表达式1,如果条件表达式1为true,则执行代码块1。否则进行判断条件表达式2,如果条件表达式2为true,则执行代码块2。否则执行代码块N.
//注意else不是必须
- 嵌套分支
if 条件表达式{
if 条件表达式{
} else{
}
}
//在一个分支结构中又完整地嵌套了另一个完整地分支结构
//里面的分支结构称为内层分支,外面的结构称为外层分支
//嵌套分支不宜过多,控制在三层内。
- switch分支结构
1)switch语句用于基于不同条件执行不同的动作,每个case分支都是唯一的。从上到下逐一测试,直到匹配停止。
2)匹配项后面不用再加break
基本语法
switch 表达式{
case 表达式1,表达式2...:
语句块1
case 表达式3,表达式4...:
语句块2
//这里可以有很多case语句
default:
语句块
}
Switch的注意事项和使用细节
1)case/switch后是个表达式(即常量值、变量、一个返回的函数值都可以)
2)case后的各个表达式的数据类型要和switch的表达式数据类型一致
3)case后面可以有多个表达式,使用逗号分隔开。
4)case后面的表达式如果是常量值,则要求不重复。
5)case后面不需要带break
6)default语句不是必须的
7)switch后也可以不带表达式,类似if-else分支来使用
8)switch后声明定义一个变量,分号结束(不推荐使用)
9)switch穿透fallthrough,则会执行下一个case.也叫switch穿透
10)Type Switch:switch语句还可用于type switch来用于判断某个interface变量中的实际指向的变量类型
switch和if的比较
1)如果判断的具体数值不多,而且符合整数,浮点数,字符和字符串几种类型。建议使用switch语句简洁高效。
2)其他情况,对区间判断和结果bool类型的判断,if使用范围更广
3)循环控制
- for循环基本语法:
for 变量初始化;循环条件;循环变量迭代{
循环操作
}
案例:for循环,输出十句hello
//第一种写法
package main
import "fmt"
func main() {
for a := 1; a <= 10; a ++ {
fmt.Println("hello")
}
}
//第二种写法
package main
import "fmt"
func main() {
a := 1
for a <= 10 {
fmt.Println("哈哈哈")
a++
}
}
for循环使用注意事项
1.循环条件是个布尔值的表达式
2.for循环的使用方法
for{
循环执行语句
}
//上面写法是个无限循环,需要使用break语句使用
3.golang提供for range的方式,可以方便遍历字符串和数组。
//传统写法
package main
import "fmt"
func main() {
var str string = "abcdefg"
for i := 0; i < len(str); i++ {
fmt.Printf("%c \n", str[i])
}
}
//for range写法
package main
import "fmt"
func main() {
var str string = "abcdefg"
for index, val := range str {
fmt.Printf("index=%d,val=%c \n", index, val)
}
}
上面代码细节讨论,如果我们的字符串含有中文,那么传统的遍历字符串方式是错误的,会出现乱码。原因是传统的是按照字节遍历。一个汉字的utf-8编码占3个字节。解决方法:
package main
import "fmt"
func main() {
var str string = "abcdefg哈哈"
str2 := []rune(str)
for i := 0; i < len(str2); i++ {
fmt.Printf("%c \n", str2[i])
}
}
练习题:
1)打印1-100之间,所有能被9整除的数值个数和总和。
package main
import "fmt"
func main() {
var a int = 1
var sum int = 0
var count int = 0
for ; a < 101; a++ {
if a%9 == 0 {
count++
sum += a
}
}
fmt.Printf("count=%d,sum=%d", count, sum)
}
2)完成下面的表达式输出
0 +6 =6
1 +5 =6
2 +4 =6
3 +3 =6
4 +2 =6
5 +1 =6
6 +0 =6
package main
import "fmt"
func main() {
var b int = 6
for a := 0; a <= 6; a++ {
fmt.Printf("%d +%d =%d\n", a, b-a, b)
}
}
while和do…while的实现。
Go语言没有while和dowhile的写法,但可以使用for循环实现类似的效果
1)for循环实现while的效果
循环变量初始化
for{
if 循环条件表达式{
break//跳出for循环
}
循环操作语句
循环变量迭代
}
多重循环控制
1)将一个循环放在另一个循环体内,就形成了嵌套循环。在外边的for称为外层循环。在里面的for循环称为内层循环(建议不超过3层)
2)实质上,嵌套循环就是把内循环当作外循环的循环体。当只有内层循环的循环条件为false时,才能跳出内循环。才能结束外循环的当次循环,开始下一次循环。
3)设外循环的条件为m次,内循环的条件是n次,内层循环实际要执行m*n次
打印99乘法口诀
package main
import "fmt"
func main() {
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d \t", j, i, j*i)
}
fmt.Println()
}
}
跳转控制语句Break
随机生成1-100的数,直到生成99的数,退出循环
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var count int = 0
for {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(100) + 1
fmt.Println("n=", n)
count++
if n == 99 {
break
}
}
fmt.Println("生成99一共使用了", count)
}
Break使用的注意事项,若break出现在多层嵌套语句中,可使用标签指定哪一层的语句块
使用示范:
package main
import "fmt"
func main() {
lable1:
for a := 1; a <= 5; a++ {
for b := 6; b <= 10; b++ {
if a == 2 {
break lable1
}
fmt.Println(b)
}
fmt.Println(a)
}
}
输出:
6
7
8
9
10
1
练习题:
1.100以内的数值求和,求出当总和大于20的当前值
package main
import "fmt"
func main() {
var sum int
for a := 0; a <= 100; a++ {
sum = a + sum
if sum > 20 {
fmt.Println("当sum大于20时,当前值为%d", a)
break
}
}
}
2.实现登录验证,有三次机会。当用户名为张无忌,密码888提示登录成功,否则提示,你还有几次机会。
package main
import "fmt"
func main() {
var name string
var password int
fmt.Println("请输入你的名字")
fmt.Scanln(&name)
if name == "张无忌" {
for i := 3; i >= 0; i-- {
fmt.Println("请输入你的密码")
fmt.Scanln(&password)
if password == 888 {
fmt.Println("登录成功")
break
}
if i == 1 {
fmt.Println("3次机会用完,自动退出程序")
break
}
fmt.Printf("你还剩%d次机会\n", i-1)
}
} else {
fmt.Println("你不是张无忌")
}
}
跳转控制语句continue
1、continue结束本次循环,进入下次循环
2、标签指定结束哪一层的循环
案例示范
package main
import "fmt"
func main() {
here:
for i := 0; i < 2; i++ {
for j := 1; j < 4; j++ {
if j == 2 {
continue here
}
fmt.Println("i=", i, "j=", j)
}
}
}
输出:
i= 0 j= 1
i= 1 j= 1
练习题:
1、用continue实现,1-100的奇数和。(for循环+continue)
package main
import "fmt"
func main() {
var sum int = 0
for i := 0; i <= 100; i++ {
if i%2 == 0 {
continue
}
sum = i + sum
}
fmt.Println("总数和为", sum)
}
2、从键盘输入个数不确定的正数或负数个数,输入为0结束程序。(for循环+break+continue)
package main
import "fmt"
func main() {
var zhengshu int
var fushu int
var num int
for {
fmt.Println("请输入一个数值")
fmt.Scanln(&num)
if num == 0 {
break //结束程序
}
if num > 0 {
zhengshu++
continue
}
fushu++
}
fmt.Printf("输入正数的个数为%d,负数的个数为%d", zhengshu, fushu)
}
跳转控制语句goto
1.go语言的goto语句,可以无条件转移到程序中的指定行
2.goto语句经常与条件语句配合使用,可以实现条件转移。跳出循环体功能
3.在go程序设计中一般不主张使用goto语句,以免造成程序流程的混乱,使理解和调试程序中产生困难
使用示范:
package main
import "fmt"
func main() {
fmt.Println("111")
goto label1
fmt.Println("222")
label1:
fmt.Println("333")
}
输出:
111
333
跳转控制语句return
1.return在普通的函数,代表跳出该函数,不再执行return后面的代码。也可以理解成终止函数
2.return在main函数,表示终止main函数,也称终止程序。
package main
import "fmt"
func main() {
var a int = 10
fmt.Println("111")
if a > 5 {
return
}
fmt.Println("222")
fmt.Println("333")
}
输出:
111
函数
函数作用:利于维护、代码简洁
函数基本语法
func 函数名 (形参列表)(返回值类型列表){
函数执行语句
return 返回值类型列表
}
1.形参列表:函数的输入
2.函数中的语句:表示为了实现某一功能代码块
3.函数可以有返回值,也可以没有
案例示范:
package main
import "fmt"
func cal(n1 float64, n2 float64, operator byte) float64 {
var res float64
switch operator {
case '+':
res = n1 + n2
case '-':
res = n1 - n2
case '*':
res = n1 * n2
case '/':
res = n1 / n2
}
return res
}
func main() {
var n1 float64 = 2.6
var n2 float64 = 1.3
var operator byte = '+'
result := cal(n1, n2, operator)
fmt.Println("result=", result)
}
16.包的介绍
包作用:
1.区分相同名字的函数、变量等标识符
2当程序文件很多时,可以很好的管理项目
3.控制函数、变量等访问范围,即作用域
打包基本语句package until
引入包的基本语法import “包的路径”
引入包注意事项
1.被引入包的函数名首字母,需要大写。
2.引入包的路径默认从Src开始写:例如go_code/project/utils
若被引入的文件路径为GoProject/Src/go_code/project/utils/abc.go
3.调用包的语法:abc.函数名()
若被引入的文件路径为GoProject/Src/go_code/project/utils/abc.go
4.在给一个文件打包时,该包对应一个文件夹。文件的包名和文件所在的文件夹名一致。一般为小写字母
被引入的文件路径为GoProject/Src/go_code/project/utils/utils.go
5.package指令在文件第一行,再是import
6.相同的包下面不能使用相同的函数名
7.只有main包才能打包成一个可执行文件
17.函数递归调用
函数递归遵守的重要原则:
1.执行一个函数时,就创建一个新的受保护空间(新函数栈)
2.函数的局部变量是独立的,不会相互影响
3.递归必须退出递归的条件逼近,否则是无限递归
4.当一个函数执行完毕,或者遇到return就会返回。遵守谁调用,把结果返回谁
代码示例:
package main
import "fmt"
func test(n int) {
if n > 2 {
n--
test(n)
}
fmt.Println("n=", n)
}
func main() {
test(4)
}
输出:
n= 2
n= 2
n= 3
递归练习题真难,做不出来,整理笔记先这样。后面可以重温
https://www.bilibili.com/video/BV1ME411Y71o/?p=117
函数的注意事项和细节讨论
1.函数的形参列表可以是多个,返回值也可以是多个。
2.形参和返回值的列表数据类型可以是值类型也可以是引用类型
3.函数的命名遵循标识符的命名规范,首字母大写可以被本包和其他包文件调用
4.函数的变量是局部的,函数外不生效
5.基本数据类型和数组默认是值传递的,即进行值拷贝,在函数内修改,不会影响到原来的值
6.如果希望函数内的变量能修改函数外的变量,可以引入变量的地址&,函数内以指针的方式操作变量
7.Go函数不支持重载
8.在Go中,函数也是一种数据类型,也可以赋值给一个变量,则该变量就是函数类型的变量了。通过该变量可以对函数调用
9.函数既然是一种数据类型,可以作为形参,并且调用
10.为了简化数据类型定义,Go支持自定义数据类型
11.支持对函数返回值命名
12.使用_标识符,忽略返回值
13.Go支持可变参数
Init函数
每个源文件都包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。init函数最主要的作用是初始化工作
代码示例:
package main
import "fmt"
func init() {
fmt.Println("init函数输出")
}
func main() {
fmt.Println("main函数输出")
}
输出:
init函数输出
main函数输出
init注意事项:如果一个文件中包含全局变量定义,Init函数和main函数。执行顺序为:全局变量>init函数>main函数
匿名函数
Go支持匿名函数,如果我们某个函数希望只使用一次,可以考虑匿名函数,匿名函数也可以实现多次调用。
匿名函数使用方式1:在定义匿名函数时直接调用
package main
import "fmt"
func main() {
resl := func(n1 int, n2 int) int {
return n1 + n2
}(10, 20)
fmt.Println("resl=", resl)
}
输出:resl= 30
匿名函数使用方式2:将匿名函数给一个变量(函数变量),再通过变量调用匿名函数
package main
import "fmt"
func main() {
a := func(n1 int, n2 int) int {
return n1 - n2
}
resl2 := a(10, 20)
fmt.Println("resl2=", resl2)
}
输出:resl2= -10
匿名函数使用方式3:将匿名函数给一个全局变量,那么这个函数为全局匿名函数
package main
import "fmt"
var (
a = func(n1 int, n2 int) int {
return n1 * n2
}
)
func main() {
res3 := a(2, 3)
fmt.Println("res3=", res3)
}
输出:res3= 6
闭包
基本介绍:闭包是指一个函数和与其相关环境组合的一个整体(实体)
代码示例:
package main
import "fmt"
func AddUpper() func(int) int {
var n int = 10
return func(x int) int {
n = n + x
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1)) //结果为11
fmt.Println(f(2)) //结果为13
fmt.Println(f(3)) //结果为16
}
对上面代码说明和总结
1.AddUpper是个函数,返回的数据类型是func(int) int
2.闭包说明
var n int = 10
return func(x int) int {
n = n + x
return n
返回的是个匿名函数,但这个匿名函数引用到函数外的n。因此这个函数和n就形成一个整体,构成闭包。
3.大家可以理解,闭包是类,函数是操作,n是字段。函数和它使用n构成闭包
4.当我们反复的调用f函数时,因为n是初始化一次,因为它每调用一次就会进行累加
5.我们要搞清楚闭包的关键,就是要分析出返回的函数使用到哪些变量。因为函数和它引用的变量会共同构成闭包
闭包的最佳实践
请编写一个程序,具体要求如下。
1.编写一个函数makesuffix(suffix string)可以接收一个文件名后缀(比如.jpg),并返回一个闭包
2.调用闭包,可以传入一个文件名。如果该文件名没有指定后缀名(比如.jpg),则返回文件名.jpg。如果已经有jpg后缀,则返回原文件名。
3.要求使用闭包的方式完成
4.strings.HasSuffix.该函数能判断某个字符串有指定的后缀
package main
import (
"fmt"
"strings"
)
func makesuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
f := makesuffix(".jpg")
fmt.Println("文件名处理后=", f("winter"))
fmt.Println("文件名处理后=", f("bird.jpg"))
}
输出:
文件名处理后= winter.jpg
文件名处理后= bird.jpg
代码说明:
1.返回的函数和makesuffix(suffix string)的stuffix变量和返回的函数组合成一个闭包。因为返回的函数引入到stuffix这个变量。
2.我们体会下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能。只是传统方法需要每次都传入后缀名,比如.jpg。而闭包因为可以保留上次引入的某个值。所以我们使用一次就可以反复使用。
函数中derfer
在函数中,程序员经常需要创建资源(比如:数据库连接,文件句柄,锁等),为了在函数执行完毕后,及时释放资源。提供defer可以延时机制。
代码示例:
package main
import (
"fmt"
)
func sum(n1 int, n2 int) int {
//当执行到defer暂时先不执行,会将defer后面的语句压入到独立的栈(defer栈)
//当函数执行完毕后,再从栈中以先进后出方式出栈。执行顺序分别是
defer fmt.Println("ok1 n1=", n1) //3.ok1 n1=10
defer fmt.Println("ok2 n2=", n2) //2.ok2 n2=20
res := n1 + n2
fmt.Println("ok3 res=", res) //1.ok3 res=30
return res
}
func main() {
res := sum(10, 20)
fmt.Println("res=", res) //4.res= 30
}
输出:
ok3 res= 30
ok2 n2= 20
ok1 n1= 10
res= 30
defer的细节说明:
1.当go执行到第一个defer时,不会立即执行defer后的语句。而是将defer语句压到一个栈。然后继续执行到下个语句。
2.当执行完毕后,再从栈中,依次从栈顶取出语句执行。遵循先入后出的机制
3.在defer将语句放入栈时,会将相关值同时copy入栈
package main
import (
"fmt"
)
func sum(n1 int) int {
defer fmt.Println("n1=", n1)
n1++
return n1
}
func main() {
n2 := sum(10)
fmt.Println("n2=", n2)
}
输出:
n1= 10
n2= 11
defer最主要价值是在于:当函数执行完毕后,可以及时释放函数创建的资源。在defer执行完后,能继续使用创建资源。当函数执行完毕后,系统会依次从defer栈中取出语句,关闭资源。
func test() {
file = openfile(文件名)
def.file.close()//关闭文件资源
}
func test() {
connect = openDatabase(文件名)
def.connect .close()//关闭数据库资源
}
函数参数的传递方式
1.值传递2.引用传递
值传递的是指copy,引用传递的是指地址的copy.地址copy效率高
值类型和引用类型
1.值类型的基本类型是int、float、string、bool、数组和结构体struct
2.引用类型是:指针,slice切片、map、管道chan、interface等引用类型
变量作用域
1.函数内部声明/定义的变量叫局部变量,作用域仅限于函数内
2.函数外部声明/定义的变量叫全局变量,作用域是整个包有效。如果首字母为大写,作用域在整个程序有效。
3.如果变量在一个代码块,比如for/if,那么作用域就仅限于代码块
18.字符串常用的系统函数
1.统计字符串的长度,按字节len(str)
2.字符串遍历,同时处理有中文的问题。r:=[]rune(str)
代码示例:
package main
import (
"fmt"
)
func main() {
a := "大家好hello"
b := []rune(a)
for i := 0; i < len(b); i++ {
fmt.Printf("字符=%c\n", b[i])
}
}
输出:
字符=大
字符=家
字符=好
字符=h
字符=e
字符=l
字符=l
字符=o
3.字符串转整数:n,err:=strconv.Atoi(“12”)
代码示例:
package main
import (
"fmt"
"strconv"
)
func main() {
a, b := strconv.Atoi("12")
if b != nil {
fmt.Println("转换失败_01", b)
} else {
fmt.Println("转换成功_01", a)
}
c, d := strconv.Atoi("hello")
if d != nil {
fmt.Println("转换失败_02", d)
} else {
fmt.Println("转换成功_02", c)
}
}
输出:
转换成功_01 12
转换失败_02 strconv.Atoi: parsing “hello”: invalid syntax
4.整数转字符串:
str=strconv.ltoa(12345)
str := strconv.Itoa(12345)
fmt.Printf("str=%v\n str=%T", str, str)
5.字符串转byte: var bytes=[]byte(“hello”)
var bytes = []byte("hello")
fmt.Printf("bytes=%v\n", bytes)
6.byte转字符串:str:=string([]byte{97,98,99})
str := string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str)
7.10进制转2、8、16进制:str:=strconv.FormatInt(123,2)
str := strconv.FormatInt(123, 2)
fmt.Printf("123对应的二进制为%v\n", str)
str1 := strconv.FormatInt(123, 16)
fmt.Printf("123对应的十六进制为%v\n", str1)
8.查找子串是否在指定的字符串中:strings.Contains(“saeafood”,“foo”)//若存在,返回布尔值
a := strings.Contains("saeafood", "foo")
fmt.Printf("a=%v", a)
9.统计一个字符串有几个指定的子串:strings.Count(“applese”,“p”)//输出为2
a := strings.Count("applese", "p")
fmt.Printf("a=%v", a)
10.不区分大小写的字符串比较(==是区分大小写):fmt.Println(strings.EqualFold(“abc”,“ABC”))
11.子串在字符串中第一次出现index的值,如果没有返回-1,strings.Index(“NLT_abc”,“abc”)
index := strings.Index("NLT_abc", "abc")
fmt.Printf("index=%v\n", index)
index1 := strings.Index("NLT_abc", "g")
fmt.Printf("index1=%v", index1)
输出:
index=4
index1=-1
12.返回子串最后一次在字符串出现的Index,如果没有返回-1,strings.LastIndex(“NLT_abc”,“abc”)
13.将指定的子串替换成另外一个字符串:strings.Replace(“go hello”,“go”,“go语言”,n),n可以指定你希望替换几个,如果n=-1全部替换
a := "no go go hello yes"
b := strings.Replace(a, "go", "go语言", -1)
fmt.Println("b=", b)
输出:b= no go语言 go语言 hello yes
14.按照指定的某个字符,为分割标识符,将一个字符串拆分成字符串数组:strings.Split(“hello,wrold,ok”,“,”)
a := strings.Split("hello,wrold,ok", ",")
fmt.Println("a=", a)
输出:a= [hello wrold ok]
15.将字符串字母进行大小写转换:strings.ToLower(“Go”)转小写,strings.ToUpper(“Go”)转大写
16.将字符串左右两边的空格去掉:strings.TrimSpace(" Go “)
17.将字符串左右两边指定字符去掉:strings.Trim(”!Go!“, “!”)
18.将字符串左边的指定字符去掉:strings.TrimLeft(”!Go!“, “!”)
19.将字符串右边的指定字符去掉:strings.TrimRight(”!Go!", “!”)
20.判断字符串是否以指定的字符串开头:strings.HasPrefix(“https://www:baidu.com”, “https:”)
21.判断字符串是否以指定的字符串结束:strings.HasSuffix(“https://www:baidu.com”, “.com”)
19.时间和日期相关的函数
时间和日期相关函数,需要导入time包
1.time.Time类型,表示时间()
package main
import (
"fmt"
"time"
)
func main() {
a := time.Now()
fmt.Printf("a=%v,类型为%T", a, a)
}
输出:a=2023-11-11 20:50:37.9225572 +0800 CST m=+0.019005301,类型为time.Time
2.获取当前时间方法:time.Now()
3.如何获取其他日期信息
now := time.Now()
fmt.Printf("now=%v,类型为%T\n", now, now)
fmt.Printf("年=%v\n", now.Year())
fmt.Printf("月=%v\n", now.Month()) //英文输出
fmt.Printf("月=%v\n", int(now.Month())) //数字输出
fmt.Printf("日=%v\n", now.Day())
fmt.Printf("时=%v\n", now.Hour())
fmt.Printf("分=%v\n", now.Minute())
fmt.Printf("秒=%v\n", now.Second())
输出:
年=2023
月=November
月=11
日=11
时=21
分=10
秒=18
4.格式化日期和时间
1)格式化第一种方法:
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf("当前年月日为:%02d-%02d-%02d %02d:%02d:%02d\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second(),
)
now2 := fmt.Sprintf("当前年月日为:%02d-%02d-%02d %02d:%02d:%02d\n",
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second(),
)
fmt.Println("now2为", now2)
}
输出:
当前年月日为:2023-11-11 21:22:35
now2为 当前年月日为:2023-11-11 21:22:35
2)格式化第二种方法:使用 time.Format()方法完成
"2006/01/02 15:04:05"各个字符串的数字是固定的,必须这样写
各个数字可以自由组合,按实际需求返回时间日期
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Printf(now.Format("2006/01/02 15:04:05"))
fmt.Println(" ")
fmt.Printf(now.Format("2006-01-02"))
fmt.Println(" ")
fmt.Printf(now.Format("15:04:05"))
fmt.Println(" ")
}
输出:
2023/11/11 21:43:24
2023-11-11
21:43:24
时间的常量
const(
Nanosecond Duration=1 纳秒
Microsecond=1000* Nanosecond Duration 微秒
Millisecond=1000Microsecond 微秒
Second=1000 Millisecond毫秒
Minute=60* Second 分钟
Hour=60* Minute小时
)
每隔0.1秒输出一个数字,打印到100时退出
func main() {
i := 0
for {
i++
fmt.Println(i)
time.Sleep(time.Millisecond * 100)
if i == 100 {
break
}
}
}
获取当前unix时间戳和unixnano时间戳。(作用是可以获取随机数字)
使用方法:
func main() {
now := time.Now()
unix := now.Unix()
fmt.Println(unix)
}
Golang内置函数
函数能被直接使用,称为内置函数。
1.len:用来求长度,比如string,array,slice,map,channel
2.new:用来分配内存,主要用来分配值类型,比如int,float,struct
3.make:用来分配内存,主要用来分配引用类型,比如chan,map,slice
Golang的错误机制
1.在默认情况下,程序发生错误,程序会自动退出(崩溃)
2.如果我们希望发生错误后,可以捕捉到错误,并进行处理,保证程序可以继续执行,还可以在捕捉到错误后,给管理员提示(邮件或者短信)
3.这里引出错误处理机制
基本说明
golang不支持try,catch,finally这种处理
golang中引入的处理方式,defer,panic,recover
使用defer+recover处理:
package main
import (
"fmt"
)
func test() {
defer func() {
err := recover() //内置函数,可以捕捉到错误信息
if err != nil { //说明捕捉到错误
fmt.Println("err=", err)
}
}()
num1 := 20
num2 := 0
num3 := num1 / num2
fmt.Println(num3)
}
func main() {
test()
fmt.Println("继续运行")
}
自定义错误
Go程序中,也支持自定义错误,使用errors.New和panic内置函数
1.errors.New(错误说明),会返回error类型的值,表示一个错误
2.panic内置函数,接收一个interface类型的值(任何值)作为参数,接收error类型的变量,输出错误信息,并退出程序
案例示范:
package main
import (
"errors"
"fmt"
)
//函数去读取配置文件init.conf信息
//如果文件传入不正确,我就返回自定义错误信息
func readConf(name string) (err error) {
if name == "config.ini" {
return nil //没有错误
} else {
return errors.New("读取文件错误")
}
}
func test2() {
// err := readConf("ggg")//----------------这里影响输出结果
err := readConf("config.ini")//----------------这里影响输出结果
if err != nil {
panic(err)
//如果读取文件发生错误,就输出这个错误,并且终止程序
}
fmt.Println("test2继续执行")
}
func main() {
test2()
fmt.Println("下面代码继续运行")
}
练习题在:【尚硅谷】Golang入门到实战教程丨一套精通GO语言 P141
20.数组和切片
数组的定义:var a [3]int
数组的第一个元素是数组的首地址,第二个元素的地址是第一个元素地址+int
func main() {
var a [3]int
fmt.Println(a)
fmt.Printf("a的地址是%p\n", &a)
fmt.Printf("a第一个元素地址是%p\n", &a[0])
fmt.Printf("a第二个元素地址是%p\n", &a[1])
}
输出:
[0 0 0]
a的地址是0xc00011e000
a第一个元素地址是0xc00011e000
a第二个元素地址是0xc00011e008
例题终端循环输入5个成绩,计算总和并输出各个成绩。
func main() {
var score [5]float64
for i := 0; i < len(score); i++ {
fmt.Printf("请输入第%d个成绩", i+1)
fmt.Scanln(&score[i])
}
TotalScore := 0.0
for i := 0; i < len(score); i++ {
TotalScore += score[i]
fmt.Printf("第%d个成绩为%v\n", i+1, score[i])
}
fmt.Printf("总成绩为%v\n", TotalScore)
}
四种初始化数组的方式
func main() {
var score [3]int = [3]int{1, 6, 9}
fmt.Println("score的值为", score)
var score1 = [3]int{2, 4, 6}
fmt.Println("score1的值为", score1)
var score2 = [...]int{3, 5, 7, 10}
fmt.Println("score2的值为", score2)
//指定元素对应的下标
var score3 = [3]string{1: "hi", 0: "hello", 2: "ok"}
fmt.Println("score3的值为", score3)
}
输出:
score的值为 [1 6 9]
score1的值为 [2 4 6]
score2的值为 [3 5 7 10]
score3的值为 [hello hi ok]
数组的遍历
for-range结构遍历
1.第一个index是数组的下标
2.第二个value是在该下标位置的值
3.他们都是仅在for循环内部可见的局部变量
4.遍历数组元素的时候,如果不行使用下标index,可以直接把下标index标为下划线_
5.index和value的名称不是固定的,即可自命名
func main() {
var a = [...]int{1, 3, 5}
for index, value := range a {
fmt.Printf("index=%v,vaule=%v\n", index, value)
}
var b = [...]int{2, 4, 6}
for _, value := range b {
fmt.Printf("vaule=%v\n", value)
}
}
输出:
index=0,vaule=1
index=1,vaule=3
index=2,vaule=5
vaule=2
vaule=4
vaule=6
数组注意事项和细节
1.数组是多个相同类型的数据组合,一个数组一旦声明了,其长度是固定的,不能动态变化
2.var arr []list,这时arr就是slice切片
3.数组中的元素可以是任何数据类型,包括值和引用类型。但不能混用
4.数组创建后,如果没有赋值,等于默认值(零值)
5.使用数组的步骤,1.声明数组并开辟空间2.给数组各个元素赋值3.使用数组
6.数组下标从0开始
7.数组下标必须在指定范围内使用,否则报panic
8.Go数组类型,在默认情况下是值传递。因此会进行值copy。数组间不会相互影响
9.若想在其他函数中,修改原来的值,可以进行引用传递(指针方式)
例题:
1.比较数组里面元素的大小
func main() {
var a = [...]int{1, 3, 5}
var max = a[0]
for i := 0; i < len(a); i++ {
if max < a[i] {
max = a[i]
}
}
fmt.Printf("max=%v", max)
}
2.用range数组和的平均值
func main() {
var a = [...]int{1, 3, 5}
sum := 0
for _, value := range a {
sum += value
}
fmt.Printf("sum=%v\n", sum)
avg := sum / len(a)
fmt.Printf("avg=%v", avg)
}
输出:
sum=9
avg=3
切片的基本介绍
1.切片英文叫slice
2.切片是数组一个引用,因此切片是引用类型。在进行传递时,遵守引用的传递机制
3.切片的使用和数组类似,遍历切片,访问切片的元素和求切片长度也一样。
4.切片长度是可以变化的,因此切片是一个可以动态变化数组
5.切片定义的基本语法
var 变量名[] 类型,比如var a[] int
切片的使用
package main
import (
"fmt"
)
func main() {
var a [5]int = [...]int{1, 3, 5, 7, 9}
slice := a[1:3]
fmt.Println("a元素是", a)
fmt.Println("slice元素是", slice)
fmt.Println("slice长度是", len(slice))
fmt.Println("slice容量是", cap(slice))
}
输出:
a元素是 [1 3 5 7 9]
slice元素是 [3 5]
slice长度是 2
slice容量是 4