Go包介绍与初始化:搞清Go程序的执行次序

Go包介绍与初始化:搞清Go程序的执行次序

文章目录

  • Go包介绍与初始化:搞清Go程序的执行次序
    • 一、main.main 函数:Go 应用的入口函数
      • 1.1 main.main 函数
      • 1.2 main.main 函数特点
    • 二、包介绍
      • 2.1 包介绍与声明
      • 2.2 非 main包的 main 函数
      • 2.3 包的命名规则
    • 三、包的导入
      • 3.1 包的导入介绍
      • 3.2 导入多个包
      • 3.2 包的别名
    • 四、神器的下划线
      • 4.1 下划线的作用
      • 4.2 下划线在代码中
      • 4.3 下划线在import中
    • 五、init 函数:Go 包的初始化函数
      • 5.1 init 函数 介绍
      • 5.2 init 函数 特点
    • 六、Go 包的初始化次序
      • 6.1 包的初始化次序探究
      • 6.2 包的初始化原则
    • 七、init 函数的常用用途
      • 7.1 用途一:重置包级变量值
      • 7.2 用途二:实现对包级变量的复杂初始化
      • 7.3 用途三:在 init 函数中实现“注册模式”

一、main.main 函数:Go 应用的入口函数

1.1 main.main 函数

在Go语言中,main函数是任何Go应用的入口函数–用户层入口。当你运行一个Go程序时,操作系统会首先调用main函数,然后程序开始执行。main 函数的函数原型是这样的:

package mainfunc main() {// 用户层执行逻辑... ...
}

你的程序的执行会从main函数开始,会在这个函数内按照它的调用顺序展开。

1.2 main.main 函数特点

main.main函数是Go应用程序的入口函数,它具有一些特点和规定,使得Go程序的执行流程有一定的规范性。以下是关于main.main函数的特点:

  1. 唯一入口点: 在一个Go应用程序中,只能有一个main.main函数。这是整个程序的唯一入口点,程序的执行将从这里开始。如果存在多个main函数,编译时会报错。
  2. 不接受参数: main.main函数不接受任何参数,它没有输入参数,也没有返回值。这是Go语言的规定,而程序的命令行参数通常通过os.Args等方式获取。

二、包介绍

2.1 包介绍与声明

在Go中,包(Package)是组织和管理代码的基本单元。包包括一组相关的函数、类型和变量,它们可以被导入到其他Go文件中以便重复使用。Go标准库以及第三方库都是以包的形式提供的。

每个Go文件都属于一个包,你可以使用package关键字来指定声明一个文件属于哪个包。例如:

package main

2.2 非 main包的 main 函数

除了 main 包外,其他包也可以拥有自己的名为 main 的函数或方法。但按照 Go 的可见性规则(小写字母开头的标识符为非导出标识符),**非 main 包中自定义的 main 函数仅限于包内使用,**就像下面代码这样,这是一段在非 main 包中定义 main 函数的代码片段:

package pkg1import "fmt"func Main() {main()
}func main() {fmt.Println("main func for pkg1")
}  

你可以看到,这里 main 函数就主要是用来在包 pkg1 内部使用的,它是没法在包外使用的。

2.3 包的命名规则

  • 在Go语言中,包的名称通常使用小写字母,具有简洁的、描述性的名称。这有助于提高代码的可读性和可维护性。标准库中的包通常具有非常清晰的包名,例如fmtmathstrings等。
  • 在Go语言中,包级别的标识符(变量、函数、类型等)的可见性是由其首字母的大小写来决定的。如果一个标识符以大写字母开头,它就是可导出的(公有的),可以被其他包访问。如果以小写字母开头,它就是包内私有的,只能在包内部使用。

三、包的导入

3.1 包的导入介绍

要在Go程序中使用其他包的功能,你需要导入这些包。使用import关键字来导入包,导入语句通常放在文件的顶部。一个典型的包导入语句的格式如下:

import "包的导入路径"

其中,包的导入路径是指被导入包的唯一标识符,通常是包的名称或路径,它用于告诉Go编译器去哪里找到这个包的代码。

例如,导入标准库中的fmt包可以这样做:

import "fmt"

然后,你就可以在你的程序中使用fmt包提供的函数和类型。

3.2 导入多个包

在Go程序中,你可以一次导入多个包,只需在import语句中列出这些包的导入路径,用括号()括起来并以括号内的方式分隔包的导入路径。

示例:

import ("fmt""math""net/http"
)

这个示例中导入了fmtmathnet/http三个包。这种方式使你可以更清晰地组织你的导入语句,以便程序更易读。

注意:Go语言的编译器会自动检测哪些导入的包是真正被使用的,未使用的导入包不会引起编译错误,但通常被视为不良实践。在Go中,未使用的导入包可能会引起代码不清晰,因此应该避免导入不需要的包。

3.2 包的别名

在Go语言中,你可以使用包的别名(package alias)来为一个导入的包赋予一个不同的名称,以便在代码中引用它。包的别名通常用于以下情况:

  1. 避免包名冲突:当你导入多个包时,有可能出现包名冲突,此时你可以为一个或多个包使用别名来解决冲突。
  2. 简化包名:有时,包的导入路径可能很长,为了减少代码中的冗长,你可以为包使用一个短的别名。

使用包的别名是非常简单的,只需在导入语句中使用as关键字为包指定一个别名。以下是示例:

import fm "fmt"

在上面的示例中,fmfmt包的别名。现在,你可以在代码中使用fm来代替fmt,例如:

fm.Println("Hello, World!")

这样,你就可以使用更短的fm来调用fmt包的函数,以减少代码中的冗长。

包的别名可以根据需要自定义,但通常建议选择一个有意义的别名,以使代码更易读。使用别名时要注意避免产生混淆,要确保别名不与其他标识符(如变量名或函数名)发生冲突。

四、神器的下划线

4.1 下划线的作用

下划线 _ 在Go语言中用于以下几个不同的场景:

  1. 匿名变量: _ 可以用作匿名变量,用于忽略某个值。当你希望某个值返回但又不需要使用它时,可以将其赋值给 _
  2. 空标识符: _ 也被称为空标识符,它用于声明但不使用变量或导入包但不使用包的标识符。这是为了确保代码通过编译,但不会产生未使用变量或包的警告。

4.2 下划线在代码中

在代码中,下划线 _ 可以用作匿名变量,用于忽略某个值。这通常在函数多返回值中使用,如果你只关心其中的某些值而不需要其他返回值,可以将其赋值给 _

示例:

x, _ := someFunction() // 忽略第二个返回值

在上面的示例中,_ 用于忽略 someFunction 函数的第二个返回值。

4.3 下划线在import中

  • 当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。
  • 这个时候就可以使用 import _ 引用该包。即使用 import _ 包路径 只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。

以下是一个示例,演示如何使用 import _ 引用一个包以执行其 init() 函数:

项目结构:

src
|
+--- main.go
|
+--- hello|+--- hello.go

main.go 文件

package mainimport _ "./hello"func main() {// hello.Print() //编译报错:./main.go:6:5: undefined: hello
}

hello.go 文件

package helloimport "fmt"func init() {fmt.Println("imp-init() come here.")
}func Print() {fmt.Println("Hello!")
}

输出结果:

    imp-init() come here.

五、init 函数:Go 包的初始化函数

5.1 init 函数 介绍

init 函数是在Go包的初始化阶段自动调用的函数。它的目的是执行一些包级别的初始化工作,例如设置变量、初始化数据、连接数据库等。init 函数没有参数,也没有返回值,它的定义形式如下:

func init() {// 包初始化逻辑... ...
}

5.2 init 函数 特点

init 函数有以下特点:

  1. 自动执行: init 函数不需要手动调用,它会在程序启动时自动执行。这确保了包的初始化工作在程序开始执行之前完成。
  2. 包级别: init 函数是包级别的,因此它只能在包的内部定义。不同包中的 init 函数互不影响,它们独立执行。
  3. 多个 init 函数: 一个包可以包含多个 init 函数,它们按照定义的顺序依次执行。被导入的包的 init 函数会在导入它的包的 init 函数之前执行。
  4. 没有参数和返回值: 和前面main.main 函数一样,init 函数也是一个无参数无返回值的函数,它只用于执行初始化工作,而不与其他函数交互。
  5. 顺序执行: 由于 init 函数的执行顺序是根据包的导入顺序确定的,因此在编写代码时应该谨慎考虑包的依赖关系,以确保正确的初始化顺序。
  6. 可用于注册和初始化: init 函数通常用于执行包的初始化工作,也可用于在导入包时注册一些功能,例如数据库驱动程序的注册。

这里要特别注意的是,在 Go 程序中我们不能手工显式地调用 init,否则就会收到编译错误,就像下面这个示例,它表示的手工显式调用 init 函数的错误做法:

package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {init()
}

构建并运行上面这些示例代码之后,Go 编译器会报下面这个错误:

$go run call_init.go 
./call_init.go:10:2: undefined: init

接着,我们将代码修改如下:

package mainimport "fmt"func init() {fmt.Println("init invoked")
}func main() {fmt.Println("this is main")
}

Go 编译器运行结果如下:

init invoked
this is main

我们看到,在初始化 Go 包时,Go 会按照一定的次序,逐一、顺序地调用这个包的 init 函数。一般来说,先传递给 Go 编译器的源文件中的 init 函数,会先被执行;而同一个源文件中的多个 init 函数,会按声明顺序依次执行。所以说,在Go中,main.main 函数可能并不是第一个被执行的函数。

六、Go 包的初始化次序

6.1 包的初始化次序探究

我们从程序逻辑结构角度来看,Go 包是程序逻辑封装的基本单元,每个包都可以理解为是一个“自治”的、封装良好的、对外部暴露有限接口的基本单元。一个 Go 程序就是由一组包组成的,程序的初始化就是这些包的初始化。每个 Go 包还会有自己的依赖包、常量、变量、init 函数(其中 main 包有 main 函数)等。

在平时开发中,我们在阅读和理解代码的时候,需要知道这些元素在在程序初始化过程中的初始化顺序,这样便于我们确定在某一行代码处这些元素的当前状态。

下面,我们就通过一张流程图,来了解 Go 包的初始化次序:

WechatIMG4036

这里,我们来看看具体的初始化步骤。

首先,main 包依赖 pkg1pkg4 两个包,所以第一步,Go 会根据包导入的顺序,先去初始化 main 包的第一个依赖包 pkg1。

第二步,Go 在进行包初始化的过程中,会采用“深度优先”的原则,递归初始化各个包的依赖包。在上图里,pkg1 包依赖 pkg2 包,pkg2 包依赖 pkg3 包,pkg3 没有依赖包,于是 Go 在 pkg3 包中按照“常量 -> 变量 -> init 函数”的顺序先对 pkg3 包进行初始化;

紧接着,在 pkg3 包初始化完毕后,Go 会回到 pkg2 包并对 pkg2 包进行初始化,接下来再回到 pkg1 包并对 pkg1 包进行初始化。在调用完 pkg1 包的 init 函数后,Go 就完成了 main 包的第一个依赖包 pkg1 的初始化。

接下来,Go 会初始化 main 包的第二个依赖包 pkg4pkg4 包的初始化过程与 pkg1 包类似,也是先初始化它的依赖包 pkg5,然后再初始化自身;

然后,当 Go 初始化完 pkg4 包后也就完成了对 main 包所有依赖包的初始化,接下来初始化 main 包自身。

最后,在 main 包中,Go 同样会按照“常量 -> 变量 -> init 函数”的顺序进行初始化,执行完这些初始化工作后才正式进入程序的入口函数 main 函数。

现在,我们可以通过一段代码示例来验证一下 Go 程序启动后,Go 包的初始化次序是否是正确的,示例程序的结构如下:

prog-init-order
├── main.go
├── pkg1
│   └── pkg1.go
├── pkg2
│   └── pkg2.go
└── pkg3└── pkg3.go

这里我只列出了 main 包的代码,pkg1pkg2pkg3 可可以到代码仓库中查看。

package mainimport ("fmt"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg1"_ "gitee.com/tao-xiaoxin/study-basic-go/syntax/prog-init-order/pkg2"
)var (_  = constInitCheck()v1 = variableInit("v1")v2 = variableInit("v2")
)const (c1 = "c1"c2 = "c2"
)func constInitCheck() string {if c1 != "" {fmt.Println("main: const c1 has been initialized!")}if c2 != "" {fmt.Println("main: const c2 has been initialized!")}return ""
}func variableInit(name string) string {fmt.Printf("main: var %s has been initialized\n", name)return name
}func init() {fmt.Println("main: first init function invoked")
}func init() {fmt.Println("main: second init function invoked")
}func main() {//
}

我们可以看到,在 main 包中其实并没有使用 pkg1 和 pkg2 中的函数或方法,而是直接通过空导入的方式“触发”pkg1 包和 pkg2 包的初始化(pkg1 包和和 pkg2 包都通过空导入的方式依赖 pkg3 包的,),下面是这个程序的运行结果:

$go run main.go
pkg3: const c has been initialized
pkg3: var v has been initialized
pkg3: init func invoked
pkg1: const c has been initialized
pkg1: var v has been initialized
pkg1: init func invoked
pkg2: const c has been initialized
pkg2: var v has been initialized
pkg2: init func invoked
main: const c1 has been initialized
main: const c2 has been initialized
main: var v1 has been initialized
main: var v2 has been initialized
main: first init func invoked
main: second init func invoked

正如我们预期的那样,Go 运行时是按照“pkg3 -> pkg1 -> pkg2 -> main”的顺序,来对 Go 程序的各个包进行初始化的,而在包内,则是以“常量 -> 变量 -> init 函数”的顺序进行初始化。此外,main 包的两个 init 函数,会按照在源文件 main.go 中的出现次序进行调用。根据 Go 语言规范,**一个被多个包依赖的包仅会初始化一次,**因此这里的 pkg3 包仅会被初始化了一次。

6.2 包的初始化原则

根据以上,包的初始化按照依赖关系的顺序执行,遵循以下规则:

  1. 依赖包按照 “深度优先” 的方式进行初始化,即先初始化最底层的依赖包。
  2. 在每个包内部以“常量 -> 变量 -> init 函数”的顺序进行初始化。
  3. 包内的多个 init 函数按照它们在代码中的出现顺序依次自动调用。

七、init 函数的常用用途

Go 包初始化时,init 函数的初始化次序在变量之后,这给了开发人员在 init 函数中对包级变量进行进一步检查与操作的机会。

7.1 用途一:重置包级变量值

init 函数就好比 Go 包真正投入使用之前唯一的“质检员”,负责对包内部以及暴露到外部的包级数据(主要是包级变量)的初始状态进行检查。在 Go 标准库中,我们能发现很多 init 函数被用于检查包级变量的初始状态的例子,标准库 flag 包对 init 函数的使用就是其中的一个,这里我们简单来分析一下。

flag 包定义了一个导出的包级变量 CommandLine,如果用户没有通过 flag.NewFlagSet 创建新的代表命令行标志集合的实例,那么 CommandLine 就会作为 flag 包各种导出函数背后,默认的代表命令行标志集合的实例。

而在 flag 包初始化的时候,由于 init 函数初始化次序在包级变量之后,因此包级变量 CommandLine 会在 init 函数之前被初始化了,可以看如下代码:

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {f := &FlagSet{name:          name,errorHandling: errorHandling,}f.Usage = f.defaultUsagereturn f
}func (f *FlagSet) defaultUsage() {if f.name == "" {fmt.Fprintf(f.Output(), "Usage:\n")} else {fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)}f.PrintDefaults()
}

我们可以看到,在通过 NewFlagSet 创建 CommandLine 变量绑定的 FlagSet 类型实例时,CommandLineUsage 字段被赋值为 defaultUsage

也就是说,如果保持现状,那么使用 flag 包默认 CommandLine 的用户就无法自定义 usage 的输出了。于是,flag 包在 init 函数中重置了 CommandLineUsage 字段:

func init() {CommandLine.Usage = commandLineUsage // 重置CommandLine的Usage字段
}func commandLineUsage() {Usage()
}var Usage = func() {fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])PrintDefaults()
}

这个时候我们会发现,CommandLineUsage 字段,设置为了一个 flag 包内的未导出函数 commandLineUsage,后者则直接使用了 flag 包的另外一个导出包变量 Usage。这样,就可以通过 init 函数,将 CommandLine 与包变量 Usage 关联在一起了。

然后,当用户将自定义的 usage 赋值给了 flag.Usage 后,就相当于改变了默认代表命令行标志集合的 CommandLine 变量的 Usage。这样当 flag 包完成初始化后,CommandLine 变量便处于一个合理可用的状态了。

7.2 用途二:实现对包级变量的复杂初始化

有些包级变量需要一个比较复杂的初始化过程。有些时候,使用它的类型零值(每个 Go 类型都具有一个零值定义)或通过简单初始化表达式不能满足业务逻辑要求,而 init 函数则非常适合完成此项工作。标准库 http 包中就有这样一个典型示例:

var (http2VerboseLogs    bool // 初始化时默认值为falsehttp2logFrameWrites bool // 初始化时默认值为falsehttp2logFrameReads  bool // 初始化时默认值为falsehttp2inTests        bool // 初始化时默认值为false
)func init() {e := os.Getenv("GODEBUG")if strings.Contains(e, "http2debug=1") {http2VerboseLogs = true // 在init中对http2VerboseLogs的值进行重置}if strings.Contains(e, "http2debug=2") {http2VerboseLogs = true // 在init中对http2VerboseLogs的值进行重置http2logFrameWrites = true // 在init中对http2logFrameWrites的值进行重置http2logFrameReads = true // 在init中对http2logFrameReads的值进行重置}
}

我们可以看到,标准库 http 包定义了一系列布尔类型的特性开关变量,它们默认处于关闭状态(即值为 false),但我们可以通过 GODEBUG 环境变量的值,开启相关特性开关。

可是这样一来,简单地将这些变量初始化为类型零值,就不能满足要求了,所以 http 包在 init 函数中,就根据环境变量 GODEBUG 的值,对这些包级开关变量进行了复杂的初始化,从而保证了这些开关变量在 http 包完成初始化后,可以处于合理状态。

7.3 用途三:在 init 函数中实现“注册模式”

首先我们来看一段使用 lib/pq 包访问 PostgreSQL 数据库的代码示例:

import ("database/sql"_ "github.com/lib/pq"
)func main() {db, err := sql.Open("postgres", "user=pqgotest dbname=pqgotest sslmode=verify-full")if err != nil {log.Fatal(err)}age := 21rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)...
}

其实,这是一段“神奇”的代码。你可以看到示例代码是以空导入的方式导入 lib/pq 包的,main 函数中没有使用 pq 包的任何变量、函数或方法,这样就实现了对 PostgreSQL 数据库的访问。而这一切的奥秘,全在 pq 包的 init 函数中:

func init() {sql.Register("postgres", &Driver{})
}

这个奥秘就在,我们其实是利用了用空导入的方式导入 lib/pq 包时产生的一个“副作用”,也就是 lib/pq 包作为 main 包的依赖包,它的 init 函数会在 pq 包初始化的时候得以执行。

从上面的代码中,我们可以看到在 pq 包的 init 函数中,pq 包将自己实现的 SQL 驱动注册到了 database/sql 包中。这样只要应用层代码在 Open 数据库的时候,传入驱动的名字(这里是“postgres”),那么通过 sql.Open 函数,返回的数据库实例句柄对数据库进行的操作,实际上调用的都是 pq 包中相应的驱动实现。

实际上,这种通过在 init 函数中注册自己的实现的模式,就有效降低了 Go 包对外的直接暴露,尤其是包级变量的暴露,从而避免了外部通过包级变量对包状态的改动。

另外,从标准库 database/sql 包的角度来看,这种“注册模式”实质是一种工厂设计模式的实现,sql.Open 函数就是这个模式中的工厂方法,它根据外部传入的驱动名称“生产”出不同类别的数据库实例句柄。

这种“注册模式”在标准库的其他包中也有广泛应用,比如说,使用标准库 image 包获取各种格式图片的宽和高:

package mainimport ("fmt""image"_ "image/gif" // 以空导入方式注入gif图片格式驱动_ "image/jpeg" // 以空导入方式注入jpeg图片格式驱动_ "image/png" // 以空导入方式注入png图片格式驱动"os"
)func main() {// 支持png, jpeg, gifwidth, height, err := imageSize(os.Args[1]) // 获取传入的图片文件的宽与高if err != nil {fmt.Println("get image size error:", err)return}fmt.Printf("image size: [%d, %d]\n", width, height)
}func imageSize(imageFile string) (int, int, error) {f, _ := os.Open(imageFile) // 打开图文文件defer f.Close()img, _, err := image.Decode(f) // 对文件进行解码,得到图片实例if err != nil {return 0, 0, err}b := img.Bounds() // 返回图片区域return b.Max.X, b.Max.Y, nil
}

你可以看到,上面这个示例程序支持 png、jpeg、gif 三种格式的图片,而达成这一目标的原因,正是 image/pngimage/jpegimage/gif 包都在各自的 init 函数中,将自己“注册”到 image 的支持格式列表中了,你可以看看下面这个代码:

// $GOROOT/src/image/png/reader.go
func init() {image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}// $GOROOT/src/image/jpeg/reader.go
func init() {image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}// $GOROOT/src/image/gif/reader.go
func init() {image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}  

那么,现在我们了解了 init 函数的常见用途。init 函数之所以可以胜任这些工作,恰恰是因为它在 Go 应用初始化次序中的特殊“位次”,也就是 main 函数之前,常量和变量初始化之后。

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

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

相关文章

【vSphere 8 自签名 VMCA 证书】企业 CA 签名证书替换 vSphere VMCA CA 证书Ⅰ—— 生成 CSR

目录 替换拓扑图证书关系示意图说明 & 关联博文1. 默认证书截图2. 使用 certificate-manager 生成CSR2.1 创建存放CSR的目录2.2 记录PNID和IP2.3 生成CSR2.4 验证CSR 参考资料 替换拓扑图 证书关系示意图 本系列博文要实现的拓扑是 说明 & 关联博文 因为使用企业 …

身份证读卡器ubuntu虚拟机实现RK3399 Arm Linux开发板交叉编译libdonsee.so找不到libusb解决办法

昨天一个客户要在RK3399 Linux开发板上面使用身份证读卡器,由于没有客户的开发板,故只能用本机ubuntu虚拟机来交叉编译,用客户发过来的交叉编译工具,已经编译好libusb然后编译libdonsee.so的时候提示找不到libusb,报错…

详解使用sklearn实现一元线性回归和多元线性回归

[Open In Colab] 文章目录 1. 线性回归简介2. 使用sklearn进行一元线性回归3. 线性回归的coef_参数和intercept_参数4. 使用sklearn实现多元线性回归4.1 利用PolynomialFeatures构造输入4.2 进行多元线性回归 5. 总结 import numpy as np import matplotlib.pyplot as plt1. 线…

Leetcode.19 删除链表的倒数第 N 个结点

题目链接 Leetcode.19 删除链表的倒数第 N 个结点 mid 题目描述 给你一个链表,删除链表的倒数第 n n n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5] 示例 2: 输…

Unity中Shader阴影的接收

文章目录 前言一、阴影接受的步骤1、在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.2、在顶点着色器中添加TRANSFER_SHADOW(o),用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间…

Node.js、Vue的安装与使用(Linux OS)

Vue的安装与使用(Linux OS) Node.js的安装Vue的安装Vue的使用 操作系统:Ubuntu 20.04 LTS Node.js的安装 安装Node.js Node.js官方下载地址 1.选择合适的系统架构(可通过uname -m查看)版本安装 2.下载文件为tar.xz格…

uniapp自定义右击菜单

效果图&#xff1a; 代码&#xff1a; 1、需要右击的view: <view class"answer-box" contextmenu.stop.prevent.native"showRightMenu($event, item, content)"> </view>2、右击弹出层&#xff1a; <view v-if"visible" :styl…

智慧矿山:让AI算法提高未戴安全带识别率!

未穿戴安全带识别AI算法&#xff0c;作为智慧矿山的重要应用之一&#xff0c;不仅可以有效提高矿山工作人员的安全意识&#xff0c;还可以降低事故发生的概率。然而&#xff0c;识别准确率的提高一直是该算法面临的挑战之一。为了解决这个问题&#xff0c;研究人员不断努力探索…

JavaEE初阶学习:Servlet

1.Servlet 是什么 Servlet 是一种 Java 程序&#xff0c;用于在 Web 服务器上处理客户端请求和响应。Servlet 可以接收来自客户端&#xff08;浏览器、移动应用等&#xff09;的 HTTP 请求&#xff0c;并生成 HTML 页面或其他格式的数据&#xff0c;然后将响应发送回客户端。S…

【C++】C++11新特性

文章目录 一、C发展简介二、C11简介三、列表初始化1.统一使用{}初始化2.initializer_list类 四、变量的类型推导1.auto2.decltype3.nullptr 五、范围for循环六、STL中一些变化七、final与override八、新的类功能1.新增默认成员函数2.成员变量的缺省值3.default 和 delete4.fina…

LABVIEW 安装教程(超详细)

目录 LabVIEW2017&#xff08;32/64位&#xff09;下载地址&#xff1a; 一 .简介 二.安装步骤&#xff1a; LabVIEW2017&#xff08;32/64位&#xff09;下载地址&#xff1a; 链接&#xff1a; https://pan.baidu.com/s/1eSGB_3ygLNeWpnmGAoSwcQ 密码&#xff1a;gjrk …

JAVA面经整理(MYSQL篇)

索引: 索引是帮助MYSQL高效获取数据的排好序的数据结构 1)假设现在进行查询数据&#xff0c;select * from user where userID89 2)没有索引是一行一行从MYSQL进行查询的&#xff0c;还有就是数据的记录都是存储在MYSQL磁盘上面的&#xff0c;比如说插入数据的时候是向磁盘上面…

C++ 类和对象(六)赋值运算符重载

1 运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c; 也具有其返回值类型&#xff0c;函数名字以及参数列表&#xff0c;其返回值类型与参数列表与普通的函数类似。 函数名字为&#xff1a;关键字operator后面接需…

css之Flex弹性布局(父项常见属性)

文章目录 &#x1f415;前言&#xff1a;&#x1f3e8;定义flex容器 display:flex&#x1f3e8;在flex容器中子组件进行排列&#x1fa82;行排列 flex-direction: row&#x1fa82;将行排列进行翻转排列 flex-direction: row-reverse&#x1f3c5;按列排列 flex-direction: col…

No170.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

mstsc改端口为33389

windows 远程默认端口3389不太安全&#xff0c;改成33389防下小人 把下面的2个文本存在后缀.reg的文件&#xff0c;双击导入注册表&#xff0c;"PortNumber"dword:0000826d 这个就是33389对应的端口号的16进制值&#xff0c;要想自己改成其它的换下值即可 Windows …

人工智能、机器学习、深度学习的区别

人工智能涵盖范围最广&#xff0c;它包含了机器学习&#xff1b;而机器学习是人工智能的重要研究内容&#xff0c;它又包含了深度学习。 人工智能&#xff08;AI&#xff09; 人工智能是一门以计算机科学为基础&#xff0c;融合了数学、神经学、心理学、控制学等多个科目的交…

LeetCode讲解篇之77. 组合

文章目录 题目描述题解思路题解代码 题目描述 题解思路 遍历nums&#xff0c;让当前数字添加到结果前缀中&#xff0c;递归调用&#xff0c;直到前缀的长度为k&#xff0c;然后将前缀添加到结果集 题解代码 func combine(n int, k int) [][]int {var nums make([]int, n)fo…

最新!两步 永久禁止谷歌浏览器 Google Chrome 自动更新

先放效果图&#xff1a; CSDN这个问题最火的大哥的用了没用 像他这样连浏览器都打不开 为什么要禁止chrome自动更新 看到很多搞笑的大哥&#xff0c;说为啥要禁止&#xff1b; 我觉得最大的原因就是chromedriver跟不上chrome的自动更新&#xff0c;导致我们做selenium爬虫的…

MySQL数据库查询实战操作

前置条件: 创建库:MySQL基本操作之创建数据库-CSDN博客 创建表:MySQL基本操作之创建数据表-CSDN博客 目录 常规查询常用函数union查询一、常规查询 普通的查询方式 1、查询所有姓名以 "张" 开头的学生: SELECT * FROM student WHERE name LIKE 张%; 这条语…