静态检查
冰雹序列
下面的例子,就是根据冰雹序列的规律给出的,根据下面的例子,尝试寻找冰雹序列的规律:
2 ,1
3 ,10, 5 ,16, 8, 4, 2, 1
2n, 2n-1 , … , 4, 2, 1
5, 16, 8, 4, 2, 1
7, 22, 11, 34, 17, 52, 26, 13, 40, …? (会停止吗?)
1
2
3
4
5
计算冰雹序列
下面为计算冰雹序列的java代码:
int n = 3;
while (n != 1) {
System.out.println(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
System.out.println(n);
1
2
3
4
5
6
7
8
9
10
心得
冰雹序列就是根据所给的序列,从正整数n开始,如果n是偶数,则下一个数是n/2,否则下一个数是3n+1,直到n等于1。由于存在3n+1这种变化,所以序列元素的大小可能会忽高忽低——这也是“冰雹序列”名称的来历,冰雹在落地前会在云层中忽上忽下。那么所有的序列都会最终“落地”变到1吗,这个问题值得大家深思。
静态类型
Java是一种静态类型语言。所有变量的类型在编译时(程序运行之前)是已知的,因此编译器也可以推断出所有表达式的类型。例如a和b被声明为int类型,那么编译器就会得出a+b也是int类型的结论。
静态类型是一种特殊的静态检查,可以避免bug的产生。其中静态类型就阻止了一大部分和类型相关的bug——确切点说,就是将操作符用到了不对应的类型对象上。如果你写了一行代码如下:
"5"*"6"
1
那么静态类型检查就会在你编辑代码的时候发现这个bug,而不是等到你编译后运行程序的时候。
静态检查、动态检查、无检查
编程语言通常能提供以下三种自动检查的方法:
静态检查: bug在程序运行前发现
动态检查: bug在程序运行中发现
无检查: 编程语言本身不帮助你发现错误,你必须通过特定的条件(例如输出的结果)检查代码的正确性。
很显然,静态检查的效果远好于动态检查,更好于无检查。
下面分析一下三种检查通常会发现什么bug。
静态检查
语法错误:例如忘记标点符号或者写错关键词。在python中,如果在编程时,有多余的缩进或者换行,就会被检查出来。
错误的名字:如Math.sine(2). (应该是 sin.)
参数的个数:如 Math.sin(30, 20).
参数的类型:如Math.sin(“30”).
错误的返回类型 :例如一个声明返回String类型函数return 10;
动态检查
非法的变量值:例如整型变量x、y,表达式x/y 只有在运行后y为0才会报错,否则就是正确的。
无法表示的返回值:例如最后得到的返回值无法用声明的类型来表示。
越界访问:例如在一个字符串中使用一个负数索引。使用一个null对象解引用。
心得
静态检查可以检测与变量类型相关的错误。正如上面提到过的,语法错误、错误的名字、参数的个数等。动态类型检查倾向于特定值才会触发的错误,例如除零错误和越界访问,编译器是不会在编译的时候报错的,只有在运行的时候才会报错。
所以我们在编写代码的时候,首先需要注意的就是要减少静态错误的出现,注意代码的书写规范,以及书写格式,提高我们的编程效率。
练习
下面有一些基于java的代码示例,判断它是以上描述的哪种错误。
//示例1
int sum=0;
for(i=0;i<=100;i++)
{
sum=sum+i
}
System.out.println(sum);
1
2
3
4
5
6
7
示例1为静态错误,编写代码时漏写“ ; ”
//示例2
int sum=10;
int i=0;
int average=sum/i;
1
2
3
4
示例2为动态错误,编写代码时出现除0错误。
//示例3
int a=100000;
int b=a*a;
System.out.println("a的平方为:"+b);
1
2
3
4
示例3无报错,但出现错误的结果。
数组和聚集类型
现在把冰雹序列的结果存储在数据结构中。在Java中有两种常用的线性存储结构:数组和列表。
数组是一连串类型相同的元素组成的结构,而且它的长度是固定的(元素个数固定)。例如,我们声明一个int类型的数组:
int a[] = new int[50];
1
下面基于数组,编写冰雹序列存储代码:
int a[] = new int[50];
int i = 0;
int n = 3;
while (n != 1) {
a[i] = n;
i++;
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
a[i] = n;
i++;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
我们可以这样声明列表:
List<Integer> list = new ArrayList<Integer>();
1
下面基于列表,编写冰雹序列存储代码:
List<Integer> list = new ArrayList<Integer>();
int n = 3;
while (n != 1) {
list.add(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);
1
2
3
4
5
6
7
8
9
10
11
比较上述两种存储方法,思考异同。
心得
我们会发现基于数组的冰雹序列存储有一些问题,我们并不知道冰雹序列的长短,就将存储冰雹序列的存储结构定义为数组,且数组的长度只有50,如果我们给出的冰雹序列足够长,系统就会报错,像这样的bug称为越界访问。
而列表类型很好的改善了这个问题,因为列表会自动扩充以满足新添加的元素,所以就不有再担心越界问题。
我们在编程的时候,要根据不同的情景及要求选择不同的存储结构,既要使代码简介易懂,还要尽可能减少不必要的错误。
迭代
对于在一个序列结构(例如列表和数组)遍历元素,Java和Python的写法差不多:
int max = 0;
for (int x : list) {
max = Math.max(x, max);
}
1
2
3
4
Math.max() 是一个Java API提供的函数。
方法
在Java中,声明通常必须在一个方法中,而每个方法都要在一个类型中,下面是冰雹序列的程序:
public class Hailstone {
/**
* Compute a hailstone sequence.
* @param n Starting number for sequence. Assumes n > 0.
* @return hailstone sequence starting with n and ending with 1.
*/
public static List<Integer> hailstoneSequence(int n) {
List<Integer> list = new ArrayList<Integer>();
while (n != 1) {
list.add(n);
if (n % 2 == 0) {
n = n / 2;
} else {
n = 3 * n + 1;
}
}
list.add(n);
return list;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
心得
MIT reading部分的Static Checking主要讲述了静态检查以及几种存储数据结构。
首先引入冰雹序列,先讲了冰雹序列的定义以及简单的输出代码。
然后通过静态检查、动态检查以及无检查的对比,让我们更加熟悉他们的异同。即:
静态检查可以检测与变量类型相关的错误。例如语法错误、错误的名字、参数的个数等。动态类型检查倾向于特定值才会触发的错误,例如除零错误和越界访问,编译器是不会在编译的时候报错的,只有在运行的时候才会报错。
所以我们在编写代码的时候,首先需要注意的就是要减少静态错误的出现,注意代码的书写规范,以及书写格式,提高我们的编程效率。
然后对比了数组和聚集的不同,以及在编写冰雹序列代码时的不同,
需要注意的是,数组有长度(即元素的个数),在存储不定量元素时,可能会出现越界的问题。而列表会自动扩充以满足新添加的元素,所以就不用再担心越界问题。
我们在编程的时候,要根据不同的情景及要求选择不同的存储结构,既要使代码简介易懂,还要尽可能减少不必要的错误。
[本文参考自]
(http://web.mit.edu/6.031/www/sp20/classes/01-static-checking/)