当前位置: 首页 > news >正文

【Go语言从入门到实战】面向对象编程篇

面向对象编程

Go语言的面向对象编程和其他语言有非常大的差别。

image-20230428111344935

Go 是一种面向对象的语言吗?

是和不是。虽然 Go 有类型和方法,并允许面向对象的编程风格,但没有类型层次结构(继承)。Go 中的“接口”概念提供了一种不同的方法,我们认为这种方法易于使用,并且在某些方面更通用。还有一些方法可以将类型嵌入到其他类型中,以提供类似于(但不完全相同)子类化的东西。此外,Go 中的方法比 C++ 或 Java 中的方法更通用:它们可以为任何类型的数据定义,甚至是内置类型,例如普通的“未装箱”整数。它们不限于结构(类)。

此外,缺少类型层次结构使得 Go 中的“对象”感觉比 C++ 或 Java 等语言中的“对象”轻量级得多。

封装数据和行为

结构体定义

image-20230428121213526

实例创建及初始化

image-20230428121307421

type Employee struct {Id   stringName stringAge  int
}func TestCreateEmployeeObj(t *testing.T) {e := Employee{"0", "Bob", 20}e1 := Employee{Name: "Mike", Age: 30}e2 := new(Employee) // 返回指针e2.Id = "2"e2.Name = "Rose"e2.Age = 22t.Log(e)t.Log(e1)t.Log(e1.Id)t.Log(e2)t.Logf("e is %T", e)t.Logf("e2 is %T", e2)
}

image-20230428122127640

行为(方法)定义

image-20230428122402465

  • 第一种

    func (e Employee) String() string {fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))fmt.Println()return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }func TestStructOperations(t *testing.T) {e := Employee{"0", "Bob", 20}fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))fmt.Println()t.Log(e.String())
    }
    

    image-20230428123715323

    所以这种写法会有复制的开销。

  • 第二种

    func (e *Employee) String() string {fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))fmt.Println()return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }func TestStructOperations(t *testing.T) {e := &Employee{"0", "Bob", 20} // 传递引用fmt.Printf("Address is %x", unsafe.Pointer(&e.Name))fmt.Println()t.Log(e.String())
    }
    

    image-20230428123601046

    更推荐这种。

接口(定义交互协议)

Go的接口和很多主流编程语言的接口有很大的区别。

Java代码的示例:

image-20230428124443597

Duck Type式接口

Go语言的interface:

image-20230428125301615

type Programmer interface {WriteHelloWorld() string
}type GoProgrammer struct {
}func (g *GoProgrammer) WriteHelloWorld() string { // duck type 鸭子类型return "Hello World"
}func TestClient(t *testing.T) {var p Programmerp = new(GoProgrammer)t.Log(p.WriteHelloWorld())
}

image-20230428143644240

Go接口

image-20230428130027398

接口变量

image-20230428130325699

自定义类型

image-20230428130434903

type IntConv func(op int) int// 计算函数操作的时长
func timeSpend(inner IntConv) IntConv { // 以前的方法特别的长 我们可以用自定义类型做替换// 类似装饰者模式,对原来的函数进行了一层包装return func(n int) int {start := time.Now()ret := inner(n)fmt.Println("time spend:", time.Since(start).Seconds())return ret}
}

扩展和复用(类似继承)

Go语言无法天然支持继承,但是又想要实现面向对象的特性。

即父类对象 使用子类对象初始化,那么该父类对象调用的函数就是子类实现的函数 ,从而满足LSP(子类交换原则)。

案例一: Go语言 支持扩展父类的功能,如下代码:

package oriented_testimport ("fmt""testing"
)// Pet 类
type Pet struct {
}func (p *Pet) Speak(){ // Pet类的函数成员fmt.Print("Pet speak.\n")
}func (p *Pet) SpeakTo(host string) { // Pet类的函数成员p.Speak()fmt.Println("Pet SpeakTo ", host)
}// Dog 扩展Pet的功能
type Dog struct {p *Pet
}// 扩展父类的方法
func (d *Dog) Speak(){d.p.Speak()
}// 扩展父类的方法
func (d *Dog) SpeakTo(host string) {d.Speak()fmt.Println("Dog Speakto ", host)
}func TestDog(t *testing.T) {dog := new(Dog)dog.SpeakTo("Test dog")
}

以上测试代码的输出如下:

image-20230428141543094

dog 的 SpeakTo 中调用了 dog 的 Speak,其中调用了 Pet 的 Speak,所以输出正常。
Pet 和 Dog 调用不会相互影响,完全由用户决定。

但是这和我们所想需要的不同,Pet 类有自己的方法,Dog 类有自己的方法,两者作用域完全不同。

这里Go语言推出了匿名嵌套类型,即 Dog 类不用实现自己的和 Pet 类同名的方法即可,通过在 Dog 类的声明中变更 Pet 成 员。

匿名嵌套类型

案例二: Go语言支持匿名函数类型

// Dog 扩展Pet的功能
type Dog struct {Pet
}

这样即不需要 Dog 声明自己的同名函数成员,默认的调用即为 Pet 成员函数的调用。

package oriented_testimport ("fmt""testing"
)type Pet struct {
}func (p *Pet) Speak(){fmt.Print("Pet speak.\n")
}func (p *Pet) SpeakTo(host string) {p.Speak()fmt.Println("Pet SpeakTo ", host)
}// Dog 扩展Pet的功能
type Dog struct {Pet // 支持匿名嵌套类型
}func TestDog(t *testing.T) {var dog Dogdog.Speak()dog.SpeakTo("Test dog")
}

最终的输出如下:

image-20230428141503654

调用的都是 Pet 的成员函数,感觉像是继承了,因为继承默认就是子类能够使用父类的公有成员。

在匿名嵌套类型下,我们想要完整尝试一下Go语言是否真正支持继承,可以像之前的代码一样在 Dog 中实现 Pet 的同名函数,且能够通过父类对象调用子类的成员方法,像 C++/Java 这样进行向上类型转换(本身是不可能的,Go语言不支持显式类型转换)。

案例三: Go语言不支持继承,如下代码:

package oriented_testimport ("fmt""testing"
)type Pet struct {
}func (p *Pet) Speak(){fmt.Print("Pet speak.\n")
}func (p *Pet) SpeakTo(host string) {p.Speak()fmt.Println("Pet SpeakTo ", host)
}// Dog 扩展Pet的功能
type Dog struct {//p *PetPet // 支持匿名嵌套类型
}// 重载父类的方法
func (d *Dog) Speak(){fmt.Print("Dog speak.\n")
}// 重载父类的方法
func (d *Dog) SpeakTo(host string) {d.Speak()fmt.Println("Dog Speakto ", host)
}func TestDog(t *testing.T) {var dog Pet = new(Dog) // 这里就会编译错误dog.Speak()dog.SpeakTo("Test dog")
}
cannot use new(Dog) (value of type *Dog) as Pet value in variable declaration
不支持将Pet类型转换为Dog类型

总结一下,Go语言并不支持继承,能够支持接口的扩展 和 复用**(匿名嵌套类型)**,内嵌这种方式是完全不能当成继承来用的,因为它不支持访问子类的方法数据(重载),不支持LSP原则。

其中扩展 就是不同类实现相同的成员函数,能够实现类似于案例一中的扩展接口形态。

复用则是通过匿名嵌套类型实现 类似于重载的功能,可以看看案例二的代码。

多态

image-20230428143317910

type Programmer interface {WriteHelloWorld() string
}type GoProgrammer struct {
}func (g *GoProgrammer) WriteHelloWorld() string {return "fmt.Println(\"Hello World\")"
}type JavaProgrammer struct {
}func (j *JavaProgrammer) WriteHelloWorld() string {return "System.out.println(\"Hello World\")"
}// 多态
func writeFirstProgram(p Programmer) {fmt.Printf("%T %v\n", p, p.WriteHelloWorld())
}func TestClient(t *testing.T) {goProgrammer := new(GoProgrammer)javaProgrammer := new(JavaProgrammer)writeFirstProgram(goProgrammer)writeFirstProgram(javaProgrammer)
}

image-20230428144147613

空接口与断言

image-20230428144326958

func DoSomething(p interface{}) {// '.' 断言,p.(int),p断言为int类型if i, ok := p.(int); ok {fmt.Println("Integer", i)return}if s, ok := p.(string); ok {fmt.Println("string", s)return}fmt.Println("UnKnow type")
}func TestEmptyInterface(t *testing.T) {DoSomething(10)DoSomething("10")DoSomething(10.00)
}

image-20230428145558309

Go接口最佳实践

image-20230428145718339

错误机制

error

image-20230428150518369

package errorimport ("errors""testing"
)func GetFibonacci(n int) ([]int, error) {if n < 2 || n > 100 {return nil, errors.New("n should be in [2, 100]")}fibList := []int{1, 1}for i := 2; i < n; i++ {fibList = append(fibList, fibList[i-1]+fibList[i-2])}return fibList, nil
}func TestGetFibonacci(t *testing.T) {if v, err := GetFibonacci(-10); err != nil {t.Error(err)} else {t.Log(v)}
}

image-20230428161919642

最佳实践

image-20230428162534768

尽早失败,避免嵌套!

例:

func GetFibonacci2(str string) {var (i    interr  errorlist []int)if i, err = strconv.Atoi(str); err != nil {fmt.Println("Error", err)return}if list, err = GetFibonacci(i); err != nil {fmt.Println("Error", err)return}fmt.Println(list)
}

panic

image-20230428163230055

panic vs os.Exit

image-20230428163307433

revocer

image-20230428164252392

recover 类似于 java 的 catch

func TestRecover(t *testing.T) {defer func() {if err := recover(); err != nil {fmt.Println("recovered from", err)}}()fmt.Println("Start")panic(errors.New("something wrong"))
}

image-20230428183030584

image-20230428165703129

其实上面这种修复方式是非常危险的。

我们一定要当心自己 revocer 在做的事,因为我们 revocer 并不检测到底发生了什么错误,而只是记录了一下或者直接忽略掉了,这时可能系统某些核心资源消耗完了,但我们把他强制恢复之后系统依然是不能正常工作的,还会导致我们的健康检查程序 health check 检查不出当前系统的问题,因为很多 health check 只是检查当前系统的进程在还是不在, 因为我们的进程是在的,所以就会形成僵尸进程,它还活着,但它不能提供服务。

image-20230428165726627

如果出现了这种问题,我们可以用 “Let is Crash” 可恢复的设计模式,我们直接 crash 掉,这样守护进程就会重新把服务进程提起来(说的有点高大上,其实就是重启),重启是恢复不确定性错误的最好方法。

包 package

构建可复用的模块(包)

image-20230428183701611

image-20230428185412938

my_series.go

package seriesfunc GetFibonacci(n int) ([]int, error) {ret := []int{1, 1}for i := 2; i < n; i++ {ret = append(ret, ret[i-1]+ret[i-2])}return ret, nil
}

package_test.go

引用另一个包中的方法:

package clientimport ("mygoland/geekvideo/ch13/series" // 包路径要从自己的gopath开始写起"testing"
)func TestPackage(t *testing.T) {t.Log(series.GetFibonacci(10))
}

init方法

image-20230428185555174

func init() {fmt.Println("init1")
}func init() {fmt.Println("init2")
}func GetFibonacci(n int) []int {ret := []int{1, 1}for i := 2; i < n; i++ {ret = append(ret, ret[i-1]+ret[i-2])}return ret
}

image-20230428185810992

如何使用远程的package

image-20230428192434197

ConcurrentMap for GO

https://github.com/easierway/concurrent_map

使用 go get 命令导入

go get -u github.com/easierway/concurrent_map

image-20230428192052651

package remoteimport (cm "github.com/easierway/concurrent_map" // 导入远程包"testing"
)func TestConcurrentMap(t *testing.T) {m := cm.CreateConcurrentMap(99)m.Set(cm.StrKey("key"), 10)t.Log(m.Get(cm.StrKey("key")))
}

image-20230428192409198

依赖管理

Go未解决的依赖问题

image-20230429184940847

vendor路径

image-20230429185019589

常用的依赖管理工具

  • godep:https://github.com/tools/godep
  • glide:https://github.com/Masterminds/glide
  • dep:https://github.com/golang/dep

安装glide

  • mac环境,使用 brew 安装 glide

    brew install glide
    

    安装成功
    image-20230430132905106

  • 初始化 glide

    glide init
    

    image-20230505090852387

    image-20230505092905006

    image-20230505093034649

    image-20230505093114018

    image-20230505093146424

    image-20230505093229266

    image-20230505093327934

  • glide init 执行完毕后,生成了一个 yaml 文件,并把依赖的包和版本号定义在了里面

    image-20230505093453057

  • 在之前的目录下执行glide install

    image-20230505094627270

    然后就会在我们的指定的文件下面生成一个 vender 目录和 glide.lock 文件。

    image-20230505094650525

  • 到此为止,Go 就能 搜索到 vender 目录下面的 package 了,我们就通过 vender 来指定了包的路径和版本号,即实现了在同一环境下使用同一个包的不同版本依赖了。

笔记整理自极客时间视频教程:Go语言从入门到实战

相关文章:

【Go语言从入门到实战】面向对象编程篇

面向对象编程 Go语言的面向对象编程和其他语言有非常大的差别。 Go 是一种面向对象的语言吗&#xff1f; 是和不是。虽然 Go 有类型和方法&#xff0c;并允许面向对象的编程风格&#xff0c;但没有类型层次结构&#xff08;继承&#xff09;。Go 中的“接口”概念提供了一种不…...

代码随想录算法训练营第四十五天 | 力扣 70. 爬楼梯(进阶), 322. 零钱兑换, 279.完全平方数

70. 爬楼梯&#xff08;进阶&#xff09; 题目 70. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 改为&#xff1a;一步一个台阶&#xff0c;两个台阶&#xff0c;三个台阶&#xff…...

dvwa靶场通关(三)

第三关&#xff1a;CSRF&#xff08;跨站请求伪造&#xff09; csrf跨站请求伪造&#xff1a;是一种对网站的恶意利用。尽管听起来像跨站脚本&#xff0c;但它与xss非常不同&#xff0c;xss利用站点内受信任用户&#xff0c;而csrf则通过伪造来自受信任用户的请求来利用受信任…...

【计算机图形学】理论考核回顾

写在前面&#xff1a; 1&#xff1a;题型主要是单选题多选题判断题计算题&#xff0c;题目量居多&#xff0c;一定要合理安排时间。 2&#xff1a;小题由于太琐碎了&#xff0c;遂不回顾&#xff0c;大致都是课件上做过的小题&#xff0c;嗯。 3&#xff1a;后续有时间更新期…...

一文了解国内外电子后视镜(CMS)现行法规标准

摘要&#xff1a; 本文小编分享一篇整合了国内外对CMS的安装及功能性做出要求的相关标准与法规。感兴趣的朋友可以专门去搜索学习。 前言&#xff1a;随着GB15084-2022的即将正式实施&#xff0c;以摄像头屏幕组合取代传统光学后视镜的新一代电子后视镜CMS相关车型将被允许上路…...

LabVIEWCompactRIO 开发指南36 确定“Clock Ticks”或模拟时间

LabVIEWCompactRIO 开发指南36 确定“Clock Ticks”或模拟时间 桌面执行节点可以控制模拟时间&#xff0c;因此开发人员可以使用模拟I/O在开发计算机上执行期间更改关键点的激励。要成功使用此功能&#xff0c;需要测量FPGA VI完成所需的时间&#xff0c;或者需要以直观地知道…...

ESP32 :项目的创建及项目架构解析

一、项目的创建 方式一&#xff1a;基于IDF示例创建 在ESP&#xff0d;IDF中有example示例库&#xff0c;以其中的一个示例为模板创建项目。 1、打开示例库 查看 - 命令面板&#xff08;也可以按住CtrlShiftP 或 F&#xff11;&#xff09; 输入 show examples projects 2…...

TI EDI 项目数据库方案开源介绍

TI EDI 工作流简介 TI EDI到SQL Server示例流具有预配置的端口&#xff0c;用于从TI的EDI集成规范转换以下交易集&#xff1a; 850 采购订单,企业 -> TI855 采购订单确认,TI -> 企业860 采购订单变更,企业 -> TI865 采购订单变更确认,TI -> 企业856 发货通知,TI …...

报表控件FastReport使用指南——使用NuGet包创建PDF文档

FastReport 是功能齐全的报表控件&#xff0c;可以帮助开发者可以快速并高效地为.NET&#xff0c;VCL&#xff0c;COM&#xff0c;ActiveX应用程序添加报表支持&#xff0c;由于其独特的编程原则&#xff0c;现在已经成为了Delphi平台最优秀的报表控件&#xff0c;支持将编程开…...

策略模式-类型统计

文章目录 前言一、策略模式是什么&#xff1f;二、策略模式应用场景三、策略模式优点四、策略模式缺点五、场景案例&#xff1a;类型统计1.项目结构2.UML图解3.代码实现3.1 指标枚举3.2 请求体3.3 响应体3.4.分析统计指标策略3.5.接口3.6.扩展接口3.7.接口实现3.8.控制层 六、P…...

android 12.0app应用安装白名单

1.概述 在12.0定制化开发中,客户需求要实现应用安装白名单功能,在白名单之中的应用可以安装,其他的app不准安装,实现一个 控制app安装的功能,这需要从app安装流程入手就可以实现功能 PMS就是负责管理app安装的,功能就添加在这里就可以了, 2.app应用安装白名单核心代码 …...

android 12.0Launcher3禁止拖动图标到Hotseat

1.概述 在12.0系统Launcher3进行定制化开发中,对于hotseat的开发中,由功能需求要求禁止拖动图标到Hotseat的功能,而拖拽也是在workspace.java中处理的 接下来就从workspace.java 开始找解决的办法 2.Launcher3禁止拖动图标到Hotseat相关代码分析 packages/apps/Launcher3…...

Java阶段三Day03

Java阶段三Day03 文章目录 Java阶段三Day03JavaScript语言JavaScript概述什么是JavaScript语言特点一门编程语言包括哪些内容 语言基础变量JavaScript常用数据类型运算符各种语句如何在HTML页面中引入JavaScript代码方法和页面相关的方法 BOM和DOM什么是BOMwindow对象location对…...

【STM32G431RBTx】备战蓝桥杯嵌入式→决赛试题→第七届

文章目录 前言一、题目二、模块初始化三、代码实现interrupt.h:interrupt.h:main.h:main.h: 四、完成效果五、总结 前言 无 一、题目 二、模块初始化 1.LCD这里不用配置&#xff0c;直接使用提供的资源包就行 2.ADC:开启ADCsingle-ended 3.LED:开启PC8-15,PD2输出模式就行了…...

Java日期时间调整的几种方式

一、Calendar类 我们现在已经能够格式化并创建一个日期对象了&#xff0c;但是我们如何才能设置和获取日期数据的特定部分呢&#xff0c;比如说小时&#xff0c;日&#xff0c;或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类。 Calendar类的…...

如何在Windows 11更新后解决C盘已满的问题?

Windows 11比Windows 10需要占用C盘更多的空间&#xff0c;在升级到Windows 11后&#xff0c;如果升级后出现问题&#xff0c;安装程序可以帮你退回到Windows 10。无论怎样&#xff0c;在升级到Windows 11后&#xff0c;系统会自动制作以前的数据的副本&#xff0c;这会占用大量…...

DJ6-1/2/3 文件系统

目录 6.1 文件系统概述 6.1.1 文件、记录和数据项 6.1.2 文件类型 6.1.3 文件系统模型 6.1.4 对文件的操作 6.2 文件的逻辑结构 6.2.1 文件逻辑结构的类型 6.2.2 顺序文件&#xff08;Sequential File&#xff09; 6.2.4 索引文件&#xff08;Index File&…...

华为OD机试真题 Java 实现【打印文件】【2023Q1 100分】

一、题目描述 有 5 台打印机打印文件,每台打印机有自己的待打印队列。 因为打印的文件内容有轻重缓急之分,所以队列中的文件有1~10不同的优先级,其中数字越大优先级越高。 打印机会从自己的待打印队列中选择优先级最高的文件来打印。 如果存在两个优先级一样的文件,则选…...

基于LSB实现文本、图片、压缩包的隐藏

关于LSB的相关介绍&#xff1a; LSB全称为 Least Significant Bit&#xff08;最低有效位&#xff09;&#xff0c;是一种基于图片最低有效位修改储存信息的隐写方法&#xff0c;在CTF杂项中经常会遇到&#xff0c;LSB属于空域算法中的一种&#xff0c;是将信息嵌入到图像点中…...

(万字长文)Linux——IO之重定向+缓冲区 +重定向 +缓冲区原理实现 +带重定向的简易版shell+标准输出标准错误

索引 文件描述符分配规则重定向 缓冲区1.什么是缓冲区2.缓冲区在哪里 重定向源码模拟实现缓冲区原理带重定向的简易版Xshell标准输入和标准错误 文件描述符分配规则 文件描述符的分配规则 从头遍历数组fd_array[],找到一个最小的&#xff0c;没有被使用的下标&#xff0c;分配…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...