// 单行注释
/*
多行注释
多行注释
多行注释
多行注释
*/
2、标识符
标识符,顾名思义就是在编程过程中,标识某种东西的符号。这个东西可以是Go语言本身的关键字、保留字,也可以是程序员自己定义的变量名、常量名、函数名、包名等。标识符由字母、数字和下划线组成,而且不能以数字开头。
// 合法标识符
user,user123,_user,_123,user_123
// 非法标识符
1user,1user123,1_user
3、关键字和保留字
关键字和保留字,是程序语言里定义的具有特殊意义的标识符。程序员编程的时候可以去使用,但是不能够去重新定义。比如去打车说去天安门,司机立马带你去了目的地。而不能说你家也叫天安门。这样的话就没法玩了。25个保留字和37个保留字。
名称 | 描述 | 名称 | 描述 |
---|---|---|---|
var | 变量定义关键字 | type | 自定义类型关键字 |
const | 定义常量关键字 | func | 定义函数和方法的关键字 |
package | 定义包名 | import | 导入包 |
if | 选择判断语句关键字 | else | 选择判断语句关键字 |
switch | 多重选择判断结构语句块关键字 | case | 用于switch和select语句块中,后跟一个表达式,表示一个待匹配项 |
fallthrough | switch语句块中使一个case子块执行完毕后,继续执行下一个case子块的代码 | default | switch和switch语句块中定义的默认项 |
for | 循环体语句块关键字 | continue | 跳过本轮循环,进入下一轮循环迭代 |
break | 强制退出循环或switch语句块 | return | 结束函数指定,使函数返回 |
chan | 用于定义channel的关键字 | struct | 结构体结构关键字 |
defer | 延迟调用关键字 | interface | 接口类型 |
go | 定义goroutine的关键字 | goto | 流程控制关键字,可以讲程序执行流程跳转到一个精确的位置 |
map | 内置map类型 | range | 定义迭代范围,常用在循环语句块中 |
select | 用于定义通信控制结构的关键字 |
类型 | 保留字 |
---|---|
Constants | true,false,iota,nil |
Types | int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr,float32,float64,complex64,complex128,byte,string,bool,rune,error |
Functions | make,len,cap,new,append,copy,close,delete,complex,real,imag,panic,recover |
在开发中,如果不使用变量,那么一万个页面就要编写一万个页面的代码。变量,就是在编写代码的时候,挖个坑,不同的数据来,然后填进去。
比如这个商品列表,挖坑的地方就是主图、价格、标题、销量、是否自营、店铺名称等,后期不一样的数据来了,形成永远都翻不完的商品列表。
4.1 变量的命名
1、变量是标识符的一种,所以首先得满足标识符的命名规则;
2、不能用关键字和保留字;
3、见名知意;
4、行业规范小驼峰。goodsList,userList…
4.2 变量使用
变量在编程里面就三个步骤:声明、赋值、使用。为了使用偷懒,组合,衍生出不一样的花样。比如多个变量一起声明,叫批量声明;变量和赋值合在一起,叫变量的初始化;声明和赋值合在一起,然后不写变量的数据类型,叫类型推导;声明和赋值合在一起写,然后变量声明的关键字也省了,叫短变量声明;声明和赋值合在一起,然后又进行批量赋值,而有些值又不想要了,叫匿名变量。
变量标准声明
var 变量名 数据类型
var name string
var age int
var height float32
var sex bool
变量批量声明
var (
name string
age int
height float32
sex bool
)
变量标准赋值
变量名 = 值
name = "buddha"
age = 18
height = 1.85
sex = true // true代表男,false代表女
变量批量赋值
变量名1,变量名2,变量名3,变量名4 = 值1,值2,值3,值4
变量初始化 = 变量声明 + 变量赋值
var 变量名 数据类型 = 值
var name string = "buddha"
var age int = 18
var height float32 = 1.85
var sex bool = true
var (
name string = "buddha"
age int = 18
height float32 = 1.85
sex bool = true
)
变量类型推导 = 变量声明 + 变量赋值,略了变量类型
var 变量名 = 值
var name = "buddha"
var age = 18
var height = 1.85
var sex = true
var (
name = "buddha"
age = 18
height = 1.85
sex = true
)
var name,age,height,sex = "buddha",18,1.85,true
短变量声明 = 变量声明 + 变量赋值,省略了变量类型和省略了变量定义关键字
变量名 := 值
name := "buddha"
age := 18
height := 1.85
sex := true
name,age,height,sex := "buddha",18,1.85,true
匿名变量 = 短变量声明后,用_作为不接收用不到变量的占位符
var 变量名1,_,变量名3,_ = 值1,值2,值3,值4
变量名1,_,变量名3,_ := 值1,值2,值3,值4
name,_,height,_ := "buddha",18,1.85,true
注意点:
- 函数外定义变量需要用关键字;
- 函数外不能用短变量声明
变量的使用
变量的使用就一言难尽,可以参与其它业务运算,也可以输出到控制台等。
name,_,height,_ := "buddha",18,1.85,true
fmt.Println(name,height) // 输出在控制台
5、常量
在开发过程中,一次性赋值,后期改变的量,叫常量。所以跟变量的区别是声明和赋值要一起,而不能说先声明,再赋值,后使用。定义常量的关键字是const。为了区分是变量和常量,所以常量的定义其关键字不能省。如果能省,那么该定义到底是变量还是常量,说不清楚了。
5.1 常量的命名
1、常量是标识符的一种,所以首先得满足标识符的命名规则;
2、不能用关键字和保留字;
3、见名知意;
4、行业规范单词全部大写,多个单词组成的常量,则用下划线隔开。如:USER,USER_NAME
5.2 常量使用
定义和使用。定义说白点就是变量里面的声明+赋值弄一起。
标准定义
const 常量名 数据类型 = 值
const PI float32 = 3.14
批量定义
const (
常量名1 数据类型 = 值1
常量名2 数据类型 = 值2
)
const (
PI float32 = 3.14
TOKEN string = "e10adc3949ba59abbe56e057f20f883e"
)
类型推导
const 常量名 = 值
const (
常量名1 = 值1
常量名2 = 值2
)
const 常量名1,常量名2 = 值1,值2
const PI = 3.14
const (
PI = 3.14
TOKEN = "e10adc3949ba59abbe56e057f20f883e"
)
const (
PI = 3.14
TOKEN // 如果给PI、TOKEN赋的值相同,则第二个开始可以不写
)
const PI,TOKEN = 3.14,"e10adc3949ba59abbe56e057f20f883e"
匿名常量
const 常量名1,_,常量名3,_ = 值1,值2,值3,值4
const PI,_ = 3.14,"e10adc3949ba59abbe56e057f20f883e"
5.3 保留字iota
编辑器点击源码发现是这个样子
// iota is a predeclared identifier representing the untyped integer ordinal
// number of the current const specification in a (usually parenthesized)
// const declaration. It is zero-indexed.
const iota = 0 // Untyped int.
所以iota是一个系统保留字常量
// 测试1
const n1,n2,n3,n4 = iota,iota,iota,iota
fmt.Println(n1,n2,n3,n4) // 0 0 0 0
// 测试2
const (
m1 = iota
m2 = iota
m3 = iota
m4 = iota
)
fmt.Println(m1,m2,m3,m4) // 0 1 2 3
// 测试3
const (
x1 = iota
x2 = iota
_ = iota
x4 = iota
)
fmt.Println(x1,x2,x4) // 0 1 3
// 测试4
y1 := iota // 编辑器提示错误,强制性运行报错
结论:
1、iota系统保留字,是一个常量
2、只能用在常量定义里,不能赋值给变量
3、iota值是在const里增加一行增1
6. 基本数据类型6.1 整型
数据类型 | 数据描述 | 最小值 | 最大值 | 默认值 |
---|---|---|---|---|
int8 | 有符号8位2进制整数,取值范围:-128 ~ 127 | math.MinInt8 | math.MaxInt8 | 0 |
int16 | 有符号16位2进制整数,取值范围:-32768 ~ 32767 | math.MinInt16 | math.MaxInt16 | 0 |
int32 | 有符号32位2进制整数,取值范围:-2147483648 ~ 2147483647 | math.MinInt32 | math.MaxInt32 | 0 |
int64 | 有符号64位2进制整数,取值范围:-9223372036854775808 ~ 9223372036854775807 | math.MinInt64 | math.MaxInt64 | 0 |
uint8 | 无符号8位2进制整数,取值范围:0 ~ 255 | 0 | math.MaxUint8 | 0 |
uint16 | 无符号8位16进制整数,取值范围:0 ~ 65535 | 0 | math.MaxUint16 | 0 |
uint32 | 无符号8位32进制整数,取值范围:0 ~ 4294967295 | 0 | math.MaxUint32 | 0 |
uint64 | 无符号8位64进制整数,取值范围:0 ~ 18446744073709551615 | 0 | math.MaxUint64 | 0 |
注意:
0b0o0x_
6.2 浮点数
数据类型 | 数据描述 | 最大值 | 默认值 |
---|---|---|---|
float32 | 有符号32位2进制小数 | math.MaxFloat32 | 0 |
float64 | 有符号64位2进制小数 | math.MaxFloat64 | 0 |
声明一个变量是浮点数时,默认声明的是float64
f := 3.14 // float64
6.3 虚数
数据类型 | 数据描述 | 默认值 | 案例 |
---|---|---|---|
complex64 | 复数有实部和虚部,complex64的实部和虚部为32位 | (0+0i) | var c complex64 = 1 + 2i |
complex128 | 复数有实部和虚部,complex128的实部和虚部为64位 | (0+0i) | var c complex128 = 1 + 2i |
var c1 complex64 = 1 + 2i
fmt.Println(c1) // (1+2i)
6.4 布尔值
数据类型 | 数据描述 | 默认值 | 案例 |
---|---|---|---|
bool | 布尔类型只有true和false两个值 | false | var b bool = true |
注意:
- 布尔类型与其它数据类型无法进行自动转换和强制转换,也无法参与运算。
- 布尔数据类型,只能表达对与错,真与假,两种情况的状态。
- 因为计算机最小单位是1个字节,所以布尔数据类型在内存中是占用1个字节的
6.5 字符串
数据类型 | 数据描述 | 默认值 | 案例 |
---|---|---|---|
string | 用双引号" "包含的内容,就是字符串定义变量可以存放的 | 默认空 | var str string = “HelloWorld” |
转移字符串
\普通字符特殊字符
\r\n\t\'\"\\
原样输出——采用反引号``
注意:
- 反引号里面任何字符都只是普通字符
字符串常用操作
// len() 字符串长度
var str string = "hello,world!"
mt.Println(len(str)) // 12
str := "中国人"
fmt.Println(len(str)) // 9
如果字符串内容多样,比如汉字占有3个字节,所以需要算一个字符串里字符格式则用rune关键字
str := "中国人"
fmt.Println(len([]rune(str))) // 3
str := "hello"
fmt.Println(len([]rune(str))) // 5
// + 字符串拼接
var str = "hello" + " world"
fmt.Println(str)
strings包下面的方法,列举几个
// strings.Trim() 去除字符串两边指定的内容
str := strings.Trim("'hello", "'")
fmt.Println(str)
// strings.TrimSpace() 去除字符串两边的空白
str := strings.TrimSpace(" hello ")
fmt.Println(str)
// strings.ToUpper() 字符串全部转换成大写
str := strings.ToUpper("hello")
fmt.Println(str)
strconv包下面的方法,列举几个
// strconv.Itoa() strconv是string和convert合成的一个函数名,意思是字符串转换,这个是int转换成字符串
str := strconv.Itoa(1234)
fmt.Println(str)
6.6 字符
在计算机的世界里,只有0和1。存在磁盘里,是0和1,读到内存里也是0和1,CPU能够处理的也是0和1。那么当需要表达字母a、b、c、A、B、C等字母时,怎么办?聪明的人们搞了个ASCII代码,用65、66、67分别代表a、b、c,用97、98、99分别代表A、B、C。ASCII码一共有128个字符。
当电脑普及到全世界后,发现ASCII码里没有汉字的代码,没有日语…怎么办?聪明的人们搞了一个unicode,它是unified(统一)和code(代码)搞出来的,翻译成中文就是:统一码。
unicode是数字和字符映射编码,类似一个厚厚的本子,上面写着,20013代表的是中国的中字。但是在计算机中如何实现呢?比如输入字符中,如何让计算机转换成20013?这个时候,UTF-8、UTF-16、UTF-32以及其它的编码来实现这个功能。其中UTF-8这个编码方式因为可以兼容ASCII而被广泛使用。
''
var b int8 = 'A'
fmt.Println(b) // 65
var c int16 = '中'
fmt.Println(c) // 20013
如果非要在控制台输出这个字符,怎么办?转换成字符串或格式化输出
var b int8 = 'A'
fmt.Println(string(b)) // A
fmt.Printf("%c", b) // A
var c int16 = '中'
fmt.Println(string(c)) // 中
fmt.Printf("%c", c) // 中
格式化输出
符号 | 描述 |
---|---|
%c | 该值对应的unicode码值 |
%d | 表示为十进制显示 |
%T | 值的类型 |
%q | 该值对应的双引号括起来的go语法字符串字面值 |
%f | 显示小数 |
注意:
- 在Go语言中,字符不是一种数据类型,字符只是一种特殊的整型
6.7 类型转换
Go语言中,没有自动数据类型转换,只有强制数据类型转换。
数据类型(值)
f := 3.14i := int8(f)
fmt.Println(i) // 3
7. 运算符
7.1 算术运算符
运算符 | 描述 |
---|---|
+ | 相加 |
- | 相减 |
* | 相乘 |
/ | 相除 |
% | 求余 |
a := 50
b := 20
fmt.Println(a + b) // 70
fmt.Println(a - b) // 30
fmt.Println(a * b) // 1000
fmt.Println(a / b) // 2
fmt.Println(a % b) // 10
自增和自减
a := 5
b := 7
// ++a 不合法
a++ // 等价于
a = a + 1
// var i = a++ 不合法
// --b 不合法
b--
b = b -1
// var i = b-- 不合法
注释掉的,其它编程语言可能合法,但是在Go语言里是不合法的。
7.2 关系运算符
运算符 | 描述 |
---|---|
== | 判断两个值 是否 相等,是true,否false |
!= | 判断两个值 是否 不相等,是true,否false |
> | 判断左边值 是否 大于 右边值,是true,否false |
>= | 判断左边值 是否 大于等于 右边值,是true,否false |
< | 判断左边值 是否 小于 右边值,是true,否false |
<= | 判断左边值 是否 小于等于 右边值,是true,否false |
><
7.3 逻辑运算符
运算符 | 描述 |
---|---|
&& | 与。 运算符两边都是true则为true,否则为false |
|| | 或。 运算符两边都是false则为false,否则为true |
! | 非。 运算符右边最终结果是true或false后,取反 |
短路特性
学习其它编程语言,在学习逻辑运算符的时候,最头疼的就是短路特性。而Go语言,不让你这么写。
a := 20
b := 10
c := 5
// flag := (a < b) && ((a = a -c) > b) // 不合法
flag := a > b && b >c // 合法
fmt.Println(flag)
7.4 位运算符
运算符 | 描述 |
---|---|
& | 位与,换算成二进制数,相同位置数字都是1的为1,否则为0 |
| | 位或,换算成二进制数,相同位置数字有是1的为1,否则为0 |
^ | 位异或,换算成二进制数,相同位置数字不同的为1,相同的为0 |
<< | 位左移,换算成二进制数左移,高位数丢弃,低位数补0 |
>> | 位右移,换算成二进制数右移,高位数补0,低位数丢弃 |
平时接触的数学是十进制,何为十进制呢,每个位,都有0,1,2,3,4,5,6,7,8,9其中的一个,组成就是逢十进一。那么何为八进制呢,就是逢八进一。以此类推,十六进制,二进制类似。
7.5 赋值运算符
假设定义了三个变量
var a int = 20
var b int = 20
var c int
运算符 | 描述 | 实例 |
---|---|---|
= | 右边表达式的结果赋值给左边变量 | c = a + b |
+= | 相加后再赋值 | 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 &= a 等于c = c & a |
|= | 按位或后赋值 | c |= a 等于c = c | a |
^= | 按位异或后赋值 | c ^= a 等于c = c ^ a |
=
8. 流程控制
流程控制就是指令运行时的方式。流程控制主要有三种方式,也叫三种结构,分别是顺序结构、分支结构和循环结构。
8.1 顺序结构
从上到下,按顺序逐步进行执行。在此之前接触的,都属于顺序结构的代码。
算法
简言之,算法就是解决问题的步骤。顺序结构是最简单的算法结构。经常会有人提一个词就是,我写一个算法。说的很高大上,其实搞不好写的就是几行顺序结构的代码。
8.2 分支结构
分支结构就是,根据不同的条件,执行不同的代码块,从而得到不一样的结果。模拟一个场景,开始是家,结束是公司,可以选择不同的交通工具。这个时候用的就是分支结构。
- if语句
if 条件表达式 {
执行语句
}
if 4 > 3 {
fmt.Println("4大于3是对的")
}
- if-else语句
if 条件表达式 {
执行语句1
} else {
执行语句2
}
if 4 > 3 {
fmt.Println("4大于3是对的")
} else {
fmt.Println("4大于3是错的")
}
- if-else if-else语句
if 条件表达式1 {
执行语句1
} else if 条件表达式2 {
执行语句2
} else {
执行语句3
}
score := 65
if score >= 90 {
fmt.Println("成绩是优秀")
} else if score >= 80 {
fmt.Println("成绩是良好")
} else {
fmt.Println("成绩马马虎虎")
}
if 条件表达式1 {
if 条件表达式2 {
执行语句1
} else {
执行语句2
}
} else {
执行语句3
}
score := 95
if score >= 60 {
if score >= 90 {
fmt.Println("成绩优秀")
} else {
fmt.Println("成绩合格")
}
} else {
fmt.Println("成绩不及格")
}
if score := 95; score >= 60 {
if score >= 90 {
fmt.Println("成绩优秀")
} else {
fmt.Println("成绩合格")
}
} else {
fmt.Println("成绩不及格")
}
floor := 2
switch floor {
case 2:
fmt.Println("2楼停")
case 3:
fmt.Println("3楼停")
case 4:
fmt.Println("4楼停")
default:
fmt.Println("1楼停")
}
switch floor := 2; floor {
case 2:
fmt.Println("2楼停")
case 3:
fmt.Println("3楼停")
case 4:
fmt.Println("4楼停")
default:
fmt.Println("1楼停")
}
上面演示的是最常见的语句,也还可以演变出更多语句来。最终如何用,根据实际业务来定。脱离实际应用,谈哪个好,都是耍流氓。
8.3 循环结构
我每天的生活都是吃饭、睡觉、打豆豆。类似这种有规律重复的行为就用循环结构来处理。
for 初始化语句;条件语句;控制语句 {
循环体
}
代码从上往下(程序里,代码默认是从上往下执行的)走到这个for循环结构体这。第一步:执行初始化语句;第二步:执行条件语句,如果条件语句执行结果是true则执行循环体,如果条件语句执行结果是false则跳过for循环结构往下走。假如条件语句执行结果是true,执行完毕了循环体,那么会开始执行控制语句,控制语句执行完毕则由开始去执行条件语句。重复着第二步的动作了。
......
for i := 0; i < 3; i++ {
fmt.Println(i)
}
......
解释:当来到这个循环体(for循环体),
第一步:执行初始化语句(i := 0);
第二步:执行条件语句(i < 3),心算过程(i 是0,然后是0 < 3,emm~~,结果是true),好的,通过心算过程得知,执行条件语句的结果是true,那么执行循环体(输出0);
第三步:执行i++(就是执行循环体后执行的控制语句),心算过程(刚才i是0,那么i++后,i是1咯),通过心算过程得知执行i++的结果是1;
第二步:执行条件语句(i < 3),心算过程(i是1,然后是1 < 3,emm,结果是true),好的,通过心算过程得知,执行条件语句的结果是true,那么执行循环体(输出1);
第三步:执行i++(就是执行循环体后执行的控制语句),心算过程(刚才i是1,那么i++后,i就是2咯),通过心算过程得知执行i++的结果是2;
第二步:执行条件语句(i < 3),心算过程(i是2,然后是2 < 3,emm,结果是true),好的,通过心算过程得知,执行条件语句的结果是true,那么执行循环体(输出2);
第三步:执行i++(就是执行循环体后执行的控制语句),心算过程(刚才i是2,那么i++后,i就是3咯),通过心算过程得知执行i++的结果是3;
第二步:执行条件语句(i < 3),心算过程(i是3,然后是3 < 3,emm,结果是false),好的,通过心算过程得知,执行条件语句的结果是false,那么不执行循环体,for循环结束,代码往下执行了。
i := 0
for ; i < 3; i++ {
fmt.Println(i)
}
;
i := 0
for i < 3 {
fmt.Println(i)
i++
}
上面这种写法,就是告诉程序,剩下这个是条件语句,初始化语句和控制语句都去掉了。
for {
fmt.Println("HelloWorld")
}
上面这种写法,就是告诉程序,for里面是循环体,默认是无限循环。是否无限循环还是有条件循环,循环体来定。
i := 0
for {
fmt.Println("HelloWorld")
i++
if i >= 10 {
return
}
}
i := 0
for {
fmt.Println("HelloWorld")
i++
if i >= 10 {
goto End
}
End:
break
}
i := 0
for {
fmt.Println("HelloWorld")
i++
if i >= 10 {
break
}
}
i := 0
for {
fmt.Println("HelloWorld")
i++
if i >= 10 {
panic("end") // 这种方式会抛出错误,业务逻辑处理时,一般不用
}
}
上面这几种都是循环体里结束循环的方式。
for i := 0; i < 3; i++{
if i == 2 {
continue
}
fmt.Println("HelloWorld")
}
continue
9. 数组
9.1 一维数组
var age intvar age [5]int[5]int[4]int[5]int
数组标准使用三个步骤,定义、赋值和使用
1. 定义var age [3]int
2. 赋值age[0] = 1age[1] = 2age[2] = 3
3. 使用fmt.Println(age) // [1 2 3]
数组在定义的时候,默认给予初始值
var age [3]int
fmt.Println(age) // [0 0 0]
数组也可以把定义和赋值一起
var age [3]int = [3]int{1,2,3}
fmt.Println(age)
var age = [3]int{1,2,3} // 数据类型可以省略,可以推导得出
fmt.Println(age)
var age = [3]int{1,2} // 初始值不赋值的,默认初始值是0
fmt.Println(age)
var age = [...]int{1,2} // 数组长度不写,根据赋值个数来定
fmt.Println(age)
var age = [...]int{1:5, 5:8} // 根据索引下标来赋值
fmt.Println(age) // [0 5 0 0 0 8]
数组的遍历
var age = [...]int{1:5, 5:8}
for i := 0; i < len(age); i++ {
fmt.Println(age[i])
}
var age = [...]int{1:5, 5:8}
for index, value := range age {
fmt.Println(index, value)
}
值传递
a := [2]int{2,3}
var b = a
fmt.Println(a) // [2 3]
fmt.Println(b) // [2 3]
a[1] = 10
fmt.Println(a) // [2 10]
fmt.Println(b) // [2 3]
数组是值传递,不是引用传递。引用传递的特点是值发生改变,关联各个地方的值也发生改变。
9.2 多维数组
// 二维数组的定义
city := [3][2]string{
{"北京", "上海"},
{"南京", "杭州"},
{"广州", "深圳"},
}
// 二维数组的遍历
for index, value := range city {
fmt.Println(index, value)
for k, v := range value {
fmt.Println(k, v)
}
}
...
city := [3][...]string{ // 不合法
{"北京", "上海"},
{"南京", "杭州"},
{"广州", "深圳"},
}
city := [...][3]string{ // 合法
{"北京", "上海"},
{"南京", "杭州"},
{"广州", "深圳"},
}
10. 切片
切片就是可变长度的数组。
切片使用三个步骤:定义、赋值、使用
var a []int
fmt.Println(a) // []
所以,切片默认是空数组
通过定义方式得到切片
a := []int{1, 2, 3}
fmt.Println(a) // [1 2 3]
通过数组方式得到切片
a := [...]int{1,2,3,4,5}b := a[1:3]
fmt.Println(b) // [2 3]
下标是1的值包含了,下标是3的值没有包含。左下标最少为0,右下标最大为切片长度-1
通过make函数得到切片
切片跟数组另外一个不同点就是数据类型是引用类型,内部结构包含引用地址、切片长度、切片容量。切片长度就是切片元素个数,切片容量就是切片最大能放多少个元素。
make([]T, size, cap) // 其中size指的是切片长度,cap指的是切片容量
a := make([]int, 3, 4)
fmt.Println(a) // [0 0 0]
fmt.Println(len(a)) // 切片长度是3
fmt.Println(cap(a)) // 切片容量是4
a := make([]int, 3, 4)
a[3] = 3
a[4] = 4
fmt.Println(a) // 运行就会报错,因为容量是4,a[4] = 4超出了定义时切片的容量,那么怎么破呢a.append(a, 1, 2, 3)方式追加,其容量就扩大了
var a []int
a = append(a, 1)
a = append(a, 2, 3, 4)
fmt.Println(a) // [1 2 3 4]
切片和数组的区别
// 数组
a := [...]int{1,2,3,4,5}
b := [...]int{1,2,3,4,5}
fmt.Println(a == b) // true
// 切片
a := []int{1,2,3,4,5}
b := []int{1,2,3,4,5}
fmt.Println(a == b) // 报错
数组是值传递,切片是引用传递。引用传递不能用关系运算符来比较。
a := []int{1,2,3,4,5}
b := a
b[0] = 20
fmt.Println(a) // [20 2 3 4 5]
fmt.Println(b) // [20 2 3 4 5]
引用传递,是共用数组的,一处发生改变,其它地方引用也会跟着改变。
a := []int{1, 2, 3}
b := make([]int, 3, 3)
copy(b, a)
b[0] = 20
fmt.Println(a) // [1 2 3]
fmt.Println(b) // [20 2 3]
利用copy函数,使得两个切片成为独立的切片,互相不受影响
判断切片是否为空
a := make([]int, 0, 4)
fmt.Println(a == nil) // false
fmt.Println(len(a)) // 0
len(s) == 0s == nil
切片遍历元素
a := []int{1,2,3,4,5}
for i := 0; i < len(a); i++ {
fmt.Println(i, a[i])
}
a := []int{1,2,3,4,5}
for index, value := range a {
fmt.Println(index, value)
}
切片追加元素
var a []int
a = append(a, 1)
a = append(a, 2, 3, 4)
b := []int{5, 6, 7}
a = append(a, b...)
fmt.Println(a)
可以用append为切片添加一个元素,多个元素,一个切片或多个切片
切片删除元素
a := []int{1, 2, 3, 4, 5, 6, 7, 8}
a = append(a[:2], a[3:]...)
fmt.Println(a) // [1 2 4 5 6 7 8]
目前go1.13.15版本,切片没有专门的方法,只能通过上面类似的代码实现。切片删除实际应用中,比较常用,相信后面迭代的版本中,会添加该方法。
11. mapm := make(map[string]string)
m["username"] = "buddha"
m["age"] = "18"
m["sex"] = "male"
fmt.Println(m) // map[age:18 sex:male username:buddha]
m := map[string]string {
"username": "buddha",
"age": "18",
"sex": "male",
}
fmt.Println(m) // map[age:18 sex:male username:buddha]
map是引用数据类型,需要初始化才能够使用。
map某个键是否存在
m := map[string]string {
"username": "buddha",
"age": "18",
"sex": "male",
}
value, ok := m["username"]
fmt.Println(ok)
如果键存在ok为true,value为对应的值;不存在ok为false,value为0。value,ok只是常用的变量名,value改成v或_,ok改成flag都可以的。
map遍历
m := map[string]string {
"username": "buddha",
"age": "18",
"sex": "male",
}
// 遍历获取键值对
for k,v := range m {
fmt.Println(k,v)
}
// 遍历获取键
for k := range m {
fmt.Println(k)
}
// 遍历获取值
for _,v := range m {
fmt.Println(v)
}
删除map的键值对
m := map[string]string {
"username": "buddha",
"age": "18",
"sex": "male",
}
delete(m, "username")
fmt.Println(m)
12. 函数
实现某功能的代码集合。
函数的定义
func 函数名(形参)(返回值){
函数体
}
,(),
func intSum(x int, y int) (result int) {
result = x + y
return result
}
func intSum(x int, y int) (result int) {
result = x + y
return // 上面有写返回值变量名的,这里用return可以省略
}
func intSum(x int, y int) int {
return x + y
}
func intSum(x, y int) int { // 形参的类型一样,可以简写
return x + y
}
func changeNumber(a int, b int) (x int, y int) {
x = a
y = b
return y, x
}
func changeNumber(a int, b int) (int, int) {
x := a
y := b
return y, x
}
func intSum(x ...int) int { // 不定参数
var sum int
for _,v := range x {
sum += v
}
return sum
}
函数的形参和返回值都是可选,根据实际需要来定。不定参数的本质是切片。
函数的调用
func intSum(x int, y int) int {
return x + y
}
sum := intSum(10, 20)
fmt.Println(sum)
函数类型
typetype 类型名称 func(形参)(返回值)
type calculation func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
type calculation func(int, int) int
package main
import "fmt"
type calculation func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func main() {
var c calculation
c = add
i := c(10, 20)
fmt.Println(i)
}
为什么要定义函数类型呢?目的是实现多态。就是在写功能的时候把函数名当成参数传进去,参数值不同就实现不同的功能。
package main
import "fmt"
type calculation func(int, int) int
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
func do(method calculation, x int, y int) int {
return method(x, y)
}
func main() {
result := do(add, 20, 30)
fmt.Println(result) // 50
}
函数作为参数
type op func(int int)int
func(int,int)int形参变量名 形参变量类型
func add(x, y int) int {
return x + y
}
func calc(x, y int) int {
return add(x, y)
}
在calc这个方法上添加一个函数来做形参,那额外添加一个函数名+函数类型
func add(x, y int) int {
return x + y
}
func calc(x, y int, add func(int,int)int) int {
return add(x, y)
}
func calc(x, y int, add func(a int, b int) int) int {
return add(x, y)
}
函数作为返回值
func add(x, y int) int {
return x +y
}
int
func add(x, y int) int {
return x +y
}
func calc() func(a, b int) int {
return add
}
f := calc()
fmt.Println(f(10, 20)) // 30
匿名函数
匿名函数,顾名思义就是没有函数名的函数
add := func(x, y int) int {
return x + y
}
sum := add(10, 20)
fmt.Println(sum) // 30
sum := func(x, y int) int {
return x + y
}(10, 20)
fmt.Println(sum) // 30
上面两种匿名函数写法的区别:第一个是在调用的时候执行,第二个是立即执行
闭包函数
闭包函数可以理解成定义在函数内部的函数
package main
import "fmt"
func add() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = add()
fmt.Println(f(10)) // 10
fmt.Println(f(20)) // 30
fmt.Println(f(30)) // 60
}
add()赋值给变量后,add()里面的变量和函数的生命周期就变成和文件的生命周期是一样了,变量也类似变成全局变量了。
defer语句
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
start
end
3
2
1
defer
13. 指针
指针就是计算机内存中的地址编号。每个人的身份证都有一个家庭地址,而运行的代码也会有一个内存地址,这个内存地址就有一个高大上的名字叫指针。
var str string = "hello"
ptr := &str
fmt.Println(ptr) // 0xc0000301f0
&变量名
var str string = "hello"
var ptr *string = &str
fmt.Println(ptr) // 0xc0000301f0
**int*string*float32
var str string = "hello"
ptr := &str
fmt.Println(*ptr) // hello
&变量名*变量名
14. 结构体
自定义类型
type op func(int, int)int
type myInt intmyIntint
类型别名
type byte = uint8
type rune = int32
类型别名,编译时还是用原来的,不会产生新的类型。自定义就是会创建新的类型。
标准结构体
一个事物,要描述的清楚,则需要把事物多个维度的信息描述清楚。比如一个人,有姓名,性别,身高,体重。这些信息一填,这个人的轮廓就很清晰了。Go语言里用结构体来描述这个事物。
type 结构体类型 struct {
字段名1 字段类型1
字段名2 字段类型2
}
type person struct {
username string
sex bool
height float32
weight int8
}
type person struct {
username string
sex bool
height float32
weight int8
}
func main() {
var buddha person
buddha.username = "buddha"
buddha.sex = true
buddha.height = 1.80
buddha.weight = 80
fmt.Println(buddha) // {buddha true 1.8 80}
fmt.Println(buddha.username) // buddha
}
首先要定义结构体类型,就是自定义了类型,类似一个int类型。有了类型后,接下来的步骤就是跟使用变量的步骤差不多。先变量定义,后赋值和再使用。
匿名结构体
如果定义结构体类型只用到一次,那么可以直接给变量该结构体类型
func main() {
var buddha struct {
username string
sex bool
height float32
weight int8
}
buddha.username = "buddha"
buddha.sex = true
buddha.height = 1.80
buddha.weight = 80
fmt.Println(buddha) // {buddha true 1.8 80}
fmt.Println(buddha.username) // buddha
}
new函数
i := new(int)
fmt.Println(i) // 0xc000060090
new(类型名称)
type person struct {
username string
sex bool
height float32
weight int8
}
func main() {
p := new(person)
fmt.Println(p) // &{ false 0 0}
p.username = "buddha"
p.sex = true
p.height = 1.80
p.weight = 80
fmt.Println(p) // &{buddha true 1.8 80}
}
""
type person struct {
username string
sex bool
height float32
weight int8
}
func main() {
p := &person{}
fmt.Println(p) // &{ false 0 0}
p.username = "buddha"
p.sex = true
p.height = 1.80
p.weight = 80
fmt.Println(p) // &{buddha true 1.8 80}
}
&new
15. 包
package 包名
-
导包
import
import "包1"
import "包2"
import (
"包1"
"包2"
)
package 包名
导包取别名
为了防止导入的包重名,可以给导入的包取别名。取了别名的包,原来的报名就不可用了。
import 别名 "包"
16. 接口
接口就是定义规范
接口的定义
type 接口类型名 interface {
方法名(形参) (返回值)
...
}
er
/* 定义接口 */
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()
}
17. 错误处理
- Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理
- Go 中引入的处理方式为:defer, panic, recover
- Go 语言使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
// 使用defer + recover来捕获和处理异常
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 说明捕获到错误
fmt.Println("err=", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println(res)