软件工程(2018)第三次个人作业

Posted L1aw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件工程(2018)第三次个人作业相关的知识,希望对你有一定的参考价值。

软件工程(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;
    }

豁然开朗,突然想起来C语言中例如a[]中的"a"实际上是一个地址,可以指向别的地址,这样我就可以将两个数组的"数组头"连接起来,模拟建立链表时光标的移动。不过我有疑问了:既然是指向,那原数组是不是应该及时删除从而减少内存呢?删除后才可以算做动态数组的完美建立。感觉又学到了丶东西。

本次作业也收获颇丰呢(最终代码)

以上是关于软件工程(2018)第三次个人作业的主要内容,如果未能解决你的问题,请参考以下文章

软件工程(2018)第三次个人作业

软件工程(2018)第三次个人作业

软件工程(2018)第三次个人作业

第三次作业——个人作业——软件产品案例分析

软工第三次作业——个人PSP

软件工程第三次作业