【go从入门到精通】作用域,包详解

news2024/11/24 16:05:03

作者简介:

        高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注)

--------------------------------------------------------------------------------------------------------------------------------

      这一节给大家来分享下作用域,包package的作用,用法以及相关代码示例,前面十几篇讲了很基础的go的语法,也用到了一些作用域和包package,但发现自始自终都还没有给大家分享他们的作用,怎么用,那么这一篇文章相信会给大家一个很好的解释和学习机会。

为什么作用域和包很重要?

包和作用域在 Go 中至关重要,原因如下:

  1. 封装:作用域控制变量和函数的可见性,促进封装并防止意外访问。
  2. 模块化:包有助于将代码组织成可管理的单元,从而更轻松地构建和维护项目。
  3. 命名空间:每个包都有自己的命名空间,这意味着您可以在不同的包中使用相同的标识符名称而不会发生冲突。
  4. 可重用性:库包可以在不同的项目中重用,节省时间和精力。

1 作用域

            在 Golang 中,作用域是指可以访问已定义变量或函数的程序区域。它对于控制变量和函数的可见性和生命周期至关重要。在Go语言中,变量的作用域取决于它们的声明位置。根据声明的位置,可以将变量的作用域分为以下几种情况:

  1. 全局作用域:在函数外部声明的变量具有全局作用域,可以在任何函数内部访问。

  2. 函数作用域:在函数内部声明的变量具有函数作用域,只能在该函数内部访问。

  3. 块作用域:在if语句、for循环等块结构内部声明的变量具有块作用域,只能在该块内部访问。

var globalVariable int  // 全局变量

func  myFunc () { 

  localVariable := 2  // 局部变量

  // 使用 localVariable 做一些事情

}

在不同作用域内,变量的可见性不同。在内部作用域中,内部变量可以访问外部作用域中的变量。而在外部作用域中,不能直接访问内部作用域中的变量。因此你可以在myFunc里使用globalVariable这个全局变量,但是你不能在全局作用域下使用局部变量localVariable

因此假设你真想这样做,你会收到这样的报错信息:

另外,对于函数参数中声明的变量,它们的作用域也是函数作用域。只能在该函数内部访问。

在代码中直观可见的显式的(explicit)code block,比如:函数的函数体、for循环的循环体等;还有隐式的(implicit)code block。

下面的代码综合了几种作用域的情况,很容易混淆。请各位仔细琢磨弄清楚。

package main

import "fmt"

var (
    globalV int = 100
) 

func GetFunc() func() int {

    if globalV := 55; globalV < 60 {
        fmt.Println("GetFunc if 中:", globalV)
    }

    for globalV := 2; ; {
        fmt.Println("GetFunc 循环中:", globalV)
        break
    }

    fmt.Println("GetFunc 函数中全局变量:", globalV)

    return func() int {
        globalV += 1
        return globalV
    }
}

func main() {
    fmt.Println("main函数中全局变量:", globalV)
    globalV := "string"
    fmt.Println("main函数中局部变量:", globalV)

    b := GetFunc()
    fmt.Println("main函数中:", b(), b(), b(), b())
    
    {
        globalV := 2
        fmt.Println(globalV)
        {
            globalV := 3
            fmt.Println(globalV)
        }
    }
    fmt.Println(globalV)
}

程序输出:

globalV作为全局变量纯在是int类型,值为100;因此在main函数的第一个打印实际上是打印的全局变量值100,接下来globalV通过简式声明 := 操作,是string类型,值为string。在main()中,v很典型地体现了在“{}”花括号中的作用域问题,每一层花括号,都是对上一层的屏蔽。而闭包函数,GetGa()返回的匿名函数,赋值给b,每次执行b(),Ga的值都被记忆在内存中,下次执行b()的时候,取b()上次执行后Ga的值,而不是全局变量Ga的值,这就是闭包函数可以使用包含它的函数内的变量,因为作为代码块一直存在,所以每次执行都是在上次基础上运行。

简单总结如下:

有花括号"{ }"一般都存在作用域的划分; := 简式声明会屏蔽所有上层代码块中的变量(常量),建议使用规则来规范,如对常量使用全部大写,而变量尽量小写; 在if等语句中存在隐式代码块,需要注意; 闭包函数可以理解为一个代码块,并且他可使用包含它的函数内的变量;

注意,简式变量只能在函数内部声明使用,但是它可能会覆盖函数外全局同名变量。而且你不能在一个单独的声明中重复声明一个变量,但在多变量声明中这是允许的,而且其中至少要有一个新的声明变量。重复变量需要在相同的代码块内,否则你将得到一个隐藏变量。

如果你在代码块中犯了这个错误,将不会出现编译错误,但应用运行结果可能不是你所期望。所以尽可能避免和全局变量同名。

思考:

func main() {
    if a := 1; false {
    } else if b := 2; false {
    } else if c := 3; false {
    } else {
        println(a, b, c)
    }
}

这段代码运行结果是什么,你能写出来吗?

包Package

Go语言使用包(package)的概念来组织管理代码,包是结构化代码的一种方式。在Go语言中,每个.go文件都必须归属于某一个包,每个文件都可有init()函数。包名在源文件中第一行通过关键字package指定,包名要小写。如下所示:

package fmt

每个目录下面可以有多个.go文件,这些文件只能属于同一个包,否则编译时会报错。同一个包下的不同.go文件相互之间可以直接引用变量和函数,所以这些文件中定义的全局变量和函数不能重名。

如果你使用某个包中的某个成员,就必须import 包名。有两种方式,单行汇导入和多行导入:

// 单行导入
import "fmt" 
import "math"


// 多行导入
import ( 
    "fmt" 
    "math" 
)

如果在一个包中,想要访问另一个包的成员,就像变数、函式、结构等,就必须导出成员,export 的方法即将成员名称设置为大写。

package mypkg 
var myVar = 100 // 只能在 mypkg 中使用
const MyConst = "hello" // 可以在 mypkg 外部使用

 

Go语言的可执行应用程序必须有main包,而且在main包中必须且只能有一个main()函数,main()函数是应用程序运行开始入口。在main包中也可以使用init()函数。

Go语言不强制要求包的名称和文件所在目录名称相同,但是这两者最好保持相同,否则很容易引起歧义。因为导入包的时候,会使用目录名作为包的路径,而在代码中使用时,却要使用包的名称。

包的导入

一个Go程序通过import关键字将一组包链接在一起。import其实是导入目录,而不是定义的包名称,实际应用中我们一般都会保持一致。

例如标准包中定义的big包:package big,import "math/big" ,源代码其实是在GOROOT下src中的src/math/big目录。在代码中使用big.Int时,big指的才是.go文件中定义的包名称。

当导入多个包时,一般按照字母顺序排列包名称,像LiteIDE会在保存文件时自动完成这个动作。所谓导入包即等同于包含了这个包的所有的代码对象。

为避免名称冲突,同一包中所有对象的标识符必须要求唯一。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。

import语句一般放在包名定义的下一行,导入包示例如下:

package main

import  "context"  //加载context包

导入多个包的常见的方式是:

import  (
"fmt"
"net/http"
 )

调用导入的包函数的一般方式:

fmt.Println("Hello World!")

下面介绍三种特殊的import方式。

点操作的含义是某个包导入之后,在调用这个包的函数时,可以省略前缀的包名,如这里可以写成Println("Hello World!"),而不是fmt.Println("Hello World!")。例如:

import( . "fmt" )

别名操作就是可以把包命名成另一个容易记忆的名字。例如:

import(
    f "fmt"
)

别名操作调用包函数时,前缀变成了别名,即f.Println("Hello World!")。在实际项目中有时这样使用,但请谨慎使用,不要不加节制地采用这种形式。

_ 操作是引入某个包,但不直接使用包里的函数,而是调用该包里面的init函数,比如下面的mysql包的导入。此外在开发中,由于某种原因某个原来导入的包现在不再使用,也可以采用这种方式处理,比如下面fmt的包。代码示例如下:

import (
    _ "fmt"
    _ "github.com/go-sql-driver/mysql"
)

标准包

在 Go 的安装文件里包含了一些可以直接使用的标准包。在$GOROOT/src中可以看到源码,也可以根据情况自行重新编译。

完整列表可以访问GoWalker(https://gowalker.org/)查看。

    unsafe: 包含了一些打破 Go 语言“类型安全”的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。
    syscall-os-os/exec:
        os: 提供给我们一个平台无关性的操作系统功能接口,采用类Unix设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
        os/exec: 提供我们运行外部操作系统命令和程序的方式。
        syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
    archive/tar 和 /zip-compress:压缩(解压缩)文件功能。
    fmt-io-bufio-path/filepath-flag:
        fmt: 提供了格式化输入输出功能。
        io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
        bufio: 缓冲输入输出功能的封装。
        path/filepath: 用来操作在当前系统中的目标文件名路径。
        flag: 对命令行参数的操作。  
    strings-strconv-unicode-regexp-bytes:
        strings: 提供对字符串的操作。
        strconv: 提供将字符串转换为基础类型的功能。
        unicode: 为 unicode 型的字符串提供特殊的功能。
        regexp: 正则表达式功能。
        bytes: 提供对字符型分片的操作。
    math-math/cmath-math/big-math/rand-sort:
        math: 基本的数学函数。
        math/cmath: 对复数的操作。
        math/rand: 伪随机数生成。
        sort: 为数组排序和自定义集合。
        math/big: 大数的实现和计算。   
    container-/list-ring-heap: 实现对集合的操作。
        list: 双链表。
        ring: 环形链表。
   time-log:
        time: 日期和时间的基本操作。
        log: 记录程序运行时产生的日志。
    encoding/Json-encoding/xml-text/template:
        encoding/Json: 读取并解码和写入并编码 Json 数据。
        encoding/xml:简单的 XML1.0 解析器。
        text/template:生成像 HTML 一样的数据与文本混合的数据驱动模板。
    net-net/http-html:
        net: 网络数据的基本操作。
        http: 提供了一个可扩展的 HTTP 服务器和客户端,解析 HTTP 请求和回复。
        html: HTML5 解析器。
    runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。
    reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。
 

从 GitHub 安装包

如果有人想安装您的远端项目到本地机器,打开终端并执行(ffhelicopter是我在 GitHub 上的用户名):

go get -u go.mongodb.org/mongo-driver/mongo

这样现在这台机器上的其他 Go 应用程序也可以通过导入路径:"go.mongodb.org/mongo-driver/mongo" 来使用。 

import "go.mongodb.org/mongo-driver/mongo"

Go 对包的版本管理做的不是很友好,不过现在有些第三方项目做的不错,有兴趣的同学可以了解下(glide、godep、govendor)。

导入外部安装包

如果你要在你的应用中使用一个或多个外部包,你可以使用 Go install在你的本地机器上安装它们。Go install 是 Go 中自动包安装工具:如需要将包安装到本地它会从远端仓库下载包:检出、编译和安装一气呵成。

在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。

Go install 使用了 GoPATH 变量

假设你想使用GitHub - gocolly/colly: Elegant Scraper and Crawler Framework for Golang 这种托管在 Google Code、GitHub 和 Launchpad 等代码网站上的包。

你可以通过如下命令安装: Go install github.com/gocolly/colly 将一个名为 github.com/gocolly/colly 安装在$GoPATH/pkg/ 目录下。

Go install/build都是用来编译包和其依赖的包。

区别: Go build只对main包有效,在当前目录编译生成一个可执行的二进制文件(依赖包生成的静态库文件放在$GoPATH/pkg)。

Go install一般生成静态库文件放在$GoPATH/pkg目录下,文件扩展名a。

如果为main包,运行Go buil则会在$GoPATH/bin 生成一个可执行的二进制文件。

包的初始化

可执行应用程序的初始化和执行都起始于main包。如果main包的源代码中没有包含main()函数,则会引发构建错误 undefined: main.main。main()函数既没有参数,也没有返回类型,init()函数和main()函数在这一点上两者一样。

如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时某个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。

当某个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init()函数(如果有的话),依次类推。

等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init()函数,最后执行main()函数。

Go语言中init()函数常用于包的初始化,该函数是Go语言的一个重要特性,有下面的特征:

  • init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
  • 每个包可以拥有多个init函数
  • 包的每个源文件也可以拥有多个init函数
  • 同一个包中多个init()函数的执行顺序不定
  • 不同包的init()函数按照包导入的依赖关系决定该函数的执行顺序
  • init()函数不能被其他函数调用,其在main函数执行之前,自动被调用

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

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

相关文章

基于React封装Handsontable并兼容antd

背景 其实Handsontable官方也提供了React的版本&#xff0c;但是官方的版本再编辑和渲染的时候并不能够很好的嵌入第三方的组件库。这就导致了&#xff0c;使用了Handsontable就没有办和普通的react项目一样轻松引用其他第三方组件。因此对其react的版本进行了二次的封装&#…

MySQL之sql性能分析

sql执行频率 MySQL客户端连接成功后&#xff0c;通过show[session|global]status命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的所有INSERT、DELETE、UPDATE、SELECT的访问频次。 慢日志查询 慢查询日志记录了所有执行时间超过指定参数(longquer…

石子合并(区间dp)-java

石子合并问题是经典的区间dp问题&#xff0c;我们需要枚举中间端点k的情况从而来推出dp数组的值。 文章目录 前言 一、石子合并问题 二、算法思路 1.问题思路 2.状态递推公式 二、代码如下 代码如下&#xff08;示例&#xff09;&#xff1a; 2.读入数据 3.代码运行结果如下&am…

CY7.5-COOH荧光属性Cyanine7.5花菁染料

CY7.5-COOH&#xff0c;作为一种Cyanine 7.5花菁染料&#xff0c;在荧光属性上表现出色。其独特的化学结构赋予了它荧光性质&#xff0c;使其在生物标记、荧光成像和光学传感等领域具有诸多应用前景。 CY7.5-COOH具有出色的荧光量子产率&#xff0c;这意味着在激发光的照射下&…

电商市场价格治理的有效方法

监测价格的目的是为了治理价格&#xff0c;品牌在发展过程中必然要做价格管控&#xff0c;否则任由渠道自由生长&#xff0c;最后的结果多是低价不断&#xff0c;且渠道愈发难以管控&#xff0c;所以在电商链接并不多时&#xff0c;也应对渠道进行监测、治理&#xff0c;将渠道…

【学习路径】AI入门路线分享

近期整理飞书文档&#xff0c;一些权限被关掉了。看好多人在申请访问这个飞书文档&#xff0c;于是把它单独拿出来放在CSDN上&#xff0c;供大家参考~ 原视频地址&#xff1a;AI&#xff1a;从小白到入门&#xff0c;超详细人工智能成长路径分享_哔哩哔哩_bilibili 文章目录 1.…

69787987

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

ArcGIS Pro中的3D建模

在本文中,我讲述了我最近一直在探索的在 ArcGIS Pro 中设计 3D 模型的过程。 我的目标是尽可能避免与其他软件交互(即使是专门用于 3D 建模的软件),并利用 Pro 可以提供的可能性。 这个短暂的旅程分为三个不同的阶段:准备、组装和照明。 我们必须使用一些布局可能性以及一…

android平台下opencv的编译--包含扩展模块

由于项目需要使用安卓平台下opencv的扩展库&#xff0c;对于通用的opencv库&#xff0c; opencv官网提供了android的SDK 但未能提供扩展库&#xff0c;因此需要自己进行源码编译。本文记录android平台下opencv及其扩展库的交叉编译。 前提&#xff1a;主机已安装android-ndk交…

泰迪智能科技受邀参加花都区产教评技能生态链建设政策宣讲培训会

2024年4月8日&#xff0c;由广州市花都区人力资源和社会保障局主办的花都区产教评技能生态链建设政策宣讲培训会在广州市华风高级技工学校顺利举行。广东省人力资源和社会保障厅职业能力建设处鲍彬科长&#xff0c;广州市人力资源和社会保障局职业能力建设处肖飞扬科长&#xf…

volta(轻松切换管理Node.js版本)

Node.js版本管理 Volta提供了一个简单直观的命令行界面&#xff0c;可以轻松地安装、卸载、更新和切换Node.js版本。 Volta 既可以全局使用&#xff0c;也可以在项目级别使用&#xff0c;可以为每个项目单独设置node版本&#xff0c;nvm不行。 下载安装Volta 参考&#xff1a; …

JavaScript ECMAScript标准的与时俱进:从ES6至ES14的革新之路与关键技术特性剖析

ECMAScript&#xff08;通常缩写为ES&#xff09;是一种标准化的脚本语言规范&#xff0c;由ECMA International&#xff08;前身为European Computer Manufacturers Association&#xff0c;欧洲计算机制造商协会&#xff09;制定。自1997年发布首个版本以来&#xff0c;ECMAS…

JavaScript_注释数据类型

JavaScript_语法_注释&数据类型&#xff1a; 1.2注释&#xff1a; 1.单行注释&#xff1a;//注释内容 2.多行注释&#xff1a;/*注释内容*/ 1.3数据类型&#xff1a; 1.原始数据类型(基本数据类型)&#xff1a;&#xff08;只有这五种&#xff09; 1.number&#xff1a;数字…

网络安全知识知多少?

网络安全知识在当今数字化的世界中显得尤为重要。随着网络技术的不断发展和普及&#xff0c;网络安全问题也日益突出&#xff0c;对个人、组织乃至国家的安全都构成了严重威胁。因此&#xff0c;了解并掌握一些基本的网络安全知识显得尤为重要。 首先&#xff0c;我们需要明白网…

第21天:WEB攻防-JavaWeb项目UWT身份攻击组件安全访问控制;第22天:WEB攻防-JS项目Node.JS框架安全识别审计验证绕过

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

利用免费AI开源引擎:实现图像识别技术在多主体检测中的应用|识别万物|本地化部署

在当今快速发展的图像处理领域&#xff0c;图像主体检测技术已成为提升图像分析效率和精度的关键工具。该技术能够自动识别和定位图像中的一个或多个主要对象&#xff0c;并提供其具体的位置坐标和分类标签。这不仅为图像编辑和优化提供了便利&#xff0c;也为后续的图像识别任…

国芯科技(C*Core)车规MCU产品选型与应用

一、MCU市场概述 MCU&#xff08;Micro Controller Unit&#xff09;即微控制器&#xff0c;俗称单片机&#xff0c;是把 CPU 的规格与频率做适当缩减&#xff0c;并将ROM、RAM、A/D转换、各式I/O接口以及Timer等功能整合在单一芯片上&#xff0c;形成芯片级的计算机。 根据…

学习记录14-运算放大器2

目录 前言 一、理想放大器 二、虚断 二、虚短 虚短的两个使用条件 1.虚短概念 2.如果我们将运放的同相端和反相端颠倒会怎样呢&#xff1f; 总结 前言 主要讲述运算放大器的虚短虚断 一、理想放大器 如果没有基础或只是想简单了解&#xff0c;可以看我前一篇文章&am…

C 强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

SpringBoot碎片化知识

参考资料&#xff1a; java官方词典&#xff1a;https://docs.oracle.com/javase/tutorial/information/glossary.html#F苍穹外卖&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6 JavaBean规范 JavaBean规范是一种类的规范&#xff0c;其要求符合下列条件&#xf…