Go学习笔记_go语言的content-程序员宅基地

技术标签: GO  go  

标准的项目结构

Golang中创建标准Go项目

--项目名称
		--src
				--包名
						--xx.go
						--....
		--main.go
		--bin 存放小工具
		--pkg  系统编译后生成的内容

开发过程中的配置

Go语言在寻找包的时候会从GOPATH/src路径,如果不存在就从GOROOT/src(GO语言标准源码)中寻找。

  • 在IDER中的配置过程

    • 点击File --> Setting

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zc13UmKB-1580735567448)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111222639691.png)]

    • 进去之后点Languages&Frameworks --> Go – GOPATH – 点击 +把当前的目录加进来就可以了。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3lEGpAx-1580735567449)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111222941635.png)]

      右键项目 -->Run -->go build

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NRsJYjCE-1580735567450)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111223304749.png)]

包的权限与设置

Go语言中唯一一个设置访问权限的就是函数名的大小写

当包中文件里的函数名为小写,说明只能在包内访问,如果为大写就说明可以在任一地方访问到。

操作如下:

demo包中创建一个demo1.godemo2.go的文件夹

  • demo1.go中写一个demo1的函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5WPJXtoa-1580735567450)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111231256199.png)]

  • demo2.go中写一个Demo2的函数并调用同包下的demo1函数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8bGSgREF-1580735567451)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111231410144.png)]

  • 在与scr同级的main.go中调用Demo2函数

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-75EwDjWE-1580735567452)(/home/wbq/文档/学习笔记/gostudyimp/image-20200111231554962.png)]

运行结果如下:

//Demo2被执行了。。
//demo1被执行了。。。

Go语言文件的基本结构

import "fmt"
//函数外部只能放置(变量/标识符/常量/函数)的声明
//语句只能放到函数里边
func main(){
	fmt.Printf("Hello,Go!!")
}

输出语句

fmt.Print()  //   原样输出  会打印ASCII码值
fmt.Printf()	//经常用的
fmt.Println()	//在第一个的基础上仅仅增加了一个换行
fmt.Sprintf()		// 把数据以字符串的形式打印出来
fmt.Fprint()	//把数据写文件过着IO里边
a := fmt.Sprintln("1","3")    //Sprint()  就是将字符拼接起来然后赋值,如果没有变量接收的化,就不会有输出 
fmt.Print(a)
fmt.Printf("%T",a)

输入语句

fmt.Scanf()
fmt.Fscanf()
fmt.Sscanf()
	var name string
	var age int
	//fmt.Scanln(&name,&age)  //输入用空格隔开
	fmt.Scanf("%s\n%d",&name,&age)  //可以自己定义用换行来分隔两个输入
	fmt.Printf("%s\n%d",name,age)

变量的声明

//单个声明
var name string
var age int
var flag bool
//多个声明 在声明全局变量的时候使用
var (
	a string
	b int
	c bool
	d float32
)
//如果直接声明代赋值的化就不必写变量类型了,他会自动识别出来变量类型
var  s2 = "name"

func main(){
    }
        //简短函数声明  只能在函数内部使用
        s3 :="哈哈"
}

注意事项:

1、函数外的每一句语句都需要以关键字开头(var , func, const 等)

2、:= 简短变量声明只能在函数内使用

3、一般情况下 全局变量用 单个声明多个声明这两种方式,局部变量一般用简短函数声明

4、_多用于占位符(可用于匿名变量),表示忽略值

变量的作用域

与其他语言一样分为局部变量,与全局变量(包内、包外)

局部变量一样,主要来说一下全局变量

全局变量是定义在函数外边的如果变量名首字母小写,说明只能在包内进行访问,如果变量首字母大写,可以在整个工程中都可以访问到访问方法包名+变量名 eg:demo.Name

Go语言中的引用数据类型

在Go语言中只有5个是引用数据类型,其他都是值类型

  • slice
  • map
  • channal
  • interface
  • func

引用数据类型作为参数的时候,成为浅拷贝,形参改变,实参也跟着改变,因为传递的是地址,一个改变另一个也会跟着改变

值数据类型作为参数的时候,称为深拷贝,形参改变,实参不改变,因为传递的时候是传递的值,形参会重新了开辟一个地址空间。如果希望,在改变形参的时候,实参也跟着改变,可以把参数设置成指针。

举例:

func main(){
    
 x := 19
 y := "魏宝强"
 str := []int{
    12,12}
 age := 24
 demo(x,y,str,&age)
 fmt.Println(x,y,str,age)
}
func demo(a int , b string, arr []int,content *int){
    
	a =18					//a 是值类型的所以形参改变实参不会发生
	b = "李宾"				//b  也是值类型参数
	arr[0] = 11				//arr是切片 是引用数据类型,传递的是地址,实参会跟着形参改变
	arr[1] = 11
	*content = 23			//content 是一个指针,虽然age是一个值类型的,但是传值的时候传递的是地址。所以也会跟着改变
}

//打印结果
19 魏宝强 [11 11] 23

常量的定义

//单个常量赋值
const pi =3.1415926
//批量声明常量   如果一个常量没有赋值,那么它的值与上一个值保持一致  eg: n3   打印出来是  456
const (
	n1 = 123
	n2 = 456
	n3
)

iota是常量中的一个计数器 只能在常量表达式中使用

iota在const关键字出现的时候会至为0 const 每增加一行iota就会计数一次。依次累加。

const (
	a = iota   //iota = 0
	b		   //iota = 1
	c		   //iota = 2
)

func main(){
    
    fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
}
//运行结果
0
1
2

const t跳跃计数

// 跳跃计数
const(
	a = iota	//iota = 0
 	b = 100		//
	c = iota	// iota = 2
	d
)
//运行结果
0
100
2
3

const中_占位符号

const (
	_ = iota    // iota = 0
	B = 1 << (10*iota)  //iota = 1
	KB = 1 << (10*iota)	//2
	MB = 1 << (10*iota)	//3
	GB = 1 << (10*iota)	//4
)

const 行的概念 无论一行有几个常量(用,隔开) 都是一行

const(
	d1,d2 = iota+1,iota+2  // 这一行的iota = 0   
	d3,d4 = iota+1,iota+2	//这一行的iota = 1
)
//运行结果
1
2
2
3

进制之间的转化

/**
	进制转换
	输出语句中  %d   打印出10进制数
			  %b   打印二进制数
			  %0   打印八进制数
			  %x   打印十六进制数

*/
i1 :=101
	fmt.Printf("10进制::%d\n",i1)
	fmt.Printf("2进制::%b\n",i1)
	fmt.Printf("8进制::%o\n",i1)
	fmt.Printf("16进制::%x\n",i1)
//输出结果
10进制::101
2进制::1100101
8进制::145
16进制::65

字符串

Go语言中字符串用"包裹。 字符用'包裹 只能用这个标准,与其他语言不太一样

Go语言用的是UTF-8编码

s1 := "Hello weibaoqiang"
//单独的字母、数字、符号就一个字符
s2 :='W'
s3 :='魏'
//英文字母一个字母占一个字节(1字节 = 8bit   及8个二进制位) 
//中文汉字在utf-8编码中   占3个字节
//中文汉字在uncode 编码中  占2个字节

反引号 ` 可以把字符串原样输出

	s1 :=`
		落霞与孤鹜齐飞,
		秋水共长天一色。
`
	fmt.Println(s1)

//输出结果
		落霞与孤鹜齐飞,
		秋水共长天一色。

字符串拼接

name :="梦想"
fmt.Println(len(name))
//运行结果
8

拼接的两种方法

name :="梦想"
world :="实现"
//拼接方法1
ss := name+world
//拼接方法2
ss1 := fmt.Sprintf("%s%s\n",name,world)

byte 和 rune类型

byte 类型是ASCII码对应的编码 (英文可以用byte),当遇到非ASCII码的时候Go语言提出了rune类型也就是int32(一个汉字用utf-8编码的时候需要3个字节,每个字节8个bit 所以是24位,有因为需要存储rune的类型,所以又多加了一个字节,所以是32位)

查看string与rune的类型

s2 := "魏宝强"
s3 := []rune(s2)
s3[1] ='张'
fmt.Println(string(s3))
fmt.Printf("s1::%T,s3::%T,s3[1]::%T",s2,s3,s3[1])
//                s1::string,             s3::[]int32,         s3[1]::int32
//可以看出来rune类型就是int32

类型转换

/*
		数据的强制转换 与其他语言一样。
		整形的可以与浮点型的进行转换
		string类型的可以与rune(切片)类型转换
		但是Boolen 类型的不能与其他类型进行强制转换
	*/
	n:=10
	var f float64
	f = float64(n)
	fmt.Printf("f:: %f\n",f)
	fmt.Printf("f的类型:%T",f)
//运行结果
//f:: 10.000000
//f的类型:float64

if and for语句

简单的for

//九九乘法表
for i :=1 ; i<10;i++{
    
		for j:=1 ; j <= i;j++ {
    
			fmt.Printf("%dx%d = %d  ",i,j,i*j)

		}
		fmt.Println()
	}

//结果
1x1 = 1  
2x1 = 2  2x2 = 4  
3x1 = 3  3x2 = 6  3x3 = 9  
4x1 = 4  4x2 = 8  4x3 = 12  4x4 = 16  
5x1 = 5  5x2 = 10  5x3 = 15  5x4 = 20  5x5 = 25  
6x1 = 6  6x2 = 12  6x3 = 18  6x4 = 24  6x5 = 30  6x6 = 36  
7x1 = 7  7x2 = 14  7x3 = 21  7x4 = 28  7x5 = 35  7x6 = 42  7x7 = 49  
8x1 = 8  8x2 = 16  8x3 = 24  8x4 = 32  8x5 = 40  8x6 = 48  8x7 = 56  8x8 = 64  
9x1 = 9  9x2 = 18  9x3 = 27  9x4 = 36  9x5 = 45  9x6 = 54  9x7 = 63  9x8 = 72  9x9 = 81  

if语句与java语句一样但有一个特殊的if语句

一般的:

if 条件1 {
    
东西
}else{
    
东西
}

特殊的:

if score :=65;score > 60{
    				// 在if声明变量  score 的有效范围只有在if语句中。
		fmt.Println("及格",score)
	}
//输出结果
及格 65

for range 语句用于打印数组等

	s := "Hello world!!!"
	for i,v := range s{
    
		fmt.Printf("序号:%d  值:%c\n",i+1,v)
	}
//结果
序号:1  值:H
序号:2  值:e
序号:3  值:l
序号:4  值:l
序号:5  值:o
序号:6  值: 
序号:7  值:w
序号:8  值:o
序号:9  值:r
序号:10  值:l
序号:11  值:d
序号:12  值:!
序号:13  值:!
序号:14  值:!

时间的获取与转换

时间在程序编成中是非常重要的,Go语言的时间需要用到time

/*
		声明时间变量
	*/
	var t time.Time

获取当前时间

t  := time.Now()
//打印结果
2020-01-07 23:15:22.096972957 +0800 CST m=+0.000301742           //这个结果显示的不仅仅有时间还有时区的信息

在正常的时间使用的时候我们需要的时间与打印出来的有一点的区别,下面就来说一下时间与字符串的相互转换

1、时间转化为字符串

t :=time.Now()
s := t.Format("2006-01-02 15:04:05")  //这个时间的数值一定不能变  但是格式可以变成你想要的格式
//打印结果
2020-01-07 23:15:22

s := t.Format("2006年1月02日 15点04分05秒")
//打印结果
2020107232402秒

s := t.Format("2006年01月02日")
//打印结果
20200107

2、字符串转换成时间

s1 := "2022年12月12日 16点14分16秒"
t,_:= time.Parse("2006年01月02日 15点04分05秒",s1)
fmt.Println(t)
//打印结果
2022-12-12 16:14:16 +0000 UTC


s1 := "2022年12月12日"
t,_:= time.Parse("2006年01月02日",s1)
//打印结果
2022-12-12 00:00:00 +0000 UTC

数组

数组的是编程语言中存储数据的一种格式

Go语言中数组的声明与java 有一定的区别

一维数组

1、数组的声明

//平常的数组声明与赋值
var arr [3] int = [3]int{
    1,2,3}

Go语言简单的写法
arr1 := [4]int{
    4,5,6,7}

2、Go语言特定的数组定义方式,不需要定义数组的长度,长度根据后边赋值的数量有关

arr2 :=[...]int {
    4,5,6,9,7,2,3,9}

3、三种方式打印结果

fmt.Println(arr)
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(len(arr),len(arr1),len(arr2))
//打印结果
[1 2 3]
[4 5 6 7]
[4 5 6 9 7 2 3 9]
3 4 8

多维数组

数组的声明与赋值,因为操作与一维数组类似,所以这里不再多说

//一般写法
var arrs [3][3]int = [3][3]int{
    {
    1,2,3},{
    1,2,3},{
    1,2,3}}

//go语言特有的写法
arrs := [3][3]int{
    
    {
    1,2,3},
    {
    1,2,3},
    {
    1,2,3},    //这种写法最后的这一个,一定不能少
}
arrs := [3][3]int{
    
    {
    1,2,3},
    {
    1,2,3},
    {
    1,2,3}}   //这种写法不需要写 ,

切片

切片英文名slice

切片是可变长度的,可以解决数组长度过长造成的内存资源的浪费

切片与数组的声明最主要的区别就是长度

切片声明

//切片的声明
	var slice []string
	//赋值
	slice = []string{
    "魏宝强","张三"}

	//简单声明与赋值
	slice1 := []string{
    "李四","王五"}
	fmt.Println(slice)
	fmt.Println(slice1)

切片与数组最大的区别就是,切片定义之后没有在内存空间开辟内存,还有就是切片就像指针一样,一个已经有数据的切片赋值给另一个切片,两个切片的地址是一样的。

name :=[]string {
    "魏宝强","张三"}
	name1 := name
	fmt.Println(name,name1)
	fmt.Printf("%p,%p",name,name1)//   %p 可以看内存地址
//打印结果
[魏宝强 张三] [魏宝强 张三]
0xc00008c040,0xc00008c040

其他操作与数组一样可以通过索引修改或者查看切片的数据

make函数

Go语言中可以使用make函数创建slice,map,channal,interface 这几种类型

使用make函数定义无内容,但不是nil的切片,意味着切片已经申请了内存空间.

make的使用

make(类型,初始长度,初始容量) (初始容量可以不定义,默认与初始长度相等)

make函数定义一个切片(已经分配内存空间的)

name := make([]int,0)
fmt.Println(name)
fmt.Printf("%p",name)
//打印结果
[]
0x57bb60          //内存不是0x0  说明已经分配了内存地址

查看make函数创建切片的长度以及容量

fmt.Println(len(name),cap(name))

长度会随着切片中数据增多而增加,容量增加的规律是 如果一下(增加+之前的)小于当前长度的2倍 容量变为当前长度的2倍,如果一次(增加+之前的)超过了当前长度的2倍容量与增加后的长度一样。依次类推

name = append(name,1,2)
fmt.Println(len(name),cap(name))
name = append(name,5,6,8,9)
fmt.Println(len(name),cap(name))
name = append(name,5,6)
fmt.Println(len(name),cap(name))
//打印结果
0 0
2 2
6 6
8 12

切片中增加切片

name := make([]int ,0)
name1 := []int{
    1,2,3,6}
name = append(name,name1...)        //切片的叠加需要在叠加的切片后边加上3个   . 
fmt.Println(name)
//打印结果
[1 2 3 6]

切片与数组

数组中取一小段就是切片,切片的特性就是指针

	/*
		数组去一小段就是切片
	*/
	arr := [6]int{
    1,2,3,4,5,6}
	slice :=arr[0:3]   //很显然有切片的特性,指针
	fmt.Println(slice)
	fmt.Println(arr)
	fmt.Printf("%T,%T\n",slice,arr)
	fmt.Printf("%p,%p\n",&arr[0],slice)
//打印结果
[1 2 3]
[1 2 3 4 5 6]
[]int,[6]int													//总数据类型中可以看出切片,与数组
0xc000016240,0xc000016240				//可以看出来数组的首地址就是与切片相同

当向从某数组中且下来的切片添加数据的时候会发现,添加一个覆盖一个数组中的数据,当添加的切片数据大于数组长度的时候,切片会重新开辟一个地址空间。

arr := [6]int{
    1,2,3,4,5,6}
slice :=arr[0:3]   //很显然有切片的特性,指针
slice = append(slice, 9)
fmt.Println(slice)
fmt.Println(arr)
//打印结果
[1 2 3 9]
[1 2 3 9 5 6]

//当添加的数量超过数组长度的时候会重新开辟一个地址空间

arr := [6]int{
    1,2,3,4,5,6}
slice :=arr[0:3]   //很显然有切片的特性,指针
slice = append(slice,6,7,8,9,4)
fmt.Println(slice)
fmt.Println(arr)
fmt.Printf("%T,%T\n",slice,arr)
fmt.Printf("%p,%p\n",&arr[0],slice)
//打印结果

[1 2 3 6 7 8 9 4]
[1 2 3 4 5 6]
[]int,[6]int
0xc000016240,0xc00001a120  		//地址发生变化

copy函数实现切片的删除

/*
		copy函数实现切片的删除
	*/
s := []int{
    1,2,3,4,5,6}
n := 2 //定义删除哪个位置的切片
s1 := make([]int,n)
copy(s1,s[0:n])									//把将要删除的索引元素之前的元素加到新创建的且片中
s1 = append(s1,s[n+1:]...)			//把剩余的拼接起来组成新的切片
fmt.Println(s)
fmt.Println(s1)
//打印结果
[1 2 3 4 5 6]
[1 2 4 5 6]

sort 包

用于排序的包操作如下

//int类型切片排序
	//num := []int{7,5,6,3,9,4}
	//sort.Ints(num)		// 简单写法生序排序
	//fmt.Println(num)

	//源码写法		Sort(IntSlice(a))
	//sort.Sort(sort.IntSlice(num))
	//
	//fmt.Println(num)
	//
	//sort.Sort(sort.Reverse(sort.IntSlice(num)))  //逆序排列
	//fmt.Println(num)

	//float 类型切片排序
	//f :=[]float64{1.2,3.6,2.9,3.4}
	//
	//sort.Float64s(f)   //简单写法  与int一样
	//fmt.Println(f)
	//sort.Sort(sort.Reverse(sort.Float64Slice(f)))  //降序排列
	//fmt.Println(f)

	//string 类型的切片排序
	s := []string{
    "a","b","ab"}
	sort.Strings(s)			//升序
	fmt.Println(s)
	sort.Sort(sort.StringSlice(s))

	sort.Sort(sort.Reverse(sort.StringSlice(s))) //降序
	fmt.Println(s)

	n :=sort.SearchStrings(s,"b")
	fmt.Println(n)
	fmt.Printf("%T\n",n)  //n是int型

map集合

map是以散列表的形式存储健值对的集合

map中的每个元素都是健值对

key是操作map的唯一标准。如果map对应的值重复赋值,key对应的value 就会被替换。

map是值类型(与切片相同),只声明时为空指针(nil)

map的声明

var m map[string]int
fmt.Println(m == nil)
fmt.Printf("%p",m)
//打印结果
true
0x0

用make实例化map集合

m1 :=make(map[string]int)  		//make实例化的时候不需要像切片一样添加长度
	fmt.Printf("%p\n",m1)
//打印结果
0xc000060150

map集合的增删改

map的增加

m1["张三"] = 16
m1["李四"] = 17
m1["王五"] = 18
//打印结果
map[张三:16 李四:17 王五:18]

map的删除

delete(m1,"张三")				//delete 没有返回值,如果删除的key没有对应的健,相当于没有任何操作,也不报错
fmt.Println(m1)
//打印结果
map[李四:17 王五:18]

map修改

m1["张三"] = 20
//打印结果
map[张三:20 李四:17 王五:18]

map结合的查询(返回值可以是一个值,也可以是两个值,这里演示的是两个值的情况)

value , ok :=m1["张三"]       //第二个返回值是表示是有没有查询到,查询到了返回true   没有查询到返回false
fmt.Println(value,ok)
//打印结果
20       true

map集合的遍历

for key,value := range m1{
    
    fmt.Println(key,value)
}
//打印结果
张三 20
李四 17
王五 18

//如果只想要值
for _,value := range m1{
    			//把key用   _  接收一下
 		fmt.Println(value)
	}
//打印结果
17
18
20

list集合(链表)

Go语言中在container/list包中提供了list 链表使用需要用到list来实现。

type List struct {
    
	root Element // sentinel list element, only &root, root.prev, and root.next are used      //头元素(根元素)
	len  int     // current list length excluding (this) sentinel element											  //当前list的长度
}

Element 结构体定义如下:

1、next 表示下一个元素, 可以使用Next()获取到

2、prev 表示上一个元素, 可以使用Prev()获取到

3、list 表示元素属于哪一个链表

4、Value 表示元素的值, interface{} 是一个借口,在Go语言中表示任一类型

type Element struct {
    
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation, internally a list l is implemented
	// as a ring, such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next, prev *Element

	// The list to which this element belongs.
	list *List

	// The value stored with this element.
	Value interface{
    }
}

双向链表

使用list来创建一个链表

使用New()函数来创建一个list链表

mylist := list.New()
fmt.Println(mylist)
//打印结果
&{
    {
    0xc000060150 0xc000060150 <nil> <nil>} 0}      //{0xc000060150 0xc000060150 <nil> <nil>}是一个root Element
   																									//	长度是0

向list中添加元素

添加头元素

mylist.PushFront("a")     //
mylist.PushFront("b")
//	遍历打印
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接			
		fmt.Print(e.Value, "  ")								
	}
 //打印结果
b  a  

添加尾元素

mylist.PushFront("a")
mylist.PushBack("b")
//	遍历打印
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接			
		fmt.Print(e.Value, "  ")								
	}
//打印结果
a  b 

在最后一个元素前边添加一个元素

mylist.PushFront("a")
mylist.PushBack("b")
mylist.InsertBefore("r",mylist.Back())//插入到最后一个元素的前边
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接
    fmt.Print(e.Value, "  ")
}
//打印结果
a  r  b

在第一元素之后添加一个元素

mylist.PushFront("a")
mylist.PushBack("b")
mylist.InsertBefore("r",mylist.Back())//插入到最后一个元素的前边
mylist.InsertAfter("o",mylist.Front())//插入到第一个元素后边
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接
    fmt.Print(e.Value, "  ")
}
//打印结果
a  o  r  b 

想要在某一元素之后或者之前添加元素

首先得获取到这一元素的指针

在链表中要想获取一个元素就必须一个一个的遍历循环一个一个的获取

取出链表中某一元素

n:=3
	var curr *list.Element					//定义一个链表的指针变量
	if n == 1{
    												//判断取的第一个元素
		curr = mylist.Front()					//给链表指针赋上第一个元素的地址
	}else if n == mylist.Len(){
    				//判断是不是最后一个元素
		curr = mylist.Back()					//赋值
	}else{
    				
		curr = mylist.Front()
		for i:=1; i < n; i++{
    							//如果既不是第一个元素,又不是最后元素,那就从一个循环获取到指定位置的地																			址并把地址赋值给链表指针
			curr = curr.Next()
		}
	}
	fmt.Printf("取出来第%d个元素 值为 %s\n",n,curr.Value)

在指定为位置添加元素

//原始数据
mylist.PushFront("a")
mylist.PushBack("b")
//mylist.PushBack("c")
mylist.InsertBefore("r",mylist.Back())//插入到最后一个元素的前边
mylist.InsertAfter("o",mylist.Front())//插入到第一个元素后边
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接
    fmt.Print(e.Value, "  ")
}
fmt.Println()
//在第二的元素后边添加一个元素
n:=2
var curr *list.Element
if n == 1{
    
    curr = mylist.Front()
}else if n == mylist.Len(){
    
    curr = mylist.Back()
}else{
    
    curr = mylist.Front()
    for i:=1; i < n; i++{
    
        curr = curr.Next()
        mylist.InsertAfter("9", curr)
    }
}

//遍历链表中的数据
for e:=mylist.Front();e!=nil;e=e.Next(){
       //e.Next() 获取到e元素的后连接
    fmt.Print(e.Value, "  ")
}
//打印结果
a  o  r  b  			//添加前的
a  o  9  r  b  			//添加后的

移动元素顺序

mylist.MoveAfter(mylist.Front(),mylist.Back())  //将第一个参数移动到第二个参数的后边
mylist.MoveBefore(mylist.Front(),mylist.Back())  //将第一个参数移动到第二个参数的前边
mylist.MoveToBack(mylist.Front())   //将参数移动到最后边
mylist.MoveToFront(mylist.Back())  //将参数移动到最前边

删除元素

mylist.Remove(mylist.Front())   //删除第一个元素

双向循环链表

创建一个循环链表 ring

创建一个双向循环链表

r := ring.New(5)		//需要定义循环链表的长度  r代表链表的第一个地址,也可以代表整个链表

循环链表的赋值(重复赋值会被覆盖)

//正向赋值
r.Value = 0
r.Next().Value = 1			//r是一个指针
//反向赋值
r.Prev().Value = 4
r.Prev().Prev().Value = 3

为了方便操作,循环链表提供了Do函数来获取值

r.Do(func(i interface{
    }) {
    			//Do里边的参数是一个匿名函数
    fmt.Print(i," ")							//   就是当前的值
})
//打印结果
0 1 2 3 4 

循环链表中提供了Move函数为了方便取某一个位置的值

fmt.Println(r.Move(1).Value)		//  r.Move(n int) 是指针      n 可以是负数 反向的查询

增加元素(元素可以是链表)

r1 := ring.New(1)
r1.Value = 9
r.Link(r1)   //Link可以把元素添加到指定位置的后边
r.Do(func(i interface{
    }) 
     	fmt.Print(i," ")
	})
//打印结果
0 9 1 2 3 4 

删除元素(删除的时候首元素是不能被删除的)

r.Unlink(1)   // r.Unlink(n) 删除当前元素后边的n个数
//当 n > r.len()  删除的个数 == n%r.len() 
//一般情况下只会用			首元素.Unlink(n)     一般不要用r.Next().Unlink(n)

goto的使用

goto让编译器执行时跳转到指定位置

Loop是标记名称 一般情况下标记名用Loop ,也可以用其他的

	a := 18
	if a == 18 {
    
		goto Loop
		fmt.Println("a==18")
	}
	Loop:
		fmt.Println("执行18完毕")
	a = 19
	if a ==19{
    
		goto Loop1
	}
	Loop1:
		fmt.Println("执行19完毕")

//输出结果
执行18完毕
执行19完毕

包的应用

自定义包

有一些操作性的功能自己可以封装到一个包里边为了以后方便调用

这里在src/github.com/Hello/day1/example01/add这个文件路径之下创建了一个add包

package add
var Name string  = "魏宝强"
var Age int = 18

可以在其他地方引用自己写的包

package main

import
(
	"fmt"
	"github.com/Hello/day1/example01/add"
)

func main(){
    
	fmt.Println("Name = ",add.Name)
	fmt.Println("Age =  ",add.Age)
}

如果引用其他人的包时候发现命名不好,想要自己重命名一下

import
(
	"fmt"
	a "github.com/Hello/day1/example01/add"    //前边加上自己重命名的名字
)

指针

指针就相当于一个索引,定义一个指针是不会分配内存空间的,只有创建一个变量,或者常量才会分配内存地址,但是创建出来的指针可以指向已经开辟的内存地址,并进行操作。

创建一个空指针

var  a *int   //创建了一个int类型的空指针,它只能指向int类型的内存空间

创建一个内存空间并且让指针a指向开辟的空间

var  a *int   //创建了一个int类型的空指针,它只能指向int类型的内存空间
b := 123
a = &b     //&符号是指一个空间的地址     这个语句的意思就是把b的地址赋值给了a指针

fmt.Print(*a)

用指针来操作内存空间里存储的值

*a = 456    ///* 就是可以获取到内存中的值 a就内存地址
*&b = 789   // 打印结果是789    &b就是b的地址
fmt.Print(b)

new函数

学了指针之后我们发现要用指针的化还需要另外开辟一个内存空间,这样很麻烦 ,然后就出现了new 函数,这个函数直接可以实现以上的两个操作。创建一个指针并指向了一个类型的内存地址。

仅仅创建指针与直接创建一个指向内存的一个指针的区别

	var b *int
   	fmt.Print(b)
//打印结果
<nil>     					//空
	a := new(int)    //创建了一个a 指针并且指向了一个int类型的内存空间
	fmt.Print(a)
//打印结果
0xc0000200b8    //地址 

函数

函数的定义与调用与其他语言一样,但有一点不一样

可以定义返回值go语言特有的方式

//返回值不太一样  Go语言可以定义返回值
func add(x, y int) (s int){
    
	s = x+y
	return
}

多返回值函数

func  函数名(参数,参数)(返回值,返回值,返回值){
    
                             函数体
                }

调用函数的语法

变量, 变量 :=函数名(参数,参数)  //举例当有两个返回值的时候

调用函数,但有一些返回值不想接收,可以用占位符占为

变量,_ :=函数名(参数, 参数)

举例

func show ()(a,b int){
    
	fmt.Println("show 执行了")
	a = 18
	b = 20
	return
}
func main(){
    
    a1,_:=show()  //只接收第一个返回值
	fmt.Println(a1) 
}
//打印结果
18

可变参数

可变参数是调用函数的时候,参数可以有任一个

可变参数只能在参数的最后边,参数名与参数类型中间一定要有三个点

func 函数名(参数,参数, 名称  ... 类型){
    
    //函数体
}

举例

func demo(name string, hover ... string){
    
	fmt.Println(name)
	for i,n := range hover{
    
		fmt.Println(i,n)
	}
}
func main(){
    
    demo("魏宝强","打代码","看视频")
}

//打印结果
魏宝强
0 打代码
1 看视频

匿名函数

匿名函数就是没有名字的函数

匿名函数定以后一定要调用,就是在大括号后边加一个小括号(如果是由参数的匿名函数,小括号里边需要传参数)小括号的作用就是调用函数

举例

//没有参数的匿名函数
func (){
    
    fmt.Println("魏宝强")
}()

//带参数的匿名函数
func (name string){
    
    fmt.Println(name)
}("魏宝强")   //小括号里边要传参数

//带返回值的匿名函数
func ()(s string){
    
    s = "魏宝强"
}()   

//匿名函数带参数带返回值
	name := func(name string)(s string){
    
		s = name
		return
	}("李宾")
//打印结果
//匿名函数输出:李宾

函数变量

函数可以作为一个函数的参数,也可以作为一个函数的返回值向双向循环链表中的Do函数就会函数型参数。

这个知识点不再举例。

闭包

闭包就是就是解决局部变量不能被外部访问的方案

是把函数作为返回值的一种应用

func main(){
    
	f := closeuse()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
}
func closeuse() func() int{
              				//函数closeuse的返回值是一个函数,而返回值函数的返回值是int的实数
	i:=1
	return func() int {
    											
		i = i+1
		return i					
	}
}

当有两个变量调用的都是用一个闭包函数的时候变量开辟的空间是不一样的,但是返回值函数的空间地址时一样的,只是在调用i的时候i的空间指向是不一样的。

举例:

func main(){
    
	f := closeuse()
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println(f())
	fmt.Println("-------------------")
	f1 :=closeuse()
	fmt.Println(f1())
	fmt.Println(f1())
	fmt.Println(f1())
	fmt.Println(f1())
	fmt.Printf("%p,%p\n",f,f1)       //函数本身就是一个引用数据类型
}
func closeuse() func() int{
    
	i:=1
	return func() int {
    
		i = i+1
		fmt.Printf("%p\n",&i)		  //值类型得加一个去地址符
		return i
	}
}
//打印结果
0xc0000200b8				//  f中的i地址
2
0xc0000200b8			
3
0xc0000200b8
4
-------------------
0xc0000200f8				//f1中的i地址
2
0xc0000200f8
3
0xc0000200f8
4
0xc0000200f8
5
0x48efa0,0x48efa0        // 函数f  与f1的地址

结构体

结构体就是将一个或者多个变量组合到一起,形成一个新的类型,这个类型就是结构体。

结构体与java中的类其实是一个概念。

结构体是一个值类型。

结构体的语法:

type   结构体的名称 struct {
    
   	名称   类型//  成员或者属性
}

结构体是一种自定义的类型。

结构体在创建的时候也是有访问权限的,如果定义在函数内部,那么只有在函数内部可以访问(局部),如果定义在函数外部还是根据首字母的大小写来判断可不可以跨包访问(这里的首字母指的是结构体的名字还有里边含有的变量的名字,只有首字母都是大写的时候才能在其他包里访问到结构体中的所有属性或者成员)

举例:

//在src中创建了一个Person包,写了一个Person文件里边定一个结构体
package Person

import "fmt"

type Person struct {
    
	Name string
	age  int			//这个不能被外包调用
}
func Show(){
    
	per := Person{
    "魏宝强",24}		//赋值方式1
	per1 := Person{
    age:23,Name:"李宾"}		//赋值方式2
	//var per Person						//赋值方式3
	//per.Name = "魏宝强"
	fmt.Println(per)
	fmt.Println(per1)

}


//在main.go文件中调用Person包的函数
func main(){
    

	Person.Show()
}
//打印结果
{
    魏宝强 24}
{
    李宾 23}

结构体指针

因为结构体是值类型,有的时候我们传递的时候想要传递结构体的地址,这是我们可以用结构体指针来做。

我们可以用new函数来创建结构体指针。

//可以更具自己的需求来创建结构体指针
type Person struct {
    
	Name string
	Age int
}
func main(){
    
    
	per :=new(Person)   //new函数创建一个per指针同时已经分配了地址空间。   //第一种创建方式
    
	per.Name = "魏宝强"
	per.Age = 18
    
    per2 :=&Person{
    "魏宝强"19}																						//第二中创建方式
    
	fmt.Println(per)
	per1 :=per				//per1的指针指向了per指针指向的地址
	fmt.Println(per1)
	per1.Name = "李宾"
    fmt.Println(per1)			//这些取得都是指针      //要取值的化可以  fmt.Println(&per)    结果 {魏宝强 18}
	fmt.Println(per)
}
//打印结果
&{
    魏宝强 18}
&{
    魏宝强 18}
&{
    李宾 18}
&{
    李宾 18}

方法

方法与函数比较像,其实方法就是特定类型的函数。

  • 调用方法的时候就把调用者赋值给了接受者。(下边的变量名就是接受者)
func (变量名  结构体类型)方法名(参数列表)返回值类型{
    //方法体
}

举例:

type Person struct {
    
	Name string
	Age int
}
func (p *Person) run (){
    					//根据需求想要效用一次方法之后实参需要跟着形参改变,所以这里的结构体类型应为结构体指针
	fmt.Println(p.Name,"现在年龄",p.Age)
	p.Age -= 1
}
func main(){
    
	per :=&Person{
    "张三",19}
	per.run()									//per在调用run()方法的时候集已经把per的地址赋值给了p
	fmt.Println(per.Name,"过完一次生日后的年龄",per.Age)
}
//打印结果
张三 现在年龄 19
张三 过完一次生日后的年龄 18

错误

在程序执行过程中出现的不正常的情况称为错误

Go语言中使用builtin包下的error接口作为错误类型。源码如下

type error interface {
    
	Error() string
}

GO语言中的错误都作为方法//函数的返回值。

GO语言error包中体提供了实现error接口的结构体errorString,并重写了error接口的Error方法,额外还提供了快速创建错误的函数New()

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
    
	return &errorString{
    text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    
	s string
}

func (e *errorString) Error() string {
    
	return e.s
}

如果错误信息由很多个变量小块组成,可以用。fmt.Errorf("verb",......)(verb,相当于字符串拼接),它的底层还是errors.New()

func Errorf(format string, a ...interface{}) error {
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)
	s := string(p.buf)
	var err error
	if p.wrappedErr == nil {
		err = errors.New(s)
	} else {
		err = &wrapError{s, p.wrappedErr}
	}
	p.free()
	return err
}

举例:

func demo(i,k int)(r int , e error){
    
	if(k == 0){
    
        //e = errors.New("除数不能为0")											//第一种方式  New()里边只能是字符串
		e = fmt.Errorf("%s,%d,%d","除数不能为0,两个值分别是:",i,k)				//这个可以实现多字符串的拼接
		return
	}
	r = i/k
	return
}

func main(){
    
result,e :=demo(6,2)
//fmt.Println(result,e)
if(e != nil){
    										//判断有没有错误信息   有错误信息,就把错误信息返回,没有就继续执行程序
	fmt.Println(e)
	return
}
fmt.Println("执行成功,结果是:",result)
}
//执行结果
执行成功,结果是: 3

Go语言面向对象开发(结构体充当类)

封装

封装主要体现在两个方面:封装数据,封装业务

Go语言中通常用名字首字母的大小写来完成权限的控制。

可以通过方法来封装业务。

这个例子可以参考方法的例子来看。

Person就相当一个类,他可以把姓名和年龄提供出来一个方法来设置和获取(setter and getter)只需要把结构体改成:

type Person struct {
    
    name string							//name   age  首字母都是小写就控制了变量只能在包内访问,不能在包外访问了。
    age int
}

对外提供方法的时候结构体类型一定要是结构体指针这样才可以随便改动形参来控制实参的改变(简而言之就是形参的指针要跟实参的指针指向同一个地址空间)

继承

继承在java中有特定的关键字来标识,而Go语言中的继承是通过组合来实现的。

匿名属性

在Go语言中是支持匿名属性的(结构体中的属性名字),编译器认为类型就是属性的名字,我们在使用的时候把类型当作属性名来使用。

Go语言中的继承就是把一个结构体类型来当作另一个结构体的属性来实现的,这样就可以通过这个对象来调用另一个结构体中的属性。

//我们写的结构体是这样的
type Person struct {
    
	string
	int
}
type Student struct {
    
	classroot string
	 Person
}
//计算机看的时候是这样的
type Person struct {
    
	string
	int
}

type Student struct {
    
	classroot string
	 string
    int
}

举例:

type Person struct {
    
	string
	int
}
func main(){
    
per := Person{
    "魏宝强",59}
    fmt.Println(per)
    per.string = "李宾"			//调用的时候直接调用结合体属性的类型基于可以 ,因为计算机中就是把类型当作了属性名
    per.int = 19
    fmt.Println(per)
}
//打印结果
{
    魏宝强 59}
{
    李宾 19}

通过组合来实现Go语言的继承

type Person struct {
    
	string
	int
}
type Student struct {
    
	classroot string
	 Person
}
func main(){
    
    per1 := Student{
    "3002",Person{
    "魏宝强",49}}			//赋值的时候需要注意下参数怎么赋值,创建对象的时候也可以																										通过new函数来创建指针然后再通过调用属性来一个一个的赋值
	fmt.Println(per1)
	per1.string = "李宾"															//调用属性直接可以修改值
	per1.int = 58
	fmt.Println(per1)
}
//打印结果
{
    3002 {
    魏宝强 49}}
{
    3002 {
    李宾 58}}

接口

接口中只能有方法的声明,方法只能有名称,参数,返回值,不能有方法体

每个接口可以有多个方法声明。结构体把接口中的所有方法都重写后,结构体就属于借口类型了。

定义接口类型的关键字是interface

type  借口名字  interface {
    
    方法名(参数列表) 返回值列表
}

接口了可以继承接口,在Go语言中推荐把接口拆分成多个接口。

实现接口重写方法的时候必须和接口的方法名称,方法参数(参数名可以不一样【因为形参跟实参可以不一样】),返回值列表完全相同。

举例:

定一个Live接口,写了两个方法,一个是run(run int),另一个是eat()

type Live interface {
    		//接口
	run(run int)			//方法声明  不能有方法体
	eat()
}

创建一个结构体,然后实现Live接口

  • 创建接口
type Person struct {
       
    name string
}
  • 实现接口(重写接口中的全部方法,结构体类型是*Person【指针型的结构体】)
func (p *Person) run(run int){
    
	fmt.Println(p.name,"正在跑步跑",run,"米")
}
func (p *Person) eat (){
    
	fmt.Println(p.name,"在吃饭")
}

创建Person对象,并且调用重写的方法

func main(){
    
	per :=new(Person)
	per.name = "魏宝强"
	per.run(100)
	per.eat()
}
//打印结果
魏宝强 正在跑步跑 100 米
魏宝强 在吃饭

当接口中的方法很多,结构体实现的时候很麻烦,这时候就可以把一些相关的方法,写到另一个接口中。方便结构体实现。

type Live interface {
    		//接口
	run(run int)			//方法声明  不能有方法体
	
}
type Eat interface {
    			//在原来的基础上把eat()方法剥离出来。方便实现
	eat()						
}					

接口也可以实现接口,有的时候是需要的。

type Live interface {
    	
	run(run int)		
	Eat							//这个接口已经实现了Eat接口中的所有方法。
}
type Eat interface {
    			
	eat()						
}	

多态

多态,就是同一个事情条件不同,至性的结果也不同。

由于Go语言中结构体不能相互转换,所以没有结构体(父子结构体)的多态,只有基于接口的多态。

多态在代码层面上最常见的一种方式就是接口作为方法的参数。(保证了参数的不同)

  • 重写借口时接收者为type,*type的区别
  • *type可以调用*typetype作为接收者的方法,所以只要接口中的的多个方法只要出现一个使用的*type接收者进行重写方法,就必须把结构体指针赋给变量,否则编译错误。
  • type只能调用type作为接收者的方法。

如果接口中有两个方法,其中一个是指针类型的接收者,另一个是结构体型的接收者,实现接口对象时候传递值会报错。

type Live interface {
    				//创建一个接口
	run()
	eat()
}
type Person struct {
    
	name string
}
func (p *Person) run(){
    				//指针接收者
	fmt.Println("在跑步")
}
func (p Person) eat(){
    				//值接收者
	fmt.Println("在吃饭")
}
func allrun(live Live){
    
	live.run()
}
func main(){
    
    //下边这两种方式都是传递的指针。   程序可以正常的执行
	//var per Live = &Person{}
	var per Live = new(Person)	  	//创建的是一个指针并且创建了一个地址空间
    
    //下边是传递的值
    var per Live = Person(){
    }   //这个时候会报错   错误内容是cannot use Person literal (type Person) as type Live in 																assignment:Person does not implement Live (run method has pointer receiver)
	per.eat()
}

多态的实现:(参数接收的是接口)相当于是谁实现的接口,传递的就是哪个类

type Live interface {
    
	run()
}
type Person struct {
    
}
type Anamals struct {
    
}

func (p *Person) run(){
    
	fmt.Println("人在跑步")
}
func (a *Anamals) run(){
    
	fmt.Println("动物在跑步")
}
func allrun(live Live){
    
	live.run()
}
func main(){
    
	//因为Person  Anmals 都实现了Live 相当于已经是他的子类了
	//可以直接创建子类对象
	anmals := new(Anamals)
	allrun(anmals)
}
//打印结构
动物在跑步

断言

判断类型

只要实现了接口的全部方法,就认为这个类型属于接口类型,如果编写了一个接口这个接口中没有任何方法,这是就认为所有类都实现了这个接口,所以Go语言中就用interface{}来表示任意类型。

如果想要判断interface到底是什么类型,Go语言中提供了断言来查看。

  • 断言的使用,使用 interface{}类型的变量名.(类型)
i  interface{
    }
i.(类型)
  • 如果使用一个返回值

    如果i.(类型)类型匹配正确就会返回i的值,否则就会报错。

func main() {
    
	var i interface{
    }
	i = 465
	result := i.(int)			//如果类型写成floate64 就会报错,  interface {} is int, not float64
	fmt.Println(result)
}
//打印结果
465
  • 如果是两个返回值,如果类型匹配正确第一个参数就会返回结果,第二个参数显示匹配是否正确返回(true ,false)
func main() {
    
	var i interface{
    }
	i = 465
	result, ok:= i.(float64)
    result1, ok1:=i.(int)
	fmt.Println(result,ok)
    fmt.Println(result1,ok1)
}
//打印结果
0 false
465 true
转换

根据上边判断的结果来转换成我们可以可能到的提示语句

举例:

func main() {
    
	show(789.10)
}
func show (i interface{
    }){
    
	result, ok:= i.(int)
	if ok{
    
		fmt.Println(result,"类型是int")
		return								//执行到立即返回不执行下边的语句
	}
	result1,ok1 := i.(float64)
	if ok1{
    
		fmt.Println(result1,"类型是float64")
		return
	}
	fmt.Println("类型没办法判断")
}
//打印结果
789.1 类型是float64

defer

defer 在函数执行完最后执行的,可以用来关闭资源,避免资源的益处。

一个defer

defer 的定义

defer fmt.Println("关闭资源")		//第一种方法      只有一个语句的时候
	defer func() {
    
		fmt.Println("关闭资源!!")	//第二种方法			如果想要进行一系列的操作  最好用这种方式    
	}()

defer 后接一个匿名函数匿名函数里边可以写一些想要程序最后执行的功能(关闭资源,关闭文件输出流等)

多个defer

当有多个defer的时候会按照栈的方式进行存储defer (先进后出)返回的时候就是按照栈的方式来。

在上边这个例子中可以看到关闭资源先创建的,关闭资源!!!后创建的,当程序执行完之后结果如下:

关闭资源!!
关闭资源

可以看出defer是先进后出的。

defer与return 一起出现的时候

可以把return看成两步:

  • 赋值
  • 返回

defer的执行时在赋值之后执行的顺序是:

  • return赋值
  • defer执行
  • return返回
func main(){
    
	a := demo(1)
	fmt.Println(a)
}
func demo(i int) (z  int){
        		//这个在赋值的时候已经把i = 1赋值给了z
 	defer func() {
    							 //在执行defer的时候已经没有用了
		i +=1				    
	}()
	return i
}
//打印结果
1						//返回的还是1  证明了上边的执行顺序
func main(){
    
	a := demo(1)
	fmt.Println(a)
}
func demo(i int) (z int){
    			//先是把i的值赋给z  然后执行defer这时候z = i+2  z值发生改变,然后返回
	defer func() {
    
		z = i+2
	}()
	return i
}

注:

  • 一定要看清返回值是什么
  • 清楚defer与return执行的先后顺序

panic

panic 是builtin中的函数。

panic类似于其他语言中的throw,抛出异常,当执行到panic后,终止代码执行,并打印错误信息。

func main(){
    
	fmt.Println("魏宝强")
	defer func (){
    
		fmt.Println("defer执行了")
	}()
	panic("panic 执行了,哈哈")

	//fmt.Println("李宾")
}
//打印结果
魏宝强
defer执行了								
panic: panic 执行了,哈哈					//代码值执行panic上边的代码,包括defer,defer之后执行(做一些善后的工作)

goroutine 1 [running]:
main.main()
	/home/wbq/GOPATH/src/github.com/wbqDemo1/main.go:277 +0xcc				//显示panic出现的所在行

recover

recover应用

recover ()表示恢复函数的panic(),让程序正常运行

recover()与panic()一样都是builtin中的函数,可以接收panic的信息,恢复程序正常运行。

panic与throw一样 没有处理会一直传下去,下边程序演示的就是这样。

func main() {
    
	fmt.Println("程序开始")
	demo1()
	fmt.Println("程序结束")
}
func demo1(){
    
	fmt.Println("demo1上")
	demo2()
	fmt.Println("demo1下")
}
func demo2(){
    
	defer func() {
    
		recover()						//仅仅取消掉了panic  还可以接收panic返回的值
	}()
	fmt.Println("demo2上")
	demo3()
	fmt.Println("demo2下")
}
func demo3(){
    
	fmt.Println("demo3上")
	panic("出现panic")
	fmt.Println("demo3下")
}

解释:先是执行main函数然后程序开始进入demo1 然后执行demo1上,依次执行到demo3中的panic,因为出现了panic所以下边的demo3下不再执行,上升到demo2因为demo3中的没有处理所以demo2下执行不了,demo2的defer中含有recover可以处理panic,所以demo2结束之后没有了panic的demo1,main函数正常执行。

recover接收panic的信息
func main() {
    
	fmt.Println("程序开始")
	defer func() {
    
		if error :=recover();error !=nil{
    
			fmt.Println("获取到panic的值为:",error)
		}
	}()
	panic("出现了panic")
}

os

创建文件夹

Go语言中提供了两种创建文件夹的方法。

  • os.MKdir(“绝对文件路径”,os.ModeDir) 返回值是error类型 可以用来判断是否创建是否成功。
  • os.MKdirAll(“绝对文件路径”,os.ModeDir) 返回值也是error类型。

两者的主要区别:

os.MKdir() 创建条件 父目录已经存在,想要创建的目录一定不存在 这样才能创建成功

os.MKdir() 没有任何的约束,如果想要创建的目录已经存在,则不做任何操作,如果不存在,则创建。

举例:

//os.MKdir()
func main() {
    
	err := os.Mkdir("/home/wbq/goyuyan", os.ModeDir)
	if err !=nil{
    
		fmt.Println("文件创建失败!",err)
	}
	fmt.Println("文件创建成功")
}
//os.MKdirAll()
func main() {
    
	err := os.MkdirAll("/home/wbq/goyuyan/abc", os.ModeDir)
	if err !=nil{
    
		fmt.Println("文件创建失败!",err)
		return
	}
	fmt.Println("文件创建成功")
}

os暂时写这么多,有需要查一下就可以了

输入流

输入一个字符串。

记住一个快捷键:F2 可以快速查看所调用函数的参数以及返回值类型

创建字符串输入流的方法:

 strings.NewReader(字符串)     

读入方法,是Read(切片类型),一般都会在调用之前需要创建一个切片然后再调用Read方法。

举例:

func main() {
    
	r := strings.NewReader("魏宝强")
	b := make([]byte, r.Size()) //创建一个切片

	n, err := r.Read(b)
	if err !=nil{
    
		fmt.Println("输入流出错,错误信息:",err)
		return
	}
	fmt.Println("数据流长度:",n)
	fmt.Println("数据信息为:",string(b))   //切片转换为字符串
}

读取文件信息:

func main() {
    
	f,_:=os.Open("/home/wbq/godemo")     //用只读方式(只能读取)打开文件或者文件夹
	fileinfo,_:=f.Stat()											//获取文件或者文件夹信息
	b :=make([]byte,fileinfo.Size())				//创建一个与文件信息长度相同的切片
	f.Read(b)															//这里没有做异常处理
	fmt.Println(string(b))
}

输出流

首先需要了解os.OpenFile的第三个参数权限:

第三个参数表示文件权限:
第一位	    在权限中总是0
第二位		
第三位
第四位
下边整理后三个位的数字代表啥意思:
0(0000) 不能读写,不能执行
1(0001)不能读写,只能执行
2(0010)可写不能读,不能执行
3(0011)	可写不能读,可以执行
4(0100)可读不可写,不能执行
5(0101)可读不能写,可以执行
6(0110)可读可写,不能执行
7(0111)可读可写,可执行


0666:
第一位0 ,代表这个数是八进制数
第二位6,代表文件拥有者有读写权限,没有执行权限
第三位6,代表文件拥有者同组用户有读写权限,没有执行权限
第四位6,代表其他用户有读写权限,没有执行权限

写入的时候因为Open是只读的所以不能写入所以只能用OpenFile()。

OpenFile(参数1,参数2,参数3)

参数1: 文件绝对路径

参数2:写入方式,调用os写好的常量就行

参数3:文件权限

举例:

func main() {
    
	f,err:=os.OpenFile("/home/wbq/godemo",os.O_APPEND,0660)
	defer f.Close()									//打开了文件别忘了就是关闭
	if err!=nil{
    
		f,_=os.Create("/home/wbq/godemo")		//如果没有发现目标文件就重新创建一个

	}
	//f.Write([]byte("输入内容::"))     				//第一种写法,但是不常用
	f.WriteString("/r/n魏宝强")					//第二种方法,其实底层还是Write
	fmt.Println("程序结束")
}

ioutil包(封装了文件操作的一些功能)

文件信息的输出

代替之前的读取文件信息的操作:

  • open函数打开一个文件
  • 创建一个切片接收文件中的数据
  • Read()读取数据
  • 返回

ioutil提供了两种方式来代替:

  • 第一种
//这种方式就是少了一个创建切片的过程。
f,_:=os.Open("/home/wbq/godemo")			
b,_:=ioutil.ReadAll(f)
fmt.Println(string(b))
  • 第二种
//这种方式是最简单的直接填写文件路径就可以了,返回的也是一个切片
b,_:=ioutil.ReadFile("/home/wbq/godemo")
fmt.Println(string(b))

文件的信息写入

代替了os提供的方法,但是有一定的局限性(替换掉了文件中原来的信息)不是在后便累加的,

ioutil.WriteFile("/home/wbq/godemo",[]byte("李宾"),0666)  //这个有一个缺陷就是会覆盖之前写的东西
																															//	返回值是一个error
																														//参数1    文件路径    参数2   切片类型的内容   参数3  权限

查看文件目录

b,_:=ioutil.ReadDir("/home/wbq")
	for _,i := range b{
    
		fmt.Println(i.Name())
	}

反射

在Go语言标准库中reflect包中提供了运行时反射,程序运行过程中动态操作结构体。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDcQQVML-1580735567454)(/home/wbq/文档/学习笔记/gostudyimp/image-20200122224600616.png)]

任何的具体(int 等)类型都可以看作空接口(接口类型)的具体实现。

  • 以下的类型都可以根据反射机制来获取:

    Bool
    	Int
    	Int8
    	Int16
    	Int32
    	Int64
    	Uint
    	Uint8
    	Uint16
    	Uint32
    	Uint64
    	Uintptr
    	Float32
    	Float64
    	Complex64
    	Complex128
    	Array
    	Chan
    	Func
    	Interface
    	Map
    	Ptr
    	Slice
    	String
    	Struct
    	UnsafePointer
    

当变量存储了结构体的属性名称,想要对结构体这个属性赋值或者查看的时候,就可以运用反射

反射还可以用作判断变量类型

在reflect包中有两个重要的类型:

  • reflect.Type类型
  • reflect.Value类型

获取到Type和Value的函数

  • reflect.TypeOf(interface{}) 返回type
  • reflect.ValueOf(interface{}) 返回value

反射获取变量的类型和值

func main() {
    
	a:=1.5
	fmt.Println(reflect.TypeOf(a))   //获取类型
	fmt.Println(reflect.ValueOf(a))	 //获取值
}
//打印结果
float64
1.5
  • 通过ValueOf()来转化为反射类型对象:

    • 有两个方法可以获取到数据的类型种类 v.Kind(),v.Type()
  • v.kind(),v.Type()两个方法的区别。

    • Kind()有slicp,map,pointer指针,struct,interface,string,Array,Funtion,int或其他基本类型。
    • Type()可以表示静态类型,比如结果体“结构体名称”,而Kind()不能。
  • 通过TypeOf()来转化为反射类型对象:

    • 通过type.Name()可以获取到类型名称type.Kind()获取类型的种类。

获取结构体属性

//获取结构体的属性值
a:= "Address"
per:= Person{
    "魏宝强","河南周口"}
v:=reflect.ValueOf(per)
fmt.Println(v.NumField())
fmt.Println(v.FieldByIndex([]int{
    0}))
fmt.Println(v.Field(0))							//这个可以强制转换成其他类型的再后加一个  .int   或者.float64 
fmt.Println(v.FieldByName(a))			//这个是反射最大的作用(通过结构体属性名称获取值)

设置结构体属性

//设置结构体的值(需要的就是形参改变实参也跟着改变,这时候就需要用到指针)
peo:= new(Person)		//创建一个指针对象
v := reflect.ValueOf(peo).Elem()  //Elem()函数就是一个指针,Elem()就是为了获取指针指向区域的值 此时的V才是值
a :=v.FieldByName("Name").CanSet()		//返回的是否可以改变的标志 (布尔类型的)只有属性名大写才可以设置属																							性值
v.FieldByName("Name").SetString("weibaoqiang")		//这里可以设置不同类型的值,这里是设置字符串类型的
fmt.Println(a)
fmt.Println(*peo)
//打印结果
true
{
    weibaoqiang }

获取结构体标记

结构体标记语法:

type 结构体名  struct{
    
    属性名||方法名   `标记内容`
}

获取标记:

type Person struct {
    
	Name string		`xml:"weibao"`
	Address string
}
//获取标记
		per:=Person{
    }
		t:= reflect.TypeOf(per)
		fmt.Println(t.FieldByName("Name"))	//获取全部name信息
		tag,_:=t.FieldByName("Name")
		fmt.Println(tag.Tag)		//获取完整标记
		s:=tag.Tag.Get("xml")	//获取标记中xml对应内容
		fmt.Println(s)
//打印结果
{
    Name  string xml:"weibao" 0 [0] false} true
xml:"weibao"
weibao

线程休眠与计时器

GO语言中的main()函数为主线程(在Go语言中叫协程),程序从上到下执行

可以通过time包中的Sleep函数让程序阻塞多少纳秒

func main() {
    
	fmt.Println("111")
	time.Sleep(3e9)		// 程序休眠3s钟		e代表多少次方
	fmt.Println("2222")
}

延迟执行

time.AfterFun(秒数,匿名函数)

延迟指定时间后执行一次,注意主线程还没有结束的情况下。(如果为了保证AfterFun()函数中的内容可以执行,有时候需要加一个休眠【保证AfterFun函数执行】,AfterFun()函数一定要在休眠前边)

func main() {
    
	fmt.Println("111")
	time.AfterFunc(2e9, func() {
    
		fmt.Println("3333")
	})
	time.Sleep(6e9)		// 程序休眠3s钟

	fmt.Println("2222")
}

goroutine(协程)

Golang 最好的一点就是可以在语言层面上直接完成并发操作。

Golang中的goroutine(协程)相当于其他语言中的线程。

  • 并发与并行

    • 并行: 指不同的代码块同时在不同的物理处理器上支持,但每一个线程只有执行完一个之后才能执行下一个。
    • 并发:同时管理很多事情,物理处理器上可能运行完某个部分的一半后就处理其他的内容了。
  • goroutine 的语句

    • 表达式可以是一条语句
    • 表达式也可以是一个函数,函数的返回值即使有也无效,当函数执行完之后goroutine自动结束
go  表达式

举例:

func demo (count int){
    
	for i:=1;i<100 ;i++  {
    
		fmt.Println(count,"::",i)
	}
}
func main() {
    
	for i:=1;i<=5 ;i++  {
    
		go demo(i)		
	}
	time.Sleep(1e9)
}

waitGroup

Golang中在sync包提供了基本同步基元,如互斥锁等,除了Once和WaitGroup类型,大部分都是用于低水平线程,高水平同步线程使用Channal通信更好。

WaitGroup又叫等待组,其实就是一个计数器,只要计数器中一直有数,就一直阻塞。

GO语言的标准库值中WaitGroup只有三种方法:

  • Add(delta int )表示向内部计数器添加增量(delta) 参数(delta)可以是复数
  • Done()表示减少WaitGroup计数器的值,在程序的最后执行,相当于Add(-1)
  • Wait()表示阻塞到WaitGroup计数器为0
func main() {
    
	var wg sync.WaitGroup
	wg.Add(5)				//给(WaitGroup)计数器赋初始值
	go func() {
    
		for i:=1;i<=5 ;i++ {
    
			fmt.Println("第",i,"次执行")
			wg.Done()			//每执行一次(WaitGroup)计数器的值-1
		}
	}()
	wg.Wait()	 //阻塞主协程   只有当WaitGroup计数器清零之后才会放行。		  如果没有等待或者阻塞其他线程就没有									时间执行(主线程执行完之后不管其他线程执行完都终止)
}

互斥索

Go语言中多个协程操作同一个变量的时候可能会出现线程冲突的问题。

可以使用sync.Mutex对内容进行加索

可以在命令行中通过go run -race查看竞争(有竞争就会报各种提示警告)

互斥索的使用场景

  • 多个goroutine访问同一个函数
  • 一个函数操作一个全局变量
  • 为了保证共享变量的安全性。

下边给出一个售票的例子:(用了WaitGroup与Mutex)

import (
	"fmt"
	"sync"
	"time"
)
var(
	ticket = 100
	wg  sync.WaitGroup
	mu sync.Mutex
)
func main() {
    
	wg.Add(4)
 	go syncdemo("窗口1")
	go syncdemo("窗口2")
	go syncdemo("窗口3")
	go syncdemo("窗口4")
	wg.Wait()
}
func syncdemo(name string){
    
	defer wg.Done()
	for {
    
		mu.Lock()					//为了保证一个线程执行完之后资源才会被重新争抢(加索)
		if ticket > 0 {
    
			time.Sleep(5e6)
			fmt.Println(name, "出售第", ticket, "张票")
			ticket--
		} else {
    
			mu.Unlock()			//这个地方加解锁的作用,就是为了保证当一个线程抢占到资源之后发现没有票了,因为下边													有break那么这个线程就直接跳出去了没有进行解锁,就会造成 main  goroutine没办法结束
			fmt.Println(name,"票已经出售完了!!")
			break
		}
		mu.Unlock()			//当一个线程执行完之后一定要解锁。
	}
}

读写锁

读写锁与同步锁都在sync包下,读锁在sync.RWMutex

读写索特点:

  • 同时只能有一个goroutine 能够获得写锁定
  • 同时只能有多个goroutine 能够获得读锁定
  • 同时只能存在读锁定或者写锁定(读写互斥)

所以RWMutex 这个读写锁,该锁可以加多个读锁定,或者一个写锁定,其经常运用在读操作远多与写操作的场景。读写锁的写锁只能锁定一次,解锁前不能多次锁定,读锁可以多次,但是读锁锁定此书最多只能比读锁多一次。

两大基本原则:

  • 可以随便读,多个goroutine可以同时读
  • 写的时候什么都不能干,不能读,也不能写。

读锁的方法:

读锁定:

RLock()

读解锁:

RUnlock()

写锁定:

Lock()

写解锁:

Unlock()

举例:读锁定

import (
	"fmt"
	"sync"
	"time"
)

var(
	wg sync.WaitGroup
	rw sync.RWMutex			//读写互斥锁
)

func main() {
    
	wg.Add(2)
	go readDemo("窗口1")
	go readDemo("窗口2")
	wg.Wait()
	fmt.Println("程序结束。。。")
}
func readDemo(name string){
    
	
    fmt.Println(name,"开始读。。")
	rw.RLock()
	time.Sleep(5e6)
	fmt.Println(name,"已经读完。。")
	rw.RUnlock()
}
//打印结果												可以看出读锁可以多个一起
窗口2 开始读。。
窗口2 正在读。。
窗口1 开始读。。
窗口1 正在读。。
窗口2 已经读完。。
窗口1 已经读完。。
程序结束。。。

举例:写与读锁定

import (
	"fmt"
	"sync"
	"time"
)

var(
	wg sync.WaitGroup
	rw sync.RWMutex			//读写互斥锁
)

func main() {
    
	wg.Add(3)
	go writeDemo("窗口2")
	go readDemo("窗口1")
	go writeDemo("窗口3")


	wg.Wait()
	fmt.Println("程序结束。。。")
}
func writeDemo(name string){
    
	defer wg.Done()
	fmt.Println(name,"开始写。。")
	rw.Lock()
	time.Sleep(5e6)
	fmt.Println(name,"正在写。。")
    fmt.Println(name,"快写完了。。。")
	rw.Unlock()
	fmt.Println(name,"已经写完。。")
}
func readDemo(name string){
    
	defer wg.Done()
	fmt.Println(name,"开始读。。")
	rw.RLock()
	time.Sleep(5e6)
	fmt.Println(name,"正在读。。")
	rw.RUnlock()
	fmt.Println(name,"已经读完。。")
}
//打印结果								可以看出来正在写的时候只能执行写,读的时候可以多个读一起
窗口3 开始写。。
窗口3 正在写。。
窗口3 快写完了。。。
窗口1 开始读。。
窗口2 开始读。。
窗口3 已经写完。。
窗口1 正在读。。
窗口2 正在读。。
窗口1 已经读完。。
窗口2 已经读完。。
程序结束。。。

channal

channal就是goroutine之间的通道,它可以让goroutine相互通信。

每一个通道都有一个类型,该类型的通道只允许传输这个类型的数据。(通道的值为nil的时候,相当于没有任何用处,因为通道需要类似切片的方式来定义)

通道(channal)的声明
//定义
var  通道名  chan  通道类型        			//与定义变量一样
//创建通道
通道名 = make(chan 类型)

//除了这种创建方式还可以用简短创建的方式
通道民   := make(chan  类型)

举例:

func main() {
    
	var ch chan int
	ch = make(chan int)
	fmt.Printf("%T,%v\n",ch,ch)		//打印出来通道的类型还有值
}
//打印结果
chan int,0xc00001a120

//简短创建
func main() {
    
	ch := make(chan int)
	fmt.Printf("%T,%v\n",ch,ch)		//打印出来通道的类型还有值
}
通道传递数据方式
  • 线程向通道里边传数据(写入)

    ch <-data
    
  • 通道向线程传输数据(读取)

    data :=  <-ch
    
发送和接收数据默认都是阻塞

当一个数据被发送到通道中时,在发送语句中被阻塞,之后又另一个线程(goroutine)将通道中的数据取出之后发送线程才会被解除,相反的,当从一个通道中读取数据,读取被阻塞,之后另一个线程将数据发送到线程之后,读取才会被解除。

举例:

func main() {
    
	ch := make(chan int)

	go func() {
    
		for i:=1;i<10 ;i++  {
    
			fmt.Println(i,"输出")
		}
		ch <-0									//从线程中输如一个数字到通道中  
	}()
	data := <- ch							//从通道中读取一个数据到线程					无论是子线程先抢到资源还是主线程先抢到资																源,都会遇到阻塞,只有里根一个资源传入或传出数据之后,整个程序才能完整执行
	fmt.Println("接收数据::",data)
	fmt.Println("结束")
}
//打印结果
1 输出
2 输出
3 输出
4 输出
5 输出
6 输出
7 输出
8 输出
9 输出
接收数据:: 0
结束
死锁

死锁出现的原因就是因为一个线程没有办法写入,或者读取到通道中信息造成的,证明得有一个或多个通道没有读或写信息。

举例:

把上边的示例去掉主线程中data := <- ch 或者子线程中的  ch <- 0都会造成死锁的现象

关闭通道

发送者可以通过关闭通道,来通知接收方不会有更多的数据被发送到channal上。

close(ch)

语法结构:

v,ok   :=   <-ch

类似map操作,存储key,或者value键值对

v,ok   :=   map[key]

在上边的语句中,如果ok的值是true,表示成功从通道中读取到一个信息,如果是false,这就意味着我们正在从一个封闭的通道里读取数据,读取的值是默认的类型值。

用一个for{} 【表示无限循环】来表示

举例:

import (
	"fmt"
	"time"
)
func main() {
    
	ch := make(chan int)
	go demo(ch)
	for{
    					//for 无限循环
		time.Sleep(1e9)
		v,ok := <-ch
		if !ok{
    
			fmt.Println("数据接收完了。。",ok)
			break						//当发现通道关闭后就会跳出循环
		}
		fmt.Println("接收数据为:",v,ok)
	}
	fmt.Println("main...over..")
}
func demo(ch1 chan int){
    
	for i:=1;i<10;i++{
    
		ch1 <- i
	}
	close(ch1)
}

for range 实现

import "fmt"
func main() {
    
	ch := make(chan int)
	go demo1(ch)
	for v:= range ch{
    							//range自动判断通道是否关闭,没关闭掉进for打印数据
		fmt.Println("读取数据::",v)
	}
	fmt.Println("main...over..")
}
func demo1(ch1 chan int){
    
	for i:=1;i<10;i++{
    
		ch1 <- i
	}
	close(ch1)			
}

缓冲区通道

缓冲区通道就是指一个通道,带了一个缓冲区,数据发送到缓冲区只有当缓冲区满的时候才会被阻塞,缓冲区通道只有在缓冲区为空的时候才会被阻塞。

语法:

ch   := make(chan type, 容量大小)

//eg:
ch  := make(chan  int , 5)			//表示创建一个int类型的通道缓冲区长度是5

举例:

import (
	"fmt"
	"strconv"
)

func main() {
    
	ch := make(chan string,3)
	go demo2(ch)
	//for v := range ch{
    
	//	fmt.Println("\t主线程读取到的信息为:",v)
	//}
	for  {
    
		v,ok := <- ch
		if !ok{
    
			fmt.Println("数据接收完了。。。",ok)
			break
		}
		//fmt.Println(len(ch))
		fmt.Println("\t主线程接收到的信息为:",v,ok)
	}
	fmt.Println("main.....over..")

}
func demo2(ch1 chan string){
    
	for i:=1;i<=10;i++{
    
		ch1 <- "数据"+strconv.Itoa(i)
		//fmt.Println(len(ch1))
		fmt.Printf("子goroutine中写的第%d条数据\n",i)
	}
	close(ch1)
}

单项通道

从词义值中节可以知道,只能进行读写操作中的一个。

语法:

ch := make(chan <- int)				//只写通道
ch := make(<- chan int)				//只读通道

一般用的是不会专门定义一个只写或者只读的通道,还是定义一个双向的通道,只是在函数传值的时候限制一下。

举例:

func main() {
    
	ch := make(chan int)			//定义的时候一般是定义的双向通道
	go fun1(ch)
	data := <- ch
	fmt.Println("main...接收",data)
	fmt.Println("main...over..")
}

func fun1(ch chan <- int){
    		//函数中参数限制一下通道为只写
	ch <- 5
	fmt.Println("值传出")
}
//打印结果
值传出
main...接收 5
main...over..

time包中的通道相关函数

主要就是定时器,标准库中的Timer可以让用户定义自己的超时逻辑,尤其是在对应的select 处理多个channal的超时、单channal读写尤其方便。

Timer是一次性的时间触发事件,Ticker是按一定时间间隔触发事件。

Timer常见的创建方式:

t:= time.NewTimer(d)    			
t:= time.After(d)
t:=time.AfterFunc(d,f)

Timer有3个要素:

定是时间:就是上边的d
触发事件:就是上边的f
时间channal: 也就是t.C      //返回值是一个通道  

举例:

func main() {
    
	t :=time.NewTimer(3e9)		//3s之后触发事件			返回值就是        *time.Timer
	fmt.Println(time.Now())
	ch := t.C
	fmt.Println(<-ch)
}
//打印结果
2020-01-21 12:16:35.101117665 +0800 CST m=+0.000068768
2020-01-21 12:16:38.10131312 +0800 CST m=+3.000264501

After()相当于就是NewTimer().C 返回值就是一个通道

	ch1 := time.After(3e9)
	fmt.Println(time.Now())
	fmt.Println(<-ch1)

select 语句

select语句的语法结构与switch语句很相似,也有case与default语句:

select{
    
    case  通道:语句
    case 通道: 语句
    。
    。
    。
    。
    default: 语句				//可选  写不写都可以
}

【注】:

  • 每一个case 都是一个通信
  • 所有的channal表达式都是一个求值
  • 所有被发送的表达式都是一个求值
  • 如果多个case都可以执行,就随机的选出一个执行,其他的不会执行
  • 如果case都不能执行,如果有default就执行default ,如果没有select就会被阻塞,直到有case可以被执行为止。

举例:

	ch1 := make(chan int)
	ch2 := make(chan int)
	go func() {
    
		time.Sleep(1e9)
		ch1 <- 100
	}()
	go func() {
    
		time.Sleep(1e9)
		ch2 <- 200
	}()
	select{
    
		case  data:=<-ch1:
			fmt.Println("ch1执行,接收数据为:",data)
		case data2 := <-ch2:
			fmt.Println("ch2被执行,接收数据为::",data2)
		case <- time.After(1e9):
			fmt.Println("After被执行。")
	default:
		fmt.Println("default")								//因为上边的case都被睡了1s 所以执行了default  如果把通道睡眠的给去掉																						就会从通道1与2中随机选一个执行
	}
}

beego

bee工具自动创建的文件解读

--conf   						//配置文件目录
	-- app.conf				//配置文件       可以用于存放数据库的连接配置
--controller				//控制目录
	--default.go          //这个文件主要时写控制的方法,可以通过路由进行访问相关方法
--model						//模型的存放目录
--router					//路由目录
	--router.go			//里边可以用与配置路由,访问的get  post  等一些请求访问的方法
--static					//存放静态的资源  
	--css
	--img					//可以把上传的图片放到这个目录
	--js
--tests						//用于写代码的测试
--views					//视图层    这个目录之下可以写html   ,  tpl   展示网页内容

路由配置

基础路由配置

在路由配置之前需要有一个结构体来实现beego.Controller

type    结构体名称  struct{
    					//一般控制什么的就用相关的词来写结构体的名字
    beego.Controller     
}
beego.Get("/get",func(context  *context.Context){
    			//这个方法只有get请求才可以
    context.Output.Body([]byte ("输出的字符串"))
})
//除了get请求,post  ,  delete, head,options 请求也是一样

固定路由配置

beego.Router("/get",&controllers.MainController{
    })   //根据路由名称"/get"只能调用controller中的Get方法
//其他的请求也是一样

正则路由配置

全匹配(所有的请求都可以访问到)
beego.Router('"/*" ,&controllers.MainController{
    })    
id匹配
beego.Router('"/get:id" ,&controllers.MainController{
    })    //路由访问的时候需要后边跟过一个id,可以时数字,可以时字符       //这个根据路由的名字可以知道值只能访问Get方法    //get请求
//其他的请求也是一样             
             还有就是可以控制id的取值范围       "/get:id([0-9]+)    id  只能是0-9的数字
             //还可以设置 id的类型    如果设置id是string  类型的        "/get:id:string"       
             如果设置id是int类型的  		"/get:id:int"
带后缀的文件配置
beego.Router("/updown/*.*",&controllers.ResController{
    }) 
//访问路径必须是带    *.*   eg:    123.jpg
自定义类型(这个最常用)
beego.Router("/访问路径", &controllers.结构体名称{
    },"get:控制控制器函数;post:控制器函数")
eg :  beego.Router("/demo",&controllers.Rescontroller{
    },"get:demo1;post:demo2")  
意思:   当ip访问  localhost:8080/demo    的时候,如果是get请求就会执行控制中的demo1方法,如果是post请求就会执行控制中的demo2方法。

beego中的基本语法

控制(controller)中的语法

通过路由在执行相应的操作的时候就会调用控制中的一些方法。下面就说几种常用到的语法。

打印日志信息
beego.Info("")     这样就可以在控制台看到相关的信息。
字符串输出到页面
有两种方式可以输出
1、this.Ctx.Output.Body([]byte("Demo执行"))  //需要记住的就是传的值是字节类型的。
2、this.Ctx.ResponseWriter.Write([]byte("固定路由配置 Post 请求"))        两种方式都可以把字符串打印到页面上,一般只用于调试
跳转方式

有两种跳转方式,一种是带数据(可以用于数据的共享),一种不带数据(仅跳转页面)。

1、 
this.Data["共享数据的名称"] = 数据
this.TplName = "页面名称带后缀"			eg:  this.TplName = "demo.html"
2、
this.Redirect("路由")  		eg: this.Redirect("/demo")
获取页面传递的数据(例如注册时候的信息)

在goweb中为了能够回去到页面传递的信息goweb的控制中可以通过GetXxx 来获取相关类型的数据。

在页面中大部分的数据都是string类型的所以最常用的就是GetString	()函数
如果前端传递给一个数据名称为"Name"的数据
data  :=  this.GetString("Name")

如果穿过来的数据数int类型的数据
data ,_  := this.GetString("数据名称")来获取

//经测试,好像接收值的时候只能用没用过的变量接收
不能       data    =   this.GetString("Name")

数据库的连接

ORM

beego中内嵌了ORM框架,它可以把结构体和数据表对应相连起来,只需要通过结构体与对象就可以对数据表进行操作。

安装ORM
go  get   github.com/astaxie/beego/orm

数据库的连接一般在model文件夹里写,在连接数据库的时候需要先引入数据库相关的包github.com/go-sql-driver/mysql,因为在启动服务的时候就应该连上了数据库,需要在引入的时候在前边加_表示跳入执行其中的init方法。

注册驱动
orm.RegisterDriver(数据库名称,orm.DRMySQL) 			//第二个参数是数据库类型    go暂时只支持三种数据库。最常用的就是mysql
//函数介绍
func RegisterDriver(driverName string, typ DriverType) error
数据库连接
	//Conn 格式: root:weibao123@tcp(127.0.0.1:3306)/data?charset=utf8
	//           用户名:密码@tcp(ip:端口)/数据库名称?charset=utf-8
这些参数都可以通过配置文件读取到。
可以通过函数   beego.AppConfig.String("配置文件中的名字") 来获取到相关相关的数据
下边就是获取的配置文件中的数据的例子:
配置文件中:
# mysql配置
driverName = mysql
userName = root
passWord = weibao123
host = localhost
post = 3306
dbname = Test			//数据库名称
model中的
driverName:=beego.AppConfig.String("driverName") 
user := beego.AppConfig.String("userName")
pwd := beego.AppConfig.String("passWord")
host:= beego.AppConfig.String("host")
post :=beego.AppConfig.String("post")
dbname := beego.AppConfig.String("dbname")
//连接拼接成Conn的格式
dbConn:= user+":"+pwd+"@tcp("+host+":"+post+ ")/"+dbname+"?charset=utf8"

//连接数据库
err := orm.RegisterDataBase("default",driverName,dbConn)   //返回值是error类型的
// 第一个参数 别名(一般为default) 第二个参数是数据库类型 第三个参数是数据库的连接

用ORM实现结构体与数据表的映射

结构体:

type User struct {
    
	Id int
	Username string
	Password string
}

用ORM映射:

//映射model数据  相当于把结构体中的属性映射成表中的属性
orm.RegisterModel(new(User))

生成表

orm.Syncdb("defaule",false,true)
//第一个参数是别名  一般用defaule代替  
 //第二个参数的意思就是重新生成表(如果需要修改表中的属性,就需要改成true)
//第三个参数的意思显示创建过程
orm进行数据库的增删该查
  • 创建ORM对象

    o  := orm.NewOrm()
    
  • 创建结构体对象

    //第一种方式
    user := model.User{
          }
    //第二种方式
    var  user  *model.User
    
  • 赋值

    一般赋值都是前端提供的数据。
    可以通过  this.GetSgtring()获取
    然后赋值
    
  • 插入 查询 删除 // 更新的得先查询,查询完之后才能更新

下边给出一些例子

//插入
func (c *MainController) Registinsert() {
    
	//创建一个orm对象
	o := orm.NewOrm()
	//创建结构体对象
	users := models.User{
    }
	//赋值
	name:=c.GetString("userName")
	pwd:=c.GetString("password")
	beego.Info("数据"+name,pwd)
	if name != ""||pwd!= ""{
    
		users.Username = name
		users.Password = pwd
		err2 := o.Read(&users,"Username")
		if err2 == nil{
        //证明查询成功了,说明已经有数据了,不能插入。
			beego.Info("数据已经存在")
			c.Data["existerror"] = "账号已经存在请修改账号"
			c.TplName = "test.html"
		}
		// 插入
		_, err := o.Insert(&users)
		if err != nil {
    
			beego.Info("插入失败")
			c.Redirect("/regist",302)
			return
		}
		beego.Info("插入成功")
		c.Ctx.ResponseWriter.Write([]byte("插入成功"))

	}else{
    
		beego.Info("账号或密码不能为空")
		c.Data["nilerror"] = "账号或密码不能为空"
		c.TplName = "test.html"
	}
    
    
    //查询
    func (this *MainController) Loginshow(){
    
	name := this.GetString("UserName")
	password := this.GetString("PassWord")
	if name ==""||password ==""{
    
		beego.Info("账号或密码为空!!")
		this.Data["nilerror"] = "账号或密码不能为空"
		this.TplName = "login.html"
		//this.Redirect("/login",302)
		return
	}
	o := orm.NewOrm()
	users := models.User{
    }
	users.Username = name
	users.Password = password
	err := o.Read(&users,"Username","Password")
	if err != nil{
    
		beego.Info("登陆查询失败,账号和密码错误!!")
		this.Data["error"] = "账号或密码错误!!"
		this.TplName = "login.html"
		//this.Redirect("login",302)

		return
	}
	this.Data["name"] = name
	this.TplName = "loginshow.html"
}
    
    
   //删除
    func (this *MainController) Deletecontent(){
    
	id,_ := this.GetInt("id")
	o := orm.NewOrm()
	deletecontent :=models.Content{
    ContentId:id}

	_,err :=o.Delete(&deletecontent,"ContentId")
	if err !=nil{
    
		beego.Info("根据ID删除失败!")
		return
	}
	this.Redirect("/content",302)

}
    
    
    //更新
    func (this *MainController) Updatehandle(){
    
	id,_:=this.GetInt("id")
	acticles :=this.GetString("ArctilesName")
	log:=this.GetString("Log")
	content:=this.GetString("ArctilesContent")
	f,h,err:=this.GetFile("uploadname")
	defer f.Close()
	//限定上传文件格式
	fileext :=path.Ext(h.Filename)			//h.Filename 可以获取到上传文件的名字包裹后缀  path.Ext()可以获取到后缀
	//限定文件大小
	if h.Size > 500000{
    
		beego.Info("上传文件过大")
		return
	}
	//重命名
	filename := time.Now().Format("2006-01-02 15:04:05")+fileext
	if err != nil{
    
		beego.Info("文件上传失败")
		return
	}else{
    
		this.SaveToFile("uploadname","./static/img/"+filename)
	}
	img := "./static/img/"+filename
	o := orm.NewOrm()
	updatecontents := models.Content{
    ContentId:id}
	err2 :=o.Read(&updatecontents)																									//此处是先查询
	if err2 != nil{
    
		beego.Info("Update查询id失败")
		return
	}
	updatecontents.ActicleName = acticles
	updatecontents.Contenttext = content
	updatecontents.Log = log
	updatecontents.Img = img
	_,err1 := o.Update(&updatecontents,"ActicleName","Contenttext","Log","Img")					//更新操作
	if err1 != nil{
       //证明有错误信息,需要返回
		beego.Info("文章提交失败,请重新提交")
		this.Redirect("/updatcontent",302)
		return
	}
	this.Redirect("/content",302)
}

数据库表的关系外健关系

一对一
rel(one)
reverse(one)
一对多
rel(fk)				//forign  key
reverse(mang)
多对多
rel(m2m)		
reverse(mang)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a17633463606/article/details/104162079

智能推荐

MACOM面向CWDM4推出L-PIC技术方案,助力云数据中心和5G光学连接发展_macom技术方案-程序员宅基地

文章浏览阅读316次。MACOM近期宣布推出 MAOT-025402 CWDM4发射器光学组件,这款组件是MACOM面向100Gbps CWDM4的L-PIC(集成有激光器的硅光子集成电路)解决方案的一部分,凭借获得专利的L-PIC,MAOT-025402可与MASC-37053A CDR配合使用,共同构成QSFP28 CWDM4解决方案的完整高速传输路径,从而实现助力和推动云数据中心及5G光学连接的发展。随着数据流..._macom技术方案

看解放军练车,想想自己的技术也该提高下了。-程序员宅基地

文章浏览阅读1.9k次。视频: : 驾校都弱暴了 看看解放军是怎么练车的 http://v.youku.com/v_show/id_XNTYxMzYxNTMy.html 同时找到一个很好听的歌曲: http://music.baidu.com/search?key=%E6%9C%80%E5%90%8E%E4%B8%80%E6%AC%A1%E9%80%89%E6%8B%A9%E6%AD%8C%E8%AF%8D想

Debezium系列之第100篇文章:阶段性详细总结对Debezium使用方式的优化,详细介绍对Debezium集群和Kafka集群做的一系列优化_debezium oracle kafka配置优化-程序员宅基地

文章浏览阅读681次。Debezium系列之第100篇文章:阶段性详细总结对Debezium使用方式的优化,同时优化Kafka集群和Debezium集群_debezium oracle kafka配置优化

旷视产品营销总监吕盟:构建AIoT时代的城市智慧|量子位沙龙回顾-程序员宅基地

文章浏览阅读465次。4月10日,量子位与中关村壹号联合主办的AI+线下沙龙—智慧城市的发展趋势与挑战在中关村壹号举办。旷视产品营销总监吕盟、明略科技COO兼数字城市发展部总经理唐日新、思必驰..._产品营销总监

Java并发编程 - 第三章 Java内存模型_数据依赖分为-程序员宅基地

文章浏览阅读1.3w次。前言:Java 线程之间的通信对程序员完全透明,内存可见性问题很容易困扰 Java 程序员,本章将揭开 Java 内存模型神秘的面纱。一、Java 内存模型的基础1.1 并发编程模型的两个关键问题在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体)。通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息_数据依赖分为

sync___fast_iob-程序员宅基地

文章浏览阅读4.6k次。1. linux下sync命令在busybox-1.14.3中sync命令相关代码非常简单,int sync_main(int argc, char **argv UNUSED_PARAM){ /* coreutils-6.9 compat */ bb_warn_ignoring_args(argc - 1);___fast_iob

随便推点

7 centos 配置sudo权限_Linux(CentOS7)赋予普通用户执行root命令权限(sudo)-程序员宅基地

文章浏览阅读1.1k次。在Linux当中有一些命令只有root账户才能使用,怎么样才能使普通的用户可以执行root权限的命令呢,就是要使用一个名字叫做sudo的命令,下面是对sudo命令的一些介绍sudo是linux系统管理指令,是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具,如halt,reboot,su等等。这样不仅减少了root用户的登录 和管理时间,同样也提高了安全性。sudo不是对shell..._centos sudo 才能执行

前端如何拥有自己的服务器-程序员宅基地

文章浏览阅读7.3k次,点赞9次,收藏25次。本文来自作者 郭方超 在 GitChat 上分享 「前端如何拥有自己的服务器」,「阅读原文」查看交流实录。「文末高能」编辑 | 哈比前言前端开发者需要一直关注浏览器的行为和表现。时间长了免不了要接触到后端的知识、服务器的知识。尤其是在前端技术爆发式发展的当下,前端慢慢的渗透到了更多的领域。比如,使用 express/koa 创建 http 服务,使用 React-Native 开发 Android_前端怎么把项目放到自己的服务器上

qt5.8+vs2015使用Qt5WebEngine搭建环境_qt qt5webengine.lib-程序员宅基地

文章浏览阅读4.1k次。1.项目属性,C/C++,所有选项,附加包含目录新增、$(QTDIR)\include\QtWebEngineWidgets2.项目属性,连接器,输入,新增Qt5WebEngine.libQt5WebEngineWidgets.libQt5WebChannel.lib_qt qt5webengine.lib

测度论与概率论笔记5:测度空间上的积分(下)_lebesgue-stieltjes积分-程序员宅基地

文章浏览阅读1.7k次。内容摘要:1. Lebesgue积分和Lebesgue-Stietjes积分 2. 随机变量期望的严格定义与计算_lebesgue-stieltjes积分

CodeForces入门-程序员宅基地

文章浏览阅读5.1k次,点赞2次,收藏17次。codeforces的正确打开方式https://www.cnblogs.com/muzu/p/7616746.html1.背景可能很多人都久闻codeforces网站的大名,却苦于各种各样的区域性问题或玄学问题,没能真正地体验到cf所带来的极致魅力而网络上关于这方面的博文太少了(至少我没找到过),于是就写了这样的一篇博文2.关于codeforces...

algorithm头文件函数全集——史上最全,最贴心-程序员宅基地

文章浏览阅读7.3w次,点赞816次,收藏3.7k次。  不知大家可否遇到这种情况:一道题想出思路后,一般都习惯性的百度一下有没有“现成的函数”可以调用,往往收获不菲,增长很多奇奇怪怪的知识。而这些“现成的函数”大部分都来自于algorithm头文件。简直是我等懒癌的福音啊!  当然,也有很多朋友喜欢自己写函数。因人而异吧,哈哈  为了更方便、易懂, 笔者将每个函数的用法都注释在了代码中,想要验证或加深印象直接复制到编译器里就可以运行。代码:..._algorithm头文件

推荐文章

热门文章

相关标签