0
点赞
收藏
分享

微信扫一扫

Go基础语法

认真的老去 2021-09-27 阅读 65
日记本

变量基本语法

    var a int
    fmt.Println(a)//0
    //自动类型推导
    PI:=3.14159
    fmt.Println(PI)
    //go中不同数据类型不能直接计算,需要类型转换
    r:=5
    //fmt.Println(PI*r) 这样就报错了
    fmt.Println(PI*(float64(r))) //15.70795

    //多重赋值(忽略某个赋值使用_占位符(匿名变量))
    b,_,d:=true,"字符串",1
    fmt.Println(b,d) //true 1
    //交换赋值
    //a,b=b,a

    //格式输出
    //%d 代表输出整形数据
    //%f 代表输出浮点形数据(默认保留6位小数位)
    //%t 代表输出布尔形数据
    //%s 代表输出字符串数据
    //%c 代表输出字符型数据对于ascii值
    e:=30
    f:=3.14
    fmt.Printf("%d\n",e) //30
    fmt.Printf("%f\n",f)//3.140000
    fmt.Printf("%.3f\n",f)//3.140
    fmt.Printf("%3d\n",e)// 30  补位,全部长度是3位,如果长度超过则无影响
    fmt.Printf("%-3d\n",e)//30  后面补位,全部长度是3位,如果长度超过则无影响
    fmt.Printf("%05d\n",e)//00030  指定补位的数据


    //格式化输入
    var a1 int
    //fmt.Scan(&a1)
    //%p 代表输出一个数据对应的内存地址
    fmt.Printf("%p\n",&a1)//0xc0000540f0
    fmt.Printf("%d\n",a1)//1

    var b1 string
    fmt.Scanf("%3d%s",&a1,&b1)
    //输入123456 则此时会123赋值给a1剩下赋值给b1
    //如果输入123456 hello 则空格代表结束输入,顾之后的hello也会被忽略,输出结果相同
    fmt.Println(a1,b1) //123 456
    
    //字符串
    g:="haha"
    j:="haha1"
    fmt.Println(g+j) //hahahaha1
    //判断是否相等
    fmt.Println(g==j)//false

数据类型

类型 名称 长度 零值 说明
bool 布尔类型 1 false 其值不为真即为假,不可以用数字代表true或false
byte 字节型 1 0 uint8别名
rune 字符类型 4 0 专用于存储unicode编码,等价于uint32
int, uint 整型 4或8 0 有符号32位或无符号64位
int8 整型 1 0 -128 ~ 127,
uint8 整型 1 0 0 ~ 255
int16 整型 2 0 -32768 ~ 32767,
uint16 整型 2 0 0 ~ 65535
int32 整型 4 0 -2147483648 到 2147483647
uint32 整型 4 0 0 到 4294967295(42亿)
int64 整型 8 0 0 到 18446744073709551615(1844京)
uint64 整型 8 0 -9223372036854775808到 9223372036854775807
float32 浮点型 4 0.0 小数位精确到7位
float64 浮点型 8 0.0 小数位精确到15位
complex64 复数类型 8
complex128 复数类型 16 64 位实数和虚数
uintptr 整型 4或8 ⾜以存储指针的uint32或uint64整数
string 字符串 "" utf-8字符串

格式化列表

格式 含义
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以'\0'结尾,这个'\0'即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示

运算符

算术运算符

运算符 术语 示例 结果
+ 10 + 5 15
- 10 - 5 5
* 10 * 5 50
/ 10 / 5 2
% 取模(取余) 10 % 3 1
++ 后自增,没有前自增 a=0; a++ a=1
-- 后自减,没有前自减 a=2; a-- a=1

赋值运算符

运算符 说明 示例
= 普通赋值 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

关系运算符

运算符 术语 示例 结果
== 相等于 4 == 3 false
!= 不等于 4 != 3 true
< 小于 4 < 3 false
> 大于 4 > 3 true
<= 小于等于 4 <= 3 false
>= 大于等于 4 >= 1 true

逻辑运算符

运算符 术语 示例 结果
! !a 如果a为假,则!a为真;如果a为真,则!a为假。
&& a && b 如果a和b都为真,则结果为真,否则为假。
` ` `a b` 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

其他运算符

运算符 术语 示例 说明
& 取地址运算符 &a 变量a的地址
* 取值运算符 *a 指针变量a所指向内存的值

位运算

运算符 术语 示例 结果
&(AND) 位与 3&9 结果:1 相同位都是1时结果才是1
` (OR)` 位或 ` 3 9` 结果:11 相同位只要有一个是1,结果就是1
^(XOR) 位异或 3^9 结果:10 相同位不同则为1,相同则为0
&^(AND NOT) 位清空 3&^9 结果: 2 后数为0,则用前数对应位代替,后数为1则取0
<< 左移 10<<2 40
>> 右移 10>>2 2

运算符优先级

优先级 运算符
7 ^ ! .
6 * / % &lt;&lt; &gt;&gt; & &^
5 **+ - ^**
4 == != &lt; &lt;= &gt;= &gt;
3 &lt;-
2 &&
1 ** **

代码示例

package main

import "fmt"

func main1() {
    str := "黑河哈"
    //go语言中一个汉字三个字符,其他语言一般是两个
    fmt.Println(len(str)) //9

    //go语言中无法直接定义二进制数据(例如java中可以0b代表二进制)
    a := 0123
    a11 := 0x123
    fmt.Println(a, a11) //83  291

    /**
    变量:存储在栈区,系统为每个应用分配1M空间来存储变量
    常量:存储在数据区内部的常量区
    */
    const b int = 10

    //iota枚举
    const (
        a1 = iota //0
        b1 = iota //1
        c1        //2 其实iota只写第一个就行
    )
    fmt.Println(a1, b1, c1)

    const (
        d = 10
        //写在同一行代表值相同
        e, f = iota, iota
        //而且此时如果想省略iota则必须下面也要是两个枚举量
        g, j
    )
    fmt.Println(d, e, f, g, j) //10 1 1 2 2

}

func main2() {
    var a int = 10
    b := 20
    //整形相除得到整型数据
    fmt.Println(a / b) //0
    //整型除以0会报错
    //浮点型除以0会得到+INF,其实也是无意义,代表正无穷
    fmt.Println(b % a) //0 取模运算符(得到余数)
    fmt.Println(a % b) //10 取模运算符(得到余数)

    /*在go中,没有前++/--,
     而且不能在放在表达式中,只能单独使用
    之所以要这样规定是为了解决类似其他语言的二义性的问题(即不同系统,结果不同)
    例如::a++*a----a这样的计算
    */
    //a-- 正确
    //a=a--错误

    //类型转换
    c := 10
    d := 3.14
    //不同类型不能直接计算,需要类型转换
    fmt.Println(float64(c) * d) //31.400000000000002

    /**
    错误实例:
        var e int32=10
        var f int64=20
        c:=a+b
    即使都是int类型,但是int32和int64也不能直接计算
    必须进行转换
    */
}

//其他运算符
func main3() {
    a := 10
    p := &a
    fmt.Println(p)   //0xc000054080
    fmt.Println(*p)  //10
    fmt.Println(*&a) //10
}

//分支语句:case不能使用数组,切片,集合,函数等,因为本质上是哈希比较必须是值类型(因为哈希比较,所以相比if性能更高)
func main4() {
    a := 30
    if a > 30 {
        println("haha")
    } else if a == 30 {
        println("hehe")
    } else {
        println("heihei")
    }

    //switch中匹配值不能使用浮点型,因为浮点型都是约等于
    switch a {
    case 1:
        println("周一")
    case 2:
        println("周二")
    default:
        println("无匹配")
    }

    //可以通过这种方式判断单一区间
    switch a>20 {
    case true:
        println("周一")
    case false:
        println("周二")
    }


    switch a {
    case 30:
        println("进入")
        //如果匹配到该条目则走完30的匹配,还会进入最近的下一级匹配
        //即此时10是否匹配已经不重要,有fallthrough默认会进入10
        fallthrough
    case 10:
        println("周一")
    case 12:
        println("周二")
    default:
        println("无匹配")
    }
}

//if判断中使用类似python中海象运算符的形式
func main() {
    if num := 10; num%2 == 0 {
        fmt.Println(num)
    }
}

循环

//循环,golang中不存在三目运算符以及while循环
func main1() {
    for i := 1; i <= 10; i++ {
        fmt.Println(i)
    }
    //等效于上面
    i:=1
    for ; ;  {
        //如果没有内部逻辑,则该for是一个死循环
        if i==10 {
            break
        }
        i++
    }
    //如果想在外部使用i则i必须定义在外面
    i1:=1
    for ; i1 < 10; i1++ {
        if i1==5 {
            //break是跳出全部循环,在嵌套循环中只是跳出本层循环,外部循环不受影响
            break
            //continue是跳出本次循环,下次循环继续
        }
    }
    fmt.Println("哈哈",i1) //哈哈 5
}

//还有for range形式:一般针对dict,字符串等
func main() {
    name := "hello哈哈哈"
    for _, v := range name {
        fmt.Printf("%c\n", v)
    }
}

goto

func main() {
    var a int = 10
LOOP:
    for a < 20 {
        if a == 15 {
            a = a + 1
            goto LOOP
            //continue
        }
        fmt.Println(a) //打印10-19但是跳过15
        a++
    }
}
  • 应用场景:错误处理
err:=firstCheckError()
if err!=nil{
    goto onExit
}
err:=secondCheckError()
if err!=nil{
    goto onExit
}
onExit:
    fmt.Println(err)
    exitProcess

//如果不用goto则onExit下面代码有多少个err!=nil判断就要写多少次

不定参数

//函数
func main() {
    sum(1,2)
    sum1(1,2,3)
    sum2(1,2,3,4)
}

func sum(a int,b int)  {
    println(a+b)
}
//不定参数一
func sum1(args ...int)  {
    total:=0
    for i:=0;i<len(args) ;i++  {
        total+=args[i]
    }
    println(total)
}
//不定参数二
func sum2(args ...int)  {
    total:=0
    //i是索引,但是一般用不上,可以用_代替
    //for i,data:=range args {
    for _,data:=range args {
        total+=data
    }
    println(total)
}

func main1() {
    test1(1,2,3,4,5,6)
}
func test1(args ...int)  {
    for i:=0;i<len(args) ;i++  {
        fmt.Println(i,args[i])
    }
    println("***************")
    test2(args[1:3]...)//还可以[1:]代表从索引1开始一直到末尾的数据
}
func test2(args ...int)  {
    println("-------------------------")
    println(len(args)) //2  其实传递过来就是包含索引1不包含索引3的两个数据
    //for i:=0;i<len(args) ;i++  {
    //  fmt.Println(i,args[i])
    //}
}

函数返回值

func main() {
    fmt.Println(lens("dasfsa")) //6
    a,_:=lens2("afafsaaaa")
    fmt.Println(a)//9
}
//函数返回值
func lens(str string) int {
    return len(str)
}

//等效于上面
func lens1(str string) ( sum int) {
     sum=len(str)
     return
}

//多个返回值
func lens2(str string) ( sum int,msg string) {
    sum,msg=len(str),str
    return
}

函数类型

//type可以给函数起别名,记住有这个用法即可
type FUNCTYPE  func(int,int)int

func test(a int ,b int)(sum int)  {
    sum=a+b
    return
}
func main1() {
    var f FUNCTYPE=test
    t:=f(1,3)
    fmt.Println(t) //4
}

func main() {
    //var f func(int,int)int
    //f=test
    //f(1,2)

    //下面是自动类型推导,等效于上面
    f:=test
    f(1,2)
}

//类型转换
//数据类型(变量) //将变量转成指定的类型
//数据类型(表达式) //将表达式转成指定的类型

函数作用域

/**
函数存储在代码区,但是内部变量等还是存储在栈区的
局部变量:存储在栈区,定义在函数内部,有顺序限制,必须定义位置在上面,使用在下面
全局变量:存储在数据区,定义在函数外部,而且无顺序限制,编译之后都在最上面
 */
func main() {
    fmt.Println(a)//50,如果不初始化,则值为0
    //同一作用域变量名唯一,作用域可以简单的理解为一对{}
    a:=10
    //匿名内部函数
    {
        a++
        fmt.Println(a)//11
        a:=20
        fmt.Println(a)//20
    }

    fmt.Println(a)//11
}
var a int=50 //全局变量

匿名函数和闭包

//匿名函数
func main1() {
    //匿名内部函数一
    func(a int, b int) {
        fmt.Println(a + b) //3
    }(1, 2)
    //匿名内部函数二
    f := func(a int, b int) {
        fmt.Println(a + b) //3
    }
    f(1, 2)
    //匿名函数获取返回值
    v := func(a int, b int) int {
        return a + b
    }(3, 4)
    fmt.Println(v) //7
}

//闭包
func main() {
    r:=test(1)(2)
    fmt.Println(r) //3
    fmt.Println(test1(5))//10
}
func test(a int) func(int) int {
    return func(b int) int {
        return a+b
    }
}
//此方式意思不大,只是说明有该用法
func test1(a int)  int {
    return func(b int) int {
        return a+b
    }(a)
}

defer延迟执行

//延迟调用defer
func main() {
    //如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
    /*defer fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")*/
    //输出顺序  c  b  a


    //defer和匿名函数
    a:=10
    b:=20
    defer func(a int,b int) {
        fmt.Println(a)
        fmt.Println(b)
    }(a,b)
    a=100
    b=200
    fmt.Println("结果",a)
    fmt.Println("结果",b)
    /**
    输出结果:
    结果 100
    结果 200
    10
    20

    总结:虽然defer函数是延迟执行,但是参数已经传递进去了
     */
}

数组

var array [5] int // 全部为 0
var array [5] *int // 指针类型
var array := [5] int {1,2,3,4,5} // 初始化
var array := [5] {1:1, 4:5} // 初始化 1, 5
var array := [...] {1,2,3,4,5} // ⻓度根据初始化确定
var array [5][2] int ⼆维数组
数组特点
⻓度固定,不能修改
赋值和函数传递过程是值复制,`涉及到内存 copy,性能低下`

数组定义和随机数

import (
    "fmt"
    "math/rand"
    "time"
)

/**
数组长度在定义后不能改变
* 数组是一个常量不允许赋值
 */
func main() {
    //创建不定长度的数组
    //arr:=[...]int{1,2,3}

    //创建固定长度的数组
    //var arr [10]int=[10]int{1,2,3,4,5,6,7,8,9,10}
     arr:=[10]int{1,2,3,4,5,6,7,8,9,10}
    arr[0] = 10
    fmt.Println(arr) //[10 2 3 4 5 6 7 8 9 10]

    //遍历数组方式一
    //for i:=0;i<len(arr) ;i++  {
    //  println(arr[i])
    //}

    //遍历数组方式二
    for _,v:=range arr {
        fmt.Println(v)
    }

    //数组名代表数组的地址,同时也是数组第一个元素的首地址
    //下面两个打印出的地址值永远相同
    fmt.Printf("%p\n",&arr)
    fmt.Printf("%p\n",&arr[0])

    //指定数组下标进行初始化(此时下标7初始化为10)
    arr1:=[10]int{1,2,3,7:10}
    fmt.Println(arr1) //[1 2 3 0 0 0 0 10 0 0]


    //数组作为函数参数和返回值
    //数组作为函数参数,是值传递,也就是说原数组不会受影响
    test(arr1)

    //随机数
    start:=time.Now().Nanosecond() //纳秒

    //1.创建随机数种子
    //如果不加随机数种子,则随机数生成可能重复
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < 100; i++ {
        //123以内的数据[0,122]的数据
        fmt.Println(rand.Intn(123))
    }
    end:=time.Now().Nanosecond()
    fmt.Println(end-start)
}

func test(arr [10]int)[10]int  {
    return arr
}

二维数组

func main() {
    var arr[3][4]int=[3][4]int{{1,2}}
    fmt.Println(arr) //[[1 2 0 0] [0 0 0 0] [0 0 0 0]]
    arr[2][3]=1
    arr[1]=[4]int{1,2,3,4}
    fmt.Println(arr) //[[1 2 0 0] [1 2 3 4] [0 0 0 1]]
}

切片(动态数组,底层是数组实现的)

slice声明⽅式
slice数据结构
数据类型(变量) //将变量转成指定的类型
数据类型(表达式) //将表达式转成指定的类型
slice := make([]int, 5)
slice2 := make([]int,0,5)
type slice struct {
array unsafe.Pointer
len int //当前存储⻓度
cap int //可⽤⻓度
}

  • 初始化
func main() {
    //切片:slice
    course := []string{"Course"}
    fmt.Printf("%T\n", course) //[]string

    //切片另一种初始化方法
    course1 := make([]string, 5)
    fmt.Println(course1)

    //数组变切片
    // var c1 = [5]string{"a", "b", "c", "d", "e"}
    // s1 := c1[1:4]
    // s1 := c1[:4] //从0取
    // fmt.Println(s1)        //[b c d]
    // fmt.Printf("%T\n", s1) //[]string

    //第四种方式:new
    // s2 := *new([]int)
    // s2 := new([]int)//也可以去掉,go会自动转换的
    // fmt.Println(s2) //[]

    //切片是引用传递,作为参数,内部修改会引发外部修改
}
//切片:可以看作集合
func main1() {
    //切片定义方式一
    var s[] int
    fmt.Println(s)//[]

    //切片定义方式二
    //自动类型推导创建切片
    s1:=make([]int ,5)
    s1[1]=123
    fmt.Println(s1) //[0 123 0 0 0]
    //添加数据信息
    s1=append(s1,456)
    fmt.Println(s1) //[0 123 0 0 0 456]
    //遍历一
    for i := 0; i < len(s1); i++ {
        //fmt.Println(s1[i])
    }
    //遍历二
    for _,value:=range s1{
        fmt.Println(value)
    }
}
//切片扩容
func main2() {
    //cap(s):求出切片整体容量大小
    //len(s):求出切片实际使用长度
    s:=make([]int,5)
    s=append(s,1,2,3)
    fmt.Println(len(s)) //8
    //如果整体数据没有超过1024字节,容量扩展每次为初始化的倍数 5->10->20->40->80
    //如果整体数据超过1024字节,则整体每次扩展上次的1/4
    fmt.Println(cap(s))//10
}
//切片的截取
func main3() {
    s:=[]int{1,2,3,4,5}
    slice:=s[1:3] //包含起始位置不包含结束位置
    //slice:=s[1:] //索引1到最后
    //slice:=s[:3] //起始到索引3但是不包含索引3的数据
    //slice:=s[:] //所有元素
    fmt.Println(slice) //[2 3]
}
//切片的地址
func main4() {
    s:=[]int{1,2,3,4,5}
    slice:=s[1:3]
    slice2:=s[2:3]
    slice[1]=100
    slice1:=s[0:3]
    fmt.Printf("%p\n",s) //0xc00000c300
    fmt.Printf("%p\n",slice)//0xc00000c308
    fmt.Printf("%p\n",slice1)//0xc00000c300
    fmt.Printf("%p\n",slice2)//0xc00000c310
    fmt.Println(s) //[1 2 100 4 5]
    /**
    结论:
    1.首先,切片操作的也是原始数据
    2. 切片和数组一样,数组变量就是数组内存首地址(也是数组第一个元素的地址)
    64位系统中,元素索引地址是一个byte(8bit)
    所以slice其实地址就是s的第二个元素地址,所以增加了8
    而slice1地址因为从0开始所以和s一致
    slice2地址再加8,因为是16进制数据,满16进一位所以结果是0xc00000c310
     */
}

//切片追加和拷贝
func main() {
    var s[]int
    s=append(s,1,2,3)
    fmt.Println(len(s)) //3
    fmt.Println(cap(s))//4
    /**
    结论:
    这种初始化方式,cap永远是2的倍数 2-》4-》6->8这种形式增加

    注意:切片不断操作中,例如追加,其地址值是可能发现变化的
    因为追加等操作的时候,可能最近的剩余空间不足,需要重新寻找一块连续内存存储数据,所以地址可能变化
     */

    //容量超出,则0占位,容量不足,则截取:比如s1只有1长度,则即使截取多个,也只一个数据
    s1:=make([]int,len(s))
    //将s拷贝到s1中,s1要有足够的容量
    //使用拷贝后,s和s1是两个独立空间,互不影响
    i := copy(s1, s[0:2])
    fmt.Println(i) //2 ,拷贝的数据长度
    fmt.Println(s1)//[1 2 0]
    
    //如果想合并两个切片:后面s1必须加...,这是函数的参数传递决定的
    s2=append(s2,s1...)
}
  • 切片的删除
func main() {
    a1 := [5]string{"a", "b", "c", "d", "e"}
    c1 := a1[:]
    //通过这种间接的方式可以实现删除某个元素
    c1 = append(c1[:1], c1[2:]...)
    fmt.Println(c1) //[a c d e]
}
  • 元素是否存在切片中
func main() {
    c1 := []string{"a", "b", "c", "d", "e"}
    //判断某个元素是否在切片中:使用循环
}

切片做函数参数和返回值

//案例一
func main0() {
    s:=[]int{1,2,3}
    //切片是地址传递(引用传递)
    test(s)
    fmt.Println(s) //[1 2 100]
}
func test(s [] int){
    s[2]=100
}

//案例二
func main1() {
    s:=[]int{1,2,3}
    test1(s)
    //可知s并没有变化,是因为,s传递之后,append可能导致地址变化(内存连续存储不足的时候)
    //所以下面的s1才是[1 2 100 1 2 3 4],但是s1再test1完毕之后就被释放了,而且内部指向的数据也会被释放
    //而s还是指向之前地址,所以数据不变,解决方案:返回值,案例三
    fmt.Println(s) //[1 2 100]
}

func test1(s [] int){
    s1:=append(s,1,2,3,4)
    fmt.Printf("%p\n",s1)
}

//案例三
//再操作切片的时候,如果涉及append等操作,务必有返回值
//如果只是修改某个索引的值,则不需要返回值
func main() {
    s:=[]int{1,2,3}
    s=test2(s)
    fmt.Println(s) //[1 2 3 1 2 3 4]
}

func test2(s [] int)[] int{
    s1:=append(s,1,2,3,4)
    return s1
}

slice的原理

  • 底层是数组,如果是基于数组形式产生的,会有一个问题就是操作会影响原来的数组
  • 切片的扩容机制,如果涉及扩容,则会重新申请内存地址,则两个依赖于同一个数组产生的切片,指针就很有可能不是指向同一处,所以一旦触发扩容,之后的操作是互不影响
  • 切片的传递是引用传递
//通过问题反思
func main() {
    //1. 第一个现象
    //不会主动扩展a的空间,所以还是0
    a := make([]int, 0)
    b := []int{1, 2, 3, 4}
    fmt.Println(copy(a, b)) //0
    fmt.Println(a)          //[]
    //2. 第二个现象
    //因为底层指向同一块内存,数组
    c := b[:]
    c[0] = 8
    fmt.Println(b) //[8 2 3 4]
    fmt.Println(c) //[8 2 3 4]
    //3. 第三个现象
    //append会引发扩容
    c = append(c, 9) //append函数没有影响到原来的数组
    fmt.Println(b)   //[8 2 3 4]
    fmt.Println(c)   //[8 2 3 4 9]
    c[0] = 9
    fmt.Println(b) //[8 2 3 4]
    fmt.Println(c) //[9 2 3 4] 9],为什么append函数之后再调用c[0]=8不会影响原来的数组
    //4. 第四个现象
    fmt.Println(len(c)) //5
    fmt.Println(cap(c)) //8
}
func main() {
    //1. 使用make方法初始化len和cap是相同的,不会预留多余的空间
    d := make([]int, 5)
    // d := make([]int, 5,6)  //可以通过指定第三个参数,指定cap(容量),cap需要>len
    fmt.Printf("len=%d,cap=%d\n", len(d), cap(d)) //len=5,cap=5

    //2. 通过数组取切片
    data := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    slice := data[2:4]
    //如果还有一个切片是通过该数组生成的,那么也会指向这个数组,某一个slice操作修改数据会影响另外一个slice,就是因为是同一个数组
    fmt.Printf("len=%d,cap=%d\n", len(slice), cap(slice)) //len=2,cap=8

    //3.正常初始化
    slice2 := []int{1, 2, 3}
    fmt.Printf("len=%d,cap=%d\n", len(slice2), cap(slice2)) //len=3,cap=3

    //append会引发扩容
    /**
    Go中切片扩容的策略:
    首先判断,如果新申请的容量(cap)大于2倍的旧容量,最终容量(newcap)就是新申请的容量(cap)
    否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(oldcap)的两倍,即(newcap=doublecap)
    否则判断,如果旧切片的长度大于等于1024,则最终容量(newcap)从旧容量(oldcap)开始循环增加原来的1/4,
    即(newcap=oldcap,for{newcap+=newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap>=cap)
    如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)

    **/
    //简单说:如果小于1024,扩容每次两倍,大于等于1024,扩容速度就是1.25倍
}

实际上下图就是说slice从第三个数据开始取两个然后作为len的存储,但是呢,为了go为了避免重新分配内存,刚刚好此处有数组,那么后面的浅绿色的数组也归为slice监控,也就是成了cap容量

Map

底层基于 Hash 实现,基于 Key-Value,⽆序的数据集合
dict := make(map[T_KEY]T_VALUE) // key 的类型需要具备 == 和 != 操作
函数类型、字典类型和切⽚类型不能作为 key,不⽀持的操作类型会导致 panic
检测值是否存在

m := make(map[string]bool)
_,h := m["hello"]
if !h {
 }
if _,h := m["hello"];h {
 }


var m map[string]int // nil 类型,添加和修改会导致 panic
nil: len/map[key]/delete(m, key) // 可以正常⼯作
map 默认并发不安全,多个 goroutine 写同⼀个 map,引发竞态错误, go run –race 或者
go build - race;使用这两个指令运行或者编译代码则可以指出存在竞态的代码,但是一般只在开发环境使用
map 对象即使删除了全部的 key,但不会缩容空间
//map  键值对
func main() {
    /**
    注意:
    键的类型必须支持==和!=操作符的类型,切片,函数以及包含切片的结构类型不能作为字典的键
     */
    //1是容量,也可以不填写,map的容量是自动扩容的,而且map的长度和容量是相等的,准备说map根本没有cap属性,只有len
    //map中的数据是无序存储的
    //创建方式一
    m:=make(map[string]string,1)
    m["aa"]="第一个"
    m["aabb"]="第二个" //如果出现相同的键,则后面会覆盖前者
    fmt.Println(m) //map[aa:第一个 aabb:第二个]

    //遍历
    for k,v:=range m{
        fmt.Println(k,v)//aa 第一个    aabb 第二个
    }

    //创建方式二
    m1:=map[string]string{"hehe":"测试一","hehe1":"测试二"}
    fmt.Println(m1) //map[hehe:测试一 hehe1:测试二]

    //判断:如果赋值的m1["hehe"]有对应的value则赋值给v,而且ok为true,否则ok为false
    //但是一般都是for循环使用,这种判断一般不需要
    v,ok:=m1["hehe"]
    if ok {
        fmt.Println(v)
    }else{
        fmt.Println("key不存在")
    }

    //删除(删除没有的键的时候,也不会报错)
    //delete(m1,"hehe")
    //fmt.Println(m1)//map[hehe1:测试二]


    //map和函数
    //map作为函数参数是引用传递,而且map和切片不同,即使增加删除等操作也会修改原始值,所以一般不需要利用返回值
    test(m1)
    fmt.Println(m1)//map[hehe:test hehe1:测试二 xinxin:test111]
}

func test(m map[string]string)  {
    m["hehe"]="test"
    m["xinxin"]="test111"
}

结构体

//结构体定义:一般在函数外部,作用域是全局的
type Student struct {
    id   int
    name string
    sex  byte
}

func main1() {
    var s Student
    s.id = 100
    s.name = "呵呵呵"
    s.sex = 1
    fmt.Println(s) //{100 呵呵呵 1}

    var s1 Student = Student{101, "hei", 0}
    fmt.Println(s1.id) //101

    //这样可以不按照顺序初始化
    s2 := Student{name: "刘备", id: 1, sex: 0}
    fmt.Println(s2) //{1 刘备 0}
}
func main2() {
    //从这里可知道结构体:s赋值给s1后。s1修改属性,不会影响s
    s := Student{name: "刘备", id: 1, sex: 0}
    s1 := s              //赋值其实是完全复制相同的数据到其他位置
    fmt.Println(s1 == s) //true
    s1.sex = 1
    fmt.Println(s)  //{1 刘备 0}
    fmt.Println(s1) //{1 刘备 1}

    //结构体比较:使用==/!=可以对结构体成员数据进行比较操作
    fmt.Println(s1 == s) //false
}

//结构体切片和数组
func main3() {
    var arr [5]Student
    arr[0].id = 100
    arr[0].sex = 1
    arr[0].name = "test"
    fmt.Println(arr) //[{100 test 1} {0  0} {0  0} {0  0} {0  0}]

    slice := [] Student{{100, "eh", 1}}
    fmt.Println(slice)                            //[{100 eh 1}]
    slice = append(slice, Student{2, "ehfiw", 2}) //也可以添加多条
    fmt.Println(slice)                            //[{100 eh 1} {2 ehfiw 2}]

    //遍历,for  或者range
    for i, v := range slice {
        fmt.Println(i, v) //0 {100 eh 1}   1 {2 ehfiw 2}
    }
}

//结构体作为map的value
func main4() {
    s := make(map[int]Student)
    s[1] = Student{1, "呵呵", 0}
    s[2] = Student{2, "呵呵1", 2}
    fmt.Println(s) //map[1:{1 呵呵 0} 2:{2 呵呵1 2}]

    //遍历
    for k, v := range s {
        fmt.Println(k, v)
    }

    //扩展:在map中使用的结构体切片
    //m:=make(map[int][]Student)
}

//结构体作为函数参数和返回值
func main() {
    s1 := Student{101, "hei", 0}
    //结构体作为参数是值传递,函数内部修改不影响实参的值,所以需要借助返回值
    test(s1)
    fmt.Println(s1) //{101 hei 0}
    s1 = test(s1)
    fmt.Println(s1) //{101 二分 0}
}
func test(s Student) Student {
    s.name = "二分"
    return s
}

指针

只要将数据存储在内存中都会为其分配内存地址。内存地址使⽤⼗六进数据表示
内存为每⼀个字节分配⼀个32位或64位的编号(与32位或者64位处理器相关)。
即指针在32位系统和64位系统分别占用4和8个字节,因为系统的虚拟内存寻址范围如此
可以使⽤运算符 & (取地址运算符)来获取数据的内存地址


func main1() {
    a := 10
    //创建指针方式一
    var p *int
    p = &a
    fmt.Println(p) //0xc000056080
    //* 取值运算符 & 取地址运算符
    fmt.Println(*p) //10
    a = 12
    fmt.Println(*p) //12

    //创建指针方式二
    p1 := &a
    fmt.Printf("%T", p1) //*int

    /**
    空指针:
    如下:只是定义未声明,则p的默认值就是nil(空指针,值为0)指向了内存地址编号为0的空间

    说明:0-255对应的内存地址为系统占用,不允许用户进行读写操作


    var p2 * int //空指针(只定义没赋值)
    p2=0x214121 //野指针 指针变量指向一个未知的空间
    //访问野指针和空指针对应的内存空间都会报错
    *p2=1000
    */
}

//创建指针空间
func main2() {
    var p *int
    fmt.Println(p) //<nil>  内存地址编号为0,只是显示为nil
    //go语言中只需要开辟空间(new(数据类型))不需要管理空间的释放
    p = new(int)
    fmt.Println(p)  //0xc000056088
    fmt.Println(*p) //0
}

//指针作为函数的参数
func main3() {
    a := 10
    b := 20
    //不能交换,因为是值传递
    swap(a, b)
    fmt.Println(a, b) //10 20
    //指针是地址传递
    swap1(&a, &b)
    fmt.Println(a, b) //20 10
}

func swap(a int, b int) {
    a, b = b, a
}

func swap1(a *int, b *int) {
    *a, *b = *b, *a
}

//数组指针
func main4() {
    arr := [5]int{1, 2, 3, 4}
    fmt.Println(arr)         //[1 2 3 4 0]
    fmt.Printf("%p\n", &arr) //0xc00006c060
    p := &arr
    fmt.Printf("%T\n", p) //*[5]int  数组指针

    //定义数组指针
    var p1 *[5]int
    p1 = &arr //数组指针接收的数组,必须数组长度和数组指针定义时候长度相同
    //上面两句与此句等效:p:&arr

    fmt.Println(p1) //&[1 2 3 4 0]

    //通过指针间接操作数组
    fmt.Println((*p1)[3]) //4  因为运算符优先级的问题必须(*p1)
    fmt.Println(p1[3])    //4  go语言中,指针做了优化,该句等效于上面一句

    //len(指针变量):代表的是元素个数
    fmt.Println(len(p1)) //5
    for i := 0; i < len(p1); i++ {
        //fmt.Println(p1[i])
    }
}

//切片指针
func main5() {
    arr := []int{1, 2, 3, 4}
    //此时的p其实是一个二级指针(因为切片的名称其实就是栈内存存储的切片真实数据的地址)而&又是取了该变量在栈内存的地址
    p := &arr
    //fmt.Println(p) //&[1 2 3 4]
    fmt.Printf("%p\n", arr) //0xc000012360
    fmt.Printf("%p\n", p)   //0xc000004480
    fmt.Printf("%p\n", *p)  //0xc000012360

    fmt.Println((*p)[1]) //2
    (*p)[1] = 200
    //p[1]=300 数组指针中,可以这么操作,但是切片指针中不行
    *p = append(*p, 1, 2, 3, 4)
    fmt.Println(arr) //[1 200 3 4 1 2 3 4]
}

//切片指针作为函数参数
func main6() {
    s := []int{1, 2, 3, 4}
    fmt.Printf("%p\n", s) //0xc000054120
    test(&s)              //如果直接传递s是不会修改s的,通过指针可以
    fmt.Println(s)        //[1 2 3 4 1 2 3]
    fmt.Printf("%p\n", s) //0xc000070100
}
func test(s *[]int) {
    *s = append(*s, 1, 2, 3)
}

//通过new创建切片指针空间
func main7() {
    var p *[]int
    p = new([]int)
    fmt.Println(*p) //[]
    *p = append(*p, 1, 2, 3)
    for i, v := range *p {
        fmt.Println(i, v)
    }
}

//指针数组,指针切片
func main8() {
    //指针数组:存储的是指针,多个指针数据形成数组
    var arr [3]*int
    a := 0
    b := 1
    arr[0] = &a
    arr[1] = &b
    fmt.Println(arr) //[0xc000056080 0xc000056088 <nil>]
    //通过指针数组改变变量的值
    *arr[0] = 10
    fmt.Println(*arr[0]) //10
    fmt.Println(a)       //10
    //遍历也是len  for不写代码了

    //指针切片
    var slice []*int
    slice=append(slice,&a,&b)
    fmt.Println(slice) //[0xc000056080 0xc000056088]
    //其他:指针切片和指针数组类似
}

type Student struct {
    id int
    name string
}

//结构体指针
func main9()  {
    var stu Student=Student{1,"hehe"}
    //定义结构体指针指向变量的地址
    //var p *Student
    p:=&stu
    fmt.Printf("%T\n",p) //*main.Student
    //通过结构体指针,间接操作结构体成员
    (*p).name="haha"
    //p.name="haha" 该句等效于上面一句
    fmt.Println(*p) //{1 haha}
    fmt.Println(stu) //{1 haha}

}

//结构体切片
func main10() {
    var stu []Student
    stu=append(stu,Student{1,"hehe1"})
    stu=append(stu,Student{2,"hehe2"})
    fmt.Println(stu) //[{1 hehe1} {2 hehe2}]


    var stus[]Student=make([]Student,3)
    p:=&stus //结构体切片指针
    fmt.Printf("%T\n",p) //*[]main.Student

    *p=append(*p,Student{3,"peiqi"})
    //注意此处长度是4,make创建默认三个{0  }
    fmt.Println(stus) //[{0 } {0 } {0 } {3 peiqi}]
    (*p)[0]=Student{0,"heh"}
    fmt.Println(stus) //[{0 heh} {0 } {0 } {3 peiqi}]
}

//多级指针:二级指针存储一级指针地址  三级指针存储二级指针地址
func main() {
    a:=10
    p:=&a
    p1:=&p
    fmt.Printf("%p\n",a) //%!p(int=10)
    fmt.Printf("%p\n",p) //0xc000056080
    fmt.Printf("%p\n",p1)//0xc000082018
}

Go内存模型图

总结

//01.go
package main

//如果使用包的初始化或接口实现,并没有明显调用,需要使用_来区分
import (
    "demo"
    "fmt"
    "math"
)

//主文件不论在哪个目录,包都是main

func main() {
    a := 10
    b := 10
    //数值交换
    a, b = b, a
    //%T 代表打印数据类型
    fmt.Printf("%T\n", a) //int
    fmt.Println(b)        //10

    //常量建议大写
    const PI float64 = 3.1415926
    /*
        * 在go语言中常量不能寻址
        fmt.Println(&PI)  错误
    */
    fmt.Println(math.Pi)

    //iota常量集(枚举):第一个iota默认是0,以后依次加1,后面的也可以不写iota
    const (
        a1 = iota
        b1 = iota
        c1 = iota
        d1
        e1
    )
    fmt.Println(a1, b1, c1, d1, e1) //0 1 2 3 4

    //同一行的iota值是相同的
    const (
        a2         = iota
        b2         = iota
        c2, d2, e2 = iota, iota, iota
    )
    fmt.Println(a2, b2, c2, d2, e2) //0 1 2 2 2

    const (
        a3 = 123
        b3 = true
        c3 = "瞅你"
    )
    fmt.Println(a3, b3, c3) //123 true 瞅你
    demo.Test()
}
//test.go
package demo

import "fmt"

/*
想让外部使用,则必须是大写的函数名
*/
func Test() {
    fmt.Println("hello")
}

//02.go
package main

import "fmt"

func main01() {
    
    /*
    *匿名函数:必须定义在函数内部
    输出30
    */

    func (a int, b int)  {
        fmt.Println(a+b);
    }(10,20)

    f:=func  (a int, b int)  {
        fmt.Println(a+b);
    }
    fmt.Printf("%T\n",f) //func(int, int)


    //函数类型
    var f1 func (int,int) 
    f1=func  (a int, b int)  {
        fmt.Println(a+b);
    }
    f1(30,20)//50
}



type FUNCTYPE func(int,bool,string)
func demo(a int,b bool,c string)  {
    
}

//函数回调
func demo2(f FUNCTYPE)  {
    f(1,false,"")
}
func main()  {
    //type
    //1. 为已存在的数据类型起别名
    type i8 int8


    // type i8 =int8   //如果没有等号,不允许计算
    var a i8=123
    fmt.Println(a)

    // var b int8=1
    // fmt.Println(a+b)  此句就是i8和Int8不允许计算,触发用有等号的别名命名方式


    //byte其实就是uint8的别名,所以输出是97而不是字符a
    var ch byte='a'
    fmt.Println(ch) //97 


    //2.为函数定义别名
    var f FUNCTYPE
    f=demo
    f(1,true,"")
}
//03.go
package main

import (
    "fmt"
    // "go/types"
    // "reflect"
)

func main01() {
    //空接口类型,可以接收任意类型的数据,但是空接口之间不允许计算操作
    var a interface{}
    a = 123
    a = 1.34
    a = "hello"
    fmt.Println(a) //hello
    a = 34
    //可以通过反射获取类型以及值,如果想计算,必须通过类型断言进行操作
    // var b interface{}=456
    //获取类型
    // t:=reflect.TypeOf(a)
    //获取值
    // a1:=reflect.ValueOf(a)

    //类型断言:只能使用再接口类型上面
    a1, ok := a.(int)
    if ok {

    } else {

    }
    fmt.Println(a1)        //34
    fmt.Println(ok)        //true
    fmt.Printf("%T\n", ok) //bool
    fmt.Printf("%T\n", a1) //int
}

//结构体,大写代表可以外部使用,同理内部字段小写也是只能内部,大写才能外部使用
type Student struct {
    id    int
    name  string
    sex   string
    score int
    addr  string
}

func main() {
    //结构体初始化
    var stu Student
    stu.id = 1001
    stu.sex = "男"
    stu.addr = "河南"
    stu.name = "哈哈"
    stu.score = 20
    fmt.Println(stu) //{1001 哈哈 男 20 河南}
}
//04.go
package main

import (
    "fmt"
    "time"
)

func main01() {
    //创建一个空的map
    // var m map[int]string  //默认值为nil,地址是0x0,但是这个m不能直接操作放数据需要make
    
    //map中key的类型不能是函数,字典,切片
    //map中数据是无序的
    m1:=make(map[int]string)  //不能直接把m传进来
    m1[1001]="哈哈1"
    m1[10022]="哈哈2"
    m1[1008]="哈哈3"
    m1[1101]="哈哈4"
    fmt.Println(m1) //map[1001:哈哈1 1008:哈哈3 1101:哈哈4 10022:哈哈2]

    //遍历
    for k, v:= range m1 {
        fmt.Println(k,v);
    }
}

func main()  {
    go func ()  {
        fmt.Println("hello")
    }()
    time.Sleep(time.Second*2)



    // switch中case值不允许是浮点型,因为浮点型是相对精准的
    var value int
    switch value {

        //多个条件值可以用逗号分割
    case 1,2,3:
        fallthrough //让当前case向下执行
    case 4:
        //go中case不需要break
    }


    /*
    defer后⾯必须是函数调⽤语句,不能是其他语句,否则编译器会出错。
    defer后⾯的函数在defer语句所在的函数执⾏结束的时候会被调⽤
    */

    //输出是111   11  1  defer先压入栈,取出顺序相反
    defer fmt.Println(1)
    defer fmt.Println(11)
    defer fmt.Println(111)
}
package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello")
    fmt.Println("哈哈")
    var 变量 int = 123
    和 := 变量 + 1
    fmt.Println(变量)
    fmt.Println(和)
}
//cgo
package main

/*
#include "stdio.h"
void Print()
{
    printf("hello,world\n");
}
*/
import "C"
import (
    "fmt"
)

func main() {
    fmt.Println("hello")
    C.Print() //cgo写法
}
package main

import (
    "fmt"
)

func main01() {
    var a float32 = 3.14
    var b float64 = 3.14
    //小数点后保留20位,默认保留6位,会对第七位进行四舍五入
    fmt.Printf("%.20f\n", a)
    fmt.Printf("%.20f\n", b)

    //uncode编码
    var c rune = '帅'
    fmt.Printf("%c\n", c) //帅

    var d byte = 'd'
    fmt.Printf("%c\n", d) //d

    str := "哈哈"
    //go中,一个汉字占三个字节:比较字符串是否相同使用==
    fmt.Println(len(str)) //6

    //使用``可以原样输出,避免转义,从而防止攻击等

    str1 := `哈哈\n`
    fmt.Println(str1) //哈哈\n
}

func testappedn(slice []int) {
    slice = append(slice, 1, 1, 1, 1)
    fmt.Println(1, slice) // [0 0 0 0 0 0 0 0 0 0 1 1 1 1]
}

func main() {
    //数组和切片
    // 数组作为函数参数是值传递,形参不可以修改实参的值,一定要注意,此处和java不同
    //但是可以传递数组参数,或者返回一个值解决问题。但是一般golang中建议使用切片代替数组

    //切片
    slice := make([]int, 10, 30)
    fmt.Println(slice)      //[0 0 0 0 0 0 0 0 0 0]
    fmt.Println(len(slice)) //10
    fmt.Println(cap(slice)) //30
    // len:切片的长度
    // cap:切片的容量,容量可以进行扩容,如果整体数据没有超过1024字节,容量扩展每次为初始化的倍数 5->10->20->40->80
    //如果整体数据超过1024字节,则整体每次扩展上次的1/4

    //切片包含指针,指向数据的位置,所以切片作为形参则可以直接修改实参的数据;但是当涉及append等操作的时候,可能导致扩容地址变化
    //即形参传递过来的地址和实际函数内部操作之后的地址已经不同,所以无变化
    testappedn(slice)
    fmt.Println(2, slice) //[0 0 0 0 0 0 0 0 0 0]
    //可以通过%p 打印切片的地址,可以发现已经不同了

    //注意点: 切片截取是在原有切片基础上,所以即使修改切片截取后的某个数据,原始切片也会变化

    //计算数据类型再内存中占的字节大小:unsafe.Sizeof()

    // switch判断时候,也是通过hash,所以debug会发现是跳着指行判断的(hash计算之后顺序可能不同),所以效率高于if

    //所有指针类型再32位操作系统下占4个字节
    //所有指针类型再64位操作系统下占8个字节
    //因为内存地址是一个无符号的十六进制整型数据,而不同操作系统的最大虚拟内存寻址范围不同,刚好对应上面的4和8

    //内存地址编号0-255为系统占用不允许用户读写操作
    /*
        var p *int //nil
        *p=123
        fmt.Println(p)

        此处直接panic异常,因为nil就是0x0地址,不允许操作
    */

    var p *int
    p = new(int)
    *p = 123
    fmt.Println(p)  //0xc000012200
    fmt.Println(*p) //123

    //go语言中没有三目运算符,以及while
}

反射

变量的内在机制

  • 类型信息,这部分是元信息,是预先定义好的
  • 值类型,这部分是程序运行过程中,动态改变的

反射与空接口

  • 空接口可以存储任何类型的变量
  • 那么给你一个空接口,怎么知道里面存储的是什么东西?
  • 在运行时动态的获取一个变量的类型信息和值信息,就叫反射

API

  • 内置包reflect
  • 获取类型信息:reflect.TypeOf
  • 获取值信息:reflect.ValueOf
  • Type.Kind():获取变量的类型,是reflect.TypeOf返回值的方法

反射对应案例

package main

import (
    "fmt"
    "reflect"
)

func testReflect(i interface{}) {
    t := reflect.TypeOf(i)
    fmt.Printf("%v\n", t) //*int32

    v := reflect.ValueOf(i)

    //Kind:虽然也是获取类型信息,但是更细节化,
    //reflect.TypeOf返回值还可以获取函数等信息,因为可能传递进来的是结构体等
    // fmt.Printf("%v\n", t.Kind()) //*int32
    k := t.Kind()
    switch k {
    case reflect.Int32:
        //通过反射直接设置基本数据类型值会panic,一般都是通过指针
        v.SetInt(40)
        fmt.Println("类型信息是Int32,值是", v.Int())
    case reflect.Float32:
        v.SetFloat(12.3)
        fmt.Println("类型信息是Float32,值是", v.Float())
    case reflect.Ptr:
        //Elem()函数其实就相当于取值运算符
        v.Elem().SetInt(12)
        fmt.Println("类型信息是指针类型,值是", v.Elem().Int())
    }

    fmt.Printf("%v\n", v) //0xc0000120b8

    //和 reflect.TypeOf功能是一样的
    fmt.Println("type", v.Type()) //*int32

}

type User struct {
    Sex  string `key1:"value1" key2:"value2"`
    Name string
    //注意小写的私有属性连反射也获取不到,并且不做特殊处理时候反射获取会报错
    // xxx int
}

func testReflectStruct(a interface{}) {
    v := reflect.ValueOf(a)
    t := reflect.TypeOf(a)
    k := t.Kind()
    switch k {
    case reflect.Struct:
        //获取到底结构体中有多少属性字段,方便遍历
        fmt.Println(v.NumField())
        for i := 0; i < v.NumField(); i++ {
            filed := v.Field(i)
            fmt.Printf("name:%s type:%v value:%v\n",
                t.Field(i).Name, filed.Type().Kind(), filed.Interface())
        }
    }
}

func (u User) Show() {
    fmt.Println(u.Name)
}
func (u *User) SetSex(str string) {
    u.Sex = str
}

func testReflectStructMethod(a interface{}) {
    t := reflect.TypeOf(a)
    k := t.Kind()
    fmt.Println(t.NumMethod())
    switch k {
    case reflect.Struct:
        //获取到底结构体中有多少方法,注意此时只有一个方法,
        //因为SetSex是指针类型不算
        //如果testReflectStructMethod传递&u,则 reflect.TypeOf(a),然后t.NumMethod()为2,但是需case reflect.Ptr:
        for i := 0; i < t.NumMethod(); i++ {
            f := t.Method(i)
            fmt.Printf("%d:method name:%v type:%v\n", i, f.Name, f.Type)
        }
    }

    //调用方法
    v := reflect.ValueOf(a)
    s := v.MethodByName("Show")
    var args []reflect.Value
    //无参也要传
    s.Call(args)

    //如果有参数,则下面例子
    /*
        s := v.MethodByName("Show")
        var args []reflect.Value
        name:="asf"
        newVal:=reflect.ValueOf(name)
        args=append(args,newVal)
        s.Call(args)
    */
}
func main() {
    // var a int32 = 20
    //如果不涉及修改,则针对基本数据类型直接传变量即可,但是如果设计修改,基本数据类型其实是副本传递
    //直接反射修改会报panic,地址查询不到,所以需要传地址
    // testReflect(&a)

    u := User{
        Sex:  "男",
        Name: "zq",
    }
    //修改结构体需要传递指针,结构体是值类型
    v := reflect.ValueOf(&u)
    v.Elem().Field(0).SetString("哈哈哈")
    //可以通过索引,也可以通过name去反射修改
    v.Elem().FieldByName("Name").SetString("新名字")

    // testReflectStruct(u)

    //反射测试结构体方法相关
    testReflectStructMethod(u)

    //获取tag信息
    t := reflect.TypeOf(u)
    f1 := t.Field(0)
    fmt.Println(f1.Tag.Get("key1"), f1.Tag.Get("key2")) //value1 value2
}
举报

相关推荐

0 条评论