GoLang基础
目录
GoLang介绍
#1 Go 即Golang,是Google公司2009年11月正式对外公开的一门编程语言
#2 解释型,编译型
Go是静态(编译型)强类型语言,是区别于解析型语言的弱类型语言(静态:类型固定 强类型:不同类型不允许直接运算)。
python动态强类型语言
#3 哪些是编译,哪些是解释
编译:java,c,c++,c#,go
解析型:python,js,php...
编译型语言涉及到跨平台,因为它需要编译成该平台的可执行文件,java--》运行在jvm之上
go:跨平台编译,交叉编译,在windows平台编译出mac上可执行
解释型:不存在跨平台问题,有解释器
#4 特性
跨平台的编译型语言,交叉编译
管道(channel),切片(slice),并发(routine)
有垃圾回收的机制
支持面向对象和面向过程的编程模式(go的面向对象没有类的概念)
# 5 发展(go是用c写起来的)
2009年11月7日 weekly.2009-11-06 —— 早期的版本
2015年8月19日 go1.5 —— 实现的架构变化,同时保留了和旧版本的兼容性,本次更新中移除了”最后残余的C代码”。# 从此以后,自举,自己写自己
2018年8月24日 go 1.11 :modules,包管理
2020 年 8 月 go 1.15
# Go语言应用
中国的互联网公司,多多少少都会用,有些没用的,都在准备用
##### docker k8s 蓝鲸 云计算 百度 小米:falcon
##### 七牛云
## 应用领域,go适合做什么
服务的开发,微服务开发,运维相关,区块链,云平台
第一款开源区块链产品是用go写的
# Go语言发展前景,为什么火
很新,生态不完善
完美契合当下高并发的互联网生态
语法简单,速度快
云计算和区块链的火,互联网企业高并发的需求
开发环境搭建
#1 ide,集成开发环境(goland等同于pycharm)
-goland(jetbrains全家桶),vscode
-推荐用goland,pycharm,idea,androidstudio
一路下一步
#2 开发环境 sdk
一路下一步
# 测试安装成功
go version 把版本打印出来就装成功了
# 3注意事项(重点)
-goland创建项目,选择go的安装路径(默认选中了)
-gopath:所有的go代码必须放在这个路径下的src文件夹下,否则无法执行,默认创建到用户家目录下的go文件夹(mac,windows,linux)
-创建项目路径,go文件都不要出现中文
# 3 go命令
# 必须记住的
go env # go的环境变量
-GO111MODULE=空的,现在没有使用MODULE模式
-GOPATH=C:\Users\oldboy\go #代码存放路径
-GOROOT=c:\go # go sdk安装路径
go build # 编译型语言,需要先编译再执行,编译成可执行文件,执行可执行文件
go run # 编译并执行,开发阶段用,两步并作一步
# 其他不太需要记得
go get # 下载并安装包和依赖等同于pip install
go version
go fmt #运行gofmt进行格式化(go fmt :自动将代码格式)
Hello World
//go语言的注释
//单行注释
/*
多行注释
多行注释
*/
// 重点
//go(所有编译型语言)项目要运行,必须有一个入口
//go的入口是main包下的main函数
// main包下可不可以有多个main函数:不可以
package main //声明包名,包名是main,每一个go文件都属于某个包
import "fmt" //导入包,内置包
func main() { //定义了一个main函数,大括号包裹是函数体的内容
fmt.Println("hello world") //打印函数等同与print()
}
// 编译
go build s1.go
// 执行
s1.exe
// 编译并执行
go run s1.go
// 在goland中,右键,运行即可
变量
package main
import "fmt"
// 变量
package main
func main() {
//1 定义变量的第一种方式:全定义
//var关键字 变量名 变量类型 = 变量值
var age int =10 //在go中,变量定义了就必须使用,如果不使用就报错
fmt.Println(age)
//2 定义变量的第二种方式:类型推导(类型不需要写了)
var age =10
age=100
var name ="lqz"
fmt.Println(age) //打印并换行
fmt.Print(name) //打印不换行
fmt.Printf("%T\n",age) //查看变量类型
fmt.Printf("%T",name)
fmt.Printf("%p",&name)
//3 定义变量的第三种方式:简略声明(类型和var关键字都不写)这种用的多,冒号和等号是一家
a:=10
var a int =100 //重复定义,报错
var a =99 //重复定义,报错
a := 99 //重复定义,报错
a=99
fmt.Println(a)
//4 其他变形方式
//4.1 只定义不赋值
var age int //定义变量 //如果是只定义,不赋值,只能用这种
var age //只定义,不赋值,不行
//4.2 声明多个变量
var width, height int = 100, 50 // 声明多个变量
var width,height =100,50 // 声明多个变量
var width,height =100,"lqz" // 声明多个变量
width,height :=100,";qz" // 声明多个变量
//4.3 声明多个变量,并赋初值
var (
name = "naveen"
age int = 29
height int
)
fmt.Println(name,age,height)
//4.4 小坑
var a int =10
//var b =99
b,a:=99,100 //这个不报错,我们觉得是重复定义,冒号左侧,只要有一个没有定义过的变量,就可以
fmt.Println(b)
fmt.Print(a)
/*
总结:
1 变量类型在定义阶段就确定了,一旦确定,不允许改变
2 变量不可以重复定义
3 变量要先定义再使用
*/
}
变量定义规范
/*
变量命名规范
-变量命令建议用驼峰,(大小写有特殊意义)
-go文件命名建议用 下划线
- 一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线
-大写字母和小写字母是不同的:Name和name是两个不同的变量
-关键字和保留字都不建议用作变量名
*/
/*
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语言中有37个保留字,主要对应内建的常量、类型和函数
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数: make len cap new append copy close delete
complex real imag
panic recover
*/
类型
/*
基础数据类型
数字:
有符号整形
-int:在32位机器是int32,在64位机器是int64
-int8:表示整数范围是:8个比特位,8个bit是1byte ,负数和0, 2的7次方-1 的范围
-int16 2的15次方减一
-int32
-int64
无符号整型
-uint8 2的8次方减一 定义一个人的年龄
-uint16
-uint32
-uint64
浮点型(小数),表示小数点后长度多少位
-float32
-float64
复数
-complex64
-complex128
byte:是int8的别名 单引号包裹
rune:是int32的别名 单引号包裹
字符串
双引号包裹
反引号包裹 ` `
布尔
bool true 和 false
数据类型默认值:
数字类型是0
字符串类型是 空字符串
布尔类型 false
*/
/*
byte:int8
short :int16
int:int32
long:int64
float:float32
double:float64
*/
常量
package main
//常量
func main() {
//常量的定义,第一种
//const 变量名 变量类型 = 变量值
//const age int8 = 99
////修改就报错
//age=199
//fmt.Println(age)
//第二种方式类型推导
//const age = 99
//age=88
//fmt.Println(age)
//其他定义方式
//const name,age = "zhangsan",99
//const (
// name string ="lqz"
// age =99
//)
//const (
// s1 =iota
// s2 =iota
// s3
// s4 =99
// s5 =iota
//)
//fmt.Println(s1)
//fmt.Println(s2)
//fmt.Println(s3)
//fmt.Println(s4)
//fmt.Println(s5)
}
//const 关键字定义,不允许改变
函数基础
package main
import "fmt"
//给类型命别名
type MyFunc func(a,b int)(int,string)
type Myint int
//函数
func main() {
var a int =10
var b Myint=9
fmt.Println(a+int(b))
//定义方式
/*
func 名字(参数名 类型,参数名 类型)(返回值类型,返回值类型){
函数体内容
return 返回值1,返回值2
}
*/
//1 调用函数
//add(2,3,"xxx")
//var a int =add(2,3)
//a := add(2, 3)
//fmt.Println(a)
//多返回值就需要用多变量接收
//a,b:=add(3,4)
//fmt.Println(a,b)
//多返回值,忽略一个返回值
//a,_:=add(3,4)
//fmt.Println(a)
//fmt.Println(_)
//匿名函数(定义在函数内部的函数,不能是有名函数),头等函数
//var a func()
//a = func (){
// fmt.Println("我是匿名函数")
//}
//a()
//函数返回值是函数
//a:=test()
//fmt.Println(a) // 函数内存地址
//a()
//8 函数返回值为函数,返回的函数带参数
//a:=test()
//a("xxx")
//9 函数返回值为函数,返回的函数带参数,带返回值
//var a func(a,b int)int
//a=test()
//fmt.Println(a(2,3))
//10 函数参数为函数类型,返回值为带参数,带返回值的函数类型
//a,b:=test(func() {
// fmt.Println("我是函数参数")
//})(3,4)
//f:= func() {
// fmt.Println("我是函数参数")
//}
//
//f1:=test(f)
//a,b:=f1(3,4)
//fmt.Println(a,b)
//闭包函数的使用
//a:=test(19)
////a是闭包函数
//a()
//装饰器是闭包函数的典型应用(go中没有装饰器的语法糖),通过闭包实现装饰器
//类型重命名
//var a MyFunc =test()
//c,d:=a(1,2)
//fmt.Println(c,d)
}
//1 有参数无返回值(定义函数)
//func add(a int,b int) {
// fmt.Println(a+b)
//}
//2 有参数无返回值,有多个相同类型参数
//func add(a ,b int) {
// fmt.Println(a+b)
//}
//3 有参数无返回值,有多个相同类型参数,也有不同类型
//func add(a ,b int,msg string) {
// fmt.Println(a+b)
// fmt.Print(msg)
//}
//4 多个参数,一个返回值
//func add(a, b int) int {
// return a + b
//}
//4 多个参数,多个返回值
//func add(a, b int) (int,int) {
// return a + b,a*b
//}
//5 命名返回值
//func add(a, b int) (c int,d int) {
// c=a+b
// d=a*b
// //return时,不需要再写c,d了
// return
//}
//6 函数是一等公民,函数可以赋值给变量
//7 函数返回值为函数
//func test() func() {
// return func() {
// fmt.Println("我是返回函数")
// }
//}
//8 函数返回值为函数,返回的函数带参数
// 类型只要有不一样的地方,就不是一个类型
//func test() func(msg string) {
// return func(msg string) {
// fmt.Println(msg)
// }
//}
//9 函数返回值为函数,返回的函数带参数,带返回值
//func test() func(a,b int) int{
// return func(a,b int) int {
// return a+b
// }
//}
//10 函数参数为函数类型,返回值为带参数,带返回值的函数类型
//func test(f func()) func(a,b int) (int,int){
// return func(a,b int) (int,int) {
// f()
// return a+b,a*b
// }
//}
//11 闭包函数:1 定义在函数内部 2对外部作用域有引用
// 闭包函数就是多了一种函数传参的方法,包进去了
//func test(age int) func() {
// a:= func() {
// fmt.Println(age)
// }
// return a
//}
//12
func test()MyFunc {
return func(a,b int)(int,string) {
fmt.Println("xxx")
return 10,"ok"
}
}
变量作用域范围
package main
import "fmt"
//在同一个包下,函数名不能重名
//var a int //全局变量,全局有效,只要改了,就是改了
//func main() {
// fmt.Println(a) //0
// a=19
// fmt.Println(a) //19
// test1() //99
// fmt.Println(a) //99
//}
//
//func test1() {
// a=99
// fmt.Println(a)
//
//}
//变量的作用域范围
var a int //全局变量,全局有效,只要改了,就是改了
func main() {
var a int
fmt.Println(a) //0
a=19
fmt.Println(a) //19
test1() // 0
fmt.Println(a) //19
}
func test1() {
fmt.Println(a)
}
包管理
#1 包:模块的意思
#2 自定义包
-go语言的代码必须放在gopath的src路径下
-包导入是从gopath的src路径下开始检索(开始找)
-除了main包以外,建议包名就叫文件夹名,一个文件夹下的包名必须一致
-同一个包下,变量,函数只能定义一次
-同一个包下的变量和函数可以直接使用
-包内的函数或变量,想让外部包使用,必须首字母大写
-以后下的第三方包都是放在gopath的src路径下
# 3 init函数(特殊函数)
-不需要调用就会执行
-可以定义多个
# 4 包导入的几种方式
-import "day02/mypackage"
-给包重命名
-import 名字 "day02/mypackage"
名字.变量/函数
-包只导入,不使用
import _ "day02/mypackage"
# 5 go语言没有一个统一包管理的地址,大家都放到github上
# 6 采用go mode模式
-两种创建方式之一
-命令行下输入:go mod init 项目名 在当前路径下创建出go.mod(该项目依赖go的版本,第三方包版本)
-项目路径的cmd窗口,go get 第三方包,就会在go.mod中加入依赖
-以后把项目copy给别人,go install
-自己写的包,就放在自己项目路径下
-加代理的方式:手动写,goland中配置
-在goland中创建项目时,直接指定modules,可以配置环境变量(加代理)
if-else语句
package main
import "fmt"
//if-else
func main() {
//1 if else
a := test()
//if a>10{
// fmt.Println("a大于10")
//}else {
// fmt.Println("a小于10")
//}
//2 if --else if--- else
if a > 10 {
fmt.Println("dayu 10")
} else if a == 10 {
fmt.Println("10101010")
} else {
fmt.Println("xiaoyu 10")
}
//3 条件语句后不能回车换行
//if a > 10 {
// fmt.Println("dayu 10")
//} else if a == 10 {
// fmt.Println("10101010")
//} else {
// fmt.Println("xiaoyu 10")
//}
}
func test() int {
return 100
}
循环
1 没有while循环,没有do while循环,只有一个for循环
package main
//for 循环
/* for 后面三部分,都可以省略
for 变量初始化;条件;变量自增/自减 {
循环体内容
}
*/
func main() {
//1 基本使用
//for i:=0;i<10;i++{
// fmt.Println(i)
//}
//2 省略第一部分
//i:=0 //作用域范围大,不止在for内部,外部也可以用
//for ;i<10;i++{
// fmt.Println(i)
//}
//2 省略第三部分
//i:=0 //作用域范围大,不止在for内部,外部也可以用
//for ;i<10;{
// fmt.Println(i)
// i++
//}
//3 省略一和三部分的简略写法(这就是while循环) for 条件{ 循环体内容}
//i:=0
//for i<10{
// fmt.Println(i)
// i++
//}
//4 死循环
//for {
// fmt.Println("ssssss")
//}
//5 只是演示开多协程
//for i:=0;i<4000;i++{
// go test2()
//}
//6 break :结束本次for循环,continue结束本次循环,继续下一次循环
}
//func test2() {
// for {
// fmt.Println("sssss")
// }
//
//}
switch语句
// switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。它可以被认为是替代多个 if else 子句的常用方式
package main
func main() {
//1 基本使用
//num:=4
//switch num {
//case 1:
// fmt.Println("1")
//case 2:
// fmt.Println("2")
//case 3:
// fmt.Println("3")
//case 4:
// fmt.Println("4")
//}
//2 默认情况
//num:=40
//switch num {
//case 1:
// fmt.Println("1")
//case 2:
// fmt.Println("2")
//case 3:
// fmt.Println("3")
//case 4:
// fmt.Println("4")
//default:
// fmt.Println("我没有匹配")
//}
//3 多表达式判断
//num:=40
//switch num {
//case 1,2,3,4,5,6,7,8:
// fmt.Println("1")
//case 10,11,16:
// fmt.Println("2")
//case 30:
// fmt.Println("3")
//case 40,44,45:
// fmt.Println("4")
//default:
// fmt.Println("我没有匹配")
//}
//4 无表达式的 switch
//num:=80
//switch {
//case num==12,num==13:
// fmt.Println("12,13")
//case num==40,num==41:
// fmt.Println("40,41")
//default:
// fmt.Println("我没有匹配")
//}
//5 Fallthrough
//num:=12
//switch {
//case num==12,num==13:
// fmt.Println("12,13")
// fallthrough
//case num==40,num==41:
// fmt.Println("40,41")
// //fallthrough //穿透,只要看到fallthrough,无条件执行下一个case或者default
//default:
// fmt.Println("我没有匹配")
//}
}
goto:饱受诟病 java 保留字,没有实际作用
数组
package main
import (
"fmt"
)
//数组:数组是同一类型元素的集合。可以放多个值,但是类型一致,内存中连续存储
// Go 语言中不允许混合不同类型的元素
func main() {
//1 数组的定义,数组的大小,在定义阶段就确定了,而且不能改
//定义了一个大小为3的string类型数组,里面可以放3个字符串
//var names [3]string
//var ages [3]int8
//fmt.Println(ages)
//2 数组赋值
//var ages [3]int8
//ages[0]=99
//ages[2]=88
//fmt.Println(ages)
//fmt.Println(ages[1])
//3 定义并初始化,
//var ages [3]int=[3]int{1,2,3}
//var ages [3]int=[3]int{1,2}
//var ages [3]int=[3]int{}
//var ages=[3]int{}
//ages:=[3]int{1,3,4,7} //不允许多放
//fmt.Println(ages)
//4 数组定义并初始化的其他(了解)数组只要定义,长度就固定了,。。。,后面放几个值,数组大小是多少
//var ages [9]int=[...]int{1,2,3,4,5,6,7,8} //不支持这个
//var ages =[...]int{1,2,3,4,5,6,7,8}
//ages :=[...]int{1,2,3,4,8}
//fmt.Println(len(ages))
//5 数组的大小是类型的一部分
//var a [2]int=[2]int{1,2}
//var b [2]int=[2]int{1,3}
//b=a //如果不是同一种类型,不允许相互赋值
//fmt.Println(b)
//6 数组是值类型
//var a [2]int=[2]int{1,2}
//fmt.Println(a)
//test5(a) //因为数组是值类型,go函数传参,都是copy传递,如果是值类型,函数内改了,不会影响原来的
//fmt.Println(a)
//7 数组长度 len() 数组长度在定义阶段已经固定
//var a [2]int=[2]int{1,2}
//fmt.Println(len(a))
//8 数组循环
//var a =[...]int{7,4,3,5,6,7}
//fmt.Println(a[99])
//fmt.Println(len(a))
//普通循环
//for i:=0;i<len(a);i++{
// fmt.Println(a[i])
//}
//通过range来循环 (range不是内置函数,是一个关键字,for,if,else),打印出索引
//for i:=range a{
// fmt.Println(i)
//}
//如果用一个变量来接收,这个值是可迭代的索引
//如果用两个变量来接收,这两个变量,一个是索引,一个具体的值
//for i,value:=range a{
// fmt.Println(i)
// fmt.Println(value)
//}
//把数组循环打印出来
//for _,value:=range a{
// fmt.Println(value)
//}
// 9 多维数组
//var a [3][3]int //定义
//a[0][1]=99 //使用
//fmt.Println(a)
//定义并赋初值
//var a [3][3]int=[3][3]int{{1},{1,2,3},{4,4,4}}
//var s =[3][3]string{{"lqz","xxx","yyy"},{},{}}
//fmt.Println(s)
//
////循环多维数组
//for _,value:=range s{
// for _,in_value:=range value{
// fmt.Println(in_value)
// }
//}
//10 数组定义并指定位置初始化
//var names [100]int=[100]int{10:99,99:99}
//var names [100]int=[100]int{10,11,2,44,99:99,45:88}
//fmt.Println(names)
}
func test5(a [2]int) {
a[0]=99
fmt.Println(a)
}
切片基础
package main
import "fmt"
//切片:切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用
func main() {
//1 切片定义的第一种方式
//定义一个数组
//var a =[10]int{9,8,7,6,5,4,3,2,1,0}
////基于数组,做一个切片
////[]int 中括号中不带东西,就是切片类型
//var b []int
//b=a[:]
//fmt.Println(b)
//fmt.Printf("%T",b)
//fmt.Println()
//fmt.Printf("%T",a)
//fmt.Println()
//
////2 使用切片
//fmt.Println(b[0])
//fmt.Println(b[1])
//
////3 修改切片,会影响数组
//b[0]=999
//fmt.Println(b)
////数组会被修改
//fmt.Println(a)
//
////4 修改数组,是否会影响切片?会
//a[3]=333
//fmt.Println(a) //数组
//fmt.Println(b) //切片
// 5 切片只切数组一部分
//var a =[10]int{9,8,7,6,5,4,3,2,1,0}
//var b []int=a[3:5]
//fmt.Println(b)
////修改切片
//b[0]=999
//fmt.Println(b)
//fmt.Println(a)
//a[4]=888
//fmt.Println(b)
//fmt.Println(a)
//6 当多个切片共用相同的底层数组时,每个切片所做的更改将反映在数组中
//var a =[10]int{9,8,7,6,5,4,3,2,1,0}
//var b []int=a[3:5]
//var c []int=a[4:6]
//fmt.Println(a)
//fmt.Println(b)
//fmt.Println(c)
//b[1]=555
//fmt.Println(a)
//fmt.Println(b)
//fmt.Println(c)
//7 切片的长度和容量
var a =[10]int{9,8,7,6,5,4,3,2,1,0}
//var b []int=a[3:5]
var b []int=a[7:8]
fmt.Println(a)
fmt.Println(b)
// 切片长度
fmt.Println(len(b))
// 切片容量(我最多能存多少值)
fmt.Println(cap(b))
//8 切片追加值
b=append(b,3)
b=append(b,999)
//到了临界点再追加
b=append(b,888)
fmt.Println(b)
fmt.Println(a)
fmt.Println(len(b))// 长度是4
fmt.Println(cap(b)) //容量是6
b[0]=111
fmt.Println(b)
fmt.Println(a)
}
切片
package main
func main() {
//1 定义切片 (对底层数组的引用),基于数组切出切片
//var a [10]int =[10]int{9,8,7,6,5,4,3,2,1}
//b:=a[6:9]
////fmt.Println(b)
////fmt.Println(len(b))
////fmt.Println(cap(b))
////2 切片和数组是相互影响的
////b[0]=99
////fmt.Println(b)
////fmt.Println(a)
//
////3 切片追加值 append
//b=append(b,99)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//fmt.Println(a)
////切片到容量了,再追加,会在原有容量的基础上翻倍
//b=append(b,88)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//fmt.Println(a)
////把切片第0个位置改成111
//b[0]=111
//fmt.Println(b)
//fmt.Println(a)
//
////继续追加
//b=append(b,77,66,55)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//
//b=append(b,44)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//2 通过make创建切片(后期引用类型初始化都是用make)
//var a []int //定义没有初始化,它空的---》nil 所有引用类型空值都是nil
//fmt.Println(a)
//fmt.Println(a==nil)
//初始化,从数组切
//通过make创建,初始化,初始化以后就不是nil
//a=make([]int,3,4) //第一个参数是类型,第二个参数是切片长度,第三个参数是切片容量
//fmt.Println(a)
//fmt.Println(len(a))
//fmt.Println(cap(a))
//a=make([]int,0,4) //底层创建了一个大小为4的数组,然后让a指向了它
//fmt.Println(a)
//fmt.Println(a==nil)
//a=append(a,3)
//fmt.Println(a)
//3 append的其他用法
//a:=make([]int,3,4)
//var b[]int=[]int{3,4,5} //切片初始化
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
////a=append(a,2,3,4)
//a=append(a,b...) //打算了,传过去
//fmt.Println(a)
//切片的函数传递(切片是引用类型),函数传递都是copy传递 {指针:地址,长度:3,容量:3}
//函数中修改会影响原来的(不要使用append操作,如果要使用,确认好不要超容量,否则再修改,就不会影响原来的了)
//var b[]int=[]int{3,4,5}
//fmt.Println(b)
//test(b)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//var b[]int=make([]int,2,4)
//fmt.Println(b)
//test(b)
//fmt.Println(b)
//fmt.Println(len(b))
//fmt.Println(cap(b))
//4 多维切片
//var a [][]int=make([][]int,3,4)
//fmt.Println(a)
//fmt.Println(a[0]==nil)
//a[0]=make([]int,0,4)
////fmt.Println(a[0][0])
//a[1]=make([]int,0,3)
//fmt.Println(a)
//var b[]int=[]int{1,2,3}
//a[2]=b
////fmt.Println(a)
////fmt.Println(a[2][2])
//for _,v:=range a{
// for _,v1:=range v{
// fmt.Println(v1)
// }
//}
//var a[][]string=[][]string{{"xx","yy"},{},{"44"}}
//a的长度是3,容量是3
//fmt.Println(len(a))
//fmt.Println(cap(a))
//fmt.Println(a)
//fmt.Println(a[0])
//fmt.Println(len(a[0]))
//fmt.Println(cap(a[0]))
//fmt.Println(a[1])
//fmt.Println(len(a[1]))
//fmt.Println(cap(a[1]))
//var c []string=make([]string,0,0)
//var d []string
//fmt.Println(len(c))
//fmt.Println(cap(c))
//
//fmt.Println(len(d))
//fmt.Println(cap(d))
//4 copy内置函数,把一个切片copy给另一个切片
//var b []int=[]int{3,4,5,67,7,8,88}
//var c []int=make([]int,2,8)
////把b copy给c
////fmt.Println(c)
////a:=copy(c,b)
////fmt.Println(c)
////fmt.Println(a)
////fmt.Println(c[2]) //报错,拿不到长度以外的值
//copy(c,b)
//fmt.Println(c[2]) //报错,下标只能取到长度,不能取到容量
}
//func test(b []int) {
// fmt.Println(b)
// b[0]=999
// b=append(b,88)
// b[1]=888
// fmt.Println(len(b))
// fmt.Println(cap(b))
// fmt.Println(b)
//}
可变长参数
package main
import "fmt"
//函数可变长参数
func main() {
//fmt.Println(1,2,3,4,6,"xxxx")
//test1(1,2,3,4,5,6,7,78)
//可不可以把切片直接传过去
//var a []int = []int{1, 2, 3}
//test1(a...) //把切片a打散了传过来
//fmt.Println(find(4,1,2,3,4,5,6,7,8))
//b:=[]int{1,2,3,4,5,6,7,8}
//fmt.Println(find(44,b...))
//welcome := []string{"hello", "world"}
//change(welcome...)
//fmt.Println(welcome)
welcome := []string{"hello", "world"}
change(welcome...)
fmt.Println(welcome)
}
//func test1(a ...int) {
// //a是什么?切片
// //fmt.Println(a)
// //fmt.Printf("%T",a)
// //fmt.Println(len(a))
// //fmt.Println(cap(a))
// fmt.Println(a[0])
//}
//func find(a int,b ...int)bool {
// flag:=false
// for _,v:=range b{
// if a==v{
// flag=true
// }
// }
// return flag
//}
//
//func change(s ...string) {
// s[0] = "Go"
//}
func change(s ...string) {
//s[0] = "Go"
s = append(s, "playground")
s[0] = "Go"
fmt.Println(s)
}
map
package main
import (
"fmt"
)
//map:map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值,value值的类型是一样的
func main() {
//1 定义一个map
//var m map[int]string //定义了一个map类型,key是int类型,value是string类型
//fmt.Println(m==nil)
//m[99]="xxx" //报错,因为没有初始化,是nil类型
//fmt.Println(m)
//m=make(map[int]string)
//fmt.Println(m==nil)
//2 map的零值是nil,它是一个引用类型,如果当作参数传递,修改会影响原来的
//3 使用map,放值
//m[1]="lqz"
//m[2]="egon"
//m[3]="zhangsan"
//fmt.Println(m)
//fmt.Println(m[1])
//4 研究(了解)
//m:=make(map[int]string) //影响初始map的长度
//m[1]="lqz"
//m[2]="egon"
//m[3]="zhangsan"
//fmt.Println(m)
//5 取值
//定义并初始化,放点值
//a:=map[string]string{"name":"lqz","age":"18","sex":"男"}
//a:=map[string]int{"name":1}
//a:=map[string][]int{"name":[]int{1,2,3}}
//fmt.Println(a["name"])
//fmt.Println(a["name"][0])
//取一个不存在值,不会报错,会取到value值的空值
//fmt.Println(a["name1"]==nil)
//fmt.Println(a["name1"])
//如何判断一个key是否再字典中
//value,flag:=a["name"]
//fmt.Println(flag) //true
//fmt.Println(value) //lqz
//value,flag:=a["name1"]
//fmt.Println(flag) //false
//fmt.Println(value) //value值的空值
//3 循环map,无序(python 3.6以后,字典有序?为什么有序?底层原理如何实现?)
//a:=map[string]string{"name":"lqz","age":"18","sex":"男"}
////for key,value:=range a{
//for _,value:=range a{
// //fmt.Println(key)
// fmt.Println(value)
//}
//4 删除元素
//a:=map[string]string{"name":"lqz","age":"18","sex":"男"}
////内置函数
//delete(a,"sex")
//delete(a,"sss") //没有不会报错
//fmt.Println(a)
//5 长度
//a:=map[string]string{"name":"lqz","age":"18","sex":"男"}
//fmt.Println(len(a))
//fmt.Println(cap(a)) //不让你看,看源码
//6 map是引用类型
//a:=map[string]string{"name":"lqz","age":"18","sex":"男"}
//a:=make(map[string]string,3)
//a["name"]="lqz"
//a["age"]="15"
//a["sex"]="男"
//test5(a)
//fmt.Println(a)
//7 Map 的相等性,map不支持等号比较,只支持等号和nil比较
//a:=map[string]string{"name":"lqz"}
////b:=map[string]string{"name":"lqz"}
////fmt.Println(a==b)
//fmt.Println(a==nil)
//long a =10L //对象
//long b=10L //对象
////if(a==b){
//if(a.equals(b)){
// System.out.println("pass")
//}else{
// System.out.println("xxx")
//}
//map的value值是map
//var a map[string]map[string]string= make(map[string]map[string]string)
//fmt.Println(a==nil)
////第二层的map没有初始化
////a["name"]["name"]="lqz"
//a["name"]= make(map[string]string)
//a["name"]["name"]="lqz"
//fmt.Println(a)
}
//再go中实现map有序,定义一个切片,每放要给,把key往切片追加一个
func test5(a map[string]string) {
a["name"]="egon"
a["xxx"]="xxx"
a["xxx1"]="xxx"
a["xxx2"]="xxx"
a["xxx3"]="xxx"
a["xxx4"]="xxx"
a["xxx5"]="xxx"
a["xxx6"]="xxx"
a["xxx7"]="xxx"
a["xxx8"]="xxx"
a["xxx9"]="xxx"
a["xxx10"]="xxx"
fmt.Println(a)
}
指针
package main
import "fmt"
//指针:指针是一种存储变量内存地址的变量。
// & 取地址符号,把它加在变量前,表示取该变量的地址
// * 放在类型前,表示指向这个类型的指针
// * 放在变量前,表示解引用,取出指针指向真正的值
func main() {
//a:=10
//s:="xxxx"
////把a的地址取出来,赋值给一个变量,那这个变量就是指针
//var b *int //定义了一个指向int类型的指针
//b=&a
//var b1 *string
//b1=&s //类型不一致 定义一个指向string类型的指针,b和b1不是同一个类型
//
//
//fmt.Println(b)
//fmt.Println(b1)
// 2 定义一个指向int类型指针的指针
//var sss **int
//sss=&b
//var ssss ***int
//ssss=&sss
//fmt.Println(sss)
//fmt.Println(ssss)
//3 通过地址,拿到实际值
//a:=10
//var b *int //定义了一个指向int类型的指针
//b=&a
//fmt.Println(b)
////根据b,把a的值打印出来
//fmt.Println(*b) //解引用
//a:=10
//b:=&a
//c:=&b
//var d ***int
//d=&c
//fmt.Println(d)
//fmt.Println(*d)
//fmt.Println(**d)
//fmt.Println(***d)
//fmt.Println(***d)
//3 指针的零值(nil),引用类型
//var b *int
//fmt.Println(b)
//4 向函数传递指针参数,函数传参都是copy传递
//a:=10
//b:=&a
//test7(b)
//fmt.Println(a)
//5 不要向函数传递数组的指针,而应该使用切片
//var a [7]int
//fmt.Println(a)
////test8(&a)
//test9(a[:])
//fmt.Println(a)
//6 Go 不支持指针运算
//var a=10
//var b=&a
//b++
//c语言为什么要支持指针运算
//7 数组指针和指针数组
//数组指针:指向数组的指针
//指针数组:数组内放一个个指针
//数组指针
//var a *[5]int=&[5]int{1,2,4}
//fmt.Println(a)
//
////指针数组
//x,y,z:=33,444,55
//var b [5]*int=[5]*int{&x,&y,&z}
//fmt.Println(b)
}
func test8(a *[5]int) {
//解引用以后再使用
//(*a)[0]=99
a[0]=99 //这样使用也可以,语言层面处理了,不需要解引用再使用,直接使用就可以
fmt.Println(a) //打印出来,显示效果不是内存地址,&[99 0 0 0 0]
}
func test9(a []int) {
a[0]=99
fmt.Println(a)
}
//func test7(b *int) {
// *b=99
//}
字符串
package main
import (
"fmt"
)
//字符串:Go 语言中的字符串是一个字节切片
func main() {
//1 循环打印字符串
//s:="hello world中国"
//fmt.Println(len(s)) //字节长度
//fmt.Println(utf8.RuneCountInString(s))
////printByte(s) //循环字节打印出来
//printZF(s) //循环字符打印出来
//2字符串是不可变的 不允许改值
//3 字符串跟数字转换
// 字符串转数字(可能会转错)
//var s="10ff"
//var a int
//a,err:=strconv.Atoi(s)
//fmt.Println(err)
//fmt.Println(a)
// 数字转字符串
//var a int=99
////s:=string(a) // 99对的assii码转过来
//s:=strconv.Itoa(a)
//fmt.Println(s)
//s := "hello world中国"
//printZF(s)
runeSlice := []rune{22269,22269}
byteSlice := []byte{97,98}
str := string(runeSlice)
str2 := string(byteSlice)
fmt.Println(str)
fmt.Println(str2)
}
func printByte(s string) {
for i := 0; i < len(s); i++ {
fmt.Println(s[i]) //s[i] 是byte int8
fmt.Println(string(s[i]))
}
}
func printZF(s string) {
for _, v := range s {
fmt.Println(v)
fmt.Printf("%T", v) //rune int32
fmt.Println(string(v))
}
}
结构体基础
package main
import "fmt"
//面向对象:结构体:一系列属性的集合
//面向对象三大特性:继承封装多态 但是没有类的概念
//1 定义一个结构体
//type 结构体名字 struct{属性...}
type Person struct {
name string
age int
sex string
}
func main() {
//1 定义结构体变量
//var per Person
////2 结构体零值(值类型),没有赋值,所以是结构体内部属性的零值
//fmt.Println(per)
//3 使用结构体(把名字改成lqz)
//per.name="lqz"
//per.age=18
//per.sex="男"
//fmt.Println(per)
//fmt.Println(per.name)
//4 定义结构体变量并赋初值
var per Person
//指名道姓的赋值,只给部分
//per=Person{name:"lqz",age:18,sex:"男"}
//per=Person{name:"lqz",sex:"男"}
per=Person{"lqz",18,"男"}
//per=Person{"lqz",18} //按位置传,必须传全了
fmt.Println(per)
fmt.Println(per.name)
}
结构体
package main
import "fmt"
//结构体:一系列属性的集合
//定义结构体(在包中定义了)
//7 定义一个结构体,内涵匿名字段(字段没有名字) 匿名字段类型就是字段名,所有类型不能重复
//type Person struct {
// string
// int
// sex string
//}
//8 嵌套结构体(结构体中套结构体)
//type Person struct {
// Name string
// Age int
// sex string
// Hobby Hobby
//}
//type Hobby struct {
// HobbyId int
// HobbyName string
//}
//9 字段提升
//type Person struct {
// Name string
// Age int
// sex string
// Hobby
//}
//type Hobby struct {
// HobbyId int
// HobbyName string
//}
//type Person struct {
// Name string
// Age int
// sex string
// Hobby
//}
//type Hobby struct {
// Id int
// Name string
//}
//11 结构体相等性
type Person struct {
Name string
Age int
sex string
//包含不可比较的字段
AAA []int
}
func main() {
//1 结构体的使用 值类型
//var per entity.Person
//fmt.Println(per)
//per.Name="lqz"
//per.Age=19
//fmt.Println(per)
//2 定义并赋初值
//var per entity.Person=entity.Person{Name:"lqz"} //不按位置,少传
//var per2 entity.Person=entity.Person{"lqz",19,"男"} //按位置,全穿
//fmt.Println(per2)
//per2.Age=20
//fmt.Println(per2)
//3 匿名结构体(定义在内部(函数,结构体),只使用一次,没有名字)
// 有什么用?当定义多个变量(想一次使用),就可以把这多个变量放到匿名结构体中
//a := struct {
// HobbyId int64
// HobbyName string
//}{HobbyId: 1, HobbyName: "篮球"}
//
//fmt.Println(a.HobbyName)
//a.HobbyName="足球"
//fmt.Println(a.HobbyName)
//4 结构体的零值,值类型
//var per entity.Person
//fmt.Println(per) //属性的零值,值类型,参数传递,copy传递,在函数中修改,不会影响原来的
//test2(per)
//fmt.Println(per)
//5 访问结构体字段 ,通过 . 来访问 注意大小写
//6 结构体的指针
//var per *entity.Person
//fmt.Println(per)
//定义并初始化
//var per *entity.Person=&entity.Person{}
//fmt.Println(per)
////把per的名字改成lqz
//(*per).Name="lqz"
////支持直接使用(数组也是这样,自动帮你处理了)
//per.Name="egon"
//fmt.Println(per)
//7 匿名字段(字段没有名字,只有类型)
//【变量提升/提升字段】面向对象的继承
//per:=Person{"lqz",19,"男"}
//per:=Person{string:"lqz",int:19,sex:"男"} //字段匿名,类型就是字段名
//fmt.Println(per)
//fmt.Println(per.string)
//fmt.Println(per.int)
//8 嵌套结构体(结构体中套结构体)
//var per Person =Person{"lqz",19,"男",Hobby{1,"篮球"}}
//var per Person =Person{Name: "lqz",Age: 19,sex: "男"}
//var per Person =Person{Name: "lqz",Age: 19,sex: "男",Hobby:Hobby{1,"足球"} }
//var per Person =Person{Name: "lqz",Age: 19,sex: "男",Hobby:Hobby{HobbyId: 1,HobbyName: "足球"}}
//fmt.Println(per.Name)
//fmt.Println(per.Hobby.HobbyName)
//9 字段提升
//var per Person =Person{"lqz",19,"男",Hobby{1,"篮球"}}
//var per Person =Person{Name: "lqz",Age: 19,sex: "男",Hobby:Hobby{HobbyId: 1,HobbyName: "足球"}}
////打印爱好的名字(Hobby是一个匿名字段,会字段提升)
//fmt.Println(per.HobbyName)
//fmt.Println(per.Hobby.HobbyName) //per.Hobby 类似于面向对象中的super()
////像什么?像面向对象的继承 子类继承父类(结构体嵌套,匿名字段),子类可以直接调用父类中的属性或方法
//var per Person =Person{Name: "lqz",Age: 19,sex: "男",Hobby:Hobby{ 1, "足球"}}
//fmt.Println(per.Name) //优先使用自己的
////打印出hobby的名字
//fmt.Println(per.Hobby.Name)
//10 导出结构体和字段 大写字母开头,在外部包可以使用
//11 结构体相等性
//结构体是值类型。
//如果它的每一个字段都是可比较的,则该结构体也是可比较的。 如果两个结构体变量的对应字段相等,则这两个变量也是相等的
//如果结构体包含不可比较的字段,则结构体变量也不可比较。
//值类型,可以直接==比较,引用类型只能跟nil用==比较
//per1:=Person{Name: "lqz"}
//per2:=Person{Name: "lqz",Age:19}
//fmt.Println(per1==per2)
//per1:=Person{Name: "lqz"}
//per2:=Person{Name: "lqz",Age:19}
//fmt.Println(per1==per2) //包含不可比较的属性
fmt.Println("lqz nb")
}
//func test2(per entity.Person) {
// per.Age=99
// fmt.Println(per)
//}
方法
package main
//方法:特殊函数,在函数的基础上加了一些东西
//在 func 这个关键字和方法名中间加入了一个特殊的接收器类型,接收器可以是结构体类型,也可以是非结构体类型
//type Person2 struct {
// Name string
// Age int
// Sex string
//}
//
////def add(self):
//// self.name
//// pass
////定义一个方法 : (p Person2) 绑定给了Person2结构体的对象
//func (p Person2) printName() {
// //在方法内可以使用p
// fmt.Println(p.Name)
//}
////修改名字
//func (p Person2) changeName(name string) {
// p.Name=name
// fmt.Println(p)
//}
////修改年龄方法
//func (p *Person2) changeAge(age int) {
// //fmt.Println(p.Age) //推荐用这个
// p.Age=age
//}
//
//func printName(p Person2) {
// fmt.Println(p.Name)
//}
// 匿名字段的方法
//type Person2 struct {
// Name string
// Age int
// Sex string
// Hobby //匿名字段
//}
//type Hobby struct {
// Id int
// Name string
//}
//
////给结构体绑定方法
//func (p Person2)printName() {
// fmt.Println(p.Name)
//}
////func (h Hobby)printHobbyName() {
//func (h Hobby)printName() {
// fmt.Println(h.Name)
//}
/// //6 在方法中使用值接收器 与 在函数中使用值参数
//type Person2 struct {
// Name string
// Age int
// Sex string
//}
////在方法中使用值接收器
//func (p Person2)printName() {
// fmt.Println(p.Name)
//}
//func (p Person2)changeName(name string) {
// p.Name=name
// fmt.Println(p)
//}
////在函数中使用值参数
//func printName(p Person2) {
// fmt.Println(p.Name)
//}
//7 在方法中使用指针接收器 与 在函数中使用指针参数
//type Person2 struct {
// Name string
// Age int
// Sex string
//}
////在方法中使用值接收器
//func (p *Person2)printName() {
// fmt.Println(p.Name)
//}
//func (p *Person2)changeName(name string) {
// p.Name=name
// fmt.Println(p)
//}
////在函数中使用指针参数
//func printName(p *Person2) {
// //fmt.Println((*p).Name)
// fmt.Println(p.Name)
//}
//8 非结构体上的方法(不允许)自己定义的类型可以绑定方法
//在int类型上绑定一个add方法
//不允许
//func (i int)add(){
// i=i+1
// i++
// //i+=1
//}
// 可以在自定义的类型上绑定方法
type Myint int
func (i *Myint)add(){
(*i)=(*i)+1
//i++
//i+=1
}
func main() {
//1 方法的定义和使用
//per:=Person2{}
//per.Name="lqz"
//per.printName() //绑定给对象的方法
//
//per1:=Person2{Name: "egon"}
//per1.printName()
//2 为什么我们已经有函数了还需要方法呢?
//per1:=Person2{Name: "egon"}
////per1.printName() //方法的特殊之处,可以自动传值
////
////printName(per1)
//per1.changeName("lqz")
//
////并没有改
//fmt.Println(per1)
//3 指针接收器与值接收器
//per1:=Person2{Name: "egon",Age: 18}
//fmt.Println(per1)
//per1.changeAge(99)
//fmt.Println(per1)
//4 时候使用指针接收器,什么时候使用值接收器:想改原来的,就用指针,不想改原来的就用值(指针用的多)
//5 匿名字段的方法(方法提升)
//per1:=Person2{Name: "lqz",Hobby:Hobby{1,"足球"}}
////per1.printHobbyName() //Hobby是个匿名字段,方法也提升了
////如果方法名冲了,优先用该结构体自己的
//per1.printName()
//per1.Hobby.printName()
//6 在方法中使用值接收器 与 在函数中使用值参数
//per1:=Person2{Name: "lqz"}
//per1.printName()
//printName(per1)
//per1:=&Person2{Name: "lqz"} //per1是个指针
//per1.printName()
//printName(*per1)
//小研究
//per1:=&Person2{Name: "lqz"} //per1是个指针
//per1.changeName("egon")
//fmt.Println(per1)
//值收器:可以用值来调,也可以用指针来调
//函数的值参数,只能传值
//7 在方法中使用指针收器 与 在函数中使用指针参数
//per1:=Person2{Name: "lqz"}
//per1.printName() //值可以来调用
//printName(&per1)
//per1:=&Person2{Name: "lqz"}
//per1.printName() //指针可以来调用
//printName(per1)
//小研究
//per1:=Person2{Name: "lqz"}
//per1.changeName("egon")
//fmt.Println(per1)
//per1:=&Person2{Name: "lqz"}
//per1.changeName("egon")
//fmt.Println(per1)
//总结:不管是值类型接收器还是指针类型接收器,都可以用值来调用,或者指针来调用
//总结:不管是值还是指针来调用,只要是值类型接收器,改的就是新的,只要是指针类型接收器,改的是原来的
//a:=1
//a+=1
//a++
//a=a+1
//++a //java中有++a
//fmt.Println(a)
//8 非结构体上绑定方法
//var a Myint =10
//fmt.Println(a)
//a.add()
//a.add()
//a.add()
//a.add()
//fmt.Println(a)
//
//var b =11
////fmt.Println(a+b) //类型不匹配
//
//c:=a+Myint(b)
//fmt.Println(a+Myint(b)) //类型匹配
//d:=int(a)+b
//fmt.Println(int(a)+b) //类型匹配
}
接口
package main
//go也是鸭子类型:我现在有个鸭子类,内有speak方法 有run方法, 子类只要实现了speak和run,我就认为子类是鸭子
//在java中,子类必须实现鸭子类的所有方法,子类才叫鸭子
//接口:面向对象的领域里,接口一般这样定义:接口定义一个对象的行为,规范子类对象的行为
//接口:是一系列方法的集合(规范行为)
//1 定义接口(定义一个鸭子接口,speak方法,run方法)
//type Duck interface {
// speak() //speak()方法
// run()
//}
//
////定义一个普通鸭子结构体
//type PDuck struct {
// name string
// sex string
// age int
//}
//
////定义一个唐老鸭结构体
//type TDuck struct {
// name string
// sex string
// age int
// wife string
//}
////让唐老鸭和普通鸭子都实现Duck接口
////结构体实现一个接口:只要绑定接口中的所有方法,就叫实现该接口
//func (p PDuck)speak() {
// fmt.Println("普通鸭子嘎嘎叫,普通鸭子名字叫",p.name)
//}
//func (p PDuck)run() {
// fmt.Println("普通鸭子歪歪扭扭走了,普通鸭子名字叫",p.name)
//}
//
////唐老鸭也实现Duck接口
//func (p TDuck)speak() {
// fmt.Println("唐老鸭说人话,唐老鸭子名字叫",p.name)
//}
//func (p TDuck)run() {
// fmt.Println("唐老鸭人走路,唐老鸭子名字叫",p.name)
//}
//
//
////6 空接口定义
//type Empty interface {
//}
func main() {
//1 得到一个普通鸭子对象
//pduck:=PDuck{"黑子","男",1}
//pduck.run()
//pduck.speak()
////2 得到一个堂老鸭子对象
//tduck:=TDuck{"egon","男",1,"刘亦菲"}
//tduck.run()
//tduck.speak()
//侵入式接口(接口没了,子类报错)和非侵入是接口(接口没了,不影响代码,go语言中的接口是非侵入式的)
//2 接口的实际用途(接口也是一个类型)
//var duck Duck
////pduck:=PDuck{"黑子","男",1}
//tduck:=TDuck{"egon","男",1,"刘亦菲"}
////duck=pduck
//duck=tduck //多态,同一类事务多种形态
//duck.run()
//3 接口内部表示
//我们可以把接口看作内部的一个元组 (type, value)。
//type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。
//4 把接口类型转成struct,属性,自有方法也有了,类型断言
//类型断言
//var duck Duck =TDuck{"egon","男",1,"刘亦菲"}
////断言是TDuck类型
////v, ok := duck.(TDuck)
//////断言成功,ok是true,v就是TDuck结构体对象
////fmt.Println(v)
////fmt.Println(v.name)
////fmt.Println(ok)
//
////断言失败
//var v PDuck
//var ok bool
//v, ok = duck.(PDuck)
////断言失败,ok是false,v是PDuck类型的空置,因为没有复制
//fmt.Println(ok)
//fmt.Println(v)
//5 类型选择(通过switch)
//var duck Duck =TDuck{"egon","男",1,"刘亦菲"}
////var duck Duck =PDuck{"egon","男",1}
//test4(duck)
//6 空接口(没有任何方法,所有数据类型都实现了空接口)
//var a int=10
//var b string="lqz"
//var c [3]int
//var e Empty //空接口类型
//e=a
//e=b
//e=c
//fmt.Println(e)
//fmt.Println(1,"xxx")
//test5(a)
//test5(b)
//test5(c)
//7 匿名空接口 没有名字的空接口 一般用在形参上
//test6(10)
//test6("lll")
//var duck TDuck =TDuck{"egon","男",1,"刘亦菲"}
//test6(duck)
//8 之前学过的集合类型,都可以放接口类型
//var a[3]Duck
//a[1]=PDuck{}
//a[2]=TDuck{}
//var a map[string]interface{}= make(map[string]interface{})
//a["name"]="lqz"
//a["age"]=19
//a["duck"]=PDuck{}
}
//func test6(b interface{}) {
// fmt.Println(b)
//}
//func test5(b Empty) {
// switch v:=b.(type) {
// case string:
// fmt.Println("我是字符串")
// fmt.Println(v)
// case int:
// fmt.Println("我是int")
// fmt.Println(v)
// case [3]int:
// fmt.Println("我是数组")
// fmt.Println(v)
// }
//}
//func test4(duck Duck) {
// if v,ok:=duck.(TDuck);ok{
// fmt.Println("我是普通鸭子")
// fmt.Println(v)
// }else if v,ok:=duck.(PDuck);ok {
// fmt.Println("我是普通鸭子")
// fmt.Println(v)
// }
//}
//使用switch,选择成功,拿到结构体对象
//func test4(duck Duck) {
// switch v:=duck.(type) {
// case PDuck:
// fmt.Println(v.name)
// fmt.Println("我是普通鸭子")
// case TDuck:
// fmt.Println(v.wife)
// fmt.Println("我是唐老鸭")
// default:
// fmt.Println(v)
// fmt.Println("我是鸭子这个类")
//
// }
//}
自定义集合类型
package main
import "fmt"
//定义MySet类型
type MySet map[interface{}]bool
//判断元素是否存在
func (m MySet) isExist(a interface{}) bool {
return m[a]
}
//返回set长度
func (m MySet) len() int {
return len(m)
}
//设置值
func (m MySet) set(a interface{}) {
m[a] = true
}
//删除值
func (m MySet) delete(a interface{}) {
delete(m, a)
}
//测试代码
func main() {
//创建一个set
var a MySet = make(MySet)
//相当于
//var a MySet = make(map[interface{}]bool)
//打印set的长度
//fmt.Println(a.len())
//放入一个值
a.set(1)
//放入一个相同值
a.set(1)
a.set("lqz")
a.set("lqz")
a.set("lqz")
a.set("lqz")
a.set("lqz")
a.set("lqz")
//打印长度,还是1
//fmt.Println(a.len())
//判断1是否存在
//fmt.Println(a.isExist(2))
////删除1
a.delete(1)
////判断1是否存在
fmt.Println(a.isExist(1))
fmt.Println(a.len())
for i,_:=range a{
fmt.Println(i)
}
}
make和new的区别
package main
//make和new的区别
type PDuck1 struct {
name string
sex string
age int
}
func main() {
//make是引用类型初始化的时候用的
//var per *PDuck1 =new(PDuck1) //new 是返回指向这个类型的指针
//fmt.Println(per)
//
//
//var per1 =&PDuck1{}
//fmt.Println(per1)
//var per2 = make([]int,3,4) //make是具体的造引用类型 //new是造指向这个类型的指针
//var per2 *[]int= new([]int)
//fmt.Println(per2)
//(*per2)=append((*per2),99)
//fmt.Println(per2)
}
结构体取代类
package main
import (
person "day04/Person"
"fmt"
)
func main() {
per :=person.New("lqz",19,"男")
//var per Person = new Person("lqz",19,"男")
fmt.Println(per)
per.PrintName()
}
并发和并行
1 并发:同一时间段内,多个任务在执行(单个cpu,执行多个任务)
2 并行:同一时刻,多个任务在执行(多个cpu的支持)
goroutine
package main
import (
"fmt"
"runtime"
"time"
)
//goroutine--->协程---2kb大小,100
//线程----》几个m
//go协程会复用线程
// goroutine之间通信,通过 信道channel 通信
//go推崇用信道通信,而不推崇用共享变量通信(锁,死锁)
//启动一个goroutine
func test() {
fmt.Println("go go go")
}
//func main() {
// fmt.Println("主线程开始执行")
// go test()
// go test()
// go test()
// go test()
// go test()
// go test()
// go test()
// go test()
// time.Sleep(1*time.Second)
// fmt.Println("主线程结束执行")
// //go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己处理
//}
//func main() {
// fmt.Println("主线程开始执行")
// for i:=0;i<10;i++ {
// go func(){
// fmt.Println("go go go ")
//
// }()
// }
// time.Sleep(1*time.Second)
// fmt.Println("主线程结束执行")
// //go语言中,主线程不会等待goroutine执行完成,要等待它结束需要自己处理
//}
// go 关键字开启goroutine,一个goroutine只占2kb
/*
go语言的GMP模型
-G:开的goroutine
-M:M当成操作系统真正的线程,实际上是用户线程(用户线程)
-P:Processor:现在版本默认情况是cpu核数(可以当做cpu核数)
用户线程,操作系统线程
python中,开的线程开出用户线程,用户线程跟操作系统线程1:1的对应关系
某些语言,用户线程和操作系统线程是n:1的关系
go语言,用户线程和操作系统线程是 n:m的关系
*/
//举个例子
func main() {
//设置P的大小,认为是cpu核数即可
runtime.GOMAXPROCS(1)
fmt.Println("主线程开始执行")
go func() {
for {
fmt.Println("xxxx")
}
}()
//for i:=0;i<10;i++ {
// go func(){
// for {
// fmt.Println("我是死循环")
//
// }
//
// }()
//}
time.Sleep(10*time.Second)
fmt.Println("主线程结束执行")
}
信道(通道)
package main
import (
"fmt"
"time"
)
//不同goroutine之间通信
//通过channel实现
func main() {
//1 定义channel
var c chan int
//2 信道的零值(引用类型,空值为nil,当做参数传递时,不需要取地址,改的就是原来的,需要初始化再使用)
fmt.Println(c)
//3 信道初始化
c=make(chan int) //数字暂时先不关注
//4 信道的放值 (注意赋值和放值)
//c<-1
//c=12 //赋值报错
//5 信道取值
//<-c
//6 取出来赋值给一个变量 int
//var a int
//a=<-c
//a:=<-c
//7 信道默认不管放值还是取值,都是阻塞的
//c是引用类型
go test1(c)
a:=<-c //阻塞 不但实现了两条协程之间通信,还实现了等待协程执行结束
fmt.Println(a)
}
func test1(a chan int) {
fmt.Println("go go go ")
time.Sleep(1*time.Second)
//往信道中放一个值
a<-10 //阻塞
}
package main
//信道小例子
//程序有一个数中 每一位的平方和与立方和,然后把平方和与立方和相加并打印出来
import (
"fmt"
"time"
)
func calcSquares(number int, squareop chan int) {
sum := 0 //总和
for number != 0 {
digit := number % 10 //589对10取余数,9 8 5
sum += digit * digit //sum=9*9 8*8 5*5
number /= 10 //num=58 5 0
}
time.Sleep(2*time.Second)
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
time.Sleep(1*time.Second)
cubeop <- sum
}
//func calcSquares(number int, squareop chan int) int{
// sum := 0 //总和
// for number != 0 {
// digit := number % 10 //589对10取余数,9 8 5
// sum += digit * digit //sum=9*9 8*8 5*5
// number /= 10 //num=58 5 0
// }
// time.Sleep(2*time.Second)
// return sum
//}
//
//func calcCubes(number int, cubeop chan int) int{
// sum := 0
// for number != 0 {
// digit := number % 10
// sum += digit * digit * digit
// number /= 10
// }
// time.Sleep(2*time.Second)
// return sum
//}
func main() {
ctime:=time.Now().Unix()
fmt.Println(ctime)
number := 589
sqrch := make(chan int)
cubech := make(chan int)
//num1:=calcSquares(number, sqrch)
//num2:=calcCubes(number, cubech)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
//squares:= <-sqrch
//cubes:=<-cubech
fmt.Println("Final output", squares + cubes)
ttime:=time.Now().Unix()
fmt.Println(ttime)
fmt.Println(ttime-ctime)
}
package main
import "fmt"
//信道的死锁现象,默认都是阻塞的,一旦有一个放,没有人取 或者一个人取,没有人放,就会出现死锁
//func main() {
// //var c chan int =make(chan int )
//
// //c<-1 //其实放不进去,阻塞在这,就死锁了
// //<-c //没有,取不到,阻塞在这,就死锁了
//
//}
//单向信道
//func sendData(sendch chan<- int) {
// sendch <- 10
//}
//
//func main() {
// //sendch := make(chan<- int) //定义了一个只写信道
// sendch := make(chan int) //定义了一个可读可写信道
// go sendData(sendch) //传到函数中转成只写信道,在goroutine中,只负责写,不能往外读,主协程读
// fmt.Println(<-sendch) //只写信道一旦读,就有问题
//}
///3 关闭信道
//func main() {
// sendch := make(chan int)
// //关闭信道
// //close(sendch)
//
// //信道可以用for循环循环
//}
// 信道关闭close(sendch) ,for循环循环信道,如果不关闭会报死锁,如果关闭了,放不进去,循环结束
func producer(chnl chan int) {
for i := 0; i < 100; i++ {
fmt.Println("放入了",i)
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for v := range ch {
fmt.Println("Received ",v)
}
}
缓冲信道
package main
import (
"fmt"
"sync"
"time"
)
//缓冲信道:只在缓冲已满的情况,才会阻塞向缓冲信道,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据
//func main() {
// var c chan int =make(chan int,6) //无缓冲信道数字是0
// c<-1
// c<-2
// c<-3
// c<-4
// c<-5
// c<-6
// //c<-7 //死锁
// <-c
// <-c
// //<-c
// //<-c
// //<-c
// //<-c
// //<-c // 取空了,死锁(一个goroutine中会出现)
//
//
// // 2 长度 vs 容量
// //fmt.Println(len(c)) //目前放了多少
// //fmt.Println(cap(c)) //可以最多放多少
//
//
//
//}
//3 WaitGroup 等待所有goroutine执行完成
//4 使用信道如何实现?
func process1(i int,wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep(2 * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
//一旦有一个完成,减一
wg.Done()
}
func main() {
var wg sync.WaitGroup //没有初始化,值类型,当做参数传递,需要取地址
//fmt.Println(wg)
for i:=0;i<10;i++ {
wg.Add(1) //启动一个goroutine,add加1
go process1(i,&wg)
}
wg.Wait() // 一直阻塞在这,知道调用了10个done,计数器减到零
}
Select
package main
import (
"fmt"
"time"
)
//func server1(ch chan string) {
// time.Sleep(6 * time.Second)
// ch <- "from server1"
//}
//
//
//func server2(ch chan string) {
// time.Sleep(3 * time.Second)
// ch <- "from server2"
//
//}
//
//func main() {
// output1 := make(chan string)
// output2 := make(chan string)
// //开启两个协程执行server
// go server2(output1)
// go server2(output2)
// select {
// case s1 := <-output1:
// fmt.Println(s1,"ddddd")
// case s2 := <-output2:
// fmt.Println(s2,"yyyy")
// }
//}
//func process(ch chan string) {
// time.Sleep(10500 * time.Millisecond)
// ch <- "process successful"
//}
//
//func main() {
// ch := make(chan string)
// go process(ch)
// for {
// time.Sleep(1000 * time.Millisecond)
// select {
// case v := <-ch:
// fmt.Println("received value: ", v)
// return
// default:
// // 可以干其他事,模拟非阻塞式io
// fmt.Println("no value received")
// }
// }
//
//}
//死锁
//func main() {
// ch := make(chan string)
// select {
// case <-ch:
// }
//}
// 随机选取
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
func main() {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(1 * time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
mutex
package main
import (
"fmt"
"sync"
)
// 使用锁的场景:多个goroutine通过共享内存在实现数据通信
// 临界区:当程序并发地运行时,多个 [Go 协程]同时修改共享资源的代码。这些修改共享资源的代码称为临界区。
//如果在任意时刻只允许一个 Go 协程访问临界区,那么就可以避免竞态条件。而使用 Mutex 可以达到这个目的
//var x = 0 //全局,各个goroutine都可以拿到并且操作
//func increment(wg *sync.WaitGroup,m *sync.Mutex) {
// m.Lock()
// x = x + 1
// m.Unlock()
// wg.Done()
//}
//func main() {
// var w sync.WaitGroup
// var m sync.Mutex //是个值类型,函数传递需要传地址
// fmt.Println(m)
// for i := 0; i < 1000; i++ {
// w.Add(1)
// go increment(&w,&m)
// }
// w.Wait()
// fmt.Println("final value of x", x)
//}
// 通过信道来做
var x = 0
func increment(wg *sync.WaitGroup, ch chan bool) {
ch <- true // 缓冲信道放满了,就会阻塞
x = x + 1
<- ch
wg.Done()
}
func main() {
var w sync.WaitGroup
ch := make(chan bool, 1) //定义了一个有缓存大小为1的信道
for i := 0; i < 1000; i++ {
w.Add(1)
go increment(&w, ch)
}
w.Wait()
fmt.Println("final value of x", x)
}
// 不同goroutine之间传递数据:共享变量, 通过信道
// 如果是修改共享变量,建议加锁
//如果是协程之间通信,用信道
异常处理
package main
import (
"fmt"
"os"
)
// 异常处理
//defer:延迟执行,并且即便程序出现严重错误,也会执行
//panic:主动抛出异常 raise
//recover:恢复程序,继续执行
//
//func main() {
//
// defer fmt.Println("我最后执行") //注册一下,并不执行,等main函数执行完了以后,从下往上执行defer定义的东西
// defer fmt.Println("我倒数第二个打印")
// fmt.Println("我先执行")
// //var a []int
// //fmt.Println(a[10])
// panic("我出错了")
// fmt.Println("ccccc")
//
// //假设打开一个文件
// //f:=open()
// //defer f.close()
// //
// //出错了
// //
//
//}
//
//func f1(){
// fmt.Println("f1 f1")
//}
//func f2(){
// defer func() { //这个匿名函数永远会执行
// //error:=recover() //恢复程序继续执行
// //fmt.Println(error) //如果没有错误,执行recover会返回nil 如果有错误,,执行recover会放错误信息
// if error:=recover();error!=nil{
// //表示出错了,打印一下错误信息,程序恢复了,继续执行
// fmt.Println(error)
// }
// // 相当于finally
// fmt.Println("我永远会执行,不管是否出错")
// }()
// fmt.Println("f2 f2")
// //panic("主动抛出错误")
//}
//func f3(){
// fmt.Println("f3 f3")
//}
//
//func main() {
// //捕获异常,处理异常,让程序继续运行
//
// f1()
//
// f2()
// f3()
//}
/*
try:
可能会错误的代码
except Exception as e:
print(e)
finally:
无论是否出错,都会执行
*/
/*现在这么写
defer func() {
if error:=recover();error!=nil{
//except的东西
fmt.Println(error)
}
//相当于finally,无论是否出错,都会执行
}()
可能会错误的代码
*/
// go的错误处理
func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}