Go语言快速入门学习
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。并且在现在的互联网公司中,Go具有越来越广泛的应用。
以下是我个人根据自己的入门规划进行的笔记,且目前会参考菜鸟编程和Go语言圣经中文版网站作为快速入门。在此以下内容如有错误或者自己理解不到位的地方还请围观的朋友批评指正,感激不尽。
下面给出我的两个学习网站:
菜鸟编程go语言入门:官网
Go语言圣经中文版:Go语言圣经
1、Go语言简介
1.1 Go语言发展及其特点
谈及Go语言,就要想到一件事情,就是发明C语言和发明Go语言都会出现Ken Thomson这个大佬的影子。在解决UNIX系统移植性问题发明的C语言,而传奇大佬的团队同样的在为了解决21世纪多核和网络化环境下愈发复杂的编程问题而发明了Go语言。
Go语言是来自Google公司的大牛于2007年开始设计和实现,并于2009年的11月对外正式发布。它拥有着和C语言类似的语法外表,并且借鉴了很多其他的好的地方加以运用,其特点总结如下:
- 简洁快速、安全
- 并行、有趣、开源
- 支持数据抽象、面向对象编程
- 集成GC垃圾回收
- 内存管理、编译迅速
- 最受欢迎的无类型脚本语言的替代者
1.2 Go安装
结合自己电脑运行情况,这里暂时介绍windows下的安装
- 使用地址:https://golang.google.cn/dl/
- 下载msi后缀,下载完后可以在浏览器列表找到该文件。
- 根据自己的需要更改下载地址,点击安装。
- 配置环境。选择好自己的安装目录,我自己安装完并打开环境配置后,发现在系统目录它已经自动帮我配置好go下的bin目录了,打开cmd输入
>go
也能正常显示。
1.3 Go语言的结构
在菜鸟编程中讲了以下几个结构:
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
1.4 第一个程序
1.首先先写入以下代码
package main //定义包名
import "fmt" //告诉编译器这个程序使用fmt包(函数或者其他元素)
func main(){ //程序的开始
fmt.Println("hello world!")
}
2.编译运行&运行结果
C:\Users\86188\Desktop>go run hello.go
Hello world!
2、Go语言入门
首先对于计算机体系结构而言,存储器、运算器、控制器和输入输出设备都是其重要的结构。这里程序首先应该获取外部输入,即通过用户输入、文件、其他程序的输出等方式让计算机获取输入源。
2.1 转义字符
- \t:表示一个制表符,通常用于排班对齐。
- \n:换行符
- \:一个\
- “:一个”
- \r:一个回车
2.2 注释
- 行注释:
// 基本内容
- 块注释:
/*基本内容*/
Go语言推荐使用行注释来注释整个方法和语句。
2.3 Go语言编码风格
1.首先是大括号的放置
//错误
func main()
{
fmt.Print("hello,world!") //shirt+alt+上下箭头--->向上向下复制
}
//正确
func main() {
fmt.Print("hello,world!") //shirt+alt+上下箭头--->向上向下复制
}
2.其次是一行所敲打的字符,不可超过80个字符。并且如果超过就要使用换行展示,以保持格式的优雅性。
2.4 后续的语言标准库
很多的go语言都可以从go语言标准库中进行查找并且使用,可以点击以下网址进入即可:
https://studygolang.com/pkgdoc
3、Go变量
变量就是内存中对数据存储空间的表示,对于我们而言不必关心其物理存储,但是我们需要能够灵活使用。
当然变量分为声明、非变量赋值和使用变量。
比如我可以输入以下内容:
var i int
i = 10
fmt.Print(i)
此时在内存中,就应该有关于i的变量存储,这应该相当于学习Java虚拟机中的方法区,即存储变量等信息。这个存储里面,包括了数值和类型两者。
变量的赋值方法
- 指定变量类型,声明后不赋值就采用默认值
- 根据值来判定该变量的类型
- 使用:=进行复制,前提是:=左侧变量名未使用
一次性多声明变量
var n1 = 100
var n2 = 200
var name = "jack"
var (
n3 = 300
n4 = 400
name1 = "matt"
)
fmt.Print("字符为:", n1, n2, n3, n4, " ", name, " ", name1)
输出如下:字符为:100 200 300 400 jack matt
变量转换
变量在同一个作用域内不可以重名,比如遇到需要类型转换的地方,就很可能报错。
加减乘除作为运算的符号,在这里进行省略,不过一般的类型转换,都是以当前所在的类型中为主。比如int+int=int
。
4、数据类型介绍
数据类型包括基本数据类型和派生复杂的数据类型。
4.1 int类型
int有符号类型
int类型在go语言分成int8\int16\int32\int64,这里的区别在于就是用多少的比特来表示一个int类型数值。
类型 | 有无符号 | 占用存储空间 | 表述的范围 | 其他 |
---|---|---|---|---|
int8 | 有 | 1字节 | -27~27-1 | |
int16 | 有 | 2字节 | -215~215-1 | |
int32 | 有 | 4字节 | -231~231-1 | |
int64 | 有 | 8字节 | -263~263-1 |
也就是说,我们在编程的时候需要注意根据自己的赋值内容的大小选择好类型,不然使用太小的类型无法表达你所需要赋值的内容,就会出现编译的错误。
int无符号类型
类型 | 有无符号 | 占用存储空间 | 表述的范围 | 其他 |
---|---|---|---|---|
uint8 | 无 | 1字节 | 0~2^8-1 | |
uint16 | 无 | 2字节 | 0~2^16-1 | |
uint32 | 无 | 4字节 | 0~2^32-1 | |
uint64 | 无 | 8字节 | 0~2^64-1 |
当然,如果在32位计算机下应该是对应4字节部分;如果是64位,那应该是对应8字节部分。
在使用Go语言程序的整型变量中,我们尽量使用较小的空间来存储一些具有一定含义的变量。比如我们就拿byte来表示一个人的年龄。
4.2 float类型
float分为单精度和双精度。当然对应起来就是float32和float64两者了。
关于浮点数,掌握其使用应该是能够完全够的。比如如下:
var num float32=-0.00089 //符号位加上指数位加上尾数位
选取类型时,我们要注意如果一个程序需要保持其数据精度,用64位数即可。
表示浮点数的方法(科学计数法):
num := 5.012345e6
num1 := 5.012345e6
num2 := 5.012345e-2
fmt.Print("结果为:", num, num1, num2)
结果为:
结果为:5.012345e+06 5.012345e+06 0.05012345
4.3 字符类型
存储单个字符——byte表示
存储一串固定长度的字符——字符类型
字符类型使用中,需要注意部分转义字符的使用。比如说换行使用\n,表示路径要输:\\
以表示一个\
。
Go语言使用utf8编码,所以可以参考utf8编码表即可。
4.4 布尔类型
布尔类型也叫作bool类型,它只允许True和False取值。
其中bool类型占用一个字节,适用于逻辑运算。
4.5 String类型
字符串就是一串固定长度的字符连接起来的字符序列,同样Go语言的字符串字节使用utf8编码标识Unicode。
这样子,其实我们使用utf8编码格式,中文乱码问题也就可以迎刃而解了。
var str = "hello"
str[0] = 'H'
fmt.Printf(str)
运行如下:
src\go_code\LearningHello\main\hello.go:7:9: cannot assign to str[0] (strings are immutable)
意思就是不能够更改其内容,字符串是不可变的。
String表示:
- 双引号
- 反引号
拼接字符:+
4.6 数据类型转换
在go语言中,数据类型的转换是从小到大,也可以是从大到小。被转换的是变量存储的数据,变量本身的数据类型没有发生变化。
func main() {
var i int32 = 100
var n1 float32 = float32(i)
var n2 int8 = int8(i)
var n3 int64 = int64(i)
fmt.Printf("i=%v n1=%v n2=%v n3=%v \n", i, n1, n2, n3)
fmt.Printf("i type is %T\n", i)
}
[Running] go run "d:\GoWorkplace\src\go_code\LearningHello\main\hello.go"
i=100 n1=100 n2=100 n3=100
i type is int32
4.7 基本数据类型和string类型转换
方式一:使用sprint函数(更便捷)
var num1 int = 100
var str string
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q\n", str, str)
//结果
str type string str="100"
方式二:使用strconv包函数
4.8 指针
基本的数据类型在于变量存的是值,也叫值类型。
其中,获取变量的地址就要使用&。
而指针类型在于存的是一个地址类型。因为我们说过一个变量存储包括变量类型和变量的内容,也就是我们对指针类型赋值就是要对所需要指向的变量的地址的一种指向,指向了地址才能叫指针嘛。
比如如下例子:
var ptr *int = &num
//注意,这里不考虑类型不匹配。如果出现类型不匹配会发生错误的。
4.9 引用类型
引用类型在于变量存储的是一个地址,这个地址对应的空间才是真正的存储数据。内存通常在堆上分配,那么此时如果没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,就由GC回收。
5、语言运算符
Go 语言内置的运算符有:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
5.1 算术运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
– | 自减 | A-- 输出结果 9 |
5.2 关系运算符
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
5.3 逻辑运算符
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
5.4 位运算符
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
5.5 赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C *= A 等于 C = C * A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
<<= | 左移后赋值 | C <<= 2 等于 C = C << 2 |
>>= | 右移后赋值 | C >>= 2 等于 C = C >> 2 |
&= | 按位与后赋值 | C &= 2 等于 C = C & 2 |
^= | 按位异或后赋值 | C ^= 2 等于 C = C ^ 2 |
|= | 按位或后赋值 | C |= 2 等于 C = C | 2 |
5.6 其他运算符
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
5.7 运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 | 运算符 |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
6、Go语言循环条件语句
6.1 循环
循环语句
Go 语言提供了以下几种类型循环处理语句:
循环类型 | 描述 |
---|---|
for 循环 | 重复执行语句块 |
循环嵌套 | 在 for 循环中嵌套一个或多个 for 循环 |
循环控制语句
循环控制语句可以控制循环体内语句的执行过程。
GO 语言支持以下几种循环控制语句:
控制语句 | 描述 |
---|---|
break 语句 | 经常用于中断当前 for 循环或跳出 switch 语句 |
continue 语句 | 跳过当前循环的剩余语句,然后继续进行下一轮循环。 |
goto 语句 | 将控制转移到被标记的语句。 |
for i := 0; i < 10; i++ {
fmt.Println("我爱学Go语言")
}
/*结果:
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
我爱学Go语言
*/
6.2 条件语句
Go 语言提供了以下几种条件判断语句:
语句 | 描述 |
---|---|
if 语句 | if 语句 由一个布尔表达式后紧跟一个或多个语句组成。 |
if…else 语句 | if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。 |
if 嵌套语句 | 你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。 |
switch 语句 | switch 语句用于基于不同条件执行不同动作。 |
select 语句 | select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。 |
在使用go语言进行if判断时,不需要打上括号。
7、函数&包
7.1 函数的语法
func 函数名(形参列表)(返回值列表){
执行语句...
return 返回值列表
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 定义局部变量 */
var result int
if num1 > num2 {
result = num1
} else {
result = num2
}
return result
}
7.2 引入包
- 引入方式1:import “包名”
- 引入方式2:import(“包名” “包名”)
注意:
在import时,路径从$GOPATH下的src开始查找,不带src的工作目录就自动从src下面开始引入。
然后就是其他包的文件可以访问本包内部函数,此时我们就要让函数名首字母大写。这时候就相当于是添加了public。
在访问其它函数或者变量时,语法为:包名.函数名
一个包下不能有一样的函数名
7.3 递归
函数递归需要遵守以下原则:
-
执行一个函数时,就创建一个新的受保护的独立空间
-
函数的局部变量是独立的不会相互影响
-
递归必须向退出递归的条件逼近,否则就会进入无限递归。
-
当一个函数执行完毕或者遇到return后,此时就遵守谁调用就返回给谁。当然调用了return就意味着函数本身被系统销毁。
8、数组
数组创建
Go语言提供很多数组类型的数据结构。首先我们来看声明一个数组:
//这里定义了一个数组arr长度为10并且类型为32
var arr [10] float32
初始化数组:
//声明一个数组的两种方式
var arr = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
arr :=[5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
可以看到,实际上我们只需要关心等号左边的初始化格式差异即可。
另外如果不确定数组长度,那么可以使用...
来进行代替:
var arr = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
这样子编译器就会根据元素个数来自行推断数组的长度了。
数组的使用
格式:数组名[下标]。比如arr[2]就是使用arr数组访问第三个元素。
数组遍历
这里着重讲解数组的遍历内容,我们可以使用以下的语法帮助我们进行遍历:
for index,value:=range array{
...
}
- index:即下标。如果不想标志下标可以用
_
表示。 - value:即下标的元素。
- 以上两个都为局部变量。
- range为关键字,而array就是目标遍历的数组。
9、结构体
结构体在我个人看来就是面向对象设计的非常有用的知识点。Go语言的结构体格式如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
首先就是type关键字要填入,其次定义一个结构体type类型,然后再填上struct。
比如如下内容:
type Class struct{
students int
teacher int
}
其次就是访问结构体:
var Class1 Class
Class1.student=52
Class1.teacher=8
结构体还可以作为参数,这一点也是比较容易理解的。而接下来结构体可以作为指针就稍微理解一下。
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */
/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407
/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700
/* 打印 Book1 信息 */
printBook(&Book1)
/* 打印 Book2 信息 */
printBook(&Book2)
}
func printBook(book *Books) {
fmt.Printf("Book title : %s\n", book.title)
fmt.Printf("Book author : %s\n", book.author)
fmt.Printf("Book subject : %s\n", book.subject)
fmt.Printf("Book book_id : %d\n", book.book_id)
}
这里的意思在于内存空间已经分配了Book1、Book2的两个空间进行存值。存值完毕过后通过指针指向其地址,即访问的数据类型和参数。
10、切片
先进入一个简单的程序:
func main() {
var intArr [6]int = [...]int{1, 22, 33, 44, 55, 66}
slice := intArr[0:4]
fmt.Println("intArr=", intArr)
fmt.Println("slice的元素是 =", slice)
fmt.Println("slice的元素个数 =", len(slice))
fmt.Println("slice的元素容量是 =", cap(slice))
}
结果输出如下:
intArr= [1 22 33 44 55 66]
slice的元素是 = [1 22 33 44]
slice的元素个数 = 4
slice的元素容量是 = 5
首先切片应该是左开右闭,就是左边能够取到下标值而右边不行。
其次就是个数也是根据切片的个数而输出。
容量在这里是可以动态变换的,跟数组的长度一样。
抛砖引玉,其底层实现也是一个结构体。
type slice struct{
ptr *[2]int
len int
cap int
}
大致的流程是其指向了切片的起始位置,并且根据len长度和cap来截取数组的数据。即slice是一个引用类型,底层实现就是运用了结构体。
整理运用:
package main
import "fmt"
func main() {
var num []int
printSlice(num)
/* 允许追加空切片 */
num = append(num, 0)
printSlice(num)
/* 向切片添加一个元素 */
num = append(num, 1)
printSlice(num)
/* 同时添加多个元素 */
num = append(num, 2, 3, 4)
printSlice(num)
/* 创建切片 numbers1 是之前切片的两倍容量*/
num1 := make([]int, len(num), (cap(num))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(num1, num)
printSlice(num1)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
/*
结果如下:
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
*/
11、map集合
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
定义map
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
使用map
var countryCapitalMap map[string]string /*创建集合 */
countryCapitalMap = make(map[string]string)
/* map插入key - value对,各个国家对应的首都 */
countryCapitalMap["France"] = "巴黎"
countryCapitalMap["Italy"] = "罗马"
countryCapitalMap["Japan"] = "东京"
countryCapitalMap["India "] = "新德里"
/*使用键输出地图值 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap[country])
}
/*查看元素在集合中是否存在 */
capital, ok := countryCapitalMap["American"] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(capital) */
/*fmt.Println(ok) */
if ok {
fmt.Println("American 的首都是", capital)
} else {
fmt.Println("American 的首都不存在")
}
整合键值对的创建,赋值并且判断是否存在该键值对。结果运行如下:
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India 首都是 新德里
American 的首都不存在
删除键值对:
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
fmt.Println("原始地图")
/* 打印地图 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
/*删除元素*/ delete(countryCapitalMap, "France")
fmt.Println("法国条目被删除")
fmt.Println("删除元素后地图")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
删除后的结果:
原始地图
India 首都是 New delhi
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
法国条目被删除
删除元素后地图
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi
12、Go语言接口
Go接口格式
Go接口跟Java语言的接口大概有一样的用法。其格式如下:
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
其中的实例如下:
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
/*
结果如下:
I am Nokia, I can call you!
I am iPhone, I can call you!
*/
13、Go错误处理
Go格式
type error interface {
Error() string
}
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}