1. 程式人生 > >Java靜態多分配,動態單分配

Java靜態多分配,動態單分配

http://blog.csdn.net/cclive1601/article/details/8050330
http://my.oschina.net/tdd/blog/341258
class Human{
}  
class Man extends Human{
}
class Woman extends Human{
}

public class StaticPai{

	public void say(Human hum){
		System.out.println("I am human");
	}
	public void say(Man hum){
		System.out.println("I am man");
	}
	public void say(Woman hum){
		System.out.println("I am woman");
	}

	public static void main(String[] args){
		Human man = new Man();
		Human woman = new Woman();
		StaticPai sp = new StaticPai();
		sp.say(man);
		sp.say(woman);
	}
}

總結:Human man = new Man();    我們把上面程式碼中的“Human”稱為變數的靜態型別,後面的“Man”稱為變數的實際型別。靜態型別和實際型別在程式中都可以發生一些變化,區別是靜態型別的變化僅僅在使用時發生,變數本身的靜態型別不會被改變,並且最終的靜態型別是在編譯期可知的,而實際型別變化的結果在執行期才可確定。    回到上面的程式碼分析中,在呼叫say()方法時,方法的呼叫者(回憶上面關於宗量的定義,方法的呼叫者屬於宗量)都為sp的前提下,使用哪個過載版本,完全取決於傳入引數的數量和資料型別(方法的引數也是資料宗量)。程式碼中刻意定義了兩個靜態型別相同、實際型別不同的變數,可見編譯器(不是虛擬機器,因為如果是根據靜態型別做出的判斷,那麼在編譯期就確定了)在過載時是通過引數的靜態型別而不是實際型別作為判定依據的。

並且靜態型別是編譯期可知的,所以在編譯階段,Javac編譯器就根據引數的靜態型別決定使用哪個過載版本。這就是靜態分派最典型的應用。

class Eat{
}
class Drink{
}

class Father{
	public void doSomething(Eat arg){
		System.out.println("爸爸在吃飯");
	}
	public void doSomething(Drink arg){
		System.out.println("爸爸在喝水");
	}
}

class Child extends Father{
	public void doSomething(Eat arg){
		System.out.println("兒子在吃飯");
	}
	public void doSomething(Drink arg){
		System.out.println("兒子在喝水");
	}
}

public class SingleDoublePai{
	public static void main(String[] args){
		Father father = new Father();
		Father child = new Child();
		father.doSomething(new Eat());
		child.doSomething(new Drink());
	}
}

總結:兒子在喝水
    
我們首先來看編譯階段編譯器的選擇過程,即靜態分派過程。這時候選擇目標方法的依據有兩點:一是方法的接受者(即呼叫者)的靜態型別是Father還是Child,二是方法引數型別是Eat還是Drink。
因為是根據兩個宗量進行選擇,所以Java語言的靜態分派屬於多分派型別。
   
 再來看執行階段虛擬機器的選擇,即動態分派過程。由於編譯期已經了確定了目標方法的引數型別(編譯期根據引數的靜態型別進行靜態分派),因此唯一可以影響到虛擬機器選擇的因素只有此方法的接受者的實際型別是Father還是Child。因為只有一個宗量作為選擇依據,所以Java語言的動態分派屬於單分派型別。

記憶體的靜態分配和動態分配的區別主要是兩個:

      一是時間不同。靜態分配發生在程式編譯和連線的時候。動態分配則發生在程式調入和執行的時候。

      二是空間不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由函式malloc進行分配。不過棧的動態分配和堆不同,他的動態分配是由編譯器進行釋放,無需我們手工實現。    

對於一個程序的記憶體空間而言,可以在邏輯上分成3個部份:程式碼區,靜態資料區和動態資料區。動態資料區一般就是“堆疊”。“棧(stack)”和“堆(heap)”是兩種不同的動態資料區,棧是一種線性結構,堆是一種鏈式結構。程序的每個執行緒都有私有的“棧”,所以每個執行緒雖然程式碼一樣,但本地變數的資料都是互不干擾。一個堆疊可以通過“基地址”和“棧頂”地址來描述。全域性變數和靜態變數分配在靜態資料區本地變數分配在動態資料區,即堆疊中。程式通過堆疊的基地址和偏移量來訪問本地變數。

一般,用static修飾的變數,全域性變數位於靜態資料區。函式呼叫過程中的引數,返回地址,EBP和區域性變數都採用棧的方式存放。

所謂動態記憶體分配就是指在程式執行的過程中動態地分配或者回收儲存空間的分配記憶體的方法。動態記憶體分配不象陣列等靜態記憶體分配方法那樣需要預先分配儲存空間,而是由系統根據程式的需要即時分配,且分配的大小就是程式要求的大小。
例如我們定義一個float型陣列:float score[100];   
但是,在使用陣列的時候,總有一個問題困擾著我們:陣列應該有多大?在很多的情況下,你並不能確定要使用多大的陣列,比如上例,你可能並不知道我們要定義的這個陣列到底有多大,那麼你就要把陣列定義得足夠大。這樣,你的程式在執行時就申請了固定大小的你認為足夠大的記憶體空間。即使你知道你想利用的空間大小,但是如果因為某種特殊原因空間利用的大小有增加或者減少,你又必須重新去修改程式,擴大陣列的儲存範圍。這種分配固定大小的記憶體分配方法稱之為靜態記憶體分配。但是這種記憶體分配的方法存在比較嚴重的缺陷,特別是處理某些問題時:在大多數情況下會浪費大量的記憶體空間,在少數情況下,當你定義的陣列不夠大時,可能引起下標越界錯誤,甚至導致嚴重後果。 
我們用動態記憶體分配就可以解決上面的問題. 所謂動態記憶體分配就是指在程式執行的過程中動態地分配或者回收儲存空間的分配記憶體的方法。動態記憶體分配不象陣列等靜態記憶體分配方法那樣需要預先分配儲存空間,而是由系統根據程式的需要即時分配,且分配的大小就是程式要求的大小。從以上動、靜態記憶體分配比較可以知道動態記憶體分配相對於景泰記憶體分配的特點: 
   1、不需要預先分配儲存空間;
   2、分配的空間可以根據程式的需要擴大或縮小。 
要實現根據程式的需要動態分配儲存空間,就必須用到malloc函式.
malloc函式的原型為:void *malloc (unsigned int size) 其作用是在記憶體的動態儲存區中分配一個長度為size的連續空間。其引數是一個無符號整形數,返回值是一個指向所分配的連續儲存域的起始地址的指標。還有一點必須注意的是,當函式未能成功分配儲存空間(如記憶體不足)就會返回一個NULL指標。所以在呼叫該函式時應該檢測返回值是否為NULL並執行相應的操作。 靜態記憶體是在程式一開始執行就會分配記憶體,直到程式結束了,記憶體才被釋放。
動態記憶體是在程式呼叫在程式中定義的函式時才被分配,函式呼叫結束了,動態記憶體就釋放。
static int a;這是定義了一個靜態的變數
int a;這是定義了一個動態的變數;
靜態記憶體可以用於求階層。
例如:
jiechen(int i)
{static int a=1;
for(;a<=i,a++)
return a*i;
}
#include"stdio.h"
main()
{int a,i;
printf("enter number:")
scanf("%d",&a);
for(i=1;i<=a;i++)
printf("i!=%d\n",jiechen(i));

}
執行
輸入3
結果為1!=1
      2!=2
      3!=3 由malloc系統函式分配的記憶體就是從堆上分配記憶體。從堆上分配的記憶體一定要自己釋放。用free釋放,不然就是術語——“記憶體洩露”(或是“記憶體漏洞”)—— Memory Leak。於是,系統的可分配記憶體會隨malloc越來越少,直到系統崩潰。還是來看看“棧記憶體”和“堆記憶體”的差別吧。

    棧記憶體分配
    —————
    char*
    AllocStrFromStack()
    {
        char pstr[100];
        return pstr;
    }
   
    堆記憶體分配
    —————
    char*
    AllocStrFromHeap(int len)
    {
        char *pstr;
        
        if ( len <= 0 ) return NULL;
        return ( char* ) malloc( len );
    }

對於第一個函式,那塊pstr的記憶體在函式返回時就被系統釋放了。於是所返回的char*什麼也沒有。而對於第二個函式,是從堆上分配記憶體,所以哪怕是程式退出時,也不釋放,所以第二個函式的返回的記憶體沒有問題,可以被使用。但一定要呼叫free釋放,不然就是Memory Leak!

在堆上分配記憶體很容易造成記憶體洩漏,這是C/C++的最大的“剋星”,如果你的程式要穩定,那麼就不要出現Memory Leak。所以,我還是要在這裡千叮嚀萬囑付,在使用malloc系統函式(包括calloc,realloc)時千萬要小心。

記得有一個UNIX上的服務應用程式,大約有幾百的C檔案編譯而成,執行測試良好,等使用時,每隔三個月系統就是down一次,搞得許多人焦頭爛額,查不出問題所在。只好,每隔兩個月人工手動重啟系統一次。出現這種問題就是Memery Leak在做怪了,在C/C++中這種問題總是會發生,所以你一定要小心。一個Rational的檢測工作——Purify,可以幫你測試你的程式有沒有記憶體洩漏。

我保證,做過許多C/C++的工程的程式設計師,都會對malloc或是new有些感冒。當你什麼時候在使用malloc和new時,有一種輕度的緊張和惶恐的感覺時,你就具備了這方面的修養了。
對於malloc和free的操作有以下規則:

1) 配對使用,有一個malloc,就應該有一個free。(C++中對應為new和delete)
2) 儘量在同一層上使用,不要像上面那種,malloc在函式中,而free在函式外。最好在同一呼叫層上使用這兩個函式。
3) malloc分配的記憶體一定要初始化。free後的指標一定要設定為NULL。    

注:雖然現在的作業系統(如:UNIX和Win2k/NT)都有程序記憶體跟蹤機制,也就是如果你有沒有釋放的記憶體,作業系統會幫你釋放。但作業系統依然不會釋放你程式中所有產生了Memory Leak的記憶體,所以,最好還是你自己來做這個工作。(有的時候不知不覺就出現Memory Leak了,而且在幾百萬行的程式碼中找無異於海底撈針,Rational有一個工具叫Purify,可能很好的幫你檢查程式中的Memory Leak) 第一個例子也講得不清楚。所謂系統釋放,應該是指系統在自己的表裡把這段記憶體標記為可以使用,以後可以被別的程式使用,所以第一個例子會造成程式能訪問到已經釋放的記憶體空間,是越界,會造成不可預測的情況。
系統一般不會自動去清除釋放空間內的資料,而是由以後的程式來覆蓋。所以很多程式開頭都會做MEMSET(...),就是為了防止這種垃圾資料的情況。
如果在程式執行中要改變記憶體塊的大小,可以用RALLOC()函式,它能在原來地址上重新分配一塊空間,不過是用的時候要小心,也是比較容易出問題