📅 2024年5月10日

📦 使用版本为 1.21.5

读写数据

⭐️ 在 Go中需要使用到 bufio来读写数据

🌟 文件读写

1️⃣ 读文件

⭐️ 在 Go中文件是用指向 os.File类型的指针来表示,也叫做文件句柄

⭐️ 在 Go中需要使用到 Open打开文件,然后读写(和Python类似 open with),传参是一个 string,返回两个参数一个是 *File指针(在输入输出提到过),还有一个是错误,如果文件不存在或者程序没有足够的权限打开这个文件就会报错

可以看见 Open函数就是调用了 OpenFile,那么在后面执行肯定也可以使用 OpenFile

func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

1️⃣ 在来看看这个 OpenFile函数有三个参数:文件名、一个或多个标志(使用逻辑运算符 | 连接),使用的文件权限

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	testlog.Open(name)
	f, err := openFileNolog(name, flag, perm)
	if err != nil { //如果有报错就返回报错
		return nil, err
	}
	f.appendMode = flag&O_APPEND != 0

	return f, nil //默认报错为nil
}

2️⃣ 和 Java类似,之前学过 deferfinally差不多,如果正常打开文件,就需要关闭文件

defer.[文件对象/文件变量].close

3️⃣ 在 OpenFile函数中,会发现有个 O_RDONLY传参,这个是文件的读取属性

// 用于OpenFile的标志,这些标志封装了底层系统的那些标志。并非所有系统都可能实现了所有标志。
const (
	// 必须明确指定以下三个标志中的一个:O_RDONLY、O_WRONLY 或 O_RDWR。
	O_RDONLY int = syscall.O_RDONLY // 以只读方式打开文件。
	O_WRONLY int = syscall.O_WRONLY // 以只写方式打开文件。
	O_RDWR   int = syscall.O_RDWR   // 以读写方式打开文件。

	// 下列标志可按需组合使用以控制文件打开的行为。
	O_APPEND int = syscall.O_APPEND // 写入时向文件追加数据。
	O_CREATE int = syscall.O_CREAT  // 如果文件不存在,则创建新文件。
	O_EXCL   int = syscall.O_EXCL   // 与O_CREATE一起使用,要求文件必须不存在。
	O_SYNC   int = syscall.O_SYNC   // 以同步I/O模式打开。
	O_TRUNC  int = syscall.O_TRUNC  // 打开可写的常规文件时将其截断。
)
const (
   ModeDir        = fs.ModeDir        // d: 目录
   ModeAppend     = fs.ModeAppend     // a: 只能添加
   ModeExclusive  = fs.ModeExclusive  // l: 专用
   ModeTemporary  = fs.ModeTemporary  // T: 临时文件
   ModeSymlink    = fs.ModeSymlink    // L: 符号链接
   ModeDevice     = fs.ModeDevice     // D: 设备文件
   ModeNamedPipe  = fs.ModeNamedPipe  // p: 具名管道 (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Unix 域套接字
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Unix 字符设备, 前提是设置了 ModeDevice
   ModeSticky     = fs.ModeSticky     // t: 黏滞位
   ModeIrregular  = fs.ModeIrregular  // ?: 非常规文件

   // 类型位的掩码. 对于常规文件而言,什么都不会设置.
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Unix 权限位, 0o777
)

⭐️ 在读文件的时候,文件的权限是被忽略的,所以在使用 OpenFile() 时传入的第三个参数可以用 0 。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666

4️⃣ 测试实例

func main() {
	file, erros := os.Open("file") //读取文件
	fmt.Printf("%v\n", erros) //输出错误如果打开正确那就是nil
	fmt.Printf("%v", file) //输出一个指向文件的地址 
	defer file.Close(); //关闭文件
}

⭐️ 既然获取到文件地址了,那就可以对文件进行相应的操作,使用 bufio来对文件进行操作

1️⃣ 使用 NewReader来读取文件获得一个读取器变量,它返回的是一个 *Reader类型,这个 defaultBufSize应该是读取大小,默认是 4096

// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}

2️⃣ 再来看看 NewReaderSize

// NewReaderSize 返回一个新的Reader,其缓冲区大小至少为指定大小。
// 如果传入的io.Reader已经是一个缓冲区大小足够的Reader,那么直接返回这个底层的Reader。
// 
// 参数:
// rd - 用于创建新Reader的io.Reader。它可以是实现io.Reader接口的任何类型。
// size - 新Reader所需缓冲区的最小大小。
// 
// 返回:
// *Reader - 指向新创建的Reader的指针,其缓冲区大小至少为指定大小。
func NewReaderSize(rd io.Reader, size int) *Reader {
	// 检查输入的reader是否已经是Reader类型并且缓冲区大小满足需求
	b, ok := rd.(*Reader)
	if ok && len(b.buf) >= size {
		return b // 如果满足条件,直接复用现有的Reader
	}
	// 如果指定的大小小于最小缓冲区大小,则调整为最小值
	if size < minReadBufferSize {
		size = minReadBufferSize
	}
	// 创建并返回一个具有调整后缓冲区大小的新Reader
	r := new(Reader)
	r.reset(make([]byte, size), rd)
	return r
}

2️⃣ Reader类型也实现了非常多的接口,也提供了很多功能

image-20240509140739259

它提供了很多 Read方法来读取数据

⭐️ 在之前的例子中,我们看到,Unix 和 Linux 的行结束符是 \n,而 Windows 的行结束符是 \r\n。在使用 ReadStringReadBytes 方法的时候,我们不需要关心操作系统的类型,直接使用 \n 就可以了

⭐️ 一旦读取到文件末尾,变量 readerError 的值将变成非空(事实上,其值为常量 io.EOF),我们就会执行 return 语句从而退出循环

//ReadString
// ReadString 读取输入直到遇到第一个 delim,
// 返回一个包含数据(包括分隔符)的字符串。
// 如果 ReadString 在找到分隔符之前遇到错误,
// 它返回读取的数据和错误本身(通常是 io.EOF)。
// ReadString 仅当返回的数据不以 delim 结尾时才返回 err != nil。
// 对于简单用途,Scanner 可能更方便。
func (b *Reader) ReadString(delim byte) (string, error) {
	full, frag, n, err := b.collectFragments(delim)
	// 分配新的缓冲区以容纳完整的片段和片段。
	var buf strings.Builder
	buf.Grow(n)
	// 将完整的片段和片段复制到缓冲区中。
	for _, fb := range full {
		buf.Write(fb)
	}
	buf.Write(frag)
	return buf.String(), err
}
// ReadLine 是一个低级别的行读取基本操作。大多数调用者应考虑使用 ReadBytes('\n') 或 ReadString('\n') 替代,或者使用 Scanner。
//
// ReadLine 尝试返回一行内容,不包括行尾的字节。如果一行太长,超出了缓冲区的大小,则会设置 isPrefix 并返回行的开始部分。行的其余部分将从后续调用中返回。当返回行的最后一部分时,isPrefix 会被设置为 false。返回的缓冲区仅在下一次调用 ReadLine 之前有效。ReadLine 要么返回非空的行,要么返回错误,不会同时返回两者。
//
// 从 ReadLine 返回的文本不包含行结束符("\r\n" 或 "\n")。
// 如果输入结束时没有最后的行结束符,也不会给出指示或错误。
// 在调用 ReadLine 后调用 UnreadByte 总是会取消读取最后一个字节(可能是属于行尾的一部分),即使该字节不属于 ReadLine 返回的行。
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
	line, err = b.ReadSlice('\n')
	if err == ErrBufferFull {
		// 处理 "\r\n" 分割缓冲区的情况。
		if len(line) > 0 && line[len(line)-1] == '\r' {
			// 将 '\r' 放回 buf 并从 line 中移除它。
			// 让下一次调用 ReadLine 检查 "\r\n"。
			if b.r == 0 {
				// 应该是不可达的
				panic("bufio: 尝试回退到缓冲区的起始位置之前")
			}
			b.r--
			line = line[:len(line)-1]
		}
		return line, true, nil
	}

	if len(line) == 0 {
		if err != nil {
			line = nil
		}
		return
	}
	err = nil

	if line[len(line)-1] == '\n' {
		drop := 1
		if len(line) > 1 && line[len(line)-2] == '\r' {
			drop = 2 // 同时移除 "\r\n"
		}
		line = line[:len(line)-drop]
	}
	return
}
// ReadBytes 读取输入直到遇到第一个 delim,
// 返回一个包含数据(包括分隔符)的字节切片。
// 如果 ReadBytes 在找到分隔符之前遇到错误,
// 它返回读取的数据和错误本身(通常是 io.EOF)。
// ReadBytes 仅当返回的数据不以 delim 结尾时才返回 err != nil。
// 对于简单用途,Scanner 可能更方便。
func (b *Reader) ReadBytes(delim byte) ([]byte, error) {
	full, frag, n, err := b.collectFragments(delim)
	// 分配新的缓冲区以容纳完整的片段和片段。
	buf := make([]byte, n)
	n = 0
	// 将完整的片段和片段复制到缓冲区中。
	for i := range full {
		n += copy(buf[n:], full[i])
	}
	copy(buf[n:], frag)
	return buf, err
}

3️⃣ Read实列

func main() {
	file, erros := os.Open("file") //读取文件
	if erros != nil { //错误
		fmt.Println(erros)
	}
	defer file.Close()
	fileReader := bufio.NewReader(file) //创建读取器
	for {
		line, err := fileReader.ReadString('\n') //按照行来读取
        fmt.Printf("%v", line)
		if err != nil { //遇到没有了,也就是报错了终止
			break
		}
	}
}

//输出:
刮风这天我试过牵着你手
但偏偏
雨渐渐
大到我看你不见

4️⃣ 还可以将整个文件读取到一个字符内,使用 ReadFile,它和 ReadLine一样返回的是一个切片

// ReadFile 读取指定文件并返回其内容。
// 成功调用返回 err == nil,而不是 err == EOF。
// 因为 ReadFile 会读取整个文件,所以它不会将来自 Read 的 EOF 视为要报告的错误。
func ReadFile(name string) ([]byte, error) {
	f, err := Open(name) // 打开文件
	if err != nil { // 如果打开文件出错
		return nil, err // 返回错误信息
	}
	defer f.Close() // 确保在函数结束时关闭文件

	var size int // 定义变量 size 存储文件大小
	if info, err := f.Stat(); err == nil { // 获取文件信息
		size64 := info.Size() // 获取文件大小
		if int64(int(size64)) == size64 { // 判断文件大小是否为整数
			size = int(size64) // 将文件大小转换为整数类型
		}
	}
	size++ // 为了在 EOF 处进行最后的读取,将文件大小加1

	// 如果文件声明了较小的大小,则至少读取 512 字节。
	// 特别是,Linux 中的 /proc 目录中的文件声明大小为 0,但
	// 如果以小块读取,则无法正常工作,
	// 因此初始读取 1 个字节将无法正确工作。
	if size < 512 { // 如果文件大小小于 512
		size = 512 // 将文件大小设置为 512
	}

	data := make([]byte, 0, size) // 创建一个长度为 size 的字节切片 data
	for { // 循环读取文件内容
		if len(data) >= cap(data) { // 如果 data 的长度大于等于容量
			d := append(data[:cap(data)], 0) // 在 data 末尾添加一个字节
			data = d[:len(data)] // 更新 data 的长度
		}
		n, err := f.Read(data[len(data):cap(data)]) // 从文件中读取数据到 data 中
		data = data[:len(data)+n] // 更新 data 的长度
		if err != nil { // 如果读取文件出错
			if err == io.EOF { // 如果错误是 EOF
				err = nil // 将错误设置为 nil
			}
			return data, err // 返回 data 和错误信息
		}
	}
}

测试

func main() {
	file, err := os.ReadFile("file")
	if err != nil {
		fmt.Printf("%v", err)
	}
	fmt.Printf("%v", string(file))
}
//输出:
刮风这天我试过牵着你手
但偏偏
雨渐渐
大到我看你不见

5️⃣ 同样的还有 WriteFile

func main() {
	file, err := os.ReadFile("file")
	if err != nil {
		fmt.Printf("%v", err)
	}
	fmt.Printf("%v", string(file))

	err = os.WriteFile("file_copy", []byte(string(file)), 0644) //0644为读取权限
	if err != nil {
		fmt.Printf("%v", err)
	}
}

⭐️ 带缓存读取很多情况下,文件的内容是不按行划分的,或者干脆就是一个二进制文件。在这种情况下,ReadString() 就无法使用了,就需要使用 Read来读取字节数

// Read 将数据读取到 p 中。
// 它返回读取到 p 中的字节数量。
// 字节最多从底层 Reader 的一次读取操作中获取,因此 n 可能小于 len(p)。
// 要读取正好 len(p) 个字节,请使用 io.ReadFull(b, p)。
// 如果底层 Reader 在遇到 io.EOF 时可以返回非零计数,
// 那么此 Read 方法也可以这样做;请参阅 [io.Reader] 文档了解详情。
func (b *Reader) Read(p []byte) (n int, err error) {
	n = len(p)
	if n == 0 {
		if b.Buffered() > 0 { // 缓冲区有数据但不读取
			return 0, nil
		}
		return 0, b.readErr() // 直接返回错误
	}
	if b.r == b.w { // 缓冲区当前为空
		if b.err != nil {
			return 0, b.readErr() // 返回已存在的错误
		}
		if len(p) >= len(b.buf) { // 请求读取的字节数大于缓冲区大小,直接读到 p 中
			n, b.err = b.rd.Read(p)
			if n < 0 {
				panic(errNegativeRead) // 防止读取负数字节的逻辑错误
			}
			if n > 0 {
				b.lastByte = int(p[n-1]) // 记录最后一个字节
				b.lastRuneSize = -1
			}
			return n, b.readErr()
		}
		// 单次读取操作,直接填充缓冲区
		b.r = 0
		b.w = 0
		n, b.err = b.rd.Read(b.buf)
		if n < 0 {
			panic(errNegativeRead)
		}
		if n == 0 {
			return 0, b.readErr()
		}
		b.w += n // 更新缓冲区写索引
	}

	// 尽可能多地复制数据到 p 中
	n = copy(p, b.buf[b.r:b.w])
	b.r += n // 更新读索引
	b.lastByte = int(b.buf[b.r-1]) // 记录最后一个字节
	b.lastRuneSize = -1
	return n, nil // 成功读取 n 字节
}

1️⃣ 测试

func main() {
	file, err := os.Open("file")
	if err != nil {
		fmt.Printf("%v", err)
	}
	defer file.Close()

	fileReader := bufio.NewReader(file)

	buf := make([]byte, 1024)
	i, err := fileReader.Read(buf)
	if err != nil {
		fmt.Printf("%v", err)
	}
	fmt.Printf("%v", i)
}

⭐️ 如果数据是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan... 开头的一系列函数来读取他们,按列读取

func main() {
	file, err := os.Open("file")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	var col1, col2, col3 []string
	for {
		var v1, v2, v3 string
		_, err := fmt.Fscanln(file, &v1, &v2, &v3)
		col1 = append(col1, v1)
		col2 = append(col2, v2)
		col3 = append(col3, v3)
		// scans until newline
		if err != nil {
			break
		}
	}
	fmt.Println(col1)
	fmt.Println(col2)
	fmt.Println(col3)
}


//输出:
[刮]
[风]
[这]

⭐️ Path包内有个 filepath,这个子包提供了跨平台的函数,用于处理文件名和路径。例如 Base() 函数用于获得路径中的最后一个元素(不包含后面的分隔符)

import "path/filepath"
filename := filepath.Base(path)

⭐️ 使用compress包读取压缩文件,compress 包提供了读取压缩文件的功能,支持的压缩文件格式为:bzip2、flate、gzip、lzw 和 zlib

package main
import (
    "fmt"
    "bufio"
    "os"
    "compress/gzip"
)
func main() {
    fName := "MyFile.gz"
    var r *bufio.Reader
    fi, err := os.Open(fName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
            err)
        os.Exit(1)
    }
    defer fi.Close()
    fz, err := gzip.NewReader(fi)
    if err != nil {
        r = bufio.NewReader(fi)
    } else {
        r = bufio.NewReader(fz)
    }
    for {
        line, err := r.ReadString('\n')
        if err != nil {
            fmt.Println("Done reading file")
            os.Exit(0)
        }
        fmt.Println(line)
    }
}

2️⃣ 写文件

⭐️ 写入和读入操作大差不多,注意事项基本上上面都提到了,写入使用 NewWriter

func NewWriter(w io.Writer) *Writer {
	return NewWriterSize(w, defaultBufSize) //调用了NewWriterSize
}
// NewWriterSize 返回一个新的带有指定最小缓冲区大小的 Writer。如果传入的 io.Writer 本身已经是一个缓冲区大小足够的 Writer,那么就直接返回这个底层的 Writer。
//
// 参数:
// w io.Writer: 需要封装的io.Writer接口实例。
// size int: 缓冲区期望的最小大小。
//
// 返回值:
// *Writer: 一个具有指定缓冲区大小的 Writer 实例。
//
// 注意:
// - 如果传入的 w 本身就是一个 Writer 且其缓冲区大小不低于指定的 size,直接返回该 Writer。
// - 如果 size 小于等于0,则会使用默认的缓冲区大小 defaultBufSize。
func NewWriterSize(w io.Writer, size int) *Writer {
	// 检查 w 是否已经是 Writer 类型
	b, ok := w.(*Writer)
	if ok && len(b.buf) >= size { // 如果是且其缓冲区足够大,直接返回
		return b
	}
	if size <= 0 { // 如果指定的大小不合法,使用默认大小
		size = defaultBufSize
	}
	// 创建并返回新的 Writer 实例,初始化缓冲区大小为 size
	return &Writer{
		buf: make([]byte, size),
		wr:  w,
	}
}

⭐️ 它也提供了很多 Write方法,测试实例

func main() {
	file, err := os.OpenFile("file", os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	fileWriter := bufio.NewWriter(file)
	text := "晴天-周杰伦"
	fileWriter.WriteString(text)
	fileWriter.Flush()
}

3️⃣ 文件拷贝

⭐️ 使用 io.Copy

// filecopy.go
package main
import (
    "fmt"
    "io"
    "os"
)
func main() {
    CopyFile("target.txt", "source.txt")
    fmt.Println("Copy done!")
}
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()
    return io.Copy(dst, src)
}

4️⃣ 从命令行读取参数

os

⭐️ os 包中有一个 string 类型的切片变量 os.Args,用来处理一些基本的命令行参数

var Args []string //源代码是一个string切片
func init() {
	if runtime.GOOS == "windows" {
		// Initialized in exec_windows.go.
		return
	}
	Args = runtime_args() //第一个索引也就是0,是当前运行的程序名
}

⭐️ 简单测试

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Printf("%v", os.Args)
}

//命令行执行go build .
//会生成一个exe然后
.\var.exe Linux!!
[D:\learnCode\Go\var\var.exe Linux!!]

⭐️ 可以实现 echo,也就是 linux的输出

flag

⭐️ 它有个扩展功能来解析命令行选项。但是通常别替换成基本常量,也就是它可以实现像 linuxshell功能,如果以后需要使用 linux命令行开发,可能需要用到

⭕️ 下面是一个测试案例(这里我新建了一个项目叫做 gosql

⭐️ 它使用 类型var类的函数来接受参数,它可以支持很多类型,可以在 flag源文件内看,var的函数需要自己定义一个变量然后传参到函数内去接收,还有一种不带 var的,它少了一个接收参数函数,多了一个返回值,也可以直接用这个返回值来定义变量

//第一个传参表示变量,第二个为参数名字,第二个为参数默认值也就是在命令未指定情况下的配置,第三个为解释,就是使用-h或者--help会显示
func BoolVar(p *bool, name string, value bool, usage string)
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)
func Float64Var(p *float64, name string, value float64, usage string)
func Int64Var(p *int64, name string, value int64, usage string)
func IntVar(p *int, name string, value int, usage string)
func StringVar(p *string, name string, value string, usage string)
func Uint64Var(p *uint64, name string, value uint64, usage string)
func UintVar(p *uint, name string, value uint, usage string)
func Bool(name string, value bool, usage string) *bool
func Duration(name string, value time.Duration, usage string) *time.Duration
func Float64(name string, value float64, usage string) *float64
func Int(name string, value int, usage string) *int
func Int64(name string, value int64, usage string) *int64
func String(name string, value string, usage string) *string
func Uint(name string, value uint, usage string) *uint
func Uint64(name string, value uint64, usage string) *uint64

⭐️ Parse()函数用于解析 var函数接受的参数

这个是它的源代码,你会发现最后还是接收的一个 os.Args,它内部也是使用的 CommadLineParse

func Parse() {
	// Ignore errors; CommandLine is set for ExitOnError.
	CommandLine.Parse(os.Args[1:])
}

CommadLine的类型是一个 FlagSetFlagSet就是一个命令行参数的集合体,当我们调用诸如 IntVar这类的函数时,就是将命令行的 默认值参数说明参数名称接收参数的变量等信息告诉flag库,而 flag内部会让 CommandLine来处理,用这些信息创建 Flag类型的变量,将添加到这个集合体中

来自:Golang标准库flag全面讲解 - 掘金 (juejin.cn)

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// Parse 从参数列表中解析标志定义,该参数列表不应包含命令名称。
// 必须在FlagSet中定义所有标志之后调用,并在程序访问标志之前调用。
// 如果设置了-help或-h但没有定义,则返回值将为ErrHelp。
func (f *FlagSet) Parse(arguments []string) error {
	f.parsed = true // 标记已解析
	f.args = arguments // 保存参数列表
	for {
		seen, err := f.parseOne() // 尝试解析一个标志
		if seen { // 如果成功解析了一个标志,继续解析下一个
			continue
		}
		if err == nil { // 如果没有错误,说明所有标志都已解析完毕,跳出循环
			break
		}
		switch f.errorHandling { // 根据错误处理方式进行处理
		case Continue: // 继续处理错误
			if err == ErrHelp { // 如果错误是帮助信息,退出程序
				os.Exit(0)
			}
			os.Exit(2) // 其他错误,退出程序并返回错误码2
		case PanicOnError: // 遇到错误时,抛出异常
			panic(err)
		}
	}
	return nil // 返回nil表示解析成功
}

测试代码使用带var的:

package main

import (
	"flag"
	"fmt"
)

// 设置字段
var (
	host     string
	dbName   string
	port     int
	user     string
	password string
)

func main() {

	//StringVar、IntVar 函数将变量绑定到命令行参数上
	flag.StringVar(&host, "host", "", "数据库地址")
	flag.StringVar(&dbName, "db_name", "", "数据库名称")
	flag.StringVar(&user, "user", "", "数据库用户")
	flag.StringVar(&password, "password", "", "数据库密码")
	flag.IntVar(&port, "port", 3306, "数据库端口")

	// 解析命令行参数
	flag.Parse()

	// 打印接受的参数
	fmt.Printf("数据库地址:%s\n", host)
	fmt.Printf("数据库名称:%s\n", dbName)
	fmt.Printf("数据库用户:%s\n", user)
	fmt.Printf("数据库密码:%s\n", password)
	fmt.Printf("数据库端口:%d\n", port)

}

使用不带 var

func main() {

	//StringVar、IntVar 函数将变量绑定到命令行参数上
	host := flag.String("host", "", "数据库地址")
	dbName := flag.String( "db_name", "", "数据库名称")
	user := flag.String("user", "", "数据库用户")
	password := flag.String( "password", "", "数据库密码")
	port := flag.Int("port", 3306, "数据库端口")

	// 解析命令行参数
	flag.Parse()

	// 打印接受的参数
	fmt.Printf("数据库地址:%s\n", host)
	fmt.Printf("数据库名称:%s\n", dbName)
	fmt.Printf("数据库用户:%s\n", user)
	fmt.Printf("数据库密码:%s\n", password)
	fmt.Printf("数据库端口:%d\n", port)

}

1️⃣ 使用 go build(开始就说过用处了),然后在使用 gosql命令

PS D:\learnCode\Go\gosql> .\gosql.exe -host localhost -db_name gosql -user root -password xxxxxx -port 3305
数据库地址:localhost
数据库名称:gosql  
数据库用户:root   
数据库密码:xxxxxx
数据库端口:3305

//它还会提示
Usage of D:\learnCode\Go\gosql\gosql.exe:
  -db_name string
        数据库名称
  -host string
        数据库地址
  -password string
        数据库密码
  -port int
        数据库端口 (default 3306)
  -user string
        数据库用户

⭐️ 上面使用的是 cmd -参数 传参 这种命令它还支持下面这几种

cmd -flag //只用布尔值的选项,如果该参数出现,则为true,不出则为默认值,而其他数据类型不能使用这种格式传值
cmd -flag=x //=
cmd -flag x //不可用于布尔值的选项

⚠️ 注意: 布尔值可以接收 1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False

⭐️ 下面是一个简单的 echo实现代码

package main

import (
	"flag"
)


func main() {
	isEcho := flag.Bool("echo", false, "echo the string") //获取是否输入了参数
	flag.Parse()  
	if *isEcho { //如果没有输出参数打印帮助信息
		flag.PrintDefaults() //打印帮助信息
	} else if flag.NArg() > 0 {  //如果输入了NArg就是接收输入参数的长度
		for _, arg := range flag.Args() { //使用for循环来打印默认分隔符为空格
			println(arg)
		}
	}

}

❓ 下面是一个 Flag包使用函数的方法

函数名 功能描述
Bool() 返回一个布尔类型的值,如果命令行中存在指定的参数,则返回该参数的布尔值,否则返回false。
BoolVar() 返回一个布尔类型的值,如果命令行中存在指定的参数,则返回该参数的布尔值,否则返回指定的默认值。
Duration() 返回一个时间间隔类型的值,如果命令行中存在指定的参数,则返回该参数的时间间隔值,否则返回0。
DurationVar() 返回一个时间间隔类型的值,如果命令行中存在指定的参数,则返回该参数的时间间隔值,否则返回指定的默认值。
Float64() 返回一个浮点数类型的值,如果命令行中存在指定的参数,则返回该参数的浮点数值,否则返回0。
Float64Var() 返回一个浮点数类型的值,如果命令行中存在指定的参数,则返回该参数的浮点数值,否则返回指定的默认值。
Int() 返回一个整数类型的值,如果命令行中存在指定的参数,则返回该参数的整数值,否则返回0。
IntVar() 返回一个整数类型的值,如果命令行中存在指定的参数,则返回该参数的整数值,否则返回指定的默认值。
String() 返回一个字符串类型的值,如果命令行中存在指定的参数,则返回该参数的字符串值,否则返回空字符串。
StringVar() 返回一个字符串类型的值,如果命令行中存在指定的参数,则返回该参数的字符串值,否则返回指定的默认值。
Parse() 解析命令行参数,并将结果存储在flag包中的变量中。
Parsed() 返回一个布尔值,表示是否已解析命令行参数。
Args() 返回一个字符串切片,包含未被flag包处理的命令行参数。
NArg() 返回一个整数,表示指定的参数在命令行中出现的次数。
NFlag() 返回一个布尔值,表示是否设置了指定的参数。
NBool() 返回一个布尔值,如果命令行中存在指定的参数,则返回该参数的布尔值,否则返回false。
NBoolVar() 返回一个布尔值,如果命令行中存在指定的参数,则返回该参数的布尔值,否则返回指定的默认值。
NDuration() 返回一个时间间隔类型的值,如果命令行中存在指定的参数,则返回该参数的时间间隔值,否则返回0。
NDurationVar() 返回一个时间间隔类型的值,如果命令行中存在指定的参数,则返回该参数的时间间隔值,否则返回指定的默认值。
NFloat64() 返回一个浮点数类型的值,如果命令行中存在指定的参数,则返回该参数的浮点数值,否则返回0。
NFloat64Var() 返回一个浮点数类型的值,如果命令行中存在指定的参数,则返回该参数的浮点数值,否则返回指定的默认值。
NInt() 返回一个整数类型的值,如果命令行中存在指定的参数,则返回该参数的整数值,否则返回0。
NIntVar() 返回一个整数类型的值,如果命令行中存在指定的参数,则返回该参数的整数值,否则返回指定的默认值。
NString() 返回一个字符串类型的值,如果命令行中存在指定的参数,则返回该参数的字符串值,否则返回空字符串。
NStringVar() 返回一个字符串类型的值,如果命令行中存在指定的参数,则返回该参数的字符串值,否则返回指定的默认值。
Visit() 遍历并处理命令行参数,对于每个参数,调用提供的回调函数进行处理。
VisitAll() 遍历并处理所有命令行参数,包括被flag包处理和未被处理的参数,对于每个参数,调用提供的回调函数进行处理。
PrintDefaults() 打印当前flag包中所有变量的默认值。
Set() 设置指定的参数的值,用于在程序运行时动态修改参数值。
SetOutput() 设置输出函数,用于将解析结果输出到指定的位置,如文件或标准输出。
Usage 打印命令行参数的使用方法。