Golang
基础语法
Golang执行流程分析
如果是对源码编译后,再执行,Go的执行流程如下图
如果是对源代码直接 执行 go run 源码,Go的执行流程如下图
编译
go build main.go
go build -o 自己命名 main.go -o 参数自定义文件名
Windows下是生成二进制exe可执行程序
Go程序开发注意事项
1)Go源文件以"go"为拓展名
2)Go应用程序的执行入口是main()方法
3)Go语言严格区分大小写
4)Go方法由一条条语句构成,每个语句不需要加分号(每行会自动加分号)
5)Go编译器是一行行进行编译的,因此我们一行只能写一行语句,不能多条语句写在同一个,否则报错
6)Go语言定义的变量或者import的包如果没有用到,代码不能编译通过
7)打括号都是成对出现 缺一不可
Go语言转义符
常用转义字符
1)\t 对齐
2)\n 换行符
3)\\ \
4)\" "号
5)\r 回车
变量
变量相当于内存中一个数据存储空间的表示
变量使用的基本步骤
1)声明变量
2)赋值
3)使用
变量使用的三种方式
1)指定变量类型,声明后若不赋值,使用默认值
2)根据值自行判定变量类型(类型推导)
3)省略var 注意:=左侧的变量不应该是已经声明过的,否则会导致编译错误
数据类型
基本数据类型
数值型:整数类型 (int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,byte)int类型默认是int32
浮点类型 (float32,float64)
字符型:没有专门的字符型,使用byte来保存单个字母字符 需要用’'来包裹 如 var a byte = ‘a’
布尔型:bool
字符串:string
派生/复杂数据类型
1.指针(pointer)
2.数组
3.结构体(struct)
4.管道(channel)
5.函数(也是一种类型)
6.切片(slice)
7.接口(interface)
8.map
基本类型转换
Golang在不同类型的变量之间赋值时需要显式转换,也就是说Golang中数据类型不能自动转换
基本语法
表达式T(v) 将值v转换成T类型
T:type类型
v:变量
package main
import (
"fmt"
"reflect"
)
func main() {
//数据类型转换
var i int = 100
//将i转换为float类型
var i1 = float32(i)
fmt.Println(i1, reflect.TypeOf(i1))
}
基本类型转换string类型 fmt.Sprintf(“格式化动作”,变量)
package main
import (
"fmt"
"strconv"
)
func main() {
// 基本数据类型转string类型
var num1 int = 99
var num2 float64 = 23.456
var b bool = true
var myChar byte = 'h'
var str string
//使用第一种方式来转换
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%t", b)
fmt.Printf("str type %T str=%q\n", str, str)
str = fmt.Sprintf("%c", myChar)
fmt.Printf("str type %T str=%q\n", str, str)
}
//第二种 strconv函数
var num3 int = 99
var num4 float64 = 23.456
var b2 bool = true
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatFloat(num4, 'f', 10, 64)
/* str = strconv.FormatFloat(num4, 'f', 10, 64)
说明:'f' 格式10:表示小数位保留10位 64:表示这个浮点数是float64
*/
fmt.Printf("str type %T str=%q\n", str, str)
str = strconv.FormatBool(b2)
fmt.Printf("str type %T str=%q\n", str, str)
//strconv包中有一个函数Itoa
var num5 int64 = 99
str = strconv.Itoa(int(num5)) // Itoa只能转换int类型 此处可以转成int类型
fmt.Printf("str type %T str=%q\n", str, str)
spring类型转为基本数据类型
package main
import (
"fmt"
"strconv"
)
func main() {
//string转基本类型
//string 转bool类型
var str string = "true"
var b bool
b, _ = strconv.ParseBool(str)
// b, _ = strconv.ParseBool(str)
//说明:strconv.ParseBool(str) 函数会返回两个值(value bool,err error) 使用,_忽略后面的值 只获取value bool
fmt.Printf("b type %T b=%v\n", b, b)
// string转int类型
var str2 string = "123"
var n1 int64
n1, _ = strconv.ParseInt(str2, 10, 8) //ParseInt方法返回int64 ,8代表此处你所转换的数值不能满足bitSize范围大小,会报错比方超过int64
fmt.Printf("n1 type %T n1=%v\n", n1, n1)
//string转浮点类型
var str3 string = "22.22"
var f float64
f, _ = strconv.ParseFloat(str3, 64)
fmt.Printf("f type %T f=%v\n", f, f)
}
数组
定义数组
var 数组名[数组大小] 数组类型
var hens [i]int
package main
import "fmt"
func main() {
//定义数组
var hens [6]float64
//给数组的每个元素赋值
hens[0] =3.1
hens[1] =1
hens[2] =2
hens[3] =6.6
hens[4] =8.7
//遍历数组
arrSum:=0.0
for i := 0; i < len(hens); i++ {
fmt.Println(hens[i]) //默认值为0
arrSum+=hens[i]
}
//求和与平均值
ageArr := fmt.Sprintf("%.2f",arrSum/6) //保留两位小数
fmt.Printf("该数组的和为:%v\n",arrSum)
fmt.Printf("该数组的平均值为:%v\n",ageArr)
}
默认情况下,数组的每个元素都被初始化为元素类型对应的零值,对于数字类型来说就是0
四种初始化数组的方式
//五种初始化数组的方式
var myArrays1 = []int{1,2,3}
fmt.Println(myArrays1)
var myArrays2 [3]int= [3]int{1,2,3}
fmt.Println(myArrays2)
var myArrays3 = [...]int{1,2,3} //...规定的写法
fmt.Println(myArrays3)
var myArrays4 = [...]int{0:200,1:100,2:300}
fmt.Println(myArrays4)
//类型推导
myArrays5:= [...]string{"小明","大毛","二毛"}
fmt.Println(myArrays5)
数组遍历
for-range结构遍历
fmt.Println(myArrays5)
for index,value := range myArrays5{
fmt.Println(index,value)
}
数组练习
package main
import
(
"fmt"
"math/rand"
"time"
)
func main() {
//打印A-Z的字母
var letters [26]byte
letters[0]='A'
for i := 0; i < 26; i++ {
letters[i] = letters[0]+byte(i)
}
for i := 0; i < 26; i++ {
fmt.Printf("%c ",letters[i])
}
fmt.Println()
//找出数组中整数的最大值 非冒泡查询
numArrays:=[...]int {10,2,3,4,5}
maxNum:=numArrays[0]
iArray:=0
for i := 0; i < len(numArrays); i++ {
if numArrays[i]>maxNum {
maxNum=numArrays[i]
iArray=i
}
}
fmt.Printf("numArray a Max: %v\n",maxNum)
fmt.Printf("numArrays a Index: numArrays[%v]\n",iArray)
//随机生成五个数,进行反转
var numArrays2 [5]int
rand.Seed(time.Now().UnixNano())
for i := 0; i < len(numArrays2); i++ {
numArrays2[i]=rand.Intn(100)
}
fmt.Printf("numArrays2替换前: %v\n",numArrays2)
temp := 0
temp = numArrays2[4]
numArrays2[4]=numArrays2[0]
numArrays2[0]=temp
fmt.Printf("numArrays2: %v\n",numArrays2)
}
切片
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
var 切片名[]类型
var testSlice[] int
切片的内存图
定义切片的三种方式
1)定义一个切片,使用切片去引用一个数组
var myArray = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice := myArray[1:3] // 前闭后开 只包含前面的 也就是不包含数组下标为3的
2)通过make来创建切片
基本语法:var切片名[]type=make([],len,[cap])
参数说明:type数据类型 len大小 cap 指定切片容量
指针
基本介绍
1) 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。使用 &
字符放在变量前面对变量进行“取地址”操作。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int、*int64、*string
等。
Pointer
2)获取变量的地址,用&,比如:var i int ,获取i的地址,&i
3)指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如
var ptr *int = &i
在指针类型前面加上*
号(前缀)来获取指针所指向的内容
键盘输入语句
在编程中,需要接收用户输入的内容,通过fmt.Scanln()或fmt.Scanf()来接受内容
package main
import "fmt"
func main() {
//使用Scanln方式接收数据
var name string
var age byte
var sal float32
var isPass bool
// fmt.Println("请输入你的姓名:")
// fmt.Scanln(&name)
// fmt.Println("请输入你的年龄:")
// fmt.Scanln(&age)
// fmt.Println("请输入你的薪水:")
// fmt.Scanln(&sal)
// fmt.Println("是否通过考试:")
// fmt.Scanln(&isPass)
// fmt.Printf("姓名:%v\n年龄:%v\n 薪水:%v \n 是否通过考试:%v \n",name,age,sal,isPass)
//使用Scanf方式接收数据
fmt.Printf("请输入你的姓名、年龄、薪水、是否通过考试 用空格分隔\n")
fmt.Scanf("%s %d %f %t",&name,&age,&sal,&isPass)
fmt.Printf("姓名:%v\n 年龄:%v\n 薪水:%v\n 是否通过考试:%v\n",name,age,sal,isPass)
}
流程控制
1)顺序控制
package main
import "fmt"
func main() {
var age int64
fmt.Println("请输入你的年龄:")
fmt.Scanln(&age)
if age>18{
fmt.Println("你年龄大于18岁要对自己的行为负责")
}else {
fmt.Println("小屁孩")
}
}
2)分支控制
Switch分支
package main
import "fmt"
func main() {
var key byte
fmt.Println("请输入一个字符:a,b,c,d,e,f,g")
fmt.Scanf("%c",&key)
switch key{
case 'a':
fmt.Println("周一")
case 'b':
fmt.Println("周二")
case 'c':
fmt.Println("周三")
case 'd':
fmt.Println("周四")
case 'e':
fmt.Println("周五")
case 'f':
fmt.Println("周六")
case 'g':
fmt.Println("周天")
default:
fmt.Println("编译错误")
}
}
case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default
default不是必须的
3)循环控制
代码循环
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
fmt.Println("我好帅!",i)
}
}
遍历
package main
import "fmt"
func main() {
//遍历方式1
var str string = "hello,world"
for i := 0; i < len(str); i++ {
fmt.Printf("%c\n",str[i])
}
//遍历方式2 for-range
str = "abc"
for index,val:=range str{
fmt.Printf("index=%v val=%c\n",index,val)
}
}
传统遍历中如果遍历的字符有中文,会出现乱码,因为遍历是单字节遍历的方式,utf-8编码对应的是3个字节
使用string转[]rune切片的方式来解决
str :=[]rune(str)//string转切片类型
函数
func 函数名(行参列表) (返回值列表){
执行语句…
返回值列表
}
1)函数的行参列表可以是多个,返回值列表也可以是多个
2)行参列表和返回值列表的数据类型可以是值类型和引用类型
3)函数命名需要像java public 首字母大写
4)函数内部的变量是局部的,函数外不生效
5)基本数据类型和数组默认都是值传递的,即进行值拷贝,在函数内修改,不会影响原来的值
6)如果希望函数内的变量能修改函数外的变量,可以穿入变量的地址&,函数内以指针的方式操作变量
7)Go函数不支持重载
package main
import "fmt"
func main() {
//主函数
var a int64 = 3
var b int64 = 1
var operator byte = '-'
result := cal(a, b, operator)
fmt.Println("result:", result)
}
func Cal(a int64, b int64, operator byte) int64 {
//自定义函数
var res int64
switch operator {
case '+':
res = a + b
case '-':
res = a - b
case '*':
res = a * b
case '/':
res = a / b
default:
fmt.Println("符号错误")
}
return res
}
包
打包基本语法:
package util
引入包:
Import “titl” (""内包的路径)
注意如果
GO111MODULE=“off”
默认使用GOPATH 环境变量路径(设置成项目路径)
GO111MODULE=“on”
在根目录下 go mod init 项目名
函数递归
1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2)函数的局部变量是独立的,不会相互影响
3)递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
4)当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时函数调用结束或返回后,该函数本身也会被销毁
init函数
每一个源文件都有一个init函数,该函数在main函数调用前会被go运行框架调用,init在main函数前调用
匿名函数
如果函数只使用一次,可以考虑使用匿名函数,匿名函数也可以多次被调用
package main
import "fmt"
//全局匿名函数
var TestFunc2 =func (n1 int,n2 int)int{
return n1 + n2
}
func main() {
//匿名函数
testFunc := func (a int,b int) int {
return a+b
}(1,2)
fmt.Println("result:", testFunc)
result := TestFunc2(1,2)
fmt.Println("result:", result)
}
全局匿名函数变量需要首字母大写
闭包
闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
package main
import "fmt"
//累加器
func addUpper() func(int)int{
var n int =10
return func(x int)int{
n +=1
return n
}
}
func main() {
result := addUpper()
fmt.Println("n=",result(1))
fmt.Println("n=",result(2))
fmt.Println("n=",result(3))
}
闭包的本质就是调用了外层变量并返回一个函数
函数defer
释放资源 延时机制
当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的栈(defer栈)
当函数执行完毕后,再从defer栈,按照先入后出的方式出栈执行
package main
import "fmt"
func sum(n1 int, n2 int) int {
defer fmt.Println("n1=",n1) //3 //先存起来,最后执行回来保留原结果不受外界干扰
defer fmt.Println("n2=",n2) //2 //先存起来,最后执行回来保留原结果不受外界干扰
n1++
n2++
res := n1+n2
fmt.Println("res=",res) //1
return res
}
func main() {
fmt.Println(sum(100, 100)) //4
}
项目案例:
func test(){
//关闭文件资源
file = openfile("文件名")
defer file.close
//向下执行,执行后关闭
}
函数参数的传递方式
1)值传递 基本数据类型int 、float、bool、string,数组和结构体struct
2)引用传递 (地址) 指针、slice切片、map、管道chan、interface
不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值拷贝,引用传递是地址的拷贝,一般来说,第值拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低
变量作用域
局部变量 作用域仅限函数内部
全局变量 整个包有效
系统函数
字符串常用函数
len(str) 判断字符串长度,根据字节判断,中文为3个字节
package main
import "fmt"
func main() {
//统计字符串的长度 按字节len(str)
str :="hello"
fmt.Println(len(str))
}
遍历是有中文字符需要转为[]rune()类型
package main
import "fmt"
func main() {
//统计字符串的长度 按字节len(str)
str :="hello你好"
fmt.Println(len(str))
str1 :=[]rune(str)
for i := 0; i < len(str1); i++ {
fmt.Printf("%c\n",str1[i])
}
}
字符串转整数,n,err :=strconv.Atoi(“12”)
n,err := strconv.Atoi("123")
if err != nil {
fmt.Println("错误",err)
}else{
fmt.Println("转成的结果",n)
}
整数转字符串, n,err :=strconv.Itoa(123)
str2 := strconv.Itoa(123)
if(err != nil){
fmt.Println("错误",err)
}else{
fmt.Printf("str类型:%T 转成的结果%v",str2,str2)
}
字符串转成[]byte
//字符串转为[]byte
str3 := []byte("hello")
fmt.Printf("%c",str3)
字符串替换
str4:=strings.Replace("go go hello", "go","go语言",n)
fmt.Printf("%v\n",str4)
字符串分隔
str5:=strings.Split("hello,1223,hell",",")
fmt.Printf("%v\n",str5)
大小写转换
//ToLower转小写
str6:=strings.ToLower("Go")
fmt.Printf("%v\n",str6)
// ToUpper转大写
str7:=strings.ToUpper("go")
fmt.Printf("%v\n",str7)
…剩下的看官方文档
时间日期相关函数
package main
import
(
"fmt"
"time"
)
func main() {
//日期时间相关函数
//获取当前时间
now :=time.Now()
fmt.Printf("类型是%T %v\n",now,now)
//通过now获取年月日,时分秒
fmt.Printf("年=%v\n",now.Year())
fmt.Printf("月=%v\n",now.Month())
fmt.Printf("年=%v\n",now.Day())
fmt.Printf("时=%v\n",now.Hour())
fmt.Printf("分=%v\n",now.Minute())
fmt.Printf("秒=%v\n",now.Second())
}
格式化日期时间
//格式化输出
fmt.Printf("%d-%d-%d %d:%d:%d",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Println(now.Format("2006-01-02 15:04:05"))
时间戳
func (t Time) Unix() int64
func (t Time) UnixNano() int64
testNow := time.Now()
sj:=testNow.Unix()
sj2:=testNow.UnixNano()
fmt.Println(sj)
fmt.Println(sj2)
内置函数
len(str) 判断字符串长度,根据字节判断,中文为3个字节
new() 用来分配内存,主要用来分配值类型,比如int、float32,struct…返回的是指针
num2 :=new(int)
*num2 = 200
fmt.Printf("num2的类型是:%T num2: %v", num2,*num2)
make 用来分配 内存,主要用来分配引用类型,比如chan、map、slice
错误处理
处理错误
package main
import
("fmt"
"time"
)
func test(){
//使用defer +recover 来捕获异常处理异常
defer func() {
err:=recover()
if err !=nil{
fmt.Println("异常错误是:",err)
}
}()
a:=10
b:=0
result:=a/b
fmt.Println(result)
}
func main() {
test()
for {
fmt.Println("下面的代码")
time.Sleep(time.Second)
}
}
自定义错误
使用errors.New和panic内置函数
1)errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
2)panic内置函数,接受一个interface{}类型的值(也就是任何值)作为参数。可以接受error类型的变量,输出错误信息,并退出程序
package main
import
(
"fmt"
"errors"
)
//读取ini.conf配置信息
//如果文件名传入不正确,我们就返回一个自定义错误
func readConfig(name string)(err error){
if name == "ini.conf"{
//读取...
return nil
}else {
//返回一个自定义错误
return errors.New("读取文件错误")
}
}
func myErr(){
err:=readConfig("ini.ff")
if err!=nil{
//如果发生错误,输出错误,并终止程序
panic(err)
}
fmt.Println("ok")
}
func main() {
//测试自定义错误的使用
myErr()
fmt.Println("下面的代码")
}
map
无序的,只可使用for range进行遍历
func Map() {
var m1 map[string]string
fmt.Println("m1==nil?", m1 == nil)
m1 = make(map[string]string, 2) //make(Type,初始Size) size可以省略,默认为1
m1["早晨"] = "敲代码"
m1["中午"] = "送外卖"
m1["晚上"] = "按摩店" // 如果按照新的key进行赋值,如果key存在就是修改,不存在就是新增key
fmt.Println("m1=", m1)
m2 := map[string]string{
"凌晨": "吃夜宵",
"梦里": "磨枪",
}
fmt.Println("m2=", m2)
//查找操作
v, ok := m2["早上"]
if ok {
fmt.Println("v=", v)
} else {
fmt.Println("vlaue不存在")
}
//删除操作
delete(m1, "晚上")
fmt.Println("m1=\n", m1)
// m1 = nil
m2 = make(map[string]string)
fmt.Println("m2=\n0", m2)
for k, v := range m1 {
fmt.Printf("m1[k]=%v \n m1[v]=%v", k, v)
}
}
delete操作
1)如果我们要删除map所有的key,没有专门一次性全部删除的方法,可以使用遍历key进行删除
2)或者map = make(…),make一个新的map让原来的成为垃圾,被gc删除
自定义数据类型
自定义数据类型
属于不同类型,需要类型转换
fmt.Println("自定义数据类型")
type mesType uint16
var u1000 uint16 = 1000
var textMes mesType = mesType(u1000)
fmt.Printf("textMesType= %T textMes= %v\n", textMes, textMes)
类型别名
属于相同类型,混用无需类型转换
fmt.Println("类型别名")
type myUint16 = uint16
var myu16 myUint16 = u1000
fmt.Printf("myu16Type= %T myu16= %v\n", myu16, myu16)
结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。用结构体的经典案例是处理公司的员工信息,每个员工信息包含一个唯一的员工编号、员工的名字、家庭住址、出生日期、工作岗位、薪资、上级领导等等。所有的这些信息都需要绑定到一个实体中,可以作为一个整体单元被复制,作为函数的参数或返回值,或者是被存储到数组中,等等。
结构体字段
结构体可以没有字段
结构体字段通过“.”访问
结构体指针
// 结构体
type User struct {
Name string
Id uint32
}
type Account struct {
User
password int64
}
type Contact struct {
*User
Remark string
}
func Struct() {
var u1 User = User{
Name: "张三",
}
u1.Id = 1000
var u2 *User = &User{
Name: "李四",
}
u2.Id = 1001 //(*u2).Id=10001
var a1 = Account{
User: User{
Name: u1.Name,
},
password: 123456,
}
var c1 *Contact = &Contact{
User: &User{},
Remark: "王麻子",
}
c1.Name = "王五"
fmt.Printf("Account Type: %T Account Value: %v\n", a1, a1)
fmt.Printf("Contact Type: %T Contact Value: %v c1.Name: %v\n", c1, c1, c1.Name)
}
字段/属性
注意事项和细节说明
1)字段声明语法同变量,示例:字段名 字段类型。
2)字段的类型可以为:基本类型、数组或引用类型。
3)在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则前面讲的一样:
布尔类型是false,数值是0,字符串是""
数组类型的默认值和他的元素类型相关,比如score [3]int 则为[0,0,0]
指针,slice,和map的零值都是nil,即还没有分配空间。
匿名函数
如果函数只使用一次,可以考虑使用匿名函数,匿名函数也可以多次被调用
方法
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。
//方法
type TestStru struct {
Name string
}
// 定义一个函数
func (p TestStru) TestFcun() {
p.Name = "jack"
fmt.Println("Celebrity()", p.Name)
}
func (p TestStru) Calculation() {
res := 0
for i := 0; i <= 1000; i++ {
res += i
}
fmt.Println(p.Name, "你的计算结果是", res)
}
func (p TestStru) Calculation1(n int) {
res := 0
for i := 1; i <= n; i++ {
res += i
}
fmt.Println(p.Name, "你的计算结果是", res)
}
func Celebrity() {
var p TestStru
p.Name = "tom"
p.TestFcun() // 只能使用这种方式调用
fmt.Println("Celebrity()", p.Name)
p.Calculation()
p.Calculation1(20)
}
// 方法案例 求面积
type Circle struct {
radius float64
}
// 生命一个方法area和Circle绑定,可以返回面积
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
//生命一个结构体circle,字段为 radius
//生命一个方法 area和circle绑定,可以返回面积
//创建一个Circle变量
func TestCircle() {
var c Circle
c.radius = 4.0
res := c.Area()
fmt.Println("面积是:", res)
}
方法调用案例
// 方法案例
type MethodUtils struct {
}
func (mu MethodUtils) Printf(m int, n int) {
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
fmt.Print(("*"))
}
fmt.Println()
}
}
// 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印10*8的矩形
func TestMu() {
var mu MethodUtils
mu.Printf(10, 8)
}
面向对象
封装
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作(方法),才能对字段进行操作。
封装的实现过程
type person struct {
Name string
age int //其他包不能进行访问
sal float64
}
// 写一个工厂模式的函数,相当于构造函数
func newPerson(name string) *person {
return &person{
Name: name,
}
}
// 为了访问age和sal 编写GET SET方法
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
}
func (p *person) SetSal(sal float64) {
if sal >= 3000 && sal < 30000 {
p.sal = sal
} else {
fmt.Println("薪水范围不正确")
}
}
func (p *person) GetSal() float64 {
return p.sal
}
func EncapsulationDemo() {
p := newPerson("smith")
p.SetAge(18)
p.SetSal(5000)
fmt.Println(p)
fmt.Println(p.Name, p.GetAge(), p.GetSal())
}
继承
代码复用 ,减少代码冗余
// 继承案例
// 编写一个学生考试系统
// 学生
type Student struct {
Name string
Age int
Score int
}
type Pupil struct {
Student //匿名结构体
}
// 显示他的成绩
func (stu *Student) ShowInfo() {
fmt.Printf("学生名=%v,年龄=%v,成绩=%v \n", stu.Name, stu.Age, stu.Score)
}
func (stu *Student) SetScore(score int) {
stu.Score = score
}
func (p *Pupil) testing() {
fmt.Println("小学生正在考试中")
}
//大学生
type Graduate struct {
Student //匿名结构体
}
func (g *Graduate) testing() {
fmt.Println("大学生正在考试中")
}
func Inherit() {
pupil := &Pupil{}
pupil.Student.Name = "tom"
pupil.Student.Age = 8
pupil.testing()
pupil.Student.SetScore(90)
pupil.Student.ShowInfo()
graduate := &Graduate{}
graduate.Student.Name = "jack"
graduate.Student.Age = 20
graduate.testing()
graduate.Student.SetScore(80)
graduate.Student.ShowInfo()
}
接口
// 声明一个接口
type Usb interface {
//声明两个方法
Start()
Stop()
}
type Phone struct {
}
func (p Phone) Start() {
fmt.Println("手机开始工作")
}
func (Phone) Stop() {
fmt.Println("手机停止工作")
}
type Camera struct {
}
func (c Camera) Start() {
fmt.Println("手机开始工作")
}
func (c Camera) Stop() {
fmt.Println("手机停止工作")
}
type Computer struct{}
// 编写一个方法working 方法,接受一个接口类型变量
// 只要是实现了usb接口
func (c Computer) Working(usb Usb) {
usb.Start()
usb.Stop()
}
func InterfaceDemo() {
computer := Computer{}
phone := Phone{}
camera := Camera{}
computer.Working(phone)
computer.Working(camera)
}
多态
多态特征是通过接口实现的。可以按照统一的借口调用不同的实现。这时接口变量就呈现不同的形态
以上代码usb变量会根据传入的实惨,来判断到底是phone还是Camra
类型断言
// 类型断言
type Point struct {
x int
y int
}
func Assert() {
var a interface{}
var point Point = Point{}
a = point //ok
//如何将a赋给一个Point变量?
var b Point
//b =a 不可以
//b =a.(Point)//可以
b = a.(Point)
fmt.Println(b)
//类型断言的其它类型
var x interface{}
var b2 float32 = 1.1
x = b2 //空接口 ,可以接受任意类型
//x=>float32[使用类型断言]
y := x.(float32)
fmt.Printf("y的类型是%T 值是=%v", y, y)
}
在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保原来的
//项目
// 模拟实现基于文本界面的《家庭记账软件》。
//该软件能够记录家庭的收入、支出,并能够打印收支明细表
//项目采用分级菜单方式,主菜单如下
// -----------家庭收支记账软件-----------
// 1.收支明细
// 2 登记收入
// 3 登记支出
// 4 退 出
面向过程编程
package main
import "fmt"
func main() {
//声明一个变量,保存接收用户输入的选项
key := ""
//声明一个变量,控制是否退出for
loop := true
//定义账户的余额
balance := 0.0
//每次收支的金额
money := 0.0
//每次收支的说明
note := ""
//收支详情使用字符串来记录
datails := "收支\t账户金额\t收支金额\t说 明"
falg := false
// 显示主菜单
for {
fmt.Println("------------------家庭收支记账软件------------------")
fmt.Println(" 1 收支明细")
fmt.Println(" 2 登记收入")
fmt.Println(" 3 登记支出")
fmt.Println(" 4 退 出")
fmt.Print("请选择(1-4):")
fmt.Scanln(&key)
switch key {
case "1":
fmt.Println("\n------------------当前收支明细------------------")
if falg {
fmt.Println(datails)
} else {
fmt.Println("当前没有收支明细。。。来一笔吧!")
}
case "2":
fmt.Println("本次收入金额:")
fmt.Scanln(&money)
balance += money // 修改账户余额
fmt.Println("本次收入说明:")
fmt.Scanln(¬e)
//将这个收入情况,拼接到details变量
datails += fmt.Sprintf("\n收入\t%v \t%v \t%v", balance, money, note)
falg = true
case "3":
fmt.Println("本次支出金额:")
fmt.Scanln(&money)
if money > balance {
fmt.Println("支出金额大于余额")
break
}
balance = balance - money //修改账户余额
fmt.Println("本次支出说明:")
fmt.Scanln(¬e)
datails += fmt.Sprintf("\n支出\t%v \t%v \t%v", balance, money, note)
falg = true
case "4":
fmt.Println("你确定要退出吗?y/n")
choice := ""
for {
fmt.Scanln(&choice)
if choice == "y" || choice == "n" {
break
}
fmt.Println("你输入的命令有误")
fmt.Println("你确定要退出吗?y/n")
}
if choice == "y" {
loop = false
}
default:
fmt.Println("请输入正确的选项")
}
if !loop {
break
}
}
}
面向对象编程
封装工具类
package utils
import "fmt"
type FamilyAccount struct {
// 声明一个字段,保存接收用户输入的选项
key string
// 声明一个字段,控制是否退出for
loop bool
// 定义账户的余额
balance float64
// 每次收支的金额
money float64
// 每次收支的说明
note string
// 收支详情使用字符串来记录
datails string
// 定义记录收支行为字段,
flag bool
}
// 编写要给工厂模式的构造方法,返回一个*FamilyAccount实例
func NewFamilyAccount() *FamilyAccount {
return &FamilyAccount{
key: "",
loop: true,
balance: 0.0,
money: 0.0,
note: "",
flag: false,
datails: "收支\t账户金额\t收支金额\t说 明 ",
}
}
// 将显示明细封装到一个方法
func (this *FamilyAccount) ShowDetails() {
fmt.Println("\n------------------当前收支明细------------------")
if this.flag {
fmt.Println(this.datails)
} else {
fmt.Println("当前没有收支明细。。。来一笔吧!")
}
}
// 将登记收入封装到一个方法
func (this *FamilyAccount) Income() {
fmt.Println("本次收入金额:")
fmt.Scanln(&this.money)
this.balance += this.money // 修改账户余额
fmt.Println("本次收入说明:")
fmt.Scanln(&this.note)
// 将这个收入情况,拼接到details变量
this.datails += fmt.Sprintf("\n收入\t%v \t%v \t%v", this.balance, this.money, this.note)
this.flag = true
}
// 将收入支出封装成一个方法
func (this *FamilyAccount) Pay() {
fmt.Println("本次支出金额:")
fmt.Scanln(&this.money)
if this.money > this.balance {
fmt.Println("支出金额大于余额")
return
}
this.balance = this.balance - this.money // 修改账户余额
fmt.Println("本次支出说明:")
fmt.Scanln(&this.note)
this.datails += fmt.Sprintf("\n支出\t%v \t%v \t%v", this.balance, this.money, this.note)
this.flag = true
}
// 将退出封装成方法
func (this *FamilyAccount) Exit() {
fmt.Println("你确定要退出吗?y/n")
choice := ""
for {
fmt.Scanln(&choice)
if choice == "y" || choice == "n" {
break
}
fmt.Println("你输入的命令有误")
fmt.Println("你确定要退出吗?y/n")
}
if choice == "y" {
this.loop = false
}
}
// 给该结构体绑定相应的方法
func (this *FamilyAccount) MainMenu() {
// 显示主菜单
for this.loop {
fmt.Println("------------------家庭收支记账软件------------------")
fmt.Println(" 1 收支明细")
fmt.Println(" 2 登记收入")
fmt.Println(" 3 收入支出")
fmt.Println(" 4 退 出")
fmt.Print("请选择(1-4):")
fmt.Scanln(&this.key)
switch this.key {
case "1":
this.ShowDetails()
case "2":
this.Income()
case "3":
this.Pay()
case "4":
this.Exit()
default:
fmt.Println("请输入正确的选项")
}
}
}
编写主函数
package main
import (
"fmt"
"github.com/mylib/utils"
)
func main() {
fmt.Println("这个是面向对象的方式完成~~")
utils.NewFamilyAccount().MainMenu()
}
文件操作
文件在程序中是以流的形式来操作的。
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
常用的文件操作函数和方法
1)打开一个文件进行读操作
os.Open(name string) (*File,error)
函数:
2)关闭一个文件:
File.Close()
函数:
3)其它的函数和方法
读文件操作应用实例
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("文件路径")
if err != nil {
fmt.Println("open file err", err)
}
//当打开一个文件的时候 要及时关闭
defer file.Close() // 防止内存泄露
//创建一个Reader, 带缓冲
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n') //读到一个换行就结束
if err == io.EOF { //io.EOF表示读到文件的末尾
break
}
//输出内容
fmt.Print(str)
}
fmt.Println("文件读取结束")
}
读取文件内容
filePath := "filePath"
content, err := os.ReadFile(filePath)
if err != nil {
fmt.Println("读取文件失败", err)
return
}
fmt.Println("文件内容", string(content))
1)创建一个新文件,写入内容5句“ hello,tom“
2)打开一个存在的文件中,将原来的内容覆盖成新的内容10句 ”你好,tom!“
3)打开一个存在的文件中,将原来的内容追加内容“ABC“
4)打开一个存在的文件,将原来的内容读出显示在终端,并且追加5句 ”hello,tom!“
使用os.OpenFile(),bufio.NewWriter(),*Writer的方法WriterString完成上面的任务
第一个
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
//创建一个新文件,写入内容5句“ hello,tom“
//1.打开文件
filePath := "path"
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666) //只读,创建
if err != nil {
fmt.Printf("open file err= %v\n", err)
return
}
//及时关闭文件,防止句柄泄露
defer file.Close()
str := "hello,tom\n"
//写入时,使用带缓存的 *Writer
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(str)
}
//因为writer是带缓存的,因此在调用WriterString方法时,其实内容是先写入到缓存的,所以需要调用Flush这个方法
//将缓存的数据真正写入文件中,否则文件中会没有数据!
writer.Flush()
}
第二个
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666) // O_TRUNC 清空内容
第三个
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666) // O_TRUNC 添加内容
第四个
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
//创建一个新文件,写入内容5句“ hello,tom“
//1.打开文件
filePath := "path"
//file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666) //清空 重新写
//file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666) //末尾添加
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0666) // 读写
if err != nil {
fmt.Printf("open file err= %v\n", err)
return
}
//及时关闭文件,防止句柄泄露
defer file.Close()
//先读取原来文件的内容,并显示在终端
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n')
if err == io.EOF { // 如果读到文件末尾
break
}
//继续
fmt.Print(str)
}
//写入
str := "hello world\n"
//写入时,使用带缓存的 *Writer
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString(str)
}
//因为writer是带缓存的,因此在调用WriterString方法时,其实内容是先写入到缓存的,所以需要调用Flush这个方法
//将缓存的数据真正写入文件中,否则文件中会没有数据!
writer.Flush()
}
复制文件内容至另一文件
package main
import (
"fmt"
"os"
)
func main() {
//将文件中的内容导入到 test2.txt中
//首先将这个文件的内容读取到内存
// 将读取到的内容 写入test2.txt
fiel1Path := "path"
file2Path := "path"
content, err := os.ReadFile(fiel1Path)
if err != nil {
fmt.Println("读取文件错误:", err)
return
}
err = os.WriteFile(file2Path, content, 0666)
if err != nil {
fmt.Println("write file err", err)
}
}
判断文件是否存在
首先调用os.Stat()
函数,并将文件路径作为参数传递给它。然后,通过检查返回的错误值来确定文件是否存在。
如果err
为nil
,则表示文件存在。如果err
为os.ErrNotExist
,则表示文件不存在。如果err
为其他非空错误,则表示在判断文件是否存在时发生了一些错误。
请注意,os.Stat()
函数还可以用于获取文件的其他属性,例如文件大小、修改时间等。您可以通过FileInfo
结构体的相应方法来获取这些信息
go
package main
import (
"fmt"
"os"
)
func main() {
filePath := "example.txt" // 文件路径
_, err := os.Stat(filePath)
if err == nil {
fmt.Println("文件存在")
} else if os.IsNotExist(err) {
fmt.Println("文件不存在")
} else {
fmt.Println("发生错误:", err)
}
}
拷贝文件
package main
import (
"bufio"
"fmt"
"io"
"os"
)
// 自己编写一个函数,接受两个文件路径,srcFileName dstFileName
func CopeFile(dstFileName string, srcFileName string) (written int64, err error) {
srcFile, err := os.Open(srcFileName)
if err != nil {
fmt.Printf("openfile err%v\n", err)
return
}
defer srcFile.Close()
//通过srcfile,获取Reader
reader := bufio.NewReader(srcFile)
//打开dstFileName
dstFile, err := os.OpenFile(dstFileName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Printf("openfile err%v\n", err)
return
}
defer dstFile.Close()
//通过dstFile,获取到Writer
writer := bufio.NewWriter(dstFile)
written, err = io.Copy(writer, reader)
if err != nil {
fmt.Printf("copy file err%v\n", err)
return
}
err = writer.Flush()
if err != nil {
fmt.Printf("flush writer err%v\n", err)
return
}
return written, nil
}
func main() {
srcFile := "目标路径"
dstFile := "原文件路径"
_, err := CopeFile(dstFile, srcFile)
if err == nil {
fmt.Printf("拷贝完成\n")
} else {
fmt.Printf("拷贝错误 err=%v\n", err)
}
}
命令行参数
请编写一段代码,可以获取命令行各个参数
func main() {
fmt.Println("命令执行的参数有", len(os.Args))
//遍历os.Args切片,就可以得到所有的命令行输入参数值
for i, v := range os.Args {
fmt.Printf("args[%v]=%v\n ", i, v)
}
}
flag包解析命令行参数