Go学习笔记

Go开发

一、基本知识

1.1、基本结构

(1)go文件的后缀是.go

(2)package main

​ 表示hello.go文件所在的包是main。在go中,每个文件都必须属于一个包。

(3)import “fmt”

​ 表示引入一个包,可以使用fmt包中的函数

(4)func main(){

}

func 是一个关键字,表示一个函数。

main 是函数名,是一个主函数,即为程序的入口

*go语言里面,创建什么变量则一定要使用变量,否则会报错。

*标识符首字母大写表示变量公有,首字母小写则表明变量私有只能在当前包内使用

1.2、转义字符

package main

import "_fmt"  //_表示暂时不使用这个包
func main( )  {
   fmt.Println("tom\tjack") //制表符,实现对齐功能

   fmt.Println("hello\nword") //换行符

   fmt.Println("D:\\Go工具\\GoLand 2021.1.3\\bin") // "\\"输出一个\

   fmt.Println("天龙八部雪山飞狐\r张") //覆盖全部内容
}

1.3、代码规范

1)行注释

// 注释内容

2)块注释

/*

注释内容

*/ (块注释里面不能再嵌套一个块注释)

3)代码实现缩进,可利用shift+tab键,或者使用gofmt格式化

1.4、DOS基本操作

1)dir 查看当前目录文件内容

2)cd /d f: 切换其他盘,例如:从d盘切换到f盘

3)cd d:\test100 从当前盘切换到当前当前盘中文件包

4)cd … 回到上级目录

5)cd \ 回到顶级目录

6)md 创建空目录

7)rd 删除空目录

8)rd /q/s 删除目录(不考虑里面有无内容)

9)rd /s 带询问删除目录

10)echo 内容 > 所想创建文件的目录绝对路径(d:\test100\abc100\hello.txt)

11)copy 指定文件 复制的文件目录路径(abc.txt d:\test100)

12)move 文件名 移动的文件目录路径(想改路径可以再后面加上文件名)

13)del *.txt删除这样类型所有文件

14)cls 清屏

15)exit 退出

二、Go语言知识

2.1、Golang变量

2.1.1创建变量代码演示

package main

import "fmt"
var(
    i1 = 100
    name2 = "joke"
    i3 = 1000
   ) //创建全局变量

func main() {
	var i int;   //指定变量类型时,创建变量格式
	i = 100
	fmt.Println("i=",i)
    
    n := "tom"  //省略var时候,需要加上 :=
    fmt.Println("name=",n)
    
    var n1, n2, n3 int //创建多个变量
    fmt.Println(n1,n2,n3)
    
    var a, name, b = 100,"tom",88 //一次性创建多个变量,其中是一一对应
    fmt.Println(a,name,b)
    
    j, na, k := 100,"tom",88 //省略var
    fmt.Println(j,na,k)
    
    fmt.Println(i1,name2,i3)
}

2.1.2 数据类型

2.1.2.1

*byte只能储存一个字符,go里面没有专门的char类型

2.1.2.2 整型类型
类型有无符号范围备注
int-2的63次方~2的63次方-1
uint0~2的64次方-1
rune-2的31次方~2的31次方-1等价int32,表示一个Unicode码
byte0~255当要存储字符时候选用byte

**Golang程序中整型变量在使用时,遵守保小不保大的原则,即保证程序正常运行的情况下采用占用空间小的数据类型。

2.1.2.3 浮点型
类型占用空间范围
单精度(float)4字节-3.403E38~3.403E38
双精度(double)8字节-1.798E308~1.798E308
2.1.2.4 字符类型

(1)go中的字符串是由字符拼接起来的

(2)

package main

import "fmt"
func main() {
	var n1 byte = 'a'
	var n2 byte = '0'
	
	 fmt.Println("n1",n1)
	 fmt.Println("n2",n2)   //只能输出码值
	 fmt.Printf("n2 = %n2",n2)//格式化输出可以输出字符
    
     //若超过ASCII范围
     var n3 int = '北'
     fmt.Printf("n3 = %n3",n3)  //若对变量赋值为数字再按照格式化输出,则输出对应字符。
    
     
}
2.1.2.5 布尔类型

(1) 布尔类型取值只能为true和false

2.1.2.6 字符串类型
package main

import "fmt"
func main() {
	//基本使用
    var adress string = "adfadsf sdafdasf"
    fmt.Println(adress)
}

(1) Go中的字符串是不可变的

(2) 创建字符串变量时,可以是使用

反引号``,可以将源码输出,双引号” “会识别引号里面的特殊字符

(3) 当一个拼接操作很长时候,可以分开写,加号必须再上面

var str = "hello" +
          "world" +
          "hahaha"
2.1.2.6 数据类型转换
 var i int32 = 100
 var n1 float32 = float32(i)
 //其余类似这种转换结构,注意转换后的i的数据类型没有改变

 //数据基本类型转换string
 //方式一、使用fmt.Sprintf
 var num1 int = 99
 var str string
 str = fmt.Sprintf("%d",num1)
 fmt.Printf("str type %T str = %v",str,str)
 
 //方式二、使用strconv 函数,详见go标准文库中文文档
 str = strconv.FormatFloat(num1,'f',10,64) //说明:'f'格式  10表示保留10小数  64表示这个小数是float64
 
 //string类型转换成数据基本类型
 var str string = "true"
 var b bool

 b , _ = strconv.ParseBool(str)//因为这个函数返回两个值,因此忽略第二个值,_表示忽略

注意:在进行string转换成基本类型时,要确保能转换成有效的数据,否则Golang会直接将其转换成0.

2.2 指针

package main
import (
    "fmt"
)
func main(){
    
    var num int = 100
    //创建go指针
    var ptr *int = &num
    
    var num2 int = *ptr
    
    fmt.Printf(num," ",ptr," ",num2)
}

2.3 值类型和引用类型

2.3.1 值类型

基本数据类型 int系列,float系列,bool,string,数组和结构体 struct

2.3.2 引用类型

指针,slice切片,map、管道 chan、interface等

2.4 运算符以及终端输入

说明:++ 和 – 只能独立使用,例如

package main
import (
    "fmt"
)
func main(){
    var name string
    var i int = 8
    var a int
    //a = i++,这样就是错误的写法
    i++
    a = i   //应该先让i实现++,再赋值给a

    //++i或者--i这样是错误的,go中没有前--或前++
    
    
	//从终端输入,方式一:
    fmt.Scanln(&name)//这里是将name的地址传给函数,可以从终端输入name的值
    fmt.Printf("name is %v",name)
    
    //方式二:
    fmt.Scanf("%s",&name)//注意输入多个时候需要一一对齐
}

2.5 流程控制

2.5.1 if语句

    //方式一:
	var age int
	var age1 int
	fmt.Println("请输入年龄")
	fmt.Scanln(&age)

	if age < 0 {
		fmt.Println("你这个是假的的把")
	}
 
	//方式二:
	if age1 = 20; age1 > 18 {
		fmt.Println("你已经成年了小子")
	}
	//在go中无论if语句中有几条语句,都要加上{}
	if age > 18 {
    	fmt.Println("你已经成年了小子")
	}else {                              //这里else必须写在这一行,否则报错
    	fmt.Println("你还是未成年")
	}

2.5.2 switch语句

switch 表达式 {
    case 表达式一,表达式二.... :
    	语句块
    case 表达式一,表达式二.... :
    	语句块
    fallthrough//执行完这一语句后,还可以执行下一语句
    case 表达式一,表达式二.... :
    	语句块
    
    
    .
    .
    .
    default:
    	语句块
    //在go语言中switch语句没有break,而是默认有
    //switch后面可以跟常量,也可以跟函数返回值
    //switch后面可以不跟表达式
    /*例如:switch {
    			case age == 10:
    			语句块
    }
    */
    
}

2.5.3 循环语句

//方式一:
for i = 1; i < 10; i++ {
	语句块
}
//方式二:
var j  =  1
for j < 10 {
    j++
}

for{	
    语句块
    break
}//for循环也可以不用写循环条件,只是需要添加break来终止循环

//for range字符串遍历或遍历数组
str := "hello"
for index, val := range str{
    fmt.Println("index=%d, val=%c \n",index, val)
}//如果字符串中有中文,用传统的遍历会出现中文乱码,可以将其转换成切片  str2 := []rune(str)

2.6 跳转控制语句

2.6.1 break

1)入门案例

package main
import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	//生成一个随机数,还需要个rand设置一个种子
	//time.Now().Unix() : 返回一个从1970:01:01 的0时时0分0秒到现在的秒数
	//rand.Seed(time.Now().Unix())
	//如何随机生成1-100的整数
	//n := rand.Intn(100) + 1
	//fmt.Println(n)
	var count int = 0
	for {
		rand.Seed(time.Now().UnixNano())
		n := rand.Intn(100) + 1
		count++
		fmt.Println("n=",n)
		if(n == 99){
			break
		}
	}

	fmt.Println("生成99一共使用了",count)
}

2)在嵌套循环中使用break,可以通过标签指定跳出那个循环。

lable1:
for i := 0; i < 10; i++ {
    if i == 2 {
        break lable1:
    }
}

2.6.2 continue

1)continue也可以通过标签跳过那个循环

lable1:
for i := 0; i < 10; i++ {
    if i == 2 {
        continue lable1:
    }
    fmt.Println(i)
}

2.6.3 goto和return

2.7 函数

2.7.1 创建函数

func 函数名(形参名 形参数据类型) (返回值数据类型) {//倘若返回值只有一个可以不加()
    执行语句块...
    return 返回值
}

2.7.2 包的使用

1)在引用包里面的函数或者变量时候,需要将变量名或者函数名首字母大写

2)在给文件打包时候,文件包名通常和所在文件夹同名

3)在import包时,src开始,不用代src,编译器会自动从src下开始引入

4)可以给包取别名,但是原先的包名就不能用了

package main
import (
	"fmt"
    util"go_code/breakDmeo/util"
)

5)在同个包下不能有相同的全局变量名或者函数名

2.7.3 函数的注意细节

1)在go中函数也是一种数据类型,可以赋值给一个变量,则该变量也就是这个函数类型的变量。通过该变量可以对函数调用

package main
import "fmt"

func getSum(a int, b int) int {
    return a + b
}
func main() {
     
    n1 := getSum
    
    n2 := n1(10,40) //等价于getSum(10,40)
}

2)函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用

package main
import "fmt"

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

func myFun(funvar func(int,int)int,num1 int, num2 int) {
    return funvar(num1,num2)
}
func main() {
     
    n1 := getSum
    
    n2 := n1(10,40)
}

3)可以为数据类型取别名

package main
import "fmt"


type myType func(int,int)int //此时myType就是func(int,int)int类型

func myFun(funvar myType,n1 int, n2 int) {
    return funvar(num1,num2)
}

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

func main() {
    res3 := myFun(getSum,500,600)
    fmt.Println(res3)
}

4)支持对返回值命名

package main
import "fmt"

func getSumAndSub (a int, b int) (sum int,sub int {
    sum = a + b
    sub = a - b
    return
}
func main() {
	var sum1 int
	var sub1 int
	sum1,sub1 = getSumAndSub(12,56)
}

5)Go支持可变参数

//支持0到多个形参
func sum(args... int) sum int{
 	
 }
 //支持1到多个形参,此时args为切片,相当于数组
func sum1(n1 int,args... int) sum int{
 	
 }

2.7.4 init函数

1)inti函数通常会在main函数之前运行,其主要是为了完成一些初始化工作:

package utils
import "fmt"
var Age int
var Name string


func init(){
    fmt.Println("utils 包的 init().....")
}
2)package main
import ("fmt"
        "go_code/charpter04/utils"
       )

var i int = test()

func test() int {
    fmt.Println("test() 中的 test().....")
}

func init() {
    fmt.Println("init 中的 init.......")
}

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

2)一个文件中包括全局变量定义、inti函数、main函数,则一般执行顺序为全局变量-》inti函数-》main函数

2.7.5 匿名函数

1)在定义匿名函数时直接调用,但只能用一次

res1 := func (n1 int, n2 int)int {
	return n1 + n2
}(10,20)

2)将匿名函数func(i int, j int) int赋值给a,此时便可以通过a来完成函数调用,但是a并不是函数名(可以用于函数写一个函数,并且调用)

a := func(i int,j int) int {
	return i + j
}

res := a(10 , 20)

3)全局匿名函数

var {
	fun = func(i int, j int) int {
	return i + j
	}
}

func main() {
	res := fun(10,20)
}

2.7.6 闭包

1)闭包就是一个函数和与其相关的引用环境组合的一个整体

2)案例演示:

func AddUpper() func(int) int {
	var n int = 10                //以下都是闭包
	return func(x  int) int {
	n = n + x
	return n
	}
}                                 //以上

func main() {
    f := AddUpper()
    fmt.Println(f(1))
    fmt.Println(f(2)) //没次调用n都不会初始化而是会累积
}

3)

package main

import (
	"fmt"
	"strings"
)

func makeSuffix(suffix string) func (string) string {

	return func (name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}

		return name
	}
}

func main() {

	f := makeSuffix(".jpg")

	fmt.Println("文件处理后的格式为:" , f("winter"))
}

2.7.7 函数的defer

1)

package main

import 
	"fmt"

func sum(i int, j int) int {
    defer fmt.Println("000")  //当执行到defer语句时,暂时不执行,将其压入独立的栈中。  执行顺序3
    defer fmt.Println("111") //当函数执行完了以后,所有defer语句都会按照先入后出的顺序. 执行顺序2
    
    tes := i + j
    fmt.Println("tes",tes) //执行顺序1
    return tes
}

func main() {
    res := sum(10 , 20)
    fmt.Println("res ",res) //执行顺序4
}

2)defer的好处在于,可以及时的释放函数所创建的资源

2.7.8 字符串常用的系统函数

1)统计字符串长度

len(str),不用导入包

2)字符串遍历,同时处理中文乱码问题

str2 := "hello世界"
str3 := []rune(str2)   //转换为切片
for i := 0; i < len(str3); i++{
    fmt.Printf("字符=%C\n", r[i])
}

3)字符串转整数和整数转字符串

//这个函数需要导入"strconv"
n, err := strconv.Atoi("123")//字符串转整数
if err != nil {
    fmt.Println("转换错误")
}else{
    fmt.Println("转换成功",n)
}

str := strconv.Itoa(123456)  //整数转字符串

4)字符串转为[]byte: var bytes = []byte(“hello go”)

转为[]byte转为字符串

 var bytes = []byte("hello go") //可以得到hello go 的编码

str := string([]byte{97,98,99}) //可以得到97,98,99对应的字符abc

5)10进制转2,8,16进制: str = strconv.Formatlnt(132,2),其中返回的时字符串

6)查找子串是否在指定字符串中:strings.Contains(“seafood”,“foo”) //true

//需要引入"strings"
strings.Contains("seafood","foo") //返回true

7)统计一个字符串又几个指定字串

num := strings.Cont("see","e") //返回2

8)不分大小写进行比较

b := strings.EqualFold("abc","ABC") //返回true
//"abc" == "ABC" false     “==”是区分大小写

9)返回子串在字符串第一次出现的位置和最后一次出现的位置

index1 := strings.Index("dafdas","f") //返回3,如果没有则返回-1
index2 := strings.LastIndex("dafdas","a") //返回5

10)将指定的子串替换成另外一个子串

str := strings.Replace("go go beijing","go","hello",1)
//得到 hello go beijing
//其中1是指换掉几个,如果全部换掉则写-1,同时"go go //beijing"可以是变量例如:str2:= "go go beijing"
//str := strings.Replace(str2,"go","hello",1) ,其中str2本身没有发生变化

11)按照某个字符,为分割标识,将一个字符串转换为字符数组

strArr := strings.Split("hello,world",",")
//得到 strArr=[hello world]

12)大小写相互转化

str = strings.ToLower("GO")
str = strings.ToUpper("go")

13)字符串去掉左右空格:string.TrimSpace(" to go ")

​ 将字符串左右指定的字符去掉:strings.Trim("! hello!","!")

​ 将字符串左边指定的字符去掉:strings.TrimLeft("")

​ 将字符串右边指定的字符去掉:strings.TrimRright("")

​ 判断字符串是否为某个字符开头:strings.HasPrefix(“adfasdf”,“a”)

​ 判断字符串是否为某个字符结尾:strings.HasSuffix(“adfasdf”,“f”)

2.7.9 日期和时间相关函数

//需要引入"time"
//1) 获取当前函数
now := time.Now()
//2) 获取其他日期信息
fmt.Printf("year = %v\n",now.Year())
fmt.Printf("month = %v\n",now.Month())
fmt.Printf("day = %v\n",now.Day())
fmt.Printf("hour = %v\n",now.Hour())
fmt.Printf("minute = %v\n",now.Minute())
fmt.Printf("second = %v\n",now.Second())
//3) 格式化日期函数
fmt.Printf(now.Format("2006-01-02 15:04:05")) //可以得到当前时间,但是其中数字是固定的不能改变
//4) 时间休眠函数 time.Sleep()
//time.Second time.Hour time.Minute time.Millisecond毫秒 time.Microsecond微秒

2.7.10 内置函数

说明:直接可以使用而不用引包的函数,例:1)new

num2 := new(int)
//num2 为*int类型即指针

2)len(str)

3)make:主要用来分配内存

2.8 异常处理

1)快速入门

package main

import "fmt"

func test() {
    //使用defer + recover 来捕获和处理异常
	defer func() {
		err := recover() // recove()为内置函数,可以捕获到异常
		if err != nil {	 // 说明捕获到异常
			fmt.Println("err =", err)
		}
	}()
	num1 := 100
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main() {
	test()
	fmt.Println("main.....")
}

2)自定义错误

package main

import ("fmt"
        "errors"
       )

func readConf(name string) (err error) {
    //如果输入文件名错误,则返回一个自定义的错误
    if name == "config.ini" {
        return nil
    } else {
        return errors.New("读取文件错误..")
    }
}

func test() {
    err := readConf("config.ini")
    if err != nil {
        //如果读取文件错误,输出这个错误,并终止程序
        panic(err)
    }
    
    fmt.Println("继续执行程序")
}

func main() {
	
}

2.9 数组

2.9.1 一维数组

1)数组的遍历:for-range

array := [...]int{1,2,3}
for index,value := range array{
    fmt.Println(value)
}

①第一个index返回的是数组下标

②第二个value则是值

③仅为for循环里的局部变量

④可以用“_”来省略下标值

⑤index,value可以自定义变量名

2)注意事项

①var arr []int 这里的arr为slice切片

②数组创建后,如果没有赋值,则有默认值

1.数值型(整数、浮点数等等)=> 0

2.字符串 =>""

3.布尔类型 => false

2.9.2 二维数组

遍历:

1)双重for循环

2)for-range遍历二维数组

for i, v := range arr {
    for j, v2 := range v {
        fmt.Println("arr[%v][%v] = %t", i, j, v2)
    }
}

2.10 切片

2.10.1 基本介绍

1)切片是数组的引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制

2)切片的长度是可以变化的,相当于一个动态变化的数组

3)演示:

package main

import "fmt"

func main() {
    var intArr [5]int = [...]int{1,22,5,5,6}
    //定义一个切片
    //slice := intArr[1:3]
    //intArr[1:3] 表示slice 引用到intArr这个数组
    //引用intArr数组的起始下标为1 ,最后下标为3(不包括3)
    slice := intArr[1:3]
    
    fmt.Println("slice元素 =",slice) // 22,33
    fmt.Println("slice元素的个数 =",len(slice))//2
    fmt.Println("slice容量 =",cap(slice))//切片的容量可以动态变化
}

2.10.2 切片内存布局

说明:1.slice为引用类型

2.slice从底层来说相当于一个结构体:

type slice struct{

​ ptr *int //储存其中的数据的地址

​ len int //slice元素的个数

​ cap int //slice的容量

}

2.10.3 切片的使用

1)定义一个切片,然后让切片去引用一个创建好的数组,如上例

2)通过make来创建切片

func main() {
	var slice []int = make([]int,4,10) //数据类型、len、cap容量,make创建一个切片对应的数组不可见,只能通过slice请访问数据
	slice[0] = 100
	slice[2] = 200
}

3)

func main() {
    var slice []string = []string{"jose","joe","jojo"}
}

4)区别

①方式一和方式二的区别在于,方式一直接引用一个事先存在的数组为可见;而方式二通过make创建切片的同时也会创建一个数组,由切片在底层维护,不可见

5)注意:

①slice := arr[:3] //默认为0-3(不包括3)同时[0:]默认为0-len(arr)

②切片可以继续切片

slice2 := slice[0:1]

③追加元素

var slice1 []int = []int{100,200,300}

slice2 = append(slice1,400,500,600) //这里append是创建了新内存,slice1并未改变

slice3 = append(slice3,slice2…)

④切片的拷贝

copy(pare1,pare2)两者都是切片类型,两者空间独立互不干扰

⑤string和slice

(1)string数组的值是不可变的,但是可以通过先将string转换[]byte切片进行修改,在转换为string数组

str := "hello"
arr1 := []byte(str)
arr1[0] = z
str = string(arr1)  //这种方法虽然可以改变string数组值,但是string中有中文就不可以

//如果string有中文

arr2 := []rune(str)
arr2[0] = "被"
str = string(arr2)

2.10.4 切片的遍历

package main
import "fmt"

func main() {
    //常规的for循环遍历
    var arr [5]int = [...]int{1,2,3,4,5}
    slice := arr[1:4]
    
    for i:= 1; i < len(slice); i++{
        fmt.Println("slice[%v]=%v", i, slice[i])
	}
    
    //使用for-range方式遍历
    for i, v:= range slice {
        fmt.Println(i," ",v)
    }
}

2.11 map

2.11.1 基本语法

var 变量名 map[keyty]valuetype

//例子:var a map[string]string
//var a map[string]map[string]string

注意:声明是不会分配内存的,初始化需要make,分配后才能赋值和使用

package main
import "fmt"

func main() {
    //方式一
    var a map[string]string
    a = make(map[string]string,10) //make分配空间
    a["no1"] = "joke"
    a["no2"] = "sandy"
    fmt.Println(a)
    
    //方式二
    b := make(map[string]string)
    b["no1"] = "hehe"
    fmt.Println(b)
    
    //方式三
    c = map[string][string]{
        "no1" : "wj",
        "no2" : "wjj"
    }
    setStu := make(map[string]map[string]string)
    setStu["stu1"] = make(map[string]string,3)
    
    setStu["stu1"]["name"] = "tome"
    setStu["stu1"]["sex"] = "man"
    setStu["stu1"]["age"] = "16"
    
    fmt.Println(setStu)
    fmt.Println(setStu["stu1"])
    fmt.Println(setStu["stu1"]["name"])
    //map的keyty不可以重复,如果重复以最后一个为准
    //map里面是无序的
}

2.11.2 map的增删改查

package main
import "fmt"

func main() {
    var a map[string]string
    a = make(map[string]string,10)
    a["no1"] = "joke"
    a["no2"] = "sandy"
    fmt.Println(a)
    
    //改
    //key已存在
    a["no1"] = "wj"
    fmt.Println(a)
    
    //删除
    //当指定的key不存在时,既不删除也不报错
    //如果要一次性删除完,可以遍历所有key逐个删除,或者采用map = make(...),make一个新的空间,让原来的成为垃圾,被gc回收-》 a = make(map[string]string)
    delete(a,"no1")
    
    //查找
    val,ok := a["no1"]
    if ok{
        fmt.Println("找到了欸")
    }else{
        fmt.Println("没有欸")
    }
    
}

2.11.3 map的遍历

package main
import "fmt"

func main() {
    var a map[string]string
    a = make(map[string]string,10)
    a["no1"] = "joke"
    a["no2"] = "sandy"
    a["no3"] = "bj"
    fmt.Println(a)
    
    for i,v := range a {
        fmt.Printf("i=%v v=%v\n",i,v)
    }
    
    setStu := make(map[string]map[string]string)
    setStu["stu1"] = make(map[string]string,3)
    
    setStu["stu1"]["name"] = "tome"
    setStu["stu1"]["sex"] = "man"
    setStu["stu1"]["age"] = "16"
    
    for k1, v1 := range setStu {
        fmt.Println("k1=",k1)
        for k2, v2 := range v1 {
             fmt.Printf("\t k2=%v v2=%v",k2,v2)
        }
        fmt.Println()
    }
}
    

2.11.4 map切片

package main
import "fmt"

func main() {
    var monster = []map[string]string
    monster = make(map[string]string, 2)
    if monster[0] == nil {
        monster[0] = make(map[string]string, 2)
        moster[0]["name"] = ["吴青峰"]
        moster[0]["age"] = ["600"]
    }
    if monster[1] == nil {
        monster[0] = make(map[string]string, 2)
        moster[0]["name"] = ["li青峰"]
        moster[0]["age"] = ["700"]
    }
    
    //动态增加
    newMonster := map[string]string{
        "name" : "老刘",
        "age"  : "600",
    }
    monster = append(monster,newMonster)
}

2.11.5 排序

package main
import (
    "fmt"
    "sort"
       )
func main() {
    map1 := make(map[int]int,10)
    map1[1] = 13
    map1[7] = 12
    map1[6] = 14
    map1[3] = 122
    map1[2] = 1111
    
    //先将map的key放入切片
    //对切片排序
    //遍历切片,然后按照key来输出map的值
    
    var key []int
    for k,_ := range map1 {
        keys = append(keys,k)
    }
    
    sort.Ins(keys)
    fmt.Println(keys)
    
    for _, k := range keys{
        fmt.Println(map1[k])
    }
}

2.11.6 使用细节

1)map是引用类型,一个函数接受map,修改后,原map的值也会改

2)map的容量达到后,再让map增加元素,会自动扩容,并不会发生panic,其能动态增长

3)map的value也经常使用struct类型

2.12 struct结构体

2.12.1 快速入门

package main
import "fmt"

//结构体名大写公有,小写私有
//如若在结构体中声明了指针、slice、map则零值为nil,需要make才能分配空间使用
type Cat struct {
    Name string
    Age int
    
}

func main() {
    var cat1 Cat
    cat1.Name = "blank"
    cat1.Age = 16
    
    var c4 Cat = Cat{"fadsf" , 16}
    //创建一个指针
    var c2 *Cat = new Cat)
    //正常来写为 (*c2).Name = "fadsf"
    c2.Name = "dsfads" //编译器自动在底层转为*c2
    fmt.Println(*c3)
    
    var c3 *Cat = &Cat{} //var c3 *Cat = &Cat{"fadsf" , 16}
    c3.Name = "fadsdaf"  //编译器自动在底层转为*c2
    fmt.Println(*c3)
}

2.12.2 使用细节

1)结构体里面的属性内存都是连续的分布(其中指针的地址为连续,但是指向的空间不一定连续)

2)两个结构体相互转换时候,两者的类型以及成员变量要全部相同

3)结构体进行type重新定义(相当于取别名),golang认为是新的数据类型,可以进行强转

stu = Studnet(stu1)

2.12.3 方法

1)方法的声明和调用

type A struct {
	Num int
}

func (a A)test() {
	fmt.Println(a.Num)
}

🥰func(a A) test() {} 表示 A 结构体有一种放法,名为 test

🥰(a A) 体现了test方法是和A 类型绑定的

举例:

package main
import "fmt"

type Person struct {
    Name string
}

func (p Person) test() {
    fmt.Println("test() name",p.Name)
}

func main() {
    var p Person
    p.Name = "zhangsna"
    p.test() //test()方法只能由Person对象调用
    (&p).test()//同样会调用test函数,同样会在test函数里拷贝p,即 p.test()==(&p).test(),若用(&p).test()改变其中的值,而外部的p依然没有改变
    /*若有func (p *Person) test() {
    fmt.Println("test() name",p.Name)
    } 则可以改变main函数p的值
    但p.test()还是(&p).test()还是一样的作用
    */
}

😴

func (a A)test() {
	fmt.Println(a.Num)
}
//其中 A可以是结构体也可以是其他数据类型
//若想调用此方法,需要相应的数据类型的变量

2.12.4 工厂模式

说明:当前包里面的声明的结构体首字母为小写,而又需要再其他包,如main包里面创建这个包的实例,则需要引入工厂模式。

实例:

package main

import (
	"fmt"
	"go_code/factory/model"
)

func main() {
	
	var stu = model.NewStudent("wj",88)
	fmt.Println(stu)
}  

package model:

package model

type student struct {
	Name string
	Age int
}

//由于student为小写,因此想在其他包里使用只能用工厂模式
 func NewStudent(n string, s int) *student{
	 return &student{      //返回一个指针,即这个空间都可以用
		 Name : n,
		 Age : s,
	 }
 }

🔔同样倘若结构体里有小写的成员变量,则也需要提供一个方法

例子:

package model

type student struct {
	Name string
	age int
}

func (s *student) Getage() int {  //得到小写变量的方法
    return s.age
}

2.13 面向对象

2.13.1 快速入门

package main
import(
	"fmt"
)

type Account struct {
	AccountNo string
	Pwd string
	Balance float64
}

//存款
func (account *Account) Deposite(money float64, pwd string) {

	if pwd != account.Pwd {
		fmt.Println("你输入的密码错误~~")
		return
	}

	if money <= 0|| money >= account.Balance {
		fmt.Println("你输入的存款金额错误~~")
	    return
	}

	account.Balance -= money
	fmt.Println("取款ok~~")
}

func (account *Account) Query(pwd string) {

	if pwd != account.Pwd {
		fmt.Println("密码错误~~~")
	}

	fmt.Println("账号余额为:",account.Balance)
}

func main() {
	account := Account{
		AccountNo : "sadfasd",
		Balance : 1000.6,
		Pwd : "12345",
	}

	account.Query("12345")
}

2.13.2 封装

1)隐藏细节

2)可以对数据进行验证,保证安全合理

3)对结构体中属性变量首字母小写进行封装

4)通过方法, 包 实现封装

5)通过一个首字母大写的Set方法对属性判断赋值

func (var结构体类名) Setxxx(){
    
}

6)通过一个首字母大写的Get方法获取其属性

func (var结构体类名) Getxxx(){
    
}

7)Golang中没有特别强调封装

快速案例

package model
import(
	"fmt"
)

type person struct {
	Name string             //公开的属性Name
	sex string				//要采用Set和Get方法
	age int
}

func NewPerson(name string) *person {     //采用工厂函数,才能在其他包里使用,相当于构造函数
    return &person{
       Name = name,
    }
}
func (p *person) SetAge(age int){
    if age > 0&&age < 150 {
        p.age = age
    }else{
        fmt.Println("年龄范围不对")
    }
}
func(p *person) GetAge() int{
    return p.age
}

main 包

package main
import (
	"fmt"
    "go_code/chapter09/model"
)

func main() {
    p := model.person("smith")
    p.SetAge(18)
    fmt.Println(p.Name,"age = " , p.GetAge)
}

2.13.3 继承

1)基本语法

type Goods struct {
    Name string
    Price int
}

type Book struct {
    Goods // 这里就是嵌套匿名结构体Goods
    Writer string
}

2)快速入门案例

package main
import(
	"fmt"
)

type Student struct {
    Name string
    Age int
    Score int
}

func(stu *Student) ShowInfo(){
    fmt.Println(stu.Name,stu.Age,stu.Score)
}
func (stu *Student) SetScore(score int) {
    stu.Score = score
}

type Pupil struct{
    Student     //嵌入Student匿名结构体名
}

func (p *Pupil) testing() {
    fmt.Println("小学生的方法")
}

type Graduate struct {
    Student     //嵌入Student匿名结构体名
}
func (p *Graduate) testing() {
    fmt.Println("大学生的方法")
}

func main() {
    pupil := &Pupil{}
    pupil.Student.Age = 8
	pupil.testing()
}

3)注意

①结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大小写字段和方法都可以使用

//b.A.name = "fasd"  == b.name   前提是b中没有name这个变量  

③结构体可以嵌入多个匿名结构体,若其中有相同的方法名或者属性,则需要指明匿名结构体来区分

④如果一个struct嵌套了一个有名结构体,这种模式就是组合,则在访问组合的结构体的字段或者方法,需要带上结构体名

type D struct{
    Name string
}
type B struct{
    d D
}
func main() {
    var b B
    b.d.Name =" sdf"  //这里必须带上匿名结构体名
}

⑤嵌套结构体后,可以在创建结构体变量时,指定各个匿名结构体字段的值

type D struct{
    Name string
}
type H struct{
    Sex string
}
type B struct{
    D
    H
}
func main() {
    var b B
    b = B{
        D{
            Name : "fads",
        },
        H{
            Sex : "fasd",
        }}
}
type D struct{
    Name string
}
type H struct{
    Sex string
}
type B struct{
    *D
    *H
}
func main() {
    var b B
    b = B{
        &D{
            Name : "fads",
        }&H{
            Sex : "fasd",
        }}
    fmt.Println(*b.D,*b.H)
}

3)多重继承

type D struct{
    Name string
}
type H struct{
    Sex string
}
type B struct{    //多重继承
    D
    H
}
func main() {
    var b B
    b = B{
        D{
            Name : "fads",
        },
        H{
            Sex : "fasd",
        }}
}

✨为了保证代码的简洁性,建议不使用多重继承

2.13.4 接口

1)基本语法

type Usb interface{   //接口定义
    //声明两个方法
    Start()
    Stop()
}

type Phone struct {
    
}

type Camera struct{
    
}

func (p Phone) Start() {
    fmt.Println("work.....")
}
func (p Phone) Stop() {
    fmt.Println("over.....")
}

func (c Camera) Start() {
    fmt.Println("work.....")
}
func (c Camera) Stop() {
    fmt.Println("over.....")
}


func Computer struct {
    
}
//接收一个Usb接口类型的变量
func (c Computer) Working (usb Usb){
    usb.Start()
    usb.Stop()
}

func main() {
    computer := Computer{}
    phone := Phone{}
    camera := Camera{}
    
    computer.Working(phone)
}

2)Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中所有的方法,那么这个变量就实现了这个接口。因此Golang中没有implement这样的关键字

3)接口注意事项和细节

package main

import(
	"fmt"
)
type AInterface interface{
    Say()
}

type Stu struct {
    Name string
}
//①
func (stu Stu) Say() {
    fmt.Println("stu Say()")
}


//②
type interger int
func (i integer) Say() {
    fmt.Println("integer Say i =",i)
}


//③
type BInterface interface{
    Hello()
}
type Moster struct{
    
}
func(m Monster) Hello() {
    fmt.Println("hello...")
}
func(m Monster) Say() {
    fmt.Println("say hello...")
}
func main(){
    
    //①接口本身不能创建实例,但是可以指向一个实现该接口的自定义类型的变量
    var stu Stu
    //var a AInterface = stu  这样就是错误
    var a AInterface = stu   //√
    a.Say()
    
    //②只要是自定义的数据类型,就可以实现接口,不仅仅是结构体类型
    var i integer = 20
    var b AInterface = i
    b.Say()
    
    //③一个自定义类型可以实现多个接口
    var monster Monster
    var a2 AInterface = monster
    var b2 BInterface = monster
    a2.Say()
    b2.Hello()
}

🤖Golang中接口中不能有任何变量

👽一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时候要实现A接口,则必须将B,C接口的方法也实现

package main

import(
	"fmt"
)
type AInterface interface{
    Say()
}
type BInterface interface{
    Hello()
}
type CInterface interface{
    AInterface
    BInterface
    test()
}
type Stu struct {
    
}
func (stu Stu)Say(){
    
}
func (stu Stu)Hello(){
    
}
func (stu Stu)test(){
    
}
func main() {
    var stu Stu
    var c CInterface = stu
    a.test()
}

👹interface是一个引用类型,如若未初始化,则未nil

👾空接口interface{},可以把任何变量赋给interface

4)经典案例:给切片排序

package main

import(
	"fmt"
	"math/rand"
	"sort"                       //调用sort中 interface接口
)
 //声明Hero结构体
type Hero struct{
	Name string
	Age int
}
//声明一个Hero结构体切片
type HeroSlice []Hero


//如下:实现Inerface接口
func (hs HeroSlice) Len()int{
	return len(hs)
}
//Less方法就是决定你使用了什么标准进行排序
//1.按Hero的年龄从小到大排序
func (hs HeroSlice) Less(i, j int) bool {
	return hs[i].Age < hs[j].Age               //升序,若返回true 就调用Swap进行排序
}
func (hs HeroSlice) Swap(i, j int) {
	temp := hs[i]
	hs[i] = hs[j]
	hs[j] = temp
}

func main() {
	var heroes HeroSlice
	for i := 0; i < 10; i++{
		hero := Hero{
			Name : fmt.Sprintf("英雄~%d",rand.Intn(100)),
			Age : rand.Intn(100),
		}
		heroes = append(heroes,hero)
	}

	for _,v := range heroes{
		fmt.Println(v)
	}
	//排序后
	fmt.Println("排序后..")
	sort.Sort(heroes)
	for _,v := range heroes{
		fmt.Println(v)
	}
}

2.13.5 继承和结构体的区别

1)接口可以当作继承的补充

2)继承的价值在于代码的复用性和可维护性

3)接口的价值在于设计好规范(方法),让其他自定义的类型去实现它

2.13.6 多态

1)多态参数

如在接口中的例子,usb既能接收phone的变量也能接收从camera的变量

2)多态数组

package main

import(
	"fmt"
)
type Usb interface{   //接口定义
    //声明两个方法
    Start()
    Stop()
}

type Phone struct {
	Name string
}

type Camera struct{
    Name string
}

func (p Phone) Start() {
    fmt.Println("手机work.....")
}
func (p Phone) Stop() {
    fmt.Println("手机over.....")
}

func (c Camera) Start() {
    fmt.Println("相机work.....")
}
func (c Camera) Stop() {
    fmt.Println("相机over.....")
}


func main() {
    //定义一个Usb接口数组,可以存放Phone和Camear的结构体变量
	//这里就是多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"小米"}
	usbArr[1] = Phone{"华为"}
	usbArr[2] = Camera{"索尼"}

	for i := 0; i < len(usbArr); i++{
		usbArr[i].Start()
		usbArr[i].Stop()
	}
	fmt.Println(usbArr)
}
//结果
/*  
手机work.....
手机over.....
手机work.....
手机over.....
相机work.....
相机over.....
[{小米} {华为} {索尼}]
*/
②类型断言
//例一
package main

import(
	"fmt"
)

type Point struct{
	x int
	y int
}
func main() {
	var a interface{}
	var point Point = Point{1,2}
	a = point //true

	var b Point
	// b = a  为错误写法

	b =a.(Point)   //将a转换,及判断a是否未指向Point的变量,如果是就转换成Point类型,相反编译器报错
	fmt.Println(b)
    
    //类型断言2
    var x interface{}
	var b2 float64 = 1.1
	x = b2
	//检测是否错误
	if y, ok := x.(float32); ok{
		fmt.Println("success")
		fmt.Println("y= ", y)
	}else {
		fmt.Println("fail")
	}
	fmt.Println("继续执行......")
}

例二

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

2.14 文件操作

2.14.1基本介绍

1)流:数据在数据源(文件)和程序(内存)之间经历的路径

输入流:数据从数据源(文件)到程序(内存)的路径

输出流:数据从程序(内存)到数据源(文件)的路径

2)os.File封装所有文件相关操作,“os”为一个包,File为结构体

例如:func Open 和 func Close使用

package main

import (
	"fmt"
	"os"
)

func main(){
	//打开文件
	//file  叫file对象
	//file  叫file指针
	//file  叫file文件句柄
	file, err := os.Open("d:/test.txt")  //如果不出错需要在相应位置有此文件
	if err != nil {
		fmt.Println("open file err=",err)
	}

	fmt.Println("file=",file)

	//关闭文件
	err = file.Close()
	if err != nil {
		fmt.Println("close file err=",err)

	}
}

2.14.2 读取文件

1)读取文件内容并显示在终端(带缓冲区的方式)

package main

import (
	"fmt"
	"os"
	"bufio"
	"io"
)
func main(){
	//打开文件
	file, err := os.Open("d:/test.txt")  
	if err != nil {
		fmt.Println("open file err=",err)
	}
	defer file.Close()  //及时关闭file句柄,否则会有内存泄漏

	//创建一个 *Reader,是带缓冲的
	/*
	const {
		defaultBufSize = 4096  //默认的缓冲区为4096
	}
	*/

	reader := bufio.NewReader(file)
	//循环的读取文件的内容
	for{
		str, err := reader.ReadString('\n') //读到一个"\n""就结束了,str存放内容,err存放错误信息
		if err == io.EOF{  //表示文件末尾
			break
		}
		fmt.Print(str)
	}
	fmt.Println("读取文件结束~~~~")
}

2)一次性打开(文件小,若文件大则会效率低)

package main

import (
	"fmt"
	"io/ioutil"
)
func main(){
	file := "d:/test.txt"
	content, err := ioutil.ReadFile(file)    //content是[]byte
	 if err != nil {
		 fmt.Printf("read file err=%v",err)
	 }
	 fmt.Println(string(content))
}

2.14.3 创建文件并写入内容

1)基本介绍

func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

说明:os.OpenFile是一次性打开文件的方法,他会使用指定的选项、指定的模式打开指定名称的文件。

第二个参数:文件打开模式(可以组合)

第三个参数:权限控制

2)案例

//Constants包
const (
    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  // 如果可能,打开时清空文件
)
①//创建一个新文件,写入内容5句“hello world”
package main

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

func main() {
    //1.打开文件
    filePath := "d:/abc.txt"
    file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
    if err != nil {
        fmt.Printf("open file err=%v\n",err)
        return
    }
    //及时关闭file句柄
    defer file.Close()
    
    //准备写入内容
    str := "hello,world\n"
    
    //写入时,使用带缓存的 *Writer
    writer := bufio.NewWriter(file)  //返回一个writer
    for i := 0; i < 5; i++{
        writer.WriteString(str)
    }
    
    //因为writer是带缓存,因此在调用WriterString方法时候,其实内容先写入缓存的。
    //因此要调用Flush方法,将缓冲的数据真正写入到文件中!!!!
    writer.Flush()
}
②//打开一个文件,将文件的原来的内容覆盖为5句“blablablabla”
package main

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

func main() {
    //1.打开文件
    filePath := "d:/abc.txt"
    file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC, 0666)  //os.O_TRUNC打开文件并清空内容
    if err != nil {
        fmt.Printf("open file err=%v\n",err)
        return
    }
    //及时关闭file句柄
    defer file.Close()
    
    //准备写入内容
    str := "blablablabla\n"
    
    //写入时,使用带缓存的 *Writer
    writer := bufio.NewWriter(file)  //返回一个writer
    for i := 0; i < 5; i++{
        writer.WriteString(str)
    }
    
    //因为writer是带缓存,因此在调用WriterString方法时候,其实内容先写入缓存的。
    //因此要调用Flush方法,将缓冲的数据真正写入到文件中
    writer.Flush()
}
③//追加内容“ABC,哈哈哈”
package main

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

func main() {
    //1.打开文件
    filePath := "d:/abc.txt"
    file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)//os.O_APPEND写操作时将数据附加到文件尾部
    if err != nil {
        fmt.Printf("open file err=%v\n",err)
        return
    }
    //及时关闭file句柄
    defer file.Close()
    
    //准备写入内容
    str := "ABC,哈哈哈\n"
    
    //写入时,使用带缓存的 *Writer
    writer := bufio.NewWriter(file)  //返回一个writer
    for i := 0; i < 5; i++{
        writer.WriteString(str)
    }
    
    //因为writer是带缓存,因此在调用WriterString方法时候,其实内容先写入缓存的。
    //因此要调用Flush方法,将缓冲的数据真正写入到文件中
    writer.Flush()
}
④//打开一个存在的文件,将原来的内容读出显示在终端
package main

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

func main() {
    //1.打开文件
    filePath := "d:/abc.txt"
    file, err := os.OpenFile(filePath, os.O_RDONLY | os.O_APPEND, 0666)// os.O_RDONLY只读模式打开文件,os.O_APPEND写操作时将数据附加到文件尾部
    if err != nil {
        fmt.Printf("open file err=%v\n",err)
        return
    }
    //及时关闭file句柄
    defer file.Close()

	//先读取原来文件的内容,并显示在终端
	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('\n')
		if  err == io.EOF{
			break
		}
		fmt.Print(str)
	}
    
    //准备写入内容
    str := "ABC,wj\n"
    
    //写入时,使用带缓存的 *Writer
    writer := bufio.NewWriter(file)  //返回一个writer
    for i := 0; i < 5; i++{
        writer.WriteString(str)
    }
    
    //因为writer是带缓存,因此在调用WriterString方法时候,其实内容先写入缓存的。
    //因此要调用Flush方法,将缓冲的数据真正写入到文件中
    writer.Flush()
}

2.14.4 拷贝文件

package main

import (
	"fmt"
	"io/ioutil"
)
func main() {
//1.将abc.txt文件读取内容
//2.将读取到的内容写入到kkk.txt
    
    file1Path := "d:/abc.txt"
    file2Path := "d:/kkk.txt"
    
    data, err := ioutil.ReadFile(file1Path)
    if err != nil {
        //说明读取文件有错误
        fmt.Println("read file err=%v", err)
        return
    }
    
    err := ioutil.WriteFile(file2Path, data, 0666)
    
    if err != nil{
        fmt.Printf("write file error = ", err)
    }
}

2)拷贝文件(图片/电影/mp3)

package main

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

    //自己编写一个函数,接收两个文件的路径
func CopyFile(dstFileName string, srcFileName string)(written int64, err error){  
    srcfile, err := os.Open(srcFileName)
    if err != nil {
        fmt.Printf("open file err = %v\n", err)
    }
    //通过srcfile,获取Reader
    reader := bufio.NewReader(srcfile)
    defer srcfile.Close()

    //打开desFileName
    dstFile,  err := os.OpenFile(dstFileName, os.O_WRONLY | os.O_CREATE, 0666)
    if err != nil {
        fmt.Printf("open file err=%v\n", err)
        return
    }

    //通过dstFile,获取到Writer
    writer := bufio.NewWriter(dstFile)
    defer dstFile.Close()

    return io.Copy(writer, reader)//io包里面的Copy函数
}
func main() {
    srcFile := "d:/test/picture.png"
    dstFile := "d:/abc.png"
    CopyFile(dstFile, srcFile)
}

2.14.5 统计不同类型的字符

package main

import (
	"fmt"
    "bufio"
    "os"
    "io"
)
type CharCount struct {
    ChCount int  //记录英文字母
    NumCount int //记录数字的个数
    SpaceCount int //记录空格的个数
    OtherCount int //记录其他字符的个数
}

func main() {
    //打开一个文件,创建一个Reader
    //没读取一行,就去统计改行有多少个英文、数字、空格和其他字符
    //然后将结果保存到一个结构体中
    fileName := "d:/abc.txt"
    file, err := os.Open(fileName)
    if err != nil {
        fmt.Printf("err = %v\n", err)
    	return
    }
    defer file.Close()
    //定义一个CharCount实例
    var count CharCount
    //创建一个Reader
    reader := bufio.NewReader(file)
    
    //开始循环读取fileName的内容
    for {
        str, err := reader.ReadString('\n')
        if err == io.EOF{
            break
        }
    }
    //遍历str,进行统计
    for _, v = range str {
        fmt.Println(str)
    }
}

2.14.5 统计文件有多少个英文、数字、空格和其他字符

package main

import (
	"fmt"
    "bufio"
    "os"
    "io"
)
type CharCount struct {
    ChCount int  //记录英文字母
    NumCount int //记录数字的个数
    SpaceCount int //记录空格的个数
    OtherCount int //记录其他字符的个数
}

func main() {
    //打开一个文件,创建一个Reader
    //没读取一行,就去统计改行有多少个英文、数字、空格和其他字符
    //然后将结果保存到一个结构体中
    fileName := "d:/abc.txt"
    file, err := os.Open(fileName)
    if err != nil {
        fmt.Printf("err = %v\n", err)
    	return
    }
    defer file.Close()
    //定义一个CharCount实例
    var count CharCount
    //创建一个Reader
    reader := bufio.NewReader(file)
    
    //开始循环读取fileName的内容
    for {
        str, err := reader.ReadString('\n')
        if err == io.EOF{     //读到文件末尾退出
            break
        }
        //遍历str,进行统计
        for _, v := range str {
        
            switch  {        //这里不用v来匹配,而是当作if分支结构语句
            case v >= 'a' && v <= 'z':
                fallthrough //穿透,即强制执行下一行语句
            case v >= 'A' && v <= 'Z':
                    count.ChCount++
            case v == ' ' || v == '\t':
                    count.SpaceCount++  
            case v >= '0' && v <= '9':
                    count.NumCount++
            default :
                    count.OtherCount++       
            }
        }
    }

    //输出统计个数
    fmt.Printf("字符的个数为=%v 数字个数为=%v 空格个数为=%v 其他字符个数为=%v",
    count.ChCount, count.NumCount,  count.SpaceCount, count.OtherCount)
}

2.14.6 flag包来解析命令行参数

package main

import (
	"fmt"
    "flag"
)

func main ()  {
    var user string
    var pwd string
    var host string
    var port int

    //user就是接收用户命令行中输入的 -u 后面的参数值
    flag.StringVar(&user,"u","","用户名,默认为空")
    flag.StringVar(&pwd,"pwd","","密码,默认为空")
    flag.StringVar(&host,"host","localhost","主机名,默认为空")
    flag.IntVar(&port,"port",3306,"端口号,默认为3306")

    //转换
    flag.Parse()

    //输出结果
    fmt.Printf("user=%v pwd=%v host=%v port=%v",user,pwd,host,port)
}

终端: D:\Go代码\src\go_code\chapter12> go build -o test.exe test.go

D:\Go代码\src\go_code\chapter12> test.exe -u root -pwd 132465 -host
127.0.0.5 -port 8080

2.15 测试函数是否执行正确

//cal_test文件
package main
import(
	_"fmt"
	"testing" //引入go中testing框架
)

//编写测试案例,去测试addUpper是否正确
func TestAddUpper(t *testing.T)  {
	
	//调用
	res := addUpper(10)
	if res != 55 {
		t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
	}

	//如果正确,输出日志
	t.Logf("AddUpper(10) 执行正确....")
}
//被测试文件
package main

//一个被测试函数
func addUpper(n int) int{
	res := 0
	for i := 1; i <= n - 1; i++ {
		res += i
	}
	return res
}

结果:

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

函数执行原理图:

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

注意事项:

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

Count int //记录空格的个数
OtherCount int //记录其他字符的个数
}

func main() {
//打开一个文件,创建一个Reader
//没读取一行,就去统计改行有多少个英文、数字、空格和其他字符
//然后将结果保存到一个结构体中
fileName := “d:/abc.txt”
file, err := os.Open(fileName)
if err != nil {
fmt.Printf(“err = %v\n”, err)
return
}
defer file.Close()
//定义一个CharCount实例
var count CharCount
//创建一个Reader
reader := bufio.NewReader(file)

//开始循环读取fileName的内容
for {
    str, err := reader.ReadString('\n')
    if err == io.EOF{     //读到文件末尾退出
        break
    }
    //遍历str,进行统计
    for _, v := range str {
    
        switch  {        //这里不用v来匹配,而是当作if分支结构语句
        case v >= 'a' && v <= 'z':
            fallthrough //穿透,即强制执行下一行语句
        case v >= 'A' && v <= 'Z':
                count.ChCount++
        case v == ' ' || v == '\t':
                count.SpaceCount++  
        case v >= '0' && v <= '9':
                count.NumCount++
        default :
                count.OtherCount++       
        }
    }
}

//输出统计个数
fmt.Printf("字符的个数为=%v 数字个数为=%v 空格个数为=%v 其他字符个数为=%v",
count.ChCount, count.NumCount,  count.SpaceCount, count.OtherCount)

}


### 2.14.6 flag包来解析命令行参数

```go
package main

import (
	"fmt"
    "flag"
)

func main ()  {
    var user string
    var pwd string
    var host string
    var port int

    //user就是接收用户命令行中输入的 -u 后面的参数值
    flag.StringVar(&user,"u","","用户名,默认为空")
    flag.StringVar(&pwd,"pwd","","密码,默认为空")
    flag.StringVar(&host,"host","localhost","主机名,默认为空")
    flag.IntVar(&port,"port",3306,"端口号,默认为3306")

    //转换
    flag.Parse()

    //输出结果
    fmt.Printf("user=%v pwd=%v host=%v port=%v",user,pwd,host,port)
}

终端: D:\Go代码\src\go_code\chapter12> go build -o test.exe test.go

D:\Go代码\src\go_code\chapter12> test.exe -u root -pwd 132465 -host
127.0.0.5 -port 8080

2.15 测试函数是否执行正确

//cal_test文件
package main
import(
	_"fmt"
	"testing" //引入go中testing框架
)

//编写测试案例,去测试addUpper是否正确
func TestAddUpper(t *testing.T)  {
	
	//调用
	res := addUpper(10)
	if res != 55 {
		t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
	}

	//如果正确,输出日志
	t.Logf("AddUpper(10) 执行正确....")
}
//被测试文件
package main

//一个被测试函数
func addUpper(n int) int{
	res := 0
	for i := 1; i <= n - 1; i++ {
		res += i
	}
	return res
}

结果:

在这里插入图片描述

函数执行原理图:

在这里插入图片描述

注意事项:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值