1、注释
// 单行注释

/*
	多行注释
	多行注释
	多行注释
	多行注释
 */
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语句块中,后跟一个表达式,表示一个待匹配项
fallthroughswitch语句块中使一个case子块执行完毕后,继续执行下一个case子块的代码defaultswitch和switch语句块中定义的默认项
for循环体语句块关键字continue跳过本轮循环,进入下一轮循环迭代
break强制退出循环或switch语句块return结束函数指定,使函数返回
chan用于定义channel的关键字struct结构体结构关键字
defer延迟调用关键字interface接口类型
go定义goroutine的关键字goto流程控制关键字,可以讲程序执行流程跳转到一个精确的位置
map内置map类型range定义迭代范围,常用在循环语句块中
select用于定义通信控制结构的关键字
类型保留字
Constantstrue,false,iota,nil
Typesint,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr,float32,float64,complex64,complex128,byte,string,bool,rune,error
Functionsmake,len,cap,new,append,copy,close,delete,complex,real,imag,panic,recover
4、变量

在开发中,如果不使用变量,那么一万个页面就要编写一万个页面的代码。变量,就是在编写代码的时候,挖个坑,不同的数据来,然后填进去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XIBqJcy5-1642149287214)(E:\go\004-基础语法.assets\image-20220107154536021.png)]

比如这个商品列表,挖坑的地方就是主图、价格、标题、销量、是否自营、店铺名称等,后期不一样的数据来了,形成永远都翻不完的商品列表。

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 ~ 127math.MinInt8math.MaxInt80
int16有符号16位2进制整数,取值范围:-32768 ~ 32767math.MinInt16math.MaxInt160
int32有符号32位2进制整数,取值范围:-2147483648 ~ 2147483647math.MinInt32math.MaxInt320
int64有符号64位2进制整数,取值范围:-9223372036854775808 ~ 9223372036854775807math.MinInt64math.MaxInt640
uint8无符号8位2进制整数,取值范围:0 ~ 2550math.MaxUint80
uint16无符号8位16进制整数,取值范围:0 ~ 655350math.MaxUint160
uint32无符号8位32进制整数,取值范围:0 ~ 42949672950math.MaxUint320
uint64无符号8位64进制整数,取值范围:0 ~ 184467440737095516150math.MaxUint640

注意:

0b0o0x_

6.2 浮点数

数据类型数据描述最大值默认值
float32有符号32位2进制小数math.MaxFloat320
float64有符号64位2进制小数math.MaxFloat640

声明一个变量是浮点数时,默认声明的是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两个值falsevar b bool = true

注意:

  • 布尔类型与其它数据类型无法进行自动转换和强制转换,也无法参与运算。
  • 布尔数据类型,只能表达对与错,真与假,两种情况的状态。
  • 因为计算机最小单位是1个字节,所以布尔数据类型在内存中是占用1个字节的

6.5 字符串

数据类型数据描述默认值案例
string用双引号" "包含的内容,就是字符串定义变量可以存放的默认空var str string = “HelloWorld”

转移字符串

\普通字符特殊字符
\r\n\t\'\"\\

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WWYys9a-1642149287217)(E:\go\004-基础语法.assets\image-20220108113404474.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4oeOvdy-1642149287218)(E:\go\004-基础语法.assets\image-20220108113524259.png)]

原样输出——采用反引号``

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnFo8CTP-1642149287221)(E:\go\004-基础语法.assets\image-20220108114943606.png)]

注意:

  • 反引号里面任何字符都只是普通字符

字符串常用操作

// 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. 流程控制

流程控制就是指令运行时的方式。流程控制主要有三种方式,也叫三种结构,分别是顺序结构分支结构循环结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCf8VTVC-1642149287222)(E:\go\004-基础语法.assets\doing.jpg)]

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. map
m := 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)