Go基础进阶语法

反射机制

反射reflect

反射可以在运行时动态获取各种变量的信息,比如变量的类型和值等
如果是结构体还可以获取到结构体本身的各种信息,比如结构体的字段和方法
通过反射还可以修改变量的值。

为什么要用反射
需要反射的 2 个常见场景:

  1. 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  2. 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

但是对于反射,还是有几点不太建议使用反射的理由:

  1. 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var a float64
	a = 3.14
	//通过反射可以获取一个变量的  类型和数值
	fmt.Println(reflect.TypeOf(a))
	fmt.Println(reflect.ValueOf(a))

	//根据反射的值  来获取对应的数值和类型
	v := reflect.ValueOf(a)
	//种类
	if v.Kind() == reflect.Float64 { //Kind种类 相当于 struct  Type类型相当于User类 
		fmt.Println("v是一个float64类型")
	}
	fmt.Println("类型是:", v.Type())
	fmt.Println("数值是:", v.Float())
}
反射获取变量的信息
package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Name string
	Age  int
	Sex  string
}

func (user User) Say(msg string) {
	fmt.Println("User说:", msg)
}

func (user User) PrintInfo() {
	fmt.Printf("名字:%s 年龄:%d 性别%s", user.Name, user.Age, user.Sex)
}
func main() {
	user := User{
		Name: "小王",
		Age:  19,
		Sex:  "男",
	}
	reflectGetInfo(user)

}

// 反射获取结构体信息
func reflectGetInfo(input interface{}) {
	getType := reflect.TypeOf(input)
	fmt.Println("类型:", getType.Name()) //类型: User
	fmt.Println("种类:", getType.Kind()) //种类: struct  不管是什么结构,类型都是结构体
	//获取值
	getValue := reflect.ValueOf(input)
	fmt.Println(getValue)

	//获取字段和字段值
	//遍历结构体中字段的个数
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i) //分别得到每一个字段的名称 类型
		fmt.Println(field)

		// Interface  获取对应的值
		value := getValue.Field(i).Interface() //获取得到每一个字段的值
		fmt.Printf("字段名:%s 字段类型: %s 字段数值: %v\n", field.Name, field.Type, value)
	}

	//这里有问题!!!!!!!!(解决  再一个包中main方法外的方法名大写才可以被main内部代码得到)
	for i := 0; i < getType.NumMethod(); i++ {
		method := getType.Method(i)
		fmt.Println(method)
		fmt.Printf("方法名称:%s 方法类型:%v\n", method.Name, method.Type)
	}
}
反射修改对应变量的值
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var num float64 = 2.13
	fmt.Println(num)

	//newValue := reflect.ValueOf(num) // 这个方法所得到的值不能继续修改
	pointer := reflect.ValueOf(&num) //得到上面num的指针
	newValue := pointer.Elem()       // 得到指针的值

	fmt.Println(newValue.Type())
	fmt.Println(newValue.Kind())
	fmt.Println(newValue.CanSet()) //判断是否可以改变

	if newValue.CanSet() {
		newValue.SetFloat(3.14) //设置值的时候一定要设定他的类型
	}
	fmt.Println(num)
}

修改对象字段
package main

import (
   "fmt"
   "reflect"
)

type User1 struct {
   Age  int
   Name string
}

func main() {
   user1 := User1{18, "小王"}

   //得到对象的指针
   value := reflect.ValueOf(&user1)
   //如果value是一个指针的话
   if value.Kind() == reflect.Pointer {
      newValue := value.Elem() //得到指针的值
      if newValue.CanSet() {
         newValue.FieldByName("Name").SetString("小城")
      }
   }
   fmt.Println(user1)
}
通过反射调用方法
package main

import (
	"fmt"
	"reflect"
)

type User2 struct {
	Name string
	Age  int
	Sex  string
}

func (user User2) Say(msg string) {
	fmt.Println("User说:", msg)
}

func (user User2) PrintInfo() {
	fmt.Printf("名字:%s 年龄:%d 性别%s\n", user.Name, user.Age, user.Sex)
}

func func3(i int, s string) string {
	fmt.Println(i, s)
	return s
}
func main() {

	user := User2{
		Name: "小王",
		Age:  18,
		Sex:  "女",
	}
	value := reflect.ValueOf(user)
	//无参方法的调用
	value.MethodByName("PrintInfo").Call(nil) //用反射得到的对象调用方法

	//有参方法的调用
	args := make([]reflect.Value, 1)//创建一个容量为1的切片
	args[0] = reflect.ValueOf("有参函数的调用来了")
	value.MethodByName("Say").Call(args)//找到对应方法的名字 有参数就在call中加入参数

	//有参有返回值的函数调用
	v3 := reflect.ValueOf(func3)
	args2 := make([]reflect.Value, 2)
	args2[0] = reflect.ValueOf(1)
	args2[1] = reflect.ValueOf("hahhaha")
	result := v3.Call(args2)           //call方法返回的切片  result接收返回值
	fmt.Println(result[0].Interface()) //通过切片下标的interface方法获取值
}

Go常用包

包的声明和使用

1.默认模式:导入系统包使用
2.包的别名:可以给导入的系统包前面加别名
3.简便模式:可以在导入的系统包前面加点可以直接使用该包下的方法
4.可以匿名导入:仅让该包执行init函数 在导入的包前面加 “_”

init函数

init函数先于main函数执行,实现包级别的一些初始化操作。
在mian包下导入另一个包时,如果该包下有init函数(没有接着导入包)就会先执行这个包下的init函数,再执行main方法里面的函数。如果在main方法的上面有init函数的话,就会执行导入的包中的init函数,再main方法上面的函数,最后再是main方法里面程序。
在mian包下导入另一个包时,如果该包下有init函数(有接着导入包),就会先执行导入的包下的init函数,再一次类推的找下去,直到最后一个包下没有init函数,再依次向前执行程序,同上。

package main

import (
	"fmt" 
	_ "gomod/lessonPackage/test"// 匿名导入  就会先看里面有没有init函数
)

// init不需要传入参数,也不需要返回值
// 与main方法相比,init没有被声明,因此也不能被调用 (意思就是不能再main方法中用init()的方式调用)
func init() {
	fmt.Println("main========1")
}

// main方法会先执行导入的包下的init文件,如果这个init方法的导的包下还有init就继续先执行后者
// 先执行最后一层导的包文件
func main() {
	//执行这段语句之前先执行导入的包中的函数
	fmt.Println("main")
}

strings包的常用函数
package main

import (
	"fmt"
	"strings"
)

func main() {
	//定义一个字符串
	str := "xiaowang,shishuaige"

	//Contains  会返回bool值  是精准匹配 如2  就是必须要有"ax"这个字符
	fmt.Println(strings.Contains(str, "x"))  //true
	fmt.Println(strings.Contains(str, "ax")) // false

	//ContainsAng  模糊匹配只要有一个就行  以后常用
	fmt.Println(strings.ContainsAny(str, "ax")) //true

	//Count  统计一个字符出现的次数
	fmt.Println(strings.Count(str, "a")) //3

	//HasPrefix  文件以什么开头
	file := "2023#@4.mp4"
	if strings.HasPrefix(file, "2023") {
		fmt.Println(file)
	}
	//HasSuffix  文件以什么结尾
	if strings.HasSuffix(file, ".mp4") {
		fmt.Println("文件是.mp4格式的")
	}

	//Index  寻找指定字符串 第一次 出现的位置,有就返回下标,没有就返回-1
	fmt.Println(strings.Index(str, "a"))

	//IndexAny  寻找指定字符串任意字符 第一次 出现的位置,有就返回下标,没有就返回-1
	fmt.Println(strings.IndexAny(str, "az")) //有a无z  返回的是a的最后一次出现的下标

	//LastIndex  寻找指定字符串 最后一次 出现的位置,有就返回下标,没有就返回-1
	fmt.Println(strings.LastIndex(str, "e"))

	//LastIndexAny  寻找指定字符串任意字符 最后一次 出现的位置,有就返回下标,没有就返回-1
	fmt.Println(strings.LastIndexAny(str, "ez")) //有e无z  返回的是e的最后一次出现的下标

	//Join  将每个字符中间加入指定字符
	str2 := []string{"a", "c", "f", "j"}
	str3 := strings.Join(str2, "-")
	fmt.Println(str3)

	//Split  按照指定符号分割字符串  如果没有找到指定的分割符号  就会直接返回原来字符串的格式
	str4 := strings.Split(str3, "-")
	fmt.Println(str4)

	//Repeat  重复拼接自己
	str5 := strings.Repeat("sx", 3)
	fmt.Println(str5)
	//Replace 替换 n代表替换的次数  n < 0就是所有的都替换
	str6 := strings.Replace(str, "x", "*", -1)
	fmt.Println(str6)
	//ToUpper   字母转大写  ToLower  全部转化成小写
	str7 := strings.ToUpper(str)
	str8 := strings.ToLower(str)
	fmt.Println(str7)
	fmt.Println(str8)

	//截取字符串  str[start, end]  所有都是左闭右开
	str9 := str[0:5]
	fmt.Println(str9)
	str10 := str[5:]
	fmt.Println(str10)

}
strconv常用函数
package main

import (
	"fmt"
	"strconv"
)

func main() {
	str1 := "23"
	//atoi  itoa(int转字符串)
	i, _ := strconv.Atoi(str1)
	fmt.Println(i)
	//将字符串转成bool
	str2 := "true"
	str3, _ := strconv.ParseBool(str2)
	//将bool转换成字符串
	s1 := strconv.FormatBool(str3)
	fmt.Println(s1)

	// 传入字符串  转换的进制数(就是让机器认为你给的数字2进制还是其他的格式)   输出的类型(bitSize)
	s2 := "100"
	i1, _ := strconv.ParseInt(s2, 2, 64)
	fmt.Println(i1) //4  以10进制的格式输出
	//传入一个int64的数  base转换的进制数
	i2 := 200
	str4 := strconv.FormatInt(int64(i2), 2)
	fmt.Println(str4)

	//将字符串转换成int
	i3, _ := strconv.Atoi("100")
	fmt.Printf("%T\n", i3)
	fmt.Println(i3 + 10)

	//将int转换成字符串
	str5 := strconv.Itoa(200)
	fmt.Printf("%T\n", str5)
	fmt.Println(str5)

}
时间与时间戳
时间的普通格式化
func time1() {
	now := time.Now()
	fmt.Println(now)

	year := now.Year()
	month := now.Month()
	day := now.Day()
	hour := now.Hour()
	minute := now.Minute()
	second := now.Second()
	fmt.Printf("%02d-%02d-%02d-%02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

利用Go语言诞生时间格式化
	//时间格式化   格式为Go语言诞生的时间 2006 01 02 13 14 
	//口诀:2006 1234
	now1 := time.Now()
	fmt.Println(now1.Format("2006-01-02 15:04:20"))
	fmt.Println(now1.Format("2006-01-02 03:04:20 PM"))
	fmt.Println(now1.Format("2006-01-02 "))
解析字符串格式的时间
	//解析字符串格式的时间
	now := time.Now()
	fmt.Println(now)
	//时区
	loc, _ := time.LoadLocation("Asia/Shanghai")
	//2006.01.02 15.04.05  必须写这个  
	//让自己定义的时间成为一个时间对象  这样就可以对它进行一些时间的操作
	timeObj, _ := time.ParseInLocation("2006/01/02 15:04:05", "2022/01/03 14:27:27", loc)
	fmt.Println(timeObj)
	fmt.Println(timeObj.Second())
时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总毫秒数。它也被成为Unix时间戳。

	//将时间戳转化为时间对象   有了对象就可以取值
	time1 := time.Unix(timestamp, 0)
	fmt.Println(time1) //输出成时间的格式
	fmt.Println(time1.Year())
随机数
package main
import (
	"fmt"
	"math/rand"
	"time"
)
func main() {
	rand.Seed(time.Now().Unix()) // 种子中必须传入一个数值才可以生成数字
	for i := 0; i < 100; i++ {
		//rand.int 生成的数字很大  不建议使用
		num := rand.Intn(100) //返回100以内的数字[0,99]!   如果是Int 就是非常大的一个数字
		//需求 生成20-29的数字
		// num:=rand.Intn(10) + 20
		fmt.Println("num = ", num)
	}
}
定时器
package main

import (
	"fmt"
	"time"
)

func main() {
	//真正的定时器  
    //Tick里传入的参数是Duration  指的是时间的一个间隔  返回一个时间
	t1 := time.Tick(time.Second) //间隔一秒执行一次
	for t := range t1 {
		fmt.Println(t) //时间间隔一秒就输出当前时间
	}

	//用sleep模拟的定时器
	//执行10次  每一次都休息一秒钟
	for i := 0; i < 10; i++ {
		fmt.Println(time.Now())
		time.Sleep(time.Second)
	}
}

时间运算
package main

import (
	"fmt"
	"time"
)

func main() {
	//时间相加
	now := time.Now()
	latter := now.Add(time.Hour)
	fmt.Println(now)
	fmt.Println(latter)

	//算两个时间的时间差值
	subtime := latter.Sub(now)
	fmt.Println(subtime) //1h

	//比较两个时间是否相等  返回bool
	fmt.Println(now.Equal(now))
	//判断一个时间是不是在他后面   返回 bool
	fmt.Println(now.After(latter))
}

GoIO

获取文件信息
package main

import (
	"fmt"
	"os"
)

func main() {
	//相对路劲 ./lessonGoIO/a.txt
    //返回的是文件  不能对他进行读写操作  只能查看
	fileInfo, err := os.Stat("./lessonGoIO/a.txt")
	if err != nil {
		fmt.Println(err) 
		return
	}
	fmt.Println(fileInfo.Name())
	fmt.Println(fileInfo.Mode())    //文件读写属性
	fmt.Println(fileInfo.IsDir())   //是否是一个文件
	fmt.Println(fileInfo.Size())    //文件的大小
	fmt.Println(fileInfo.ModTime()) //文件修改的时间
	//反射获取文件更加详细的信息
	fmt.Println(fileInfo.Sys())
}

创建目录与文件

package main

import (
	"fmt"
	"os"
)

func main() {
	//os.ModePerm   给文件设置权限 777:  所有操作文件的人都可读可写可执行
	//这种方法只能先找到最后一个文件的前一个文件 如果能找到就创建  没找到就不创建  例如下面的层级目录是不能创建的
	err := os.Mkdir("/Users/dean/GoWorks/src/gomod/lessonGoIO/txtaa", os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}

	//创建多层目录
	err = os.MkdirAll("/Users/dean/GoWorks/src/gomod/lessonGoIO/txtbb/a/a/a", os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("文件创建完毕")

	//RemoveAll  删除一个文件夹下的所有文件
	//Remove     删除一个指定的文件 文件里面不能有其他的东西  
	err2 := os.Remove("/Users/dean/GoWorks/src/gomod/lessonGoIO/txtaa")
	if err2 != nil {
		fmt.Println(err)
		return
	}
	//启动该语句需要注释上面的程序内容
	//创建一个文件  如果此时已经存在该文件,并且文件内还有内容就是重新创建文件  此时文件内容为空
	file, err3 := os.Create("/Users/dean/GoWorks/src/gomod/lessonGoI O/b.txt")
	//此时返回的file是一个指针对象  可以用它来操作指针
	fmt.Println(file.Name())
	fmt.Println(err3)
}

IO读
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {

	//与文件建立连接  返回的是指针对象 可以对文件进行读写操作  Open就是仅仅是打开
	file, err := os.Open("./lessonGoIO/a.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(file.Name())
	//关闭连接
	defer file.Close()

	//对文件进行读操作
	//创建切片 大小为2 容量为1024
	s1 := make([]byte, 2, 1024)
	n, err4 := file.Read(s1) //此时n是读到的个数(这个个数就是[]byte 里面设置的大小)
	fmt.Println(n)

	//将文件中的内容用字符串并以切片的大小 2展示出来
	fmt.Println(string(s1))
	//可以一直打印  当超过文件中字符的大小时就会报错  此时读到了文件末尾 err = io.EOF
	//如果读到的n > 0 时err就为nil (意思就是读到了数据 即使没有装满整个切片)
	//没有读到内容 就返回EOF  (说明没有数据可以读了)
	fmt.Println(err4)
	
	//OpenFile  (文件名,打开方式,文件权限)
	//如果用上面的打开方式不能可读可写 我们就要设置打开方式
	file2, err2 := os.OpenFile("./lessonGoIO/a.txt", os.O_RDONLY|os.O_WRONLY, os.ModePerm)
	if err2 != nil {
		fmt.Println(err2)
		return
	}
	fmt.Println(file2.Name())
	defer file.Close()

}

IO写
package main

import (
	"fmt"
	"os"
)

func main() {
	//向文件中写入数据  O_RDONLY  可读  O_WRONLY  可写  O_APPEND  向文件后面直接追加写入的文件
	file2, err2 := os.OpenFile("./lessonGoIO/a.txt", os.O_RDONLY|os.O_WRONLY|os.O_APPEND, os.ModePerm)
	if err2 != nil {
		fmt.Println(err2)
		return
	}
	defer file2.Close()

	//业务代码写入数据   开发中用切片用的更多
	bs := []byte{66, 67, 68, 67}
	n, err := file2.Write(bs)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(n)

    //这里用的=赋值  因为前面已经都有n 和 err 了
	n, err = file2.WriteString("hahahha,小可爱")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(n)
}

文件复制
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	source := "/Users/dean/GoWorks/src/gomod/lessonGoIO/aaa/王定勇_Java后端开发.png"
	//复制过来的源文件  必须也有文件名
	destination := "/Users/dean/GoWorks/src/gomod/lessonGoIO/xk-copy3.png"
	//copy(source, destination, 1024)
	copy2(destination, source)
}

func copy3(destination, source string) {
	//不建议使用这种创建文件   因为可能一个文件太大  直接把buffer撑爆了  导致内存溢出
	buffer, _ := os.ReadFile(source)
	err := os.WriteFile(destination, buffer, 0777)
	fmt.Println(err)

}

func copy2(destination, source string) {
	//输入文件  输出的file对象
	sourceFile, err := os.Open(source)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer sourceFile.Close()

	//连接输出文件   可能目标文件是不存在的所以使用O_CREATE 不存在的话就创建
	destinationFile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer destinationFile.Close()

	written, _ := io.Copy(destinationFile, sourceFile)
	fmt.Println("文件大小:", written)

}

func copy(source, destination string, bufSize int) {
	//输入文件
	sourceFile, err := os.Open(source)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer sourceFile.Close()

	//连接输出文件   可能目标文件是不存在的所以使用O_CREATE 不存在的话就创建!!!
	destinationFile, err := os.OpenFile(destination, os.O_WRONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer destinationFile.Close()

	//缓冲区
	buf := make([]byte, bufSize)

	for {
		//读取
		n, err := sourceFile.Read(buf)
		if err == io.EOF || n == 0 {
			fmt.Println("文件复制完毕")
			break
		}
		//写出
		_, err = destinationFile.Write(buf[:n])
		if err != nil {
			fmt.Println("写入失败", err)
		}
	}
}

seeker
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {

	file, _ := os.OpenFile("/Users/dean/GoWorks/src/gomod/lessonGoIO/a.txt", os.O_RDWR, os.ModePerm)
	file.Seek(2, io.SeekStart)
	//括号里面的值 是用来存入新读入的值的 有几个数就在offset后面读几个
	buf := []byte{0, 2, 0, 5}
	file.Read(buf)
	fmt.Println(string(buf))

	//当前光比的末尾因该是第六个字母  需要从第七个数字开始读
	file.Seek(2, io.SeekCurrent)
	file.Read(buf)
	fmt.Println(string(buf))

	//光标在文件末尾  再写入字符  相当于 append
	file.Seek(0, io.SeekCurrent)
	file.WriteString("hahahhaha,小可爱")

}

断点续传
 package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
)

func main() {
	//传输的文件
	srcFile := "/Users/dean/GoWorks/src/gomod/lessonGoIO/aaa/王定勇_Java后端开发.png"
	//传输目的地
	destFile := "/Users/dean/GoWorks/src/gomod/lessonGoIO/xk-seek3.png"
	//临时记录文件
	tempFile := "/Users/dean/GoWorks/src/gomod/lessonGoIO/xk-temp.txt"
	//与文件创建连接
	file1, _ := os.Open(srcFile)
	file2, _ := os.OpenFile(destFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
	file3, _ := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
	defer file1.Close()
	defer file2.Close()

	//1、读取临时文件记录的位置
	file3.Seek(0, io.SeekStart)     //找到光标
	buf := make([]byte, 1024, 1024) //创建一个缓冲区间
	n, _ := file3.Read(buf)         //n为读到的个数
	countStr := string(buf[:n])
	fmt.Println("countStr:", countStr) //输出之前临时文件中存入的数据

	//ParseInt(字符串,进制数,类型)   将上面存储的string转换为int类型
	count, _ := strconv.ParseInt(countStr, 10, 64)
	//seek  找到光标位置
	file1.Seek(count, io.SeekStart)
	file2.Seek(count, io.SeekStart)

	bufData := make([]byte, 1024, 1024)
	total := int(count) //记录所有读到的count

	for {
		readnum, err := file1.Read(bufData) //readnum是读到的数据
		if err == io.EOF {
			fmt.Println("数据读取完毕")
			file3.Close()
			os.Remove(tempFile)
			break
		}
		writeNum, err := file2.Write(bufData[:readnum])
		total = total + writeNum //total是还需要写的数据

		//将文件的进度存储到临时文件当中
		file3.Seek(0, io.SeekStart)            //找到临时文件的光标位置
		file3.WriteString(strconv.Itoa(total)) //将之前存储的int类型的总字符数写出来
	}

	//模仿程序异常
	if total > 6000 {
		panic("断电")
	}

}

bufio
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	//连接文件
	file, _ := os.OpenFile("/Users/dean/GoWorks/src/gomod/lessonGoIO/a.txt", os.O_RDWR, os.ModePerm)

	//读文件
	reader := bufio.NewReader(file)

	//缓存区
	buf := make([]byte, 1024)
	//将读的内容放放缓存区
	n, _ := reader.Read(buf)
	fmt.Println(n)               //输出的是字符的大小
	fmt.Println(string(buf[:n])) //将buf里面从0到n的所有字符输出

	/*inputReader := bufio.NewReader(os.Stdin)
	str, _ := inputReader.ReadString('\n') //   \n为最后一个字符  回车键
	fmt.Println("我输出的键盘是", str)*/

	writer := bufio.NewWriter(file) //写文件  返回一个写的对象
	//要开启文件的各种权限   写的内容需要超过buf的大小  这些数据才会写入文件  要不然就flush  强制写入
	writerNum, _ := writer.WriteString("hello")
	fmt.Println(writerNum)
	writer.Flush()
}

遍历目录
package main

import (
	"fmt"
	"os"
)

func main() {
	listDir("/Users/dean/GoWorks/src/gomod", 0)

}
func listDir(filepath string, tabint int) {
	dir := filepath
	tab := "|--"
	for i := 0; i < tabint; i++ {
		tab = "|    " + tab  // 加入一个层级显示
	}
	dirInfor, _ := os.ReadDir(dir)
	for _, file := range dirInfor {
		fileName := dir + "/" + file.Name()//名字为总路径+文件名
		fmt.Printf("%s %s\n", tab, fileName)
		if file.IsDir() {
			//如果file还是一个文件  接着调用函数
			listDir(fileName, tabint+1)
		}

	}
}

Go并发编程

进程 线程 协程

进程:进程就是一个静态的程序执行后成为动态。
线程:好比于一个主程序中方法的执行。
协程:就是主程序和协程一起执行。

Goroutine

规则 :
1、当新的Goroutine开始时,Goroutine调用立即返回。与函数不同,go不等待Goroutine执行结束。
2、当Goroutine被调用,并且Goroutine任何的返回值被忽略之后,go立即执行到下一行代码。(例如一个方法前面加go, 程序就不会等待这个方法结束,自己会接着向下执行其他的代码)
3、main的Goroutine应该为其他的Goroutines执行,如果main的Goroutine终止了,程序将被终止,其他的Goroutine将不会执行。

Goroutine
	package main
import (
	"fmt"
	"runtime"
)
func main() {
	fmt.Println(runtime.GOROOT()) // 获取goroot目录/usr/local/go
	fmt.Println(runtime.GOOS)     //获取操作系统信息 darwin
	fmt.Println(runtime.NumCPU())  //获取cpu的个数
	go hello() //再函数前面加go就可以实现
	for i := 0; i < 100; i++ {
		fmt.Println("========main", i)
	}
}

func hello() {
	for i := 0; i < 100; i++ {
		runtime.Gosched() 	// 让出时间片    让其他的代码先执行
		fmt.Println("======hello", i)

	}
}
Gorountine调度与终止

runtime.Gosched() // 让出时间片 让其他的代码先执行
return 终止函数
runtime.Goexit() 终止当前的Goroutine

package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {

	go func() {
		fmt.Println("======main")
		test()
		fmt.Println("======end")
	}()
	time.Sleep(time.Second * 2)
}
func test() {
	defer fmt.Println("test  defer")
	//return //终止函数(本代码块的后面代码不执行)
	runtime.Goexit() //终止协程  让该语句后面的程序都不执行
	fmt.Println("test====")
}
临界资源安全问题

同时被进程,线程,协程抢用的资源就是临界资源

package main

import (
	"fmt"
	"time"
)

func main() {
	a := 2
	go func() {
		a = 3
		fmt.Println("a:", a) //a被赋值为3
	}()
	a = 4
	fmt.Println("a:", a) //本来a=4  再主程序睡眠等待 协程(goroutine)里面的程序执行
	time.Sleep(3 * time.Second)
	fmt.Println("a:", a) //这时a资源被协程抢夺  a为3
}

package main

import (
	"fmt"
	"time"
)

var ticket int = 10  //定义总票数 
func main() {
	go sale("售票口1") 
	//go sale("售票口2")
	//go sale("售票口3")
	//go sale("售票口4")

	time.Sleep(time.Second * 5)  //需要让主程序睡一下  要不然太快协程还没有执行
}
func sale(name string) {
	for {
		if ticket > 0 {
			time.Sleep(time.Millisecond * 500)
			fmt.Println(name, "剩余票数", ticket)
			ticket--
		} else {
			fmt.Println("卖光了")
			break  //结束循环 
		}
	}
}

互斥锁 和 waitgroup等待组

锁:var mutex sync.Mutex
等待组:var wg2 sync.WaitGroup
Add() 添加协程的个数
Done() 一个协程结束add里面的个数就减一
Wait() 当Add里面的个数减的为1时 程序就自动执行完毕
使用等待组的前提是你已经知道你有几条协程!!

package main

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

// 创建锁
var mutex sync.Mutex
var wg2 sync.WaitGroup
var ticket = 10

func main() {
	wg2.Add(4)
	//当多个协程抢夺一个资源
	go saleTickets("收票口1")
	go saleTickets("收票口2")
	go saleTickets("收票口3")
	go saleTickets("收票口4")
	wg2.Wait()
	//让住程序多睡一会 ,要不然会出现 资源还没分配完  程序就结束的问题
	//time.Sleep(time.Second * 10)

}

// 售票函数
func saleTickets(name string) {
	defer wg2.Done()
	for {
		//打开锁 每个程序调用是会看一看 有没有人正在用这个资源  必须在for循环执行之后加锁
		mutex.Lock()
		if ticket > 0 {
			time.Sleep(time.Millisecond * 200)
			fmt.Println(name, "剩余票数:", ticket)
			ticket--
		} else {
			//操作完毕解锁
			mutex.Unlock()
			fmt.Println("票卖光了")
			break //跳出循环
		}
		//操作完毕解锁   必须在for循环结束之前解锁
		mutex.Unlock()
	}
}

channel通道

一个通道发送和接收数据,默认是阻塞的。当一个数据数据被发送到通道时,发送语句被阻塞,直到另一个Goroutine从通道中读取数据
相对的,当通道读取数据时,读取被阻塞,直到另一个Goroutine将数据写入通道
本身channel就是同步的,意味着同一时间,只有一条Goroutine来操作
最后:通道是Goroutine之间的连接,所以通道的发送和接收必须处在不同的Goroutine中
这些通道的特性是帮助Goroutines有效的进行通信,而无需向其他编程语言中非常常见的显示锁和条件变两个

package main

import (
	"fmt"
)

// 不要通过共享内存来通信,要通过通信的方式来共享内存
func main() {

	//定义一个布尔类型的通道
	var ch chan bool
	ch = make(chan bool)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println("test", i)
		}
		//向通道写入数据 表示要结束了
		ch <- true
	}()
	//将通道的数据写入data
	date := <-ch
	fmt.Println(date)

}

关闭通道
package main

import (
	"fmt"
	"time"
)

func main() {
	//定义一个通道
	ch := make(chan int) //make(类型 放的数据类型, 容量)
	//将通道打开并传入值
	go test3(ch)
	/*for {
		time.Sleep(time.Second * 1)
		v, ok := <-ch
		//通道没关闭之前 ok为true  之后为false执行下面语句
		if !ok {
			fmt.Println("通道已经关闭", ok)
			break
		}
        //如果为true 就一直输出通道里面的数据
		fmt.Println("v:", v)  

	}*/
	//更加方便的传入值
	for v := range ch {
		fmt.Println("v:", v)
	}
}

func test3(ch chan int) {
	//写数据到通道
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second * 1)
		ch <- i
	}

	//当for中遍历结束 关闭通道
	close(ch)
}

缓冲通道

当我们遇到一次性要接收很多数据,但可以慢慢处理这些数据时,就可以使用缓冲通道。例如消息队列

package main

import (
	"fmt"
	"strconv"
	"time"
)

func main() {
	ch1 := make(chan int, 4)
	fmt.Println(len(ch1), cap(ch1))

	ch2 := make(chan int, 4)
	fmt.Println(len(ch2), cap(ch2))
	ch2 <- 2
	ch2 <- 3
	//当写入的数据 超过长度时   会报错!!
	fmt.Println(len(ch2), cap(ch2))

	ch3 := make(chan string, 5)
	//test4中是写入10个数据  我们缓存的大小是5
	go test4(ch3)
	//所以咱们先写入5个数据 再开始读出数据
	for v := range ch3 {  //使用forange遍历更加方便
		time.Sleep(time.Second)
		fmt.Println("v:", v)
	}

}

func test4(ch chan string) {
	for i := 0; i < 10; i++ {
		ch <- "test-" + strconv.Itoa(i)
		//由于上面定义的chan大小为5所以程序会一下子就写入5个  然后再慢慢读写
		fmt.Println("开始写入数据", "test-"+strconv.Itoa(i))
	}
	close(ch)  //写入数据之后必须关闭通道
}

定向通道
package main

import (
	"fmt"
	"time"
)

//封装的思想 一个方法只做一件事
func main() {

	ch3 := make(chan int)
	go writeOnly(ch3)
	go readOnly(ch3)
	time.Sleep(time.Second * 3)

}
func writeOnly(ch chan<- int) {
	ch <- 100
}
func readOnly(ch <-chan int) {
	data := <-ch
	fmt.Println(data)
}

select

每个case语句都必须是一个通道操作
如果任意一个case都能执行,select会随机选择一个执行,其他的被忽略
有default就会先执行default 与Switch有区别

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		ch1 <- 100
	}()

	go func() {
		time.Sleep(time.Second)
		ch2 <- 200
	}()

	//当有多个协程同时进行  select会随机匹配执行一个
	//必须是协程才可以用select  case中也必须是chan类型
	select {
	case num1 := <-ch1:
		fmt.Println("num1", num1)
	case num2 := <-ch2:
		fmt.Println("num2", num2)

        //有default就会先执行default  与Switch有区别
    /*default: 
	fmt.Println("我先执行")*/

	}

}

定时器
package main

import (
	"fmt"
	"time"
)

func main() {
	/*//time.NewTimer  == time.After
	timer := time.NewTimer(time.Second)
	fmt.Println(time.Now())
	timerchan2 := timer.C
	fmt.Println(<-timerchan2)

	timer2 := time.NewTimer(time.Second * 3)
	go func() {
		<-timer2.C //本来3S后停止  有stop函数 可以提前停止
		fmt.Println("子程序end。。。")
	}()
	time.Sleep(time.Second)
	stop := timer2.Stop()
	if stop { //停止或结束返回 false,否则返回true
		fmt.Println("timer2停止了")
	}*/
	//间隔多长时间后输出时间  例如:备份
	timechan := time.After(time.Second * 2)
	fmt.Println(time.Now())

	chantime := <-timechan
	fmt.Println(chantime)

	// 间隔一定时间后执行一个函数
	time.AfterFunc(time.Second*3, test8)

	time.Sleep(time.Second * 4)
}
func test8() {
	fmt.Println("time时间后执行")
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴回答你关于Golang语法进阶的问题!Golang是一门简洁高效的编程语言,下面我将分享一些Golang语法进阶知识。 1. 嵌套结构体:在Golang中,你可以在一个结构体中嵌套另一个结构体。这样可以更好地组织和管理代码。例如: ```go type Person struct { Name string Age int } type Employee struct { Person Salary int } ``` 这里的Employee结构体嵌套了Person结构体,可以通过Employee结构体访问Person结构体的字段。 2. 接口和类型断言:Golang中的接口是一种约定,定义了一组方法。通过接口,你可以实现多态性。类型断言则用于检查接口变量是否实现了某个接口。例如: ```go type Shape interface { Area() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } func main() { var shape Shape = Circle{Radius: 5} if circle, ok := shape.(Circle); ok { fmt.Println("Area of circle:", circle.Area()) } } ``` 3. 并发编程:Golang内置了并发编程的支持,通过goroutine和通道(channel)可以实现轻量级线程间通信。例如: ```go func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Println("Worker", id, "processing job", j) time.Sleep(time.Second) // 模拟耗时操作 results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) // 启动三个goroutine for w := 1; w <= 3; w++ { go worker(w, jobs, results) } // 发送5个任务 for j := 1; j <= 5; j++ { jobs <- j } close(jobs) // 输出结果 for r := 1; r <= 5; r++ { fmt.Println(<-results) } } ``` 以上是Golang语法进阶的一些例子,希望对你有帮助!如果你有更多问题,欢迎继续提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值