Golang基础笔记

Go笔记

一、注释

  1. 单行注释

    //单行注释
    
  2. 多行注释

    /*
    	这是多行注释
    	这是一个main函数,这个是go语言启动的入口
    */
    

二、变量

  1. 声明变量使用var关键字:

    var name type
    var 变量名 变量类型
    
    1. 第一个var是声明变量的关键字

    2. 第二个name,就是我们的变量名字

    3. 第三个type,就是用来代表变量的类型

    4. 命名规则驼峰命名,ex:userInfo

      var name string
      var age int
      var {//定义多个变量
      	name string //默认值 空
      	age int	//默认值 0
      	addres string
      }
      
  2. 短变量声明并初始化

    // := 自动推导
    name := "Jay"
    age := 18
    fmt.Printf("%T,%T", name, age) //查看变量类型
    
    1. 定义变量同时显示初始化
    2. 不能提供数据类型
    3. 只能在函数内部,不能随便到处定义
  3. 打印变量内存地址

    var num int
    num = 100
    fmt.Printf("num:%d,内存地址:%p", num, &num) //取地址符, &变量名
    
  4. 变量的交换

    var a int = 100
    var b int = 200
    b, a = a, b
    fmt.Println(a, b)
    
  5. 匿名变量

    func main() {
    	a, _ := test() //匿名变量_
    	_, b := test() //匿名变量_
    	fmt.Println(a)
    	fmt.Println(b)
    }
    func test() (int, int) {
    	return 100, 200
    }
    
  6. 变量的作用域

    1. 局部变量

      func main() {
      	var a int = 100 //局部变量
      	var b int = 200 //局部变量
      }
      

      函数体内定义,只能在该函数内使用

    2. 全局变量

      var a int = 100 //全局变量
      var b int = 200 //全局变量
      func main() {
      	
      }
      

      函数体外定义,在整个go文件都可使用

    3. 外面定义了全局变量,函数内部还可以定义局部变量,使用就近原则

三、常量

  1. 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

    const identifer[type] = value
    const URL string = "www.baidu.com" //显示定义
    const URL2 = "www.google.cn"       //隐式定义
    const a, b, c = 3.14, "Jay", false
    
  2. 特殊常量-iota

    package main
    import "fmt"
    func main() {
        iota可以用作枚举值
        const (
                a = iota    //a=0
                b           //b=1
                c           //c=2
                d = "haha"  //haha
                e           //haha
                f = 100     //100
                g           //100
                h = iota    //iota7
                i           //iota8
            )
        const (
            j = iota        //0
            k               //1
            )
            fmt.Println(a, b, c, d, e, f, g, h, i, j, k)
    }
    

    四、数据类型

image-20230531184536399

1.布尔型

布尔型的值只可以是常量true或false;var flag bool = true

package main
import "fmt"
func main() {
	var isFlag bool
	fmt.Println(isFlag) //默认值false
	fmt.Printf("%T,%t", isFlag, isFlag) //bool, false
}
2.整数
序号类型和描述
1uint8无符号8位整型(0到255)
2uint16无符号16位整型(0到65535)
3uint32无符号32位整型(0到4294967295)
4uint64无符号64位整型(0到18446744073709551615)
5int8有符号8位整型(-128到127)
6int16有符号16位整型(-32768到32767)
7int32有符号32位整型(-2147483648到2147483647)
8int64有符号64位整型(-9223372036854775808到9223372036854775807)
package main
import "fmt"
func main() {
	var age int = 18
	fmt.Println(age)
	fmt.Printf("%T,%d\n", age, age)
}
3.浮点数
序号类型和描述
1float32 IEEE-754 32位浮点型数
2float64 IEEE-754 64位浮点型数(默认)
3complex64 32位实数和虚数
4complex128 64位实数和虚数
package main
import "fmt"
func main() {
	var money float64 = 3.145
	fmt.Println(money)
	fmt.Printf("%T,%d\n", age, age) 
	//%f默认保留小数点6位数
	fmt.Printf("%T,%.2f\n", money, money) //%.2f保留2位;丢失精度,四舍五入
}
4.字符串型
package main
import "fmt"
func main() {
	var str string
	str = "hello,Golang"
	fmt.Println(str)
	fmt.Println(str + ",study") //字符串拼接 +
	//转义字符 \"  \n  \t
	fmt.Println(str + "\"study")
	fmt.Printf("%T,%s\n", str, str)
	//编码表ASCII字符码
	//所有中国汉字GBK表
	//全世界的编码表 Unicode编码表
	v1 := 'A'
	v2 := "A"
	v3 := '中'
	fmt.Printf("%T,%d\n", v1, v1) //int32,65
	fmt.Printf("%T,%s\n", v2, v2) //string,A
	fmt.Printf("%T,%d\n", v3, v3) //int32,20013
}

遍历字符串string

package main
import "fmt"
func main() {
	str := "hello,jay"
	fmt.Println(str)
	//获取字符串的长度 len
	fmt.Println("字符串的长度为:", len(str))
	//获取指定的字节,下标从 0 开始
	fmt.Println("字符打印:", str[0]) //输出的是ASCII码表对应的值
	//for循环遍历 string
	for i := 0; i < len(str); i++ {
		fmt.Printf("%c", str[i])
		//fmt.Println(str[i])
	}
	//for range 循环,遍历数组、切片...
	for i, v := range str {
		fmt.Println()
		fmt.Print(i)
		fmt.Printf("%c", v)
		fmt.Print("\t")
	}
}
5.数据类型转换

Go语言不存在隐式类型转换,因此所有的类型转换都必须显示的声明

valueOfTypeB = typeB(valueOfTypeA)
package main
import "fmt"
func main() {
	a := 3
	b := 5.0
	var num byte = 11
	//类型转换
	c := float64(a)
	d := int(b)
	e := int(num)
	f := byte(a)
	fmt.Printf("%T\n", a) //int
	fmt.Printf("%T\n", b) //float64
	fmt.Printf("%T\n", c) //float64
	fmt.Printf("%T\n", d) //int
	fmt.Printf("%T\n", e) //int
	fmt.Printf("%T\n", f) //uint8
}

五、运算符

  1. 算术运算符

    假定A的值10,B的值20

    运算符描述实例
    +相加A + B = 30
    -相减A - B = -10
    *相乘A * B = 200
    /相除B / A = 2
    %求余B % A = 0
    ++自增A++ = 11
    自减A-- = 9
    package main
    import "fmt"
    func main() {
    	var a int = 10
    	var b int = 3
    	fmt.Println(a + b) //13
    	fmt.Println(a - b) //7
    	fmt.Println(a * b) //30
    	fmt.Println(a / b) //3
    	fmt.Println(a % b) //1
    	a++
    	fmt.Println(a) //11
    	a = 100
    	a--
    	fmt.Println(a) //99
    }
    
  2. 关系运算符

    假定A的值10,B的值20

    运算符描述实例
    ==检查两个值是否相等,如果相等返回true否则返回false(A == B)为false
    !=检查两个值是否不相等,如果不相等返回true否则返回false(A != B)为true
    >检查左边值是否大于右边值,如果大于返回true否则返回false(A > B)为false
    <检查左边值是否小于右边值,如果小于返回true否则返回false(A < B)为true
    >=检查左边值是否大于等于右边值,如果大于等于返回true否则返回false(A >= B)为false
    <=检查左边值是否小于等于右边值,如果小于等于返回true否则返回false(A <= B)为true
    package main
    import "fmt"
    func main() {
    	var a int = 11
    	var b int = 10
    	//关系运算符返回布尔值
    	fmt.Println(a == b) //false
    	fmt.Println(a != b) //true
    	fmt.Println(a > b)  //true
    	fmt.Println(a < b)  //false
    	fmt.Println(a >= b) //true
    	fmt.Println(a <= b) //false
    }
    
  3. 逻辑运算符

    假定A的值true,B的值false

    运算符描述实例
    &&逻辑AND运算符,如果两边的操作数都是true,则为true否则为false(A && B) 为false
    ||逻辑OR运算符,如果两边的操作数有一个true,则为true否则为false(A || B) 为true
    逻辑NOT运算符,如果条件为true,则为false否则为true(! A) 为false
    package main
    import "fmt"
    func main() {
    	var a bool = true
    	var b bool = false
    	fmt.Println(a && b) //false
    	fmt.Println(a || b) //true
    	fmt.Println(!b)     //true
    }
    
  4. 位运算符

    假定A为60,B为13

    运算符描述实例
    &按位与运算符&是双目运算符,都是1结果位1,否则为0(A&B)=12
    |按位或运算符|是双目运算符,都是0结果为0,否则为1(A|B)=61
    ^按位异或运算符^是双目运算符,不同则为1,相同则为0(A^B)=49
    &^位清空A &^ B对于每个数值,如果为0则取A对应位上的值,如果为1,则取0(A&^B)=48
    <<左移运算符,高位丢弃,低位补0;移一位等于 *2A<<2=240
    >>右移运算法,移一位等于 /2A>>2=15
    package main
    import "fmt"
    func main() {
    	var a uint = 60
    	var b uint = 13
    	var c uint = 0
    	// 60 0011 1100
    	// 13 0000 1101
    	c = a & b
    	fmt.Printf("%T,二进制%b\n", c, c) //&  0000 1100 
    	c = a | b
    	fmt.Printf("%T,二进制%b\n", c, c) // |  0011 1101
    	c = a ^ b
    	fmt.Printf("%T,二进制%b\n", c, c) // ^  0011 0001
    	c = a &^ b
    	fmt.Printf("%T,二进制%b\n", c, c) // &^ 0011 0000
    	c = a << 2
    	fmt.Printf("%T,二进制%b\n", c, c) // a<<2 1111 0000
    	c = a >> 2
    	fmt.Printf("%T,二进制%b\n", c, c) // a>>2 0000 1111
    }
    
  5. 赋值运算符

    运算符描述
    =简单的赋值运算符,将一个表达式的值赋给一个左值
    +=相加后再赋值 a += b //a = a + b
    -=相减后再赋值 a -= b //a = a - b
    *=相乘后再赋值a *= b //a = a * b
    /=相除后再赋值a /= b //a = a / b
    %=求余后再赋值a %= b // a = a % b
    <<=左移后再赋值
    >>=右移后再赋值
    &=按位与后再赋值
    ^=按位异或后再赋值
    |=按位或后再赋值
  6. 其他运算符

    运算符描述实例
    &返回变量存储地址&a;将给出变量的实际地址
    *指针变量*a;是一个指针变量想
    package main
    import "fmt"
    func main() {
    	var a int = 4
    	var b int32
    	var c float32
    	var ptr *int
    	fmt.Printf("%T\n", a) //int
    	fmt.Printf("%T\n", b) //int32
    	fmt.Printf("%T\n", c) //float32
    	ptr = &a
    	fmt.Printf("%d\n", a)   //4
    	fmt.Printf("%p\n", ptr) //0xc00001c0a8
    	fmt.Printf("%d\n", ptr) //824633835688
    }
    

六、键盘输入输出

package main
import "fmt"
func main() {
	/** 输入
		fmt.Scanf()
		fmt.Scanln()
		fmt.Scan()
	 */
	var x int
	var y float64
	fmt.Println("请输入两个数:1.整数 2.浮点数")
	fmt.Scanln(&x, &y) //指针地址来修改操作变量
	fmt.Println("x:", x)
	fmt.Println("y:", y)
}

七、编码规范

  1. 命名规范

    1. 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象中的public);
    2. 命名如果以小写字母开头,则对包外是不可见的,但是他们整个包的内部是可见并且可用的(像面向对象语言中的private)
  2. 包名:package

    保持package的名字和目录名字保持一致,尽量采取简短、有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

    package model
    package main
    
  3. 文件名

  4. import规范

  5. 错误处理

八、流程控制

  1. 顺序结构

    顺序结构:从上到下,逐行执行。默认的逻辑

  2. 选择结构

    条件满足某些代码才会执行

    • if

      package main
      import "fmt"
      func main() {
      	var score int = 90
      	if score >= 90 && score <= 100 {
      		fmt.Println("A")
      	} else if score >= 80 && score < 90 {
      		fmt.Println("B")
      	} else if score >= 70 && score < 80 {
      		fmt.Println("C")
      	} else if score >= 60 && score < 70 {
      		fmt.Println("D")
      	} else if score >= 0 && score < 60 {
      		fmt.Println("E")
      	} else {
      		fmt.Println("不合格")
      	}
      }
      
      if 布尔表达式1 {
      	if 布尔表达式2 {
      		//在布尔表达式2为true时执行
      	}
      }
      
      package main
      import "fmt"
      func main() {
      	var a, b int
      	var pwd int = 20230303
      	//用户输入
      	fmt.Println("请输入密码:")
      	fmt.Scan(&a)
      	if a == pwd {
      		fmt.Println("请再次输入密码")
      		fmt.Scan(&b)
      		if b == pwd {
      			fmt.Println("恭喜,登录成功")
      		} else {
      			fmt.Println("登录失败了,第二次密码错误")
      		}
      	} else {
      		fmt.Println("登录失败,密码错误")
      	}
      }
      
      
      • switch
      package main
      import "fmt"
      func main() {
      	var score int = 90
      	//匹配规则
      	switch score {
      	case 90:
      		fmt.Println("A")
      		fallthrough //case穿透
      	case 80:
      		fmt.Println("B")
      	case 60, 70:
      		fmt.Println("C")
      	default:
      		fmt.Println("D")
      	}
      } //输出A和B
      
    • select

  3. 循环结构

    循环结构:条件满足某些代码会被反复执行0-N次

    • for

      package main
      import "fmt"
      func main() {
      	//for一个参数都没有,无限循环
      	//计算1-10的和
      	sum := 0
      	for i := 1; i <= 10; i++ {
      		sum += i
      	}
      	fmt.Println(sum)	
      }
      

      练习题

      //习题一:打印一个方阵 5*5
      package main
      import "fmt
      func main() {
      	for i := 0; i < 5; i++ {
      		for j := 0; j < 5; j++ {
      			fmt.Print("*") //打印一排星号
      		}
      		fmt.Println() //每打印一排就换行
      	}
      }
      //习题二:打印九九乘法表
      package main
      import "fmt"
      func main() {
      	for i := 1; i <= 9; i++ {
      		for j := 1; j <= i; j++ {
      			//fmt.Print(j)
      			//fmt.Print("*")
      			//fmt.Print(i)
      			//fmt.Print("=")
      			//fmt.Print(i * j)
      			//fmt.Print("\t")
      			//Printf 格式化输出,也可以进行格式编排
      			//fmt.Printf("%d*%d=%d \t", j, i, j*i)
      			fmt.Print(j, "*", i, "=", j*i, "\t")
      		}
      		fmt.Println()
      	}
      }
      
    • break 结束当前整个循环

      package main
      import "fmt"
      func main() {
      	for i := 1; i < 5; i++ {
      		if i == 3 {
      			break
      		}
      		fmt.Print(i) //输出1,2
      	}
      }
      
    • continue 结束当次循环

      package main
      import "fmt"
      func main() {
      	for i := 1; i < 5; i++ {
      		if i == 3 {
      			continue
      		}
      		fmt.Print(i) //输出1,2,4
      	}
      }
      
    • goto 可以跳出双重循环

      package main
      
      import "fmt"
      
      func main() {
      	test()
      }
      func test() {
      	for i := 0; i < 10; i++ {
      		for j := 0; j < 10; j++ {
      			if i >= 2 && j >= 2 {
      				goto END
      			}
      			fmt.Println(i, j)
      		}
      	}
      END:
      	fmt.Println("END...")
      }
      
    • for range 循环

      package main
      import "fmt"
      func main() {
      	str := "hello,jay"
      	fmt.Println(str)
      	//获取字符串的长度 len
      	fmt.Println("字符串的长度为:", len(str))
      	//获取指定的字节,下标从 0 开始
      	fmt.Println("字符打印:", str[0]) //输出的是ASCII码表对应的值
      	//for循环遍历 string
      	for i := 0; i < len(str); i++ {
      		fmt.Printf("%c", str[i])
      		//fmt.Println(str[i])
      	}
      	
      	//for range 循环,遍历数组、切片...
      	for i, v := range str {
      		fmt.Println()
      		fmt.Print(i)
      		fmt.Printf("%c", v)
      		fmt.Print("\t")
      	}
      }
      

      九、数组/切片/map

1.数组
  1. 数组是相同类型的一组数据的集合,数组一旦定义长度不能修改,数组可以通过下标(索引)来访问元素。

  2. 数组定义的语法:

    var variable_name [size] variable_type
    variable_name:数组名称
    size:数组长度,必须是常量
    variable_type:数组保存元素的类型
    
    省略长度定义数组,会自动推断
    var variable_name = [...] variable_type{value1,value2}
    
    通过索引来定义数组
    var variable_name = [...] variable_type{index0: value1, index2: value2}
    索引(下标)可随意写,没有定义索引的值就是默认值
    
    数组长度len(variable_name)
    最大下标len(variable_name) - 1
    

    实例

    package main
    
    import "fmt"
    
    func main() {
    	test1()
    	test2()
    	test3()
    }
    
    func test1() {
    	var a1 [2]int
    	var a2 [3]string
    	fmt.Printf("a1:%T\n", a1) //a1:[2]int
    	fmt.Printf("a2:%T\n", a2) //a2:[3]string
    	fmt.Printf("a1:%v\n", a1) //a1:[0 0] 默认值0
    	fmt.Printf("a2:%v\n", a2) //a2:[  ] 默认值null
    }
    
    func test2() {
    	var a1 = [2]int{1, 2}
    	fmt.Printf("a1:%v", a1) //[1,2]
    	//数组的初始化,默认长度或省略长度 ... 会自动推断长度
    	var a2 = [...]int{3, 4, 5}
    	fmt.Printf("\na2:%v", a2) //[3,4,5]
    }
    
    func test3() {
    	var a1 = [...]int{0: 1, 2: 3, 4: 5}
    	fmt.Printf("a1:%v", a1) //[1,0,3,0,5]
    }
    

    遍历数组

    package main
    
    import "fmt"
    
    func main() {
    	testPrint()
    	testPrint1()
    	testPrint2()
    }
    
    func testPrint() {
    	var a1 = [3]int{1, 2, 3}
    	for i := 0; i < len(a1); i++ {
    		fmt.Println(a1[i]) //1 2 3
    	}
    }
    
    func testPrint1() {
    	var a1 = [4]int{1, 2, 3, 4}
    	for i, v := range a1 {
    		fmt.Printf("a1[%v]: %v \n", i, v) //a1[0]: 1,a1[1]: 2,a1[2]: 3,a1[3]: 4
    	}
    }
    
    func testPrint2() {
    	//匿名变量可以去掉下标
    	var a1 = [4]int{1, 2, 3, 4}
    	for _, v := range a1 {
    		fmt.Println(v) //1 2 3 4
    	}
    }
    
2.切片
  1. Go语言中的切片理解为,可变长度大的数组,其实底层还是数组实现,增加了自动扩容功能,切片(slice)是一个拥有相同类型元素的可变长度的序列。

  2. 切片的语法

    var identifier []type 
    
  3. 切片是引用类型,可以使用make函数类创建切片

    var slice []type = make([]type, len)
    简写
    slice := make([]type, len)
    
  4. 也可以给切片指定容量,其中capacity为可选参数

    make([]T, length, capacity)
    length是数组的长度,也是切片的初始长度
    
  5. 切片实例

    package main
    
    import "fmt"
    
    func main() {
    	testFunc()
    	testFunc1()
    	testFunc2()
    }
    
    func testFunc() {
    	var s1 []int
    	var s2 []string
    	fmt.Printf("s1:%v\n", s1) //s1:[]
    	fmt.Printf("s2:%v\n", s2) //s2:[]
    }
    
    func testFunc1() {
    	var s2 = make([]int, 2)
    	fmt.Printf("s2:%v\n", s2) //s2:[0 0]
    }
    
    func testFunc2() {
    	var s1 = []int{1, 2, 3}
    	fmt.Printf("len(s1):%v\n", len(s1)) //len(s1):3
    	fmt.Printf("cap(s1):%v\n", cap(s1)) //cap(s1):3
    }
    
  6. 切片初始化

    package main
    
    import "fmt"
    
    func main() {
    	sliceFunc()
    }
    
    func sliceFunc() {
    	var s1 = []int{1, 2, 3, 4, 5, 6}
    	s2 := s1[0:3]             //左闭右开区间
    	fmt.Printf("s2:%v\n", s2) //1,2,3
    	s3 := s1[:]
    	fmt.Printf("s3:%v\n", s3) //全部取出
    }
    
  7. 切片的遍历和数组一样

  8. 切片元素的添加、删除、复制

    package main
    
    import "fmt"
    
    func main() {
    	addSlice()
    	deleteSlice()
    	updateSlice()
    	querySlice()
    	copySlice()
    }
    
    func addSlice() {
    	var s1 = []int{}
    	s1 = append(s1, 100)
    	s1 = append(s1, 200)
    	s1 = append(s1, 300)
    	fmt.Printf("s1:%v\n", s1) //s1:[100 200 300]
    }
    
    func deleteSlice() {
    	var s2 = []string{"a", "b", "c", "d", "e"}
    	s2 = append(s2[:2], s2[3:]...) //删除下标2这个元素
    	//公式:a = append(a[:index],a[index+1:]...)
    	fmt.Printf("s2:%v\n", s2) //s2:[a b d e]
    }
    
    func updateSlice() {
    	var s3 = []float64{1.0, 2.0, 3.0, 4.0}
    	s3[1] = 1.1
    	fmt.Printf("s3:%v\n", s3) //s3:[1 1.1 3 4]
    }
    
    func querySlice() {
    	var s4 = []int{1, 2, 3, 4}
    	var key = 2
    	for i, v := range s4 {
    		if v == key {
    			fmt.Printf("i:%v\n", i) //i:1
    			fmt.Printf("v:%v\n", v) //v:2
    		}
    	}
    }
    
    func copySlice() {
    	var s5 = []int{1, 2, 3, 4, 5}
    	var s6 = make([]int, 5)
    	copy(s6, s5)
    	s5[0] = 100
    	fmt.Printf("s5:%v\n", s5) //s5:[100 2 3 4 5]
    	fmt.Printf("s6:%v\n", s6) //s6:[1 2 3 4 5]
    }
    
    3.map
  9. map是一种key:value键值对的数据结构容器。map内部实现是哈希表(hash)。

  10. map最重要的一点是通过key来快速检索数据,key类似于索引,指向数据的值。

  11. map是引用类型的。

  12. map的语法格式

    var map_variable map [key_data_type] value_data_type
    map_variable = make(map [key_data_type] value_data_type)
    

    map_variable:变量名称
    key_data_type:key的数据类型
    value_data_type:值得数据类型

  13. 实例 map是无序的

    package main
    
    import "fmt"
    
    func main() {
    	mapFunc()
    	mapFunc1()
    }
    
    func mapFunc() {
    	//map类型的声明
    	var m1 map[string]string
    	m1 = make(map[string]string)
    	fmt.Printf("m1:%v\n", m1) //m1:map[]
    	fmt.Printf("m1:%T\n", m1) //m1:map[string]string
    }
    
    func mapFunc1() {
    	var m2 = map[string]string{"name": "tom", "age": "20", "email": "tom@gmail.com"}
    	fmt.Printf("m2:%v\n", m2) //m2:map[age:20 email:tom@gmail.com name:tom]
    	m3 := make(map[string]string)
    	m3["name"] = "jack"
    	m3["age"] = "21"
    	m3["email"] = "jack@gmail.com"
    	fmt.Printf("m3:%v\n", m3) //m3:map[age:21 email:jack@gmail.com name:jack]
    }
    
  14. key来取值

    package main
    
    import "fmt"
    
    func main() {
    	mapFunc2()
    }
    
    func mapFunc2() {
    	var m4 = map[string]string{"name": "tom", "age": "20", "email": "tom@gmail.com"}
    	var k1 = "name"
    	var k2 = "age1"
    	v, ok := m4[k1]
    	fmt.Printf("v:%v\n", v)
    	fmt.Printf("ok:%v\n", ok)
    	v1, ok1 := m4[k2]
    	fmt.Printf("v:%v\n", v1)
    	fmt.Printf("ok:%v\n", ok1)
    }
    
  15. 遍历map

    package main
    
    import "fmt"
    
    func main() {
    	mapIterate()
    }
    
    func mapIterate() {
    	var m1 = map[string]string{"name": "tom", "age": "20", "email": "tom@gmail.com"}
    	for k, v := range m1 {
    		fmt.Printf("%v:%v\n", k, v)
    	}
    }
    

十、函数

1.什么是函数
  1. 函数是基本的代码块,用于执行一个任务。
  2. Go语言最少有个main()函数。
  3. 可以通过函数来划分不同功能,逻辑上每个函数执行的指定的任务。
  4. 函数声明告诉了编译器函数的名称,返回类型和参数。
2.函数的声明
  1. Go语言函数定义格式如下:

    func function_name([parameter list]) [return_types] {
    	//函数体
    }
    
    • 无参无返回值函数

    • 有一个参数的函数

    • 有两个参数的函数

    • 有一个返回值的函数

    • 有多个返回值的函数

      package main
      import (
      	"fmt"
      )
      func main() {
      	printInfo()
      	myPrint("有一个参数的函数")
      	//有返回值的函数,需要接收返回值
      	myPrintNum(add1(1, 2))
      	x, y := swap("Jay", "Mikro")
      	fmt.Println(x, y)
      }
      
      // 无参无返回值的函数
      func printInfo() {
      	fmt.Println("无参无返回值函数")
      }
      
      // 有一个参数的函数
      func myPrint(msg string) {
      	fmt.Println(msg)
      }
      
      func myPrintNum(num int) {
      	fmt.Println(num)
      }
      
      // 有两个参数的函数
      func add1(a, b int) int {
      	c := a + b
      	return c
      }
      
      // 有多个返回值的函数
      func swap(x, y string) (string, string) {
      	return y, x
      }
      
    • 函数形式参数和实际参数

      package main
      import "fmt"
      func main() {
      	//形参和实参要一一对应,顺序,个数,类型
      	maxNum := max(10, 15)
      	fmt.Println(maxNum)
      }
      
      // max 两个整数比大小
      // 形式参数:定义函数时,用来接收外部传入数据的参数,就是形式参数
      // 实际参数:实际调用函数时,传给形参的实际数据叫做实际参数
      func max(num1, num2 int) int {
      	var result int
      	if num1 > num2 {
      		result = num1
      	} else {
      		result = num2
      	}
      	//一个函数定义上有返回值,那么函数中必须使用 return 语句
      	//返回值,调用处需要使用变量接收该结果
      	return result
      }
      
      3.可变参数

概念:一个函数的参数类型确定,但个数不确认,就可以使用可变参数

package main
import "fmt"

func main() {
	getSum(1, 2, 3, 4, 10)
}

// ...可变参数,可以传入多个参数
func getSum(nums ...int) {
	sum := 0
	for i := 0; i < len(nums); i++ {
		sum += nums[i]
	}
	fmt.Println("sum:", sum)
}

注意事项

  1. 如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在列表的最后。
  2. 一个函数的参数列表最多只能有一个可变参数。
4.参数传递
  1. 按照数据的存储特点来分:

    1. 值类型的数据:操作的数据本身,int、string、bool、float64、array、struct…
    2. 引用类型的数据:操作的是数据的地址,slice、map、chan…
  2. 值传递

    package main
    
    import "fmt"
    
    func main() {
    	/**
    		值传递
    		1.arr2 的数据是从 arr1 复制来的,所以是不同的空间
    		2.修改 arr2 并不会影响 arr1
    		3.值传递:传递的是数据的副本,修改数据,对于原始的数据没有影响
    		4.值类型的数据,默认都是值传递,基础类型、array、struct
    	 */
    	//定义一个数组
    	arr := [4]int{1, 2, 3, 4}
    	fmt.Println(arr) //输出[1,2,3,4]
    	//传递,拷贝arr
    	update(arr)
    	fmt.Println("调用修改后的数据:", arr) //输出[1,2,3,4]
    }
    
    func update(arr2 [4]int) {
    	fmt.Println("arr2接收的数据:", arr2) //输出[1,2,3,4]
    	arr2[0] = 100
    	fmt.Println("arr2修改后的数据:", arr2) //输出[100,2,3,4]
    }
    
  3. 引用传递

    变量在内存中是存放在一定的地址上的,修改变量实际是修改变量地址的内存。

    package main
    
    import "fmt"
    
    func main() {
    	//切片,可扩容的数组
    	s1 := []int{1, 2, 3, 4}
    	fmt.Println("默认的数据:", s1) //输出[1,2,3,4]
    	//传入的是引用类型的数据,地址;
    	update2(s1)
    	fmt.Println("调用方法后的数据:", s1) //输出[100,2,3,4]
    }
    func update2(s2 []int) {
    	fmt.Println("传递的数据:", s2) //输出[1,2,3,4]
    	s2[0] = 100
    	fmt.Println("修改后的数据:", s2) //输出[100,2,3,4]
    }
    
5.指针
  1. 类型指针不能进行偏移和运算。

  2. Go语言的指针操作非常简单,&(取地址) 和 *(根据地址取值)。

  3. 指针地址和指针类型

    1. 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行取地址操作。Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如*int
  4. 指针语法

    var var_name *var-type
    var-type为指针类型
    var_name为指针变量名
    *用于指定变量作为一个指针
    
6.作用域
  1. 局部变量:函数内定义的变量,叫做局部变量

  2. 全局变量:函数外部定义的变量,叫做全局变量

  3. 变量遵循就近原则

    package main
    
    import "fmt"
    
    // 全局变量
    var num int = 100
    
    func main() {
    	temp := 100
    	//在 if、for 语句定义的变量,只能在该代码块里面使用
    	if b := 1; b <= 10 {
    		temp := 50
    		fmt.Println(temp) //变量遵循就近原则
    		fmt.Println(b)
    	}
    	fmt.Println(temp)
    	f1()
    	f2()
    }
    func f1() {
    	a := 1
    	fmt.Println(a)
    	fmt.Println(num)
    }
    func f2() {
    	//不能使用其他函数定义的变量
    	//fmt.Println(a)
    	fmt.Println(num)
    }
    
7.递归函数

定义:一个函数自己调用自己,就叫做递归函数。递归十分耗内存

注意:递归函数需要有一个出口,逐渐向出口靠近,没有出口就会形成死循环。

package main

import "fmt"

func main() {
	sum := getSum1(5)
	fmt.Println(sum)
}
func getSum1(n int) int {
	if n == 1 {
		return 1
	}
	return getSum1(n-1) + n
}
8.defer

defer语义:推迟、延迟

在go语言中,使用defer关键字来延迟一个函数或者方法执行。

defer函数或者方法:一个函数或方法的执行被延迟了

  1. 你可以在函数中添加多个defer语句,当函数执行到最后是,这些defer语句会按照逆序执行,最后该函数的返回,特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。
  2. 如果有很多调用defer,那么defer是采用后进先出(栈)模式。
package main

import "fmt"

func main() {
	f("1")
	fmt.Println("2")
	defer f("3") //会被延迟到最后执行
	fmt.Println("4")
	defer f("5")
	fmt.Println("6")
	defer f("7")
	fmt.Println("8")
}
func f(s string) {
	fmt.Println(s)
}

defer函数传递

package main

import "fmt"

func main() {
	a := 10
	fmt.Println("a=", a)
	defer f3(a) //参数就已经传递进去了,在最后执行
	a++
	fmt.Println("end a=", a)
}

func f3(b int) {
	fmt.Println("函数里面的a:", b)
}
9.init函数

Go语言有个特殊的函数init函数,先于main函数执行,实现包级别的一些初始化操作。

init函数特点

  1. init函数先于main函数自动执行,不能被其他函数调用;
  2. init函数没有输入参数,返回值;
  3. 每个包可以有多个init函数;
  4. 包的每个源文件也可以有多个init函数,这点比较特殊;
  5. 同一个包的init执行顺序,Go没有明确定义,编程时要注意程序不要依赖这个执行顺序;
  6. 不同包的init函数按照包的导入的依赖关系决定执行顺序。

Go初始化顺序

初始化顺序:变量初始化–>init–>main()

package main

import "fmt"

var num int = initVar()

func init() {
	fmt.Println("init函数") //2.第二输出
}
func init() {
	fmt.Println("init函数2") //3.第三输出
}
func initVar() int {
	fmt.Println("initVar") //1.最先输出
	return 100
}
func main() {
	fmt.Println("main函数") //4.最后输出
}
10.函数的数据类型

函数的类型:func(参数类型)(返回值类型)

函数也是一种数据类型

11.函数的本质

函数类型的变量

package main

import "fmt"

// func() 本身就是一个数据类型
func main() {
	//函数不加括号,函数就是一个变量
	//f1() 如果加了括号那就成了函数的调用
	fmt.Printf("%T \n", f1) //输出func() | func(int, int) | func(int, int) int
	//定义函数类型的变量
	var f5 func(int, int)
	f5 = f1
	f5(1, 2)
}

// func f1()  {
//
// }

func f1(a, b int) {
	fmt.Println(a, b)
}

//func f1(a,b int) int {
//	 return 0
//}

函数在Go语言中是复合类型,可以看做是一种特殊的变量。

函数名():调用返回结果。

函数名:指向函数体的内存地址,一种特殊类型的指针变量。

12.匿名函数
package main

import "fmt"

func main() {
	func1()
	func2 := func1 //函数本身也是变量
	func2()
	//匿名函数
	func3 := func() {
		fmt.Println("我是匿名func3函数")
	}
	func3()
	func(a, b int) {
		fmt.Println(a, b)
		fmt.Println("我是匿名func4函数")
	}(1, 2)
	num2 := func(a, b int) int {
		return a + b
	}(1, 2)
	fmt.Println(num2)
}

func func1() {
	fmt.Println("我是func1函数")
}

Go语言是支持函数式编程:

  1. 将匿名函数作为另一个函数的参数,回调函数。
  2. 将匿名函数作为另一个函数的返回值,可以形成闭包结构。
13.回调函数

高阶函数:根据Go语言的数据类型的特点,可以将一个函数作为另外一个函数的参数

package main

import "fmt"

func main() {
	sum := add(1, 2)
	fmt.Println(sum)
	sum2 := operate(3, 4, add) // add() 就作为一个回调函数
	fmt.Println(sum2)
	sum3 := operate(5, 6, sub)
	fmt.Println(sum3)
	sum4 := operate(8, 4, func(a, b int) int {
		if b == 0 {
			fmt.Println("被除数不能为0")
			return 0
		}
		return a / b
	})
	fmt.Println(sum4)
}

// 高阶函数,可以接受一个函数作为参数
func operate(a, b int, fun func(int, int) int) int {
	sum := fun(a, b)
	return sum
}

func add(a, b int) int {
	return a + b
}

func sub(a, b int) int {
	return a - b
}
14.闭包
package main

import "fmt"

func main() {
	/*
		一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量
		并且该外层函数的返回值就是这个内层函数。
		这个内层函数和外层函数的局部变量,统称为闭包结构
		局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用二创建,
		随着函数的结束而销毁,但是闭包结构中的外层函数的局部变量并不会随着外层函数
		的结束而销毁,因为内层函数还在继续使用
	*/
	r1 := increment()
	fmt.Println(r1)
	v1 := r1()
	fmt.Println(v1)   //输出1
	v2 := r1()
	fmt.Println(v2)   //输出2
	fmt.Println(r1()) //输出3
	fmt.Println(r1()) //输出4
	fmt.Println(r1()) //输出5
	//再次调用
	r2 := increment()
	v3 := r2()
	fmt.Println(v3) //输出1
	fmt.Println(r2()) //输出2
}

func increment() func() int {
	i := 0
	//定义一个匿名函数,给变量自增并返回
	fun := func() int {
		i++
		return i
	}
	return fun
}

十一、泛型

1.快速入门
  1. 我们不限定类型,让调用者自己去定义类型。

  2. 泛型减少重复代码并提高类型安全性。

  3. 泛型 T占位符;形式类型,内置的泛型类型 anycomparable

  4. any 表示 Go 里面所有的内置基本类型,等价于interface{}

  5. comparable 表示Go 里面所有内置的可比较类型: int、uint、float、bool、struct、指针等一切可比较的类型。

  6. 实例

    package main
    
    import "fmt"
    
    // 内部业务代码一样
    func printStringArray(arr []string) {
    	for _, v := range arr {
    		fmt.Println(v)
    	}
    }
    
    // 内部业务代码一样
    func printIntArray(arr []int) {
    	for _, v := range arr {
    		fmt.Println(v)
    	}
    }
    
    /*func printArray[T string | int | float64](arr []T) {
    	for _, v := range arr {
    		fmt.Println(v)
    	}
    }*/
    
    func printArray[T any](arr []T) {
    	for _, v := range arr {
    		fmt.Println(v)
    	}
    }
    
    func main() {
    	strs := []string{"jay", "peng"}
    	//printStringArray(strs)
    	is := []int{1, 2, 3}
    	fs := []float64{1.1, 2.2, 3.3}
    	//printIntArray(is)
    	printArray(strs)
    	printArray(is)
    	printArray(fs)
    }
    
2.泛型类型
  1. 针对不同的数据类型我们要定义不同的切片或数组,我们使用泛型就能代表这些类型

    type Slice[T int | float64 | float32] []T
    type MyMap[KEY int|string,VALUE float32|float64] map[KEY]VALUE
    

    例子:

    package main
    
    import "fmt"
    
    func main() {
    	type Slice[T int | float64 | float32] []T
    	var a Slice[int] = []int{1, 2, 3, 4}
    	fmt.Println(a)
    	fmt.Printf("a:%T", a)
    	var b Slice[float64] = []float64{1.1, 2.2, 3.3, 4.4}
    	fmt.Println(b)
    	fmt.Printf("b:%T", b)
    	var c Slice[float32] = []float32{1, 2, 3, 4}
    	fmt.Println(c)
    	fmt.Printf("c:%T", c)
    	fmt.Println("\n=========")
    	type MyMap[KEY int | string, VALUE float32 | float64] map[KEY]VALUE
    	var m1 MyMap[string, float64] = map[string]float64{"java": 9.0, "go": 9.6}
    	fmt.Println(m1)
    }
    
  2. 所有类型定义都可使用类型形参,所以结构体以及接口的定义也可以使用类型形参:

    //一个泛型类型的结构体,可以用 int 或 string 类型实例化
    type MyStruct [T int | string] struct {
    	Id T
    	Name string
    }
    //一个泛型接口
    type MyInterface [T float32 | string] interface {
    	Print(data T)
    }
    //一个泛型通道,可用类型实参 int 或 string 实例化
    type MyChan[T int | string] chan T
    
  3. 特殊的泛型类型(理解机制)

    type Wow[T int | string] int
    
    var a Wow[int] = 123
    var b Wow[string] = 123
    var c Wow[string] = "hello" //编译错误,因为 "hello" 不能赋值给底层类型 int
    
    3.泛型函数
  4. 例子

    package main
    
    import "fmt"
    
    type MySlice[T int | float64] []T
    
    // 泛型方法
    func (s MySlice[T]) Sum() T {
    	var sum T
    	for _, v := range s {
    		sum += v
    	}
    	return sum
    }
    
    // 泛型函数
    func Add[T int | float64 | string](a T, b T) T {
    	return a + b
    }
    
    func main() {
    	fmt.Println(Add[int](1, 2))
    	fmt.Println(Add(1, 2)) //自动推导类型
    	fmt.Println(Add[string]("hello", "world"))
    	fmt.Println(Add("hello", "world"))
    	fmt.Println(Add[float64](1.1, 2.2))
    	fmt.Println(Add(1.1, 2.2))
    
    	var i MySlice[int] = []int{1, 2, 3, 4}
    	fmt.Println(i.Sum())
    	var f MySlice[float64] = []float64{1.1, 2.2, 3.3, 4.4}
    	fmt.Println(f.Sum())
    }
    
4.自定义泛型

​ 如果类型太多了怎么办?这时候我们就可以自定义泛型类型

//像声明接口一样声明,叫做泛型类型的约束,自定义约束
type MyInt interface {
   int | int8 | int32 |int64
}

//T的类型为声明的MyInt
func GetMaxNum[T MyInt](a, b T) T{
   if a > b {
      return a
   }
   return b
}

十二、指针

  1. Go预言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。传递数据使用指针,而无须拷贝数据。

  2. 类型指针不能进行偏移和运算。

  3. 一个指针变量指向了一个值的内存地址。

  4. Go语言中的指针操作非常简单,只需记住两个符号:&(取地址)和*(根据地址取值)。

  5. 指针语法

    var var_name *var-type
    var-type:为指针类型
    var_name:为指针变量名
    *:用于指定变量是作为一个指针
    
    package main
    
    import "fmt"
    
    func main() {
    	var ip *int
    	fmt.Printf("ip:%v\n", ip) //ip:<nil>
    	fmt.Printf("ip:%T\n", ip) //ip:*int
    	var i int = 100
    	ip = &i
    	fmt.Printf("ip:%v\n", ip)  //ip:0xc00001c0f0
    	fmt.Printf("ip:%v\n", *ip) //ip:100
    	var sp *string
    	var s string = "hello"
    	sp = &s
    	fmt.Printf("sp:%T\n", sp)  //sp:*string
    	fmt.Printf("sp:%v\n", *sp) //sp:hello
    	var bp *bool
    	var b bool = true
    	bp = &b
    	fmt.Printf("bp:%T\n", bp)  //bp:*bool
    	fmt.Printf("bp:%v\n", *bp) //bp:true
    }
    
  6. 指向数组指针

    1. 定义语法

      var ptr [MAX]*int //表示数组里面的元素类型是指针类型
      
    2. 实例演示

      package main
      
      import "fmt"
      
      func main() {
      	a := [3]int{1, 2, 3}
      	var ap [3]*int
      	fmt.Printf("ap:%v\n", ap) //ap:[<nil> <nil> <nil>]
      	for i := 0; i < len(a); i++ {
      		ap[i] = &a[i] //赋值
      	}
      	fmt.Printf("ap:%v\n", ap) //ap:[0xc000010120 0xc000010128 0xc000010130]
      	for i := 0; i < len(ap); i++ {
      		fmt.Printf("%v \t", *ap[i]) //1 2 3
      	}
      }
      

十三、类型定义和类型别名

  1. 类型定义

    type newType Type
    
  2. 类型别名

    type newType = Type
    
  3. 区别:

    1. 类型定义相当于定义了一个全新的类型,与之前的类型不同,但是类型别名并没有定义一个新的类型,而是使用一个别名来替换之前的类型。
    2. 类型别名只会在代码中存在,在编译完成之后并不会存在该别名。
    3. 因为类型别名和原来的类型是一致的,所以原来类型所拥有的方法,类型别名中也可以调用,但是如果是重新定义的一个类型,那么不可以调用之前的任何方法。
  4. 实例

    package main
    
    import "fmt"
    
    func main() {
    	type MyInt int
    	var i MyInt
    	i = 100
    	fmt.Printf("i:%T,%v\n", i, i) //i:main.MyInt,100
    	type OurInt = int
    	var a OurInt = 100
    	fmt.Printf("a:%T,%v\n", a, a) //a:int,100
    }
    

十四、结构体

1.快速入门
  1. Go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承、多态、组合等特性。

  2. 结构体的定义

    type struct_variable_type struct {
    	member definition;
    	member definition;
    	...
    	member definition;
    }
    

    type: 结构体定义关键字

    struct_variable_type: 结构体类型名称

    struct: 结构体定义关键字

    ==member definition;:==成员定义

  3. 实例

    package main
    
    import "fmt"
    
    func main() {
    	var tom Person
    	tom.id = 101
    	tom.name = "tom"
    	tom.age = 20
    	tom.email = "tom@gmail.com"
    	fmt.Printf("tom:%v\n", tom)   //tom:{101 tom 20 tom@gmail.com}
    	var jack Customer
    	fmt.Printf("jack:%v\n", jack) //jack:{0 0  }
    }
    
    type Person struct { //自定义了一个类型
    	id    int
    	name  string
    	age   int
    	email string
    }
    
    type Customer struct {
    	id, age     int
    	name, email string
    }
    
  4. 匿名结构体

    package main
    
    import "fmt"
    
    func main() {
    	var tom struct { //匿名结构体
    		id   int
    		name string
    		age  int
    	}
    	tom.id = 102
    	tom.age = 20
    	tom.name = "name"
    	fmt.Printf("tom:%v\n", tom)
    }
    
  5. 结构体初始化

    package main
    
    import "fmt"
    
    func main() {
    	type Student struct {
    		id    int
    		name  string
    		age   int
    		email string
    	}
    	var tom Student
    	tom = Student{ //必须和定义的顺序一致
    		102,
    		"tom",
    		23,
    		"tom@gmail.com",
    	}
    	//tom := Student{ //对个别键初始化
    		//id:   103,
    		//name: "tom",
    	//}
    	//tom = Student{ //键值对初始化
    	//	id:    101,
    	//	name:  "tom",
    	//	age:   22,
    	//	email: "tom@qq.com",
    	//}
    	fmt.Printf("tom:%v\n", tom) 
    }
    

    Animal
    shout string
    }

    
    
  6. Go语言可以使用函数来模拟构造函数的功能。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	per, err := NewPerson("name", 20)
    	if err == nil {
    		fmt.Printf("per:%v\n", *per)
    	} else {
    		fmt.Printf("err:%v\n", err)
    	}
    }
    
    type Person4 struct {
    	name string
    	age  int
    }
    
    func NewPerson(name string, age int) (*Person4, error) {
    	if name == "" {
    		return nil, fmt.Errorf("name不能为空")
    	}
    	if age < 0 {
    		return nil, fmt.Errorf("age不能小于0")
    	}
    	return &Person4{name: name, age: age}, nil
    }
    

十八、Go包

  1. 包可以区分命令空间(一个文件夹中不能有两个同名文件),可以更好的管理项目。使用package关键字声明包名称,通常文件夹名称和包名称相同。
  2. 要使用某个包下面的变量或者方法,需要导入该包,要导入从GOPATH开始的包路径。(import)
  3. 一个文件夹下只能有一个package
    1. import后面其实是GOPATH开始的相对目录路径,包括最后一段,但由于一个目录下只能有一个package,所以import一个路径就等于是import了这个路径下的包。
    2. 注意,这里指的是直接包含的Go文件。如果有子目录,那么子目录的父目录是完全两个包。
1.包管理工具

Go mod使用方法

  1. 初始化模块

    go mod init <项目块名称>
    
  2. 依赖关系处理,根据go.mod文件

    go mod tidy
    
  3. 将依赖包复制到项目下的vendor目录

    go mod vendor
    

    如果包被屏蔽(墙),可以使用这个命令,随后使用go build-mod=vendor编译

  4. 显示依赖关系

    go list -m all
    
  5. 显示详细依赖关系

    go list -m -json all
    
  6. 下载依赖

    go mod download [path@version]
    

    [path@version]是必须写的

十九、并发编程

1.协程
  1. Go语言中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Go语言提供了Goroutines作为并发处理操作的一种方式。

  2. 实例

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func showMessage(msg string) {
    	for i := 0; i < 5; i++ {
    		fmt.Println(msg)
    		time.Sleep(time.Millisecond * 100)
    	}
    }
    
    func main() {
    	go showMessage("Java") 
    	go showMessage("Golang")
    	time.Sleep(time.Millisecond * 2000)
    	fmt.Println("main") //主函数退出,程序就结束了
    }
    
2.通道
  1. Go提供了一种通道机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道并提供一种机制来保证同步交换。

  2. 通道由make函数创建,该函数指定chan关键字和通道元素类型。

    Unbuffered := make(chan int) //整型无缓冲通道
    buffered := make(chan int, 10) //整型有缓冲通道
    
  3. 将值发送到通道的代码块需要使用 <- 运算符。

    goroutine1 := make(chan string, 5) //字符串缓冲通道
    goroutine1 <- "hello" //通过通道发送字符串
    data := <- goroutine1 //从通道接收字符串
    
  4. 通道的发送和接收特性

    1. 对于同一个通道,发送操作之间是互斥的,接收操作之间也是互斥的。
    2. 发送操作和接收操作中对元素值的处理都是不可分割的。
    3. 发送操作在完全完成之前会被阻塞,接收操作也是如此。
3.WaitGroup实现同步
package main

import "fmt"

import "sync"

var wp sync.WaitGroup

func showMsg(i int) {
	defer wp.Done() // goroutine 结束登记-1
	fmt.Printf("i:%v\n", i)
}

func main() {
	for i := 0; i < 10; i++ {
		go showMsg(i) 启动一个 goroutine 就+1
		wp.Add(1)
	}
	//主协程
	fmt.Println("end...")
}
4.runtime包
  1. runtime.Gosched() 让出CPU时间片,重新等待安排任务

    package main
    
    import (
    	"fmt"
    	"runtime"
    )
    
    func showInfo(msg string) {
    	for i := 0; i < 2; i++ {
    		fmt.Printf("msg:%v\n", msg)
    	}
    }
    
    func main() {
    	go showInfo("java") //子协程来运行
    	for i := 0; i < 2; i++ {
    		runtime.Gosched() //我有权利执行任务了,让给其他子协层来执行
    		fmt.Println("golang")
    	}
    	fmt.Println("end...")
    }
    
  2. runtime.Goexit() 退出当前协程

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func show() {
    	for i := 0; i < 10; i++ {
    		fmt.Printf("i:%v\n", i)
    		if i >= 5 {
    			runtime.Goexit()
    		}
    	}
    }
    
    func main() {
    	go show()
    	time.Sleep(time.Second)
    }
    
  3. runtime.GOMAXPROCS()

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"time"
    )
    
    func a() {
    	for i := 0; i < 10; i++ {
    		fmt.Println("A:", i)
    	}
    }
    
    func b() {
    	for i := 0; i < 10; i++ {
    		fmt.Println("B:", i)
    	}
    }
    
    func main() {
    	fmt.Printf("runtime.NumCPU():%v\n", runtime.NumCPU())
    	runtime.GOMAXPROCS(2) //设置cpu个数查看交替过程
    	go a()
    	go b()
    	time.Sleep(time.Second)
    }
    
5.Mutex互斥锁
package main

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

var i int = 100

var wg sync.WaitGroup

var lock sync.Mutex

func add() {
	defer wg.Done()
	lock.Lock()
	i += 1
	fmt.Println("addI:", i)
	time.Sleep(time.Millisecond * 10)
	lock.Unlock()
}

func sub() {
	lock.Lock()
	defer wg.Done()
	i -= 1
	fmt.Println("subI:", i)
	time.Sleep(time.Millisecond * 2)
	lock.Unlock()
}

func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		add()
		wg.Add(1)
		sub()
	}
	wg.Wait()
	fmt.Println("endI:", i)
}
6.channel的遍历
  1. for循环+if判断

  2. for range

  3. 实例

    package main
    
    import "fmt"
    
    var c = make(chan int)
    
    func main() {
    	go func() {
    		for i := 0; i < 3; i++ {
    			c <- i
    		}
    	}()
    
    	for { //繁杂
    		v, ok := <-c
    		if ok {
    			fmt.Println("v:", v)
    		} else {
    			break
    		}
    	}
    
    	//for v := range c { //简洁
    	//	fmt.Println("v:", v)
    	//}
    
    	//for i := 0; i < 2; i++ {
    	//	r := <-c
    	//	fmt.Println("r:", r)
    	//}
    }
    
7.select switch
  1. select是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。

  2. 如果有多个case都可以运行,select会随机公平地选出一个执行,其它不会执行。

  3. 如果没有可运行的case语句,且有default语句,那么会执行default的动作。

  4. 如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可运行。

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var chanInt = make(chan int, 0)
    var chanStr = make(chan string)
    
    func main() {
    	go func() {
    		chanInt <- 100
    		chanStr <- "hello"
    		defer close(chanInt)
    		defer close(chanStr)
    	}()
    
    	for {
    		select {
    		case r := <-chanInt:
    			fmt.Println("chanInt:", r)
    		case r := <-chanStr:
    			fmt.Println("chanStr:", r)
    		default:
    			fmt.Println("default...")
    		}
    		time.Sleep(time.Second)
    	}
    }
    
8.Timer
  1. timer就是定时器的意思,可以实现一些定时操作,内部也是通过channel来实现的。

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	//timer := time.NewTimer(time.Second * 2)
    	//fmt.Println(time.Now())
    	//t1 := <-timer.C //阻塞,直到指定时间到达
    	//fmt.Println(t1)
    	//
    	//fmt.Println(time.Now())
    	//timer1 := time.NewTimer(time.Second * 2)
    	//<-timer1.C
    	//fmt.Println(time.Now())
    	//
    	//<- time.After(time.Second*2)
    	//fmt.Println("2s后")
    
    	timer2 := time.NewTimer(time.Second * 2)
    	go func() {
    		<-timer2.C
    		fmt.Println("timer2的定时")
    	}()
    
    	stop := timer2.Stop()
    	if stop {
    		fmt.Println("timer2 is stopped")
    	}
    	
    	time.Sleep(time.Second * 3)
    	fmt.Println("main end...")
    }
    
9.Ticker
  1. ticker可以周期的执行。

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	ticker := time.NewTicker(time.Second)
    	//counter := 1
    	//for _ = range ticker.C {
    	//	fmt.Println("ticker...")
    	//	counter++
    	//	if counter > 5 {
    	//		ticker.Stop()
    	//		break
    	//	}
    	//}
    	chanInt := make(chan int)
    	go func() {
    		for _ = range ticker.C {
    			select {
    			case chanInt <- 1:
    			case chanInt <- 2:
    			case chanInt <- 3:
    			}
    		}
    	}()
    
    	sum := 0
    	for v := range chanInt {
    		fmt.Println("v:", v)
    		sum += v
    		if sum >= 10 {
    			fmt.Println("sum:", sum)
    			break
    		}
    	}
    }
    
10.原子变量
  1. 增减操作

    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    	"time"
    )
    
    var num int64 = 100
    
    func add2() {
    	atomic.AddInt64(&num, 1)
    }
    
    func sub2() {
    	atomic.AddInt64(&num, -1)
    }
    
    func main() {
    	for i := 0; i < 100; i++ {
    		go add2()
    		go sub2()
    	}
    	time.Sleep(time.Second * 2)
    	fmt.Println("num:", num)
    }
    
  2. 载入操作(read)

    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    )
    
    func main() {
    	var i int32 = 100
    	atomic.LoadInt32(&i) //read
        fmt.Println("i:", i) //i: 100
    }
    
  3. 比较并交换

    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    )
    
    func main() {
    	var i int32 = 100
    	b := atomic.CompareAndSwapInt32(&i, 100, 200)
    	fmt.Println("b:", b) //b:true
    	fmt.Println("i:", i) //i:200
    }
    
  4. 存储

    package main
    
    import (
    	"fmt"
    	"sync/atomic"
    )
    
    func main() {
    	var i int32 = 100
    	atomic.StoreInt32(&i, 200) //write
        fmt.Println("i:", i) //i: 200
    }
    

二十、标准库

总览
标准库包名功能简介
bufio带缓冲的 I/O 操作
bytes实现字节操作
container封装堆、列表和环形列表等容器
crypto加密算法
database数据库驱动和接口
debug各种调试文件格式访问及调试功能
encoding常见算法如 JSON、XML、Base64 等
flag命令行解析
fmt格式化操作
goGo 语言的词法、语法树、类型等。可通过这个包进行代码信息提取和修改
htmlHTML 转义及模板系统
image常见图形格式的访问及生成
io实现 I/O 原始访问接口及访问封装
math数学库
net网络库,支持 Socket、HTTP、邮件、RPC、SMTP 等
os操作系统平台不依赖平台操作封装
path兼容各操作系统的路径操作实用函数
pluginGo 1.7 加入的插件系统。支持将代码编译为插件,按需加载
reflect语言反射支持。可以动态获得代码中的类型信息,获取和修改变量的值
regexp正则表达式封装
runtime运行时接口
sort排序接口
strings字符串转换、解析及实用函数
time时间接口
text文本模板及 Token 词法器
1.文件读写
  1. 创建文件

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    // 创建文件
    func createFile() {
    	f, err := os.Create("a.txt")
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("f.Name()", f.Name())
    	}
    }
    
    func main() {
    	createFile()
    }
    
    
  2. 创建文件

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    // 创建目录
    func createDir() {
    	//创建目录
    	/*err := os.Mkdir("test", os.ModePerm)
    	if err != nil {
    		fmt.Println("err", err)
    	}*/
    	//创建根目录和子目录
    	err1 := os.MkdirAll("a/b/c", os.ModePerm)
    	if err1 != nil {
    		fmt.Println("err1:", err1)
    	}
    }
    
    func main() {
    	createDir()
    }
    
    
  3. 删除目录/文件

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    // 删除目录或者文件
    func remove() {
    	/*err := os.Remove("a.txt")
    	if err != nil {
    		fmt.Println("err:", err)
    	}*/
    
    	err := os.RemoveAll("test")
    	if err != nil {
    		fmt.Println("err:", err)
    	}
    }
    
    func main() {
    	remove()
    }
    
  4. 读写文件

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    // 读文件
    func readFile() {
    	file, _ := os.ReadFile("test2.txt")
    	fmt.Println("file:", string(file[:])) //切片
    }
    
    // 写文件
    func writeFile() {
    	os.WriteFile("test2.txt", []byte("hello,world"), os.ModePerm)
    }
    
    func main() {
    	readFile()
    	writeFile()
    }
    
  5. 文件的打开和关闭

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    // 打开关闭文件
    func openClose() {
    	/*open, err := os.Open("test2.txt")
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("open.Name()",open.Name())
    	}
    	open.Close()*/
    	
    	file, err := os.OpenFile("test1.txt", os.O_RDWR|os.O_CREATE, 755)
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("file.name():", file.Name())
    		file.Close()
    	}
    }
    
    func main() {
    	openClose()
    }
    
  6. 读文件

    package main
    
    import (
    	"fmt"
    	"io"
    	"os"
    )
    
    func readOps() {
    	open, _ := os.Open("test1.txt")
    	for {
    		buf := make([]byte, 3) //可调节读取长度
    		n, err := open.Read(buf)
    		//n, _ := open.ReadAt(buf, 3)
    		if err == io.EOF {
    			break
    		}
    		fmt.Println("n:", n)
    		fmt.Println("string(buf):", string(buf))
    	}
    	open.Close()
    }
    
    func main() {
    	readOps()
    }
    
  7. 写文件

    package main
    
    import "os"
    
    func write() {
    	file, _ := os.OpenFile("test1.txt", os.O_RDWR|os.O_APPEND, 0755)
    	file.Write([]byte("hello,world"))
    	file.Close()
    }
    
    func writeString() {
    	file, _ := os.OpenFile("test1.txt", os.O_RDWR|os.O_APPEND, 0755)
    	file.WriteString("hello Java")
    	file.Close()
    }
    
    func writeAt() {
    	file, _ := os.OpenFile("test1.txt", os.O_RDWR, 0755)
    	file.WriteAt([]byte("aaa"), 3)
    	file.Close()
    }
    
    func main() {
    	//write()
    	//writeString()
    	writeAt()
    }
    
  8. 进程操作

    package main
    
    import (
    	"fmt"
    	"os"
    	"time"
    )
    
    func main() {
    	//获得当前正在运行的进程id
    	fmt.Println("os.Getpid():", os.Getpid())
    	//父id
    	fmt.Println("os.Getppid():", os.Getppid())
    	//设置新进程的属性
    	attr := &os.ProcAttr{
    		//files指定新进程继承的活动文件对象
    		//前三个分别为,标准输入,标准输出,标准错误输出
    		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
    		//新进程的环境变量
    		Env: os.Environ(),
    	}
    	//开始一个新进程
    	p, err := os.StartProcess("C:\\Windows\\System32\\notepad.exe", []string{
    		"C:\\Windows\\System32\\notepad.exe", "D:\\a.txt"}, attr)
    	if err != nil {
    		fmt.Println("err:", err)
    	}
    	fmt.Println(p)
    	fmt.Println("进程ID:", p.Pid)
    	//通过进程ID查找进程
    	p2, _ := os.FindProcess(p.Pid)
    	fmt.Println(p2)
    	//等待10秒,执行函数
    	time.AfterFunc(time.Second*10, func() {
    		//向p进程发送退出信号
    		p.Signal(os.Kill)
    	})
    	//等待进程p的退出,返回进程状态
    	ps, _ := p.Wait()
    	fmt.Println(ps.String())
    }
    
  9. 环境相关操作

    package main
    
    import (
    	"fmt"
    	"os"
    )
    
    func main() {
    	//打印所有的环境变量
    	fmt.Println(os.Environ())
    	//打印Go的环境变量
    	goPath := os.Getenv("GOPATH")
    	fmt.Println("goPath:", goPath)
    	//打印Java的环境变量
    	javaHome := os.Getenv("JAVA_HOME")
    	fmt.Println("javaHome:", javaHome)
    	//查找环境变量
    	env, b := os.LookupEnv("GOPATH")
    	if b {
    		fmt.Println("env:", env)
    	}
    	//替换环境变量
    	os.Setenv("NAME","gopher")
    	os.Setenv("BURROW","/user/gopher")
    	fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))
        //清除所有环境变量
    	//os.Clearenv()
    }
    
2.IO
  1. 基本的IO接口

    1. Reader接口

      type Reader interface {
      	Read(p []byte) (n int, err error)
      }
      
    2. Writer接口

      type Writer interface {
      	Write(p []byte) (n int, err error)
      }
      
3.bufio
  1. Writer
  2. Reader
  3. Scanner
4.log
  1. Go语言内置了log包,实现简单的日志服务,通过调用log包的函数,可以实现简单的日志打印功能。

  2. 函数系列作用
    print单纯的打印日志
    panic打印日志,抛出panic异常
    fatal打印日志,强制结束程序(os.Exit(1)),defer函数不会执行
  3. 标准log配置

    1. 默认情况下log只会打印出时间,但是实际情况下我们可能还需要获取文件名,行号等信息,log包提供给我们定制的接口。

    2. log包提供两个标准log配置相关方法

      func Flags() int 		//返回标准log输出配置
      func SetFlags(flag int) //设置标准log输出配置
      
    3. 实例

      package main
      
      import (
      	"fmt"
      	"log"
      	"os"
      )
      
      func init() {
      	log.SetFlags(log.Ldate | log.Ltime | log.Llongfile) //设置格式
      	log.SetPrefix("MyLog: ")                            //设置前缀
      	file, err := os.OpenFile("test1.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0664)
      	if err != nil {
      		log.Fatal("日志文件错误")
      	}
      	log.SetOutput(file)
      }
      
      func main() {
      	log.Println("my log")
      }
      
5.builtin
  1. 这个包提供了一些类型声明、变量、常量声明,还有一些便利函数,这个包不需要导入,这些变量和函数就可以直接使用。
  2. new和make的区别:
    1. make只能用来分配及初始化类型为slice,map,chan的数据;new可以分配任意类型的数据。
    2. new分配返回的是指针,即类型*T;make返回引用,即T。
    3. new分配的空间被清零,make分配后,会进行初始化
6.bytes
  1. bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等。
7.errors
  1. errors包实现了操作错误的函数。语言使用error类型来返回函数执行过程中遇到的错误,如果返回的error值为nil,则表示未遇到错误,否则error会返回一个字符串,用于说明遇到了什么错误。

  2. error结构

    type error interface {
    	Error() string	
    }
    
  3. error可以是任何一个类型,可以包含任意信息,不一定是字符串。

  4. error不一定表示一个错误,可以表示任何信息,例如io包中就用error类型的io.EOF表示数据读取结束。

  5. errors包用法很简单,用New函数,生成一个最简单的error对象。

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func check(s string) (string, error) {
    	if s == "" {
    		err := errors.New("字符串不能为空")
    		return "", err
    	} else {
    		return s, nil
    	}
    }
    
    func main() {
    	s, err := check("hello")
    	if err != nil {
    		fmt.Println(err.Error())
    	} else {
    		fmt.Println(s)
    	}
    }
    
8.sort
  1. sort包提供了排序切片和用户自定义数据集以及相关功能的函数。
  2. sort包主要针对[]int、[]float64、[]string以及其他自定义切片的排序。
9.time
  1. time包提供测量和显示时间的功能。

  2. 在编程中时间戳的应用很广泛,例如在web开发中做cookies有效期,接口加密,Redis中的key有效期等。

  3. Go语言中获取时间戳操作如下:

    func main() {
    	now := time.Now()
    	fmt.Printf("TimeStamp type:%T,TimeStamp:%v", now.Unix(), now.Unix())
    }
    
  4. 时间格式化

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	now := time.Now()
    	fmt.Println(now.Format("2006/01/02 15:03")) 
    }
    
10.json
  1. 这个包可以实现json的编码和解码,就是将json字符串转换为struct,或者将struct转换为json。

  2. 两个核心函数

    1. 将struct编码成json,可以接受任意类型。

      func Marshal(v interface{}) ([]byte, error)
      
      package main
      
      import (
      	"encoding/json"
      	"fmt"
      )
      
      type Person struct {
      	Name  string
      	Age   int
      	Email string
      }
      
      func marshal() {
      	p := Person{
      		Name:  "tom",
      		Age:   20,
      		Email: "tom@gmail.com",
      	}
      	b, _ := json.Marshal(p)
      	fmt.Printf("b:%v\n", string(b))
      }
      
      func main() {
      	marshal()
      }
      
    2. 将json编码成struct结构体。

      func Unmarshal(data []byte, v interface{}) error
      
      package main
      
      import (
      	"encoding/json"
      	"fmt"
      )
      
      type Person struct {
      	Name  string
      	Age   int
      	Email string
      }
      
      func main() {
      	b := []byte(`{"Name":"tom","Age":20,"Email":"tom@gmail.com"}`)
      	var p Person
      	json.Unmarshal(b, &p)
      	fmt.Printf("p:%v\n", p)
      }
      
  3. 两个核心结构体

    1. 从输入流读取并解析json

      type Decoder struct {
      	//contains filtered or unexported fields
      }
      
    2. 写json到输出流

      type Encoder struct {
      	//contains filtered or unexported fields
      }
      
11.xml
  1. 两个核心函数

    1. 将struct编码成xml,可以接收任意类型

      func Marshal(v interface{}) ([]byte, error)
      
    2. 将xml转码成struct结构体

      func Unmarshal(data []byte, v interface{})  error
      
  2. 两个核心结构体

    1. 从输入流读取并解析xml

      type Decoder struct {
      	//contains filtered or unexported fields
      }
      
    2. 写xml到输出流

      type Encoder struct {
      	//contains filtered or unexported fields
      }
      
12.math
  1. 该包包含一些常量和一些有用的数学计算机函数,例如:三角函数、随机数、绝对值、平方根等。

二十一、Mysql数据库

  1. 下载安装MySQL数据库

    //启动MySQL服务 管理员运行cmd
    net start mysql
    //数据库登录
    mysql -u root -p
    //回车输入密码
    
  2. 创建一个go_db数据库

    create databse go_db;
    
  3. 打开数据库

    use go_db;
    
  4. 创建表

    create table user_tbl(
    	id integer primary key auto_increment,
    	username varchar(20),
    	password varchar(20)
    );
    
  5. 添加模拟数据

    INSERT INTO user_tbl(username,password) VALUES("tom","123");
    INSERT INTO user_tbl(username,password) VALUES("kite","456");
    
  6. 安装MySQL驱动

    go get -u github.com/go-sql-driver/mysql //终端路径运行
    
  7. 数据库连接

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    
    // 初始化数据库
    var db *sql.DB
    
    func initDB() (err error) {
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True"
    	db, err = sql.Open("mysql", dsn)
    	if err != nil {
    		return err
    	}
    	return nil
    }
    
    func main() {
    	/*db, err := sql.Open("mysql", "root:jayish@/go_db")
    	if err != nil {
    		panic(err)
    	}
    	// See "Important settings" section.
    	db.SetConnMaxLifetime(time.Minute * 3)
    	db.SetMaxOpenConns(10)
    	db.SetMaxIdleConns(10)
    	fmt.Printf("db:%v\n", db)*/
    	err := initDB()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("连接成功!")
    	}
    }
    
  8. 插入、更新和删除操作都使用Exec方法

    func (db *DB) Exec(query string,args ...interface{}) (Result, error)
    
  9. 插入数据

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    // 初始化数据库
    var db *sql.DB
    
    func initDB() (err error) {
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True"
    	db, err = sql.Open("mysql", dsn)
    	if err != nil {
    		return err
    	}
    	return nil
    }
    
    func insert1() {
    	s := "insert into user_tbl (username,password) values (?,?)"
    	r, err := db.Exec(s, "zhangsan", "zs123")
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		i, _ := r.LastInsertId()
    		fmt.Println("i:", i)
    	}
    }
    
    func insert2(username string, password string) {
    	s := "insert into user_tbl (username,password) values (?,?)"
    	r, err := db.Exec(s, username, password)
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		i, _ := r.LastInsertId()
    		fmt.Println("i:", i)
    	}
    }
    
    func main() {
        err := initDB()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("连接成功!")
    	}
    	//insert1()
    	insert2("李四", "ls123")
    }
    
  10. 查询数据

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    // 初始化数据库
    var db *sql.DB
    
    func initDB() (err error) {
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True"
    	db, err = sql.Open("mysql", dsn)
    	if err != nil {
    		return err
    	}
    	return nil
    }
    
    type user struct {
    	id       int
    	username string
    	password string
    }
    
    func queryOneRow() {
    	s := "select * from user_tbl where id = ?"
    	var u user
    	err := db.QueryRow(s, 1).Scan(&u.id, &u.username, &u.password)
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println(u)
    	}
    }
    
    func queryManyRow() {
    	s := "select * from user_tbl"
    	r, err := db.Query(s)
    	var u user
    	defer r.Close()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		for r.Next() {
    			r.Scan(&u.id, &u.username, &u.password)
    			fmt.Println(u)
    		}
    	}
    }
    
    func main() {
        err := initDB()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("连接成功!")
    	}
    	//queryOneRow()
    	queryManyRow()
    }
    
  11. 更新数据

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    
    // 初始化数据库
    var db *sql.DB
    
    func initDB() (err error) {
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True"
    	db, err = sql.Open("mysql", dsn)
    	if err != nil {
    		return err
    	}
    	return nil
    }
    
    func update() {
    	s := "update user_tbl set username=?,password=? where id=?"
    	r, err := db.Exec(s, "jack", "123456", 2)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		a, _ := r.RowsAffected()
    		fmt.Println(a)
    	}
    }
    
    func main() {
    	err := initDB()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("连接成功!")
    	}
    	update()
    }
    
  12. 删除数据

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    
    // 初始化数据库
    var db *sql.DB
    
    func initDB() (err error) {
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True"
    	db, err = sql.Open("mysql", dsn)
    	if err != nil {
    		return err
    	}
    	return nil
    }
    func delete() {
    	s := "delete from user_tbl where id=?"
    	r, err := db.Exec(s, 1)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		i, _ := r.RowsAffected()
    		fmt.Println(i)
    	}
    }
    
    func main() {
    	err := initDB()
    	if err != nil {
    		fmt.Println("err:", err)
    	} else {
    		fmt.Println("连接成功!")
    	}
    	delete()
    }
    

二十二、MongoDB

1.连接
  1. 下载安装驱动

    https://www.mongodb.com/download-center/community
    

    安装可参考此文章https://www.cnblogs.com/bornforthis/p/17024616.html

  2. 打开客户端

    mongo //在bin目录下打开cmd,MongoDB6.0以上版本需要到官网下载mongosh
    
  3. 创建数据库

    use go_db;
    
  4. 创建集合

    db.createCollection("student");
    
  5. 下载驱动

    go get go.mongodb.org/mongo-driver/mongo
    
  6. 连接MongoDB

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    var client *mongo.Client
    
    func initDb() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
        client = c
    }
    
    func main() {
    	initDb()
    }
    
2.BSON
  1. MongoDB中的JSON文档存储在名为BSON(二进制编码的JSON)的二进制表示中。
  2. 连接MongoDB的Go驱动程序中有两大类型表示BSON数据:DRaw
  3. 类型D家族被用来简洁地构建使用本地Go类型的BSON对象。这对于构造传递给MongoDB的命令特别有用。D类型家族包括四类:
    • D:一个BSON文档。这种类型应该在顺序重要的情况下使用,比如MongoDB命令。
    • M:一张无序的map,它和D是一样的,只是不保持顺序。
    • A:一个BSON数组。
    • E:D里面的一个元素。
  4. Raw类型家族用于验证字节切片。还可以使用Lookup()从原始类型检索单个元素。
3.添加文档
  1. 添加一条数据

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    type Student struct {
    	Name string
    	Age  int
    }
    
    var client1 *mongo.Client
    
    func initDb1() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
    	client1 = c
    }
    
    func insert() {
    	s1 := Student{
    		"tom",
    		20,
    	}
    	collection := client1.Database("go_db").Collection("Student")
    	ior, err := collection.InsertOne(context.TODO(), s1)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(ior.InsertedID)
    	}
    }
    
    func main() {
    	initDb1()
    	insert()
    }
    
  2. 添加多条数据

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    type Student1 struct {
    	Name string
    	Age  int
    }
    
    var client2 *mongo.Client
    
    func initDb2() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
    	client2 = c
    }
    
    func insertMany() {
    	s1 := Student1{
    		"tom",
    		20,
    	}
    	s2 := Student1{
    		"jack",
    		21,
    	}
    	stus := []interface{}{s1, s2}
    	collection := client2.Database("go_db").Collection("Student")
    	many, err := collection.InsertMany(context.TODO(), stus)
    	if err != nil {
    		fmt.Println(err)
    	}
    	fmt.Println(many.InsertedIDs)
    }
    
    func main() {
    	initDb2()
    	insertMany()
    }
    
  3. 查询文档

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/bson"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    var client3 *mongo.Client
    
    func initDb3() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
    	client3 = c
    }
    
    func main() {
    	initDb3()
    	ctx := context.TODO()
    	defer client3.Disconnect(ctx) //关闭
    	collection := client3.Database("go_db").Collection("Student")
    	cur, err := collection.Find(ctx, bson.D{})
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer cur.Close(ctx)
    	for cur.Next(ctx) {
    		var result bson.D
    		cur.Decode(&result)
    		fmt.Println(result.Map())
    	}
    }
    
  4. 更新文档

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/bson"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    var client4 *mongo.Client
    
    func initDb4() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
    	client4 = c
    }
    
    func main() {
    	initDb4()
    	ctx := context.TODO()
    	defer client4.Disconnect(ctx)
    	c := client4.Database("go_db").Collection("Student")
    	update := bson.D{{"$set", bson.D{{"name", "rose"}, {"age", 22}}}}
    	ur, err := c.UpdateMany(ctx, bson.D{{"name", "tom"}}, update)
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Printf("ur.ModifiedCount:%v", ur.ModifiedCount)
    }
    
  5. 删除文档

    package main
    
    import (
    	"context"
    	"fmt"
    	"go.mongodb.org/mongo-driver/bson"
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"log"
    )
    
    var client5 *mongo.Client
    
    func initDb5() {
    	//设置客户端连接配置
    	co := options.Client().ApplyURI("mongodb://localhost:27017")
    	//连接到MongoDB
    	c, err := mongo.Connect(context.TODO(), co)
    	if err != nil {
    		log.Fatal(err)
    	}
    	//检查连接
    	err2 := c.Ping(context.TODO(), nil)
    	if err2 != nil {
    		log.Fatal(err2)
    	}
    	fmt.Println("connect success!")
    	client5 = c
    }
    
    func main() {
    	initDb5()
    	ctx := context.TODO()
    	defer client5.Disconnect(ctx)
    	c := client5.Database("go_db").Collection("Student")
    	dr, err := c.DeleteMany(ctx, bson.D{{"age", 21}})
    	if err != nil {
    		log.Fatal(err)
    	}
    	fmt.Println(dr.DeletedCount)
    }
    

二十三、gorm

1.gorm

1.快速入门
  1. 安装

    go get -u gorm.io/gorm
    go get -u gorm.io/driver/mysql
    
  2. CRUD

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    type Product struct {
    	gorm.Model
    	Code  string
    	Price uint
    }
    
    func create(db *gorm.DB) {
    	//创建表
    	db.AutoMigrate(&Product{})
    }
    
    func insert(db *gorm.DB) {
    	//插入数据
    	p := Product{
    		Code:  "1001",
    		Price: 100,
    	}
    	db.Create(&p)
    }
    
    func query(db *gorm.DB) {
    	//查询数据
    	var product Product
    	db.First(&product, 1)                //查询主键为1的
    	db.First(&product, "code=?", "1001") //查找code字段值为1001的记录
    	fmt.Println(product)
    }
    
    func update(db *gorm.DB) {
    	//更新数据
    	var product Product
    	db.First(&product, 1)
    	db.Model(&product).Update("Price", 200)
    	db.Model(&product).Updates(Product{Price: 1003, Code: "1003"}) //更新多个字段
    }
    
    func delete(db *gorm.DB) {
    	//删除数据 只添加删除标记
    	var product Product
    	db.Delete(&product, 1)
    }
    
    func main() {
        //连接MySQL
    	dsn := "root:jayish@tcp(127.0.0.1:3306)/golang_db?charset=utf8&parseTime=True&loc=Local"
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	if err != nil {
    		panic("failed to connect database")
    	}
    	//create(db)
    	//insert(db)
    	//query(db)
    	//update(db)
    	delete(db)
    }
    

    // 删除 user 时,也删除 user 的 account
    db.Select(“Account”).Delete(&user)

    // 删除 user 时,也删除 user 的 Orders、CreditCards 记录
    db.Select(“Orders”, “CreditCards”).Delete(&user)

    // 删除 user 时,也删除用户所有 has one/many、many2many 记录
    db.Select(clause.Associations).Delete(&user)

    // 删除 user 时,也删除 user 的 account
    db.Select(“Account”).Delete(&users)

    
    
  3. 关联标签

    标签描述
    foreignKey指定外键
    references指定引用
    polymorphic指定多态类型
    polymorphicValue指定多态值、默认表名
    many2many指定连接表表名
    joinForeignKey指定连接表的外键
    joinReferences指定连接表的引用外键
    constraint关系约束,例如:OnUpdateOnDelete
6.预加载
  1. GORM 允许在 Preload 的其它 SQL 中直接加载关系,例如:

    type User struct {
      gorm.Model
      Username string
      Orders   []Order
    }
    
    type Order struct {
      gorm.Model
      UserID uint
      Price  float64
    }
    
    // 查找 user 时预加载相关 Order
    db.Preload("Orders").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4);
    
    db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
    // SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
    // SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
    
  2. Joins 预加载

    Preload 在一个单独查询中加载关联数据。而 Join Preload 会使用 inner join 加载关联数据,例如:

    db.Joins("Company").Joins("Manager").Joins("Account").First(&user, 1)
    db.Joins("Company").Joins("Manager").Joins("Account").First(&user, "users.name = ?", "jinzhu")
    db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})
    

    注意 Join Preload 适用于一对一的关系,例如: has one, belongs to

  3. 预加载全部

    与创建、更新时使用 Select 类似,clause.Associations 也可以和 Preload 一起使用,它可以用来 预加载 全部关联,例如:

    type User struct {
      gorm.Model
      Name       string
      CompanyID  uint
      Company    Company
      Role       Role
      Orders     []Order
    }
    
    db.Preload(clause.Associations).Find(&users)
    

    clause.Associations 不会预加载嵌套的关联,但你可以使用嵌套预加载 例如:

    db.Preload("Orders.OrderItems.Product").Preload(clause.Associations).Find(&users)
    
  4. 带条件的预加载

    GORM 允许带条件的 Preload 关联,类似于内联条件

    // 带条件的预加载 Order
    db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
    
    db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
    // SELECT * FROM users WHERE state = 'active';
    // SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
    
  5. 自定义预加载SQL

    您可以通过 func(db *gorm.DB) *gorm.DB 实现自定义预加载 SQL,例如:

    db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
      return db.Order("orders.amount DESC")
    }).Find(&users)
    // SELECT * FROM users;
    // SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;
    
  6. 嵌套预加载

    db.Preload("Orders.OrderItems.Product").Preload("CreditCard").Find(&users)
    
    // 自定义预加载 `Orders` 的条件
    // 这样,GORM 就不会加载不匹配的 order 记录
    db.Preload("Orders", "state = ?", "paid").Preload("Orders.OrderItems").Find(&users)
    

二十四、Git

1.Git下载与安装
  1. Git是一个开源的分布式控制软件,用来管理项目版本。

  2. 下载Git

    https://git-scm.com/download/win
    
  3. 安装Git (默认选项)

  4. 使用Git

    //右击桌面 Git Bash Here
    git --version
    git --help
    
2.四个工作区域

Git本地有四个工作区域:工作目录(Working Directory)、暂存区(Stage/Index)、资源库(Repository)(Git Directory)git仓库(Remote Directory)。文件在四个区域之间的转换关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BIyBQK9t-1685532074199)(D:\GoogleDownload\Git工作区域.png)]

  • WorkSpace:工作区,就是你平时存放项目代码的地方。
  • Index/Stage:暂存区,用于临时存放你的改动,事实上它只是一个文件,保存即将提交到文件列表信息。
  • Repository:仓库区(或版本库),就是安全存放数据的位置,这里面有你提交的所有版本数据。其中HEAD指向最新放入仓库的版本。
  • Remote:远程仓库,托管代码的服务器,可以简单的任务是你项目组中的一台电脑用于远程数据交换。
3.文件的四种状态

Git的工作流程:

  1. 在工作目录中添加、修改文件;
  2. 将需要进行版本管理的文件放入暂存区域;
  3. 将暂存区域的文件提交到Git仓库。

因此,Git管理的文件有三种状态:已修改(modified)、已暂存(staged)、已提交(committed)

  1. 版本控制就是对文件的版本控制,要对文件进行修改、提交等操作,首先要知道文件当前在什么状态,不然可能会提交了现在还不想提交的文件,或者要提交的文件还没提交上。

  2. Git不关心文件两个版本之间的具体差别,而是关心文件的整体是否有改变,若文件被改变,在添加提交时就生成文件新版本的快照,而判断文件整体是否改变的方法就是用SHA-1算法计算文件的校验和。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ze5ogZdb-1685532074201)(C:\Users\周洪杰\AppData\Roaming\Typora\typora-user-images\image-20230316190210455.png)]

    Untracked:未跟踪,此文件在文件夹中,但并没有加入到git库,不参与版本控制,通过git add状态变为Staged。

    Unmodify:文件以及入库,未修改,即版本库中的文件快照内容与文件夹中完全一致。这种类型的文件有两种去处,如果它被修改,而变为Modified。如果使用git rm移出版本库,则成为Untracked文件。

    Modified:文件已修改,仅仅是修改,并没有进行其他的操作,这个文件也有两个去处,通过git add可进入暂存staged状态,使用git checkout则丢弃修改过,返回到unmodify状态,这个git checkout即从库中取出文件,覆盖当前修改。

    Staged:暂存状态,执行git commit则将修改同步到库中,这时库中的文件和本地文件又变为一致,文件为unmodify状态,执行git reset HEAD filename取消暂存,文件状态为Modified

4.常用命令
git init // 初始化 在工作路径上创建主分支
git clone 地址 // 克隆远程仓库
git clone -b 分支名 地址 // 克隆分支的代码到本地
git status // 查看状态
git add 文件名 // 将某个文件存入暂存区
git checkout -- file // 撤销工作区的修改 例如git checkout -- readMe.txt 将本次readMe.txt在工作区的修改撤销掉
git add b c //把b和c存入暂存区
git add . // 将所有文件提交到暂存区
git add -p 文件名 // 一个文件分多次提交
git stash -u -k // 提交部分文件内容 到仓库 例如本地有3个文件 a b c 只想提交a b到远程仓库 git add a b 然后 git stash -u -k 再然后git commit -m "备注信息" 然后再push push之后 git stash pop 把之前放入堆栈的c拿出来 继续下一波操作
git commit -m "提交的备注信息"  // 提交到仓库
若已经有若干文件放入仓库,再次提交可以不用git add和git commit -m "备注信息" 这2步, 直接用
git commit -am "备注信息" // 将内容放至仓库 也可用git commit -a -m "备注信息"
* git commit中的备注信息尽量完善 养成良好提交习惯 例如 git commit -m "变更(范围):变更的内容"

//初始化版本库,并提交到远程服务器端
mkdir WebApp
cd WebApp
git init //本地初始化
touch README
git add README //添加文件
git commit -m 'first commit'
git remote add origin git@github.com:daixu/WebApp.git
5.配置用户签名
  1. 作用:如果想要将本地的项目提交的远程仓库的话,必须要设置签名。签名的作用就是用来表示用户,以区分不同的开发人员。

  2. 设置方式:

    1. 一种是为单个仓库单独设置,这种方式只针对单个仓库有效;
    2. 另一种是全局配置,采用这种方式配置后,所有仓库都有效。如果对两种方式都进行了配置,那么会优先使用单个仓库配置方式的配置信息。
  3. 配置格式

    方法一:单个仓库有效

    git config user.name //用户名
    git config user.email //邮箱
    

    方法一配置完,信息会保存在当前仓库目录下的.git/config文件中。

    方法二:全局有效

    git config --global user.name //用户名
    git config --global user.email //邮箱
    

    方法二配置完,信息会保存在系统盘的系统用户目录下的.gitconfig文件中。

    一般情况下都是配置成全局有效,不用为每个仓库都设置签名。当需要为某个仓库配置不同的信息时,只需要单独再为这个仓库按照方式配置即可。

6.初始化本地库
  1. 初始化命令

    git init
    
  2. Git目录

    Git目录是为你的项目存储所有历史和元信息的目录,包括所有的对象(commits,trees,blobs,tags),这些对象指向不同的分支。

    每一个项目只能有一个Git目录,这个叫.git目录在项目的根目录下。

Web基础

  1. 搭建一个web服务器

    package main
    
    import (
        "fmt"
        "net/http"
        "strings"
        "log"
    )
    
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
        r.ParseForm()  // 解析参数,默认是不会解析的
        fmt.Println(r.Form)  // 这些信息是输出到服务器端的打印信息
        fmt.Println("path", r.URL.Path)
        fmt.Println("scheme", r.URL.Scheme)
        fmt.Println(r.Form["url_long"])
        for k, v := range r.Form {
            fmt.Println("key:", k)
            fmt.Println("val:", strings.Join(v, ""))
        }
        fmt.Fprintf(w, "Hello astaxie!") // 这个写入到 w 的是输出到客户端的
    }
    
    func main() {
        http.HandleFunc("/", sayhelloName) // 设置访问的路由
        err := http.ListenAndServe(":9090", nil) // 设置监听的端口
        if err != nil {
            log.Fatal("ListenAndServe: ", err)
        }
    }
    

CSS

基础语法
  1. CSS的三种导入方式

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    <!--内部样式-->
        <style>
            h3{
                color: green;
            }
        </style>
        <!--css link-->
        <!--外部样式-->
        <link rel="stylesheet" href="../css/class01.css">
    </head>
    <body>
    <h1>我是一级标题,外部样式</h1>
    <!--行内样式:在标签元素中,编写一个style属性,编写样式即可。-->
    <h2 style="color: aqua">我是二级标题,行内样式</h2>
    <h3>我是三级标题,内部样式</h3>
    </body>
    </html>
    
  2. 三种基本选择器

    1. 标签选择器:选择一类标签;标签{}
    2. 类选择器:选中所有的class属性一致的标签,可以跨标签使用;.类名{}
    3. ID选择器:全局唯一;#id名{}
    4. 优先级:ID > class > 标签
  3. 层次选择器

    1. 后代选择器:在某个元素的后面

      body p{
      	background: green;
      }
      
    2. 子选择器

      body>p{
      	background: green;
      }
      
    3. 相邻兄弟选择器;向下只有一个标签生效

      .active + p{
      	background: green;
      }
      
    4. 通用选择器;向下所有的兄弟标签都生效

      .active~p{
      	background: green;
      }
      
  4. 结构伪类选择器

    ul li:first-child{
        background: green;
    }
    ul li:last-child{
        background: red;
    }
    p:nth-child(2){
        background: blue;
    }
    p:nth-of-type(1){
        background: yellow;
    }
    
  5. 属性选择器

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
      <style>
        .demo a{
          float: left;
          display: block;
          height: 50px;
          width: 50px;
          border-radius: 10px;
          background: green;
          text-align: center;
          color: gainsboro;
          text-decoration: none;
          margin-right:10px;
          font: bold 20px/50px Arial;
        }
        /*a[id]{*/
        /*  background: yellow;*/
        /*}*/
        a[id=first]{
          background: blue;
        }
        /*a[class*=links]{*/
        /*  background: yellow;*/
        /*}*/
        /*a[href^=http]{*/
        /*  background: yellow;*/
        /*}*/
        /*以什么结尾*
        a[href$=pdf]{
          background: yellow;
        }
      </style>
    </head>
    <body>
    <p class="demo">
      <a href="http://www.baidu.com" class="links item first" id="first">1</a>
      <a href="" class="links item active" target="_blank" title="test">2</a>
      <a href="images/123.html" class="links item">3</a>
      <a href="images/123.jpg" class="links item">4</a>
      <a href="images/123.png" class="links item">5</a>
      <a href="abc" class="links item">6</a>
      <a href="/a.pdf" class="links item">7</a>
      <a href="/abc.pdf" class="links item">8</a>
      <a href="a.doc" class="links item">9</a>
      <a href="abc.doc" class="links item last">10</a>
    </p>
    </body>
    </html>
    
    image-20230323103925510
美化网页
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <style>
    body{
        font-family:楷体;
        color: green;
    }
    h1{
        font-size: 50px;
        text-align: center;
        text-decoration: line-through;
    }
    .p1{
        font-weight: bold;
        text-indent: 2em;
    }

    .p2{
        font-weight: bold;
        text-indent: 2em;
    }
    a{
        text-decoration: none;
    }
  </style>
</head>
<body>
<h1>故事介绍</h1>
<p class="p1">
  平静安详的元泱境界,每隔333年,总会有一个神秘而恐怖的异常生物重生,它就是魁拔!魁拔的每一次出现,都会给元泱境界带来巨大的灾难!即便是天界的神族,也在劫难逃。在天地两界各种力量的全力打击下,魁拔一次次被消灭,但又总是按333年的周期重新出现。魁拔纪元1664年,天神经过精确测算后,在第六代魁拔苏醒前一刻对其进行毁灭性打击。但谁都没有想到,由于一个差错导致新一代魁拔成功地逃脱了致命一击。很快,天界魁拔司和地界神圣联盟均探测到了魁拔依然生还的迹象。因此,找到魁拔,彻底消灭魁拔,再一次成了各地热血勇士的终极目标。
</p>
<p class="p2">
  在偏远的兽国窝窝乡,蛮大人和蛮吉每天为取得象征成功和光荣的妖侠纹耀而刻苦修炼,却把他们生活的村庄搅得鸡犬不宁。村民们绞尽脑汁把他们赶走。一天,消灭魁拔的征兵令突然传到窝窝乡,村长趁机怂恿蛮大人和蛮吉从军参战。然而,在这个一切都凭纹耀说话的世界,仅凭蛮大人现有的一块杂牌纹耀,不要说参军,就连住店的资格都没有。受尽歧视的蛮吉和蛮大人决定,混上那艘即将启程去消灭魁拔的巨型战舰,直接挑战魁拔,用热血换取至高的荣誉。
</p>
<p>
  When I wake up in the morning,You are all I see;
</p>
<p>
  When I think about you, And how happy you make me You're everything I wanted;
</p>
  You're everything I need;I look at you and know; That you are all to me.
</p>
<a href="">a标签</a>

</body>
</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJ8JB6LY-1685532074203)(C:\Users\周洪杰\AppData\Roaming\Typora\typora-user-images\image-20230323140941802.png)]

JavaScrip

表单

验证输入
  1. 必填字段

    if len(r.Form["username"][0])==0{
        // 为空的处理
    }
    
  2. 数字

    getint,err:=strconv.Atoi(r.Form.Get("age"))
    if err!=nil{
        // 数字转化出错了,那么可能就不是数字
    }
    
    // 接下来就可以判断这个数字的大小范围了
    if getint >100 {
        // 太大了
    }
    

    正则表达式判断

    if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m {
        return false
    }
    
  3. 中文

    if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
        return false
    }
    
  4. 英文

    if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
        return false
    }
    
  5. 电子邮件地址

    if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, r.Form.Get("email")); !m {
        fmt.Println("no")
    }else{
        fmt.Println("yes")
    }
    
  6. 手机号码

    if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
        return false
    }
    
  7. 下拉菜单

    <select name="fruit">
    <option value="apple">apple</option>
    <option value="pear">pear</option>
    <option value="banana">banana</option>
    </select>
    
    slice:=[]string{"apple","pear","banana"}
    
    v := r.Form.Get("fruit")
    for _, item := range slice {
        if item == v {
            return true
        }
    }
    
    return false
    
  8. 单选按钮

    <input type="radio" name="gender" value="1"><input type="radio" name="gender" value="2">
    slice:=[]string{"1","2"}
    
    for _, v := range slice {
        if v == r.Form.Get("gender") {
            return true
        }
    }
    return false
    
  9. 复选框

    <input type="checkbox" name="interest" value="football">足球
    <input type="checkbox" name="interest" value="basketball">篮球
    <input type="checkbox" name="interest" value="tennis">网球
    
    slice:=[]string{"football","basketball","tennis"}
    a:=Slice_diff(r.Form["interest"],slice)
    if a == nil{
        return true
    }
    
    return false
    

    上面这个函数 Slice_diff 包含在我开源的一个库里面 (操作 slice 和 map 的库)github.com/astaxie/beeku

  10. 日期和时间

    t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.Local)
    fmt.Printf("Go launched at %s\n", t.Local())
    
  11. 身份证号码

    // 验证 15 位身份证,15 位的是全部数字
    if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
        return false
    }
    
    // 验证 18 位身份证,18 位前 17 位为数字,最后一位是校验位,可能为数字或字符 X。
    if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
        return false
    }
    
防止多次提交表单

Go实战Web入门

  1. git做版本控制

    git init .  //git初始化
    git add .  //添加
    git commit -m "content_target" //提交版本 可用中文表示
    
  2. Go API

    go install golang.org/x/tools/cmd/godoc@latest //安装godoc文档
    godoc -http=:6060 //在终端运行此命令
    //在浏览器输入http://localhost:6060/pkg/ 即可实时查看API文档
    

    新手入门必会

  3. 标头

    Content-Type: 响应标头是告知客户端内容的类型,客户端再根据这个信息将内容正确地呈现给用户。

    常见的内容类型有:

    • text/html —— HTML 文档
    • text/plain —— 文本内容
    • text/css—— CSS 样式文件
    • text/javascript —— JS 脚本文件
    • application/json—— JSON 格式的数据
    • application/xml —— XML 格式的数据
    • image/png —— PNG 图片
     w.Header().Set("Content-Type", "text/html; charset=utf-8")
    
  4. Web数据响应

    Web 的响应与请求结构是类似的,响应分为三个部分:响应行、响应头部、响应体。

    1. 响应行:协议、响应状态码和状态描述,如: HTTP/1.1 200 OK
    2. 响应标头:包含各种头部字段信息,如 cookie,Content-Type 等头部信息。
    3. 响应体:携带客户端想要的数据,格式与编码由头部的 Content-Type 决定。

    响应状态码的有固定取值和意义:

    • 100~199:表示服务端成功接收客户端请求,要求客户端继续提交下一次请求才能完成整个处理过程。
    • 200~299:表示服务端成功接收请求并已完成整个处理过程。最常用就是:200
    • 300~399:为完成请求,客户端需进一步细化请求。比较常用的如:客户端请求的资源已经移动一个新地址使用 302 表示将资源重定向,客户端请求的资源未发生改变,使用 304,告诉客户端从本地缓存中获取。
    • 400~499:客户端的请求有错误,如:404 表示你请求的资源在 web 服务器中找不到,403 表示服务器拒绝客户端的访问,一般是权限不够。
    • 500~599:服务器端出现错误,最常用的是:500
  5. Go Modules

    go env -w  GOPROXY=https://goproxy.cn,direct
    
    命令作用
    go mod init生成 go.mod 文件
    go mod download下载 go.mod 文件中指明的所有依赖
    go mod tidy整理现有的依赖
    go mod graph查看现有的依赖结构
    go mod edit编辑 go.mod 文件
    go mod vendor导出项目所有的依赖到 vendor 目录
    go mod verify校验一个模块是否被篡改过
    go mod why查看为什么需要依赖某模块
  6. st

打包部署

Linux
  1. linux操作命令

    mkdir [-mp] 目录名称
    rm -rf FileName 删除文件
    ps -ef|grep nginx 查询nginx状态
    kill -9 pid 杀死进程
    netstat -anop|grep 运行名称 查看运行项目的信息
    nohup  始终运行项目
    
  2. Nginx目录 /usr/local/webserver/nginx/sbin

  3. 存放web项目路径 /data/www/

Mac
  1. 根目录使用下面的指令可以静态编译 Linux 平台 amd64 架构的可执行文件。Mac 或 Linux 系统:

    CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o goblog
    
Windows
  1. 交叉编译成Linux可执行的二进制文件

    go env -w CGO_ENABLED=0
    go env -w GOOS=linux
    go env -w GOARCH=amd64
    go build -o name
    

    按顺序执行:

    • CGO_ENABLED :设置是否在 Go 代码中调用 C 代码。0 为关闭,采用纯静态编译;
    • GOOS : 目标操作系统
    • GOARCH : 目标操作系统的架构
    • -o 是产出的目标文件名称
  2. 上传到服务器的/data/www/目录下

  3. 给项目二进制文件赋予执行权限 chmod +x filename

  4. 长期运行项目命令 nohup ./jayblog_linux & 会输出日志文件;nohup command &

  5. 停止项目

    ps aux | grep jayblog_linux
    kill -9 pid
    
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
个人学习golang笔记,从各种教程中总结而来,作为入门参考。目录如下 目录 1. 入门 1 1.1. Hello world 1 1.2. 命令行参数 2 2. 程序结构 3 2.1. 类型 4 2.1.1. 命名类型(named type)与未命名类型(unamed type) 4 2.1.2. 基础类型(underlying type) 4 2.1.3. 可赋值性 5 2.1.4. 类型方法集 6 2.1.5. 类型声明 6 2.2. 变量 8 2.2.1. 变量声明 8 2.2.2. 类型零值 12 2.2.3. 指针 13 2.3. 赋值 17 2.4. 包和文件 17 2.5. 作用域 18 2.6. 语句 19 2.7. 比较运算符 20 2.8. 类型转换 21 2.9. 控制流 23 2.9.1. If 23 2.9.2. Goto 24 2.9.3. For 25 2.9.4. Switch 25 2.9.5. break语句 31 2.9.6. Continue语句 31 3. 基础数据类型 31 3.1. golang类型 31 3.2. Numeric types 32 3.3. 字符串 33 3.3.1. 什么是字符串 33 3.3.2. 字符串底层概念 35 3.3.3. 获取每个字节 38 3.3.4. Rune 39 3.3.5. 字符串的 for range 循环 40 3.3.6. 用字节切片构造字符串 41 3.3.7. 用rune切片构造字符串 42 3.3.8. 字符串的长度 42 3.3.9. 字符串是不可变的 42 3.3.10. UTF8(go圣经) 43 3.4. 常量 45 3.4.1. 常量定义 45 3.4.2. 常量类型 46 3.4.3. Iota 46 4. 组合数据类型 47 4.1. 数组 47 4.1.1. 数组概述 47 4.1.2. 数组的声明 49 4.1.3. 数组的长度 50 4.1.4. 遍历数组 50 4.1.5. 多维数组 51 4.2. 切片 52 4.2.1. 什么是切片 52 4.2.2. 切片概述 55 4.2.3. 创建一个切片 55 4.2.4. 切片遍历 57 4.2.5. 切片的修改 58 4.2.6. 切片的长度和容量 60 4.2.7. 追加切片元素 62 4.2.8. 切片的函数传递 65 4.2.9. 多维切片 66 4.2.10. 内存优化 67 4.2.11. nil slice和empty slice 69 4.2.12. For range 70 4.3. 结构 71 4.3.1. 什么是结构体? 71 4.3.2. 结构体声明 73 4.3.3. 结构体初始化 77 4.3.4. 嵌套结构体(Nested Structs) 81 4.3.5. 匿名字段 82 4.3.6. 导出结构体和字段 84 4.3.7. 结构体相等性(Structs Equality) 85 4.4. 指针类型 86 4.5. 函数 87 4.6. map 87 4.6.1. 什么是map 87 4.6.2. 声明、初始化和make 89 4.6.3. 给 map 添加元素 91 4.6.4. 获取 map 中的元素 91 4.6.5. 删除 map 中的元素 92 4.6.6. 获取 map 的长度 92 4.6.7. Map 的相等性 92 4.6.8. map的排序 92 4.7. 接口 93 4.7.1. 什么是接口? 93 4.7.2. 接口的声明与实现 96 4.7.3. 接口的实际用途 97 4.7.4. 接口的内部表示 99 4.7.5. 空接口 102 4.7.6. 类型断言 105 4.7.7. 类型选择(Type Switch) 109 4.7.8. 实现接口:指针接受者与值接受者 112 4.7.9. 实现多个接口 114 4.7.10. 接口的嵌套 116 4.7.11. 接口的零值 119 4.8. Channel 120 4.9. 类型转换 120 5. 函数 120 5.1. 函数的声明 121 5.2. 一个递归函数的例子( recursive functions) 121 5.3. 多返回值 121 5.4. 命名返回值 121 5.5. 可变函数参数 122 5.6. Defer 123 5.6.1. Defer语句介绍 123 5.6.2. Defer使用场景 128 5.7. 什么是头等(第一类)函数? 130 5.8. 匿名函数 130 5.9. 用户自定义的函数类型 132 5.10. 高阶函数(装饰器?) 133 5.10.1. 把函数作为参数,传递给其它函数 134 5.10.2. 在其它函数中返回函数 134 5.11. 闭包 135 5.12. 头等函数的实际用途 137 6. 微服务创建 140 6.1. 使用net/http创建简单的web server 140 6.2. 读写JSON 144 6.2.1. Marshal go结构到JSON 144 6.2.2. Unmarshalling JSON 到Go结构 146 7. 方法 146 7.1. 什么是方法? 146 7.2. 方法示例 146 7.3. 函数和方法区别 148 7.4. 指针接收器与值接收器 153 7.5. 那么什么时候使用指针接收器,什么时候使用值接收器? 155 7.6. 匿名字段的方法 156 7.7. 在方法中使用值接收器 与 在函数中使用值参数 157 7.8. 在方法中使用指针接收器 与 在函数中使用指针参数 159 7.9. 在非结构体上的方法 161 8. 并发入门 162 8.1. 并发是什么? 162 8.2. 并行是什么? 162 8.3. 从技术上看并发和并行 163 8.4. Go 对并发的支持 164 9. Go 协程 164 9.1. Go 协程是什么? 164 9.2. Go 协程相比于线程的优势 164 9.3. 如何启动一个 Go 协程? 165 9.4. 启动多个 Go 协程 167 10. 信道channel 169 10.1. 什么是信道? 169 10.2. 信道的声明 169 10.3. 通过信道进行发送和接收 169 10.4. 发送与接收默认是阻塞的 170 10.5. 信道的代码示例 170 10.6. 信道的另一个示例 173 10.7. 死锁 174 10.8. 单向信道 175 10.9. 关闭信道和使用 for range 遍历信道 176 11. 缓冲信道和工作池(Buffered Channels and Worker Pools) 179 11.1. 什么是缓冲信道? 179 11.2. 死锁 182 11.3. 长度 vs 容量 183 11.4. WaitGroup 184 11.5. 工作池的实现 186 12. Select 188 12.1. 什么是 select? 188 12.2. 示例 189 12.3. select 的应用 190 12.4. 默认情况 190 12.5. 死锁与默认情况 191 12.6. 随机选取 191 12.7. 这下我懂了:空 select 191 13. 文件读写 191 13.1. GoLang几种读文件方式的比较 197 14. 个人 197 14.1. ++,-- 198 14.2. 逗号 198 14.3. 未使用的变量 199 14.4. Effective go 199 14.4.1. 指针 vs. 值 199 14.5. 可寻址性-map和slice的区别 201 14.5.1. slice 201 14.5.2. map 202 14.6. golang库 203 14.6.1. unicode/utf8包 203 14.6.2. time包 205 14.6.3. Strings包 205 14.6.4. 输入输出 212 14.6.5. 正则处理 224 14.6.6. Golang内建函数 226

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值