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 | |
uint | 无 | 0~2的64次方-1 | |
rune | 有 | -2的31次方~2的31次方-1 | 等价int32,表示一个Unicode码 |
byte | 无 | 0~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
}
结果:
函数执行原理图:
注意事项: