教程网址
开发环境
系统:Win10 64位;
go版本:go1.14.windows-amd64;
go开发软件版本:GoLand 2020.1 x64;
环境部署
1、下载 go 1.14.windows-amd64 安装包,注意一定要下载这个版本,高版本的go安装后,GoLand 2020识别不了,会报“所选目录不是 Go SDK 的有效主路径”错误。
2、设置GoLand软件的环境变量 ;
File | Settings | Go | GOROOT,在列表中选择 Go 1.14
3、创建一个工程;
File | New | Project,选择项目文件保存路径。
Go 语言结构
Go 语言的基础组成有以下几个部分:
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
关于包,根据本地测试得出以下几点:
- 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
- 文件夹名与包名没有直接关系,并非需要一致。
- 同一个文件夹下的文件只能有一个包名,否则编译报错。
- 每个 Go 应用程序都包含一个名为 main 的包。
- 文件名和文件夹名称不能一样,否则运行代码时会提示“cannot find package "xxx" in any of:”错误。
Go 语言基础语法
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。
注释
注释不会被编译,每一个包应该有相关注释。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:
// 单行注释
/*
Author by 菜鸟教程
我是多行注释
*/
标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字,不能是go语言的关键字或保留字,不能包含运算符。
字符串连接
Go 语言的字符串可以通过 + 实现:
实例:
package main
import "fmt"
func main() {
fmt.Println("Google" + "Runoob")
}
以上实例输出结果为:
GoogleRunoob
关键字
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
程序中可能会使用到这些标点符号:.、,、;、: 和 …。
Go 语言格式化字符串
格 式 | 描 述 |
---|---|
%v | 按值的本来值输出 |
%+v | 在 %v 基础上,对结构体字段名和值进行展开 |
%#v | 输出 Go 语言语法格式的值 |
%T | 输出 Go 语言语法格式的类型和值 |
%% | 输出 % 本体 |
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%d | 整型以十进制方式显示 |
%x | 整型以十六进制方式显示 |
%X | 整型以十六进制、字母大写方式显示 |
%U | Unicode 字符 |
%f | 浮点数 |
%p | 指针,十六进制方式显示 |
Go 语言数据类型
Go 语言按类别有以下几种数据类型:
序号 | 类型和描述 |
---|---|
1 | 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 |
2 | 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 |
3 | 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 |
4 | 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型 |
Go 语言变量
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字,字符串变量的赋值语句必须用双引号“”,不能使用单引号''。
声明变量的一般形式是使用 var 关键字:
var identifier type
可以一次声明多个变量:
var identifier1, identifier2 type
变量声明
第一种,指定变量类型,如果没有初始化,则变量默认为零值。
var v_name v_type
v_name = value
例如:
var a = "RUNOOB"
var b int
var c bool
零值就是变量没有做初始化时系统默认设置的值。
- 数值类型(包括complex64/128)为 0
- 布尔类型为 false
- 字符串为 ""(空字符串)
- 以下几种类型为 nil:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
第二种,根据值自行判定变量类型。
var v_name = value
例如:
var d = true
第三种,如果变量已经使用 var 声明过了,再使用 *:=* 声明变量,就产生编译错误,格式:
v_name := value
例如:
var intVal int
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"
func main(){
g, h := 123, "hello"
println(x, y, a, b, c, d, e, f, g, h)
}
值类型和引用类型
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。
当使用等号 =
将一个变量的值赋值给另一个变量时,如:j = i
,实际上是在内存中将 i 的值进行了拷贝。
你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
package main
import "fmt"
func main() {
var name1 string
// var age = 18
// name := "xxxx"
name1 = "xiang186"
name2 := name1
var name3 *string
name3 = &name1
fmt.Println("name1's var:",name1,"\nname1's mem adress is:",&name1)
fmt.Println("name2's var:",name2,"\nname2's mem adress is:",&name2)
fmt.Println("name3's var:",name3,"\n*name3's var is:",*name3,"\nname3's mem adress is:",&name3)
}
运行结果:
name1's var: xiang186
name1's mem adress is: 0xc0000881e0
name2's var: xiang186
name2's mem adress is: 0xc0000881f0
name3's var: 0xc0000881e0
*name3's var is: xiang186
name3's mem adress is: 0xc0000ca018
注:*string表示一个字符串指针的类型。
从运行结果可以看出:
1、使用“=”赋值时,只是将“=”符号右边变量的值赋值给了左边的变量,左边的变量使用单独的内存空间来储存右边变量当前的值,当右边的变量重新赋值后,左边变量的值不会跟着变化;
2、使用指针变量*赋值时,是将“=”符号右边变量的内存地址赋值给了左边变量,左边变量使用单独的内存空间来存储右边变量的内存地址,当右边变量被重新赋值后,左边变量的值也会跟着变化(因为值是储存在内存地址中的);
简短形式,使用 := 赋值操作符
我们可以在创建变量时简写为
a := 50
或
b := false
a 和 b 的类型(int 和 bool)将由编译器自动推断。这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。
注意事项
在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明。
例如:
var name string
name := "xxxx"
运行时会报错:
no new variables on left side of :=
如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:
package main
import "fmt"
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
// 尝试编译这段代码将得到错误 a declared and not used。
全局变量是允许声明但不使用的。 同一类型的多个变量可以声明在同一行,如:
var a, b, c int
多变量可以在同一行进行赋值,如:
var a, b int
var c string
a, b, c = 5, 7, "abc"
上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:
a, b, c := 5, 7, "abc"
右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。
这被称为 并行 或 同时 赋值。
如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。
空白标识符 _ 也被用于抛弃值,如值 5 在:
_, b = 5, 7
中会被抛弃。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。
空白标识符在函数返回值时的使用:
package main
import "fmt"
func main() {
_,numb,strs := numbers() //只获取函数返回值的后两个
fmt.Println(numb,strs)
}
//一个可以返回多个值的函数
func numbers()(int,int,string){
a , b , c := 1 , 2 , "str"
return a,b,c
}
输出结果:
2 str
Go 语言常量
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
const name = "jim"
name = "sam"
编译以上语句时会报错:
cannot assign to name
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
const identifier [type] = value
可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
- 显式类型定义:
const b string = "abc"
- 隐式类型定义:
const b = "abc"
多个相同类型的声明可以简写为:
const c_name1, c_name2 = value1, value2
例如:
const a, b, c = 1, false, "str" //多重赋值
常量还可以用作枚举:
const (
Unknown = 0
Female = 1
Male = 2
)
常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
运行结果:
abc 3 16
iota常量
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
const (
a = iota
b = iota
c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
a = iota
b
c
)
Go 语言运算符
运算符用于在程序运行时执行数学或逻辑运算。
Go 语言内置的运算符有:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
算术运算符
下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
-- | 自减 | A-- 输出结果 9 |
关系运算符
下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 True |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 True |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 True |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
逻辑运算符
下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
位运算符
位运算符对整数在内存中的二进制位进行操作。
Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位中有0,那么计算结果就是0,否则就是1,比如:0和0,计算结果为0,0和1,计算结果是0,1和1,计算结果是1。 | (A & B) 结果为 12, 二进制为 0000 1100 |
| | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位中有1,那么计算结果就是1,否则就是0,比如:0和0,计算结果为0,0和1,计算结果是1,1和1,计算结果是1。 | (A | B) 结果为 61, 二进制为 0011 1101 |
^ | 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位的值相同,那么计算结果就是0,否则就是1,比如:0和0,计算结果为0,0和1,计算结果是1,1和1,计算结果是0。 | (A ^ B) 结果为 49, 二进制为 0011 0001 |
<< | 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。通俗点讲就是:<<n==*(2^n)。 | A << 2 结果为 240 ,二进制为 1111 0000 |
>> | 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。通俗点讲就是:>>n==/(2^n)。 | A >> 2 结果为 15 ,二进制为 0000 1111 |
赋值运算符
下表列出了所有Go语言的赋值运算符。
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | 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 |
其他运算符
下表列出了Go语言的其他运算符。
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。(即变量的内存地址) |
* | 指针变量。 | *a; 是一个指针变量 |
以下实例演示了其他运算符的用法:
实例:
package main
import "fmt"
func main() {
var a int = 4
var b int32
var c float32
var ptr *int
/* 运算符实例 */
fmt.Printf("第 1 行 - a 变量类型为 = %T\n", a );
fmt.Printf("第 2 行 - b 变量类型为 = %T\n", b );
fmt.Printf("第 3 行 - c 变量类型为 = %T\n", c );
/* & 和 * 运算符实例 */
ptr = &a /* 'ptr' 包含了 'a' 变量的地址 */
fmt.Printf("a 的值为 %d\n", a);
fmt.Printf("*ptr 为 %d\n", *ptr);
}
以上实例运行结果:
第 1 行 - a 变量类型为 = int
第 2 行 - b 变量类型为 = int32
第 3 行 - c 变量类型为 = float32
a 的值为 4
*ptr 为 4
运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 | 运算符 |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。
Go 语言条件语句
条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。
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 语句
语法
Go 编程语言中 if 语句的语法如下:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
}
if...else 语句
语法
Go 编程语言中 if...else 语句的语法如下:
if 布尔表达式 {
/* 在布尔表达式为 true 时执行 */
} else {
/* 在布尔表达式为 false 时执行 */
}
if 语句嵌套
语法
Go 编程语言中 if...else 语句的语法如下:
if 布尔表达式 1 {
/* 在布尔表达式 1 为 true 时执行 */
if 布尔表达式 2 {
/* 在布尔表达式 2 为 true 时执行 */
}
}
你可以以同样的方式在 if 语句中嵌套 else if...else 语句
switch 语句
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。
switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。
语法
Go 编程语言中 switch 语句的语法如下:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。
实例:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var grade string = "B"
var marks int = 100
switch marks {
case 90 :
grade = "A"
case 80 : grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
switch grade{
case "A" :
fmt.Printf("优秀!\n" )
case "B", "C" :
fmt.Printf("良好\n" )
case "D" :
fmt.Printf("及格\n" )
case "F":
fmt.Printf("不及格\n" )
default:
fmt.Printf("差\n" )
}
fmt.Printf("你的等级是 %s\n", grade )
}
以上代码运行结果:
及格
你的等级是 D
Type Switch
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
Type Switch 语法格式如下:
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
实例:
package main
import "fmt"
func main() {
var i = 3.1515156633666636622
var x interface{} = &i
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 %T 型",i)
case float64:
fmt.Printf("x 是 %T 型",i)
case func(int) float64:
fmt.Printf("x 是 %T 型",i)
case bool, string:
fmt.Printf("x 是 %T 型",i )
default:
fmt.Printf("x 是未知型:%T",i)
}
}
以上代码执行结果为:
x 是未知型:*float64
fallthrough
使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
实例:
package main
import "fmt"
func main() {
switch {
case true:
fmt.Println("1、case 条件语句为 true")
fallthrough
case false:
fmt.Println("2、case 条件语句为 false")//这一行因为上一行的case中有fallthrough,所以会被打印出来
case true:
fmt.Println("3、case 条件语句为 true")/*这一行按理应该是要打印的,但是上一行是因为加
了fallthrough才打印出来的,程序执行到上一行的时候语句就退出了,所以这一行不会被打印出来。*/
fallthrough
default:
fmt.Println("4、默认 case")
}
}
以上语句执行结果:
1、case 条件语句为 true
2、case 条件语句为 false
select 语句
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
语法
Go 编程语言中 select 语句的语法如下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
以下描述了 select 语句的语法:
-
每个 case 都必须是一个通信
-
所有 channel 表达式都会被求值
-
所有被发送的表达式都会被求值
-
如果任意某个通信可以进行,它就执行,其他被忽略。
-
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
- 如果有 default 子句,则执行该语句。
- 如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
关于这个语句的实例和详细说明见文档:golang-chan关键字的说明.md
Go 语言循环语句
Go 语言提供了以下几种类型循环处理语句:
循环类型 | 描述 |
---|---|
for 循环 | 重复执行语句块 |
循环嵌套 | 在 for 循环中嵌套一个或多个 for 循环 |
for 循环
for 循环是一个循环控制结构,可以执行指定次数的循环。
语法
Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。
和 C 语言的 for 一样:
for init; condition; post { }
和 C 的 while 一样:
for condition { }
和 C 的 for(; 一样:
for { }
- init: 一般为赋值表达式,给控制变量赋初值;
- condition: 关系表达式或逻辑表达式,循环控制条件;
- post: 一般为赋值表达式,给控制变量增量或减量。
for语句执行过程如下:
- 1、先对表达式 1 赋初值;
- 2、判别赋值表达式 init 是否满足给定条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
For-each range 循环
这种格式的循环可以对字符串、数组、切片等进行迭代输出元素。
实例:
package main
import "fmt"
func main() {
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.Println(i, s)//i是数据在数组中的序列,从0开始,s是数组中的数据的值
}
numbers := [6]int{1, 2, 3, 5}//[6]表示声明的变量包含6个序列,无数据的部分用零填充,int数据类型的零值是0
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
}
以上实例运行输出结果为:
0 google
1 runoob
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0
循环嵌套
Go 语言允许用户在循环内使用循环。接下来我们将为大家介绍嵌套循环的使用。
语法
以下为 Go 语言嵌套循环的格式:
for [condition | ( init; condition; increment ) | Range]
{
for [condition | ( init; condition; increment ) | Range]
{
statement(s);
}
statement(s);
}
实例1:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var i, j int
for i=2; i < 12; i++ { //素数是大于1的自然数,1既不是素数也不是合数,所以要从2开始
for j=2; j <= (i/j); j++ {
//println(i,j,i/j)
if(i%j==0) {
break // 如果发现因子,则不是素数,跳出循环
}
}
if(j > (i/j)) {
//fmt.Println(i,j,i/j)
fmt.Printf("%d 是素数\n", i)
}
}
}
以上实例运行输出结果为:
2 是素数
3 是素数
5 是素数
7 是素数
11 是素数
实例1:
package main
import "fmt"
func main() {
for m := 1; m < 10; m++ {
/* fmt.Printf("第%d次:\n",m) */
for n := 1; n <= m; n++ {
fmt.Printf("%dx%d=%d ",n,m,m*n)
}
fmt.Println("")
}
}
以上实例运行输出结果为:
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
Go 循环控制语句
循环控制语句可以控制循环体内语句的执行过程。
GO 语言支持以下几种循环控制语句:
控制语句 | 描述 |
---|---|
break 语句 | 经常用于中断当前 for 循环或跳出 switch 语句 |
continue 语句 | 跳过当前循环的剩余语句,然后继续进行下一轮循环。 |
goto 语句 | 将控制转移到被标记的语句。 |
break 语句
Go 语言中 break 语句用于以下两方面:
- 用于循环语句中跳出循环,并开始执行循环之后的语句。
- break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
- 在多重循环中,可以用标号 label 标出想 break 的循环。
语法
break 语法格式如下:
break;
实例:
package main
import "fmt"
func main() {
// 不使用标记
fmt.Println("---- break ----")
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
break
}
}
// 使用标记
fmt.Println("---- break label ----")
re://这个re只是一个label名称,可以为任意字符串,只要上下两个一样即可,比如这里也可以用xiang:,下面用break xiang
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
break re //直接从re:标识的位置跳出for循环
}
}
}
以上实例执行结果为:
---- break ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11
---- break label ----
i: 1
i2: 11
continue 语句
Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。
for 循环中,执行 continue 语句会触发 for 增量语句的执行。
在多重循环中,可以用标号 label 标出想 continue 的循环。
语法
continue 语法格式如下:
continue;
实例:
package main
import "fmt"
func main() {
// 不使用标记
fmt.Println("---- continue ---- ")
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
continue /*跳出内部的的for循环,继续执行内部for循环的下一个循环,其实这里加不加这个continue输出的结果是一样的
因为程序默认就是先执行完内部的循环后再去执行外面的循环*/
}
}
// 使用标记
fmt.Println("---- continue label ----")
re:
for i := 1; i <= 3; i++ {
fmt.Printf("i: %d\n", i)
for i2 := 11; i2 <= 13; i2++ {
fmt.Printf("i2: %d\n", i2)
continue re/*这里是继续执行re:标记位置的循环,也就是最外层的for循环,所以内部的for循环只会执行一次*/
}
}
}
以上实例执行结果为:
---- continue ----
i: 1
i2: 11
i2: 12
i2: 13
i: 2
i2: 11
i2: 12
i2: 13
i: 3
i2: 11
i2: 12
i2: 13
---- continue label ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11
goto 语句
Go 语言的 goto 语句可以无条件地转移到过程中指定的行。
goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。
但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。
语法
goto 语法格式如下:
goto label;
..
.
label: statement;
实例:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 10
/* 循环 */
LOOP1: for a < 20 { //声明一个LOOP1标签,LOOP1这个名称可以自定义
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP1 //跳转到LOOP1标签
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}
以上实例执行结果为:
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19
无限循环
如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环。
实例:
package main
import "fmt"
func main() {
wuxian()
}
func wuxian() {
for true {
fmt.Printf("这是无限循环。\n");
}
}
以上实例执行结果为:
这是无限循环。
这是无限循环。
这是无限循环。
这是无限循环。
这是无限循环。
...
Go 语言函数
函数是基本的代码块,用于执行一个任务。
Go 语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,返回类型,和参数。
Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。
函数定义
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
函数定义解析:
- func:函数由 func 开始声明
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型(注意这里返回的不是值,而是值的类型),函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
函数调用
当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。
实例:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
var ret int
/* 调用函数并返回最大值 */
ret = max(a, b)//这里是通过调用函数的方式,将变量a和b的值传入函数max,然后将函数max返回的结果赋值给变量ret
fmt.Printf( "最大值是 : %d\n", ret )
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 定义局部变量 */
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
以上实例在 main() 函数中调用 max()函数,执行结果为:
最大值是 : 200
函数返回多个值
Go 函数可以返回多个值。
实例:
package main
import "fmt"
func swap(x, y string) (string, string) { //声明函数swap包含x和y两个参数,返回结果为2个字符串类型的值
return y, x
}
func main() {
a, b := swap("Google", "Runoob") //通过调用函数swap的方式,将变量x的值和y的值传入,并将返回结果的值赋予到变量a和b
fmt.Println(a, b)
}
以上实例在 main() 函数中调用 swap()函数,执行结果为:
Runoob Google
函数参数
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数:
传递类型 | 描述 |
---|---|
值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 |
引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
值传递值
值传递方式传递参数的值,调用过程中不会影响到实际参数的值。
实例:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %d\n", a ) // 调用函数前a的值是100
fmt.Printf("交换前 b 的值为 : %d\n", b ) // 调用函数前b的值是200
/* 通过调用函数来交换值 */
swap_ab(a, b)
fmt.Printf("交换后 a 的值 : %d\n", a ) // 调用函数后a的值仍然是100
fmt.Printf("交换后 b 的值 : %d\n", b ) // 调用函数后b的值仍然是200
}
/* 定义相互交换值的函数 */
func swap_ab(x, y int) int {
var temp int
/* 以下3行的代码也可以直接写成:x, y = y, x 这样的方式 */
//x,y = y,x
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp
}
以上实例在 main() 函数中调用 swap_ab()函数,执行结果为:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
引用传递值
引用传递指针参数传递到函数内。
实例:
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前,a 的值 : %d\n", a )
fmt.Printf("交换前,b 的值 : %d\n", b )
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap_ab(&a, &b) // 调用函数swap_ab,注意看这里传参是用的变量a和b指针地址
fmt.Printf("交换后,a 的值 : %d\n", a )
fmt.Printf("交换后,b 的值 : %d\n", b )
}
func swap_ab(x *int, y *int) *int { // 定义函数参数时,使用指针类型,在传参是必须用指针类型来传参
var temp int
//*x,*y = *y,*x //下面三行也可以这样写
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
return &temp
}
以上实例在 main() 函数中调用 swap_ab()函数,执行结果为:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
函数用法
Go语言函数有以下三种用法。
函数用法 | 描述 |
---|---|
函数作为另外一个函数的实参 | 函数定义后可作为另外一个函数的实参数传入 |
闭包 | 闭包是匿名函数,可在动态编程中使用 |
方法 | 方法就是一个包含了接受者的函数 |
函数作为实参
Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。
以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数。
实例1:
package main
import (
"fmt"
"math"
)
func main(){
/* 声明函数变量 */
//getSquareRoot := func(x float64) float64 {
var getSquareRoot = func(x float64) float64 { // getSquareRoot是函数名称,右边的两个语句分别是函数的参数和返回值类型
return math.Sqrt(x) // 计算x的平方,也即是计算√x的值
}
/* 使用函数 */
fmt.Println(getSquareRoot(9))
fmt.Println(test(16))
}
func test(y float64) float64{ //上面getSquareRoot函数的写法和test函数这种写法效果是一样的
return math.Sqrt(y)
}
以上实例在 main() 函数中调用 getSquareRoot()和test()函数,执行结果为:
3
4
实例2(把函数作为参数来传递,实现回调):
package main
import (
"fmt"
)
// 声明一个函数类型
type cb func(x,y int) (int,int) // 写成这样时:type cb func(int) int,表示声明一个只传入一个int类型的参数,返回一个int类型的结果的函数类型
func main() {
/* 调用testCallBack函数将值1和2传值给第参数x和y,然后再通过第二个参数调用函数callBack,将x和y的值传给函数callBack中的参数x
和y实现通过参数调用函数*/
testCallBack(1,2, callBack)
/* 下面这句是直接将第二个参数写成了函数的形式,跟上面调用函数的方式实现的效果是一样的 */
testCallBack(3,4, func(x,y int) (int,int) {
fmt.Printf("我是回调,x:%d\n我是回调,y:%d\n", x,y)
return x,y
})
}
func testCallBack(x,y int, f cb) {
f(x,y) // 这一句相当于var f = func(x,y int) (int,int)
}
func callBack(x,y int) (int,int) {
fmt.Printf("我是回调,x:%d\n我是回调,y:%d\n", x,y) // 打印x的值
return x,y
}
以上代码执行结果为:
我是回调,x:1
我是回调,y:2
我是回调,x:3
我是回调,y:4
函数闭包
Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。
匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
实例1(变量在函数表达式里面时):
package main
import "fmt"
/* 声明一个闭包函数,getSequence的返回类型是 func() (int,int) 这个函数 */
func getSequence() func() (int,int) {
x:=0
/* getSequence函数的返回值是func() (int,int) ,func() int函数的返回值是x,y,
这里可以看到x是在函数getSequence中定义的,y是在函数中的函数定义的,x是全局变量,
y是局部变量 */
return func() (int,int) {
y:=0 // 变量y在函数的返回函数中被赋值,每次函数被调用时,y的值会被重置
x++
y++
return x,y
}
}
func main(){
/* 以函数getSequence()作为模板声明nextNumber 为一个函数
类似java中的实例对象,可以把getSequence函数看成一个类,nextNumber函数看成getSequence函数的一个实例化对象,
*/
nextNumber := getSequence()
/* 调用 nextNumber 函数,每次调用函数时 x 变量的值自增 1 并返回,y变量值被重置 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,创建新的函数后,x 变量的值会被重置为0,然后在调用时自增 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
以上代码执行结果为:
1 1
2 1
3 1
1 1
2 1
实例2(变量在函数参数里面时):
package main
import "fmt"
/* 声明一个闭包函数,分别在函数的参数和函数的返回值函数的参数中赋值 */
/* 这里可以看到a和是在函数getSequence中定义的,b是在函数中的函数定义的,a是全局变量,
b是局部变量*/
func getSequence(a int) func(b int) (int,int) {
return func(b int) (int,int) {
a++ // 每次调用函数时a会自增1
b++ // 每次调用函数时b会重置为传入的值,然后自增1
return a,b
}
}
func main(){
/* 以函数getSequence()作为模板声明nextNumber 为一个函数
类似java中的实例对象,可以把getSequence函数看成一个类,nextNumber函数看成getSequence函数的一个实例化对象,
*/
nextNumber := getSequence(1) // 这里的1是赋值给变量a的
/* 调用 nextNumber 函数,每次调用函数时 a 变量的值自增 1(a++) 并返回,b变量值不变 */
fmt.Println(nextNumber(2)) // 这里的2是赋值给变量b的
fmt.Println(nextNumber(2))
fmt.Println(nextNumber(2))
/* 创建新的函数 nextNumber1,创建新的函数后,a 变量的值会按照新传入的值计算 */
nextNumber1 := getSequence(3)
fmt.Println(nextNumber1(4))
fmt.Println(nextNumber1(4))
}
以上代码执行结果为:
2 3
3 3
4 3
4 5
5 5
函数方法
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。
所有给定类型的方法属于该类型的方法集。
语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
实例1:
package main
import (
"fmt"
)
/* 定义 Circle 类型结构体 */
type Circle struct {
radius float64 //声明一个radius属性
}
func main() {
var c1 Circle //定义一个 Circle 类型对象: c1
c1.radius = 10.00 //radius是Circle 类型对象中的一个属性
// fmt.Println("圆的面积 = ", c1.getArea()) //调用getArea()方法
// fmt.Println("圆的周长 = ", c1.getRound()) //调用getRound()方法
fmt.Printf("圆的面积 = %0.0f\n圆的周长 = %0.0f\n", c1.getArea(), c1.getRound()) //格式化字符串,只保留结果的整数部分
}
//下面的语句是为 Circle 类型对象写一个getArea()方法,该 method 属于 Circle 类型对象中的方法
//这里的变量c1也可以是其他名称,但为了保持代码的易读性,所有Circle 类型对象中的方法尽量使用一样的
func (c1 Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c1.radius * c1.radius
}
//定义一个getRound()方法
func (c1 Circle ) getRound() float64{
return 3.14 * c1.radius * 2
}
以上代码执行结果为:
圆的面积 = 314
圆的周长 = 63
实例2(使用指针在方法中改变结构体类型中的属性的值):
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64 //为 Circle 类型声明一个属性radius
}
func main() {
var c Circle
fmt.Println(c.radius) //属性radius没有赋值时,打印该属性类型的初值,float64类型的变量初值为0
c.radius = 10.00
fmt.Println(c.getArea()) //调用getArea()方法
c.changeRadius(20) //向changeRadius(radius float64)方法中的参数传值
fmt.Println(c.radius) //打印c.radius属性的值,因为该方法传递的是指针,所以属性值会变成传递进来的值
c.changeRadius2(30) //向changeRadius2(radius float64)方法中的参数传值
fmt.Println(c.radius) //打印c.radius属性的值,因为该方法传递的不是指针,所以该属性值不会变成传递进来的值
change(&c, 40) //调用change()方法,并向该方法传值
fmt.Println(c.radius) //打印打印c.radius属性的值,因为该方法传递的是指针,所以属性值会变成传递进来的值
}
func (c Circle) getArea() float64 {
return c.radius
}
// 注意如果想要更改成功c的值,这里需要传指针
func (c *Circle) changeRadius(radius float64) {
c.radius = radius
}
// 以下操作将不生效
func (c Circle) changeRadius2(radius float64) {
c.radius = radius
}
// 引用类型要想改变值需要传指针
func change(c *Circle, radius float64) {
c.radius = radius
}
以上代码执行结果为:
0
10
20
20
40