标识符&关键字
标识符
关键字
变量
var 变量名 变量类型 or var (变量名1 变量类型 \n 变量名2 变量类型)
变量声明未被使用,则会编译不过
- 变量声明
- 变量批量声明
- 局部变量
- 匿名变量
常量
const
- 批量声明
iota
常量计数器
数组
数组声明
var 数组名 [数组长度]数据类型
例子:var al [3]bool //[false,false,false]
var a12 [行数量][列数量] int
数组初始化
不够的数量用相应的类型数据填充
a1 = [3]bool{true, true}
a1 = [...]bool{true,true,true} //自动判断数组长度
a1 = [5]{0:1,4:2}//特定数组长度设置值
a12 = [3]int{
[2]int{1,2},
[2]int{3,4},
[2]int{5,6},
}
数组的遍历
a1 := [3]string{"胡", "加", "加"}
for i := 0; i < len(a1); i++ {
fmt.Println(a1[i])
}
a1 := [3]string{"胡", "加", "加"}
for i, v := range a1 {
fmt.Println(i, v)
}
for _, v := range a1 {
fmt.Println(i, v)
}
切片(slice)
拥有相同类型数据可变长度的数据序列 ,长度可变,引用类型,判断slice是空的,用len(slice)==0判断,不用nil;
定义
var 数组名 []数据类型
例子: var al []int
初始化
a1 := []int{1,2,3}
// 由数组创建切片
a2 :=[5]int{1,2,3,4,5}
a3 :=a1[1:4]
// 由切片创建切片
a4 :=a3[0:2]
fmt.Println(a2, a3, a4) => 输出 [1 2 3 4 5] [2 3 4] [2 3]
切片容量&长度
make 函数创建切片
make([]数据类型,长度,容量)
a1 := make([]int,5,10)
fmt.Println(len(a1), cap(a1))
append 切片追加元素
当容量 不够时,底层会进行扩容处理,扩容策略会根据当前值大小,数据类型等做出相应的扩容处理!!!!
a1 := make([]int, 2, 2)
fmt.Println(len(a1), cap(a1), a1)
a1 = append(a1, 3)
fmt.Println(len(a1), cap(a1), a1)
a1 = append(a1, 4, 5)
fmt.Println(len(a1), cap(a1), a1)
a2 := make([]int ,1,2)
a1 = append(a1,a2...)
扩容策略源码见 /src/runtime/slice.go growslice方法。go版本 “go1.15.6 windows/amd64”
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
复制切片copy
copy 复制,值赋值
copy(目标切片,源切片)
指针
&=》 取地址
*=》 根据地址取值
var a1 *int // =>指针指向nil
*a1 = 100 // 会报错的
var a2 = new(int) 开辟一个空间
map
无序的key-value数据结构。引用类型。类似js中的object
声明 名字 map[key类型] value类型
var mapname map[string] int ;
初始化 使用make
mapname =make(map[string]int,size) // 估算map容量,避免动态扩容
// 设置值
mapname["key"] = 2
// 获取值
value ,isbe := mapname["key2"]
//map遍历
for key, value := range mapname {
fmt.Printf(key, v)
}
// map 删除
函数
函数的定义
func1 functionname(参数1 参数类型1,参数2 参数类型2)(返回值1 返回类型1){
return 返回类型1的数据
}
func1 functionname(参数1 参数类型1,参数2 参数类型2) 返回类型1{
return 返回类型1的数据
}
func functionname(){
}
defer
延迟操作,延迟后面的语句
defer fmt.Printf("start 1 => ")
defer fmt.Printf("start 2 => ")
defer fmt.Printf("start 3 => ")
fmt.Printf("start 4 => ")
结果 start 4 => start 3 => start 2 => start 1 =>
defer执行契机:
如果延迟执行内部有需要执行的条件,需要先满足执行条件
func() {
a := 1
b := 2
defer callbac("1", a, callbac("10", a, b))
a = 0
defer callbac("2", a, callbac("20", a, b))
b = 1
}()
run ==>
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
函数作为参数
函数可以作为一种类型,可以作为参数传递
func f3(x func() int) {
result := x()
fmt.Printf("%T\n", result)
}
函数可以作为一个返回值
func f3(x func() int) func() {
result := x()
return result
}
func f3(x func() int) func(int ,int ) int {
result := x()
return result
}
实际上也还是函数的样子。参数的变量,变量类型。 返回值,返回类型。
匿名函数
没有名字的函数,一般写在函数内,作为代码块。或者立即执行此函数
f1 := func() {
fmt.Println("这是匿名函数")
}
f1()
func() {
fmt.Println("这是匿名函数立即执行")
}()
闭包
和js 类似,将变量的”生命”延长了,本来变量随着函数的生命周期的结束而结束,但是闭包将需要使用的变量生命延长至使用处。这个不需要过度的研究闭包。本身这个名字就很怪,非常怪,这概念在js中也是令人头痛。
内置函数
close | 主要关闭channel |
---|---|
len | string,array,slice,map,channel 长度 |
new | 分配值类型 内存,比如int,struct |
make | 分配引用类型内存,chan.map,slice |
append | 追加元素到array,slice |
panic& recover | 错误处理 |
panic&recover
panic 在程序的任何地方都可能出现会导致程序崩溃,recover 在defer 中才能使用,用来补救函数.
在某些资源被打开之后,程序panic .这是补救就是,在defer 下释放资源。
func test() {
defer func() {
err := recover()
fmt.Println(err)
println("兜底关闭了文件")
}()
println("打开了文件")
panic("程序崩溃了")
}
defer 补救在可能出现错误之前定义
结构体
类型别名
type 类型别名 = 类型 // 类型别名,在程序编译的时候是原型类型
自定义类型
type 自定义类型 类型 // 自定义类型,编译之后是一定义类型
定义声明
type 结构体名字 struct{
属性名字1 属性类型1
属性名字2 属性类型2
}
匿名结构体
var dd struct {
属性名字1 属性类型1
属性名字2 属性类型2
}
结构体指针
type person struct{
name string
age int
}
per := person{
name :"名字",
age:1
}
per:= new(person)
内存分布
结构体中数据的内存是连续的,即初始化时分配了连续的内存
仿造构造函数
type person struct {
name string
age int
}
func newperson(name string, age int) person {
return person{
name: name,
age: age,
}
}
// 或者
func newperson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}
仿造“对象”的方法
限定方法的时候对象
func (per person) hold(){
fmt.Printf("人类对象实例",per.name) // 此时per和外部的不是一个东西
}
func (per *person) hold(){
per.age ++;
fmt.Printf("人类对象实例",per.name) // 传外部指针
}
var pers = new(person)
pers.hold()
此操作可以给自定义类型进行扩展
type myInt int
func (myint myInt)hello(){
fmt.Println("这是自定义类型")
}
func main(){
test := myInt(100)
test.hello()
}
结构体匿名字段
字段类型当作 key
type person struct{
string
int
}
结构体嵌套
结构体组成了结构体
type address struct {
city string
}
type person struct {
name string
age int
address address
}
//type person struct { 匿名嵌套结构体
// name string
// age int
// address
//}
func main (){
per :=person{
name:"名字",
age:1,
address:{
city :"城市"
}
}
fmt.Println(per.name,per.address.city);
}
仿造“对象”继承
利用嵌套结构体,将父级属性嵌入.
json于结构体的转换
结构体中属性名需要大写才可以被其他包识别
序列化 && 反序列化
per := person{
Name: "名字", Age: 1,
}
// 序列化
p, err := json.Marshal(per)
if err != nil {
fmt.Println("序列化错误")
return
}
fmt.Println("序列化结果", string(p))
str := `{"Name":"理想","age":18}`
var paf person
json.Unmarshal([]byte(str), &paf)
fmt.Printf("反序列化结果%#v\n", paf)
接口
interface, 一种类型,但是不能被实例化,他的作用呢?可以说是一些方法定义的集合体。任何实现了这些方法一个或者多个的数据类型都是这个接口的“实现”。这意味着可以通过一个统一的渠道来执行这些方法!!!
package
- 一个文件夹下面只能有一个包
- 一个程序有且只有一个main 方法,入口方法
- 引入包,以$GOPATH/src 为起始路径
- 包中标识符(变量名/接口/方法/结构体)首字母小写,表示私有,其他包不能使用.
给引入的包设置别名
import(
myself "com.yourself.code.project.module"
)
匿名导入.导入了但是没有使用其中方法.(会执行init),比如数据库操作
impoert _ "com.yourself.code.project.module"
文件操作
读取文件
filrobj, err := os.Open("./main.go") // 打开文件
if err != nil {
fmt.Println("open file error : %v", err)
return
}
defer filrobj.Close()
for {
var tmp [128]byte
filedata, err := filrobj.Read(tmp[:]) //从文件对象拿数据
if err != nil {
fmt.Println("read file error,errr:%v", err)
return
}
fmt.Println("读取了字节数", filedata)
fmt.Println(string(tmp[:filedata]))
if filedata < 128 { // 代表数据读取完毕
return
}
}
使用包ioutil读取文件
ret, err := ioutil.ReadFile("./main.go")
if err != nil {
fmt.Println("读取文件错误%v", err)
return
}
fmt.Println(string(ret))
写入文件
file, err := os.OpenFile("./main.go", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println("写文件打开错误%v", err)
}
defer file.Close()
file.WriteString("//测试") == >实际上是对Write 的封装
file.Write([]byte("//测试"))
使用bufio 写文件
file, err := os.OpenFile("./res.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println("写文件打开错误%v", err)
}
defer file.Close()
// file.WriteString("//测试")
// 使用bufio
wr := bufio.NewWriter(file)
wr.WriteString("使用bufio 写数据到缓冲,不过记得将缓冲数据刷新到文件内")
wr.Flush()
获取用户控制台输入
var s string
reader := bufio.NewReader(os.Stdin)
s, _ = reader.ReadString('\n') // 回车终止
fmt.Println("用户输入的数据库%s", s)
反射
json与结构体的转换应用了反射,GO 是强类型的语言,基本上在编译阶段就能确定程序的类型,但是有的情况是在编译时不知道的,这时候使用反射,在程序运行的时候再去获取变量类型.比如orm库.配置文件等等
reflect.TypeOf
func reflectType(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("类型:%v\n", v)
}
func main() {
var a float32 = 3.14
reflectType(a) // == > 类型:float32
}
类型种类(kind)&&名称(Name)
type Dog struct {
}
func reflectType(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("type:%v kind :%v ", v.Name(), v.Kind())
}
func main() {
var dog = Dog{}
reflectType(dog)
}
reflect.ValueOf
值的种类
func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind()
switch k {
case reflect.Int64:
fmt.Printf("type is int64 ,value is %d\n ", int64(v.Int()))
case reflect.Float32:
fmt.Printf("type is float32 ,value is %f\n ", float32(v.Float()))//=>type is float32 ,value is 3.140000
}
}
func main() {
var a float32 = 3.14
reflectValue(a)
}
反射设置值
函数传值在函数或方法体内是操作值拷贝.所以基本传指针.否则通过反射修改值会引发panic.通过Elem 获取指针的值进行修改操作
// 指针传递
func reflectSetValue(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Float32 {
v.Elem().SetFloat(3.222)
}
}
// 非指针传递
func reflectSetValuePinc(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Float32 {
v.Elem().SetFloat(3.111)
}
}
func main() {
var a float32 = 3.14
reflectSetValue(&a)
// reflectSetValuePinc(a)
fmt.Print("修改之后的值是", a)
// var dog = Dog{}
// reflectType(&dog)
}
IsValid&& IsNil
IsValid 判定返回值是否有效,IsNil 判定指针是否为空
func IsNilIsValid() {
var a *int
fmt.Println("指针空", reflect.ValueOf(a).IsNil())
fmt.Println("nil is Valid", reflect.ValueOf(nil).IsValid())
b := struct{}{}
fmt.Println("结构体中是否存在某个属性", reflect.ValueOf(b).FieldByName("属性名字在").IsValid())
fmt.Println("结构体中是否存在某个方法", reflect.ValueOf(b).MethodByName("方法名字").IsValid())
mapobj := map[string]int{}
fmt.Println("map中不存在的key", reflect.ValueOf(mapobj).MapIndex(reflect.ValueOf("搞钱")).IsValid())
}
//指针空 true
//nil is Valid false
//结构体中是否存在某个属性 false
//结构体中是否存在某个方法 false
//map中不存在的key false
结构体反射
StructField
structfield 用来描述结构体中一个字段信息,其拥有修改获取方法. 官方
type StructField struct {
// Name is the field name.
Name string
// PkgPath is the package path that qualifies a lower case (unexported)
// field name. It is empty for upper case (exported) field names.
// See https://golang.org/ref/spec#Uniqueness_of_identifiers
PkgPath string
Type Type // field type 字段类型
Tag StructTag // field tag string 字段标签
Offset uintptr // offset within struct, in bytes 字段在结构体中字节偏移量
Index []int // index sequence for Type.FieldByIndex 索引切片
Anonymous bool // is an embedded field 是否是匿名字段
}
exapmle
type student struct {
Name string `json:"name" yml:"servename"`
Age int `json:"age"`
}
func readstructField() {
stu := student{
Name: "护甲问",
Age: 28,
}
typ := reflect.TypeOf(stu)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
fmt.Printf("name:%s index:%d type:%v tag:%v \n", field.Name, field.Index, field.Type, field.Tag.Get("yml"))
}
if scoreField, ok := typ.FieldByName("Name"); ok {
fmt.Printf("name:%s index:%d type:%v tag:%v \n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("yml"))
}
}
name:Name index:[0] type:string tag:servename
name:Age index:[1] type:int tag:
name:Name index:[0] type:string tag:servename
缺点
- 反射类型错误,在运行时才会出错,意味着错误延后性.
- 性能低下
- 代码缺少联系性,增加阅读难度