Golang&&学习笔记1
参考资料:
变量
声明变量的三种方式
一次性声明多个变量。
声明全局变量。
变量的数据类型
基本数据类型
整数型
- int:
- int8: (长度为)8位(范围内的有符号)整数类型。2^7 = 128,故可表示的整数范围在 -128 ~ 127 。
- int32: rune 为 int32 的别名。
- uint8: byte 为 uint8 的别名。(长度为)8位(范围内的无符号)整数类型。可表示的整数范围在 0 ~ 255 。
8位 等于 1字节
var i = 100
// 如何查看变量的数据类型
// fmt.Printf() 常用于做格式化输出。
fmt.Printf("变量i的数据类型是 %T\n", i)
// 如何查看变量的字节大小
fmt.Printf("变量i占用的字节数是 %d", unsafe.Sizeof(i))
浮点类型
浮点数都是有符号的。
float64 的精度比 float32 的要准确。开发中,推荐使用 float64 !
浮点型 默认声明为 float64 类型。
字符型
使用byte来保存单个字母字符。
一个汉字字符占3个byte。
布尔型
bool 类型占1个字节。
字符串型
字符串 是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。
Go的字符串,是由字节组成的。
Go语言的字符串的字节使用UTF-8编码标识Unicode文本。
%v,表示按照变量的原始值输出。
基本数据类型(位大小)的转换
基本数据类型和string的转换
基本数据类型转string
方式一:Sprintf 根据于格式说明符进行格式化并返回其结果字符串。
方式二:strconv 函数
方式三:Itoa是FormatInt(i, 10) 的简写。可以很方便的把int转为string。
string转基本数据类型
在接收函数的返回结果时,如果对多余的返回值不关心,可以使用下划线 “_” 接受多余的值。
在类型转换中,如果类型不匹配(如字符串型转整数型),则将直接转成默认值(如0,false等)。
指针
值类型和引用类型
标识符
- xxxxx
- 下划线 “_” 本身在Go中是一个特殊的标识符,称为 空标识符。可以代表任何其他的标识符,但是它对应的值会被忽略(比如 忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用。
运算符
算术运算符
算术运算符是对 数值类型的变量 进行运算的。如:加减乘除。
关系运算符
逻辑运算符
赋值运算符
位运算符
其他运算符
获取用户终端输入
流程控制
switch 分支控制
for 循环控制
对于 for-range 遍历方式而言,是按照字符方式遍历,因此如果字符串中包含中文,也完全没问题。
while 和 do…while 的实现
跳转控制语句 - break
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
for {
// 在go中,我们为了生成一个随机数,需要为rand设置一个种子,否则返回的值总是固定的。
// time.Now().Unix(): 返回一个从 1970-1-1 0:0:0 到现在的一个秒数
// rand.Seed(time.Now().Unix())
// time.Now().UnixNano(): 返回一个从 1970-1-1 0:0:0 到现在的纳秒数
rand.Seed(time.Now().UnixNano())
var randNum int = rand.Intn(100) + 1
fmt.Println("randNum=", randNum)
if randNum == 99 {
break
}
}
}
break 语句用于终止某个语句块的执行,用于中断当前 for 循环或跳出 switch 语句。
跳转控制语句 - continue
跳转控制语句 - goto
跳转控制语句 - return
函数
函数-递归调用
一个函数在函数体内又调用了本身,我们称为递归调用。
函数注意事项和细节讨论
函数重载:即函数名相同,而形参的个数或数据类型不同的现象。
slice: 切片
init函数
匿名函数
包
- “包”,为了更好的管理一组相关性强的功能集合,引入了包的概念。如fmt(格式化IO)等。
- 通常创建一个名字为包名的文件夹,其内有多个子文件,子文件的首行通常为 “package 包名”。
- 包的本质 实际上就是创建不同的文件夹,来存放程序文件。
- go的每一个文件都是属于一个包的,也就是说,go是以包的形式来管理文件和项目目录结构的。
包的三大作用
package main
import (
// 如果想保留一个没有实际使用到的包,可以通过在前面加下划线 "_" 表示忽略该包。
_ "fmt"
)
如何引入自己的包
- 找到go的工作目录(即为配置的GOPATH路径)。
- 进入src目录,从src内的目录开始写引入包的路径。
包使用 注意事项和细节
库文件utils.a ,随编译生成,无法直接阅读,常用作SDK,配合接口文档使用。
闭包
基本介绍
闭包就是一个函数和与其相关的引用环境结合的一个整体(实体)。
defer
函数参数传递方式
变量的作用域
常见函数
字符串函数
builtin: 内建函数,可直接使用而不需要引入包。
时间和日期函数
统计函数执行时间
内置函数(buildin)
Go 错误处理机制
使用 defer + recover 来处理错误异常
package main
import (
"fmt"
)
func testPanic() {
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 说明捕获到错误
fmt.Println("err=", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res= ", res)
}
func main() {
testPanic()
fmt.Println("testPanic()函数执行完毕!")
}
自定义错误
package main
import (
"errors"
"fmt"
)
func readConf(name string) (err error) {
if name == "config.ini" {
return nil
} else {
// 返回一个自定义错误
return errors.New("读取文件异常。。。")
}
}
func testPanic() {
err := readConf("config.ini1")
if err != nil {
panic(err)
}
fmt.Println("testPanic()函数执行至最后一行!")
}
func main() {
testPanic()
fmt.Println("testPanic()函数执行完毕!")
}
数组
数组可以存放多个同一类型数据。
数组也是一种数据类型,在Go中,数组是值类型。
fmt.Printf %p,即point,常用于打印地址。
数组的使用
访问数组元素
四种初始化数组的方式
数组 使用注意事项和细节
[3]int,在go语言中,3被认为是数组的一部分。因此,[3]int,[4]int属于两种数据类型。
(8)
(9)
切片
切片在内存中的形式
使用切片的三种方式
切片的使用
切片的遍历
切片的注意事项和细节
string 和 slice 的使用
数组排序
冒泡排序
顺序查找
二分查找
多维数据-二维数据
map
map 是 key-value数据结构,又称为字段或者关联数组。类似其他编程语言的集合。
map 的声明
## map使用的方式
map 使用的课堂案例
map 的增删改查操作
map 删除
map 查找
map 的遍历
map 切片
map 排序
map 使用细节
结构体
go 语言中,对象 = 结构体 = struct
go 语言中没有类class的概念。
Golang 语言面向对象编程说明
结构体变量(实例)在内存中的布局
结构体 struct 是值类型
字段/属性
创建结构体实例的四种方式
日常开发中,推荐使用 方式2。
示例: struct类型的内存分配机制
结构体使用细节
- 结构体的所有字段在内存中是连续的。
方法
p 是值拷贝,改变 p 的值不会影响到变量(示例)的属性的值。可以通过传递地址实现对变量(示例)属性值的修改。
- p 的名字,由程序员指定,不是固定的。建议使用结构体名字的小写代替。 func (person Person) test() { }
方法的调用和传参机制原理
方法 注意事项和细节讨论
2.
3.
5.
方法和函数的区别
结构体 方法的传递方式是值拷贝还是地址引用,取决于方法编写时取的是什么,不取决于结构体变量传的是值还是指针。
底层编译器做了优化处理
创建结构体变量时 指定字段值
工厂模式
工厂模式示例
一个结构体的字段是私有的,该如何去访问?
VSCode 的使用
设置
快捷键
面向对象编程的三大特性
封装
继承
深入讨论
继承 不是将多层的结构体混为单一平面,而是保存多层结构体嵌套顺序。
故,在寻找变量时,如果在当前层级找不到目标变量,将重复向上层寻找。
故,在更新变量时,如果更新了当前层级的目标变量,上层级的同名变量值不改变。
多重继承
接口
应用场景
注意事项和细节
(1) / (4)
即我们可以把任何一个变量赋给空接口。
接口最佳实践
接口和继承的比较
实现接口 是对 继承机制 的补充。
接口不负责具体的实现(只制定规范),当需要执行方法时,直接使用其他数据类型写好的具体方法即可。
比如我 要 吃 这个功能,具体怎么实现吃这个动作,我不管,我只告诉你吃是什么效果,只要你能满足我要的效果,当我 要 吃 的时候,我就可以执行你的方法达到吃的效果。
多态
类型断言
类型断言的最佳实践
家庭收支记账软件项目
项目开发流程
需求说明
设计阶段
实现阶段
package main
import (
"fmt"
)
func main() {
// 保存用户输入的选项
var choiceNum int
// 控制是否退出for
var ifLogout bool = false
// 保存账户余额
var balance float64 = 10000.0
// 每次收支金额
var money float64 = 0.0
// 每次收支的说明
var note string = ""
// 收支详细信息
var detail string = "收支\t账户金额\t收支金额\t说 明\n"
// 是否确定退出
var ifConfirm string = ""
for {
fmt.Println("------------------家庭收支记账软件------------------")
fmt.Println()
fmt.Println(" 1 收支明细")
fmt.Println(" 2 登记收入")
fmt.Println(" 3 登记支出")
fmt.Println(" 4 退 出")
fmt.Println()
fmt.Print(" 请选择(1-4):")
fmt.Scanln(&choiceNum)
fmt.Println()
switch choiceNum {
case 1:
fmt.Println("------------------当前收支明细记录------------------")
fmt.Println(detail)
case 2:
fmt.Print("本次收入金额:")
fmt.Scanln(&money)
// 修改账户余额
balance = balance + money
fmt.Print("本次收入说明:")
fmt.Scanln(¬e)
// 记录收支明细
detail += fmt.Sprintf("收入\t%v\t\t%v\t\t%v\n", balance, money, note)
case 3:
fmt.Print("本次支出金额:")
fmt.Scanln(&money)
if money > balance {
fmt.Println("您的余额不足!")
break
}
// 修改账户余额
balance -= money
fmt.Print("本次支出说明:")
fmt.Scanln(¬e)
// 记录收支明细
detail += fmt.Sprintf("支出\t%v\t\t%v\t\t%v\n", balance, money, note)
case 4:
for {
fmt.Print("你确定要退出吗? y/n:")
fmt.Scanln(&ifConfirm)
if ifConfirm == "y" {
ifLogout = true
break
}
if ifConfirm == "n" {
break
}
fmt.Println("请输入y/n")
}
default:
fmt.Println("请输入正确的选项。")
}
if ifLogout {
break
}
}
fmt.Println(choiceNum)
fmt.Println("您已登出软件。")
}
变更为面向对象编程
main.go
package main
import (
"fmt"
"homeBill/utils"
)
func main() {
utils.NewFamilyAccount().MainMenu()
fmt.Println("您已登出软件。")
}
familyAccount.go
package utils
import (
"fmt"
)
type FamilyAccount struct {
ChoiceNum int
IfLogout bool
IfHasDetail bool
Balance float64
Money float64
Note string
Detail string
}
func NewFamilyAccount() *FamilyAccount {
return &FamilyAccount{
// 保存用户输入的选项
ChoiceNum: 0,
// 控制是否退出for
IfLogout: false,
IfHasDetail: false,
// 保存账户余额
Balance: 10000.0,
// 每次收支金额
Money: 0.0,
// 每次收支的说明
Note: "",
// 收支详细信息
Detail: "收支\t账户金额\t收支金额\t说 明\n",
}
}
// 显示明细
func (familyAccount *FamilyAccount) showDetails() {
if familyAccount.IfHasDetail {
fmt.Println("------------------当前收支明细记录------------------")
fmt.Println(familyAccount.Detail)
} else {
fmt.Println("当前没有任何收支明细。")
}
}
// 登记收入
func (familyAccount *FamilyAccount) inCome() {
fmt.Print("本次收入金额:")
fmt.Scanln(&(familyAccount.Money))
// 修改账户余额
familyAccount.Balance += familyAccount.Money
fmt.Print("本次收入说明:")
fmt.Scanln(&(familyAccount.Note))
// 记录收支明细
familyAccount.Detail += fmt.Sprintf("收入\t%v\t\t%v\t\t%v\n", familyAccount.Balance, familyAccount.Money, familyAccount.Note)
familyAccount.IfHasDetail = true
}
// 登记支出
func (familyAccount *FamilyAccount) pay() {
fmt.Print("本次支出金额:")
fmt.Scanln(&(familyAccount.Money))
if familyAccount.Money > familyAccount.Balance {
fmt.Println("您的余额不足!")
return
}
// 修改账户余额
familyAccount.Balance -= familyAccount.Money
fmt.Print("本次支出说明:")
fmt.Scanln(&(familyAccount.Note))
// 记录收支明细
familyAccount.Detail += fmt.Sprintf("支出\t%v\t\t%v\t\t%v\n", familyAccount.Balance, familyAccount.Money, familyAccount.Note)
familyAccount.IfHasDetail = true
}
// 退出软件
func (familyAccount *FamilyAccount) exit() {
// 是否确定退出
var ifConfirm string = ""
fmt.Print("你确定要退出吗? y/n:")
for {
fmt.Scanln(&ifConfirm)
if ifConfirm == "y" {
familyAccount.IfLogout = true
break
}
if ifConfirm == "n" {
break
}
fmt.Println("请输入y/n")
}
}
// 显示主菜单
func (familyAccount *FamilyAccount) MainMenu() {
for {
fmt.Println("------------------家庭收支记账软件------------------")
fmt.Println()
fmt.Println(" 1 收支明细")
fmt.Println(" 2 登记收入")
fmt.Println(" 3 登记支出")
fmt.Println(" 4 退 出")
fmt.Println()
fmt.Print(" 请选择(1-4):")
fmt.Scanln(&(familyAccount.ChoiceNum))
fmt.Println()
switch familyAccount.ChoiceNum {
case 1:
familyAccount.showDetails()
case 2:
familyAccount.inCome()
case 3:
familyAccount.pay()
case 4:
familyAccount.exit()
default:
fmt.Println("请输入正确的选项。")
}
if familyAccount.IfLogout {
break
}
}
}
客户关系管理项目
需求说明
设计阶段
customer.go
package model
import "fmt"
// 声明一个Customer结构体,表示一个客户信息
type Customer struct {
Id int
Name string
Gender string
Age int
Phone string
Email string
}
// 使用工厂模式,返回一个customer实例
func NewCustomer(id int, name string, gender string, age int, phone string, email string) Customer {
return Customer{
Id: id,
Name: name,
Gender: gender,
Age: age,
Phone: phone,
Email: email,
}
}
// 返回用户信息,格式化后的字符串
func (this Customer) GetInfo() string {
return fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v", this.Id, this.Name, this.Gender, this.Age, this.Phone, this.Email)
}
customerService.go
package service
import (
"customerInfo/model"
)
// 该 CustomerService 完成对 customer 的操作,包括增删改查
type CustomerService struct {
customers []model.Customer
// 声明一个字段,表示当前切片含有多少个客户,该字段可作为新客户的 id+1
customerNum int
}
// 返回 *CustomerService
func NewCustomerService() *CustomerService {
customerService := &CustomerService{}
customerService.customerNum = 1
customer := model.NewCustomer(1, "ycc", "nan", 29, "155", "ycc@qq.com")
customerService.customers = append(customerService.customers, customer)
return customerService
}
// 返回客户列表切片
func (this *CustomerService) List() []model.Customer {
return this.customers
}
// 添加客户到客户列表切片
func (this *CustomerService) Add(name string, gender string, age int, phone string, email string) bool {
this.customerNum++
customer := model.NewCustomer(this.customerNum, name, gender, age, phone, email)
this.customers = append(this.customers, customer)
return true
}
// 根据id删除客户
func (this *CustomerService) Delete(id int) bool {
index := this.FindById(id)
if index == -1 {
return false
}
// 如何从切片中删除一个元素
this.customers = append(this.customers[:index], this.customers[index+1:]...)
return true
}
// 根据id查找客户在切片中对应下标,如果没有该客户,返回-1
func (this *CustomerService) FindById(id int) int {
index := -1
for i := 0; i < len(this.customers); i++ {
if id == this.customers[i].Id {
index = i
break
}
}
return index
}
// 根据id获取用户详细信息
func (this *CustomerService) GetById(id int) model.Customer {
for i := 0; i < len(this.customers); i++ {
if id == this.customers[i].Id {
return this.customers[i]
break
}
}
return model.Customer{}
}
// 根据index更新用户详细信息
func (this *CustomerService) UpdateByIndex(index int, id int, name string, gender string, age int, phone string, email string) {
this.customers[index] = model.Customer{
Id: id,
Name: name,
Gender: gender,
Age: age,
Phone: phone,
Email: email,
}
}
customerView.go
package main
import (
"customerInfo/service"
"fmt"
)
type customerView struct {
ChoiceNum int // 保存用户输入的选项
IfLogout bool // 是否退出主菜单
CustomerService *service.CustomerService
}
// 显示所有的客户信息
func (this *customerView) list() {
customers := this.CustomerService.List()
fmt.Println("----------------------客户列表----------------------")
fmt.Println("编号\t姓名\t性别\t年龄\t电话\t邮箱")
for i := 0; i < len(customers); i++ {
fmt.Println(customers[i].GetInfo())
}
fmt.Printf("------------------------END------------------------\n\n\n")
}
// 得到用户的输入信息,调用add方法完成添加
func (this *customerView) add() {
fmt.Println("----------------------添加客户----------------------")
fmt.Printf("姓名:")
var name string
fmt.Scanln(&name)
fmt.Printf("性别:")
var gender string
fmt.Scanln(&gender)
fmt.Printf("年龄:")
var age int
fmt.Scanln(&age)
fmt.Printf("电话:")
var phone string
fmt.Scanln(&phone)
fmt.Printf("邮箱:")
var email string
fmt.Scanln(&email)
if this.CustomerService.Add(name, gender, age, phone, email) {
fmt.Printf("--------------------添加成功------------------------\n\n\n")
} else {
fmt.Printf("--------------------添加失败------------------------\n\n\n")
}
}
func (this *customerView) delete() {
fmt.Println("----------------------删除客户----------------------")
fmt.Printf("请选择待删除客户编号(-1退出):")
var id int
fmt.Scanln(&id)
if id == -1 {
return
}
for {
fmt.Printf("确认是否删除(y/n):")
var ifExitDelete string
fmt.Scanln(&ifExitDelete)
if ifExitDelete == "y" || ifExitDelete == "Y" {
// 调用customerService 的 Delete方法
if this.CustomerService.Delete(id) {
fmt.Println("----------------------删除完成----------------------")
return
} else {
fmt.Println("----------------------删除失败,id不存在-------------")
return
}
}
if ifExitDelete == "n" || ifExitDelete == "N" {
fmt.Println("----------------------删除取消----------------------")
return
}
}
}
// 更新用户信息
func (this *customerView) update() {
fmt.Println("----------------------修改客户----------------------")
fmt.Printf("请选择待修改客户编号(-1退出):")
var id int
fmt.Scanln(&id)
if id == -1 {
return
}
index := this.CustomerService.FindById(id)
if index == -1 {
fmt.Println("----------------------客户不存在--------------------")
return
}
customerDetail := this.CustomerService.GetById(id)
fmt.Printf("姓名(%v):", customerDetail.Name)
var name string = ""
fmt.Scanln(&name)
if name == "" {
name = customerDetail.Name
}
fmt.Printf("\n性别(%v):", customerDetail.Gender)
var gender string = ""
fmt.Scanln(&gender)
if gender == "" {
gender = customerDetail.Gender
}
fmt.Printf("\n年龄(%v):", customerDetail.Age)
var age int = 0
fmt.Scanln(&age)
if age == 0 {
age = customerDetail.Age
}
fmt.Printf("\n电话(%v):", customerDetail.Phone)
var phone string = ""
fmt.Scanln(&phone)
if phone == "" {
phone = customerDetail.Phone
}
fmt.Printf("\n邮箱(%v):", customerDetail.Email)
var email string = ""
fmt.Scanln(&email)
if email == "" {
email = customerDetail.Email
}
this.CustomerService.UpdateByIndex(index, id, name, gender, age, phone, email)
}
// 显示主菜单
func (cv *customerView) mainMenu() {
for {
fmt.Println("------------------客户信息管理软件------------------")
fmt.Println()
fmt.Println(" 1 添加客户")
fmt.Println(" 2 修改客户")
fmt.Println(" 3 删除客户")
fmt.Println(" 4 客户列表")
fmt.Println(" 5 退 出")
fmt.Println()
fmt.Print(" 请选择(1-5):")
fmt.Scanln(&cv.ChoiceNum)
fmt.Println()
switch cv.ChoiceNum {
case 1:
cv.add()
case 2:
cv.update()
case 3:
cv.delete()
case 4:
cv.list()
case 5:
//cv.exit()
cv.IfLogout = true
default:
fmt.Println("请输入正确的选项!")
}
if cv.IfLogout {
break
}
}
fmt.Println("您已退出客户信息管理软件!")
}
func main() {
// 在main函数中,创建一个customerView,并运行显示主菜单页面
customerView := customerView{
ChoiceNum: -1,
IfLogout: false,
}
// 这里完成对 customerView 结构体的 CustomerService 字段的初始化
customerView.CustomerService = service.NewCustomerService()
customerView.mainMenu()
}
文件操作
文件的基本介绍
File代表一个打开的文件对象,也被称为文件句柄(或文件指针)。
打开文件和关闭文件
带缓冲的Reader读文件
一次性读取文件
创建文件并写入内容
写文件的四种方式
2)
3)
4)
判断文件或目录存在
拷贝文件
统计不同类型的字符个数
命令行参数 基本使用
flag 包解析命令行参数
json 应用
反序列化
反序列化 map,不需要 make,因为 make 操作被封装到 Unmarshal 函数。
单元测试
case001.go
package main
// 一个被测试函数
func addUpper(n int) int {
res := 0
for i := 1; i < n; i++ {
res += i
}
return res
}
case001_test.go
package main
import "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) 执行正确...")
}
入门总结
单元测试案例
goroutine 协程
案例说明
示例图
MPG模式
go 设置运行 cpu 数目
协程并发(并行)资源竞争问题
channel 管道
基本使用
channel 是引用类型
channel 的cap(容量)是固定的,不会自动增长。
注意事项:
channel 的遍历和关闭
综合案例
管道阻塞的机制
协程求素数