随笔 - 134,  文章 - 0,  评论 - 0,  阅读 - 21210

GO(一)

  • go mod init test
  • go mod tidy

快速生成代码片段

pkgm main包+main主函数
ff  fmt.Printf("",var)
for for i:=0;i<count;i++{}
fmain func main(){}
a.print! fmt.Printf("a:%v/n",a)

实现步骤

  1. 创建项目
  2. 初始化项目
  3. 创建包
  4. 创建模块
  5. 相互调用

Go语言介绍

目录

Go语言介绍

img

Go 即Golang,是Google公司2009年11月正式对外公开的一门编程语言。

Go是静态强类型语言,是区别于解析型语言的编译型语言。

解析型语言——源代码是先翻译为中间代码,然后由解析器对代码进行解释执行。

编译型语言——源代码编译生成机器语言,然后由机器直接执行机器码即可执行。

Go语言特性

跨平台的编译型语言

语法接近C语言

管道(channel),切片(slice),并发(routine)

有垃圾回收的机制

支持面向对象和面向过程的编程模式

Go 语言最主要的特性:

  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性

Go语言发展(版本/特性)

2009年11月7日 weekly.2009-11-06 —— 早期的版本

2012年3月28日 go1 —— 稳定版版的发布

2013年5月14日 go1.1 —— 语言本身的变化、库的变化等。

2013年12月01日 go1.2 —— 提供更好的调度和一个新的语言特性。

2014年6月18日 go1.3 —— 主要是在实现的工作上的变化,以及精准的垃圾回收。

2014年12月10日 go1.4 —— 包含了2个很小的语言变更。

2015年8月19日 go1.5 —— 实现的架构变化,同时保留了和旧版本的兼容性。

2016年2月7日 go1.6 —— 语言本身、运行时、和库的实现的变化。

2016年8月15日 go1.7 —— 标准库有显着的性能改进和变化。

2017年2月26日 go1.8 —— 标准库有显着的性能改进和变化。

2017年8月24日 go1.9 —— 是包含语言,标准库,以及运行时的诸多变化。这个版本中的大部分工作都是对运行时和工具的改进。

2018年2月16日 go1.10 —— 大部分更改都在工具链,运行时和库的实现中。

2018年8月24日 go1.11 —— modules和WebAssembly支持。

Go语言应用

谁在用

Google

这个不用多做介绍,作为开发Go语言的公司,当仁不让。Google基于Go有很多优秀的项目,比如:https://github.com/kubernetes/kubernetes ,大家也可以在Github上 https://github.com/google/ 查看更多Google的Go开源项目。

Facebook

Facebook也在用,为此他们还专门在Github上建立了一个开源组织facebookgo,大家可以通过https://github.com/facebookgo访问查看facebook开源的项目,比如著名的是平滑升级的grace。

腾讯

腾讯作为国内的大公司,还是敢于尝试的,尤其是Docker容器化这一块,他们在15年已经做了docker万台规模的实践,具体可以参考http://www.infoq.com/cn/articles/tencent-millions-scale-docker-application-practice

主要职责是:

负责腾讯游戏蓝鲸平台后台开发工作
负责容器相关的开发工作

和蓝鲸平台,容器开发有关。腾讯作为主要使用C/C++的公司,使用Go会方便很多,也有很多优势,不过日积月累的C/C++代码很难改造,也不敢动,所以新业务会在Go方面尝试。

百度

目前所知的百度的使用是在运维这边,是百度运维的一个BFE项目,负责前端流量的接入。他们的负责人在2016年有分享,大家可以看下这个 http://www.infoq.com/cn/presentations/application-of-golang-in-baidu-frontend .

其次就是百度的消息系统,从其最近的Golang招聘介绍就可以看出来.

负责公司手百消息通讯系统服务器端开发及维护

京东

京东云消息推送系统、云存储,以及京东商城等都有使用Go做开发。

小米

小米对Golang的支持,莫过于运维监控系统的开源,也就是 http://open-falcon.com/

此外,小米互娱、小米商城、小米视频、小米生态链等团队都在使用Golang。

360

360对Golang的使用也不少,一个是开源的日志搜索系统Poseidon,托管在Github上,https://github.com/Qihoo360/poseidon.

还有360的推送团队也在使用,他们还写了篇博文在Golang的官方博客上 https://blog.golang.org/qihoo

360直播在招聘Golang开发工程师。

美团、滴滴、新浪以及七牛等。一般的选择,都是选择用于自己公司合适的产品系统来做,比如消息推送的、监控的、容器的等,Golang特别适合做网络并发的服务,这是他的强项,所以也是被优先用于这些项目。

应用领域

服务开发、并发、分布式、微服务等方向

Go语言项目

docker:基于lxc的一个虚拟打包工具,能够实现PAAS平台的组建。

kubernetes :Kubernetes是Google开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。在生产环境中部署一个应用程序时,通常要部署该应用的多个实例以便对应用请求进行负载均衡 k8s

区块链:BTCD 是用go语言实现的完整节点的比特币实现

nsq:bitly开源的消息队列系统,性能非常高,目前他们每天处理数十亿条的消息

packer:用来生成不同平台的镜像文件,例如VM、vbox、AWS等,作者是vagrant的作者

skynet:分布式调度框架

Doozer:分布式同步工具,类似ZooKeeper

Heka:mazila开源的日志处理系统

cbfs:couchbase开源的分布式文件系统

tsuru:开源的PAAS平台,和SAE实现的功能一模一样

groupcache:memcahe作者写的用于Google下载系统的缓存系统

god:类似redis的缓存系统,但是支持分布式和扩展性

gor:网络流量抓包和重放工具

Go语架构

GOPATH的工作区包含bin,src,和pkg这三个:

src——源码(包含第三方的和自己项目的)

bin——编译生成的可执行程序

pkg——编译时生成的对象文件

Go语言发展前景

Go语言将登上语言榜前列,与C和Java并列前三甲

Go语言称霸云计算

Go将统治下一个10年

Go语言组成

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

例子到了解GO

package main

import "fmt"

func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}
  1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  2. 下一行 import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
  3. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  4. 下一行 /.../ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。
  5. 下一行 fmt.Println(...) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。
    使用 fmt.Print("hello, world\n") 可以得到相同的结果。
    Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。
  6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

运行go程序

一:

$ go run hello.go
Hello, World!

二:

$ go build hello.go 
$ ls
hello    hello.go
$ ./hello 
Hello, World!

开发环境搭建

目录

一 下载地址

安装包下载地址为:https://golang.org/dl/

如果打不开可以使用这个地址:https://golang.google.cn/dl/

各个系统对应的包名:

操作系统 包名
Windows go1.13.3.windows-amd64.msi
Linux go1.13.3.linux-amd64.tar.gz
Mac go1.13.3.darwin-amd64.pkg
FreeBSD go1.13.3.freebsd-amd64.tar.gz

image-20191023180544174

二 安装

Linux安装

1、下载二进制包:go1.13.3.linux-amd64.tar.gz

2、将下载的二进制包解压至 /usr/local目录。

Copytar -C /usr/local -xzf go1.13.3.linux-amd64.tar.gz

3、将 /usr/local/go/bin 目录添加至PATH环境变量:

Copyexport PATH=$PATH:/usr/local/go/bin

Windows安装

Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.13.3.windows-amd64.msi)的安装包来安装。

默认情况下 .msi 文件会安装在 c:\Go 目录下。你可以将 c:\Go\bin 目录添加到 Path 环境变量中。添加后你需要重启命令窗口才能生效

Mac安装

Mac下直接双击go1.13.3.darwin-amd64.pkg,一路下一步安装即可

三 测试安装

1、创建工作目录 C:>Go_Project

2、 创建文件test.go,写入如下代码

Copypackage main
import "fmt"
func main() {
   fmt.Println("Hello, World!")
}

3、打开cmd,切换目录到C:>Go_Project下,执行如下命令:

Copygo run test.go

看到打印结果:

CopyHello, World!

四 命令介绍

基本介绍

直接在终端中输入 go help 即可显示所有的 go 命令以及相应命令功能简介,主要有下面这些:

  • go env用于打印Go语言的环境信息。
  • go run命令可以编译并运行命令源码文件。
  • go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
  • go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。
  • go install用于编译并安装指定的代码包及它们的依赖包。
  • go clean命令会删除掉执行其它命令时产生的一些文件和目录。
  • go doc命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。
  • go test命令用于对Go语言编写的程序进行测试。
  • go list命令的作用是列出指定的代码包的信息。
  • go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
  • go vet是一个用于检查Go语言源码中静态错误的简单工具。
  • go tool pprof命令来交互式的访问概要文件的内容。

build 和 run 命令

就像其他静态类型语言一样,要执行 go 程序,需要先编译,然后在执行产生的可执行文件。go build 命令就是用来编译 go程序生成可执行文件的。但并不是所以的 go 程序都可以编译生成可执行文件的, 要生成可执行文件,go程序需要满足两个条件:

  • 该go程序需要属于main包
  • 在main包中必须还得包含main函数

也就是说go程序的入口就是 main.main, 即main包下的main函数, 例子(test.go):

编译hello.go,然后运行可执行程序:

Copy$ go run test.go   # 将会生成可执行文件 test
$ ./test           # 运行可执行文件
Hello, World!

上面就是 go build 的基本用法,另外如果使用 go build 编译的不是一个可执行程序,而是一个包,那么将不会生成可执行文件。

go run 命令可以将上面两步并为一步执行(不会产生中间文件)。

Copy$ go run test.go
Hello, World!

上面两个命令都是在开发中非常常用的。

此外 go clean 命令,可以用于将清除产生的可执行程序:

Copy$ go clean    # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean hello.go  # 会删除对应的可执行文件

get 命令

这个命令同样也是很常用的,我们可以使用它来下载并安装第三方包, 使用方式:

Copygo get src

从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装,例如我们想使用 beego 来开发web应用,我们首先就需要获取 beego:

Copygo get github.com/astaxie/beego

这条命令将会自动下载安装 beego 以及它的依赖,然后我们就可以使用下面的方式使用:

Copypackage main

import "github.com/astaxie/beego"   # 这里需要使用 src 下的完整路径

func main() {
    beego.Run()
}

Go包管理

一 包管理历史

Golang 的包管理一直被大众所诟病的一个点,但是我们可以看到现在确实是在往好的方向进行发展。下面是官方的包管理工具的发展历史:

  • 在 1.5 版本之前,所有的依赖包都是存放在 GOPATH 下,没有版本控制。这个类似 Google 使用单一仓库来管理代码的方式。这种方式的最大的弊端就是无法实现包的多版本控制,比如项目 A 和项目 B 依赖于不同版本的 package,如果 package 没有做到完全的向前兼容,往往会导致一些问题。
  • 1.5 版本推出了 vendor 机制。所谓 vendor 机制,就是每个项目的根目录下可以有一个 vendor 目录,里面存放了该项目的依赖的 package。go build 的时候会先去 vendor 目录查找依赖,如果没有找到会再去 GOPATH 目录下查找。
  • 1.9 版本推出了实验性质的包管理工具 dep,这里把 dep 归结为 Golang 官方的包管理方式可能有一些不太准确。关于 dep 的争议颇多,比如为什么官方后来没有直接使用 dep 而是弄了一个新的 modules,具体细节这里不太方便展开。
  • 1.11 版本推出 modules 机制,简称 mod。modules 的原型其实是 vgo,关于 vgo,可以自行搜索。

除此之外,社区也一直在有几个活跃的包管理工具,使用广泛且具有代表性的主要有下面几个:

  • godep
  • glide
  • govendor

二 modules 的使用

2.1 准备

Golang 版本:1.12.3。在 1.12 版本之前,使用 Go modules 之前需要环境变量 GO111MODULE:

  • GO111MODULE=off: 不使用 modules 功能,查找vendor和GOPATH目录
  • GO111MODULE=on: 使用 modules 功能,不会去 GOPATH 下面查找依赖包。
  • GO111MODULE=auto: Golang 自己检测是不是使用 modules 功能,如果当前目录不在$GOPATH 并且 当前目录(或者父目录)下有go.mod文件,则使用 GO111MODULE, 否则仍旧使用 GOPATH mode
Copyset GO111MODULE=on    //windows
export GO111MODULE=on //linux

在 GOPATH 之外创建一个项目 mod-demo,包含一个 main.go 文件,内容如下:

Copypackage main

import "fmt"
import "github.com/astaxie/beego"
func main() {
	fmt.Println("hello")
	beego.Run()
}

2.2 初始化go modules

在项目根目录执行命令 go mod init test-demo ,然后会生成一个 go.mod

image-20191120210941001

这里比较关键的就是这个 go.mod 文件,这个文件中标识了我们的项目的依赖的 package 的版本。执行 init 暂时还没有将所有的依赖管理起来。我们需要将程序 run 起来(比如执行 go run/test),或者 build(执行命令 go build)的时候,才会触发依赖的解析。

比如使用 go run 即可触发 modules 工作。

Copygo run test.go

这个时候我们再查看 go.mod 文件:

Copymodule test-demo

go 1.13

require (
	github.com/astaxie/beego v1.12.0 // indirect
	github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)

注意:有indirect注释的代表间接依赖,没有的代表直接依赖,这里是版本号+时间戳+hash

注意:在goland中可以用alt+enter/option+enter来下载依赖包

同时我们发现项目目录下多了一个 go.sum 用来记录每个 package 的版本和哈希值。go.mod 文件正常情况会包含 module 和 require 模块,除此之外还可以包含 replace 和 exclude 模块。

这些 package 并不是直接存储到 GOPATH/src,而是存储到 GOPATH/pkg/mod 下面,不同版本并存的方式。

image-20191120211440359

2.3 依赖的升级和降级

可以使用如下命令来查看当前项目依赖的所有的包

Copygo list -m -u all
//如果我想要升级(降级)某个 package 则只需要 go get 即可,比如:
go get package@version

需要注意的是,在 modules 模式开启和关闭的情况下,go get 的使用方式不是完全相同的。在 modules 模式开启的情况下,可以通过在 package 后面添加 @version 来表明要升级(降级)到某个版本。如果没有指明 version 的情况下,则默认先下载打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果没有 release 版本,则下载最新的 pre release 版本,比如 v0.0.1-pre1。如果还没有则下载最新的 commit。这个地方给我们的一个启示是如果我们不按规范的方式来命名我们的 package 的 tag,则 modules 是无法管理的。version 的格式为 v(major).(minor).(patch) ,更多信息可以参考:https://semver.org/

比如我们现在想将我们依赖中的 beego 项目的版本改为 v1.11.1,则可以像如下操作。我们发现执行完 go get 之后, go.mod 中的项目的版本也相应改变了。

Copygo get github.com/astaxie/beego@v1.11.1

在 modules 开启的模式下,go get 还支持 version 模糊查询,比如 > v1.0.0 表示大于 v1.0.0
的可使用版本;< v1.12.0 表示小于 v1.12.0 版本下最近可用的版本。version 的比较规则按照 version
的各个字段来展开。

除了指定版本,我们还可以使用如下命名使用最近的可行的版本:

  • go get -u 使用最新的 minor 或者 patch 版本
  • go get -u=patch 使用最新的 patch 版本

2.4 vender

我们知道 Go 1.5 推出了 vendor 机制,go mod 也可以支持 vendor 机制,将依赖包拷贝到 vendor 目录。但是像一些 test case 里面的依赖包并不会拷贝的 vendor 目录中。

Copygo mod vendor

三 modules 高级特性

3.1 代理服务器GoProxy

proxy 顾名思义,代理服务器。众所周知,有些 Golang 的 package 在国内是无法直接 go get 的。在之前,我们解决这个问题,一般都是通过设置 http_proxy/https_proxy 来解决。GoProxy 相当于官方提供了一种 proxy 的方式让用户来进行包下载。要使用 GoProxy 只需要设置环境变量 GOPROXY 即可。目前公开的 GOPROXY 有:

  • goproxy.io 在goland-->Go-->Go Modules(ego)中配置这个地址
  • goproxy.cn: 由七牛云提供,参考 github repo

image-20191120231215167

当然你也可以实现自己的 GoProxy 服务,比如项目中的依赖包含外部依赖和内部依赖的时候,那么只需要实现 module proxy protocal 协议即可。

值得注意的是,在最新 release 的 Go 1.13 版本中默认将 GOPROXY 设置为 https://proxy.golang.org,这个对于国内的开发者是无法直接使用的。所以如果升级了 Go 1.13 版本一定要把 GOPROXY 手动改掉。

3.2 replace

replace 主要为了解决某些包发生改名的问题。

对于另外一种场景有的时候也是有用的,比如对于有些 golang.org/x/ 下面的包由于某些原因在国内是下载不了的,但是对应的包在 github 上面是有一份拷贝的,这个时候我们就可以将 go.mod 中的包进行 replace 操作。

下面是一个 Beego 项目的 go.mod 的 replace 的示例。

Copyreplace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85
replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d

3.3 SubCommand

modules 支持的 subcommand 如下

即输入go mod后面可以跟以下命令

  • download: 下载 modules 到本地缓存
  • edit: 提供一种命令行交互修改 go.mod 的方式
  • graph: 将 module 的依赖图在命令行打印出来,其实并不是很直观
  • init: 初始化 modules,会生成一个 go.mod 文件
  • tidy: 清理 go.mod 中的依赖,会添加缺失的依赖,同时移除没有用到的依赖
  • vendor: 将依赖包打包拷贝到项目的 vendor 目录下,值得注意的是并不会将 test code 中的依赖包打包到 vendor 中。这种设计在社区也引起过几次争论,但是并没有达成一致。
  • verify: verify 用来检测依赖包自下载之后是否被改动过。
  • why: 解释为什么 package 或者 module 是需要,但是看上去解释的理由并不是非常的直观。

四 Go 1.13 对 modules 的改动

上面在讨论 GoProxy 的时候提到了 Go 1.13 默认设置环境变量 GOPROXY 的值,除此之外 Go 1.13 对 modules 还有哪些值得注意的改动呢?

4.1 默认开启

modules 在 Go 1.13 的版本下是默认开启的。

4.2 GOPRIVATE

前面也说到对于一些内部的 package,GoProxy 并不能很好的处理,Go 1.13 推出了 GOPRIVATE 机制。只需要设置这个环境变量,然后标识出哪些 package 是 private 的,那么对于这个 package 的处理将不会从 proxy 下载。GOPRIVATE 的值是一个以逗号分隔的列表,支持正则(正则语法遵守 Golang 的 包 path.Match)。下面是一个 GOPRIVATE 的示例:

CopyGOPRIVATE=*.corp.example.com,rsc.io/private

上面的 GOPRIVATE 表示以 *.corp.example.com 或者 rsc.io/private 开头的 package 都是私有的。

4.3 GOSUMDB

GOSUMDB 的全称为 Go CheckSum Database,用来下载的包的安全性校验问题。包的安全性在使用 GoProxy 之后更容易出现,比如我们引用了一个不安全的 GoProxy 之后然后下载了一个不安全的包,这个时候就出现了安全性问题。对于这种情况,可以通过 GOSUMDB 来对包的哈希值进行校验。当然如果想要关闭哈希校验,可以将 GOSUMDB 设置为 off;如果要对部分包关闭哈希校验,则可以将包的前缀设置到环境变量中 GONOSUMDB 中,设置规则类似 GOPRIVATE。

关于 GOSUMDB 的配置格式为:<db_name>++。

CopyGOSUMDB="sum.golang.org"GOSUMDB="sum.golang.org+<publickey>"GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"

上面三种配置都是合理的,因为对于 sum.golang.org,Go 自己知道其对应的 publickey 和 url,所以我们只要配置一个名字即可,对于另外一个 sum.golang.google.cn 也是一样。除此之外的,都需要指明 publickey,url 默认是 https://<db_name>。

关于 GOSUMDB 更多的详细信息可以参考:https://golang.org/cmd/go/#hdr-Module_authentication_failures

五 自己写的包如何导入使用

直接在go.mod中写包名即可

Copymodule test-demo

go 1.13

require (
	github.com/astaxie/beego v1.12.0
	github.com/gin-gonic/gin v1.4.0 // indirect
	github.com/go-redis/redis v6.15.6+incompatible
	github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
    redisPackage v0.0.1  //自己加的v0.1.1 必须为三个数(也可以 不加)
)

五 总结

Golang 包管理历经多个版本,目前来看并没有一个完全让开发者满意的方案,比如类似 Java 的 maven 的包管理方式。很多开发者也表示 modules 的管理方式也不是很直观。其实 Golang 的 package 使用 git 的管理方式来管理其实是一个很好的管理方式,我们最需要的其实如何能让普通开发者获取到任何 package 的心智负担降到最低。值得欣慰的是我们确实有看到大家在这个方向上的努力,比如 modules 的 GoProxy。

放眼未来,希望会有一种令大部分普通开发者满意的包管理方式吧。

最后常用命令

Copygo mod init:初始化modules
go mod download:下载modules到本地cache
go mod edit:编辑go.mod文件,选项有-json、-require和-exclude,可以使用帮助go help mod edit
go mod graph:以文本模式打印模块需求图
go mod tidy:检查,删除错误或者不使用的modules,下载没download的package
go mod vendor:生成vendor目录
go mod verify:验证依赖是否正确
go mod why:查找依赖

go test    执行一下,自动导包

go list -m  主模块的打印路径
go list -m -f={{.Dir}}  print主模块的根目录
go list -m all  查看当前的依赖和版本信息

补充-关于文件和包

  • 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
  • 文件夹名与包名没有直接关系,并非需要一致。
  • 同一个文件夹下的文件只能有一个包名,否则编译报错

文件结构:

Test
--helloworld.go

myMath
--myMath1.go
--myMath2.go

测试代码:

// helloworld.go
package main

import (
"fmt"
"./myMath"
)

func main(){
    fmt.Println("Hello World!")
    fmt.Println(mathClass.Add(1,1))
    fmt.Println(mathClass.Sub(1,1))
}
// myMath1.go
package mathClass
func Add(x,y int) int {
    return x + y
}
// myMath2.go
package mathClass
func Sub(x,y int) int {
    return x - y
}

GO语言基础

main函数和init函数

init函数

go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。

有下面的特征:

    1 init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等

    2 每个包可以拥有多个init函数

    3 包的每个源文件也可以拥有多个init函数

    4 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)

    5 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序

    6 init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

main函数

    Go语言程序的默认入口函数(主函数):func main()
    函数体用{}一对括号包裹。

    func main(){
        //函数体
    }

init函数和main函数的异同

    相同点:
        两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
    不同点:
        init可以应用于任意包中,且可以重复定义多个。
        main函数只能用于main包中,且只能定义一个。

两个函数的执行顺序:

对同一个go文件的init()调用顺序是从上到下的。

对同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。

对于不同的package,如果不相互依赖的话,按照main包中"先import的后调用"的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init(),最后调用main函数。

如果init函数中使用了println()或者print()你会发现在执行过程中这两个不会按照你想象中的顺序执行。这两个函数官方只推荐在测试环境中使用,对于正式环境不要使用。

变量

变量的是什么?

变量指定了某存储单元(Memory Location)的名称,该存储单元会存储特定类型的值。在 Go 中,有多种语法用于声明变量。

变量会有默认的值

Go 标记

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:

fmt.Println("Hello, World!")

6 个标记是(每行一个):

1. fmt
2. .
3. Println
4. (
5. "Hello, World!"
6. )


行分隔符

在 Go 程序中,一行代表一个语句结束。每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成。

如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

以下为两个语句:

fmt.Println("Hello, World!")

fmt.Println("菜鸟教程:runoob.com")

注释

注释不会被编译,每一个包应该有相关注释。

单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:

// 单行注释
/*
 Author by 菜鸟教程
 我是多行注释
 */


标识符

和java类似

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

以下是有效的标识符:

mahesh   kumar   abc   move_name   a_123
myname50   _temp   j   a23b9   retVal

以下是无效的标识符:

  • 1ab(以数字开头)
  • case(Go 语言的关键字)
  • a+b(运算符是不允许的)

命名规则

Go是一门区分大小写的语言

任何对外暴露的名字必须首字母大写(public),不需要暴露的名字首字母小写(private)

包名称

保持package的名字和目录保持一致,尽量采取有意义的包名。包名应为小写单词,不要使用下划线或者混合大小写。

package dao
package service

文件命名

尽量采取有意义的文件名,简短有意义,应为小写单词,使用下划线分割各个单词

customer_dao.go

结构体命名

采用驼峰命名法,首字母根据访问权限来控制大小写字母。

struct 声明和初始化格式采用多行,

type CustomerOrder struct{
    Name string
    Address string
}
order := CustomerOrder {"tom","北京"}

接口命名

命名规则基本和结构体类似

单个函数的结构名以“er”作为后缀

type Reader interface{
    Read ([]byte)(n int,err error)
}

变量命名

和结构体类似,变量的命名一般驼峰命名,依据访问权限来控制首字母的大小写

var usExist bool
var allCount int

常量命名

常量均需要使用全部大写字母组成,并使用下划线分隔

const APP_URL = "https://www.baidu.com"
//枚举类型
type Scheme string
const (
	HTTP Scheme = "http"
	HTTPS Scheme = "https"
)

错误处理

错误处理的原则就是不能丢弃任何有返回err的调用,必须全部处理。接收到错误,要么返回err,或者使用log记录下来尽早retrurn;一旦有错误发生马上返回,尽量不要使用panic。错误如果是英文小写,不需要标点结尾,采用独立的错误流进行处理

if err!=nil{
    //错误处理
    return
}
//正常代码

单元测试

单元测试文件命名规范应该为example_test.go 测试用的函数必须用Test开头,例如TestExample每一个重要的函数都首先要测试用例,测试和正规代码一起提交方便进行回归测试。

字符串连接

Go 语言的字符串连接可以通过 + 实现:

实例

package main
import "fmt"
func main() {
    fmt.Println("Google" + "Runoob")
}

以上实例输出结果为:

GoogleRunoob


关键字

下面列举了 Go 代码中会使用到的 25 个关键字或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

除了以上介绍的这些关键字,Go 语言还有 36 个预定义标识符:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

程序一般由关键字、常量、变量、运算符、类型和函数组成。

程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。

程序中可能会使用到这些标点符号:.、,、;、: 和 …。


Go 语言的空格

Go 语言中变量的声明必须使用空格隔开,如:

var age int;

语句中适当使用空格能让程序更易阅读。

无空格:

fruit=apples+oranges;

在变量与运算符间加入空格,程序看起来更加美观,如:

fruit = apples + oranges; 


格式化字符串

Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串:

package main
import (
    "fmt"
)
func main() {
   // %d 表示整型数字,%s 表示字符串
    var stockcode=123
    var enddate="2020-12-31"
    var url="Code=%d&endDate=%s"
    var target_url=fmt.Sprintf(url,stockcode,enddate)
    fmt.Println(target_url)
}

输出结果为:

Code=123&endDate=2020-12-31

补充-Go程序结构

整体分析

// 当前程序的包名
package main

// 导入其他包
import . "fmt"

// 常量定义
const PI = 3.14

// 全局变量的声明和赋值
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明
type gopher struct{}

// 接口的声明
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
    Println("Hello World!")
}

Go 程序是通过 package 来组织的。

只有 package 名称为 main 的源码文件可以包含 main 函数。

一个可执行程序有且仅有一个 main 包。

通过 import 关键字来导入其他非 main 包。

可以通过 import 关键字单个导入:

import "fmt"
import "io"

也可以同时导入多个:

import (
    "fmt"
    "math"
)

例如:

package main
import (
    "fmt"
    "math"
)
func main() {
    fmt.Println(math.Exp2(10))  // 1024
}

使用 . 调用:

package 别名:
// 为fmt起别名为fmt2
import fmt2 "fmt"

省略调用(不建议使用):

// 调用的时候只需要Println(),而不需要fmt.Println()
import . "fmt"

前面加个点表示省略调用,那么调用该模块里面的函数,可以不用写模块名称了:

import . "fmt"
func main (){
    Println("hello,world")
}

通过 const 关键字来进行常量的定义。

通过在函数体外部使用 var 关键字来进行全局变量的声明和赋值。

通过 type 关键字来进行结构(struct)和接口(interface)的声明。

通过 func 关键字来进行函数的声明。

可见性规则

Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。

函数名首字母小写即为 private :

func getId() {}

函数名首字母大写即为 public :

func Printf() {}

包的分析-补充

Go 语言的包引入一般为: 项目名/包名

import "test/controllers"

方法的调用为: 包名.方法名()

controllers.Test()

本包内方法名可为小写,包外调用方法名首字母必须为大写。

fmt包详解-输入输出

Golang fmt 包

Print() 函数将参数列表 a 中的各个参数转换为字符串并写入到标准输出中。

非字符串参数之间会添加空格,返回写入的字节数。

func Print(a ...interface{}) (n int, err error)

Println() 函数功能类似 Print,只不过最后会添加一个换行符。

所有参数之间会添加空格,返回写入的字节数。

func Println(a ...interface{}) (n int, err error)

Printf() 函数将参数列表 a 填写到格式字符串 format 的占位符中。

填写后的结果写入到标准输出中,返回写入的字节数。

func Printf(format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过将转换结果写入到 w 中。

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过将转换结果以字符串形式返回。

func Sprint(a ...interface{}) string
func Sprintln(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string

以下函数功能同 Sprintf() 函数,只不过结果字符串被包装成了 error 类型。

func Errorf(format string, a ...interface{}) error

实例:

func main() {
    fmt.Print("a", "b", 1, 2, 3, "c", "d", "\n")
    fmt.Println("a", "b", 1, 2, 3, "c", "d")
    fmt.Printf("ab %d %d %d cd\n", 1, 2, 3)
    // ab1 2 3cd
    // a b 1 2 3 c d
    // ab 1 2 3 cd

    if err := percent(30, 70, 90, 160); err != nil {
        fmt.Println(err)
    }
    // 30%
    // 70%
    // 90%
    // 数值 160 超出范围(100)
}

func percent(i ...int) error {
    for _, n := range i {
        if n > 100 {
            return fmt.Errorf("数值 %d 超出范围(100)", n)
        }
        fmt.Print(n, "%\n")
    }
    return nil
}

Formatter 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要格式化该类型的变量时,会调用其 Format 方法。

type Formatter interface {
    // f 用于获取占位符的旗标、宽度、精度等信息,也用于输出格式化的结果
    // c 是占位符中的动词
    Format(f State, c rune)
}

由格式化器(Print 之类的函数)实现,用于给自定义格式化过程提供信息:

type State interface {
    // Formatter 通过 Write 方法将格式化结果写入格式化器中,以便输出。
    Write(b []byte) (ret int, err error)
    // Formatter 通过 Width 方法获取占位符中的宽度信息及其是否被设置。
    Width() (wid int, ok bool)
    // Formatter 通过 Precision 方法获取占位符中的精度信息及其是否被设置。
    Precision() (prec int, ok bool)
    // Formatter 通过 Flag 方法获取占位符中的旗标[+- 0#]是否被设置。
    Flag(c int) bool
}

Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的字符串格式时就会调用其 String 方法。

type Stringer interface {
    String() string
}

Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的 Go 语法字符串(%#v)时就会调用其 String 方法。

type GoStringer interface {
    GoString() string
}

实例:

type Ustr string

func (us Ustr) String() string {
    return strings.ToUpper(string(us))
}

func (us Ustr) GoString() string {
    return `"` + strings.ToUpper(string(us)) + `"`
}

func (u Ustr) Format(f fmt.State, c rune) {
    write := func(s string) {
        f.Write([]byte(s))
    }
    switch c {
    case 'm', 'M':
        write("旗标:[")
        for s := "+- 0#"; len(s) > 0; s = s[1:] {
            if f.Flag(int(s[0])) {
                write(s[:1])
            }
        }
        write("]")
        if v, ok := f.Width(); ok {
            write(" | 宽度:" + strconv.FormatInt(int64(v), 10))
        }
        if v, ok := f.Precision(); ok {
            write(" | 精度:" + strconv.FormatInt(int64(v), 10))
        }
    case 's', 'v': // 如果使用 Format 函数,则必须自己处理所有格式,包括 %#v
        if c == 'v' && f.Flag('#') {
            write(u.GoString())
        } else {
            write(u.String())
        }
    default: // 如果使用 Format 函数,则必须自己处理默认输出
        write("无效格式:" + string(c))
    }
}

func main() {
    u := Ustr("Hello World!")
    // "-" 标记和 "0" 标记不能同时存在
    fmt.Printf("%-+ 0#8.5m\n", u) // 旗标:[+- #] | 宽度:8 | 精度:5
    fmt.Printf("%+ 0#8.5M\n", u)  // 旗标:[+ 0#] | 宽度:8 | 精度:5
    fmt.Println(u)                // HELLO WORLD!
    fmt.Printf("%s\n", u)         // HELLO WORLD!
    fmt.Printf("%#v\n", u)        // "HELLO WORLD!"
    fmt.Printf("%d\n", u)         // 无效格式:d
}

Scan 从标准输入中读取数据,并将数据用空白分割并解析后存入 a 提供的变量中(换行符会被当作空白处理),变量必须以指针传入。

当读到 EOF 或所有变量都填写完毕则停止扫描。

返回成功解析的参数数量。

func Scan(a ...interface{}) (n int, err error)

Scanln 和 Scan 类似,只不过遇到换行符就停止扫描。

func Scanln(a ...interface{}) (n int, err error)

Scanf 从标准输入中读取数据,并根据格式字符串 format 对数据进行解析,将解析结果存入参数 a 所提供的变量中,变量必须以指针传入。

输入端的换行符必须和 format 中的换行符相对应(如果格式字符串中有换行符,则输入端必须输入相应的换行符)。

占位符 %c 总是匹配下一个字符,包括空白,比如空格符、制表符、换行符。

返回成功解析的参数数量。

func Scanf(format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过从 r 中读取数据。

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过从 str 中读取数据。

func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

实例:

// 对于 Scan 而言,回车视为空白
func main() {
    a, b, c := "", 0, false
    fmt.Scan(&a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 abc 1 回车 true 回车
    // 结果 abc 1 true
}

// 对于 Scanln 而言,回车结束扫描
func main() {
    a, b, c := "", 0, false
    fmt.Scanln(&a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 abc 1 true 回车
    // 结果 abc 1 true
}

// 格式字符串可以指定宽度
func main() {
    a, b, c := "", 0, false
    fmt.Scanf("%4s%d%t", &a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 1234567true 回车
    // 结果 1234 567 true
}

Scanner 由自定义类型实现,用于实现该类型的自定义扫描过程。

当扫描器需要解析该类型的数据时,会调用其 Scan 方法。

type Scanner interface {
    // state 用于获取占位符中的宽度信息,也用于从扫描器中读取数据进行解析。
    // verb 是占位符中的动词
    Scan(state ScanState, verb rune) error
}

由扫描器(Scan 之类的函数)实现,用于给自定义扫描过程提供数据和信息。

type ScanState interface {
    // ReadRune 从扫描器中读取一个字符,如果用在 Scanln 类的扫描器中,
    // 则该方法会在读到第一个换行符之后或读到指定宽度之后返回 EOF。
    // 返回“读取的字符”和“字符编码所占用的字节数”
    ReadRune() (r rune, size int, err error)
    // UnreadRune 撤消最后一次的 ReadRune 操作,
    // 使下次的 ReadRune 操作得到与前一次 ReadRune 相同的结果。
    UnreadRune() error
    // SkipSpace 为 Scan 方法提供跳过开头空白的能力。
    // 根据扫描器的不同(Scan 或 Scanln)决定是否跳过换行符。
    SkipSpace()
    // Token 用于从扫描器中读取符合要求的字符串,
    // Token 从扫描器中读取连续的符合 f(c) 的字符 c,准备解析。
    // 如果 f 为 nil,则使用 !unicode.IsSpace(c) 代替 f(c)。
    // skipSpace:是否跳过开头的连续空白。返回读取到的数据。
    // 注意:token 指向共享的数据,下次的 Token 操作可能会覆盖本次的结果。
    Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
    // Width 返回占位符中的宽度值以及宽度值是否被设置
    Width() (wid int, ok bool)
    // 因为上面实现了 ReadRune 方法,所以 Read 方法永远不应该被调用。
    // 一个好的 ScanState 应该让 Read 直接返回相应的错误信息。
    Read(buf []byte) (n int, err error)
}

实例:

type Ustr string

func (u *Ustr) Scan(state fmt.ScanState, verb rune) (err error) {
    var s []byte
    switch verb {
    case 'S':
        s, err = state.Token(true, func(c rune) bool { return 'A' <= c && c <= 'Z' })
        if err != nil {
            return
        }
    case 's', 'v':
        s, err = state.Token(true, func(c rune) bool { return 'a' <= c && c <= 'z' })
        if err != nil {
            return
        }
    default:
        return fmt.Errorf("无效格式:%c", verb)
    }
    *u = Ustr(s)
    return nil
}

func main() {
    var a, b, c, d, e Ustr
    n, err := fmt.Scanf("%3S%S%3s%2v%x", &a, &b, &c, &d, &e)
    fmt.Println(a, b, c, d, e)
    fmt.Println(n, err)
    // 在终端执行后,输入 ABCDEFGabcdefg 回车
    // 结果:
    // ABC DEFG abc de
    // 4 无效格式:x
}

Go数据类型

值类型

    bool
    int(32 or 64), int8, int16, int32, int64
    uint(32 or 64), uint8(byte), uint16, uint32, uint64
    float32, float64
    string
    complex64, complex128
    array    -- 固定长度的数组

引用类型

  slice   -- 序列数组(最常用)
    map     -- 映射
    chan    -- 管道

内置函数

    append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
    close           -- 主要用来关闭channel
    delete            -- 从map中删除key对应的value
    panic            -- 停止常规的goroutine  (panicrecover:用来做错误处理)
    recover         -- 允许程序定义goroutine的panic动作
    real            -- 返回complex的实部   (complexreal imag:用于创建和操作复数)
    imag            -- 返回complex的虚部
    make            -- 用来分配内存,返回Type本身(只能应用于slice, map, channel)
    new                -- 用来分配内存,主要用来分配值类型,比如intstruct。返回指向Type的指针
    cap                -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 mapcopy            -- 用于复制和连接slice,返回复制的数目
    len                -- 来求长度,比如string、array、slice、map、channel ,返回长度
    printprintln     -- 底层打印函数,在部署环境中建议使用 fmt 包

内置接口error

    type error interface { //只要实现了Error()函数,返回值为String的都实现了err接口

            Error()    String

    }
序号 类型和描述
1 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

数字类型

int类型是计算最快的类型

Go 也有基于架构的类型,例如:int、uint 和 uintptr。

整型分为以下两个大类: 按长度分为:int8int16int32int64对应的无符号整型:uint8uint16uint32uint64

其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。

序号 类型和描述
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点型

**Go语言支持两种浮点型数:float32float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为3.4e38,可以使用常量定义:math.MaxFloat32。 float64 的浮点数的最大范围约为 1.8e308,可以使用一个常量定义:math.MaxFloat64。 **

序号 类型和描述
1 float32 IEEE-754 32位浮点型数
2 float64 IEEE-754 64位浮点型数
3 complex64 32 位实数和虚数
4 complex128 64 位实数和虚数

其他数字类型

以下列出了其他更多的数字类型:

复数类型

complex64complex128

复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。

序号 类型和描述
1 byte 类似 uint8
2 rune 类似 int32
3 uint 32 或 64 位
4 int 与 uint 一样大小
5 uintptr 无符号整型,用于存放一个指针

字符串

Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8编码。 字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符,例如:

s1 := "hello"
s2 := "你好"

转义字符

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。

转义 含义
\r 回车符(返回行首)
\n 换行符(直接跳到下一行的同列位置)
\t 制表符
' 单引号
" 双引号
\ 反斜杠

例子:

package main
import (
    "fmt"
)
func main() {
    fmt.Println("str := \"c:\\pprof\\main.exe\"")
}

多行字符串

多行字符串

Go语言中要定义一个多行字符串时,就必须使用反引号字符:

    s1 := `第一行
    第二行
    第三行
    `
    fmt.Println(s1)

反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。

字符串的操作

符串的常用操作

方法 介绍
len(str) 求长度
+或fmt.Sprintf 拼接字符串
strings.Split 分割字符串
strings.Contains 判断是否包含
strings.HasPrefix,strings.HasSuffix 前缀/后缀判断
strings.Index(),strings.LastIndex() 子串出现的位置
strings.Join(a[]string, sep string) join操作-连接数组安装sep

注释:

  • join -- Join连接其第一个参数的元素以创建单个字符串。分隔符字符串sep放置在结果字符串中的元素之间。

byte和rune类型

遍历尽量使用range遍历

组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:

    var a := '中'

    var b := 'x'

Go 语言的字符有以下两种:

    uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。

    rune类型,代表一个 UTF-8字符。

当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型实际是一个int32。 Go 使用了特殊的 rune 类型来处理 Unicode,让基于 Unicode的文本处理更为方便,也可以使用 byte 型进行默认字符串处理,性能和扩展性都有照顾

    // 遍历字符串
    func traversalString() {
        s := "pprof.cn博客"
        for i := 0; i < len(s); i++ { //byte
            fmt.Printf("%v(%c) ", s[i], s[i])
        }
        fmt.Println()
        for _, r := range s { //rune
            fmt.Printf("%v(%c) ", r, r)
        }
        fmt.Println()
    }

输出:

    112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
    112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)

因为UTF8编码下一个中文汉字由3~4个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。

字符串底层是一个byte数组,所以可以和[]byte类型相互转换。字符串是不能修改的 字符串是由byte字节组成,所以字符串的长度是byte字节的长度。 rune类型用来表示utf8字符,一个rune字符由一个或多个byte组成。

修改字符串

要修改字符串,需要先将其转换成[]rune或[]byte,完成后再转换为string。无论哪种转换,都会重新分配内存,并复制字节数组。

    func changeString() {
        s1 := "hello"
        // 强制类型转换
        byteS1 := []byte(s1)
        byteS1[0] = 'H'
        fmt.Println(string(byteS1))

        s2 := "博客"
        runeS2 := []rune(s2)
        runeS2[0] = '狗'
        fmt.Println(string(runeS2))
    }

类型转换

Go语言中只有强制类型转换,没有隐式类型转换。该语法只能在两个类型之间支持相互转换的时候使用。

强制类型转换的基本语法如下:

    T(表达式)
Copypackage main

import (  
    "fmt"
)

func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + j //不允许 int + float64
    fmt.Println(sum)
}

上面的代码在 C 语言中是完全合法的,然而在 Go 中,却是行不通的。i 的类型是 int ,而 j 的类型是 float64 ,我们正试图把两个不同类型的数相加,Go 不允许这样的操作。如果运行程序,你会得到 main.go:10: invalid operation: i + j (mismatched types int and float64)

要修复这个错误,i 和 j 应该是相同的类型。在这里,我们把 j 转换为 int 类型。把 v 转换为 T 类型的语法是 T(v)。

Copypackage main

import (  
    "fmt"
)

func main() {  
    i := 55      //int
    j := 67.8    //float64
    sum := i + int(j) //j is converted to int
    fmt.Println(sum)
}

现在,当你运行上面的程序时,会看见输出 122

赋值的情况也是如此。把一个变量赋值给另一个不同类型的变量,需要显式的类型转换。下面程序说明了这一点。

Copypackage main

import (  
    "fmt"
)

func main() {  
    i := 10
    var j float64 = float64(i) // 若没有显式转换,该语句会报错
    fmt.Println("j", j)
}

在第 9 行,i 转换为 float64 类型,接下来赋值给 j。如果不进行类型转换,当你试图把 i 赋值给 j 时,编译器会抛出错误。

补充-理解类型

自动判断类型位数

go 1.9版本对于数字类型,无需定义int及float32、float64,系统会自动识别。

package main
import "fmt"

func main() {
   var a = 1.5
   var b =2
   fmt.Println(a,b)
}

运行结果

# go run int.go
1.5 2

默认的布尔类型

在 Go 中,布尔值的类型为 bool,值是 true 或 false,默认为 false。

//示例代码
var isActive bool  // 全局变量声明
var enabled, disabled = true, false  // 忽略类型的声明
func test() {
    var available bool  // 一般声明
    valid := false      // 简短声明
    available = true    // 赋值操作
}

去掉字符串前的空格和换行符号

字符串去除空格和换行符

package main  
  
import (  
    "fmt"  
    "strings"  
)  
  
func main() {  
    str := "这里是 www\n.runoob\n.com"  
    fmt.Println("-------- 原字符串 ----------")  
    fmt.Println(str)  
    // 去除空格  
    str = strings.Replace(str, " ", "", -1)  
    // 去除换行符  
    str = strings.Replace(str, "\n", "", -1)  
    fmt.Println("-------- 去除空格与换行后 ----------")  
    fmt.Println(str)  
}

输出结果为:

-------- 原字符串 ----------
这里是 www
.runoob
.com
-------- 去除空格与换行后 ----------
这里是www.runoob.com

Go语言的变量

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始成其他默认类型值。

类型 默认值
整形和浮点型 0
字符串 “ ”
布尔类型 false
切边、函数、指针 nil

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

声明变量的一般形式是使用 var 关键字:

var identifier type

可以一次声明多个变量:

var identifier1, identifier2 type
package main
import "fmt"
func main() {
    var a string = "Runoob"
    fmt.Println(a)
    var b, c int = 1, 2
    fmt.Println(b, c)
}

以上实例输出结果为:

Runoob
1 2

变量声明

  • 指定变量类型,如果没有初始化,则变量默值
类型 默认值
数值类型(包括complex64/128) 0
布尔类型 false
字符串 “”
*int、[]int、map[string]int、chan int 、func(string)int、error//error是接口 nil
  • 根据值自行判断变量的类型
var v_name = value
  • 如果变量已经使用var声明过了,再使用:=声明变量就会产生编译错误
var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明

intVal :=1 相当于

var intVal int
intVal = 1

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误
// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

值类型和引用类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:

你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。

值类型变量的值存储在堆中。

内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。

更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

4.4.2_fig4.3

这个内存地址称之为指针,这个指针实际上也被存在另外的某一个值中。

同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

简短形式,使用 := 赋值操作符

我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50 或 b := false。

a 和 b 的类型(int 和 bool)将由编译器自动推断。

这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

注意事项

如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。

如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a。

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:

实例

package main
import "fmt"
func main() {
   var a string = "abc"
   fmt.Println("hello, world")
}

尝试编译这段代码将得到错误 a declared but not used

此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用

fmt.Println("hello, world", a)

会移除错误。

但是全局变量是允许声明但不使用的。 同一类型的多个变量可以声明在同一行,如:

var a, b, c int

多变量可以在同一行进行赋值,如:

var a, b int
var c string
a, b, c = 5, 7, "abc"

上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:

a, b, c := 5, 7, "abc"

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。

这被称为 并行 或 同时 赋值。

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

补充-空白的标识符

空白标识符在函数返回值时的使用:

package main

import "fmt"

func main() {
  _,numb,strs := numbers() //只获取函数返回值的后两个
  fmt.Println(numb,strs)
}

//一个可以返回多个值的函数
func numbers()(int,int,string){
  a , b , c := 1 , 2 , "str"
  return a,b,c
}

输出结果:

2 str

Go语言常量

常量,就是在程序编译阶段就确定下来的值,而在程序运行时则无法改变该值。在Go语言中,常量可以是数值类型(包括整形、浮点型和复数类型)、布尔类型、字符串类型等。

常量来定义枚举类型

	const (
		DAY  = 1
		DATE = 2
	)

也可以用iota来简化赋值

	const (
		DAY  = iota //0
		DATE        //1
		time        //2
	)

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"
const c_name1, c_name2 = value1, value2

枚举形式:

**常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值 **

  • unsafe.Sizeof() 可以得出所占用的长度
const (
    Unknown = 0
    Female = 1
    Male = 2
)

iota

类似一个占位值

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)

例子

package main
import "fmt"
const (
    i=1<<iota
    j=3<<iota
    k
    l
)
func main() {
    fmt.Println("i=",i)
    fmt.Println("j=",j)
    fmt.Println("k=",k)
    fmt.Println("l=",l)
}
package main

import "fmt"

func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}

再看个有趣的的 iota 实例:

当你想跳过某一个值时,可以用_来跳过

const (
	a1=iota
	a2=iota
	_
	a3=iota
)

实例

package main

import "fmt"

const (

    i=1<<iota

    j=3<<iota

    k

    l

)

func main() {

    fmt.Println("i=",i)

    fmt.Println("j=",j)

    fmt.Println("k=",k)

    fmt.Println("l=",l)

}

以上实例运行结果为:

i= 1
j= 6
k= 12
l= 24

iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。

简单表述:

  • i=1:左移 0 位,不变仍为 1。
  • j=3:左移 1 位,变为二进制 110,即 6。
  • k=3:左移 2 位,变为二进制 1100,即 12。
  • l=3:左移 3 位,变为二进制 11000,即 24。

注:<<n==*(2^n)。

补充-常量的解析

分析函数unsafe.Sizeof(...)

a = "hello"
unsafe.Sizeof(a)

输出结果为:16

字符串类型在 go 里是个结构, 包含指向底层数组的指针长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。

定义常量时不提供初始值,使用上一行的表达式

在定义常量组时,如果不提供初始值,则表示将使用上行的表达式。

package main

import "fmt"

const (
    a = 1
    b
    c
    d
)

func main() {
    fmt.Println(a)
    // b、c、d没有初始化,使用上一行(即a)的值
    fmt.Println(b)   // 输出1
    fmt.Println(c)   // 输出1
    fmt.Println(d)   // 输出1
}

解析iota

iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。

package main

const (
    i = iota
    j = iota
    x = iota
)
const xx = iota
const yy = iota
func main(){
    println(i, j, x, xx, yy)
}

// 输出是 0 1 2 0 0

移位运算符

左移运算符 << 是双目运算符。左移 n 位就是乘以 2 的 n 次方。 其功能把 << 左边的运算数的各二进位全部左移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补 0。

右移运算符 >> 是双目运算符。右移 n 位就是除以 2 的 n 次方。 其功能是把 >> 左边的运算数的各二进位全部右移若干位, >> 右边的数指定移动的位数。

Go语言运算符

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。

运算符 描述 实例
+ 相加 A + B 输出结果 30
- 相减 A - B 输出结果 -10
* 相乘 A * B 输出结果 200
/ 相除 B / A 输出结果 2
% 求余 B % A 输出结果 0
++ 自增 A++ 输出结果 11
-- 自减 A-- 输出结果 9

关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。

运算符 描述 实例
== 检查两个值是否相等,如果相等返回 True 否则返回 False。 (A == B) 为 False
!= 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 (A != B) 为 True
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 (A > B) 为 False
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 (A < B) 为 True
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 (A >= B) 为 False
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 (A <= B) 为 True

逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

运算符 描述 实例
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False
|| 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 (A || B) 为 True
! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True

位运算符

位运算符对整数在内存中的二进制位进行操作。

下表列出了位运算符 &, |, 和 ^ 的计算:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

运算符 描述 实例
& 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。 (A & B) 结果为 12, 二进制为 0000 1100
| 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或 (A | B) 结果为 61, 二进制为 0011 1101
^ 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (A ^ B) 结果为 49, 二进制为 0011 0001
<< 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 A << 2 结果为 240 ,二进制为 1111 0000
>> 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 A >> 2 结果为 15 ,二进制为 0000 1111

例子

package main

import "fmt"

func main() {

   var a uint = 60      /* 60 = 0011 1100 */  
   var b uint = 13      /* 13 = 0000 1101 */
   var c uint = 0          

   c = a & b       /* 12 = 0000 1100 */
   fmt.Printf("第一行 - c 的值为 %d\n", c )

   c = a | b       /* 61 = 0011 1101 */
   fmt.Printf("第二行 - c 的值为 %d\n", c )

   c = a ^ b       /* 49 = 0011 0001 */
   fmt.Printf("第三行 - c 的值为 %d\n", c )

   c = a << 2     /* 240 = 1111 0000 */
   fmt.Printf("第四行 - c 的值为 %d\n", c )

   c = a >> 2     /* 15 = 0000 1111 */
   fmt.Printf("第五行 - c 的值为 %d\n", c )
}

赋值运算符

下表列出了所有Go语言的赋值运算符。

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 C = A + B 将 A + B 表达式结果赋值给 C
+= 相加后再赋值 C += A 等于 C = C + A
-= 相减后再赋值 C -= A 等于 C = C - A
*= 相乘后再赋值 C *= A 等于 C = C * A
/= 相除后再赋值 C /= A 等于 C = C / A
%= 求余后再赋值 C %= A 等于 C = C % A
<<= 左移后赋值 C <<= 2 等于 C = C << 2
>>= 右移后赋值 C >>= 2 等于 C = C >> 2
&= 按位与后赋值 C &= 2 等于 C = C & 2
^= 按位异或后赋值 C ^= 2 等于 C = C ^ 2
|= 按位或后赋值 C |= 2 等于 C = C | 2

其他运算符

下表列出了Go语言的其他运算符。

运算符 描述 实例
& 返回变量存储地址 &a; 将给出变量的实际地址。
* 指针变量。 *a; 是一个指针变量

运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级 运算符
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

补充-指针的使用

*和&的区别

指针变量 * 和地址值 & 的区别:指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字。

func main() {
   var a int = 4
   var ptr *int
   ptr = &a
   println("a的值为", a);    // 4
   println("*ptr为", *ptr);  // 4
   println("ptr为", ptr);    // 824633794744
}

自增自减

Go 的自增,自减只能作为表达式使用,而不能用于赋值语句。

a++ // 这是允许的,类似 a = a + 1,结果与 a++ 相同
a-- //与 a++ 相似
a = a++ // 这是不允许的,会出现编译错误 syntax error: unexpected ++ at end of statement

使用指针变量的区别与否

使用指针变量与不使用的区别:

func main(){
    var a int = 4
    var ptr int
    ptr = a 
    fmt.Println(ptr)//4
    a = 15
    fmt.Println(ptr)//4
    
    var b int = 5 
    var ptr1 *int
    ptr1 = &b 
    fmt.Println(*ptr1)//5
    b=15 
    fmt.Println(*ptr1)//15
}

Go语言的作用域

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

局部变量

package main

import "fmt"

func main() {
   /* 声明局部变量 */
   var a, b, c int

   /* 初始化参数 */
   a = 10
   b = 20
   c = a + b

   fmt.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)
}

全局变量

package main

import "fmt"

/* 声明全局变量 */
var g int

func main() {

   /* 声明局部变量 */
   var a, b int

   /* 初始化参数 */
   a = 10
   b = 20
   g = a + b

   fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)
}

**Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。 **

例子:

package main

import "fmt"

/* 声明全局变量 */
var g int = 20

func main() {
   /* 声明局部变量 */
   var g int = 10

   fmt.Printf ("结果: g = %d\n",  g)
}

形式参数

package main

import "fmt"

/* 声明全局变量 */
var a int = 20;

func main() {
   /* main 函数中声明局部变量 */
   var a int = 10
   var b int = 20
   var c int = 0

   fmt.Printf("main()函数中 a = %d\n",  a);
   c = sum( a, b);
   fmt.Printf("main()函数中 c = %d\n",  c);
}

/* 函数定义-两数相加 */
func sum(a, b int) int {
   fmt.Printf("sum() 函数中 a = %d\n",  a);
   fmt.Printf("sum() 函数中 b = %d\n",  b);

   return a + b;
}

初始化局部和全局变量

不同类型的局部和全局变量默认值为:

数据类型 初始化默认值
int 0
float32 0
pointer nil

比较学习的例子:

形参和实参的比较

package main

import "fmt"

/* 声明全局变量 */
var a int = 20

func main() {
    /* main 函数中声明局部变量 */
    var a int = 10
    var b int = 20
    var c int = 0

    fmt.Printf("main()函数中 a = %d\n", a)
    c = sum(a, b)
    fmt.Printf("main()函数中 a = %d\n", a)
    fmt.Printf("main()函数中 c = %d\n", c)
}

/* 函数定义-两数相加 */
func sum(a, b int) int {
    a = a + 1
    fmt.Printf("sum() 函数中 a = %d\n", a)
    fmt.Printf("sum() 函数中 b = %d\n", b)
    return a + b
}
main()函数中 a = 10
sum() 函数中 a = 11
sum() 函数中 b = 20
main()函数中 a = 10
main()函数中 c = 31

循环使用中的形参--没有操作地址->不会改变值

package main

import "fmt"

func main(){
  var a int = 0
  fmt.Println("for start")
  for a:=0; a < 10; a++ {
    fmt.Println(a)
  }
  fmt.Println("for end")

  fmt.Println(a)
}
for start
0
1
2
3
4
5
6
7
8
9
for end
0

Go语言数组

简介

  1. 数组:是同一种数据类型的固定长度的序列。
  2. 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
  3. 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。
  4. 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
    for i := 0; i < len(a); i++ {
    }
    for index, v := range a {
    }
  5. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
  6. 数组是值类型赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
    7.支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
    8.指针数组 [n]*T,数组指针 *[n]

Go 语言提供了数组类型的数据结构。

数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。

相对于去声明 number0, number1, ..., number99 的变量,使用数组形式 numbers[0], numbers[1] ..., numbers[99] 更加方便且易于扩展。

数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。

img

声明数组

var variable_name [SIZE] variable_type

例子:

var balance [10] float32

使用数组

    全局:
    var arr0 [5]int = [5]int{1, 2, 3}
    var arr1 = [5]int{1, 2, 3, 4, 5}
    var arr2 = [...]int{1, 2, 3, 4, 5, 6}
    var str = [5]string{3: "hello world", 4: "tom"}
    局部:
    a := [3]int{1, 2}           // 未初始化元素值为 0。
    b := [...]int{1, 2, 3, 4}   // 通过初始化值确定数组长度。
    c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
    d := [...]struct {
        name string
        age  uint8
    }{
        {"user1", 10}, // 可省略元素类型。
        {"user2", 20}, // 别忘了最后一行的逗号。
    }

初始化数组

以下演示了数组初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

我们也可以通过字面量在声明数组的同时快速初始化数组:

balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

初始化数组中 {} 中的元素个数不能大于 [] 中的数字。

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

 balance[4] = 50.0

访问数组

数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。例如:

var salary float32 = balance[9]

以上实例读取了数组 balance 第 10 个元素的值。

package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,j int

   /* 为数组 n 初始化元素 */        
   for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}

多维数组

 全局
    var arr0 [5][3]int
    var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
    局部:
    a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。

Go 语言支持多维数组,以下为常用的多维数组声明方式:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

以下实例声明了三维的整型数组:

var threedim [5][10][4]int

二维数组

二维数组是最简单的多维数组,二维数组本质上是由一维数组组成的。二维数组定义方式如下:

var arrayName [ x ][ y ] variable_type

variable_type 为 Go 语言的数据类型,arrayName 为数组名,二维数组可认为是一个表格,x 为行,y 为列,下图演示了一个二维数组 a 为三行四列:

package main

import "fmt"

func main() {
    // Step 1: 创建数组
    values := [][]int{}

    // Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
    row1 := []int{1, 2, 3}
    row2 := []int{4, 5, 6}
    values = append(values, row1)
    values = append(values, row2)

    // Step 3: 显示两行数据
    fmt.Println("Row 1")
    fmt.Println(values[0])
    fmt.Println("Row 2")
    fmt.Println(values[1])

    // Step 4: 访问第一个元素
    fmt.Println("第一个元素为:")
    fmt.Println(values[0][0])
}

内置函数 len 和 cap 都返回数组长度 (元素数量)。

数组拷贝和传参

package main

import "fmt"

func printArr(arr *[5]int) {
    arr[0] = 10
    for i, v := range arr {
        fmt.Println(i, v)
    }
}

func main() {
    var arr1 [5]int
    printArr(&arr1)
    fmt.Println(arr1)
    arr2 := [...]int{2, 4, 6, 8, 10}
    printArr(&arr2)
    fmt.Println(arr2)
}

补充-数组的例子

求随机数字之和

package main

import (
    "fmt"
    "math/rand"
    "time"
)

// 求元素和
func sumArr(a [10]int) int {
    var sum int = 0
    for i := 0; i < len(a); i++ {
        sum += a[i]
    }
    return sum
}

func main() {
    // 若想做一个真正的随机数,要种子
    // seed()种子默认是1
    //rand.Seed(1)
    rand.Seed(time.Now().Unix())

    var b [10]int
    for i := 0; i < len(b); i++ {
        // 产生一个0到1000随机数
        b[i] = rand.Intn(1000)
    }
    sum := sumArr(b)
    fmt.Printf("sum=%d\n", sum)
}

杨辉三角

package main

import "fmt"

func main() {
   yanghuisanjiao(12)
}

func yanghuisanjiao(rows int) {
   for i := 0; i < rows; i++ {
      number := 1;
      for k := 0; k < rows-i; k++ {
         fmt.Print("  ")
      }
      for j := 0; j <= i; j++ {
         fmt.Printf("%5d", number)
         number = number * (i - j) / (j + 1);
      }
      fmt.Println()
   }
}

打印结果:

                            1
                          1    1
                        1    2    1
                      1    3    3    1
                    1    4    6    4    1
                  1    5   10   10    5    1
                1    6   15   20   15    6    1
              1    7   21   35   35   21    7    1
            1    8   28   56   70   56   28    8    1
          1    9   36   84  126  126   84   36    9    1
        1   10   45  120  210  252  210  120   45   10    1
      1   11   55  165  330  462  462  330  165   55   11    1

posted on   Steam残酷  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示