1. 程式人生 > >軟件工程(2018)第三次個人作業

軟件工程(2018)第三次個人作業

邏輯 靜態方法 equals sys bubuko 增加 body break 直接

軟件工程(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)第三次個人作業