4. 有一段以下代码能够对数组正确初始化:int[] arr = new int[5]; 这表示,这个数组的元素的类型是_?

Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷。如果你刚从其他语言转到 Go,那这篇文章里的坑多半会踩到。

如果花时间学习官方 doc、wiki、讨论邮件列表、 Rob Pike 的大量文章以及 Go 的源码,会发现这篇文章中的坑是很常见的,跳过这些坑,能减少大量调试代码的时间。

如果在函数体代码中有未使用的变量,则无法通过编译,不过全局变量声明但不使用是可以的。即使变量声明后为变量赋值,依旧无法通过编译,需在某处使用它:

// 可以直接注释或移除未使用的变量

如果你 import一个包,但包中的变量、函数、接口和结构体一个都没有用到的话,将编译失败。可以使用 _下划线符号作为别名来忽略导入的包,从而避免编译错误,这只会执行 package 的 init()

// 可以使用 goimports 工具来注释或移除未使用到的包

4.简短声明的变量只能在函数内部使用

5.使用简短声明来重复声明变量

不能用简短声明方式来单独为一个变量重复声明,:=左侧至少有一个新变量,才允许多变量的重复声明:

6.不能使用简短声明来设置字段的值

struct 的变量字段不能使用 := 来赋值以使用预定义的变量来避免解决:

对从动态语言转过来的开发者来说,简短声明很好用,这可能会让人误会 := 是一个赋值操作符。如果你在新的代码块中像下边这样误用了 :=,编译不会报错,但是变量不会按你的预期工作:

这是 Go 开发者常犯的错,而且不易被发现。可使用 vet工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 -shadow 选项来启用:

注意 vet 不会报告全部被覆盖的变量,可以使用 go-nyet 来做进一步的检测:

8.显式类型的变量无法使用 nil 来初始化

在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 cap() 来检测分配空间的大小:

对那些喜欢用 nil 初始化字符串的人来说,这就是坑:

12.Array 类型的值作为函数参数

在 C/C++ 中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数内部会改变该数组的值。

在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的:

// 数组使用值拷贝传参
  • 直接传递指向这个数组的指针类型:
// 传址会修改原数据
  • 直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)

与其他编程语言中的 for-in 、foreach 遍历语句不同,Go 中的 range 在遍历时会生成 2 个值,第一个是元素索引,第二个是元素的值:

看起来 Go 支持多维的 array 和 slice,可以创建数组的数组、切片的切片,但其实并不是。

对依赖动态计算多维数组值的应用来说,就性能和复杂度而言,用 Go 实现的效果并不理想。

可以使用原始的一维数组、“独立“ 的切片、“共享底层数组”的切片来创建动态的多维数组。

1.使用原始的一维数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配。

2.使用“独立”的切片分两步:

  • 对每个内部 slice 进行内存分配

  • 注意内部的 slice 相互独立,使得任一内部 slice 增缩都不会影响到其他的 slice

1.使用“共享底层数组”的切片

  • 创建一个存放原始数据的容器 slice
// 等间距切割原始 slice,创建动态多维数组 table

和其他编程语言类似,如果访问了 map 中不存在的 key 则希望能返回 nil,比如在 PHP 中:

Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。

检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:

16.string 类型的值是常量,不可更改

尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。

// 修改字符串的错误示例

注意: 上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4个字节来存储,此时更新其中的一个字节是错误的。

当进行 string 和 byte slice 相互转换时,参与转换的是拷贝的原始值。这种转换的过程,与其他编程语的强制类型转换操作不同,也和新 slice 与旧 slice 共享底层数组不同。

Go 在 string 与 byte slice 相互转换上优化了两点,避免了额外的内存分配:

对字符串用索引访问返回的不是字符,而是一个 byte 值。

string 的值不必是 UTF8 文本,可以包含任意的值。只有字符串是文字字面值时才是 UTF8 文本,字串可以通过转义来包含其他数据。

注意: RuneCountInString 并不总是返回我们看到的字符数,因为有的字符会占用 2 个 rune:

声明语句中 } 折叠到单行后,尾部的 , 不是必需的。

log 标准库提供了不同的日志记录等级,与其他语言的日志库不同,Go 的 log 包在调用 Fatal()、Panic() 时能做更多日志外的事,如中断程序的执行等:

23.对内建数据结构的操作并不是同步的

尽管 Go 本身有大量的特性来支持并发,但并不保证并发的数据安全,用户需自己保证变量等数据以原子操作更新。

range 得到的索引是字符值(Unicode point / rune)第一个字节的位置,与其他编程语言不同,这个索引并不直接是字符在字符串中的位置。

注意一个字符可能占多个 rune,比如法文单词 café 中的 é。操作特殊字符可使用norm 包。

如果你希望以特定的顺序(如按 key 排序)来迭代 map,要注意每次迭代都可能产生不一样的结果。

Go 的运行时是有意打乱迭代顺序的,所以你得到的迭代结果可能不一致。但也并不总会打乱,得到连续相同的 5 个迭代结果也是可能的,如:

如果你去 Go Playground 重复运行上边的代码,输出是不会变的,只有你更新代码它才会重新编译。重新编译后迭代顺序是被打乱的:

不过你可以在 case 代码块末尾使用 fallthrough,强制执行下一个 case 代码块。

也可以改写 case 为多条件判断:

很多编程语言都自带前置后置的 ++、-- 运算。但 Go 特立独行,去掉了前置操作,同时 ++、— 只作为运算符而非表达式。

很多编程语言使用 ~ 作为一元按位取反(NOT)操作符,Go 重用 ^ XOR 操作符来按位取反:

同时 ^ 也是按位异或(XOR)操作符。

除了位清除(bit clear)操作符,Go 也有很多和其他语言一样的位操作符,但优先级另当别论。

以小写字母开头的字段成员是无法被外部直接访问的,所以 struct 在进行 json、xml、gob 等格式的 encode 操作时,这些私有字段会被忽略,导出时得到零值:

程序默认不等所有 goroutine 都执行完才退出,这点需要特别注意:

// 主程序会直接退出

如下,main() 主程序不等两个 goroutine 执行完就直接退出了:

常用解决办法:使用 "WaitGroup" 变量,它会让主程序等待所有 goroutine 执行完毕再退出。

如果你的 goroutine 要做消息的循环处理等耗时操作,可以向它们发送一条 kill 消息来关闭它们。或直接关闭一个它们都等待接收数据的 channel:

看起来好像 goroutine 都执行完了,然而报错:

为什么会发生死锁?goroutine 在退出前调用了 wg.Done() ,程序应该正常退出的。

只有在数据被 receiver 处理时,sender 才会阻塞。因运行环境而异,在 sender 发送完数据后,receiver 的 goroutine 可能没有足够的时间处理下一个数据。如:

从已关闭的 channel 接收数据是安全的:

接收状态值 ok 是 false 时表明 channel 中已没有数据可以接收了。类似的,从有缓冲的 channel 中接收数据,缓存的数据获取完再没有数据可取时,状态值也是 false

在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:

利用这个死锁的特性,可以用在 select 中动态的打开和关闭 case 语句块:

35.若函数 receiver 传参是传值方式,则无法修改参数的原有值

方法 receiver 的参数与一般函数的参数类似:如果声明为值,那方法体得到的是一份参数的值拷贝,此时对参数的任何修改都不会对原有值产生影响。

除非 receiver 参数是 map 或 slice 类型的变量,并且是以指针方式更新 map 中的字段、slice 中的元素的,才会更新原有值:

公司:程序咖(北京)科技有限公司
/ruby0015/冒泡排序、选择排序、顺序查找和二分法查找)
 

1.1 为什么使用数组

如果说程序中,需要存储大量的相同类型的一组数据,如果直接使用变量来进行存储,每个变量只能存储一个值,就需要大量的变量。
 
就是一组相同数据类型的数据。内存上是开辟的连续的内存空间。
1、定长。容量固定。数组一旦创建后,那么长度不能更改。(容量,长度,都是指存储的数量)
2、存储的数据类型必须都一致。
3、在内存中空间连续挨个。
4、数组是引用类型的数据,存在栈和堆的地址引用关系。
Java中:两大数据类型
 引用:数组,对象,集合。。。。
step1:先创建数组
step2:使用数组:存储数据,访问数据。
方式一:数据类型[] 数组名 
方式二:数据类型 数组名[] 
 推荐使用方式一,c#等越来越多的语言已经不支持方式二定义数组

1.6 数组中的默认值

创建好数组后,里面存储了默认的数值。到底存储哪个数值,要看创建的数组是何种类型。

数组的默认值就是数组创建后里面存储的默认的数据
数组的引用存在栈内存中数组本身存在堆内存中
数组创建完就有默认的数据了
 
Java中的数组必须先初始化,然后才可以使用,所谓初始化,就是为数组中的数组元素分配内存空间,并为每个数组元素赋初始值。否则数组中存储的就是默认数值。
初始化时由程序员指定每个数组元素的初始值,由系统计算数组长度
 语法:数组元素类型[] 数组名 = new 数组元素类型[]{元素0,元素1,....};
 可简写为:数组元素类型[] 数组名 = {元素0,元素1,....};
 说明:任何一个变量都得有自己的数据类型,这里的arr表示数组变量名称,int表示数组中元素的类型,int[]才是数组类型
静态初始化:由我们指定元素的初始值,由系统计算长度或者元素的个数
初始化时程序员只指定数组长度,由系统为数组元素分配初始值
 语法:元素类型[] 数组名 = new 元素类型[元素个数或者数组长度];
 系统对初始值分配规则如下:a.整数型为0
 c.字符型为‘\u0000’(不同的系统平台显示结果不同)
 
动态初始化:初始化时由程序员指定数组的长度,系统负责分配元素的初始值 4为常量,存储在常量池中 arr也是在栈空间中开辟空间的 通过new关键字创建出来的数组存储在堆空间中 arr其实就是一个引用,指向了一个真正的数组 a.在初始化数组时不要静态初始化和动态初始化同时使用也就是说不要在进行数组初始化时既指定数组的长度也为每个数组元素分配初始值 b.既然数组也是一种数据类型则在初始化的时候也可以先声明再初始化
动态创建数组:先创建数组,然后再根据下标一个一个存储数据。 A:先声明,再创建(分配内存空间) 数据类型 [] 数组名; //静态创建数组:声明,创建,赋值一起写完。 C:声明,创建,并赋值 数据类型[] 数组名 = {数值1,数值2,数值3,数值4.。。。}; //=左边声明数组,=右边,会先根据{}中数据的个数,然后再将{}中数据,按照顺序存储进去。 2.根据{}中数组值的个数,开辟堆内存 3.将{}中的数组值,依次按照顺序存入数组中 D:声明,创建,并赋值

通过下标访问指定元素,数组中的每个下标,都有编号,顺序,也叫索引,index。从0开始,到个数减1。

比如说一个数组中元素的个数是3个。

那么1这个元素,存储在数组中的第一个位置上,下标为0。 2这个元素,存储在数组中的第二个位置上,下标为1。 3这个元素,则存储在第三个位置上,下标为2。

我们就可以通过数组的名字,结合下标来进行访问数组中的元素:

//使用静态初始化的方式定义一个数组 //数组中可以存放重复数据 //1.访问数组中的元素 //格式:数组名称[下标] 表示获取指定下标所对应的值 //需求:获取下标3对应的元素
//2.修改数组元素的值

 //格式:数组名称[下标] = 被修改之后的值
 //注意:不管是静态初始化还是动态初始化,都可以采用这种方式修改元素的值
 
//3.如果下标超出数组的长度,会下标越界异常
 
数据类型[] 数组名 = new 数据类型[长度、容量、个数]; 数据类型 数组名[] = new 数据类型[长度];//也可以 数组名[index],操作数组 index:因为一个数组存储了多个数据(也叫元素),每个元素都有一个下标,也叫索引,index。理解起来就是给每个数据排个编号,固定从0开始,0,1,2,3,4.。。。到长度减1。

获取数组元素的个数:之前我们知道在访问数组的时候,可以通过下标进行访问,但是如果下标越界就会产生异常。在Java中,所有数组都提供了一个length属性,通过这个属性可以访问到数组的长度或者数组中元素的个数。

//4.获取数组中的元素个数或者数组的长度
 

既然可以通过数组的下标进行访问数组,而下标又是从0开始,逐渐递增1的有规律的一组数值。那么我们是否可以通过循环来进行访问数组?

依次访问数组中的每一个元素,获取每个下标对应的元素值
方式一:简单for循环
方式二:增强for循环
1遍历依次访问数组中每个元素可以赋值可以取值
2因为操作数组就是数组名字配合下标而下标固定都是从0开始到长度减1
 
增强for循环JDK1.5的版本出现的 特定的用法专门用于获取数组中的每一个元素的值 依次获取数组的元素赋值给e Afor-each只能获取数组的数据但是不能给数组进行赋值 Bfor-each在代码这个层面不能操作下标
优点:用于遍历数组和集合,无需通过数组下标,就可以直接访问数组或者集合中的元素 for(元素数据类型 变量名:数组名称) { //底层工作原理:根据下标获取数组元素 两种遍历方式的选择:不需要知道下标,只需要获取元素值,则采用增强for循环 //需求:打印下标为偶数的元素值【只能采用简单for循环】
A:栈,存放的是基本数据类型的变量,以及引用类型变量的引用。 特点:函数中的变量所占用的空间,执行之后就会销毁。 B:堆,存放的是new出来的东西。 特点:执行之后,堆里存储的内容(对象等),会被标记为垃圾,但是不会立即被销毁,而是等待系统的垃圾回收机制来回收(GC)。

数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存空间,只有当这个引用指向有效的空间时,才可以通过引用去操作真正数组中的元素

结论:数组的引用变量存储在栈空间中,而真正的数组存储在堆空间在中。

//使用静态初始化的方式初始化一个数组a //使用动态初始化的方式初始化一个数组b
扩展:基本数据类型和引用数据类型在内存中的区别
 a.寄存器:最快的存储区域,由编译器根据需求进行自动的分配,我们在程序中无法控制
 b.栈:存放的是基本数据类型的变量以及引用数据类型变量的引用
 特点:被执行之后,该函数或者变量所占用的空间会被销毁【方法压栈】
 c.堆:存放所有使用new关键字创建出来的实体
 特点:执行完不会立即被释放,当使用完成之后,会被标记上垃圾的标识,等待系统的垃圾回收机制来回收它
 常量池:存放基本数据类型的常量和字符串常量
 静态域:static,静态全局变量

3.2 数组的地址转移:了解

Java中的数据分为两大类:
基本类型进行赋值:数值
引用类型进行赋值:地址
数组名,存储是引用地址。 int[] c = a;//将a的值赋值给c,就是将a存储的数组的地址赋值给c。a和c存储的地址相同,那么就指向了同一个数组

数组是引用类型:参数传递的时候,传递的是数组的地址。就是参数也会指向这块内存。当方法结束的时候,参数就销毁了。

//设计一个方法,用于打印数组 //设计一个方法,用于打印数组

3.4 数组作为返回值

一个数组可以作为参数,也可以作为返回值。那么返回的实际上是数组的内存地址。

//设计一个方法,用于打印数组 //设计一个方法,用于创建一个数组, 并赋值。数组要返回给调用处 //设计一个方法,用于打印数组

3.5 可变参数【扩展】

概念:一个方法可以接收的参数的数量不定(0-多个),但是类型固定。
语法:数据类型 ... 参数名,可变参数在方法中当数组使用。
 1、如果参数列表中,除了可变参数还有其他的参数,可变参数要写在整个参数列表的最后。
 2、一个方法最多只能有一个可变参数。
//演示不定长参数的使用
 //2.对于一个方法的参数是不定长参数时,实参可以直接传一个数组

 //需求:计算不确定个整数的和
 //1.不定长的参数在进行使用的时候被当做数组来进行处理
 //num其实就相当于一个数组的引用变量
 //3.不定长参数在使用的时候,必须出现在参数列表的最后一个
 //4.在同一个参数列表中,不定长参数只能出现一次
a.不定长的参数在进行使用的时候被当做数组来进行处理
b.不定长参数在使用的时候必须出现在参数列表的最后一个
c.对于一个方法的参数是不定长参数时实参可以直接传一个数组

排序:数组是存储一组数据,而且这些数据是有顺序的。但是数值本身可能是无序的。通过算法来实现给数组进行排序,升序(数值从小到大),降序(数值从大到小)。

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序算法的运作如下:

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

交换过程图示(第一次):

那么我们需要进行n-1次冒泡过程。

//外层循环:控制比较的轮数 //内层循环:控制每一轮比较的次数和参与比较的下标

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

那么我们需要进行n-1次冒泡过程。

//用于标记最小值的下标

数组的查找,就是指在给定的一个数组中,查找指定的数值,返回该数值在数组中的下标位置。但如果数组中包含重复的元素,不保证找到的是哪一个,如果数组中没有指定的元素,一般返回-1。

查找思路:遍历这个数组,依次把每一位元素和要查找的数据进行比较
*顺序查找,在数组arr中,查找key的位置,如果存在返回下标,否则返回-1 //需求:查找65在数组中的位置

二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查数组为有序的数组,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列数组。首先,假设数组中元素是按升序排列,将数组中间位置记录的元素与要查找的数值比较,如果两者相等,则查找成功;否则利用中间位置记录将数组分成前、后两个部分,如果中间位置记录的元素值大于查找的数值,则进一步查找前一部分数组,否则进一步查找后一八月份数组。重复以上过程,直到找到满足条件的记录,使查找成功,或直到部分数组不存在为止,此时查找不成功。

优点:通过折半来缩小查找范围,提高查找效率

缺点:要求数组是有序的。

//下次在左半部分查找 //下次在右半部分查找

Arrays类是jdk提供的操作数组的一个工具类,位于java.util包下。

作用主要用于对数组进行排序查找填充比较等的操作
注意1如果在同一个Java文件中同时使用Scanner和Arrays则可以向如下方式导包
注意2但凡是工具类类中的方法全部是静态的方便调用
 调用语法类名.方法名实参列表
 DArrays提供数组的常规操作的
 
//使用动态初始化的方式初始化一个数组 将指定的 int 值分配给指定 int 型数组的每个元素。 将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。 //Java但凡涉及到区间问题,一般规律:包头不包尾 前闭后开区间 对指定的 int 型数组按数字升序进行排序。 对指定 int 型数组的指定范围按数字升序进行排序。 //注意:默认情况下只能进行升序排序 //前提:必须是排好序的数组 使用二分搜索法来搜索指定的 int 型数组,以获得指定的值。 使用二分搜索法来搜索指定的 int 型数组的范围,以获得指定的值。 //规律:按照原来的顺序将待查找的元素插入到原数组中的下标,对下标加1然后求相反数 复制指定的数组,截取或用 0 填充(如有必要),以使副本具有指定的长度。 将指定数组的指定范围复制到一个新数组。 //5.将数组转换为字符串 返回指定数组内容的字符串表示形式。 如果两个指定的 int 型数组彼此相等,则返回 true。 如果两个指定的 int 型数组彼此相等,则返回 true。 如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。 换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。 此外,如果两个数组引用都为 null,则认为它们是相等的 ==:是一个运算符,可以比较基本数据类型和引用数据类型【当比较引用数据类型的时候,比较的地址值】 equals:是一个方法,只能比较引用数据类型 【补充:如果equals方法在一个类中没有没重新实现,则equals方法的作用和==一样,都比较的是地址值 如果equals方法在一个类中被重新实现,则比较的是内容

数组的拷贝:就是将一个数组的数据,复制到另一个数值中。

方法一通过循环依次复制将原数组的数据一个一个复制到目标数组中
方法二Arrays类里方法copyOf(原数组新数组的长度)-->返回值是新数组
方法三System类里的方法arraycopy(原数组原数组位置新数组新数组位置拷贝的个数)
//方法一:自己通过循环依次复制,吭哧吭哧 * 返回值就是新的数组 * 第一个参数:原始数组 * 第二个参数:从原始数组的哪个下标开始复制 * 第三个参数:目标数组 * 第四个参数:目标数组从哪个下标开始贴 * 第五个参数:拷贝几个数据

七、使用数组时常见的问题

出现的时机:当使用了不存在的下标时,则会出现这个错误 出现的时机:当数组的引用变量赋值为null,还在后面的代码中使用这个引用 3>基本数据类型和引用数据类型打印的区别
//3.基本数据类型和引用数据类型打印的区别 打印引用数据类型的引用变量时,拿到的是地址 15db9742 ----- 十六进制,地址值的哈希编码【哈希算法】

Java并没有真正的多维数组,二维数组可以看成以数组为元素的数组。如:

元素类型[二维数组的长度][一维数组的长度] 说明定义一个数组arr二维数组中一维数组的个数为3个每个一维数组中元素的个数为4个
简化元素类型[][] 数组名称

《JA V A语言程序设计》期末考试试题及答案

1、编译Java Application 源程序文件将产生相应的字节码文件,这些字节码文件的扩展名为( )。

3、不允许作为类及类成员的访问控制符的是( )。

4、为AB类的一个无形式参数无返回值的方法method书写方法头,使得使用类名AB作为前缀就可以调用它,该方法头的形式为( )。

1、开发与运行Java程序需要经过的三个主要步骤为编辑源程序、

编译生成字节码和解释运行字节码。

用2字节内存空间,这样,无论是中文字符还是英文字符,都是占

5、抽象(abstract) 方法是一种仅有方法头,没有具体方法体和操作实现的方法,该方法必须在抽象类之中定义。最终(final)方法是不能被当前类的子类重新定义的方法。

该语句应该放在程序的位置为:应该在程序第一句。

我要回帖

更多关于 以下代码能够对数组正确初始化 的文章

 

随机推荐