Go!第一周笔记
浏览大佬李文周的博客初步了解go语言,从B站尚硅谷韩顺平老师的视频开始学习,安装go的SDK过程中发现1.20版本与视频演示的1.9版本有以下区别:安装完成得到的bin文件夹中没有godoc.exe;无需配置环境,大大便捷了新手操作过程
第一段go代码:
package main//该文件所在包为main
import "fmt"//引入fmt包
func main() {
fmt.Println("Hello,world!")//func为关键字,表示函数;main为主函数
}
通过go build命令对该go文件编译,生成.exe文件
C:\Users\zyf>cd G:\GoProject\src\go_code\project01\main
C:\Users\zyf>G:
G:\GoProject\src\go_code\project01\main>dir
驱动器 G 中的卷是 环境
卷的序列号是 B0EF-3A2F
G:\GoProject\src\go_code\project01\main 的目录
2023/02/28 19:20 <DIR> .
2023/02/28 19:17 <DIR> ..
2023/02/28 19:20 0 hello.go
1 个文件 0 字节
2 个目录 549,609,558,016 可用字节
G:\GoProject\src\go_code\project01\main>go build hello.go
G:\GoProject\src\go_code\project01\main>dir
驱动器 G 中的卷是 环境
卷的序列号是 B0EF-3A2F
G:\GoProject\src\go_code\project01\main 的目录
2023/02/28 22:14 <DIR> .
2023/02/28 19:17 <DIR> ..
2023/02/28 22:14 1,927,680 hello.exe//所得hello.exe
2023/02/28 22:13 80 hello.go
2 个文件 1,927,760 字节
2 个目录 549,607,628,800 可用字节
若得到
G:\GoProject\src\go_code\project01\main>go build hello.go
hello.go:1:1: expected 'package', found 'EOF'
则是需要现在vscode里保存(ctrl+s)
go run 可以直接运行!!但是速度不如运行hello.exe
G:\GoProject\src\go_code\project01\main>go run hello.go
Hello,world!
GoLANG执行流程分析
Hello.go经过go build编译,形成二进制可执行文件hello.exe可直接运行,无需golang的SDK;若直接go run hello.go,编译和运行一步进行,速度慢,需要SDK
在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,故可执行文件变大很多
另外,编译时可指定所生成的可执行文件名,如:
G:\GoProject\src\go_code\project01\main>go build -o myhello.exe hello.go
则所得到的可执行文件名为myhello.exe//但在windows中所写文件名后缀必须为exe
另外,若所写程序有错,将会在错误行报错
与视频中旧版VScode不同,新版设置与pycharm类似,新手好上手
语法要求和注意事项!
- 程序后缀必须是.go
- 区分大小写
- Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号)
- Go编译器是一行行进行编译的,因此一行就写一条语句,不能把多条语句写在同一行,否则报错
- go语言定义的变量或者import的包如果没有使用到,代码不能编译通过。
- 大括号都是成对出现的,缺一不可。
转义字符
\n换行 \t制表 \r回车
与视频教程不同,新版本\r不再会用\r后的字符覆盖前面,改为单纯回车
应用实例:
要得
代码:
package main
import "fmt"
func main() {
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
}
这里新版本不需要重新建一个文件夹以解决一个包中重复的package main导致的报错
注释
行注释(//)块注释(/* */)和C语言一样,快捷键框中需要注释的内容ctrl+/,但注意块注释不能嵌套(/*/* */*/是不允许的)
缩进
Tab整体右移一个缩进Shift tab整体左移一个缩进
或者使用go format对代码格式化
如:
package main
import "fmt"
func main() {
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
}
得到:
G:\GoProject\src\go_code\project01\main>gofmt demo.go
package main
import "fmt"
func main() {
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
fmt.Println("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京")
}//cmd中输出的对齐了,但没有改变原程序格式
所以需要gofmt -w demo.go以改变原程序
//其实现在版本的vscode保存的时候会自动排版,不需要视频中那么复杂了
对C语言,
func main(){
}
和
func main()
{
}
两种格式都可以,但对go语言,仅前者正确
另外,约定成俗地,一行代码不宜过长
Golang标准库API文档
Golang中文网 在线标准库文档:Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国
也可以直接再所下载的go文件夹中查看src(源码包)下看具体源代码
调用函数时需要先import包名,然后 包名.函数名 调用
Doss指令
查看目录C:\Users\zyf>dir
切换到其他盘C:\Users\zyf>cd /d G: 切换到G盘
切换到当前盘的其他目录下(使用相对路径和绝对路径演示)
区别:对C:>Users>zyf,目前所在 Users,要转到zyf,绝对路径为cd C:\Users\zyf,相对路径为cd zyf
回到上一级目录cd ..
回到根目录 cd \
创建新的目录 md 目录名(可同时创建多个目录)
删除空目录 rd 目录名
删除目录及其下子目录和文件且不询问 rd /q/s 目录名
删除目录及其下子目录和文件且询问 rd /s 目录名
文件的操作
创建文件:
echo hello>G:\GoProject\abc.txt
即在goproject目录下创建内容为hello的txt文本
复制文件:
copy abc.txt>G:\Go\efg.txt
即将abc.txt以 efg.txt的名称复制到G:\Go
移动文件:move abc.txt
删除指定文件:del abc.txt
删除所有文件:del *.txt即删除所有txt文件
清屏:cls
退出:exit
小结
两天之内看了前三十节视频,尚硅谷的教程确实非常细致,毫无编程经验的小白都能看懂,但是由于是19年的视频了,版本更新中出现了诸多不同,老师提到的一些需要注意的细节
现在已经不需要拘泥了,更加人性化,可见go语言在短短3年中也有着日新月异的变化。对于之前有一些python学习经验的笔者来说,正常速度已经有些慢了,2倍速刚好。希望自己能坚持下去,看完388节基本语法和二十多个小时的goWeb教程,从这周开始每周打卡!
变量
- 声明(定义)变量//不同于python,必须声明变量类型
- 非变量赋值
- 使用变量
变量定义的简单案例:
func main() {
var i int = 1
fmt.Println("i=", i)
}
1.在go语言中,变量的默认值为0
func main() {
var i int
fmt.Println("i=", i)
}
得到i=0
2.在go语言中,可以自动进行变量的类型推导,如:
Var i = 10 则自动认为i的类型为int
3.可以省略var, var I = 10效果等同于I := 10
但是 :=左侧的变量不能是已经声明过类型的,否则导致编译出错
- golang中支持多变量声明
- var a,b,c int则声明a,b,c 都是int
- var a,b,c = 10,”tom”,5.5
- a,b,c := 10,”tom”,5.5
以上三种方式都正确,第二种基本等同于第三种
另外还可以
var (
a = 1
b = 2
c = "mary"
)
变量在该区域的数值可以在同一类型范围内不断变化
变量在同一作用域(暂时理解为在一个函数中或者在代码块)内不能重名
加号的使用
若加号两边都是数据类型,则求和;若两边都是字符,则拼接
整数类型
- 有符号类型(用一位来表示符号)int
- 无符号类型(默认为正,所有位都用来表示数)uint
- Rune等价于int32,但是对于中文有特效
- byte在储存单个字符时选用,等价于uint8
查看变量的类型
func main() {
var a = "mary"
fmt.Printf("a 的类型是 %T", a)
}
得到:a 的类型是 string
查看占用字节(需要引用unsafe包)
func main() {
var a = "mary"
fmt.Printf("a 的类型是 %T ,占用字节为 %d", a, unsafe.Sizeof(a))
}
Go语言在使用整型变量时遵从保小不保大的原则,如描述一个人的年龄255足够了,无需使用int64这种极大的,byte比较合适
补充常识:bit是计算机最小的存储单位,byte是最小的存储单元。1 byte=8 bit
浮点类型
- 浮点数的存放方式为:符号位(说明浮点数都有符号)+指数位+尾数位
- 尾数位可能丢失,造成精度损失 双精度的损失更小
3.Golang中的浮点类型有固定范围和长度,不受os(操作系统)影响
4.Golang浮点型默认声明位float64
5.浮点数常量的两种表现形式:(1)十进制数形式 如:5.12 .532(等同于0.532,但是小数点一定不能省略)(2)科学计数法 如:5.1284e3(等同于5128.4),其中e大小写都可以,5.1284e3即5.1283*10**3
6.一般情况下推荐使用float64,精度更高
字符类型
Golang中没有专门的字符类型,储存单个字符(字母)用byte保存
不同于传统用字符组成字符串,go使用字节来组成字符串
这也导致了我们直接输出byte值时其实输出的是字符所对应的码值,如:
func main() {
var a byte = 'b'
fmt.Println("a=", a)
}
得到的是a=98
如果我们需要的是对应字符,则需要用格式化输出
func main() {
var a byte = 'b'
fmt.Printf("a=%c", a)
}
得到 a=b
而对于中文字符,对应的码值都超过了255,故不能用byte保存,此时就体现出rune对中文的特效了,用其他范围大于255的类型保存也可以
- 字符常量是单引号括起来的单个字符
- Go语言中存在转义字符(前面已经写到)
- Go语言使用utf-8编码规则
- 在go中,字符的本质是一个整数(码值)
- 也可以反过来给某变量赋一个数字,然后格式化输出会输出其对应的unicode字符
func main() {
var a rune = 35418
fmt.Printf("a=%c", a)
}
得到a=詚
- 在Go中,字符也可以运算,相当于其对应的整数运算
反思:go语言使用统一的utf-8编码,很方便,避免出现乱码
每个字符对应唯一码值,进而对应二进制数
布尔类型
- 只能取true和false(不同于python True=1,Faulse=0),不能用0或非0整数替代
- Bool类型占用一个字节
- 适用于逻辑运算,一般用于流程控制
字符串类型
- 字符串一旦赋值,就不能修改,go中字符串不可变,但是可以重新赋值
func main() {
var str = "hello"
str[0] = 's'//典型错误,与python不同!
fmt.Println(str)
}
- 字符串的两种表现形式(1)双引号会识别转义字符*(2)反引号(“)内以字符串原生形式输出,不识别换行和特殊字符,可以防止攻击、输出源代码
- 字符串的拼接:当加号两边都是字符串时,go认为进行拼接操作
当一个拼接操作很长时,可以分行写:
func main() {
var str = "hello" + "," +
"world" + "!"
fmt.Println(str)
}
然而加号必须在上一行末尾而不能在下一行开头(go默认在每一行末尾加;)
Golang基本数据默认值
整型:0 浮点型:0 字符串:“” 布尔类型:false
Go语言中,%(占位符的使用)
常用:十进制整数(%d)布尔值(%t)值的默认格式(%v)相应类型(%T)有小数无指数(%f)字符(%c)单引号括起来的go语法字符字面值,必要时会采用安全的转义表示(%q)
基本数据类型转换
Go语言与java/C不同,在不同的变量赋值需要显式转换,也就是说不能自动转换
func main() {
var a int = 10
var b float64 = float64(a)
fmt.Printf("%T", b)
}
1.转换方式不能直接var a float64 = float64(a),这样就违背了“字符串一旦赋值就不能修改”,故需要向上面的代码一样引入新的变量
2.另外,不同于一些语言在低精度转高精度时可以直接(var b float64 = a),GO中无论低精度(如float32)转高精度(如float64),还是高精度转低精度,都需要严格按照上面代码所展示的格式
3.被转换的是变量储存的数据(即值),变量本身的数据类型没有发生变化
4.转换过程中要注意转变成的数据类型要能容纳下,如果超出范围,编译器不会报错,只会按溢出处理
案例题:
判断:
func main() {
var a int32 = 10
var b int64
var c float32
b = a + 20
c = a + 20
}
是否能够编译? 答案:不能,因为a + 20的类型是与a一样为int32,等式两边数据类型不同 解决方法:改为 b = int64(a) +20
案例2:
func main() {
var a int32 = 12
var b int8
var c int8
b = int8(a) + 127 //能通过编译,但是int(a)+127和溢出int8
c = int8(a) + 128 //不能通过编译,因为128已经超出int8范围
fmt.Println(b, c)
}
基本数据类型和string的转换
方法一:使用 fmt.sprintf()
案例:
func main() {
var a int = 99
var b float64 = 38.7325
var c bool = true
var d byte = 'h'
var e string
e = fmt.Sprintf("%d", a)
fmt.Printf("str type %T str=%q\n", e, e)
e = fmt.Sprintf("%f", b)
fmt.Printf("str type %T str=%q\n", e, e)
e = fmt.Sprintf("%t", c)
fmt.Printf("str type %T str=%q\n", e, e)
e = fmt.Sprintf("%c", d)
fmt.Printf("str type %T str=%q\n", e, e)
}
输出得到:
str type string str="99"
str type string str="38.732500"
str type string str="true"
str type string str="h"
可见通过这种方法可以将其他类型转为字符串类型
方法二:使用strconv包函数
func main() {
var a int = 99
var b float64 = 38.7325
var c bool = true
var e string
e = strconv.FormatInt(int64(a), 10)
fmt.Printf("str type %T str=%q\n", e, e)
e = strconv.FormatFloat(b, 'f', 10, 64)
fmt.Printf("str type %T str=%q\n", e, e)
e = strconv.FormatBool(c)
fmt.Printf("str type %T str=%q\n", e, e)
}
得到:
str type string str="99"
str type string str="38.7325000000"
str type string str="true"
其中,strconv.FormatInt(转换对象,转换成的进制)
strconv.FormatFloat( 转换对象,转换格式(‘f’为最常见的),保留小数位数,原数据类型(例子中表示float64))
strconv.FormatBool(仅转换对象)
另外该包中有Intoa函数,可以实现int转string,但是不能是int64等类型!
这个函数只有转换对象这一个参数,就不做案例了
String转基本数据
func main() {
var a string = "1314"
var a1 int64
var a2 int
a1, _ = strconv.ParseInt(a, 10, 64)
a2 = int(a1)
fmt.Printf("a2 type %T,a2 %v\n", a2, a2)
var b string = "13.14"
var b1 float64
b1, _ = strconv.ParseFloat(b, 64)
fmt.Printf("b1 type %T,b1 %v\n", b1, b1)
var c string = "true"
var c1 bool
c1, _ = strconv.ParseBool(c)
fmt.Printf("c1 type %T,c1 %v\n", c1, c1)
}
得到
a2 type int,a2 1314
b1 type float64,b1 13.14
c1 type bool,c1 true
注意:
- parse系列函数都会得到两个值,第二个是是否超出范围出错,我们不需要就直接用”_”占位忽略。
- ParseFloat有两个参数,对象和转换成的数据类型
- ParseInt 有三个参数,对象,进制和转换成的数据类型
- PaeseBool只有对象这一个参数
注意事项
在将string类型转化成基本数据类型时,要确保string类型能够转化成有效的数据,如果将“1314“转化成bool类型,则Go将其转化为默认值faulse
- string双引号中内容与要转化成的类型对应
- 在转化数据时注意不要超出类型的范围
指针
func main() {
var a int = 1314
fmt.Printf("a 的地址是%v\n", &a)
var b *int = &a
fmt.Printf("b = %v", b)
}
得到
a 的地址是0xc0000180a8
b = 0xc0000180a8
- 基本数据类型变量存的就是值,也叫值类型
- 获取变量的地址用&,如上
- 指针是一个类型,这个类型存的是一个地址,这个地址指向一个存值的空间,而存地址的空间也有一个地址(套娃)
fmt.Printf("b所储存的地址对应的值是%v", *b)
得到:
b所储存的地址对应的值是1314
- 由上,获取指针类型所指向的值,使用:*
通过指针改变变量的值:
func main() {
var a string = "hello"
var b *string = &a
*b = "world"
fmt.Printf("a的值现在是%v", a)
}
得到
a的值现在是world
说明通过指针成功改变了变量a的值,但是a的地址没有改变
关于指针的细节:
- 所有值类型都有其对应的指针类型,形式为 *值类型
- 值类型包括之前提到的基本数据类型(int,float,bool,string),数组,结构体struct;
引用类型包括指针,切片(slice),map,管道(chan),接口(interface)
值类型和引用类型
- 值类型:变量直接存储值,内存通常在栈中分配
- 引用类型:变量储存一个地址地址对应的空间才是真正存储的数据(值),内存通常在堆中分配
标识符
- 概念
- Go语言对各种变量、方法、函数等命名时使用的字符序列成为标识符
- 凡是自己可以取名的地方都叫标识符
- 命名规则
- 有26字母大小写,0-9数字,_组成
- 数字不能开头
- 严格区分大小写
- 不能包含空格
- 对于下划线“_“,其本身在go中是一个特殊的标识符,称为空标识符,可以代表其他任何标识符,但其对应值会被忽略。故仅能占位,不嫩那个作为一般标识符使用
- 不能以系统保留关键字作为标识符
- 命名注意事项
- 包名:尽量保持包名与该文件所在文件夹一致,采取有意义的包名,简短且不与标准库冲突
- 变量名,函数名,常量名:采用驼峰法(stuName,goProject1)
- 若变量名、函数名、常量名首字母大写,则可以被其他的包访问,若首字母小写,则只能在本包使用(大写公用,小写私用)
系统保留关键字
问题:在导入自定义包的过程中,新版需要使用go mod
预定义标识符(包括数据类型和内嵌函数)
运算符
- 算术运算符(+-*/)
需要注意的:%取余,++自加(在原数基础上加一),–自减
- 赋值运算符(= += -= *=)
- 比较运算符(== != > < >= <=)
- 逻辑运算符(与或非)
- 位运算符(与二进制相关)
- 其他运算符
声明:学习资源大部分来自尚硅谷韩顺平老师的视频,侵删;笔者是编程小白,如有错误还请各位大佬指正。