《快学 Go 语言》第 4 课 —— 低调的数组
只要掌握了数据结构中的四大法宝,就可以包打天下,他们是:array 、linked list 、hash table、binary tree 。这四大法宝可不是各自为战的,灵活结合才能游刃有余。比如,一个用 hash table 组织的 symbol table,其中个个都是由字符型 array 构成的 linked list 组成的。 --- Go 语言之父 Rob Pike
Go 语言里面的数组其实很不常用,这是因为数组是定长的静态的,一旦定义好长度就无法更改,而且不同长度的数组属于不同的类型,之间不能相互转换相互赋值,用起来多有不方便之处。
切片是动态的数组,是可以扩充内容增加长度的数组。当长度不变时,它用起来就和普通数组一样。当长度不同时,它们也属于相同的类型,之间可以相互赋值。这就决定了数组的应用领域都广泛地被切片取代了。
不过也不可以小瞧数组,在切片的底层实现中,数组是切片的基石,是切片的特殊语法隐藏了内部的细节,让用户不能直接看到内部隐藏的数组。切片不过是数组的一个包装,给顽固的数组装上了灵活的翅膀,让石头也可以展翅飞翔。
仅仅是上面纯文字的说明,读者肯定会感觉很懵。下面让我们来看具体的实例。
数组变量的定义
我们先试一下只申明类型,不赋初值。这时编译器会给数组默认赋上「零值」。数组的零值就是所有内部元素的零值。
package main
import "fmt"
func main() {
var a [9]int
fmt.Println(a)
}
------------
[0 0 0 0 0 0 0 0 0]
下面我们看看另外三种变量定义的形式, 效果都是一样的
package main
import "fmt"
func main() {
var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
c := [8]int{1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
---------------------
[1 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8]
数组的访问
接下来我们使用下标来简单操作一下数组,这个数组里存的是数字的平方值
package main
import "fmt"
func main() {
var squares [9]int
for i := 0; i < len(squares); i++ {
squares[i] = (i + 1) * (i + 1)
}
fmt.Println(squares)
}
--------------------
[1 4 9 16 25 36 49 64 81]
数组的下标越界检查(高阶知识)
上面的代码中我们注意到可以使用内置函数 len() 来直接获取数组的长度。数组的长度是编译期确定的,当我们使用 len() 函数访问数组的长度属性时,编译器在背后偷偷把它替换成了整数值。
package main
import "fmt"
func main() {
var a = [5]int{1,2,3,4,5}
a[101] = 255
fmt.Println(a)
}
-----
./main.go:7:3: invalid array index 101 (out of bounds for 5-element array)
上面的代码运行结果说明了 Go 语言会对数组访问下标越界进行编译器检查。有一个重要的问题是,如果下标是一个变量,Go 是如何检查下标越界呢?变量需要在运行时才可以决定是否越界,Go 是如何办到的呢?
package main
import "fmt"
func main() {
var a = [5]int{1,2,3,4,5}
var b = 101
a[b] = 255
fmt.Println(a)
}
------------
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/Users/qianwp/go/src/github.com/pyloque/practice/main.go:8 +0x3d
exit status 2
答案是 Go 会在编译后的代码中插入下标越界检查的逻辑,所以数组的下标访问效率是要打折扣的,比不得 C 语言的数组访问性能。
数组赋值
同样的子元素类型并且是同样长度的数组才可以相互赋值,否则就是不同的数组类型,不能赋值。数组的赋值本质上是一种浅拷贝操作,赋值的两个数组变量的值不会共享。
package main
import "fmt"
func main() {
var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
var b [9]int
b = a
a[0] = 12345
fmt.Println(a)
fmt.Println(b)
}
--------------------------
[12345 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]
从上面代码的运行结果中可以看出赋值后两个数组并没有共享内部元素。如果数组的长度很大,那么拷贝操作是有一定的开销的,使用的时候一定需要注意。下面我们尝试使用不同长度的数组赋值会有什么结果
package main
import "fmt"
func main() {
var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
var b [10]int
b = a
fmt.Println(b)
}
--------------------------
./main.go:8:4: cannot use a (type [9]int) as type [10]int in assignment
可以看出不同长度的数组之间赋值是禁止的,因为它们属于不同的类型。
数组的遍历
数组除了可以使用下标进行遍历之外,还可以使用 range 关键字来遍历,range 遍历提供了下面两种形式。
package main
import "fmt"
func main() {
var a = [5]int{1,2,3,4,5}
for index := range a {
fmt.Println(index, a[index])
}
for index, value := range a {
fmt.Println(index, value)
}
}
------------
0 1
1 2
2 3
3 4
4 5
0 1
1 2
2 3
3 4
4 5
考虑到切片的内容太多,我们将独立一节专门讲解切片,下一节将是 Go 语言的极有价值的一节,读者一定要努力搞清楚每一个细节。
原文发布时间为: 2018-11-08
本文作者:码洞
本文来自云栖社区合作伙伴“码洞”,了解相关信息可以关注“码洞”。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程
许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存、CPU、缓存等予以说明。实际上,在实际的Java开发工作中,仅仅了解并发编程的创建、启动、管理和通信等基本知识还是不够的。一方面,如果要开发出高效、安全的并发程序,就必须深入Java内存模型和Java虚拟机的工作原理,从底层了解并发编程的实质;更进一步地,在现今大数据的时代,要开发出高并发、高可用、考可靠的分布式应用及各种中间件,更需要深入到计算机工作原理的底层去进行代码开发。 本文尝试以一个较为全面的角度,以Java虚拟机工作原理和Java内存模型为切入,配合一些计算机CPU缓存的知识,深入理解Java多线程开发中的难点,包括线程安全和线程通信等内容。 如果需要先行了解Java并发编程的基础知识,可参考以下随笔: 1.Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future) 2.Java并发编程之线程生命周期、守护线程、优先级、关闭和join、sleep、yield、interrupt...
- 下一篇
Java并发编程之ThreadGroup
ThreadGroup是Java提供的一种对线程进行分组管理的手段,可以对所有线程以组为单位进行操作,如设置优先级、守护线程等。 线程组也有父子的概念,如下图: 线程组的创建 1 public class ThreadGroupCreator { 2 3 public static void main(String[] args) { 4 //获取当前线程的group 5 ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); 6 //在当前线程执行流中新建一个Group1 7 ThreadGroup group1 = new ThreadGroup("Group1"); 8 //Group1的父线程,就是main线程所在Group 9 System.out.println(group1.getParent() == currentGroup); 10 //定义Group2, 指定group1为其父线程 11 ThreadGroup group2 = new ThreadGroup(group1, "Gro...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果