2013年7月22日 星期一

建立Android開發環境:寫Android App的第一步

image


要在電腦上開發Android App或者第一次設定環境,你需要以下幾個步驟
  1. 下載並安裝Java開發工具,包括兩個部分:
    1. JDK(Java Development Kit
    2. JRE(Java Runtime Environment)
  2. 下載並安裝編譯器(Eclipse整合開發環境)
  3. 安裝ADT(Android Development Tool)擴充套件
  4. 下載Android SDK(Android Sofrware Development Kit) 工具包
  5. 設定Android SDK

下載並安裝Java開發工具 Image(17)
下載並安裝Eclipse整合開發環境

Image(18)

  • Eclipse網站
  • Eclipse IDE for Java Developers Or Eclipse IDE for Java EE Developers
  • 初學者建議選擇"for Java Developers"

安裝Android開發工具擴充套件(ADT)
  • 線上安裝
  • 開啟Eclipse IDE,從主選單中選擇[Help]中的[Install New Software]
  • Image(19)
  • 在"work with"輸入"http://dl-ssl.google.com/android/eclipse/site.xml"
  • 按下"Add..."
  • Image(20)
  • 在"Name:"輸入名稱,按"OK"
  • 勾選"Developer Tools",按下"Next",接著按下"Finish",Eclipse會安裝ADT套件

下載Android軟體開發套件
  • Software Development Kit,SDK
  • Image(22)
  • SDK是Android的軟體開發套件,Eclipse必須利用這些套件來開發Android app ,你解壓縮時,可以隨意決定它的資料路徑,但是要注意的是設定Eclipse時,必須指定這個資料路徑。
  • 設定Android SDK
    • 打開Preference選單
      • image

      在SDK Location欄位按下"Browse...",選擇剛剛解壓縮完的SDK資料夾,然後按下"Apply"。

完成這些設定就可以開始寫Android App了

2013年7月20日 星期六

多執行緒程式設計-同步(synchronization)

多執行緒程式設計-同步(synchronization)

Image(13)


Monitor的概念
  • 當兩個以上的執行緒需要存取共同的資源時,需要以某種方式來確定同一時間只有一個執行緒在使用資源。為了達到這個目的的過程稱之同步
  • monitor的概念(也稱為semaphore)是同步的關鍵:
    • monitor是一種用來當成機鎖(lock)、互斥器(mutex)
    • 某時間裡只有一個執行緒可以持有monitor,當執行緒獲得機鎖(lock)時,我們稱之它進入monitor
    • 其他企圖進入monitor的執行緒必須暫停,直到第一個執行緒離開monitor
    • 這些暫停的執行緒就是正在等待monitor
  • 大多數語言語言本身並不支援同步(例如C/C++),因此若要使用執行緒同步則需要利用作業系統的基本指令。
  • Java透過語言元件來實作同步,所以大部分與同步有關的複雜工作也跟著消除了

使用同步方法
  • Java中的同步化很容易,因為所有的物件都有它自己的隱式monitor
  • 若要進入monitor只需要呼叫已經用synchronized關鍵字修飾過的方法即可
    • 當某個執行緒已經進入同步方法時,其他所有試著呼叫相同實體上同步方法(包刮其他的同步方法)的執行緒必須等待
    • 若要離開monitor並且讓出物件的控制權給下一個等待的執行緒,只要讓monitor的持有者從同步方法回傳即可
  • 沒有同步的程式
  • 輸出結果:
    [Hellow[Synchronized[World
    ]
    ]
    ]
  • 競爭情況(race condition):利用sleep()方法,讓正在call()方法中的執行緒能夠讓出執行權,並切換到另一個執行緒。這將會導致三個訊息字串輸出混和在一起。
  • 因為三個執行緒為了完成方法與彼此互相競爭。使得競爭情況非常難以捉模與難以預料,這可能使得你的程式上次執行是正確的,線次執行卻是錯誤的。
  • 若要修正上述程式,你必須將call()的存取序列化(serialize)
    • 限制每次只有一個執行緒能夠存取此方法
    • 在call()的定義式前面加上synchronized關鍵字即可
    • image
      如此才能得到正確的輸出結果:
      [Hellow]
      [Synchronized]
      [World]
  • 在多執行緒的情況下,每當有一個方法或一群方法需要操作物件的內部狀態時,應該要使用synchronized來避免競爭

synchronized敘述句
  • 假設你想要同步存取某個類別的物件,但此類別並不是為多執行緒存取設計的
    • 也就是說此類別並沒有使用synchronized方法
    • 或者類別並不是你所建立的,而且你無法存取原始程式碼
  • 你可以將此物件的方法之呼叫放在synchronized區塊中:

    synchronized (object ){

    //需要同步的敘述

    }

    • object是想要同步的物件之參考
    • 同步區塊可以確保只有當目前執行緒成功進入object的monitor時,才會呼叫object的方法。
  • 以下是另一種版本,在run()方法裡使用同步區塊
      1: public class Synch1 {
      2:   public static void main(String[] args) {
      3:     Callme target=new Callme();
      4:     Caller2 ob[]={
      5:         new Caller2(target, "Hellow"),
      6:         new Caller2(target, "Synchronized"),
      7:         new Caller2(target, "World")
      8:         };
      9:     try {
     10:       ob[0].t.join();
     11:       ob[1].t.join();
     12:       ob[2].t.join();
     13:     } catch (InterruptedException e) {
     14:       // TODO: handle exception
     15:       System.out.println("Interrupted.");
     16:     }    
     17:   }
     18: 
     19: }
     20: 


  • 將target寫在同步區塊中:
      1: public class Caller2 implements Runnable {
      2: 
      3:   String msg;
      4:   Callme target;
      5:   Thread t;
      6:   public Caller2(Callme targ,String s) {
      7:     target=targ;
      8:     msg=s;
      9:     t=new Thread(this);
     10:     t.start();
     11:   }
     12:   @Override
     13:   public void run() {
     14:     synchronized(target){
     15:     target.call(msg);
     16:     }
     17:     // TODO Auto-generated method stub
     18:     
     19:   }
     20: }

  • 此程式同樣可以產生正確的結果

2013年7月15日 星期一

C 語言新手十誡(The Ten Commandments for Newbie C Programmers)

  1. 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,並多多實作練習。
  2. 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並解答了。
  3. 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,那只是不足為恃的一時僥倖。
  4. 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。

  • 1. 不可以使用尚未給予適當初值的變數
  • 2. 不能存取超過陣列既定範圍的空間
  • 3. 不可以提取不知指向何方的指標
  • 4. 不要試圖用 char* 去更改一個"字串常數"
  • 5. 不能在函式中回傳一個指向區域性自動變數的指標
  • 6. 不可以只做 malloc(), 而不做相應的 free()
  • 7. 在數值運算、賦值或比較中不可以隨意混用不同型別的數值
  • 8. 在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
  • 9. 在 Macro 定義中, 務必為它的參數個別加上括號
  • A(10). 不可以在 stack 設置過大的變數
  • B(11). 使用浮點數精確度造成的誤差問題
  • C(12). 不要猜想二維陣列可以用 pointer to pointer 來傳遞
  • D(13). 函式內 new 出來的空間記得要讓主程式的指標接住

01. 你不可以使用尚未給予適當初值的變數
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    錯誤例子:
    int accumulate(int max)    /* 從 1 累加到 max,傳回結果 */
    {
        int sum;    /* 未給予初值的區域變數,其內容值是垃圾 */
        int num;
        for (num = 1; num <= max; num++) {  sum += num;  }
        return sum;
    }

    正確例子:
    int accumulate(int max)
    {
        int sum = 0;    /* 正確的賦予適當的初值 */
        int num;
        for (num = 1; num <= max; num++) {  sum += num;  }
        return sum;
    }

02. 你不可以存取超過陣列既定範圍的空間
1
2
3
4
5
6
7
8
9
    錯誤例子:
    int str[5];
    int i;
    for (i = 0 ; i <= 5 ; i++) str[i] = i;

    正確例子:
    int str[5];
    int i;
    for (i = 0; i < 5; i++) str[i] = i;
  • 說明:宣告陣列時,所給的陣列元素個數值如果是 N, 那麼我們在後面
  • 透過 [索引值] 存取其元素時,所能使用的索引值範圍是從 0 到 N-1
  • C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,我們要自己來確保不會越界。一旦越界,操作的不再是合法的空間,將導致無法預期的後果。

03. 你不可以提取(dereference)不知指向何方的指標(包含 null 指標)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    錯誤例子:
    char *pc1;      /* 未給予初值,不知指向何方 */
    char *pc2 = 0;  /* pc2 起始化為 null pointer */
    *pc1 = 'a';     /* 將 'a' 寫到不知何方,錯誤 */
    *pc2 = 'b';     /* 將 'b' 寫到「位址0」,錯誤 */

    正確例子:
    char c;          /* c 的內容尚未起始化 */
    char *pc1 = &c;  /* pc1 指向字元變數 c */
    *pc1 = 'a';      /* c 的內容變為 'a' */

    /* 動態分配 10 個 char(其值未定),並將第一個char的位址賦值給 pc2 */
    char *pc2 = (char *) malloc(10);
    pc2[0] = 'b';    /* 動態配置來的第 0 個字元,內容變為 'b'
    free(pc2);
  • 說明:指標變數必需先指向某個可以合法操作的空間,才能進行操作。
    ( 使用者記得要檢查 malloc 回傳是否為 NULL,礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    錯誤例子:
    char *name;   /* name 尚未指向有效的空間 */
    printf("Your name, please: ");
    gets(name);   /* 您確定要寫入的那塊空間合法嗎??? */
    printf("Hello, %s\n", name);

    正確例子:
    /* 如果編譯期就能決定字串的最大空間,那就不要宣告成 char* 改用 char[] */

    char name[21];   /* 可讀入字串最長 20 個字元,保留一格空間放 '\0' */
    printf("Your name, please: ");
    gets(name);
    printf("Hello, %s\n", name);
正確例子(2):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    /* 若是在執行時期才能決定字串的最大空間,則需利用 malloc() 函式來動態分配空間 */

    size_t length;
    char *name;
    printf("請輸入字串的最大長度(含null字元): ");
    scanf("%u", &length);

    name = (char *)malloc(length);
    printf("Your name, please: ");
    scanf("%s", name);
    printf("Hello, %s\n", name);

    /* 最後記得 free() 掉 malloc() 所分配的空間 */
    free(name);

04. 你不可以試圖用 char* 去更改一個"字串常數"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    錯誤例子:
    char* pc = "john";   /* pc 現在指著一個字串常數 */
    *pc = 'J';   /* 但是 pc 沒有權利去更改這個常數! */



    正確例子:
    char pc[] = "john";  /* pc 現在是個合法的陣列,裡面住著字串 john */
                         /* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',                                              pc[3]='n', pc[4]='\0'  */
    *pc = 'J';
    pc[2] = 'H';
  • 說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
  • 若您希望使用可以更改的字串,那您應該將其放在合法空間
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    錯誤例子:
    char *s1 = "Hello, ";
    char *s2 = "world!";
    /* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串的後面,       造成寫入到程式無權碰觸的記憶體空間 */
    strcat(s1, s2);



    正確例子(2)
    /* s1 宣告成陣列,並保留足夠空間存放後續要附加的內容 */
    char s1[20] = "Hello, ";
    char *s2 = "world!";
    /* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */
    strcat(s1, s2);

05. 你不可以在函式中回傳一個指向區域性自動變數的指標。否則,會得到垃圾值
[感謝 gocpp 網友提供程式例子]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    錯誤例子:
    char *getstr(char *name)
    {
        char buf[30] = "hello, "; /*將字串常數"hello, "的內容複製到buf陣列*/
        strcat(buf, name);
        return buf;
    }

    說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
    被消滅,因此呼叫端得到的指標所指的字串內容就失效了。

    正確例子:
    void getstr(char buf[], int buflen, char const *name)
    {
        char const s[] = "hello, ";
        strcpy(buf, s);
        strcat(buf, name);
    }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    正確例子:
    int* foo()
    {
        int* pInteger = (int*) malloc( 10*sizeof(int) );
        return pInteger;
    }

    int main()
    {
        int* pFromfoo = foo();
    }
  • 說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
  • 而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack
  • 又因 heap 空間並不會自動回收,因此這塊空間在離開函式後,依然有效
  • (但是這個例子可能會因為 programmer 的疏忽,忘記 free 而造成memory leak)
[針對字串操作,C++提供了更方便安全更直觀的 string class, 能用就盡量用]


1
2
3
4
5
6
7
8
9
    正確例子:

    #include <string>    /* 並非 #include <cstring> */
    using std::string;

    string getstr(string const &name)
    {
        return string("hello, ") += name;
    }

06. 你不可以只做 malloc(), 而不做相應的 free(). 否則會造成記憶體漏失
  • 但若不是用 malloc() 所得到的記憶體,則不可以 free()。
  • 已經 free()了所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可以再被 free(),也不可以提取(dereference)這個指標。
  • [C++] 你不可以只做 new, 而不做相應的 delete
  • 注:new 與 delete 對應,new[] 與 delete[] 對應,不可混用
    (切記,做了幾次 new,就必須做幾次 delete)
  • 小技巧: 可在 delete 之後將指標指到 0,由於 delete 本身會先做檢查,因此可以避免掉多次 delete 的錯誤
1
2
3
4
5
   正確例子:
   int *ptr = new int(99);
   delete ptr;
   ptr = NULL;
   delete ptr;   /* delete 只會處理指向非 NULL 的指標 */

07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
  • 慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算的結果,其範圍是否會超出變數的型別
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    錯誤例子:
    unsigned int sum = 2000000000 + 2000000000;  /* 超出 int 存放範圍 */
    unsigned int sum = (unsigned int) (2000000000 + 2000000000);
    double f = 10 / 3;


    正確例子:
    /* 全部都用 unsigned int, 注意數字後面的 u, 大寫 U 也成 */
    unsigned int sum = 2000000000u + 2000000000u;

    /* 或是用顯式的轉型 */
    unsigned int sum = (unsigned int) 2000000000 + 2000000000;

    double f = 10.0 / 3.0;


1
2
3
4
    錯誤例子:
    unsigned int a = 0;
    int b[10];
    for(int i = 9 ; i >= a ; i--) {  b[i] = 0;  }
  • 說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,因此迴圈條件永遠滿足,與預期行為不符
1
2
3
4
5
6
    錯誤例子:                                 (感謝 sekya 網友提供)
    unsigned char a = 0x80;   /* no problem */
    char b = 0x80;    /* implementation-defined result */
    if( b == 0x80 ) {        /* 不一定恒真 */
        printf( "b ok\n" );
    }
  • 說明:語言並未規定 char 天生為 unsigned 或 signed,因此將 0x80 放入char 型態的變數,將會視各家編譯器不同作法而有不同結果

08. 你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值超過一次以上。否則,將導致未定義的行為(undefined behavior)
1
2
3
4
5
6
7
8
    錯誤例子:
    int i = 7;
    int j = ++i + i++;

    正確例子:
    int i = 7;
    int j = ++i;
    j += i++;
  • 你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。
1
2
    錯誤例子:
    x = x++;

1
 2
 3
 4
 5
 6
 7
 8
 9
10
    錯誤例子:
    int arr[5];
    int i = 0;
    arr[i] = i++;

    正確例子:
    int arr[5];
    int i = 0;
    arr[i] = i;
    i++;

1
2
3
4
5
6
7
8
    錯誤例子:
    int i = 10;
    cout << i << "==" << i++;

    正確例子:
    int i = 10;
    cout << i << "==";
    cout << i++;

^L#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    錯誤例子:
    int Integer=10;
    printf( "%d %d %d", Integer++, Integer++, Integer++ );


    錯誤例子:
    void foo(int a, int b) { ... }
    int main() {
        int i=0;
        foo(i++, i++);
    }

  • 說明: C/C++ 並沒有強制規定參數會由哪個方向開始處理(不像Java是由左到右),因此可能會造成與預期不符的情況

09. 在 Macro 定義中, 務必為它的參數個別加上括號

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    錯誤例子:
    #include <stdio.h>
    #define SQUARE(x)    (x * x)
    int main()
    {
        printf("%d\n", SQUARE(10-5));
        return 0;
    }

    正確例子:
    #include <stdio.h>
    #define SQUARE(x)    ((x) * (x))
    int main()
    {
        printf("%d\n", SQUARE(10-5));
        return 0;
    }
  • 說明:如果是用 C++, 請多多利用 inline function 來取代上述的 macro,以免除 macro 定義的種種危險性。如:
    inline int square(int x) { return x * x; }
  • macro 定義出的「偽函式」至少缺乏下列數項函式本有的能力:
    • (1) 無法進行參數型別的檢查。
    • (2) 無法遞迴呼叫。
    • (3) 無法用 & 加在 macro name 之前,取得函式位址。
    • (4) 呼叫時往往不能使用具有 side effect 的引數。例如:
1
2
3
4
5
6
7
8
    錯誤例子:                                      (感謝 yaca 網友提供)
    #define MACRO(x)     (((x) * (x)) - ((x) * (x)))
    int main()
    {
        int x = 3;
        printf("%d\n", MACRO(++x));
        return 0;
    }
    • 備註:副作用(side effect)
    • 表达式有两种功能:每个表达式都产生一个值( value ),同时可能包含副作用( side effect )。副作用是指改变了某些变量的值。
      • 1:20 //这个表达式的值是20;它没有副作用,因为它没有改变任何变量的值。
      • 2:x=5 // 这个表达式的值是5;它有一个副作用,因为它改变了变量x的值。
      • 3:x=y++ // 这个表达示有两个副作用,因为改变了两个变量的值。
      • 4:x=x++ // 这个表达式也有两个副作用,因为变量x的值发生了两次改变。

10. 不可在 stack 設置過大的變數,否則會造成 stack overflow
(感謝 VictorTom 版友幫忙)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    錯誤例子:
    int array[10000000];       // 僅舉例說明

   /* 說明:由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十 KB
    當變數所需的空間過大時,很容易造成 stack overflow,程式亦隨之
    當掉,若真正需要如此大的空間,那麼建議配置在 heap 上,或是採用
    static / globla variable,亦或是改變編譯器的設定
    使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
    的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
    時會回傳 null,不會影響到正常使用下的程式功能    */
    正確例子:
    int *array = (int*) malloc( 10000000*sizeof(int) );
  • 說明:由於此時 stack 上只需配置一個 int* 的空間,可避免 stack overflow
  • 更多說明請參考精華區 z-10-13

11. 使用浮點數千萬要注意精確度所造成的誤差問題

根據 IEEE 754 的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)

更詳細的說明可以參考精華區 z-8-11
或參考冼鏡光老師所發表的一文 "使用浮點數最最基本的觀念"
http://blog.dcview.com/article.php?a=VmgBZFE5AzI%3D

12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞
(感謝 loveme00835 legnaleurc 版友的幫忙)

首先必須有個觀念,C 語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
1
2
3
4
    void pass1DArray( int array[] );

    int a[10];
    pass1DArray( a );   /* 可以合法編譯,而且執行結果正確!! */
事實上,編譯器會這麼看待
1
2
3
4
    void pass1DArray( int *array );

    int a[10];
    pass1DArray( &a[0] );
  • 我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
  • 因此我們可以 int *p = a; 這種方式,拿指標去接陣列。
  • 也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **
1
2
3
4
5
6
7
    錯誤例子:
    void pass2DArray( int **array );

    int a[5][10];
    pass2DArray( a );
    /* 這時候編譯器就會報錯啦 */
    /* expected ‘int **’ but argument is of type ‘int (*)[10]’*/

  • 在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍,例如,宣告了一個 a[10],那我可以把 a 當成指標來操作 *a 至 *(a+9)
  • 因此我們可以得到一個概念,在操作的時候,可以 decay 成指標來使用,也就是我可以把一個陣列當成一個指標來使用 (again, 陣列!=指標)
  • 但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個pointer to pointer to int 來操作一個 int 二維陣列,這是不合理的!
  • 儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"
  • 如果想通了這一點,我們可以仿造之前的規則,把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列
1
2
3
4
    void pass2DArray( int (*array) [10] );  // array 是個指標,指向 int [10]

    int a[5][10];
    pass2DArray( a );

  • 這時候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢?答案是它代表著 a[0] 外層的那一維陣列,裡面包含著內層 [0]~[9],也因此 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9]
  • 結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
  • 也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞指標,也因此如果我們想用 sizeof 來求得陣列元素個數,那是不可行的
1
2
3
4
    錯誤例子:
    void print1DArraySize( int* arr ) {
        printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
    }      
  • 受此限制,我們必須手動傳入大小
1
    void print1DArraySize( int* arr, size_t arrSize );
  • 註:typedef unsigned int size_t 與平台無關的,表示0-MAXINT的無號整數
  • C++ 提供 reference 的機制,使得我們不需再這麼麻煩,可以直接傳遞陣列的 reference 給函數,大小也可以直接求出
1
2
3
4
    正確例子:
    void print1DArraySize( int (&array)[10] ) {    // 傳遞 reference
        cout << sizeof(array) / sizeof(int);   // 正確取得陣列元素個數
    }

13. 函式內 new 出來的空間記得要讓主程式的指標接住
  • 對指標不熟悉的使用者會以為以下的程式碼是符合預期的
1
2
3
4
5
6
7
8
    void newArray(int* local, int size) {
        local = (int*) malloc( size * sizeof(int) );
    }

    int main() {
        int* ptr;
        newArray(ptr, 10);
    }
  • 接著就會找了很久的 bug,最後仍然搞不懂為什麼 ptr 沒有指向剛剛拿到的合法空間,讓我們再回顧一次
  • 1. int* ptr;ptr 指向未知的空間
  • 2. 呼叫函式 newArray,ptr和local指向未知的空間
  • 3. malloc 取得合法空間ptr指向未知的空間 ,為local
  • 4. 離開函ptr 只向未知空間
  • 也許有人會想問,指標不是傳址嗎?精確來講,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe)
    • local 接到了 ptr 指向的那個位置,接著函式內 local 要到了新的位置
    • 但是 ptr 指向的位置還是沒變的,因此離開函式後就好像事什麼都沒發生
      ( 嚴格說起來還發生了 memory leak )
  • 以下是一種解決辦法
1
2
3
4
5
6
7
8
    int* createNewArray(int size) {
        return (int*) malloc( size * sizeof(int) );
    }

    int main() {
        int* ptr;
        ptr = createNewArray(10);
    }
  • 改成這樣亦可 ( 為何用 int** 就可以?想想他會傳什麼過去給local )
1
2
3
4
5
6
7
8
    void createNewArray(int** local, int size) {
        *local = (int*) malloc( size * sizeof(int) );
    }

    int main() {
        int *ptr;
        createNewArray(&ptr, 10);
    }
  • 如果是 C++,別忘了可以善用 Reference
1
2
3
    void newArray(int*& local, int size) {
        local = new int[size];
    }


後記:從「古時候」流傳下來一篇文章
  • The Ten Commandments for C Programmers"(Annotated Edition)
  • 一方面它不是針對 C 的初學者,一方面它特意模仿中古英文聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。
  • 作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP看板眾多網友提供寶貴意見及程式實例。
  • nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。如發現 Bug 請推文回報,謝謝您!

資料來源:http://www.ptt.cc/bbs/C_and_CPP/M.1268491365.A.EF7.html
--> 儲存至 Evernote
<!-- Google Analytics -->

2013年7月14日 星期日

多執行緒程式設計

多執行緒程式設計

多執行緒程式設計

已更新的 今日

多執行緒程式設計

Java提供許多執行緒程式設計(multithreaded programming),Multithreaded program包含兩個或多個可同步執行的部分,它的每一個部分都稱為一個執行緒(thread),因此,它是一種多工(multitasking)的特殊形式。

多工的工作方式:
  1. 以程序為基礎(process-based)
    • 本質上,process就是指正在執行的程式
    • 通常程式就是排程器所能分派(dispatch)的最小單位
    • 通常用以做重量級(heavyweight)工作的處理,需要有自己的位址空間
    • 程序間溝通消耗許多資源
  2. 以執行緒為基礎(thread-based)
    • thread為可分派的最小單位
    • 單一程式可以同時執行兩個以上的工作
    • 多處理輕量級(lightweight)工作
    • 消耗資源較程序處理小,共用相同位址空間
    • 一起使用相同的重量級程序,彼此溝通所耗資源較小,執行緒間切畫布用消耗太多資源

Java的執行緒模型
  • Java的所有類別函式庫都將multithread列入考量。
  • Java利用thread將整個環境變成非同步(asynchronous)。避免CPU週期所造成的浪費,有助於提升整體效率。

同步
  • 若兩個執行緒需要互相溝通,並且共用某個複雜的資料結構,就必須確保執行緒使用的資料是一致的。
  • Java利用monitor來達到這個目的,monitor只能容納一個執行緒,一旦一個執行緒進入monitor,其他的執行緒必須等待,直到這個執行緒離開。
  • monitor可用以保護共用資源,避免同一時間被多個執行緒操縱

建立執行緒
  • Java的執行緒是建立於Thread類別、他的方法,以及Runnable介面的基礎上
  • Thread將執行緒封裝
  • 無法直接參考正在執行的執行緒之狀態,必須透過產生該執行緒的Thread實體來處理
  • 若要建立執行緒,你需要繼承Thread類別或者實作Runnable介面。
  • 實作Runnable Interface
    • Runnable將一組可執行的程式碼抽象化
    • 可以在任何時做Runnable的物件上建構執行緒
    • 若要實作Runnable,執行要實作一個稱為run()的方法
      • public void run()
      • 在run()裡,你將定義構成新執行緒的程式碼
      • run()為另一個同時在程式中執行的thread建立進入點
      • 當run()回傳時,此執行緒將會結束
    • 在你建立一個實作Runnable的類別後,你將利用此類別建立Thread型別的物件實體
    • Thread定義許多建構式,其中一個如下
      • Thread(Runnable threadOb, String threadName)
        • threadOb:實作Runnable介面的類別實體。它定義了thread的進入點
        • threadName:指定新執行緒的名稱
      • 新執行緒建立後,必須呼叫Thread定義的start()方法,新的執行緒才會開始執行。
        • 本質上Start()會呼叫run()方法:若直接呼叫run()方法,則只是單純的方法呼叫,而不會啟動新的執行緒
        • start方法如下所示:void start()
    • 以下範例示範如何建立新的執行緒,並使之開始執行:
      • public class NewThread implements Runnable {
        Thread t ;
        public NewThread() {
        // TODO Auto-generated constructor stub
        t= new Thread (this, "Demo Thread");
        System. out. println("Child thread: " +t );
        t. start();
        }
        @Override
        public void run() {
        // TODO Auto-generated method stub
        try {
        for(int i=5;i>0;i--){
        System. out. println("Child Thread" +i );
        Thread. sleep(500 );
        }
        } catch (InterruptedException e) {
        // TODO: handle exception
        System. out. println("Exiting child thread." );
        }

        }

        }
      • //主程式
      • public class ThreadDemo {
        public static void main(String args[]) {
        new NewThread ();
        try {
        for(int i=5;i>0;i--){
        System. out. println("Main Thread: " +i );
        Thread. sleep(1000 );
        }
        } catch (InterruptedException e) {
        // TODO: handle exception
        System. out. println("Main thread interrupted." );
        }
        System. out. println("Main thread exiting." );
        }
        }
      • 在NewThread的建構式中
        • t=new Thread (this,"Demo Thread");
        • 將this當作第一個引數,表示你希望新的thread呼叫this物件上的run()

主執行緒(main thread)
  • 當Java程式開始執行時,有一個執行緒會立即開始執行,此執行緒稱之程式的主執行緒
    • 子執行緒都是由主執行緒產生
    • 通常它必須是最後一個完成的執行緒,因為它會進行許多關閉的動作
  • 透過currentTread()方法可以獲得主執行緒的參考,其為Class Thread的public static member
    static Thread currentThread()
  • 執行緒範例程式:
    public class CurrentThreadDemo {
    public static void main(String args[]) {
    Thread t =Thread .currentThread();
    System. out. println("Current Thread:" +t );
    t. setName("Main Thread" );
    System. out. println("After name change:" +t );
    try {
    for ( int n = 5; n > 0; n--) {
    System. out. println(n );
    Thread. sleep(1000 );
    }
    } catch (InterruptedException e) {
    // TODO: handle exception
    System. out. println("Main thread interrupted" );
    }
    }
    }
    • 將t透過println()印出,依次會顯示
      • 執行緒名稱:主執行緒名稱預設為main
      • 優先權:主執行緒優先權預設為5
      • 所屬群組名稱:執行緒群組(thread group)是一種資料結構,可把一群執行緒當成一個整體來控制
    • static void sleep(long milliseconds) throws InsterruptedException
      • milliseconds:執行緒暫停的毫秒數
      • InsterruptedException :當其他執行緒試圖中斷(interrupt)處於睡眠狀態的執行緒時,會發生此意外
      • sleep()的第二種形式,可以毫秒以及十億分之秒的方式指定暫停時間
        • static void sleep(long milliseconds,int nanoseconds) throws InsterruptedException
        • 只在計時週期小於十億分之秒的環境有效
    • final void setName(String threadName):設定執行緒名稱
    • final String getName():取得執行緒名稱
儲存至 Evernote