Golang进阶

1.面向对象

1.1.golang语言面向对象编程说明

  1. Golang 也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。
  2. Golang 没有类(class),Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct 来实现 OOP 特性的。
  3. Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等
  4. Golang仍然有面向对象编程的继承封装多态的特性,只是实现的方式和其它 OOP 语言不一样,比如继承 : Golang 没有 extends 关键字,继承是通过匿名字段来实现。
  5. Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang 中面向接口编程是非常重要的特性。

1.2.结构体

将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体
通过这个结构体,我们可以创建多个变量(实例/对象)

1.2.1.结构体和结构体变量的区别
  • 结构体是自定义的数据类型,代表一类事物
  • 结构体变量(实例)是具体的,实际的,代表一个具体变量
1.2.2.声明结构体
type structName struct {field1 typefield2 type
}
1.2.3.字段

从概念或叫法上看:结构体字段 = 属性 = field (即授课中,统一叫字段)

字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型

注意:

  • 字段声明语法同变量,示例:字段名 字段类型
  • 字段的类型可以为:基本类型、数组或引用类型
  • 在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样:
    • 布尔类型是 false ,数值是0,字符串是 “”
    • 数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0,0,0]
    • 指针,slice,和map 的零值都是 nil ,即还没有分配空间。
  • 不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体
    是值类型。
1.2.4.创建结构体变量和访问结构体字段

1.直接声明

var person Person

2.{}

var person Person = Person{}
p2 := Person{"mary", 20}
//p2.Name = "tom"
//p2.Age = 18

3.&

var person *Person = new(Person)
var p3 *Person = new(Person)
(*p3).Name = "smith"
//p3.Name = "smith"  也可以
(*p3).Age = 30
//p3.Age = 30 也可以

4.{}

var Person *Person = &Person{}

说明:

第3种和第4种方式返回的是结构体指针

结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*person).Name="tom"

但 go 做了一个简化,也支持结构体指针.字段名,比如person.Name="tom",更加符合程序员使用的习惯,go编译器底层 对 person.Name做了转化(*person).Name

不能写成*p.Age,因为.的运算符优先级比*高

1.2.5.结构体使用注意事项和细节
  1. 结构体的所有字段在内存中是连续的
  2. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
  3. 结构体进行type 重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
  4. struct 的每个字段上,可以写上一个 tag,该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化

1.3.方法

Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是 struct

1.3.1.方法的定义
func (receiver type) methodName (参数列表) (返回值列表){方法体return 返回值
}
  • 参数列表:表示方法输入
  • receiver type:表示这个方法和 type 这个类型进行绑定,或者说该方法作用于 type 类型
  • receiver type: type可以是结构体,也可以其它的自定义类型
  • receiver:就是 type 类型的一个变量(实例),比如 :Person 结构体 的一个变量(实例)
  • 返回值列表:表示返回的值,可以多个
  • 方法主体:表示为了实现某一功能代码块
  • return 语句不是必须的
1.3.2.方法的声明和调用
type A struct {Num int
}
func(a A) test() {fmt.Println(a.Num)
}

对上面的语法的说明

  • func(a A) test() {} 表示 A 结构体有一方法,方法名为 test
  • (a A)体现test 方法是和 A 类型绑定的

说明:

  • 在通过一个变量去调用方法时,其调用机制和函数一样
  • 不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝
1.3.3.方法的注意事项和细节
  1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
  2. 如程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
  3. Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型都可以有方法,而不仅仅是 struct, 比如 int,foat32 等都可以有方法
  4. 方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
  5. 如果一个类型实现了 String()这个方法,那么 fmt.Println 默认会调用这个变量的 String()方法进行输出
1.3.4.方法和函数区别
  • 调用方式不一样
    • 函数的调用方式:函数名(参数列表)
    • 方法的调用方式:变量.方法名(实参列表)
  • 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
  • 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以

总结:

不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定

如果是和值类型,比如(p Person),则是值拷贝, 如果和指针类型,比如是(p*Person) 则是地址拷贝

1.4.工厂模式

Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题

如果结构体变量首字母小写,引入后,不能直接使用,可以工厂模式解决

package modeltype student struct {Name  stringscore float64
}func NewStudent(n string, s float64) *student {return &student{Name:  n,score: s,}
}
func (s *student) GetScore() float64 {return s.score
}
func main() {stu := model.NewStudent("xiaoming", 45)fmt.Println(*stu)fmt.Println(stu.GetScore())
}

1.5.封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作

1.5.1封装的理解和好处
  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理
1.5.2如何体现封装
  • 对结构体中的属性进行封装
  • 通过方法,包 实现封装
1.5.3封装的实现步骤
  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似 private)

  2. 给结构体所在包提供一个工厂模式的函数,首字母大写,类似一个构造函数

  3. 提供一个首字母大写的 Set 方法(类似其它语言的 public),用于对属性判断并赋值

    func(var 结构体类型名) SetXxx(参数列表) (返回值列表) {//加入数据验证的业务逻辑var.字段 = 参数
    }
    
  4. 提供一个首字母大写的 Get 方法(类似其它语言的 public),用于获取属性的值

    func (var 结构体类型名) GetXxx() {return var.age
    }
    

1.6.继承

继承可以解决代码复用,让我们的编程更加靠近人类思维。

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。

其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student匿名结构体即可。

也就是说:在 Golang 中,如果一个 struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

1.6.1.嵌套匿名结构体的基本语法
type Goods struct {Name  stringPrice int
}
type Book struct {Goods  //这里就是嵌套匿名结构体Writer string
}
1.6.2.继承的深入讨论
  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,
    都可以使用

  2. 匿名结构体字段访问可以简化

    type A struct {Name stringage int
    }func (a *A) sayOk()  {fmt.Println("A SayOk",a.Name)
    }
    func (a *A) hello()  {fmt.Println("A hello",a.Name)
    }
    type B struct {A
    }
    func main() {var b Bb.A.Name = "tom"b.A.age = 19b.A.sayOk()b.A.hello()//上面的写法可以简化b.Name = "smith"b.age = 20b.sayOk()b.hello()
    }
    

    对上面的代码小结

    • 当我们直接通过b访问字段或方法时,其执行流程如下比如 b.Name

    • 编译器会先看b对应的类型有没有 Name,如果有,则直接调用 B 类型的 Name 字段

    • 如果没有就去看B中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用.如果没 有继续查找.如果都找不到就报错

  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分

  4. 结构体嵌入两个(或多个)匿名结构体,如两个名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。

  5. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字

  6. 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

  7. 如果一个结构体有 int类型的匿名字段,就不能第二个。如果需要有多个int的字段,则必须给 int 字段指定名字

1.6.3.多重继承

如果一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

type Goods struct {Name stringPrice float64
}
type Brand struct {Name stringAddress string
}
type TV struct {GoodsBrand
}

细节说明:

  1. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
  2. 为了保证代码的简洁性,建议大家尽量不使用多重继承

1.7.接口

interface 类型可以定义一组方法,但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

1.7.1.基本用法
type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表...
}
func (t 自定义类型) method1(参数列表) 返回值列表{//方法实现
}
func (t 自定义类型) method2(参数列表) 返回值列表{//方法实现
}
//...

说明:

  • 接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态高内聚低偶合的思想。
  • Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字
1.7.2.注意事项和细节
  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)
  2. 接口中所有方法都没有方法体,即都是没有实现的方法
  3. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现
    了该接口。
  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型
  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
  6. 一个自定义类型可以实现多个接口
  7. Golang接口中不能有任何变量
  8. 一个接口(比如A接口)可以继承多个别的几口(比如B,C接口),这时如果要实现A接口,须将B,C接口的方法也全部实现
  9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  10. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口
1.7.3.接口和继承的比较
  • 实现接口可以看作是对继承的一种补充
    • 当 A 结构体继承了 B结构体,那么 A 结构就自动的继承了B结构体的字段和方法,并且可以直接使用
    • 当A 结构体需要扩展功能,同时不希望去破坏继承关系,则可以去实现某个接口即可,因此我们可以认为:实现接口是对继承机制的补充,实现接口可以看作是对 继承的一种补充
  • 接口和继承解决的解决的问题不同
    • 继承的价值主要在于:解决代码的复用性和可维护性
    • 接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
  • 接口比继承更加灵活
    • 接口比继承更加灵活,继承是满足 is-a 的关系,而接口只需满足 like-a 的关系。
  • 接口在一定程度上实现代码解耦

1.8.多态

变量(实例)具有多种形态。面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

1.8.1.接口体现多态的两种形式
  • 多态参数
    • 在前面的 Usb 接口案例,Usb usb ,即可以接收手机变量,又可以接收相机变量,就体现了 Usb 接口 多态。
  • 多态数组
    • 演示一个案例:给Usb数组中,存放Phone结构体和Camera 结构体变量

1.9.类型断言

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,

在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型断言时,要确保原来的空接口指向的就是断言的类型

如何在进行断言时,带上检测机制,如果成功就 ok,否则也不要报 panic

func main() {var x interface{}var b2 float32 = 2.1x = b2if y, ok := x.(float32); ok {fmt.Println("convert success")fmt.Printf("y的类型是%T,值是%v", y, y)} else {fmt.Println("convert fail")}fmt.Println("继续执行...")
}

2.文件操作

文件在程序中是以流的形式来操作的

  • 流:数据在数据源(文件)和程序(内存)之间经历的路径
  • 输入流:数据从数据源(文件)到程序(内存)的路径
  • 输出流:数据从程序(内存)到数据源(文件)的路径

os.File 封装所有文件相关操作,File 是一个结构体

2.1.打开和关闭文件

使用的函数和方法:

func Open

func Open(name string) (file *File, err error)

Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。

func (*File) Close

func (f *file) Close() error

Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。

案例演示:

package mainimport ("fmt""os"
)func main() {file, err := os.Open("C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text")if err != nil {fmt.Println("open file err:", err)}fmt.Println(file)fmt.Printf("file = %v", file)err = file.Close()if err != nil {fmt.Println("close file err:", err)}
}

2.2.读文件操作

  1. 读取文件的内容显示在终端(带缓冲区的方式),使用 os.Open,file.Close,bufio.NewReader(),reader.ReadString 函数和方法
package mainimport ("bufio""fmt""io""os"
)func main() {file, err := os.Open("C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text")if err != nil {fmt.Println("open file err:", err)}defer file.Close()//创建一个*Reader,是带缓冲的,默认的缓冲区为4096字节reader := bufio.NewReader(file)//循环读取文件的内容for {str, err := reader.ReadString('\n') //读到一个换行就结束if err == io.EOF {                  //io.EOF表示文件的末尾fmt.Println(str)break}fmt.Print(str)}fmt.Println("文件读取结束···")
}
  1. 读取文件的内容并显示在终端(使用 ioutil 一次将整个文件读入到内存中),这种方式适用于文件不大的情况。相关方法和函数(ioutil.ReadFile)
func main() {//使用ioutil.ReadFile一次性将文件读取到位file := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"content, err := ioutil.ReadFile(file)if err != nil {return}fmt.Printf("%v", string(content))
}

2.3.写文件操作

基本介绍-os.OpenFile函数

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

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

第三个参数:权限控制(linux)

说明: os.OpenFile是一个更一般性的文件打开函数,它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/0。如果出错,错误底层类型是PathError

创建一个新文件,写入内容5句"hello, Gardon’

func main() {filePath := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)if err != nil {fmt.Println("open file err=", err)}defer file.Close()str := "hello,Gardon\n"writer := bufio.NewWriter(file)for i := 0; i < 5; i++ {writer.WriteString(str)}//因为writer十带缓存的,因此在调用WriteString方法时,其实内容时先写入到缓存的//所以需要调用Flush,将缓存的数据真正写入到文件中,否则文件中会没有数据writer.Flush()
}

打开一个存在的文件中,将原来的内容覆盖成新的内容10句"你好"

func main() {filePath := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666)if err != nil {fmt.Println("open file err=", err)}defer file.Close()str := "你好\r\n"writer := bufio.NewWriter(file)for i := 0; i < 10; i++ {writer.WriteString(str)}writer.Flush()
}

打开一个存在的文件,在原来的内容追加内容’ABC!ENGLISH!’

func main() {filePath := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)if err != nil {fmt.Println("open file err=", err)}defer file.Close()str := "'ABC!ENGLISH!\r\n"writer := bufio.NewWriter(file)for i := 0; i < 10; i++ {writer.WriteString(str)}writer.Flush()
}

打开一个存在的文件,将原来的内容读出显示在终端,并且追加5句"hello,北京!"

func main() {filePath := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0666)if err != nil {fmt.Println("open file err=", err)}defer file.Close()reader := bufio.NewReader(file)for {str, err := reader.ReadString('\n')fmt.Print(str)if err == io.EOF {break}}str := "hello,北京\r\n"writer := bufio.NewWriter(file)for i := 0; i < 5; i++ {writer.WriteString(str)}writer.Flush()
}

将文件内容写入另一个文件

func main() {//将test的内容写入test2filePath := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text"filePath2 := "C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test2.text"data, err := ioutil.ReadFile(filePath)if err != nil {fmt.Println("read file err=", err)return}err = ioutil.WriteFile(filePath2, data, 0666)if err != nil {fmt.Println("write file error=", err)return}
}

2.4.判断文件是否存在

golang判断文件或文件夹是否存在的方法为使用os.Stat()函数返回的错误值进行判断:

  1. 如果返回的错误为ni,说明文件或文件夹存在
  2. 如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
  3. 如果返回的错误为其它类型,则不确定是否在存在

写一个函数

func PathExists(path string) (bool, error) {_, err := os.Stat(path)if err == nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, err
}

2.5.拷贝文件

说明:将一张图片/电影/mp3 拷贝到另外一个文件

func Copy(dst Writer, src Reader) (written int64, err error)

注意;:Copy 函数是 io 包提供的

package main
import ("fmt""os""io""bufio" 
)//自己编写一个函数,接收两个文件路径 srcFileName dstFileName
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)}defer srcFile.Close()//通过srcfile ,获取到 Readerreader := bufio.NewReader(srcFile)//打开dstFileNamedstFile, err := os.OpenFile(dstFileName, os.O_WRONLY | os.O_CREATE, 0666)if err != nil {fmt.Printf("open file err=%v\n", err)return }//通过dstFile, 获取到 Writerwriter := bufio.NewWriter(dstFile)defer dstFile.Close()return io.Copy(writer, reader)}func main() {//将d:/flower.jpg 文件拷贝到 e:/abc.jpg//调用CopyFile 完成文件拷贝srcFile := "d:/flower.jpg"dstFile := "e:/abc.jpg"_, err := CopyFile(dstFile, srcFile)if err == nil {fmt.Printf("拷贝完成\n")} else {fmt.Printf("拷贝错误 err=%v\n", err)}}

2.6.统计不同字符的个数

package main
import ("fmt""os""io""bufio" 
)//定义一个结构体,用于保存统计结果
type CharCount struct {ChCount int // 记录英文个数NumCount int // 记录数字的个数SpaceCount int // 记录空格的个数OtherCount int // 记录其它字符的个数
}func main() {//思路: 打开一个文件, 创一个Reader//每读取一行,就去统计该行有多少个 英文、数字、空格和其他字符//然后将结果保存到一个结构体fileName := "e:/abc.txt"file, err := os.Open(fileName)if err != nil {fmt.Printf("open file err=%v\n", err)return}defer file.Close()//定义个CharCount 实例var count CharCount//创建一个Readerreader := bufio.NewReader(file)//开始循环的读取fileName的内容for {str, err := reader.ReadString('\n')if err == io.EOF { //读到文件末尾就退出break}//遍历 str ,进行统计for _, v := range str {switch {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.7.命令行参数

os.Args 是一个 string 的切片,用来存储所有的命令行参数

C:\Users\ASUS\Desktop\GoCode\examine-basic\file>main.exe tom c:/aaa/bbb/test/log 99
命令行参数有 4
args[0]=main.exe
args[1]=tom
args[2]=c:/aaa/bbb/test/log
args[3]=99
func main() {fmt.Println("命令行参数有", len(os.Args))for i, v := range os.Args {fmt.Printf("args[%v]=%v\n", i, v)}
}

说明:前面的方式是比较原生的方式,对解析参数不是特别的方便,特别是带有指定参数形式的命令行。
比如:cmd>main.exe -f c:/aaa.txt -p 200 -u root 这样的形式命令行,go 设计者给我们提供了 flag包,可以方便的解析命令行参数,而且参数顺序可以随意

C:\Users\ASUS\Desktop\00\代码\chapter14\flagdemo>test.exe -u root -pwd root -h 192.168.0.1 -port 3306
user=root pwd=root host=192.168.0.1 port=3306
package main
import ("fmt""flag"
)func main() {//定义几个变量,用于接收命令行的参数值var user stringvar pwd stringvar host stringvar port int//&user 就是接收用户命令行中输入的 -u 后面的参数值//"u" ,就是 -u 指定参数//"" , 默认值//"用户名,默认为空" 说明flag.StringVar(&user, "u", "", "用户名,默认为空")flag.StringVar(&pwd, "pwd", "", "密码,默认为空")flag.StringVar(&host, "h", "localhost", "主机名,默认为localhost")flag.IntVar(&port, "port", 3306, "端口号,默认为3306")//这里有一个非常重要的操作,转换, 必须调用该方法flag.Parse()//输出结果fmt.Printf("user=%v pwd=%v host=%v port=%v", user, pwd, host, port)}

3.JSON

  • JSON(avaScript Object Notation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。key-val
  • JSON是在2001年开始推广使用的数据格式,目前已经成为主流的数据格式
  • JSON易于机器解析和生成,并有效地提升网络传输效率,通常程序在网络传输时会先将数据(结构体、map等)序列化成json字符串,到接收方得到json字符串时,在反序列化恢复成原来的数据类型(结构体、map等)。这种方式已然成为各个语言的标准。
  • 在JS语言中,一切都是对象。因此,任何的数据类型都可以通过JSON来表示,例如字符串、数字、对象、数组,map,结构体等.

JSON数据在线解析:www.json.cn

序列化

ison 序列化是指,将有 key-value 结构的数据类型(比如结构体、map、切片)序列化成ison 字符串的操作。

package main
import ("fmt""encoding/json"
)//定义一个结构体
type Monster struct {Name string `json:"monster_name"` //反射机制Age int `json:"monster_age"`Birthday string //....Sal float64Skill string
}func testStruct() {//演示monster := Monster{Name :"牛魔王",Age : 500 ,Birthday : "2011-11-11",Sal : 8000.0,Skill : "牛魔拳",}//将monster 序列化data, err := json.Marshal(&monster) //..if err != nil {fmt.Printf("序列号错误 err=%v\n", err)}//输出序列化后的结果fmt.Printf("monster序列化后=%v\n", string(data))}//将map进行序列化
func testMap() {//定义一个mapvar a map[string]interface{}//使用map,需要makea = make(map[string]interface{})a["name"] = "红孩儿"a["age"] = 30a["address"] = "洪崖洞"//将a这个map进行序列化//将monster 序列化data, err := json.Marshal(a)if err != nil {fmt.Printf("序列化错误 err=%v\n", err)}//输出序列化后的结果fmt.Printf("a map 序列化后=%v\n", string(data))}//演示对切片进行序列化, 我们这个切片 []map[string]interface{}
func testSlice() {var slice []map[string]interface{}var m1 map[string]interface{}//使用map前,需要先makem1 = make(map[string]interface{})m1["name"] = "jack"m1["age"] = "7"m1["address"] = "北京"slice = append(slice, m1)var m2 map[string]interface{}//使用map前,需要先makem2 = make(map[string]interface{})m2["name"] = "tom"m2["age"] = "20"m2["address"] = [2]string{"墨西哥","夏威夷"}slice = append(slice, m2)//将切片进行序列化操作data, err := json.Marshal(slice)if err != nil {fmt.Printf("序列化错误 err=%v\n", err)}//输出序列化后的结果fmt.Printf("slice 序列化后=%v\n", string(data))}//对基本数据类型序列化,对基本数据类型进行序列化意义不大
func testFloat64() {var num1 float64 = 2345.67//对num1进行序列化data, err := json.Marshal(num1)if err != nil {fmt.Printf("序列化错误 err=%v\n", err)}//输出序列化后的结果fmt.Printf("num1 序列化后=%v\n", string(data))
}func main() {//演示将结构体, map , 切片进行序列号testStruct()testMap()testSlice()//演示对切片的序列化testFloat64()//演示对基本数据类型的序列化
}

注意事项
对于结构体的序列化,如果我们希望序列化后的key的名字,又我们自己重新制定,那么可以给 struct指定一个 tag 标签.

反序列化

json 反序列化是指,将json 字符串反序列化成对应的数据类型(比如结构体、map、切片)的操作

  • 在反序列化一个json字符串时,要确保反序列化后的数据类型和原来序列化前的数据类型一致。
  • 如果 json 字符串是通过程序获取到的,则不需要再对“ 转义处理。
package main
import ("fmt""encoding/json"
)//定义一个结构体
type Monster struct {Name string  Age int Birthday string //....Sal float64Skill string
}//演示将json字符串,反序列化成struct
func unmarshalStruct() {//说明str 在项目开发中,是通过网络传输获取到.. 或者是读取文件获取到str := "{\"Name\":\"牛魔王~~~\",\"Age\":500,\"Birthday\":\"2011-11-11\",\"Sal\":8000,\"Skill\":\"牛魔拳\"}"//定义一个Monster实例var monster Monstererr := json.Unmarshal([]byte(str), &monster)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 monster=%v monster.Name=%v \n", monster, monster.Name)}
//将map进行序列化
func testMap() string {//定义一个mapvar a map[string]interface{}//使用map,需要makea = make(map[string]interface{})a["name"] = "红孩儿~~~~~~"a["age"] = 30a["address"] = "洪崖洞"//将a这个map进行序列化//将monster 序列化data, err := json.Marshal(a)if err != nil {fmt.Printf("序列化错误 err=%v\n", err)}//输出序列化后的结果//fmt.Printf("a map 序列化后=%v\n", string(data))return string(data)}//演示将json字符串,反序列化成map
func unmarshalMap() {//str := "{\"address\":\"洪崖洞\",\"age\":30,\"name\":\"红孩儿\"}"str := testMap()//定义一个mapvar a map[string]interface{} //反序列化//注意:反序列化map,不需要make,因为make操作被封装到 Unmarshal函数err := json.Unmarshal([]byte(str), &a)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 a=%v\n", a)}//演示将json字符串,反序列化成切片
func unmarshalSlice() {str := "[{\"address\":\"北京\",\"age\":\"7\",\"name\":\"jack\"}," + "{\"address\":[\"墨西哥\",\"夏威夷\"],\"age\":\"20\",\"name\":\"tom\"}]"//定义一个slicevar slice []map[string]interface{}//反序列化,不需要make,因为make操作被封装到 Unmarshal函数err := json.Unmarshal([]byte(str), &slice)if err != nil {fmt.Printf("unmarshal err=%v\n", err)}fmt.Printf("反序列化后 slice=%v\n", slice)
}func main() {unmarshalStruct()unmarshalMap()unmarshalSlice()
}

4.单元测试

Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试,testing 框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。通过单元测试,可以解决如下问题:

  • 确保每个函数是可运行并且运行结果是正确的
  • 确保写出来的代码性能是好的
  • 单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定

案例:

cal.go

package calfunc addNums(a int, b int) int {return a + b
}

cal_test.go

package calimport "testing"func TestAddNums(t *testing.T) {res := addNums(3, 4)if res != 7 {t.Fatalf("addNums(3,4) error")}t.Logf("test addNums success")
}

单元测试快速入门总结

\1) 测试用例文件名必须以 _test.go 结尾。 比如 cal_test.go , cal 不是固定的。

\2) 测试用例函数必须以 Test 开头,一般来说就是 Test+被测试的函数名,比如 TestAddUpper

\3) TestAddUpper(t *tesing.T) 的形参类型必须是 *testing.T 【看一下手册】

\4) 一个测试用例文件中,可以有多个测试用例函数,比如 TestAddUpper、TestSub

\5) 运行测试用例指令

​ (1) cmd>go test [如果运行正确,无日志,错误时,会输出日志]

(2) cmd>go test -v [运行正确或是错误,都输出日志]

\6) 当出现错误时,可以使用 t.Fatalf 来格式化输出错误信息,并退出程序

\7) t.Logf 方法可以输出相应的日志

\8) 测试用例函数,并没有放在 main 函数中,也执行了,这就是测试用例的方便之处[原理图].

\9) PASS 表示测试用例运行成功,FAIL 表示测试用例运行失败

\10) 测试单个文件,一定要带上被测试的原文件

go test -v cal_test.go cal.go

\11) 测试单个方法

go test -v -test.run TestAddUpper

5.协程和管道

5.1.进程和线程

  • 进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位

  • 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。

  • 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行。

  • 一个程序至少有一个进程,一个进程至少有一个线程

5.2.并发和并行

  • 多线程程序在单核上运行,就是并发
  • 多线程程序在多核上运行,就是并行

5.3.go协程和主线程

Go 主线程(有程序员直接称为线程/也可以理解成进程):一个Go 线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程「编译器做优化]。

Go 协程的特点

  • 有独立的栈空间
  • 共享程序堆空间
  • 调度由用户控制
  • 协程是轻量级的线程

5.4.goroutine快速入门

案例:

func main() {go test() //开启了一个协程for i := 1; i <= 10; i++ {fmt.Println("main() ", i)time.Sleep(time.Second / 4)}
}
func test() {for i := 1; i <= 10; i++ {fmt.Println("test() ", i)time.Sleep(time.Second / 4)}
}

如果主线程退出了,则协程即使还没有执行完毕,也会退出

当然协程也可以再主线程没有退出前,就自己结束了,比如完成了自己的任务

5.5.快速入门小结

  • 主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。
  • 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
  • Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了

5.6.MPG 模式

  • M: 操作系统的主线程(是物理线程)
  • P:协程执行需要的上下文
  • G: 协程
MPG模式运行的状态1

在这里插入图片描述

MPG模式运行的状态2

在这里插入图片描述

设置Golang运行的cpu数
import ("fmt""runtime"
)func main() {cpuNum := runtime.NumCPU()fmt.Println("cpuNum=", cpuNum)//可以自己设置使用多少个cpuruntime.GOMAXPROCS(cpuNum)
}

g01.8后,默认让程序运行在多个核上,可以不用设置了

go1.8前,还是要设置一下,可以更高效的利益cpu

5.7.channel(管道)

5.7.1.使用全局变量加锁
package mainimport ("fmt""sync""time"
)var (myMap = make(map[int]int, 10)//声明一个全局的互斥锁//sync是包, synchornized 同步//Mutex 互斥lock sync.Mutex
)func main() {for i := 1; i <= 200; i++ {go test(i)}time.Sleep(time.Second * 10)lock.Lock()for i, v := range myMap {fmt.Printf("myMap[%v]=%v", i, v)}lock.Unlock()
}
func test(n int) {res := 1for i := 1; i <= n; i++ {res *= i}//加锁lock.Lock()myMap[n] = res//解锁lock.Unlock()
}
5.7.2.为什么需要channel
  1. 前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美
  2. 主线程在等待所有 goroutine 全部完成的时间很难确定,我们这里设置 10 秒,仅仅是估算。
  3. 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有 goroutine 处于工作状态,这时也会随主线程的退出而销毁
  4. 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作。
  5. 上面种种分析都在呼唤一个新的通讯机制-channel
5.7.3.channel 的基本介绍
  1. channle 本质就是一个数据结构-队列
  2. 数据是先进先出【FIFO : first in first out】
  3. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  4. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据

channel是线程安全的,多个协程操作一个管道时,不会发生资源竞争问题

5.7.4.声明channel

var 变量名 chan 数据类型

举例:

var	intChan	chan int //intChan 用于存放 int 数据
var	mapChan chan map[int]string //mapChan用于存放map[int]string类型
var	perChan	chan Person
var	perChan2 chan *Person
...

说明:

  • channel 是引用类型
  • channel 必须初始化才能写入数据, 即 make 后才能使用管道是有类型的,intChan 只能写入 整数 int

使用案例:

package mainimport "fmt"func main() {//创建一个可以存放 3 个 int 类型的管道var intChan chan intintChan = make(chan int, 3)//2. 看看 intChan 是什么fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan)//向管道写入数据intChan <- 10num := 211intChan <- numfmt.Printf("channel len=%v, cap=%v \n", len(intChan), cap(intChan))num2 := <-intChanfmt.Println(num2)num3 := <-intChanfmt.Println(num3)//在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlocknum4 := <-intChanfmt.Println(num4)fmt.Println("num3=", num3, "num4=", num4)
}
5.7.5.channel 使用的注意事项
  • channel 中只能存放指定的数据类型
  • channle 的数据放满后,就不能再放入了
  • 如果从 channel 取出数据后,可以继续放入
  • 在没有使用协程的情况下,如果 channel 数据取完了,再取,就会报 dead lock

在这里插入图片描述

5.7.6.channel的关闭

使用内置函数close 可以关闭 channel, 当 channel 关闭后,就不能再向 channel 写数据了,但是仍然可以从该 channel 读取数据

func main() {intChan := make(chan int, 3)intChan <- 100intChan <- 200close(intChan)//这时不能够再写入数到channel//intChan<-300fmt.Println("ok")//当管道关闭后,读取数据时可以的n1 := <-intChanfmt.Println(n1)
}
5.7.7.channel 的遍历

channel 支持 for–range 的方式进行遍历,注意两个细节

  1. 在遍历时,如果 channel 没有关闭,则回出现 deadlock 的错误
  2. 在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完后,就会退出遍历
func main() {intChan := make(chan int, 100)for i := 0; i < 100; i++ {intChan <- i * 2}//遍历管道不能使用普通的for循环//在遍历时,如果channel没有关闭,则会出现deadlock的错误//在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历close(intChan)for v := range intChan {fmt.Println("v=", v)}
}

5.8.应用实例

在这里插入图片描述

在这里插入图片描述

package mainimport "fmt"func main() {intChan := make(chan int, 50)exitChan := make(chan bool, 1)go writeData(intChan)go readData(intChan, exitChan)for {_, ok := <-exitChanif !ok {break}}
}// write Data
func writeData(intChan chan int) {for i := 1; i <= 50; i++ {intChan <- ifmt.Printf("readData写入数据=%v\n", i)}close(intChan)
}
func readData(intChan chan int, exitChan chan bool) {for {v, ok := <-intChanif !ok {break}fmt.Printf("readData读到数据=%v\n", v)}//读取数据后,即任务完成exitChan <- trueclose(exitChan)
}
阻塞

在这里插入图片描述

求素数
package mainimport ("fmt""time"
)func main() {intChan := make(chan int, 1000)primeChan := make(chan int, 2000)exitChan := make(chan bool, 4)go putNum(intChan)for i := 0; i < 4; i++ {go primeNum(intChan, primeChan, exitChan)}go func() {for i := 0; i < 4; i++ {<-exitChan}close(primeChan)}()for {res, ok := <-primeChanif !ok {break}fmt.Printf("素数=%d\n", res)}fmt.Println("main线程退出")
}
func putNum(intChan chan int) {for i := 1; i <= 8000; i++ {intChan <- i}close(intChan)
}
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {time.Sleep(time.Millisecond * 10)num, ok := <-intChanif !ok {break}flag = truefor i := 2; i < num; i++ {if num%i == 0 {flag = falsebreak}}if flag {primeChan <- num}}fmt.Println("有一个primeNum协程因为取不到数据退出")exitChan <- true
}

5.9.channel 使用细节和注意事项

1.channel 可以声明为只读,或者只写性质

//只写
var chan2 chan<- int
chan2 = make(chan<- int, 3)
chan2 <- 20
//只读
var chan3 <-chan int
num := <-chan3
fmt.Println(num)

2.只读和只写的最佳实践案例

在这里插入图片描述

3.使用 select 可以解决从管道取数据的阻塞问题

func main() {intChan := make(chan int, 10)for i := 0; i < 10; i++ {intChan <- i}stringChan := make(chan string, 5)for i := 0; i < 5; i++ {stringChan <- "hello" + fmt.Sprintf("%d", i)}for {select {//注意:这里如果intChan一直没有关闭,不会一直阻塞而deadlock//会自动到下一个case匹配case v := <-intChan:fmt.Printf("%d\n", v)case v := <-stringChan:fmt.Printf("%s\n", v)default:fmt.Printf("都取不到了")return}}
}

4.goroutine 中使用 recover,解决协程中出现 panic,导致程序崩溃问题

在这里插入图片描述

package mainimport ("fmt""time"
)// 函数
func sayHello() {for i := 0; i < 10; i++ {time.Sleep(time.Second)fmt.Println("hello,world")}
}// 函 数
func test() {//这里我们可以使用 defer + recoverdefer func() {if err := recover(); err != nil {fmt.Println("test() 发生错误", err)}}()//定义了一个 mapvar myMap map[int]stringmyMap[0] = "golang" //error
}func main() {go sayHello()go test()for i := 0; i < 10; i++ {fmt.Println("main() ok=", i)time.Sleep(time.Second)}
}

6.反射

6.1.反射的基本介绍

  1. 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要 import (“reflect”)

示意图:

在这里插入图片描述

6.2.反射的应用场景

在这里插入图片描述

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/imag在这里插入图片描述
es/20230724024159.png?origin_url=Go%E8%BF%9B%E9%98%B6%2Fimage-20241009193451064.png&pos_id=img-kbADoGEh-1731135619287)

6.3.反射重要的函数和概念

在这里插入图片描述

3)变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。画出示意图

在这里插入图片描述

在这里插入图片描述

6.4.快速入门

请编写一个案例,演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作代码演示

package mainimport ("fmt""reflect"
)func main() {var num int = 100reflectTest01(num)
}
func reflectTest01(b interface{}) {tTyp := reflect.TypeOf(b)fmt.Println(tTyp)//获取reflect.ValuerVal := reflect.ValueOf(b)n2 := 2 + rVal.Int()fmt.Println("n2=", n2)fmt.Printf("rVal=%v rVal type=%T\n", rVal, rVal)//下面将rVal转成interface{}iV := rVal.Interface()//将interface{}通过断言转成需要的类型num2 := iV.(int)fmt.Println("num2=", num2)
}

请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作代码演示

func reflectTest02(b interface{}) {tTyp := reflect.TypeOf(b)fmt.Println(tTyp)//获取reflect.ValuerVal := reflect.ValueOf(b)//下面将rVal转成interface{}iV := rVal.Interface()fmt.Printf("%v,%T\n", iV, iV)stu, ok := iV.(Student)if ok {fmt.Println(stu.Name)}
}

6.5.反射的注意事项和细节

  1. reflect.Value.Kind,获取变量的类别,返回的是一个常量

在这里插入图片描述

  1. Type 和 Kind 的区别
  • Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的
  • 比如: var num int = 10 num 的 Type 是 int , Kind 也是 int
  • 比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct

在这里插入图片描述

  1. 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法

在这里插入图片描述

6.reflect.Value.Elem() 应该如何理解?

在这里插入图片描述

6.6.反射最佳实践

1.使用反射来遍历结构体的字段调用结构体的方法,并获取结构体标签的值

package mainimport ("fmt""reflect"
)// Monster 定义了一个 Monster 结构体
type Monster struct {Name  string  `json:"name"`Age   int     `json:"monster_age"`Score float32 `json:"成绩"`Sex   string
}// Print 方法,显示 s 的值
func (s Monster) Print() {fmt.Println("---start~ ")fmt.Println(s)fmt.Println("---end~   ")
}// GetSum 方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {return n1 + n2
}// Set 方法, 接收四个值,给 s 赋值
func (s Monster) Set(name string, age int, score float32, sex string) {s.Name = names.Age = ages.Score = scores.Sex = sex
}func TestStruct(a interface{}) {//获取 reflect.Type 类型typ := reflect.TypeOf(a)//获取 reflect.Value 类型val := reflect.ValueOf(a)//获取到 a 对应的类别kd := val.Kind()//如果传入的不是 struct,就退出if kd != reflect.Struct {fmt.Println("expect struct")return}//获取到该结构体有几个字段num := val.NumField()fmt.Printf("struct has %d fields\n", num) //4//变量结构体的所有字段for i := 0; i < num; i++ {fmt.Printf("Field %d:  值为=%v\n", i, val.Field(i))//获取到 struct 标签, 注意需要通过 reflect.Type 来获取 tag 标签的值tagVal := typ.Field(i).Tag.Get("json")//如果该字段于 tag 标签就显示,否则就不显示if tagVal != "" {fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)}}//获取到该结构体有多少个方法numOfMethod := val.NumMethod()fmt.Printf("struct has %d methods\n", numOfMethod)//var params []reflect.Value//方法的排序默认是按照 函数名的排序(ASCII 码)val.Method(1).Call(nil) //获取到第二个方法。调用它//调用结构体的第 1 个方法 Method(0)var params []reflect.Value //声明了 []reflect.Valueparams = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := val.Method(0).Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Valuefmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/
}
func main() {//创建了一个 Monster 实例var a Monster = Monster{Name: "黄鼠狼精", Age: 400,Score: 30.8,}//将 Monster 实例传递给 TestStruct 函数TestStruct(a)
}

7.TCP编程

18.1 网络编程基本介绍

Golang 的主要设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端 程序必不可少也是至关重要的一部分。

网络编程有两种:

  1. TCP socket 编程,是网络编程的主流。之所以叫 Tcp socket 编程,是因为底层是基于 Tcp/ip 协议的. 比如: QQ 聊天 [示意图]
  2. b/s 结构的 http 编程,我们使用浏览器去访问服务器时,使用的就是 http 协议,而 http 底层依旧是用 tcp socket 实现的。[示意图] 比如: 京东商城 【这属于 go web 开发范畴 】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/885462.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Node(节点)、Menu(菜单) 和 Tab(标签页)之间的关系

在系统开发中&#xff0c;尤其是在涉及到前端界面设计和后台管理系统时&#xff0c;我们经常会看到 Node&#xff08;节点&#xff09;、Menu&#xff08;菜单&#xff09; 和 Tab&#xff08;标签页&#xff09; 这几个概念。这些概念有不同的用途和功能&#xff0c;理解它们之…

Python数据分析案例64——杭帮菜美食探索数据分析可视化

案例背景 杭州是真没啥美食呀.....但是 总是还是有好吃的店家&#xff0c;于是就发挥专业长处&#xff0c;进行一下分析&#xff0c;看看杭帮菜的一些特点。。例如看看品种分布啊&#xff0c;类型分布啊&#xff0c;行政区的分布啊&#xff0c;店铺评分的一些分布啊&#xff0…

基于SSD模型的路面坑洼检测系统,支持图像、视频和摄像实时检测【pytorch框架、python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 基于SSD模型的路面坑洼检测系统&#xff0c;支持图像、视频和摄像实时检测【pytorch框架、python源码】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于SSD模型的路面坑洼检测系统是在 Py…

《Python编程实训快速上手》第四天--字符串操作

一、处理字符串 1、单引号和双引号 Python中单双引号均可以表示字符串&#xff0c;区别在于&#xff1a; 1、双引号中可以使用到单引号 2、单引号字符串中如果要使用单引号&#xff0c;要使用到转义字符 \ \ \t \n \\ 原始字符串 在开始的引号前加r&#xf…

泷羽sec学习打卡-Windows基础命令

声明 学习视频来自B站UP主 泷羽sec,如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 关于windows的那些事儿-Base 一、Windows-BaseWindows有哪些版本呢&#xff0c;有什么区别呢&#xff1f…

Node.js——fs模块-文件夹操作

1、借助Node.js的能力&#xff0c;我们可以对文件夹进行创建、读取、删除等操作 2、方法 方法 说明 mkdir/mkdirSync 创建文件夹 readdir/readdirSync 读取文件夹 rmdir/rmdirSync 删除文件夹 3、语法 其余的方法语法类似 本文的分享到此结束&#xff0c;欢迎大家评论区…

VMware Fusion和centos 8的安装

资源 本文用到的文件&#xff1a;centos8镜像 , VMware 软件包 , Termius 文件链接: https://pan.baidu.com/s/1kOES_ZJ8NGN-BnJl6NC7Sg?pwd63ct 安装虚拟机 先 安装 vmware &#xff0c;然后打开&#xff0c;将下载的 iso 镜像拖入 拖入镜像文件iso Continue, 然后随便选…

【嵌入式开发——ARM】1ARM架构

嵌入式领域&#xff0c;使用ARM架构的芯片公司可不占少数吧&#xff0c;intel的x86架构主要占据PC、服务器市场&#xff0c;ARM架构主要占据移动市场。x86架构和ARM架构不同的主要原因&#xff0c;是背后使用的计算机指令集不同。计算机有自己的语言系统&#xff08;汇编&#…

LabVIEW扫描探针显微镜系统

开发了一套基于LabVIEW软件开发的扫描探针显微镜系统。该系统专为微观尺度材料的热性能测量而设计&#xff0c;特别适用于纳米材料如石墨烯、碳纳米管等的研究。系统通过LabVIEW编程实现高精度的表面形貌和热性能测量&#xff0c;广泛应用于科研和工业领域。 项目背景 随着纳…

JavaScript day01 笔记

一、引入方式 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。通过 script 标签将 JavaScript 代码引入到 HTML 中 1️⃣内部 通过 script 标签包裹 JavaScript 代码&#xff08;一般就写在</script>的…

【Git】Liunx环境下Git的使用:“克隆,提交,推送“

目录 一、常用参数 二、我们为什么要使用Git&#xff1f; 三、创建远程仓库 第一步&#xff1a;创建对应代码托管平台账号。 第二步&#xff1a;在托管平台创建仓库 第三步&#xff1a;完善仓库内容&#xff08;选择性使用&#xff09; 开源和私有 四、克隆远程仓库到本…

机器人零位、工作空间、坐标系及其变换,以UR5e机器人为例

机器人中的主要坐标系 在机器人中&#xff0c;常用的坐标系包括&#xff1a; 基坐标系&#xff08;Base Frame&#xff09;&#xff1a;固定在机器人基座上的坐标系&#xff0c;用于描述机器人的整体位置和方向&#xff0c;是其他所有坐标系的参考点。 连杆坐标系&#xff08…

JavaSE:运算符 (学习笔记)

目录 一&#xff0c;算术运算符 【1】 共同点&#xff1a; 【2】 不同点&#xff1a; 二&#xff0c;关系运算符 三&#xff0c;逻辑运算符 2&#xff0c;&和&&的区别和联系 { |和||的区别和联系 }---两题类似 四&#xff0c;赋值运算符 五&#xff0c;拓展…

C++中类的默认成员函数

默认成员函数 1.构造函数2.析构函数3.拷贝构造函数4.赋值运算符重载4.1运算符重载4.2赋值运算符重载 #mermaid-svg-oipiwg9stvONvYK0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oipiwg9stvONvYK0 .error-icon{f…

游戏引擎学习第一天

视频参考: https://www.bilibili.com/video/BV1zGDCYHErA/ 创建一个保存项目的路径 VS的安装略过&#xff0c;个人自行百度 1. vs 创建第一个CMAKE的窗口项目 game.cpp 修改如下的代码 到https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winmain 去…

视频播放相关的杂记

基于QT FFMPEG设计一款 RTMP协议推流、视频录制软件 实现的功能&#xff1a; &#xff08;1&#xff09;将摄像头视频流 麦克风音频流合并&#xff0c;并推到流媒体服务器 &#xff08;2&#xff09;将摄像头视频流 麦克风音频流保存到本地磁盘 基于QtFFMPEG设计一款RTM…

2024年将尽,我们开始为ESG的未来感到担忧 | 深度

2024已经接近尾声了&#xff0c;今年ESG的发展状况非常两级分化。最极端者&#xff0c;有人觉得ESG要在2024年起飞的&#xff0c;毕竟今年三大交易所出台了《上市公司可持续发展报告指引》&#xff0c;“A股公司进入ESG信披新纪元”。而在另一个极端&#xff0c;有人认为ESG“将…

windows中docker安装redis和redisinsight记录

创建一个Redis运行容器&#xff0c;命令如下 docker run -it -d --name redis -p 6379:6379 redis --bind 0.0.0.0 --protected-mode no -d 代表Redis容器后台运行 --name redis 给创建好的容器起名叫redis -p 6379:6379 将容器的6379端口映射到宿主机的6379端口&#xff0c;注…

atcoder解题

#include <iostream> #include <vector>using namespace std;int main() {long long N, M;cin >> N >> M;vector<long long> X(M), A(M);long long totalStones 0;// 读入 X 和 Afor (int i 0; i < M; i) {cin >> X[i];}for (int i …

Vue:侦听属性

Vue&#xff1a;侦听属性 watch深度侦听异步任务 watch 在Vue中&#xff0c;允许用户在数据改变时&#xff0c;做出一定的处理。 语法&#xff1a; new Vue({watch:{属性名:{handler(newValue, oldValue){// 函数体} }} })当一个属性被写入watch中&#xff0c;每当这个属性…