变量基本语法
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 |
* / % << >> & &^ |
5 |
**+ - |
^** |
4 |
== != < <= >= > |
3 |
<- |
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
}