Go语言的100个错误使用场景(11-20)|项目组织和数据类型

news2024/9/23 11:14:55

前言

大家好,这里是白泽。 《Go语言的100个错误以及如何避免》 是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“Go: Simple to learn but hard to master”,整本书通过分析100个错误使用 Go 语言的场景,带你深入理解 Go 语言。

我的愿景是以这套文章,在保持权威性的基础上,脱离对原文的依赖,对这100个场景进行篇幅合适的中文讲解。所涉内容较多,总计约 8w 字,这是该系列的第二篇文章,对应书中第11-20个错误场景。

🌟 当然,如果您是一位 Go 学习的新手,您可以在我开源的学习仓库中,找到针对 《Go 程序设计语言》 英文书籍的配套笔记,期待您的 star。

公众号【白泽talk】,聊天交流群:622383022,原书电子版可以加群获取。

前文链接:

《Go语言的100个错误使用场景(1-10)|代码和项目组织》

2. Code and project organization

🌟 章节概述:

  • 主流的代码组织方式
  • 高效的抽象:接口和范型
  • 构建项目的最佳实践

2.11 没有使用函数式选项模式(#11)

当设计一个 API 的时候,如何处理可选的配置项输入项是一个问题。下面先展示一些不好的例子。假设场景是创建一个 HTTP Server 服务,需要输入 IP 地址,端口等信息,但同时需要提供默认值,当不传入的时候也可以工作。

反例1(直接包含所有,配置参数):

func NewServer(addr string, port int) (*http.Server, error) {
    // ...
}

直接在参数列表罗列各种参数,然后在内部依次处理,但是使用时必须传入所有参数。

反例2(Config 存放可选参数):

package httplib
​
type Config struct {
    // 使用引用类型则未传入int则是nil,否则会和0值混淆
    Port *int
}func NewServer(addr string, cfg Confg) {
}
--------------------------------------------------
func main() {
    port := 0
    config := httplib.Config{
        Port: &port,
    }
    httplib.NewServer("localhost", config)
}

这种方式允许用户将可选的配置参数通过 Config 存放,然后在 NewServer 方法的内部读取 Config 结构的字段去初始化,但是有两个问题:

  1. 随着可选参数的增多,NewServer 内的初始化逻辑将无限扩大。
  2. 如果用户 Config 整个都选择默认参数,则必须传入一个空的 Config{},使得用户需要对 Config 的用法提高理解成本。
httplib.NewServer("localhost", httplib.Config{})

反例3(建造者模式):

package httplib
​
type Config struct {
    Port int
}type ConfigBuilder sruct {
    port *int
}func (b *ConfigBuilder) Port(port int) *ConfigBuilder {
    b.port = &port
    return b
}func (b *ConfigBuilder) Build() (Config, error) {
    cfg := Config{}
    
    if b.port == nil {
        cfg.Port = defaultHTTPPort
    } else {
        if *b.port == 0 {
            cfg.Port = randomPort()
        } else if *b.port < 0 {
            return Config{}, errors.New("port should be positive")
        } else {
            cfg.Port = *b.port
        }
    }
    reutrn cfg, nil
}func NewServer(addr string, config Config) (http.Server, error) {
    // ...
}
----------------------------------------------
// 用法
func main() {
    builder := httplib.ConfigBuilder{}
    builder.Port(8080)
    cfg, err := builder.Build()
    if err != nil {
        // ...
    }
    server, err := httplib.NewServer("localhost", cfg)
    if err != nil {
        // ...
    }
}

这种写法下仍然有两个问题:

  1. 当可选参数希望使用默认值的时候,需要传递 nil(虽然已经比示例2进步了)。
server, err := httplib.NewServer("localhost", nil)
  1. 使用建造者模式链式创建过程中,只允许返回一个参数。一旦过程中发生错误,为了确保链式调用继续,即使需要错误处理也只能内聚的具体一个个方法内部,并不能将 err 传递出来。从而只能在 Builder 方法内验证可能发生的错误,使得 err 的处理成本大大提高。
cfg, err := builder.Foo("foo").Bar("bar").Build()

🌟 推荐方法(函数式配置选项模式),核心思想如下:

  1. 用一个不对外倒出的结构存放配置:options
  2. 每一个 option 是一个函数返回同一个结构:Option

代码展示:

package httplib
​
type options struct {
    port *int
}type Option func(options *options) errorfunc WithPort(port int) Option {
    return func(options *options) error {
        if port < 0 {
            return errors.New("port should be positive")
        }
        options.port = &port
        return nil
    }
}

WithPort 接受一个 port 参数代表端口号,返回一个给 options 设置端口号的函数。这种形式的函数本质是一个匿名的闭包,持有外部的 options 配置集合。

func NewServer(addr string, opts ...Option) (*http.Server, error) {
    // 初始化配置集合 options
    var options options
    for _, opt := range opts {
        err := opt(&options)
        if err != nil {
            return nil, err
        }
    }
    // 针对配置字段内容,添加验证需要的逻辑
    var port int
    if options.port == nil {
        port = defaultHTTPPort
    } else {
        if *options.port == 0 {
            port = randomPort()
        } else {
            port = *options.port
        }
    }
}
---------------------------------------------
// 用法
int main() {
    server, err := httplib.NewServer("localhost", httplib.WithPort(8080), httplib.WithTimeout(time.second))
}

在 NewServer 内通过循环将 Option 的配置通过函数调用应用到 options 集合中,然后在编写针对 options 配置字段的验证逻辑,因为所有可选的 Option 都是外部传入的,NewServer 内需要为其进行二次校验。

🌟 这种方式也是 Go 的地道用法,在很多开源项目如 gRPC 中都大量使用。

2.12 项目缺乏组织(#12)

Go 语言是一个自由的语言,并不强制要求你选择某一种组织项目的模板,但是你需要为此行动。一个常见的模板展示:

/cmd # 主要的源代码位置,foo应用的入口文件位于 /cmd/foo/main.go
/internal # 内部使用的代码,不希望被导出使用
/pkg # 公共的代码,希望被导出
/test # 额外的外部测试代码和测试数据,Go的单测应该和源代码在同一个package内,但是集成测试等代码需要在这个目录
/configs # 配置文件
/docs # 设计文档和用户手册
/examples # 项目的使用示例代码
/api # API 文件,例如 Swagger,PB等
/web # Web应用拥有的资源文件,如静态文件等
/build # 打包和持续集成文件
/scripts # 各种脚本
/vendor # 当前项目的依赖文件

没有 src/ 目录因为它太泛用了,从而将其细分成了上述的各个目录。但这只是一个参考。

package 的组织方式:

/net
    /http
        client.go
        ...
    /smtp
        auth.go
        ...
    addrselect.go
    ...

这是 Go 标准库中 net 包的组织结构,虽然 /http 位于 /net 之后,但是 net/http 这个包只能访问 net 包中被导出的内容(大写开头),使用子目录这种组织结构是为了使相关功能的包聚集在一起管理。

🌟 选择根据上下文进行组织项目还是分层组织项目都可以,只要你可以确保项目清晰:

  • 按上下文:将代码分成 customer,constract 等等模块。
  • 六边形架构(DDD):按功能进行分层。

🌟 最佳实践:

  1. 避免为时过早的 package 创建,允许演化,而不是一直遵守一开始的强制规划。
  2. 避免产生大量细粒度的 package,只包含个别文件。当然过大也是一个问题。
  3. 包的命名需要根据它提供的功能出发,用一个小写单词表示。
  4. 最小化包需要导出的内容,减少耦合,不确定就先不导出。
  5. 代码库的编码风格一致性。

2.13 创建公共设施包(#13)

一种常见的不好的实践:创建共享的包如 utils,common & base。

代码展示:

package util
​
func NewStringSet(...string) map[string]struct{} {
    // ...
}func SortStringSet(map[string]struct{}) []string {
    // ...
}
-----------------------------------------
// 用法
func main() {
    set := util.NewStringSet("a", "b", "c")
    fmt.Println(util.SortStringSet(set))
}

工具包内的两个函数实现了创建 string 集合和针对 key 进行排序输出的函数,但是此处包命名为 util 则没有任何意义,完全可以替换成 common,shared…

代替方案:

package stringset
​
type Set map[string]struct{}
func New(...string) Set {...}
func (s Set) Sort() []string {...}
-----------------------------------------
// 用法
set := stringset.New("a", "b", "c")
fmt.Println(set.Sort())

用 stringset 代替 util 这个包的名称,使其更具表达性。同时将方法的前缀去除,用一个结构 Set 去接收 Sort 方法,将所有逻辑内聚在一个用途明确的 stringset 包中。调用侧使用也收到了明确约束,更加方便。

2.14 忽略包名的冲突(#14)

示例代码:

package redis
​
type Client struct {...}func NewClient() *Client {...}func (c *Client) Get(key string) (string, error) {...}
----------------------------------------------------
// 冲突的场景
func main() {
    redis := redis.NewClient()
    v, err := redis.Get("Foo")
}

在这种场景下,虽然 redis 变量现在是可以工作的,但它本质是变量,会被修改。但是 redis 包将无法再在代码中访问。

  • 直观的解决方案:
func main() {
    redisClient := redis.NewClient()
    v, err := redisClient.Get("Foo")
}

修改变量名称,避免冲突。

  • 更推荐的解决方案:
import redisapi "mylib/redis"func main() {
    redis := redis.NewClient()
    v, err := redis.Get("Foo")
}

通过给 import 的 redis 包起别名的方式,避免与变量名的冲突,这是一种更推荐的做法。

📒 Tips:变量名的创建要避免与内置关键字或者函数同名

2.15 代码文档缺失(#15)

文档对于项目的开发者和使用者都十分重要,这里给出几个法则:

  1. 为每一个导出的对象都配备文档(通过注释)
// Customer is a customer representation
type Customer struct// ID returns the customer identifier
func (c Customer) ID() string {...}
  1. 注释需要是一个完整的句子,以.结尾。并且针对于描述对象的功能,而不是如何实现。确保提供足够的描述信息,使得用户无需阅读代码即可使用。
  2. 针对废弃的 API 使用注释:
// ComputePath returns the fastset path between two points
// Deprecated: This function uses a deprecated way to compute
// the fastest Path. Use ComputeFastestPath instead. func ComputePath () {}
func ComputePath() {}
  1. 为 package 添加文档:
// Package math provides basic constants and mathmatical functions
//
// This package ...
package math

第一行需要简洁,因为在文档中会展现:

image-20240130150051245

2.16 不使用 code-linter(#16)

Linter 是一个自动化代码分析工具,可以帮助我们分析代码,找到潜在的错误。所以 Code Linter 也是持续集成中必不可少的一环。

go vet: Go 内置的静态代码检查工具。

go vet ./...

Linters: 可以是外部工具,如 Golint、GolangCI-Lint、Staticcheck 等,它们通过外部安装,并提供更多的规则和功能。

通常情况下,建议同时使用 go vet 和 linters 以确保代码的质量和一致性。

示例代码:

func main() {
    unusedVariable := 42 // 未使用的变量
}

运行 go vet ./...

baize@baizedeMacBook-Air mistakes % go vet ./...
# mistake
vet: ./main.go:4:2: unusedVariable declared and not used

3. Data types

🌟 章节概述:

  • 基本类型涉及的常见错误
  • 掌握 slice 和 map 的基本概念,避免使用时产生 bug
  • 值的比较

3.1 八进制产生的混乱局面(#17)

在 Go 当中,以0字面量开头的数值表示8进制,因此:

sum := 100 + 010 // 结果为108

但是8进制也有其发挥作用的场景,如赋予文件对应 Linux 系统的权限:

file, err := os.OpenFile("foo", os.O_RDONLY, 0644)
// 0644可以替换成0o644或者0O644

Go 语言中的不同进制表示:

  • 二进制:使用 0b 或者 0B 为前缀
  • 十六进制:使用0x 或者 0X 为前缀
  • 虚数:使用 i 为后缀

Go 语言中支持用下划线作为数值的分隔符,提高可读性:

1_000_000_000 // 一百万
0b00_00_01 // 二进制也是可以的

3.2 忽略整型溢出(#18)

常见的整型类型:

image-20240131112800057

整型溢出场景:

var counter int32 = math.MaxInt32
counter++
fmt.Printf("counter=%d\n", counter)

整个程序编译和运行不会报错,但是:

counter = -2147483648 // counter++ 导致int32溢出

计算机存储有符号整数用二进制表示:

image-20240131114037048

针对有符号整数,第一位为符号位,0表示正数,1表示负数,全0表示0(约定)。

比如上述 int32 有符号整型最大值+1(左侧图片),得到右侧图片(是一个负数)。

计算时使用补码:正数的补码等于原码,负数的补码等于原码除去符号位外,所有位数取反,最后整体+1。

举例 int8 计算 8 + (-8) = 0 用补码的表示:

00001000 + 10001000 // 原码
00001000 + 11111000 = 100000000 // 补码,左侧0溢出,因为只有8位,得到0

如果希望手动检测整型溢出,这是一些模板代码:

func Inc32(counter int32) int32 {
    if counter == math.MaxInt32 {
        panic("int32 overflow")
    }
    return counter+1
}func addInt(a, b int) int {
    if a > math.MaxInt-b {
        panic("int overflow")
    }
    return a + b
}func MultiplInt(a, b int) int {
    if a == 0 || b == 0 {
        return 0
    }
    
    result := a * b
    if a == 1 || b == 1 {
        return result
    }
    if a == math.MinInt || b == math.MinInt {
        panic("integer overflow")
    }
    if result/b != a {
        panic("integer overflow")
    }
    return result
}

3.3 不理解浮点数(#19)

浮点数在计算机中的存储通常遵循 IEEE 754 标准,该标准定义了单精度和双精度浮点数的表示方式。

  1. 单精度浮点数(32位):

    • 符号位:1位
    • 指数位:8位
    • 尾数位:23位

    单精度浮点数的存储结构如下:

    SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
    
    • S:符号位,表示正负。
    • E:指数位,以偏移值(127)存储,范围为-126到+127。
    • M:尾数位,23位精度。

    具体数值表示为:(-1)^S * 1.M * 2^(E-127)

  2. 双精度浮点数(64位):

    • 符号位:1位
    • 指数位:11位
    • 尾数位:52位

    双精度浮点数的存储结构如下:

    SEEEEEEE EEEEMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM
    
    • S:符号位,表示正负。
    • E:指数位,以偏移值(1023)存储,范围为-1022到+1023。
    • M:尾数位,52位精度。

    具体数值表示为:(-1)^S * 1.M * 2^(E-1023)

需要注意的是,由于二进制浮点数的特性,某些十进制小数可能无法精确表示,会有舍入误差。在编程中,特别是涉及金融等需要高精度的领域,需要小心处理浮点数的精度问题。

🌟 举例 1.0001 在单精度和双精度下的存储:

  1. 单精度(32位):

    0 01111111 00010010000111101011100
    
    • 符号位:0(正数)
    • 指数位:01111111(127的二进制表示,表示偏移值为0)
    • 尾数位:00010010000111101011100(23位精度)

    具体数值表示为:(-1)^0 * 1.00010010000111101011100 * 2^(0-127) ≈ 1.0001

  2. 双精度(64位):

    0 01111111111 0001001000011110101110000101000111101011100010100011111
    
    • 符号位:0(正数)
    • 指数位:01111111111(1023的二进制表示,表示偏移值为0)
    • 尾数位:0001001000011110101110000101000111101011100010100011111(52位精度)

    具体数值表示为:(-1)^0 * 1.0001001000011110101110000101000111101011100010100011111 * 2^(0-1023) ≈ 1.0001

🌟 浮点数的使用准则:

  1. 比较大小需要在合理精度范围内
  2. 运算时注意相似精度优先运算,减少精度波动

3.4 不理解切片长度和容量(#20)

Go 的切片本质上是一个结构体,包含一个指向数组的指针,以及两个变量记录长度和容量。

🌟 切片创建于扩容场景分析:

s := make([]int, 3, 6) // 创建长度为3容量为6的int类型的切片
s[1] = 1 // 赋值s[1]=1

image-20240131123853071

访问切片大于长度范围的位置将触发 panic:

panic: runtime error: index out of range [4] with length 3

在 len 小于 cap 的时候,可以直接向切片添加元素:

s = append(s, 2)

image-20240131124253144

此时切片的 len 自动增加到4,因为容量足够,不会触发切片的扩容,但如果添加的元素数量超过 cap 的限制,则会触发切片的扩容:

s = append(s, 3, 4, 5)
fmt.Println(s) // 得到:[0 1 0 2 3 4 5]

image-20240131124546956

当添加5的时候,原底层数组的容量达到上限6,触发2倍扩容(创建新数组),拷贝原切片内容到新数组,并追加5。

大致的 Go 切片扩容规则:容量1024以下双倍扩容,以上扩容25%

原底层数组因为丢失了引用,如果在堆内存内,会被后续的 Go GC 回收(GC 相关场景将在全书后期讲解)。

🌟 切片截取场景分析:

s1 := make([]int, 3, 6) // 长度为3,容量为6
s2 := s1[1:3] // 从s1的索引1-3截取,左闭右开,此时s2的长度为2,容量是5

image-20240131125818202

此时如果更新 s1[1] 或者 s2[0] 为1,则由于共享底层数组的原因,导致另一方可以读取到变更内容。

如果追加内容,则底层共享的数组追加2。此时 s2 的 len 变为3,但是 s1 的 len 依旧为3。

image-20240131130436982

并且此时打印两个切片得到的内容如下:

s1 = [0 1 0], s2 = [1 0 2]

如果继续向 s2 追加元素直到 s2 的长度超过容量,则会触发扩容,创建新底层数组(二倍):

s2 = append(s2, 3)
s2 = append(s2, 4)
s2 = append(s2, 5)

image-20240131130649349

小结

已完成全书学习进度20/100,再接再厉。

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

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

相关文章

Channel事件管理类实现(模块三)

目录 类功能 类定义 类实现 类功能 因为涉及到后续一些实现&#xff0c;因此后续可能会进行修改 类定义 class Channel { private:uint32_t _events; // 当前需要监控的事件uint32_t _revents; // 当前连接触发的事件using EventCallback std::function<void()>;E…

自学Java的第58,59天

网络通信 网络通信三要素&#xff1a;ip地址&#xff0c;端口号&#xff0c;协议 ip地址 常用方法 写法 端口号 协议 UDP通信 快速入门 写法&#xff08;客户端&#xff09; &#xff08;服务端&#xff09; UDP通信 多发多收 TCP通信 写法&#xff08;客户端&#xff09; …

Node.js版本管理工具之_Volta

Node.js包管理工具之_Volta 文章目录 Node.js包管理工具之_Volta1. 官网1. 官网介绍2. 特点1. 快( Fast)2. 可靠(Reliable)3. 普遍( Universal) 2. 下载与安装1. 下载2. 安装3. 查看 3. 使用1. 查看已安装的工具包2. 安装指定的node版本3.切换项目中使用的版本 1. 官网 1. 官网…

网络协议梳理

1 引言 在计算机网络中要做到有条不紊地交换数据&#xff0c;就必须遵守一些事先约定好的规则。这些规则明确规定了所交换的数据的格式以及有关的同步问题。这里所说的同步不是狭义的&#xff08;即同频或同频同相&#xff09;而是广义的&#xff0c;即在一定的条件下应当发生什…

大数据本地环境搭建03-Spark搭建

需要提前部署好 Zookeeper/Hadoop/Hive 环境 1 Local模式 1.1 上传压缩包 下载链接 链接&#xff1a;https://pan.baidu.com/s/1rLq39ddxh7np7JKiuRAhDA?pwde20h 提取码&#xff1a;e20h 将spark-3.1.2-bin-hadoop3.2.tar.gz压缩包到node1下的/export/server目录 1.2 解压压…

镜舟科技客户成功团队负责人孟庆欢:湖仓一体将成为数据架构的新范式

大数据产业创新服务媒体 ——聚焦数据 改变商业 随着数字化的概念逐步深入不同领域企业的运营中&#xff0c;业务形态和数字化路径也越来越丰富。这也为企业数据处理、储存的方式提出了更多要求。对于企业&#xff0c;尤其是数据驱动型企业来说&#xff0c;需要强大的解决方案…

LNMP.

一.mysl配置 1.安装mysql yum install mysql-server -y 2.进入mysql配置文件目录 cd /etc/my.cnf.d3.编辑mysql配置文件 vim mysql-server.cnf 在[mysqld]中添加: character-set-serverutf84.启动mysql服务 systemctl start mysqld5.登入mysql mysql 6.创建数据库 cre…

153基于matlab的滚动轴承故障诊断

基于matlab的滚动轴承故障诊断&#xff0c;基于小波包分解&#xff0c;得到数据峭度值&#xff0c;以正常与故障数据峭度差值进行最大尺度重构&#xff0c;对重构信号进行包络谱分析。程序已调通&#xff0c;可直接运行。 153matlab 信号重构 包络谱分析 故障诊断 (xiaohongshu…

Macbook 安装金铲铲之战等 IOS 游戏

前言 Macbook 现在可以玩一下 IOS 系统上的游戏啦&#xff0c;以笔者的 M1 Pro 芯片为例 步骤 一、安装 PlayCover 推荐 Sonama 安装 Nightly 版本 官网地址&#xff1a; https://playcover.io/ Nightly: https://nightly.link/playcover/playcover/workflows/2.nightly_re…

基础小白快速入门python------Python程序设计结构,循环

循环在计算机中&#xff0c;是一个非常重要的概念&#xff0c;是某一块儿代码的不断重复运行&#xff0c;是一种逻辑思维 在编程中的体现&#xff0c;运用数学思维加代码结合加数据&#xff0c;就构成了一个循环。 在Python中&#xff0c;循环主要分为三大类 for循环 while循…

二维图像生成 3D 场景:nerfstudio 帮你简化流程 | 开源日报 No.164

nerfstudio-project/nerfstudio Stars: 7.7k License: Apache-2.0 nerfstudio 是一个友好的 NeRFs 协作工作室。 该项目旨在简化创建、训练和测试 NeRFs 的端到端流程&#xff0c;支持更模块化的 NeRFs 实现&#xff0c;并提供了简单的 API。 其主要功能和优势包括&#xff1…

ABAP 笔记--内表结构不一致,无法更新数据库MODIFY和UPDATE

目录 ABAP 笔记内表结构不一致&#xff0c;无法更新数据库MODIFY和UPDATE ABAP 笔记 内表结构不一致&#xff0c;无法更新数据库 MODIFY和UPDATE 如果是使用MODIFY或者UPDATE

【DDD】学习笔记-什么是模型

从领域驱动的战略设计进入战术设计&#xff0c;简单说来&#xff0c;就是跨过系统视角的限界上下文边界进入它的内部&#xff0c;从分层架构的逻辑分层进入到每一层的内部。在思考内部的设计细节时&#xff0c;首先需要思考的问题就是&#xff1a;什么是模型&#xff08;Model&…

Android 13.0 原生SystemUI下拉通知栏每条通知默认展开

1.前言 在13.0的系统rom原生开发中,在在对SystemUI下拉通知栏做定制的时候,在下拉状态栏的时候,通知栏中最后一条通知默认是收缩的 点击按钮 就会展开 原生系统systemui就是如此,为了更美观 所以要求最后一条通知也默认展开,显得更美观 最终效果图: 2.原生SystemUI下拉通…

Git使用命令大全

命令大全参考阮一峰的博客&#xff0c;根据自己的使用习惯作了调整。 Git常用命令 其他常用的命令 配置Git # 显示当前的Git配置 $ git config --list# 编辑Git配置文件 $ git config -e [--global]# 设置提交代码时的用户信息 $ git config [--global] user.name "[nam…

Multiuser Communication Aided by Movable Antenna

文章目录 II. SYSTEM MODEL AND PROBLEM FORMULATIONA. 通道模型B. Problem Formulation III. PROPOSED SOLUTION II. SYSTEM MODEL AND PROBLEM FORMULATION 如图1所示&#xff0c;BS配置了尺寸为 N N 1 N 2 NN_{1} \times N_{2} NN1​N2​ 的均匀平面阵列&#xff08;uni…

第二十五天| 216.组合总和III、17.电话号码的字母组合

Leetcode 216.组合总和III 题目链接&#xff1a;216 组合总和III 题干&#xff1a;找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#…

【Qt5小项目】接金币小游戏

代码量在250行左右&#xff0c; 需要源码的可以私信我。

蓝桥杯嵌入式第六届真题(完成)STM32G431

蓝桥杯嵌入式第六届真题&#xff08;完成&#xff09;STM32G431 题目部分 相关文件 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program b…

计算机科学导论(0)冯诺依曼体系结构

文章目录 定义主要特点缺陷定义 冯诺依曼体系结构(Von Neumann architecture),也称为普林斯顿体系结构(Princeton architecture),是一种计算机架构理论,由匈牙利数学家和物理学家约翰冯诺依曼(John von Neumann)在1945年提出。这一体系结构是现代计算机设计的基础,其…