语法注意事项
- 源文件以go为扩展名
- 程序执行入口是main()函数
- 严格区分大小写
- 方法由一条条语句构成,每个语句后不需要分号(go语言会在每一行后面自动加分号)
- go编译器是由一行行进行编译的,因此一行就写条语句,不能把多条语句写在同一行
- 定义的变量或者import 的包如果没有使用到,代码不能编译通过
- 大括号都是成对出现的,缺一不可
- 一行最长超过80个字符,超过要换行
fmt.Println("aaaaaaa","aaaaaaaa","aaaaaaaa")
go语言结构
go语言的基础组成
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
package main //包声明 import "fmt" //引入包 func main(){ //程序执行函数 fmt.Println("Hello World") //输出内容 }执行go程序
在终端上执行 go run go文件
用go build 命令生成二进制文件
go语言基础语法
go标志
go程序由许多标志组成,关键字、标志符、常量、字符串、符号
行分割
go程序每一行代表一个语句,在编译的时候自动生成;号。故不用加分号
多个语句不可以在同一行,若要在同一行需要分号隔开
注释
行注释: // 快捷键: CTRL + ?
块注释:/* 内容 */
标识符
标识符用来命名变量、类型等程序实体
无效标识符:
- 以数字开头
- go语言关键字
- 有运算符
字符串连接
通过+号连接
fit.Println("hello"+"world")关键字
var 定义变量 //可以省略声明类型
var a int
var b string
var c byte
const 定义常量
const a int
const b string
const c byte
go语言空格
空格常用于分割标识符、关键字、运算符、和表达式,增加代码的可读性
也可以不加空格
格式化字符串
Sprintf
生成格式化字符串后返回该字符串
var a=fmt.Sprintf("a:%d&&b:%s",1,"sss") //先格式化再写入a中 fmt.Println(a) //输出aPrintf
生成格式化字符穿后写入标准输出
fmt.Printf("a:%d&&b:%s",1,"sss") //直接输出:= //新建变量加赋值
= //只有赋值
go语言数据类型
数据类型用于声明函数和变量
布尔型
是可以是常量true 或者 false
var b bool = true
数字类型
整数型
无符号类型
uint8 uint16 uint32 uint64
有符号类型
int8 int 16 int 32 int64
可以直接用int
浮点型
float32 float64
复数型 complex64 complex128
注意:必须标注位数
其他数字类型
byte 类似于 uint8
rune 类似于int32
int 与uint一样大
uintptr 无符号类型,用于存放一个指针
字符串类型
使用uft-8编码标识Unicode文本
派生类型
- 指针类型Pointer
- 数组类型
- 结构化类型struct
- Channel类型
- 函数类型
- 切片类型
- 接口类型
- Map类型
go语言变量
变量名由数字、下划线、字母组成,数字不能放在首位
声明变量的一般形式使用var关键字
var 变量名 变量类型
一次可以声明多个变量
var 变量1,变量2,... 变量类型
变量声明
指定变量类型,如果没有初始化,则变量默认为零值
默认值
- 数值类型为0
- 布尔类型为false
- 字符串为“”(空字符串)
- 其他为 nil
根据值自行判定变量类型
var v_name = value
使用:=声明变量
v_name := value 相当于 var v_name; v_name = value;
注意:如果变量已经使用var声明过了,在使用 := 声明变量就会产生编译错误
多变量声明
//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
// 和 python 很像,不需要显示声明类型,自动推断
var vname1, vname2, vname3 = v1, v2, v3
// 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
vname1, vname2, vname3 := v1, v2, v3
// 这种因式分解关键字的写法一般用于声明全局变量
var (
vname1 v_type1
vname2 v_type2
)
值类型与引用类型
值类型
使用等号,将一个变量的值赋值给另一个变量
引用类型
引用类型的变量储存的是变量的值所在的存储地址,或存储地址中第一个字所在的位置
这个内存地址称为指针
同一个引用类型的指针的多个字可以是在连续的内存地址中,也可以将这些字分散在内存中
引用类型赋值是将地址复制,如果被引用变量的值修改了,所有引用他的值也会被修改
go语言常量
常量是一个简单值的标识符,在程序运行是不可以修改
可以是 数字型、布尔型、字符串型
基本语法与参数
常量定义格式: const 常量名 类型 = 常量值
类型可以省去
定义方式
- 显示类型定义:const b string = "abc:"
- 隐式类型变量:const b = "abc"
- 批量定义:const a , b , c = v1 , v2 , v3
常量组与枚举
go语言没有枚举关键字,通常使用常量组来实现枚举效果
package main import "fmt" //定义枚举 const ( unknown = iota female male ) func main() { fmt.Printf("未知=%d,女性=%d,男性=%d", unknown, female, male) }内置函数在使用中的应用
常量表达式中可以使用 len() , cap() , unsafe.Sizeof() 等内置函数计算表达式的值
注意:常量表达式的值必须是内置函数
iota常量计数器
可以在 const 声明块中自主生成递增的序列值
重要特性
- iota 在 const 关键字出现时将被重置为 0
- const 中每新增一行常量声明,iota 自动计数加一
- const 语句块中的索引从0开始
package main import "fmt" func main() { const ( a = iota // 0 b // 1 c // 2 d = "ha" // 独立值,iota += 1 e // "ha",iota += 1 f = 100 // 独立值,iota += 1 g // 100,iota += 1 h = iota // 7,恢复使用 iota 值 i // 8 ) fmt.Printf("a=%d, b=%d, c=%d, d=%s, e=%s, f=%d, g=%d, h=%d, i=%d\n", a, b, c, d, e, f, g, h, i) }package main import "fmt" const ( i = 1 << iota // 1 << 0 = 1,1*2^0 j = 3 << iota // 3 << 1 = 6, 3*2^1 k // 3 << 2 = 12, 3*2^2 l // 3 << 3 = 24 , 3*2^3 ) func main() { fmt.Printf("i=%d, j=%d, k=%d, l=%d\n", i, j, k, l) }go语言运算符
1.算数运算符
自增 : ++
自减 : --
2.关系运算符
3.逻辑运算符
与:&&
或:||
非:!
4.位运算符
运算符 | 描述 |
& | 参与运算的两个数对应的二进制位相与 |
| | 参与运算的两个数对应的二进制位相或 |
^ | 参与运算的两个数对应的二进制位相异时,结果为1 |
<< | 左移运算符,左移n位是乘以2的n次方。 是将数的二进制全部左移n位,高位丢弃,低位补0 |
>> | 右移运算符,右移n位是除以2的n次方。 是将数的二进制全部右移n位,低位丢弃,高位补0 |
5.赋值运算符
= 简单的赋值运算
运算后赋值的有:+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
6.其他运算符
& 返回变量存储地址 //&a 将给出变量的实际地址
*指针变量 //*a 是一个指针变量
7.运算符优先级
优先级 | 运算符 |
5 | */ % << >> & &^ |
4 | +- | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
通过使用括号来临时提升某个表达式的整体运算优先级
go语言条件语句
if语句
if else 语句
go特有
if里面可以先定义变量,变量仅在if/else里有效
if num := 20; num > 10 { fmt.Println("大于10") } else { fmt.Println("小于等于10") }if 嵌套语句
switch 语句
基础特点
- go 的 switch 自带 break,匹配后自动结束,不用手写break
- 支持多条件匹配无表达式 、无表达式、fallthrough 穿透
- case 可以放表达式、变量、常量
基础语法
switch 变量 { case 值1: 逻辑1 case 值2, 值3: // 多个条件满足其一就匹配 逻辑2 default: // 都不匹配执行 默认逻辑 }无表达式 switch
类似于 if-else 多判断
switch 后不写变量,等价于 switch true。 case 里写布尔判断
case中初始化变量
switch 开头可以定义临时变量
fallthrough 穿透
go 默认不穿透,fallthrough 会强制执行下一个case
select 语句
select 语句类似于 switch 语句, 但是select会随机执行一个可运行的case。如果case可运行,
他将会阻塞,直到有case可运行
基础语法
select { case 通道读操作: // 读就绪执行 case 通道写操作: // 写就绪执行 default: // 所有通道都未就绪时执行(非阻塞) }有default 时select 可能会发生阻塞
没有default 时select 不会发生阻塞
超时控制
使用 time.After 防止 select永久阻塞
package main import ( "fmt" "time" ) func main() { ch := make(chan string) select { case res := <-ch: fmt.Println(res) case <-time.After(2 * time.Second): fmt.Println("等待2秒超时") } }go语言循环语句
循环类型
for 循环
循环嵌套
循环控制语句
控制语句 | 描述 | |
break语句 | 终断当前的for循环或跳出switch语句 | |
continue语句 | 跳出当前循环的剩余语句,然后继续进行下一个循环 | |
goto 语句 | 将控制转移到被标记的语句 | |
goto语句
- 基础定义
无条件跳转语句,直接跳转到同函数内只定义的标签(label)所在代码行,直线控制流转移
package main import "fmt" func main() { i := 0 LOOP: // 定义标签 fmt.Println(i) i++ if i < 3 { goto LOOP // 跳转到LOOP标签 } fmt.Println("结束") }- 关键约束
1.只能在同一个函数里跳转,不能跳转到别的函数
2.不允许跳过变量声明
3.不能从代码外层跳进 if 、 for 等代码块内部
4.允许从循环/判断内部,跳出到外部标签 - 实用的应用场景
break 只能跳出当前的一层循环
goto 可直接终止所有的嵌套循环
无限循环
package main import "fmt" func main(){ for true{ fmt.Pritf("这是无限循环") } }go语言函数
函数是基本的代码块,用于执行一个任务
go语言至少有个main函数
函数的声明需要告诉编译器函数的名字,返回类型,和参数
go语言标准库有许多内置函数
函数的定义
go语言函数格式
func function_name( [parameter list] ) [reture_types ] {
函数体
}
- func : 函数由func 开始声明
- function_name:函数名称,参数列表和返回值构造了成了函数签名
- paramater_list :参数列表
- return_types: 返回值类型 //有些函数不需要返回值,不是必须的
- 函数体:函数定义的代码聚合
package main import "fmt" func max1(a, b int) int { if a > b { return a } else { return b } } func main() { var maxn int maxn = max1(1, 2) fmt.Println(maxn) }函数调用
调用函数时,向函数传递参数,并返回值
同一个go文件内
函数的调用无先后顺序
同一个包下跨文件调用
同一个包下的不同文件下,无需前置声明,可以直接调用
函数首字母权限:
- 首字母小写:包私有,进当前包所有文件可调用
- 首字母大写:导函数,当前包,外部其他包均可以调用
挎包调用
满足三个条件:
- 被调用包的函数名首字母大写
- 调用函数的文件要导入目标包 // import "项目下包的路径"
- 调用格式: 包名.函数名()
函数参数
函数使用的参数,该变量称为函数形参
形参就像是定义在函数内的局部变量
值传递
在调用函数时,将实际参数复制一份传递到函数上,在函数中对参数修改,不过会影响实际参数
引用传递
核心定义
引用传递本质:吧实参的内存地址传递给函数形参
函数内通过地址修改内存里的数据,原始实参的值会用不发生改变
两个关键符号
- &变量: 去变量内存地址
- *指针变量: 解引用,操作地址对应的的值
package main import "fmt" // 参数是 int 指针,接收地址 func changeNum(n *int) { *n = 999 // 通过地址修改原始变量的值 } func main() { a := 10 changeNum(&a) // &a 取出a的地址传入函数 fmt.Println(a) // 输出 999,原值被修改 }函数用法
作为另一个函数的实参
go中的函数可以像普通函数一样:定义、赋值、当作参数传递给其他函数、作为返回值返回
把一个函数传递入另一个函数的参数位置,就是“函数做实参”
- 先定义函数类型
格式: type 类型名 func(参数类型) 返回值类型
// 定义函数类型 calc // 规则:必须接收2个int参数,返回1个int type calc func(int, int) int- 目标函数声明该类型参数
// compute就是目标函数,第三个参数f的类型是calc func compute(a, b int, f calc) int { return f(a, b) } //f calc: 代表参数f 只能满足calc格式的函数- 调用时满足格式的函数传出去
前提要有一个完全匹配calc模板的普通函数
再把add直接当作第三个实参传递给目标函数
package main import "fmt" // 第一步:定义函数类型,定好函数格式标准 type calc func(int, int) int // 第二步:目标函数,声明calc类型的形参f func compute(a, b int, f calc) int { return f(a, b) } // 符合calc格式的普通函数 func add(x, y int) int { return x + y } func main() { // 第三步:调用,传入满足格式的add函数 res := compute(2, 3, add) fmt.Println(res) }闭包与匿名函数
匿名函数
没有函数名的函数就叫匿名函数
// 格式1:定义后立刻调用 func(参数) 返回值 { 逻辑 }(传入实参) // 格式2:赋值给变量保存 变量名 := func(参数) 返回值 { 逻辑 }闭包
定义:捕获了外层局部变量的匿名函数
两个必要条件:
- 本身时匿名函数
- 匿名函数能访问、修改外层函数的局部变量,延长变量生名命周期
核心特点:闭包会记住外层变量的环境,哪怕外层函数执行完毕,变量也不会销毁
闭包格式
// 外层函数,返回值为函数类型 func 外层函数名() func(形参) 返回类型 { //闭包类型的返回值类型: func(形参) 返回值类型 // 外层局部变量(被闭包捕获) 变量 := 初始值 // 返回匿名函数(闭包) return func(形参) 返回类型 { // 操作外层变量 变量++ return 变量 } }package main import "fmt" // 外层函数,返回一个闭包 func counter() func() int { num := 0 // 外层局部变量 // 返回匿名函数(闭包,捕获num) return func() int { num++ //使用并修改了匿名函数外的局部变量 return num } } func main() { f := counter() fmt.Println(f()) // 1 fmt.Println(f()) // 2 fmt.Println(f()) // 3 f2 := counter() fmt.Println(f2()) // 1(全新独立变量num) }方法
定义:本质是绑定了接受者的函数
格式
func (接收者变量 接收者类型) 方法名 (参数列表) 返回值列表{
方法逻辑
}
接收者:代表这个方法属于哪一种自定义类型(结构体,基础自定义类型)
调用方式:变量.方法名
接受方式
- 值接收
package main import "fmt" type Person struct { name string age int } // 值接收者 func (p Person) rename(newName string) { p.name = newName // 修改的是拷贝出来的副本 } func main() { man := Person{name: "小明", age: 18} man.rename("小红") fmt.Println(man.name) // 输出:小明,原值没变 }- 指针接收
// 指针接收者 func (p *Person) grow() { p.age++ // 直接修改原结构体 } func main() { man := Person{name: "小明", age: 18} man.grow() fmt.Println(man.age) // 输出:19,原值被修改 }go语言变量作用域
局部变量
在函数体内声明的变量称为局部变量,只作用于函数体内
参数和返回值变量也是局部变量
全局变量
在函数体外面的变量称为全局变量,
全局变量可以在整个包使用
注意:全局变量与局部变量名字是相同的,但是函数内部的变量会优先考虑
go语言数组
声明数组
格式:var arrayName [size]dataType
初始化数组
默认初始化
var numbers [5]int //默认初始值都为0
var numbers = [5]int{1,2,3,4,5}
numbers := [5]int{1,2,3,4,5}
注意:数组的大小是类型的一部分,因此不同大小的数组是不兼容的
如果数组的长度不确定,可以使用...来代替长度,编译器会根据元素的个数判断长度
访问数组元素
通过循环遍历数组元素
go语言指针
指针变量指向的是一个值的变量地址
指针:存储变量内存地址的变量
两个关键符号
- &变量: 取变量内存地址
- *指针变量: 解引用,操作地址对应的的值
指针使用流程
定义指针变量
使用前需要声明指针,格式:
var var_name *var-tpye
- var_name 为指针变量名
- *用于指定变量是作为一个指针
- var-type 为指针类型 *int指向整数型 *float32 指向浮点型
为指针变量赋值
p = &num
&变量:取变量内存地址,把地址存进指针 p
访问指针变量的值
*指针变量:根据指针存放的地址,取出对应对应内存里的数据
package main import "fmt" func main() { var a int= 20 /* 声明实际变量 */ var ip *int /* 声明指针变量 */ ip = &a /* 指针变量的存储地址 */ fmt.Printf("a 变量的地址是: %x\n", &a ) /* 指针变量的存储地址 */ fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) /* 使用指针访问值 */ fmt.Printf("*ip 变量的值: %d\n", *ip ) }空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil
nil 的指针也为空指针
nil 指的是零值或空值
指针数组
概念:数组中每个元素都是指针,统一类型 *T
package main import "fmt" func main() { a, b, c := 10, 20, 30 // 定义指针数组,3个*int元素 var ptrArr [3]*int = [3]*int{&a, &b, &c} // 遍历打印地址、解引用值 for i := 0; i < 3; i++ { fmt.Printf("地址:%p,值:%d\n", ptrArr[i], *ptrArr[i]) } // 通过指针修改原变量 *ptrArr[1] = 999 fmt.Println(b) // 999 }二级指针
指向指针的指针
普通变量->一级指针存放变量地址->二级指针存放一级指针地址
语法: var pp **int
package main import "fmt" func main() { num := 66 var p *int = &num // 一级指针 var pp **int = &p // 二级指针,存p的地址 fmt.Println("num原值:", num) fmt.Println("p存储地址:", p) fmt.Println("pp存储p的地址:", pp) fmt.Println("*pp = p:", *pp) fmt.Println("**pp = num:", **pp) // 二级指针修改原始值 **pp = 888 fmt.Println(num) // 888 }函数传递指针参数
值传递:函数内修改只是副本,外部不变
指针传递:传变量地址函数内解引用直接修改外部原始数据
package main import "fmt" // 接收int指针 func changeVal(x *int) { *x = 1000 // 修改原变量 } func main() { n := 10 changeVal(&n) // 传入地址 fmt.Println(n) // 1000 }go语言结构体
结构体是由一系列具有相同类型或不同类型的数据构成的数据的 集合
定义结构体
type 结构体名字 struct { meber definition meber definition meber definition ... }定义结构体后就可以用于结构声明,格式如下
variable_name := 结构体名字 {value1,value2,value3...} variable_name := 结构体名字 { key1: value1, key2 : value2, key3 : value3 }访问结构体成员
格式为:结构体.成员名
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 信息 */ fmt.Printf( "Book 1 title : %s\n", Book1.title) fmt.Printf( "Book 1 author : %s\n", Book1.author) fmt.Printf( "Book 1 subject : %s\n", Book1.subject) fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id) /* 打印 Book2 信息 */ fmt.Printf( "Book 2 title : %s\n", Book2.title) fmt.Printf( "Book 2 author : %s\n", Book2.author) fmt.Printf( "Book 2 subject : %s\n", Book2.subject) fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id) }结构体作为函数参数
可以将结构体类型作为参数传递给函数,
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) }结构体指针
可以定义结构体的指针类似与其他指针变量
var struct_pointer *Books
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) }go语言切片
切片 slice 十动态数组,基于数组封装,长度可变,底层引用数组
go语言切片是对数组的抽象,是动态数组
切片的长度是不固定的,可以追加元素,在追加时可能是切片的容量增大
定义切片
- 可以使用为指定大小的数组来定义切片
var identifier []type - 使用 make()函数来创建切片
var slice1 []type =make([]type, len)
slice1 : make([]type, len) - 可以指定容量,其中 capacity 为可选参数
make([]type,len,capacity)
这里 len 是数组的长度也是原始长度,capacity 为切片容量
切片初始化
arr := []int {1,2,3,4}
切片: s:= arr[start : end : max] //arr[start : end]为左开右闭
len 与 cap 函数
切片是可以索引的,并且可以由len()方法获取长度
cap()方法可以计算容量
append 和 copy 函数
append为追加函数
nums = append( nums , a , b, c) //在nums后面追加a,b,c
copy 为拷贝函数
copy ( nums1 , nums2 ) //拷贝 nums2的内容到nums1