软件工程(2018)第三次个人作业
前方高能:本次作业中含有大量基础知识,请不要嘲笑我QAQ
第三次作业来了。选择看似相比有难度的(1)(其实是看不懂(2)在干什么)
题目要求:题目(1):最大连续子数组和(最大子段和)
背景
问题: 给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时定义子段和为0,依此定义,所求的最优值为: Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n
例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)时,最大子段和为20。
读完题目,第一想到就是数组解决问题。定义数组a[]输入变量后,遍历数组,将数组每个元素分别开始向后逐步取和,将这些和存到数组b[]中,最后比较数组b[]中,取最大值即可。
看起来用到的方法都是数组的基本方法,第三次作业果然还是简单(心里长舒一口气)
所以我的代码构思是在某堂课上随手完成的,大体思路算法有了后,在自己计算机上实现就行
但是实现又遇到了各种问题,一些在C上容易实现的方法,在Eclipse上还没有试过
问题(1):Java中数组的定义(没错第一个问题就是这个)
解决:参考《疯狂JAVA讲义》。JAVA中数组可动态定义或静态定义,前者定义后分配指定个数个内存,后者定义后直接给数组赋值,由系统来确定分配多少个空间。
int[] intArr;
intArr=new int[] {5,6,8,20};//静态定义
int[] intArr2= {5,6,8,21};//简化静态定义方式
int[] intArr3;
intArr3=new int[4];//动态定义
int[] intArr4=new int[4];//简化动态定义方式
与此同时,我想着把JAVA中所有关于数组的基本方法学习一下,数组的输入输出当然是最重要的
输入:利用循环逐个输入元素,循环变量控制条件是数组的length变量,此时须注意动态定义中会使length有可能大于我们程序运行时要输入的元素个数,在此我有疑问,JAVA是否可以实现动态数组,我们先完成作业,然后再考虑这个
而JAVA中输入方法为下
Scanner x = new Scanner(System.in);
int n1=x.nextInt();
输出:利用循环遍历数组元素,逐个输出,输出方法如下
System.out.println();//ln是换行
同时,了解到JAVA中有foreach循环遍历数组,看起来能简化代码
for(int m:intArr)
System.out.println(m);//foreach用法,注意m相当于一个临时变量,所以foreach方法无法完成数组的输入。
由问题(1)引发的关于数组的基本用法先学到这里,基本够用
这样程序代码可以写了,以下是我的程序代码
import java.util.Scanner;
public class Sz
{
int n;//数组元素个数
static int[] a=new int[10] ;//输入数组
static int[] b=new int[10];//以每个元素为首元素的组合集
public static void createArray(int n)//数组输入
{
for(int i=0;i<n;i++)
{
Scanner x = new Scanner(System.in);
int n1=x.nextInt();
a[i]=n1;
}
}
public static int getMax(int i,int n)//生成数组第i个元素为首元素的组合最大的组合
{
int[] c=new int[10];//用于循环中求该元素为首元素的组合中最大的组合
c[i]=a[i];
for(int j=i;j<n-1;j++)
{
c[j+1]=c[j]+a[j+1];
}
for(int m:c)
{
if(b[i]<m)
b[i]=m;
}
return b[i];
}
public static int getbMax(int[] b)//将b[]中最大元素筛选出来
{
int max=0;
for(int m:b)
{
if(max<m)
max=m;
}
return max;
}
public static void main(String[] args)
{
Scanner x = new Scanner(System.in);
int n=x.nextInt();
createArray(n);
for(int i=0;i<n;i++)
{
getMax(i,n);
}
System.out.println(getbMax(b));
}
}
附上程序运行截图
样例1:数组(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)
样例2:(a[1],a[2],a[3],a[4],a[5])=(-2,6,4,-7,-2)
注:以上代码是我最终版本代码,没以下问题,我是先写完程序再写博客,所以要保证代码都OK才会回忆我当时编程的心路过程(我觉得边写代码边写博客真的破坏思路,当然我的梦想是能边写边编而不影响思路,正如老师说过的直播写代码,我觉得OK)(我也有主播梦想)
写代码后调试修改了一些小错误,问题不大的,没有写出来。接下来碰到一个我不太理解的错误,虽然Eclipse强大的帮助修改能力教我将所以变量、方法加上static属性,但是我还是要弄懂有啥区别
问题(2)JAVA中static的用法
老规矩,上网学
这次应该是问题明确又基础,很容易找到回答
static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。
只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象市,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用--废话),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。
static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:
类名.静态方法名(参数列表...)
类名.静态变量名
用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块(用处非常大,呵呵)。
1、static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。
两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
所以一般在需要实现以下两个功能时使用静态变量:
? 在对象之间共享值时
? 方便访问变量时
2、静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,
因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。
因为实例成员与特定的对象关联!这个需要去理解,想明白其中的道理,不是记忆!!!
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
例如为了方便方法的调用,Java API中的Math类中所有的方法都是静态的,而一般类内部的static方法也是方便其它类对该方法的调用。
静态方法是类内部的一类特殊方法,只有在需要时才将对应的方法声明成静态的,一个类内部的方法一般都是非静态的
以上内容摘自这篇博客(特意看了下欢迎转载,具体示例也有)
个人总结:static修饰的变量和方法称为静态变量,可以在类中直接引用变量、直接调用方法
问题解决后,经过测试也都ok,突然想到,我用Eclipse调试不像c一样熟练,而且我还没掌握Eclipse调试的方法
问题(3)Eclipse中如何调试
网上学习。Eclipse支持设置断点进行调试(否则程序直接运行到底)。右键代码行头数字设置breakpoint(或直接双击数字)(breakpoint更有详细设置,支持条件断点调试,有丶厉害啊这个)F5逐步调试,F6具体进入方法,F7跳出方法,CTRL+F2结束调试,嗯,我记住了
调试过程中,可将鼠标直接放在程序变量上显示变量,更可以设置出Variables窗口查看其余变量(Window——show view——Other——debug——Variables),这样跟C语言调试几乎完美相同了,或许有更多功能待挖掘
接下来是测试
覆盖方法
(1))语句覆盖:选择合适用例,所有语句被执行一次。
语句覆盖是指选择足够的测试用例,使得运行这些测试用例时,被测程序的每一个语句至少执行一次,其覆盖标准无法发现判定中逻辑运算的错误。
(2)判定覆盖:每个判定至少取一次真、一次假。
判定覆盖是设计足够多的测试用例,使得程序中的每一个判断至少获得一次“真”和一次“假”,即使得程序流程图中的每一个真假分支至少被执行一次。
(3)条件覆盖:每个条件的各种可能结果至少满足一次。
条件覆盖是指选择足够的测试用例,使得运行这些测试用例时,判定中每个条件的所有可能结果至少出现一次,但未必能覆盖全部分支。
(4)判定条件覆盖:同时满足判断覆盖和条件覆盖。
判定条件覆盖是设计足够的测试用例,得使判断中每个条件的所有可能取值至少执行一次,同时每个判断本身所有可能结果也至少执行一次。缺点是忽略了条件的组合情况。
(5)条件组合覆盖:所有组合情况都要覆盖一次。
在白盒测试法中,选择足够的测试用例,使得每个判定中条件的各种可能组合都至少出现一次。显然,满足“条件组合覆盖”的测试用例是一定满足“判定覆盖”、“条件覆盖”和“判定/条件覆盖”的。
选定的覆盖方法(判定/条件覆盖)我采用的是判定条件覆盖:
(1)max>b[] max>0
(2)max>b[] max=0
(3)max<b[] max>0
(4)max<b[] max=0
选择用例:
(1)测试用例:{-2,11,-4,9}
(2)测试用例:{-2,-3,-4,-5}
(3)测试用例:{1,2,3,4}
(4)测试用例:{-5,-4,-3,-1}
建立JUNIT测试单元,测试代码如下
import static org.junit.Assert.*;
import org.junit.Test;
public class SzTest
{
int[] a=new int[] {-2,11,-4,9};
static int[] b=new int[] {14,16,5,9};
@Test
public void testGetMax()
{
assertEquals(14,new Sz().getMax(0,4));
assertEquals(16,new Sz().getMax(1,4));
assertEquals(5,new Sz().getMax(2,4));
assertEquals(9,new Sz().getMax(3,4));
}
@Test
public void testGetbMax()
{
assertEquals(16,new Sz().getbMax(b));
}
}
进行测试,出现问题(4)测试失败
如图
java.lang.AssertionError: expected:<14> but was:<0>
测试结果是0?不应该啊。于是设置断点逐步调试,发现问题。由于Sz中a[]是静态变量,在那个类中可以被程序直接引用修改。而在Sztest类的调用中,Sz的a[]的值一直为0。所以程序输出为0。为了测试用,我将Sz.getMax中临时定义a[]的值为{-2,11,-4,9}(仅测试用,不影响原程序),并且将Sztest中定义的a[]删除(这的a[]没有意义了),重新调试,OK。如图。
接下来将剩余测试测完,直接列图了
注:这里的0是因为都是负数,所以默认全用0取代
注:这里同理
单元测试完成!
后记:本次作业完成,问题也都圆满解决,最后调试出现的问题是靠自己调试分析完成有点成就感。下次见~!
对了,动态数组还没实现!
我觉得,如果能像建立链表一样建立一个元素,然后每次输入元素都连接上一个元素(链表靠地址连接,数组就靠建立数组时分配的连续内存这个顺序连接),输入结束后,数组也建立完成。通过思考和逐渐实践,我觉得可行,但是总感觉自己理解不深导致没有弄出来。最后在网上发现了跟我思路特别像的大佬实现了(侵删)
// 定义一个初始长度为0的数组,用来缓存数据
private String[] src = new String[0];
// 增加
public void add(String s)
{
//定义新数组,长度是原数组长度+1
String[] dest = new String[src.length+1];
//将原数组的数据拷贝到新数组
System.arraycopy(src, 0, dest, 0, src.length);
//将新元素放到dest数组的末尾
dest[src.length]=s;
//将src指向dest
src=dest;
}