Go 语言基础 学习笔记
一. go语言的特点
-
既有静态编译语言的安全和性能,又拥有动态语言开发维护的高效性
即:go = c + python。 -
从c语言中继承了很多的理念(表达式语法、控制结构、基础的数据类型、调用参数传值、指针等等),保留了和c语言一样的编译执行方式及弱化的指针。
-
引入了包的概念,用于组织程序结构,go语言的一个文件都要归属于一个包,而不能单独的存在。
-
垃圾回收机制,内存自动回收,不需要开发人员管理。程序员只需要关注业务逻辑
-
天然并发(重要特点)
(1)从语言的层面支持并发,实现简单。
(2)go routine,轻量级线栈,可以实现大并发处理,高效利用多核。
(3)基于cps并发模型实现。 -
吸收了管道通信机制,形成Go语言特有的管道channel,通过管道channel,可以实现不同的goroute之间的相互通信。
-
函数可以返回多个值。
-
创新点:切片、延时执行defer等
总结:Go 语言最主要的特性:
-
自动垃圾回收
-
更丰富的内置类型
-
函数多返回值
-
错误处理
-
匿名函数和闭包
-
类型和接口
-
并发编程
-
反射
-
语言交互性
二、 Go语言环境配置
-
SDK下载地址:https://studygolang.com/dl
-
环境变量配置:
环境变量 | 说明 |
---|---|
GOROOT | 指定的SDK的安装路径 |
Path | 添加SDK的/bin目录 |
GOPATH | 工作目录(go项目的目录) |
- 项目结构
三、Go语言的基本代码构成
-
go程序后缀名 .go
-
一个hello world 程序示例
其中:
(1)package main:表示该hello.go 文件所在的包是main, 在go语言中,每个文件都归属于一个包。
(2) import"fmt":表示引入一个包,名字叫做fmt, 引入该包后就可以使用该包里的函数,比如后面用到的fmt.Println()。
(3) func main(){} :其中func是一个关键字,表示声明一个函数;main 是函数名,一个主函数,是整个程序的入口。
- go程序的编译
**两种方法的区别:**如果使用第一种先go build的方法编译形成.exe 文件,可以将该程序拷贝到没有go环境的系统运行,第二种方法则不可以在没有go环境的系统执行。
四、第一个go程序 hello go
1、Go 语言的基础组成
-
包声明
-
引入包
-
函数
-
变量
-
语句 & 表达式
-
注释
package main
import "fmt"
/*主函数*/
func main() {
/*输出*/
fmt.Println("hello go!")
}
// 注意:{不能独占一行,是会报错的
/*
func main()
{ // 报错
fmt.Println("hello go!")
}
*/
以上程序的各个部分:
- 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
- 下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
- 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
- 下一行 /…/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 / 开头,并以 / 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
- 下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
使用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。 - 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
2、 import的使用
-
相对路径
import "./model" //当前文件夹同一目录的model目录 一般不建议使用
-
绝对路径
import "shorturl/model" //加载GOPATH/src/shorturl/model
-
一些特殊的操作
import ( ."fmt" // 点操作,导入该包后,调用该包的函数时,可以省略前缀的包名, fmt.print() == // Print() f "fmt" // 别名操作 f.Print() ) // _操作 其实就是引入该包,而不是直接使用包内的函数,而是调用了包内的 init 函数 import ( "database/sql" _"fithub.com/ziutek/mymysql/godrv )
五、基础语法
1、标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
2、行分隔符
在go语言中不需要行分割符。一行为一句的结束。
fmt.Println("Hello, World!")
fmt.Println("Hello Go!")
3、字符串的连接
通过+实现
package main
import "fmt"
func main() {
fmt.Println("Google" + "Runoob")
}
4、注释
// 单行注释
/*
多行注释
*/
5、关键字
关键字(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 |
预留关键字 | (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 |
6、go语言的空格
- go语言中变量的声明必须使用空格隔开
var age int;
附加:go输入输出流
标准输出
使用fmt包中的函数
1.fmt.Print()
作用:直接输出
package main
import "fmt"
func main(){
fmt.Print("你好!") // 直接输出
}
2.fmt.Println()
作用:输出并换行
package main
import "fmt"
func main(){
fmt.Println("你好!") // 输出并换行
}
3.fmt.Printf()
作用:按照指定格式输出
package main
import "fmt"
func main(){
value := 10
fmt.Printf("value = %d",value) // 按照指定的格式输出
}
标准输入
1.fmt.Scan()语法:fmt.Scan(变量地址列表)
作用:直接输入,以空格或者回车作为输入结束的标志
package main
import "fmt"
func main(){
var num int
fmt.Scan( &num)
// fmt.Scan(变量地址列表)
//此处可先简略认为在变量前加&后可变为变量的地址
fmt.Println(num)
}
同样的我们可以一次性对多个变量输入
package main
import "fmt"
func main(){
var num1 int
var num2 int
fmt.Scan( &num1,&num2)
fmt.Println(num1,num2)
}
2.fmt.Scanln()语法:fmt.Scanln (变量地址列表)
作用:直接输入,以空格或者回车作为输入结束的标志
package main
import "fmt"
func main(){
var a1 string
fmt.Scanln(&a1)
fmt.Println(a1)
}
同样的,我们也可以一次性对多个变量输入
package main
import "fmt"
func main(){
var a1,a2 string
fmt.Scanln(&a1,&a2)
fmt.Println(a1,a2)
}
3.fmt.Scanf() 语法:fmt.Scanf (“格式化字符串”, 变量地址列表)
作用:直接输入,以空格或者回车作为输入结束的标志
package main
import "fmt"
func main(){
var num int
fmt.Scanf("%d", &num)
fmt.Println(num)
}
7、格式化字符串
go语言中使用 fmt.Sprintf(格式化样式,参数列表)
或者 fmt.Printf(格式化样式,参数列表)
格式化字符串并赋值给新串
格式化样式:
格 式 | 描 述 |
---|---|
%v | 按值的本来值输出 |
%+v | 在 %v 基础上,对结构体字段名和值进行展开 |
%#v | 输出 Go 语言语法格式的值 |
%T | 输出 Go 语言语法格式的类型和值 |
%% | 输出 % 本体 |
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%d | 整型以十进制方式显示 |
%x | 整型以十六进制方式显示 |
%X | 整型以十六进制、字母大写方式显示 |
%U | Unicode 字符 |
%f | 浮点数 |
%p | 指针,十六进制方式显示 |
%s | 字符串形式 |
参数列表:
多个参数以 ,分隔,个数必须与格式化样式中的个数一一对应
实例:
package main
import ("fmt")
func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=
fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}
8、数据派生类型
派生类型:
-
(a) 指针类型(Pointer)
-
(b) 数组类型
-
© 结构化类型(struct)
-
(d) Channel 类型
-
(e) 函数类型
-
(f) 切片类型
-
(g) 接口类型(interface)
-
(h) Map 类型
-
uintptr无符号整型,用于存放一个指针
9、变量
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
注意:
-
go语言中变量的声明方法:
-
- var 变量名 类型 = 值
-
- var 变量名 = 值 (自动识别类型)
-
- 变量名 := 值(此变量名必须从未声明过,此方法只在函数体中使用)
-
- 多变量也可以一起声明,一同赋值, 见下程序示例
-
局部变量声明了必须使用,而全局变量允许声明但不使用
package main
/**
变量的定义及使用
*/
import "fmt"
func main() {
/*
定义方法:
1、var定义
var 变量名 类型 = 值
var 变量名 = 值 (自动识别类型)
*/
var a,b int = 8,9
fmt.Println(a,b)
var c string = "hello world"
fmt.Println(c)
var d bool = true
var e = false
fmt.Println(d,e)
/*
2、 := 声明变量 出现在声明符号左侧的变量不应该是已经被声明过得变量
这种不带声明格式的方法 只能在函数体里面出现,不能用于声明全局变量
name:="张三"
这种形式就等同于
var name string
name = "张三"
*/
name := "zhangsan"
fmt.Println(name)
/*
当不给变量指定值时,则会出现以下情况
int默认为0
string默认为 ""
bool默认为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 是接口
*/
/*
3、多变量的声明
*/
// 类型相同的多个变量。非全局变量
var name1,name2,name3 int = 1, 2, 3
var n1,n2,n3 = name1,name2,name3
fmt.Println(n1,n2,n3)
n5,n6,n7 := name1,name2,name3
fmt.Println(n5,n6,n7)
/*
因式分解关键字写法 一般用于声明全局变量
*/
var(
name4 string
name5 string
)
name4 = "wangwu"
name5 = "lisi"
fmt.Println(name4,name5)
}
10、常量
常量是一个简单的标识符,在程序运行时,不会被修改的量
常量中的数据类型,只可以是以下几种:
-
布尔型
-
数字型
-
字符串型
定义规则:const 变量名 [类型] = 值 //类型可选填,会自动识别类型
const name1, name2, name3 = "zhangsan", "lisi"
注意:
-
常量中还可以使用
len() , cap(),unsafe.Sizef()函数计算表达式的值
-
常量中函数必须是内置函数
-
iota常量
特殊常量,可以认为是一个可以被编译器修改的常量
iota在const中出现时,将被重置为0,没新增一行常量声明将iota计数一次。
可以将iota理解为常量语句块中的索引。
实例:
func main() {
const LENGTH int = 5
const WIDTH int = 10
var area int
const A, B, C = 1,false,"str" // 常量的多重赋值
area = LENGTH * WIDTH
fmt.Println("面积为",area)
println()
println(A,B,C)
/*
常量还可以用作枚举
*/
const(// 数字0,1,2分别代表未知,女性,男性
UnKnown = 0
Female = 1
Male = 2
)
}
11、值类型和引用类型(未完)
值类型:
所谓的值类型就是像 int, bool,string这些基本类型。使用这些类型的变量直接指向存在内存中的值,此时
var i int = 2
var j int = 3
j = i
/*
此时对已经声明的值类型,使用等号时,进行赋值操作,就是简单的吧 i 内存里的值拿出来拷贝到了 j内存中。
*/
引用类型:
引用类型就是通过 & 符号实现取地址的操作,使用值类型进行赋值,原值变了,赋予的值也不会变;
而用引用类型后,赋的不是数值,而是内存地址。这样的话,原值内存中的值改变后,赋予的值也会变化。
package main
import "fmt"
func swapNum(n *int, m *int) {
temp:= *n
*n = *m
*m = temp
}
func main() {
var i, j = 8, 9
fmt.Println("引用前的值",i,j)
swapNum(&i, &j)
fmt.Println("引用后的值",i,j)
}
// 输出结果: 8, 9
// 9, 8
/*
所以:引用类型将原值也会改变
*/
12、运算符
go语言包含的运算符雷同于其他编程语言:
-
算术运算符
-
关系运算符
-
逻辑运算符
-
位运算符
-
赋值运算符
-
其他运算符
-
- & 返回变量的存储地址 &a:将给出变量的实际地址
-
-
-
- 指针变量 *a:是一个指针变量
-
13、条件语句
(1)if语句
基本语法:
if 布尔表达式 {
/*在布尔表达式为true时执行*/
}
// 实例
var age int = 17;
if age < 18{
fmt.Println("未成年!")
}
(2)if-else语句
基本语法:
if 布尔表达式{
表达式为true时执行
}else{
表达式为false时执行
}
// 实例:
package main
import "fmt"
func main() {
var age int = 17
if age < 18 {
fmt.Println("未成年!")
} else {
fmt.Println("已经成年啦!")
}
}
// 实例2:
// 多条件
var grade float32 = 85.3
if grade < 60{
fmt.Println("不及格")
}else if (grade >= 60 && grade < 85){
fmt.Println("良好")
}else if(grade >= 85 && grade < 100){
fmt.Println("优秀")
}else{
fmt.Println("成绩输入格式不符")
}
// 实例3
// 条件嵌套
var personSex = "women"
var personAge = 16
if personSex == "women"{
if personAge > 18{
fmt.Println("是一个成年的女性")
}else{
fmt.Println("是一个未成年小女孩")
}
}
注:可以在if-else 和 else if语句内嵌套多个if条件语句
(3)Switch语句
基本语法:
// 注意:go语言中的Switch后面默认自带break;执行完一种case后将不会执行后面的case。
// 若想要继续执行下一条的case 可以通过关键字 fallthrough实现
switch var1{
case 情况1: ...
case 情况2: ...
...
default:...
}
实例:
// switch 语句
var grades = 54
switch grades / 10 {
case 6:
fmt.Println("等级为D")
case 7:
fmt.Println("等级为C")
case 8:
fmt.Println("等级为B")
case 9:
fmt.Println("等级为A")
case 10:
fmt.Println("等级为A")
default:
fmt.Println("等级为D")
}
(4)select语句
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
14、循环语句
循环中的控制语句
-
break语句:经常用于终端当前for循环或者跳出Switch语句
-
continue语句:跳过当前循环的剩余语句,然后继续进行下一轮的循环
(1)for循环
go语言中的for循环有三种模式:
- 第一种普通的for条件
for 初始变量; 条件; 赋值表达式(控制变量的增减){
循环体
}
- 第二种 类似于c语言中 while的for循环
for 条件{
循环体
}
- 第三种 类似于C语言中的for(;;)一样 不会自动停下来
for {
循环体
}
- 第四种 迭代输出数组或map等其中的值
for key, value := range oldMap {
newMap[key] = value
}
/*
其中 key为键 value为值
如果只想读取键 则 for key := range Map
如果只想读取值 则 for _,value := range Map
*/
循环的实例:
(2)goto语句
goto语句可以无条件地转移到指定的行来执行程序
实际作用:实现条件转移、构成循环、跳出循环等
基本语法:
goto lable // 跳到带有lable标签的哪一行
lable:代码块
实例:
// for循环打印乘法表
for n := 1; n < 10; n++ {
for m := 1; m <= n ; m++ {
fmt.Printf("%dx%d=%d ",n,m,n*m)
}
fmt.Println()
}
// goto语句实现乘法表的打印
for num := 1; num <10 ; num++ {
n := 1
LOOP:if n <= num{
fmt.Printf("%dx%d=%d ",num,n,num*n)
n++
goto LOOP
}
fmt.Println()
}
六、go语言函数
go语言中最少有一个main()函数,可以根据函数来划分不同的功能,逻辑上每个函数执行的是指定的任务。函数告诉了编译器函数的名称,返回值,参数。
基本写法:
func 函数名([参数列表]) [返回类型]{// 不需要返回值的函数就不用写返回类型
函数体
}
// go语言支持不定数的参数列表 变参
func myfunc(arg ...int){ // 可以传入不止一个参数
函数体
}
函数参数的传递:
-
值传递:调用函数时将实际的参数复制一份传递到函数中,函数如果对参数进行了修改,将不会影响到实际的参数。
-
引用传递:调用函数时将参数的实际地址传递到函数中来,那么在函数中对参数所进行的修改,将影响到实际参数。
1、函数中的引用传递和值传递
实例:以交换两个数的值为例
// 引用传递
func swapNum(n *int, m *int) {
temp := *n
*n = *m
*m = temp
}
// 值传递
func swapNum1(n, m int) {
temp := n
n = m
m = temp
}
func main() {
// 引用传递
var i, j = 8, 9
fmt.Println("引用前的值", i, j)
swapNum(&i, &j)
fmt.Println("引用后的值", i, j)
fmt.Println("---------------")
// 值传递
var n, m = 5, 6
fmt.Println("引用前的值", n, m)
swapNum1(n, m)
fmt.Println("引用后的值", n, m)
}
2、函数变量
// 定义函数变量
getNumSqrt := func(num float64)float64{
return math.Sqrt(num)
}
// 使用函数变量
fmt.Println(getNumSqrt(6))
3、函数作为值、类型
在go中函数也是一种变量,可以通过type来定义它,它的类型就是所拥有相同的参数,相同的返回值的一种类型。
例如:
package main
import "fmt"
type testInt func(int) bool // 声明一个函数类型
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 将声明的函数类型在此做一个参数
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
// main函数
func main() {
slice := []int{1, 2, 3, 4, 5, 6}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // isOdd函数当做值来传入
fmt.Println("odd elements of slice are:", odd)
even := filter(slice, isEven)
fmt.Println("even elements of slice are:", even)
}
七、数组
go语言数组的声明方式:
var 数组名 [size] 数组类型` 或者 `balance := [5]float32{1:2.0}
// 定义一个不定长的数组 var nums[...] int
1、数组的访问
数组素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:
// 数组的访问
numbers := [5]int{2, 4, 6, 8, 10}
for key, num := range numbers {
fmt.Println(key, "----", num)
}
for i := 0; i < len(numbers); i++ {
fmt.Printf("数组下标[%d]中存放的值为%d\n", i, numbers[i])
}
2、特殊点
- go语言中数组的初始化可以是直接放入很多的值,如下
var nums[5] int = {1,2,3,4,5}
nums := [5] int{1,2,3,4,5}
-
当数组的长度不确定时,可以使用
...
来替代数组的长度,编译器会根据元素的个数自行推断出长度。 -
数组初始化时 第一个值可以是位置,后面跟值:
var nums = [3]int{1:2,3:7}
3、动态数组的删除
注意:拼接数组时后面应该使用解压缩符号 …
// go语言实现数组的删除
// 基本思路 删除对应元素后进行数组的拼接
var nums = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
fmt.Println(nums)
// 删除元素 5 拼接删除第四个元素与第五个元素
newNums := append(nums[:4], nums[5:]...)
fmt.Println(newNums)
八、指针
一个指针的变量存放了一个值的内存地址
声明格式:var 变量名 *类型
指针的使用:
-
定义指针变量。
-
为指针变量赋值
-
访问指针变量指向的地址
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("a变量的值为%d \n", *ip) // 使用指针变量访问值
}
go语言中的空指针,它的值为nil,nil也称为空指针,指代零值或者空值。
go语言中一个指针变量通常缩写为ptr。
1、指针的使用
(1)go语言指针数组
// 用一种情况我们需要保存数组
var ptr [SIZE]*int // 定义一个指针数组
// 将数组的地址赋值给指针ptr数组
for j := 0; j < len(nums); j++ {
ptr[j] = &nums[j]
}
// 读取指针ptr地址
for n := 0; n < len(ptr); n++ {
fmt.Println(ptr[n])
}
// 使用指针读取数组的值
for k := 0; k < len(ptr); k++ {
fmt.Println(*ptr[k])
}
(2)指向指针的指针
go语言支持指向指针的数组
声明指向指针的指针 var ptr **int
// 指向指针的指针
num := 200
var ptr1 *int
var pptr **int // 指向指针的指针 用**声明
/*指向num的地址*/
ptr1 = &num
/*指向ptr的地址*/
pptr = &ptr1
// 依次输出值
fmt.Printf("num的值为%d\n", num)
fmt.Printf("ptr1的值为%d\n", *ptr1)
fmt.Printf("pptr的值为%d\n", **pptr)
(3)函数参数中指针的使用
为什么要在函数中传指针:
- 传指针使得多个函数可以操作同一个数
- 传指针比较轻量级(8 bytes),只是传内存地址,我们可以用指针传递体积很大的结构体,如果不用指针的话,在每次的copy中就会花费比较多的系统开销(时间和内存)。所以在传比较大的结构体的时候指针是一个不错的选择。
- go语言中,string、Slice、map这三种类型的实现机制类似于指针,所以可以直接传递,不需要用取地址后再去传递指针。当然,若是需要改变slice的长度的话,仍然需要取地址后传递指针。
函数参数使用指针则可以改变原来参数的值
通过引用或地址传参,在函数调用时可以改变其值
// 函数的参数中使用指针
var num1 int = 30
var num2 int = 20
fmt.Printf("改变前 参数num1 %d, 参数num2 %d\n", num1, num2)
swap1(&num1, &num2) // 交换两个值
fmt.Printf("改变后 参数num1 %d, 参数num2 %d\n", num1, num2)
}
func swap1(x *int, y *int) {
s := *x
*x = *y
*y = s
}
// 简写的方式
func swap2(x *int, y *int) {
*x, *y = *y, *y
}
九、go语言结构体
go语言中数组可以存储一种类型的数据,在结构体中可以定义不同的数据类型。
即,go语言是由一系列具有相同类型或不同类型的数据构成的数据集合。
1、基本的结构体
实例:
// 定义结构体
type Books struct {
title string
author string
book_id int
}
func main() {
// 创建一个新的结构体
// 1、直接输入形式
book1 := Books{"go语言学习", "张三", 1000}
// 2、指定关键字形式 忽略的字段为0 或者 空
book2 := Books{title: "java学习", book_id: 1001}
// 结构体成员的访问
book2.author = "李四"
fmt.Println(book1)
fmt.Println(book2)
}
注意:结构体的创建两种方式,一种是一一对应的将数据填充进去,另一种是指定关键字的方式,未填充的自动为空值或者是0
结构体作为函参数的使用以及结构体指针的使用:
// 结构体作为函数参数的使用
func printBook(book Books) {
fmt.Printf("书名为:%s", book.title)
fmt.Printf(" 作者为:%s", book.author)
fmt.Printf(" 序号为:%d \n", book.book_id)
}
// 使用结构体指针访问结构体
func printBook1(book *Books) {
fmt.Printf("书名为:%s", book.title)
fmt.Printf(" 作者为:%s", book.author)
fmt.Printf(" 序号为:%d", book.book_id)
}
main函数中的调用
// 函数的调用
printBook(book2)
// 结构体指针的使用
var book_point *Books = &book1 //定义结构体指针
fmt.Println(book_point) // 输出指针的地址
printBook1(&book1) // 使用结构体指针函数访问结构体成员
2、struct的匿名字段
定义一个struct的时候是字段名与其一一对应的,实际上go支持只提供类型,而不写字段名的方式,这就是匿名字段,也被称为嵌入式字段。
当一个匿名字段为struct时,那么这个struct所拥有的全部字段都被隐式的引入了当前定义的struct。正如下例:
type Human struct {
name string
age int
weight int
}
type Student struct {
Human // 匿名字段 将隐式的把human所有字段都加到student中
id int
}
func main() {
// 初始化一个学生
mark := Student{
Human: Human{"Mark", 18, 120},
id: 1,
}
fmt.Println(mark)
}
十、go语言的切片(Slice)
go语言切片是对数组的抽象。数组的长度是固定的,所以go中增加了Slice切片类似于动态数组,长度是不固定的,可以追加元素。
1、切片的定义
func main() {
/*切片*/
// 声明一个未指定大小的数组来定义切片
var numbers []int
// 或者使用make()函数来声明切片
var slice1 []int = make([]int, 5)
slice2 := make([]int, 5)
}
2、make()函数
make函数中的参数 make([]类型, length, capacity)
其中capacity是可选参数,length是数组的长度,也是切片的初始化长度。
3、切片的初始化
未指定元素时切片的长度为0,里面的元素自动填充零。
/*切片*/
// 声明一个未指定大小的数组来定义切片
var numbers []int
numbers = []int{1, 2, 3, 4, 5}
// 或者使用make()函数来声明切片
var slice1 []int = make([]int, 5)
slice2 := make([]int, 5)
fmt.Println(slice2)
fmt.Println(slice1)
fmt.Println(numbers)
// 初始化切片
slice3 := []int{1, 2, 3}
fmt.Println(slice3)
// 使用数组初始化切片 将数组下标0到3的元素填充到切片中
slice1 = numbers[0:3]
fmt.Println(slice1)
4、len()和cap()函数
切片是可索引的,并且由len()方法获取长度。
切片提供了计算容量的方法cap()可以测量切片的长度最长可以到达多少。
5、切片的截取
可以通过指定上限和下限来截取切片的内容
slice[下限:上限]
// 实例
numbers := []int{1,2,3,4,5,6}
fmt.Println(numbers[:3]) // 打印切片numbers下标 0到3 的元素
fmt.Println(numbers[2:3]) // 打印切片numbers下标 2到3 的元素
6、append()函数和copy()函数
// 切片的append()函数和copy()函数
// append()向切片追加新的元素
slice1 = append(slice1, 0, 1, 2, 3, 4)
// 拷贝切片
// 创建新的切片是原先切片的 两倍容量
numbers1 := make([]int, len(slice1), (cap(slice1) * 2))
// 拷贝 Slice1中的内容到 numbers1中
copy(numbers1, slice1)
fmt.Println(slice1)
fmt.Println(numbers1)
十一、go语言范围(Range)
range关键字应用于for循环中迭代数组、切片、通道或者集合map的元素。
注意:
-
在数组和切片中返回元素的索引和索引对应的值
-
在集合中返回 key->value对
应用场景
for key, value := range oldMap{
newMap[key] = value
}
// 代码中的key 和 value是可以省略的
// 如果只想读取 key
for key := range oldMap
for key,_ := range oldMap
// 如果只想读取value
for _, value := range oldMap
// 遍历数组
arry1 := [5]int{1, 2, 3, 4, 5}
for key, value := range arry1 {
fmt.Printf("key %d -- value %d\n", key, value)
}
// 只输出key 或 value
for key := range arry1 {
fmt.Printf("key %d\n", key)
}
for _, value := range arry1 {
fmt.Printf("value %d\n", value)
}
// 遍历集合
map1 := make(map[int]int)
map1[0] = 1
map1[1] = 2
map1[2] = 3
// 读取key 和 value
for key1, value1 := range map1 {
fmt.Println(key1, value1)
}
十二、go语言Map(集合)
map是一种无序的键值对集合。重要的一点是可以通过key来快速的检索数据
注意:虽然可以像遍历数组一样遍历map,但是map是一种无序的集合对,无法决定它的返回顺序,应为map是使用hash表来实现的。
1、定义map
var 名 map[键值数据类型] 值类型
名 := make( map[键值数据类型] 值类型)
如果不初始化map则就会创建一个nil map,nil map不能用来存键值对
2、map的使用
func main() {
countryMap := make(map[string]string)
countryMap["china"] = "beijing"
countryMap["japan"] = "dongjing"
countryMap["france"] = "bali"
for country := range countryMap {
fmt.Printf("国家为:%s, 首都是:%s \n", country, countryMap[country])
}
// 查看元素是否在集合中存在
capital, ok := countryMap["USA"]
fmt.Println(capital)
fmt.Println(ok) // 返回一个boolean类型
if ok {
fmt.Printf("%s在集合中存在\n", capital)
} else {
fmt.Printf("%s在集合中不存在\n", capital)
}
3、delete()函数
// 删除函数 delete()的使用
// delete(集合,键值)
delete(countryMap, "france")
十三、go语言类型转换
基本语法:type_name(experssion)
type_name为类型experssion为表达式
func main() {
// 类型转换的基本格式 变量1 = 转换类型(值)
var demo1 int
var demo2 float32
demo1 = 5
/* 类型转换 */
demo2 = float32(demo1)
fmt.Printf("%f", demo2)
}
十四、go并发
1、go并发
go语言支持并发,通过go关键字开启gorountine即可。
2、gorountine
gorountine是一个轻量级的线程,它的调度是由Golang运行时进行管理的。
go 函数名(参数列表)
go语言允许使用go语句来开启一个新的运行期线程,以一个新的gorountine来执行一个函数。同一个程序中的所有gorountine共享一个地址空间。
/* 多线程 高并发 */
// go开启一个新的运行期线程
go say("world")
say("hello")
/*会输出十条信息,无序的,因为它们是两个gorountine在执行*/
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond) // 获取毫秒单位
fmt.Println(s)
}
}
3、通道channle
通道是用来传递数据的一个数据结构,可以应用于两个gorountine之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或者接收。如果未指定方向,则为双向通道。
注意:通道是不自带缓冲区的,发送端传数据必须有相应的接收端接收。
(1)通道的创建
channl的创建必须使用make函数 其类型关键字是 chan
channleName := make(chan int) // chan是通道关键字 int是通道数据类型
ch := make(chan int, 100) // 创建一个缓冲区大小为100的通道
注意:带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
(2)通道的使用实例
func main() {
/* 通道的创建 */
channle2 := make(chan int)
// 通道使用实例 求一个数组的和
s := []int{1, 2, 3, 6, 7, -6}
// 创建线程 求数组前半部分的和 以及后半部分的和
go sum(s[:len(s)/2], channle2)
go sum(s[len(s)/2:], channle2)
// 从通道中接收
x, y := <-channle2, <-channle2
fmt.Printf("前半部分和:%d, 后半部分和 %d, 总和:%d \n", x, y, x+y)
}
/*
*
求数组的和,并将结果发送到通道中
*/
func sum(s []int, c chan int) {
sum := 0
for _, values := range s {
sum += values
}
c <- sum // 将结果发送到通道c中
}
带缓冲区的通道:
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
(3) range 和 close
chan中也有 range 用于不断地读取chan中的数据,知道chan被显式的关闭
close 便是关闭这个通道
(4)select
通过select可以监听channel上的数据流动,select默认是阻塞的,只有当监听的chan中有发送或者接收可以进行的时候才会运行,当多个channle都准备好的时候,select是随机的选择一个可执行的。
select中海油default的语法,default就是当监听的cahnnle都没有准备好的时候,默认执行的(select不再默认阻塞等待channle)
select也可以用来设置超时。
十五、错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
Error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
实例:
func main() {
result, err := Sqrt(8)
fmt.Println(result, err)
result1, err1 := Sqrt(-4)
fmt.Println(result1, err1)
}
// 求一个数的平方差
func Sqrt(num float64) (float64, error) {
if num < 0 {
// errors.New("String") 返回一条错误文字
return 0, errors.New("输入的数小于零!")
} else {
return math.Sqrt(num), nil
}
}
十六、defer
延迟,在函数中可以添加defer语句,当函数执行到最后的时候,这些defer语句会按照逆序执行,最后该函数返回。
实践中,打开一些文件进行操作的时候,遇到错误代码需要返回,返回前需要关闭相应的资源,代码见下
// 正常情况下 需要重复写file.close很多次
fun ReadWrite() bool {
file.Open("file")
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return ture
}
// 这时候如果使用defer后就能有效的解决这个问题,让代码量变得更少
// 正常情况下 需要重复写file.close很多次
fun ReadWrite() bool {
file.Open("file")
defer file.Close() //延迟到在程序结束后执行此语句
if failureX {
return false
}
if failureY {
return false
}
return ture
}
十七、go语言的面向对象
1、struct结构体
见上述
2、method
method是函数的另一种形态,自带一个接收者的函数。
基本的语法:func(接收者) 函数名(参数)(返回值){}
注意:
-
虽然method的名字一模一样,但是如果接收者不同,那么method就不一样
-
method里面可以访问接收者的字段
-
调用method用
.
进行访问,就像struct里面的字段一样 -
method可以被定义到任何的你自定义的类型、内置类型、struct等各种类型上面
-
指针也可以当做是一个接收者,尤其是一个值需要修改原值而不是修改copy的时候
type Rectangle struct { Width, Heignt float64 } /* 把指针作为接收者,实现对原先的宽度的修改 */ func (r *Rectangle) SetRectangleWidth(width float64) bool { r.Width = width return true } // 调用方法 r1.SetRectangleWidth(20) fmt.Println(r1.Area())
- method和字段都可以实现继承,如果一个匿名字段实现了一个method,那么继承了这个匿名字段的struct也继承了该字段所含的method
- method也可以实现重写
实例:
// 不使用method 求解面积
package main
import ."fmt"
// 正常利用函数的方法求一个正方形的面积
type Rectangle struct{
// 矩形的结构体
width, height float64
}
/**
返回矩形的面积
*/
func area(r Rectangle)float64 {
return r.width * r.height
}
func main (){
// 正常情况下使用area求解面积
var r1 = Rectangle{12, 2}
var r2 = Rectangle{14, 5}
// 普通函数求解面积
Print("r1面积: %f",area(r1))
Print("r2面积: %f")
}
如果此时我们需要求解多个图形的面积,圆形、矩形都有自己不同的方式求解,但需要调用不同的area方法求解,这时候我们就可以利用method完成。
package area
import "math"
/*
利用method 求解不同图形的面积
*/
type Rectangle struct {
Width, Heignt float64
}
type Circle struct {
Radius float64
}
func (r Rectangle) Area() float64 {
return r.Heignt * r.Width
}
func (c Circle) Area() float64 {
return c.Radius * math.Pi
}
/*
go语言面向对象
*/
func main() {
r1 := Rectangle{12, 5}
c1 := Circle{4}
// 直接通过结构体调用 Area方法
fmt.Println(r1.Area())
fmt.Println(c1.Area())
}
3、method的继承和重写以及匿名字段的继承
import "fmt"
/*
匿名字段的继承
method的继承和重写
*/
type Human struct {
Name string
Age int
Phone string
}
type Student struct {
Human // 继承了匿名字段 Human
School string
}
type Employee struct {
Human // 匿名字段
Company string
}
// 在human上定义了一个 method
func (h *Human) SayHi() {
fmt.Printf("Hi,this is Human, I am %s you can call me on %s \n", h.Name, h.Phone)
}
// method的重写 针对student
func (s *Student) SayHi() {
fmt.Printf("Hi,this is Student, I am %s you can call me on %s, t am from %s \n", s.Name, s.Phone, s.School)
}
func main() {
human1 := person.Human{"张三", 20, "13476972395"}
human1.SayHi()
// 此时定义一个student 可以继承它的匿名字段的同时,也继承了human的sayhi method方法
// 如果针对student对sayhi方法进行了重写,那么下面将调用重写的student.sayhi 方法
student1 := person.Student{person.Human{"李四", 23, "15569960740"}, "长江大学"}
student1.SayHi()
}
4、interface类型 (接口)
什么是interface:
interface简单的讲就是一组method的组合,我们可以通过interface来定义对象的一组行为。
interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。
interface的定义:
/*
interface 可以被任意的对象实现,同时,一个对象也可以实现任意多个interface
也可以说,任意的对象都实现了 空的interface
*/
type Men interface {
SayHi()
Sing(songs string)
}
// human的struct实现了此接口的所有方法
func (h *Human) SayHi() {
fmt.Printf("Hi,this is Human, I am %s you can call me on %s \n", h.Name, h.Phone)
}
func (h *Human) Sing(songs string) {
fmt.Printf("human %s sing a song is %s \n", h.Name, songs)
}
5、interface变量
interface变量里可以存放,实现了这个interface的任意类型的对象,也可以定义一个包含interface类型的元素的slice,这个slice可以被赋予实现了interface接口的任意结构对象。如下例:
// interface Men
type Men interface {
SayHi()
Sing(songs string)
}
// 接口 Men 的实现
type Human struct {
Name string
Age int
Phone string
}
type Student struct {
Human // 继承了匿名字段 Human
School string
}
type Employee struct {
Human // 匿名字段
Company string
}
// human 实现了man接口的所有方法
func (h Human) SayHi() {
fmt.Printf("Hi,this is Human, I am %s you can call me on %s \n", h.Name, h.Phone)
}
func (h Human) Sing(songs string) {
fmt.Printf("human %s sing a song is %s \n", h.Name, songs)
}
// student 实现了 man接口的所有方法
func (s Student) SayHi() {
fmt.Printf("Hi,this is Student, I am %s you can call me on %s, t am from %s \n", s.Name, s.Phone, s.School)
}
func (h Student) Sing(songs string) {
fmt.Printf("human %s sing a song is %s \n", h.Name, songs)
}
// Employee实现了 接口man的都有方法
func (s Employee) SayHi() {
fmt.Printf("Hi,this is Student, I am %s you can call me on %s, t am from %s \n", s.Name, s.Phone, s.Company)
}
func (h Employee) Sing(songs string) {
fmt.Printf("human %s sing a song is %s \n", h.Name, songs)
}
// main方法
human1 := person.Human{"张三", 20, "13476972395"}
student1 := person.Student{person.Human{"李四", 23, "15569960740"}, "长江大学"}
employee1 := person.Employee{person.Human{"王五", 23, "15569960740"}, "腾讯"}
// 定义一个 interface 类型的变量 i
var i person.Men
// i 能存储student 并实现i调用接口方法 有指针接收的不能用Men存储实现
i = student1
i.SayHi()
// 也可以存储employee
i = employee1
i.SayHi()
i.Sing("征服")
// 定义一个interface类型的slice
x := make([]person.Men, 0)
x = append(x, human1, student1, employee1)
// 遍历分别用他们调用 sayhi方法
for _, men := range x {
men.SayHi()
}