2-Go强化(2):结构体和文件操作

一、结构体

1 - 结构体变量定义和初始化

  • 结构体类型
    • 有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理
    • 结构体是一种聚合的数据类型,它是由一系列具有相同类型或不同类型的数据构成的数据集合。每个数据称为结构体的成员
      在这里插入图片描述
  • 普通变量定义和初始化
    • 顺序初始化:依次将结构体内部所欲成员初始化
    • 指定成员初始化:man := Person{name:"rose", age:18}----未初始化的成员变量,取该数据类型对应的默认值
type Person struct {
	name string
	sex  byte
	age  int
}
func main() {
	// 1. 顺序初始化, 必须全部初始化完整
	var man Person = Person{"andy", 'm', 18}
	fmt.Println("man:", man)

	// 2. 部分初始化
	man2 := Person{sex: 'f', age: 19}
	fmt.Println("man2:", man2)
}

2 - 结构体赋值、比较和传参

  • 普通变量的赋值和使用:使用“.”索引成员变量
	var man3 Person
	man3.name = "mike"
	man3.sex = 'm'
	man3.age = 99
	fmt.Println("man3:", man3)
	man3.age = 1073
	fmt.Println("man3:", man3)
  • 结构体变量的比较
    • 比较:只能使用 == 和 != 不能 > < >= <=…
    • 相同结构体类型(成员变量的类型、个数、顺序一致)变量之间可以直接赋值
	// 结构体比较
	var p1 Person = Person{"andy", 'm', 18}
	var p2 Person = Person{"andy", 'm', 18}
	var p3 Person = Person{"andy", 'm', 181}

	fmt.Println("p1 == p2 ?", p1 == p2) // true
	fmt.Println("p1 == p3 ?", p1 == p3) // false

	// 相同类型结构体赋值
	var tmp Person
	fmt.Println("tmp", tmp)
	tmp = p3
	fmt.Println("tmp", tmp)
  • 结构体地址:结构体变量的地址 == 结构体首个元素的地址
  • unsafe.Sizeof(变量名):此种类型的变量所占用的内存空间大小
  • 结构体传参:将结构体变量的值拷贝一份,传递。 —— 几乎不用。 内存消耗大,效率低
type Person struct {
	name string
	sex  byte
	age  int
}

func test(man Person) {
	fmt.Println("test temp size:", unsafe.Sizeof(man)) // test temp size: 32
	man.name = "name1"
	man.age = 33
	fmt.Println("test: man", man) // test: man {name1 0 33}
}

func main() {
	// 函数内部使用结构体传参
	var temp Person
	fmt.Println("main temp size:", unsafe.Sizeof(temp)) // main temp size: 32
	test(temp) // 值传递,将实参的值拷贝一份给形参
	fmt.Println("temp", temp) // temp { 0 0}

	// 结构体变量的地址 = 结构体首个元素的地址
	fmt.Printf("&tmp = %p\n", &temp)
	fmt.Printf("&temp.name = %p\n", &(temp.name))
}

3 - 结构体指针

  • 结构体指针变量定义和初始化
    • 顺序初始化:依次将结构体内部所欲成员初始化var man *Person = &Person{"andy", 'm', 20}
    • 使用指针索引到已有的对象地址:var p2 *Person2 p2 = &tmp
    • new(Person):p := new(Person) p.name = “name” p.age = 10
  • 指针索引成员变变量:使用“.”索引成员变量
  • 结构体指针地址:结构体指针变量的值 == 结构体首个元素的地址
  • 结构体指针传参
    • unSafe.Sizeof(指针) : 不管何种类型的指针,在 64位操作系统下,大小一致。均为 8 字节!!!
    • 结构体变量地址值传递(传引用)。 —— 使用频率非常高!!!
package main

import (
	"fmt"
	"unsafe"
)

type Person2 struct {
	name string
	sex  byte
	age  int
}

func test2(p *Person2) {
	fmt.Println("test2:", unsafe.Sizeof(p)) // test2: 8
	p.name = "Luffy"
	p.age = 779
	p.sex = 'm'
}
func main() {
	var p1 *Person2 = &Person2{"n1", 'f', 19}
	fmt.Println("p1", p1)

	var tmp Person2 = Person2{"n1", 'f', 19}
	var p2 *Person2
	p2 = &tmp
	fmt.Println("p2", p2)

	p3 := new(Person2)
	p3.name = "n3"
	p3.age = 22
	p3.sex = 'f'
	fmt.Println("p3:", p3)
	fmt.Printf("p3 = %p\n",p3) // p3 = 0xc000122440
	fmt.Printf("&p3.name = %p\n",&p3.name) // &p3.name = 0xc000122440

	fmt.Printf("p3, type= %T\n", p3) // p3, type= *main.Person2
	test2(p3)
	fmt.Println("p3:", p3)
	fmt.Println("main:", unsafe.Sizeof(p3)) // main: 8
}
  • 通过函数参数初始化结构体
type Person3 struct {
	name      string
	age       int
	flg       bool
	intereset []string
}

// 通过函数参数初始化结构体
func initFunc(p *Person3) {
	p.name = "Nami"
	p.age = 18
	p.flg = true
	p.intereset = append(p.intereset, "shopping")
	p.intereset = append(p.intereset, "sleeping")
}

func main() {
	var person Person3
	initFunc(&person)
	fmt.Println("person:", person) // person: {Nami 18 true [shopping sleeping]}
}
  • 通过函数返回值初始化结构体
    • 不能返回局部变量的地址,可以返回局部变量的值
    • 局部变量保存栈帧上,函数调用结束后,栈帧释放。局部变量的地址,不再受系统保护,随时可能分配给其他程序

注意:return p返回的是局部变量的值,return &p才是返回局部变量的地址

type Person3 struct {
	name      string
	age       int
	flg       bool
	intereset []string
}

// 通过函数返回值,初始化结构体
func initFunc2() *Person3 {
	p := new(Person3)
	p.name = "Nami"
	p.age = 18
	p.flg = true
	p.intereset = append(p.intereset, "shopping")
	p.intereset = append(p.intereset, "sleeping")
	return p // 返回指针变量的值 —— heap的地址
}

func main() {
	p2 := initFunc2()
	fmt.Println("p2", p2) // p2 &{Nami 18 true [shopping sleeping]}
}

二、字符串处理函数

  • Split:字符串按指定分割符拆分ret := strings.Split(str, " I")
  • Fields:字符串按空格拆分ret = strings.Fields(str)
  • HasSuffix:判断字符串结束标记flg := strings.HasSuffix("test.abc", ".mp3")
  • HasPrefix:判断字符串起始标记flg := strings.HasPrefix("test.abc", "tes.")
func main() {
	str := "I love my work and I love my family too"

	// 字符串按指定分割符拆分
	ret := strings.Split(str, " I")
	for _, s := range ret {
		fmt.Println(s)
	}

	// 字符串按空格拆分
	ret = strings.Fields(str)
	for _, s := range ret {
		fmt.Println(s)
	}

	// 判断字符串结束标记
	flg := strings.HasSuffix("test.abc", ".mp3")
	fmt.Println(flg) // false

	// 判断字符串起始标记
	flg = strings.HasPrefix("test.abc", "tes.")
	fmt.Println(flg) // false
}

三、文件操作强化

1 - 创建文件和打开文件

  • func Create(name string) (*File, error):根据提供的文件名创建新的文件,返回一个文件对象,默认权限是0666的文件,返回的文件对象是可读写的
    • 文件不存在创建, 文件存在,将文件内容清空
func main() {
	f, err := os.Create("E:/Test/testFile.xyz")
	if err != nil {
		fmt.Println("create err:", err)
		return
	}
	defer f.Close()
	fmt.Println("successful")
}
  • func Open(name string) (*File, error):Open()是以只读权限打开文件名为name的文件,得到的文件指针file,只能用来对文件进行“读”操作
    • 文件不存在,打开失败
func main() {
	f, err := os.Open("E:/Test/testFile.xyz")
	if err != nil {
		fmt.Println("open err:", err)
		return
	}
	defer f.Close()
	_, err = f.WriteString("######")
	if err != nil {
		fmt.Println("WriteString err:", err)
		return
	}
	fmt.Println("successful")
}
  • func OpenFile(name string, flag int, perm FileMode) (*File, error):OpenFile()可以选择打开name文件的读写权限
    • 参1:name, 打开文件的路径: 绝对路径、相对路径
    • 参2:打开文件权限: O_RDONLY、O_WRONLY、O_RDWR
    • 参3:一般传6
func main() {
	f, err := os.OpenFile("E:/Test/testFile.xyz", os.O_RDWR, 6)
	if err != nil {
		fmt.Println("OpenFile err:", err)
		return
	}
	defer f.Close()

	_, err = f.WriteString("######")
	if err != nil {
		fmt.Println("WriteString err:", err)
		return
	}

	fmt.Println("successful")
}

2 - 写文件操作

  • 按字符串写n, err := f.WriteString("123"):WriteString --> n个写入的字符个数
  • 按位置写off, _ := f.Seek(-5, io.SeekEnd):Seek -> 修改文件的读写指针位置
    • 参1: 偏移量。 正:向文件尾偏, 负:向文件头偏
    • 参2: 偏移起始位置
      • io.SeekStart: 文件起始位置
      • io.SeekCurrent: 文件当前位置
      • io.SeekEnd: 文件结尾位置
    • 返回值:表示从文件起始位置,到当前文件读写指针位置的偏移量
  • 按字节写n, _ = f.WriteAt([]byte("1111"), off):writeAt -> 在文件制定偏移位置,写入 []byte , 通常搭配 Seek()
    • 参1: 待写入的数据
    • 参2:偏移量
    • 返回:实际写出的字节数
func main() {
	f, err := os.OpenFile("E:/Test/testFile.xyz", os.O_RDWR, 6)
	if err != nil {
		fmt.Println("OpenFile err:", err)
		return
	}
	defer f.Close()
	fmt.Println("successful")

	n, err := f.WriteString("helloworld\r\n")
	if err != nil {
		fmt.Println("WriteString err:", err)
		return
	}
	fmt.Println("WriteString n = ", n)

	off, _ := f.Seek(-5, io.SeekEnd)
	fmt.Println("off:", off)

	n, _ = f.WriteAt([]byte("1111"), off)
	fmt.Println("WriteAt n :", n)
}

3 - 读文件操作

  • 按行读
    • ①.创建一个带有缓冲区的Reader(读写器)reader : = bufio.NewReader(打开的文件指针)
    • ②.从reader的缓冲区中,读取指定长度的数据buf, err := reader.ReadBytes( ' \n' )
func main() {
	f, err := os.OpenFile("C:/itcast/testFile.xyz", os.O_RDWR, 6)
	if err != nil {
		fmt.Println("OpenFile err:", err)
		return
	}
	defer f.Close()
	fmt.Println("successful")

	// 创建一个带有缓冲区(用户缓冲)的 reader
	reader := bufio.NewReader(f)
	for {
		buf, err := reader.ReadBytes('\n') // 读一行数据
		if err != nil && err == io.EOF {
			fmt.Println("文件读取完毕")
			return
		} else if err != nil {
			fmt.Println("ReadBytes err:", err)
		}
		fmt.Print(string(buf))
	}
}
  • 按字节读文件:read([]byte)
  • 按字节写文件:write([]byte)
  • 案例:大文件拷贝
func main() {
	// 打开读文件
	f_r, err := os.Open("E:/Test/01-复习.avi")
	if err != nil {
		fmt.Println("Open err: ", err)
		return
	}
	defer f_r.Close()
	// 创建写文件
	f_w, err := os.Create("E:/Test/test.avi")
	if err != nil {
		fmt.Println("Create err: ", err)
		return
	}
	defer f_w.Close()

	// 从读文件中获取数据,放到缓冲区中。
	buf := make([]byte, 4096)
	// 循环从读文件中,获取数据,“原封不动的”写到写文件中。
	for {
		n, err := f_r.Read(buf)
		if err != nil && err == io.EOF {
			fmt.Printf("读完。n = %d\n", n)
			return
		}
		f_w.Write(buf[:n]) // 读多少,写多少
	}
}

四、目录操作

1 - 目录操作函数

  • 打开目录OpenFile:以只读方式打开目录
    • 参1:name, 打开目录的路径: 绝对路径、相对路径
    • 参2:flg,表示打开文件的读写模式。可选择 -> O_RDONLY只读模式、O_WRONLY只写模式、O_RDWR读写模式
    • 参3:os.ModeDir
    • 返回值: 返回一个可以读目录的文件指针
  • 读目录Readdirfunc (f *File) Readdir(n int) ([]FileInfo, error)
    • 参数:欲打开的目录项个数。通常传-1,表读取目录所有文件对象
    • 返回值:FileInfo类型的切片。其内部保存了文件名。error中保存错误信息
      • 得到 FileInfo类型切片后,我们可以range遍历切片元素,使用.Name()获取文件名。使用.Size()获取文件大小,使用.IsDir()判断文件是目录还是非目录文件
package main

import (
	"fmt"
	"os"
)

func main() {
	// 获取用户输入的目录路径
	fmt.Println("请输入待查询的目录:")
	var path string
	fmt.Scan(&path)

	// 打开目录
	f, err := os.OpenFile(path, os.O_WRONLY, os.ModeDir)
	if err != nil {
		fmt.Println("OpenFile err: ", err)
		return
	}
	defer f.Close()
	// 读取目录项
	info, err := f.Readdir(-1) // -1: 读取目录中所有目录项
	if err != nil {
		fmt.Println("Readdir err: ", err)
		return
	}
	// 变量返回的切片
	for _, fileInfo := range info {
		if fileInfo.IsDir() { // 是目录
			fmt.Println(fileInfo.Name(), " 是一个目录")
		} else {
			fmt.Println(fileInfo.Name(), " 是一个文件")
		}
	}
}

2 - 目录文件操作案例

  • 案例:从用户给出的目录中,找出所有的 .jpg 文件
func main() {
	// 获取用户输入的目录路径
	fmt.Println("请输入待查询的目录:")
	var path string
	fmt.Scan(&path)

	// 打开目录
	f, err := os.OpenFile(path, os.O_RDONLY, os.ModeDir)
	if err != nil {
		fmt.Println("OpenFile err: ", err)
		return
	}
	defer f.Close()

	// 读取目录项
	info, err := f.Readdir(-1) // -1: 读取目录中所有目录项
	if err != nil {
		fmt.Println("Readdir err: ", err)
		return
	}
	// 变量返回的切片
	for _, fileInfo := range info {
		if !fileInfo.IsDir() { // 文件
			if strings.HasSuffix(fileInfo.Name(), ".mp3") {
				fmt.Println("jpg 文件有:", fileInfo.Name())
			}
		}
	}
}
  • 案例:从用户给出的目录中,拷贝 .mp3文件到指定目录中
package main

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

// 拷贝mp3 文件到指定目录的操作
func cpMP32Dir(src, dst string) {
	//fmt.Println("src:", src)
	//fmt.Println("dst:", dst)

	//打开读文件
	f_r, err := os.Open(src)
	if err != nil {
		fmt.Println("Open err: ", err)
		return
	}
	defer f_r.Close()
	// 创建写文件
	f_w, err := os.Create(dst)
	if err != nil {
		fmt.Println("Create err: ", err)
		return
	}
	defer f_w.Close()

	// 从读文件中获取数据,放到缓冲区中。
	buf := make([]byte, 4096)
	// 循环从读文件中,获取数据,“原封不动的”写到写文件中。
	for {
		n, err := f_r.Read(buf)
		if err != nil && err == io.EOF {
			fmt.Printf("读完。n = %d\n", n)
			return
		}
		f_w.Write(buf[:n]) // 读多少,写多少
	}
}

func main() {
	// 获取用户输入的目录路径
	fmt.Println("请输入待查询的目录:")
	var path string
	fmt.Scan(&path)

	// 打开目录
	f, err := os.OpenFile(path, os.O_RDONLY, os.ModeDir)
	if err != nil {
		fmt.Println("OpenFile err: ", err)
		return
	}
	defer f.Close()

	// 读取目录项
	info, err := f.Readdir(-1) // -1: 读取目录中所有目录项
	if err != nil {
		fmt.Println("Readdir err: ", err)
		return
	}
	// 变量返回的切片
	for _, fileInfo := range info {
		if !fileInfo.IsDir() { // 文件
			if strings.HasSuffix(fileInfo.Name(), ".mp3") {
				cpMP32Dir(path+"/"+fileInfo.Name(), "./"+fileInfo.Name())
			}
		}
	}
}
  • 案例:统计指定目录下,所有.txt文件中,“Love”这个单词 出现的次数
package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"
)

// 从一个文件中逐行读取内容,统计该文件共有多少个 love
func readFile(fileName string) int {
	fp, err := os.Open(fileName)
	if err != nil {
		fmt.Println("Open err:", err)
		return -1
	}
	defer fp.Close()

	row := bufio.NewReader(fp) // 创建一个reader
	var total int = 0          // 统计 Love 总数的变量

	for { // 循环 按行读取文件内容,存入buf中
		buf, err := row.ReadBytes('\n')
		if err != nil && err == io.EOF {
			break
		}
		// 交给 wordCount 统计每行中 love 出现的次数。累加各行 love 数
		total += wordCount(string(buf[:]))
	}
	return total
}

// 从一行字符串中获取 love 出现的次数。将该次数返回。
func wordCount(s string) int {
	arr := strings.Fields(s)  // 分割字符串,存入字符数组
	m := make(map[string]int) // 创建map

	// 对arr中的每个单词进行循环,存入map中,统计
	for i := 0; i < len(arr); i++ {
		m[arr[i]]++
	}
	// 统计 map 中一共有多少个 "Love"
	for k, v := range m {
		if k == "love" {
			fmt.Printf("%s : %d\n", k, v)
			return m[k] // 返回 Love 出现的次数
		}
	}
	return 0 // 没有Love, 返回 0
}

func main() {
	/*	// 测试从文件读一行
		oneFilenum := readFile("C:/itcast/test2/test.txt")
		fmt.Printf("一个文件中有:%d 个Love\n", oneFilenum)

		writeString
	*/

	fmt.Println("请输入要找寻的目录:")
	var path string
	fmt.Scan(&path) // 获取用户指定的目录名

	dir, _ := os.OpenFile(path, os.O_RDONLY, os.ModeDir) // 只读打开该目录
	defer dir.Close()

	names, _ := dir.Readdir(-1) // 读取当前目录下所有的文件名和目录名,存入names切片

	var AllLove int = 0
	for _, name := range names { // 遍历切片,获取文件/目录名
		if !name.IsDir() {
			s := name.Name() // 文件名不包含路径。
			if strings.HasSuffix(s, ".txt") {
				AllLove += readFile(path + s) // 拼接有路径的文件名(绝对路径)
			}
		}
	}
	fmt.Printf("目录所有文件中共有 %d 个Love\n", AllLove)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值