顯示具有 C++ 標籤的文章。 顯示所有文章
顯示具有 C++ 標籤的文章。 顯示所有文章

2014年3月5日 星期三

C&C++: Extern functions

There's [almost] never any need to use the keyword extern when declaring a function, either in C or in C++. In C and in C++ all functions have external linkage by default.

http://stackoverflow.com/questions/11712707/extern-functions-in-c-vs-c

Re: [C++] 有關 inline 函式...

 作者  khoguan (Khoguan Phuann)                               看板  C_and_CPP
 標題  Re: [C++] 有關 inline 函式...
 時間  Sun Aug 21 15:33:07 2005
───────────────────────────────────────

※ 引述《eliang ()》之銘言:
: 在 C++ Primer 這本書裡, 提到 inline 函式應該定義在 header file 裡
: 書上說明原因如下:
: An inline function may be defined more than once in a program as long as the
: definition appears only once in a given source file and the definition is
: exactly the same in each source file. By putting inline functions in headers,
: we ensure that the same definition is used whenever the function is called
: and that the compiler has the function definition available at the point of
: call.
: 我看不太懂它的意思...有沒有人可以解釋一下?
: 我之前都比較習慣把函式定義放在 .cpp 檔, 函式宣告放 .h 檔
: 因為這樣程式碼感覺起來比較乾淨, 像是這樣:
: // foo.h
: class foo {
: public:
:     int f();
: };
: // foo.cpp
: inline int foo::f() {
:     // short statement...
: }

  既然要讓它 inline, 就把函式定義一起寫在 foo.h 中。
  就是把你的 foo.cpp 檔的內容直接copy到 foo.h 檔原來的
  內容後面。這樣一樣很乾淨啦。

: 可是我發現這樣寫 "有時候" 編譯會發生錯誤, 訊息大概就類似:
:   [Linker error] undefined reference to 'foo::f()'
: 如果把 inline 去掉, 或照書上說的, 把定義改寫到 .h 檔, 就可以正常編譯,
: 請問這是什麼原因? 謝謝!!

  因為 inline 的本質就是可以讓編譯器在單獨編譯某個
  使用到這個 inline function 的 source file的時候,
  可以將整個 inline function 的定義插入並取代呼叫
  inline function的那行敘述。你沒給他函式定義,它
  怎麼做這個動作呢?

  當然,你指定要 inline 編譯器不一定真能做到 inline,
  inline 只是吾人對編譯器的「建議」。但是好歹你要將
  inline function 的定義透過 .h 檔 include 進來,不然,
  編譯器根本沒機會幫你 inline.

  inline 的時機是定義簡短的函式太長的、或是 virtual
  function 或是 recursive function 都不宜或不能 inline.

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.130.208.168
推 renderer:推 滿重要的 inline 概念                       61.228.217.108 08/21
推 eliang:說明得很詳細,感謝!!                                210.58.40.9 08/21

C&C++: Re: [問題] 請問static是什麼意思...

 作者  meltice (三億兩千萬大散戶)                             看板  C_and_CPP
 標題  Re: [問題] 請問static是什麼意思...

in C/C++

static variable像是global變數
差別只在它的visible範圍只在該scope內
所以static變數的初始值就是0
當然你也可以像global變數一樣指定初始值給它

static function表示該function的visible範圍只在該.cpp內
可以避免污染global namespace
所以很多個.cpp內可以有同樣名稱的static function

在C++ class之中

static member variable在該class中始終只存在一份
不會因為new delete而有數量上的變化
需要在global定義初始值
不用宣告實體object即可存取該值

static member function
只允許存取static member variable或function
不用宣告實體object即可呼叫該function

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 60.245.93.33

C&C++: [重要] 發文前務必閱讀:常見問題十三誡

 作者  nowar100 (拋磚引玉)                                    看板  C_and_CPP 
 標題  [重要] 發文前務必閱讀:常見問題十三誡                                  

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

                                                       by Khoguan Phuann
請注意:

(1) 本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
    但不能取代完整的學習,請自己好好研讀一兩本 C 語言的好書,
    並多多實作練習。

(2) 強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並
    解答了。

(3) 以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,
    那只是不足為恃的一時僥倖。

(4) 不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則
    引爆核彈、毀滅地球(如果你的 C 程式是用來控制核彈發射器的話)。

  ████████ 請選擇相對應的數字鍵,或按 End 結束播放 ████████

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. 你不可以使用尚未給予適當初值的變數

    錯誤例子:
    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. 你不可以存取超過陣列既定範圍的空間

    錯誤例子:
    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 指標)。

    錯誤例子:
    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,
      礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體 )

    錯誤例子:
    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):

    /* 若是在執行時期才能決定字串的最大空間,則需利用 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* 去更改一個"字串常數"

    錯誤例子:
    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';

    說明:字串常數的內容是"唯讀"的。您有使用權,但是沒有更改的權利。
    若您希望使用可以更改的字串,那您應該將其放在合法空間


    錯誤例子:
    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 網友提供程式例子]
    錯誤例子:
    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);
    }

    正確例子:
    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, 能用就盡量用]


    正確例子:

    #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 的錯誤

   正確例子:
   int *ptr = new int(99);
   delete ptr;
   ptr = NULL;
   delete ptr;   /* delete 只會處理指向非 NULL 的指標 */



07. 你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
    慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。必須隨時注意數值運算
    的結果,其範圍是否會超出變數的型別

    錯誤例子:
    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;


    錯誤例子:
    unsigned int a = 0;
    int b[10];
    for(int i = 9 ; i >= a ; i--) {  b[i] = 0;  }

    說明:由於 int 與 unsigned 共同運算的時候,會提升 int 為 unsigned,
          因此迴圈條件永遠滿足,與預期行為不符


    錯誤例子:                                 (感謝 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)

    錯誤例子:
    int i = 7;
    int j = ++i + i++;

    正確例子:
    int i = 7;
    int j = ++i;
    j += i++;

    你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,
    而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,
    是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。


    錯誤例子:
    x = x++;

    錯誤例子:
    int arr[5];
    int i = 0;
    arr[i] = i++;

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

    錯誤例子:
    int i = 10;
    cout << i << "==" << i++;

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


    錯誤例子:
    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 定義中, 務必為它的參數個別加上括號

    錯誤例子:
    #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 的引數。例如:

    錯誤例子:                                      (感謝 yaca 網友提供)
    #define MACRO(x)     (((x) * (x)) - ((x) * (x)))
    int main()
    {
        int x = 3;
        printf("%d\n", MACRO(++x));
        return 0;
    }


10. 不可在 stack 設置過大的變數,否則會造成 stack overflow
                                                   (感謝 VictorTom 版友幫忙)
    錯誤例子:
    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 語言中陣列是無法直接拿來傳遞的!
    不過這時候會有人跳出來反駁:

    void pass1DArray( int array[] );

    int a[10];
    pass1DArray( a );   /* 可以合法編譯,而且執行結果正確!! */

    事實上,編譯器會這麼看待

    void pass1DArray( int *array );

    int a[10];
    pass1DArray( &a[0] );

    我們可以順便看出來,array 變數本身可以 decay 成記憶體起頭的位置
    因此我們可以 int *p = a; 這種方式,拿指標去接陣列。

    也因為上述的例子,許多人以為那二維陣列是不是也可以改成 int **

    錯誤例子:
    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 二維陣列,這是不合理的!


^L#@N,f+1,下一頁#@P,f-1,上一頁#@H,:Menu:,回主選單#@E,:End:,結束播放#
    儘管我們無法將二維陣列直接 decay 成兩個指標,但是我們可以換個角度想,
    二維陣列可以看成 "外層大的一維陣列,每一維內層各又包含著一維陣列"

    如果想通了這一點,我們可以仿造之前的規則,
    把外層大的一維陣列 decay 成指標,該指標指向內層的一維陣列

    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 來求得陣列元素個數,那是不可行的

    錯誤例子:
    void print1DArraySize( int* arr ) {
        printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */
    }                                             /* 一個指標的大小   */

    受此限制,我們必須手動傳入大小
    void print1DArraySize( int* arr, size_t arrSize );


    C++ 提供 reference 的機制,使得我們不需再這麼麻煩,
    可以直接傳遞陣列的 reference 給函數,大小也可以直接求出

    正確例子:
    void print1DArraySize( int (&array)[10] ) {    // 傳遞 reference
        cout << sizeof(array) / sizeof(int);   // 正確取得陣列元素個數
    }


13. 函式內 new 出來的空間記得要讓主程式的指標接住


    對指標不熟悉的使用者會以為以下的程式碼是符合預期的


    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 )


    以下是一種解決辦法

    int* createNewArray(int size) {
        return (int*) malloc( size * sizeof(int) );
    }

    int main() {
        int* ptr;
        ptr = createNewArray(10);
    }


    改成這樣亦可   ( 為何用 int** 就可以?想想他會傳什麼過去給local )

    void createNewArray(int** local, int size) {
        *local = (int*) malloc( size * sizeof(int) );
    }

    int main() {
        int *ptr;
        createNewArray(&ptr, 10);
    }



    如果是 C++,別忘了可以善用 Reference

    void newArray(int*& local, int size) {
        local = new int[size];
    }



後記:從「古時候」流傳下來一篇文章

      "The Ten Commandments for C Programmers"(Annotated Edition)
      by Henry Spencer
      http://www.lysator.liu.se/c/ten-commandments.html

      一方面它不是針對 C 的初學者,一方面它特意模仿中古英文
      聖經的用語,寫得文謅謅。所以我現在另外寫了這篇,希望
      能涵蓋最重要的觀念以及初學甚至老手最易犯的錯誤。


作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP
      看板眾多網友提供寶貴意見及程式實例。


      nowar100 多次加以修改整理,擴充至 13 項,並且製作成動畫版。
      如發現 Bug 請推文回報,謝謝您!



--
※ 發信站: 批踢踢實業坊(ptt.cc)
推 cosmosQQ:第8戒 int i = 7; int j = ++i + i++; 蠻多公司有這考題   09/26 11:00
→ cosmosQQ:printf("i=%d, j=%d \n", i, j); => i=9, j=16            09/26 11:00
→ cosmosQQ:一般推論答案無誤, 但就與第8戒違反了@@ 該如何解....     09/26 11:01
→ nowar100:問題不存在 不應該出現的就是不應該 就算大家約定成俗     09/26 13:06
→ nowar100:討論它也是沒什麼意義的                                 09/26 13:07
※ 編輯: nowar100        來自: 140.112.30.82        (10/08 13:53)
→ wawi2:第13點的最後一個例子是不是錯了?  int (&array)[10]??       06/22 00:14

2014年3月4日 星期二

C&C++: i++ ++i

By adrianshum from telnet://ptt.cc 看板 C_and_CPP
i++ 是 做完遞增後, 回傳 遞增 *前* 的值
++i 則是做完遞增後, 回傳遞增 *後* 的值


你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值超過一次以上。否則,將導致未定義的行為(undefined behavior)

錯誤例子:
int i = 7;
int j = ++i + i++;

正確例子:
int i = 7;
int j = ++i;
j += i++;

你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,而且還在同一個式子的其他地方為了其他目的而存取該變數的值。(其他目的,是指不是為了計算這個變數的新值的目的)。否則,將導致未定義的行為。

錯誤例子:
x = x++;

錯誤例子:
    int arr[5];
    int i = 0;
    arr[i] = i++;

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

    錯誤例子:
    int i = 10;
    cout << i << "==" << i++;

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

錯誤例子:
    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是由左到右),因此可能會造成與預期不符的情況

2012年4月27日 星期五

C&C++: 強迫float做低階運算的方法

強迫float做低階運算的方法

float a, b;
long i;
a=0.1;
b=0.2;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;
i=*(long*)&a^*(long*)&b;
b=*(float*)&i;
i=*(long*)&a^*(long*)&b;
a=*(float*)&i;

=========================================

float a=4.3,b=2.4;
*(long*)&a = *(long*)&a ^ *(long*)&b;
*(long*)&b = *(long*)&b ^ *(long*)&a;
*(long*)&a = *(long*)&a ^ *(long*)&b;
cout << a << ", " << b << endl ;

2012年4月22日 星期日

C&C++: Re: [問題] 請教如何只用x,y兩變數來交換彼此數值

 作者  LPH66 (-858993460)                                     看板  C_and_CPP
 標題  Re: [問題] 請教如何只用x,y兩變數來交換彼此數值
 時間  Mon Apr 11 23:40:29 2011
───────────────────────────────────────

: → littleshan:不要這樣寫!這是 implementation-defined behavior     04/11 15:48
: → littleshan:就是十誡之八啦                                       04/11 15:49
基本上這個寫法的確是 implementation-defined behavior

不管有沒有加括號都一樣

事情是這樣的

a ^= b ^= a ^= b;
1 2 3 4 5 6 7

這樣寫的人想要做的事的順序是
                                值為
(7) 取 b 的值                   原來的 b
(5) 取 a 的值                   原來的 a
(6) 做 (5)^(7) 並把值放回 a 去  原來的 a^b
(3) 取 b 的值                   原來的 b
(4) 做 (3)^(6) 並把值放回 b 去  原來的 a
(1) 取 a 的值                   原來的 a^b
(2) 做 (1)^(4) 並把值放回 a 去  原來的 b

但有些 compiler (如我手上的 g++ 4.1.0 for win) 會編成這個順序

                                值為
(1) 取 a 的值                   原來的 a
(3) 取 b 的值                   原來的 b
(5) 取 a 的值                   原來的 a
(7) 取 b 的值                   原來的 b
(6) 做 (5)^(7) 並把值放回 a 去  原來的 a^b
(4) 做 (3)^(6) 並把值放回 b 去  原來的 a
(2) 做 (1)^(4) 並把值放回 a 去  一定是 0

結果是原來 a 的值在 b  但 a 卻變成 0 了

問題在於

雖然這個式子的確是分析成

     ^= (2)
   /    \
 a      ^= (4)
(1)   /    \
    b      ^= (6)
   (3)   /    \
       a        b
      (5)      (7)

這個樣子  但在計算 a ^= b 時  a b 取值的順序卻是 implementation-defined

也就是在上面那棵語法樹的後序走訪中先走左子樹還是先走右子樹

如果先走左子樹  就是 1 3 5 7 6 4 2 的順序

    先走右子樹  就是 7 5 6 3 4 1 2 的順序

而這兩個順序  上面我也寫出來了

後者是順利交換沒錯  前者卻是把 a 搬到 b 後把 a 炸成 0 !

所以結論是千萬不要用這個寫法

---
其實簡單一句話就是十三誡之八裡說的

    你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值
    超過一次以上。否則,將導致未定義的行為(undefined behavior)

如果真要用 xor 來交換  乖乖寫三條

x ^= y; y ^= x; x ^= y;

就什麼事都沒有....

當然最好的寫法還是 int temp = x; x = y; y = temp; 就是了

--
  'You've sort of made up for it tonight,' said Harry. 'Getting the
sword. Finishing the Horcrux. Saving my life.'
  'That makes me sound a lot cooler then I was,' Ron mumbled.
  'Stuff like that always sounds cooler then it really was,' said
Harry. 'I've been trying to tell you that for years.'
                               -- Harry Potter and the Deathly Hollows, P.308

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.30.142
推 attomahawk:推薦這棵 語法樹!                                    04/12 00:02
推 glennchen:好強大                                                04/12 00:07
推 loveme00835:不管多炫, 在可讀性上永遠是個失敗品, 是禁忌          04/12 00:09
推 james732:原來如此!                                             04/12 00:12
推 VictorTom:推語法樹:)                                            04/12 00:16
→ bibo9901:借問一下, 那寫成 a+=b; b=a-b; a-=b; 可以嗎             04/12 00:39
推 purpose:有緩衝區溢位的風險,如 a = INT_MAX; b = 1;              04/12 00:41
→ purpose:沒有緩衝區,打太快了                                    04/12 00:41
推 littleshan:overflow 是 undefined behavior                       04/12 00:43
推 xatier:語法樹! 推                                              04/12 07:36
推 wanwan2:讚!!                                                    04/12 08:40
推 genghiskii:3F正解                                               04/12 10:08
推 j094097:大推                                                    04/12 21:23
推 yauhh:都忘了這件事,果然imperative就怕太依賴state                04/12 21:40
推 h520:用樹講解好清楚啊!! 大推                                    04/14 13:42

2012年3月13日 星期二

C&C++: [問題] 新手發問"!!"的意思

!!
節錄部份

 作者  Barros (Leila)                                       站內  Programming
 標題  [問題] 新手發問 "!!"的意思
 時間  Thu Mar  8 21:11:04 2012
───────────────────────────────────────

看資料結構的書的時候,應該是用c寫的吧
發現code裡出現 !! 這個符號
煩請解惑感激不盡~

節錄部份code:

BinQueue
Merge(BinQueue H1, BinQueue H2)
{
  Bintree T1, T2, Carry=Null;
  int i j;

  if(H1->CurrentSize +H2->CurrentSize >Capacity)
    error("Merge would exceed capacity");
  H1->CurrentSize += H2->CurrentSize;
  for(i=0, j=1; j<= H2->CurrentSize; i++, j*=2)
  }
     T1=H1->TheTrees[i];T2=H2->TheTrees[i];

  switch( !!T1 + 2 * !! T2 + 4 * !! Carry)
  {
      case 0: /*no trees*/
      case 1: /*only H1*/
           break;



--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 219.84.2.1
→ Barros:因為google過了也找不到 只好來這裡發問了       219.84.2.1 03/08 21:12
→ tropical72:是長得像這樣嗎?int a=5; if(!!a)..    123.195.165.40 03/08 21:13
→ tropical72:簡單的說,a是非0的話,!!a會傳回1;否則  123.195.165.40 03/08 21:14
→ tropical72:傳回0。                               123.195.165.40 03/08 21:14
※ 編輯: Barros          來自: 219.84.2.1           (03/08 21:29)
→ s66449:加了兩個not 不就等同互相抵消了??          114.27.187.150 03/08 22:13
→ s66449:有加和沒加是一樣的?                       114.27.187.150 03/08 22:13
→ tropical72:不同唷,a=1,b=5而言 :                  123.195.165.40 03/08 22:19
→ tropical72:a+=b; --> a+=5 ---> a=6;              123.195.165.40 03/08 22:20
→ tropical72:a+=!!b ---> a+=!(!5)-->a+=!0-->a+=1   123.195.165.40 03/08 22:20
→ tropical72:--> a=2;                              123.195.165.40 03/08 22:20
→ tropical72:(T1!=0)|((T2!=0)<<1)|((C!=0)<<2)      123.195.165.40 03/08 22:26
→ Barros:了解了,謝謝                                219.84.58.43 03/08 22:47
→ MOONRAKER:真是神祕。                               61.231.8.206 03/09 08:46
推 yauhh:一個!將整數變成布爾值,但邏輯意思是反的,     180.206.52.95 03/09 18:42
→ yauhh:再加!就回來了.                              180.206.52.95 03/09 18:43
→ yauhh:他做二件事,一是將整數對應為0,1,二是將0,1   218.160.108.73 03/09 21:56
→ yauhh:(true,false)反過來當作數字來用. 但例子中   218.160.108.73 03/09 21:57
→ yauhh:看起來作法好怪,邏輯運算當數字運算用,數字   218.160.108.73 03/09 21:57
→ yauhh:運算卻當位元運算用.                        218.160.108.73 03/09 21:58


 作者  LPH66 (-858993460)                                   看板  Programming
───────────────────────────────────────

這是懶人用switch...

!! 是做兩次 boolean not

所以非零的會變成 1  是零的就還是 0

於是  !!T1 + 2 * !! T2 + 4 * !! Carry 這個算式

將三件事 (T1 != 0, T2 != 0, Carry != 0) 編碼成一個整數

若三者都為 0 則它會算出二進位的 000 = 十進位 0

若只有 T1 非 0 則它會算出二進位的 001 = 十進位 1

若只有 T2 非 0                    010 = 十進位 2

若只有 Carry 非 0                 100 = 十進位 4

等等

這樣就能以 switch 一次判斷三個條件的真假

你可以注意到這個 switch 裡的 case 有註解寫說這是什麼情況

就是這麼來的

※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 140.112.28.91

→ popcorny:我是覺得最起碼要用Macro包一下           114.32.239.120 03/11 14:20
→ popcorny:#define ISTRUE(x) (!!(x))               114.32.239.120 03/11 14:21
→ popcorny:可讀性跟你說的其實沒有那麼衝突          114.32.239.120 03/11 14:22


 作者  adrianshum (Alien)                                   看板  Programming
───────────────────────────────────────

不是覺得這種精簡寫法一定有錯,但同樣做法,真的要寫得那麼
非直覺嗎?為什麼不可以寫成:
int t = 0;
if (T1) t |= 1;
if (T2) t |= 2;       // 或寫成 t |= (1<<1);
if (Carry) t |= 4;    // t |= (1 << 2)

switch (t) {
  //..
}

再不然,寫成
int t = (T1? 1 : 0) | (T2 ? 2 : 0) | (Carry ? 4 : 0);
(以上 bitwise OR 可改用 +,看你自己喜好)

這樣單從 code 表面上就看得出是根據 T1, T2, Carry 來 turn-on
各個 bit。除非真的程式慢得連這一點點 performance 都要壓搾,
不然至少對我來說,上面的寫法比 !!T1 + 2 * !! T2 + 4 * !! Carry
看起來合理得多了

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 223.19.47.36

2011年12月20日 星期二

用gdb解決core dumped

在FreeBSD上執行程式./a.out出錯了 Segmentation fault (core dumped)

產生了副檔名為core的檔案: a.out.core

別急著在程式碼中插入print再compile

先試試這個:

gdb a.out a.out.core

詳細用法:

進階gdb
multi-thread與multi-process除錯
http://www.study-area.org/cyril/opentools/opentools/x1265.html

基本gdb
http://www.study-area.org/cyril/opentools/opentools/x1253.html

2011年12月11日 星期日

C++: static_cast, const_cast, reinterpret_cast, dynamic_cast

 作者  cppOrz (cppOrz)                                        看板  C_and_CPP
 標題  Re: [問題] static.const.reinterpret_cast和c-sty …
 時間  Sun Mar 12 23:26:23 2006
───────────────────────────────────────


Sorry, 有很多地方錯誤,不得不指出來。
熱心回答問題是好的,但是最好對內容有一定的把握,以免誤導。

這一篇介紹 C++ 的四個關鍵字: static_cast, const_cast, reinterpret_cast,
以及 dynamic_cast 的意義用法。

學習任何編程語言,只單純學習語法是不夠的,要適當的運用某種語言機制,最好
要先知道該機制存在的目的,和什麼狀況下會需要用到它,否則還不如不要使用。


※ 引述《godfat (godfat 真常)》之銘言:
: 靜態轉型,簡單地說就是強制轉型,
: 只是不合理的轉型 compiler 會告訴你不能這樣轉
: 例如兩個毫無關聯的物件
: class A{};
: class B{};
: A a;
: static_cast<B>(a); // error

static_cast 用於「相關型別」之間的轉換,其目的是讓編譯器知道,
程序員是「有意識」的進行轉換動作,並非不小心打錯。例如:

float f = 123.456f;

int i = f; // 把 float 轉為 int,編譯器一般會給警告
int v = static_cast<int>(f); // 用 static_cast 通知編譯器,設計意途確實如此


此外 static_cast 也可用於繼承體系內的 downcast,例如:

struct B { void f(); };
struct D : B { void g(); } d;

B &b = d;
...

b.f(); // ok
b.g(); // 錯誤,b 是 reference to B 物件

static_cast<D&>(b).g(); // ok, 因為程序員知道 b 確實是參照某個 D 物件


: : const_cast
: 單純去掉物件的常數性 和/或 volatile 性

這裏是對的。

: int const i = 10;
: i = 5; // error
: const_cast<int&>(i) = 5; // ok
: (這邊我不太確定是不是這樣寫,很少用)
: (總之觀念是這樣)

舉例有誤。

const_cast 只是把常數性去掉,目的是方便設計,但是企圖通過 const_cast
修改一個 const 物件,其結果未定義(視實作環境是否對該 const 物件採取
保護措施)。

至於 const_cast 使用的時機,通常是為了在不修改既有模組(例如沒有 source
code 的程式庫)的情況下,解決兩個模組之間 const-correctness 不相容的問題
(通常是不能改的那個模組,其設計不夠周延)。例如:

struct C { void f(); }; // 假設 C 是一個既存的模組(不能修改)

void foo(C const &c) // foo 是自行設計的模組,c 物件只作為輸入,不會被更動
{
  ...

  c.f(); // 由於 c 是 referece to const C 物件,但 C::f 並未被設計為
         // const,因此這樣寫是不成立的。(編譯器會給錯誤或至少警告)

  const_cast<C&>(c).f(); // ok, 用 const_cast 去掉 c 的常數性
}

: : reinterpret_cast
: 強迫 compiler 把輸入型別視為欲轉換的型別
: 這個不太好解釋…總之就是暴力轉型就對了 XD
: 直接把該記憶體位置的資料視為欲轉換的型別來看待

reinterpret_cast 幾乎等於暴力轉型(指 C-style 轉型),但不能去掉物件
的常數性(也就是還不夠暴力)。它用來處理任意型別之間的轉換,通常是為
了爭取運算或儲存空間的效率時,所採取的低階操作。例如:

int IP_Addr = 0;
char *p = reinterpret_cast<char*>(&IP_Addr); // 轉型為 char *,以便於
                                             // 以 Byte 為單位來處理
p[0] = 192;
p[1] = 168;
p[2] = 0;
p[3] = 1;

傳統的 C-style 轉型相當於 static_cast, const_cast, reinterpret_cast
三合一(也就是暴力加三級)。


: 你漏了 dynamic_cast<>
: 這跟 static_cast<> 有些類似

dynamic_cast 和 static_cast 無關。

: dynamic_cast<> 只能對於具有多形性型別轉型,
: 也就是他至少得要有一個 virtual function

這裏是對的。dynamic_cast 是 RTTI 的一部份,它用來檢測一個多型基底
類別的 pointer 或 reference 所參照物件的實際型別。例如:

struct B1 { virtual void f() = 0; };
struct D1 { void f(); };
struct X : D1
{
  void f();
  void g();
};

void foo(B1 *b1) // foo 模組並不知道 b1 所參照物件的實際型別
{
  if (dynamic_cast<D1*>(b1)) // 如果 b1 參照的是 D1 物件
  {
    b1->f();
  }
  else if (D1 *d1 = dynamic_cast<X*>(b1)) // 如果 b1 參照的是 X 物件
  {
    d1->g();
  }
  else
  {
    ...
  }
}

此例中,dynamic_cast 的用法也是所謂的 downcast,但和 static_cast
不同的是,前者用於偵測未知多型物件的實際型別,而後者用於對已知物件
(可以是多型物件或普通物件)的轉型。

可以看出,B1, D1, X 的繼承體系設計並不是很理想,因為在這種情況下,應該
直接利用 virtual function 的動態多型機制,而非依賴 dynamic_cast 偵測物
件的型別。例如:

struct B2
{
  virtual void f() = 0;
  virtual void g() = 0;
};

struct D2 : B2
{
  void f();
  void g();
} d2;

struct Y : D2
{
  void f();
  void g();
} y;

void foo()
{
  B2 *b2 = &d2; // b2 參照 D2 物件
  b2->g(); // 實際上會執行 D2::g,不必依賴 dynamic_cast

  b2 = &y; // 換一下,改參照 Y 物件
  b2->g(); // 實際上會執行 Y::g
}

顯然,B2, D2, Y 的設計比較合理。事實上,如果所有的模組都是自行設計,
dynamic_cast 是多餘的。但它之所以存在,其目的就是為了解決「既存模組
」不能修改的問題。(最常見的例子,就是缺乏源代碼的程式庫)

前面的例子中,如果 B1, D1 兩模組不能修改,為了新擴充 X 模組,又要和
舊模組相容,dynamic_cast 在此情況下就能派上用場(反過來說,如果沒有
dynamic_cast,問題就會變得很麻煩,而且各種替代方案都不太安全);但
如果完全是可自行控制的設計,就應該儘量遵循 B2, D2, Y 的方式來組織。


以上是 dynamic_cast 的功能中,downcast 的部份。另外,由於 C++ 支援
多重繼承的機制,dynamic_cast 亦可用於 crosscast(橫向轉型),例如:

struct Z : B1, B2 // 多重繼承
{
  void f();
  void g();
} z;

void foo()
{
  B1 *b1 = &z;
  B2 *b2 = dynamic_cast<B2*>(b1); // crosscast
}

這個例子可以用來說明,dynamic_cast 實際上的動作是「型別檢測」,而非
僅僅是「轉型」,後者只是它輸出的結果。此例中,b1 表面上是 (B1 *),但
它實際上參照的是 Z 物件,因此 dynamic_cast 偵測的結果,就是它可以順
利轉為 (B2 *) 。


: struct Base{virtual ~Base(){}};
: struct Derived: public Base{};
: Base pb = new Derived;
: Derived pd = dynamic_cast<Derived*>(pb);
: 如果 pb 真的是指向 Derived, 則 dynamic_cast<> 傳回 pb 的地址
: 否的話,pd 為 NULL
: boost 有提供 polymorphic_cast<>, 用處同 dynamic_cast<>
: 差別在於錯誤時不是傳回 NULL, 而是丟出 std::bad_cast
: polymorphic_downcast<> 則是專門用來在已知必然成功的 downcast
: 這種時候內建的 static_cast<> 和 dynamic_cast<> 其實都可以用,
: 只是 static_cast<> 沒有錯誤檢查,dynamic_cast<> 效率太差
: polymorphic_downcast<> 則使用 assert(); 來檢查是否成功
: http://www.boost.org/libs/conversion/cast.htm
: : c-style cast
: 等同於上面全部...
: 在 C++ 中替他們分類,避免造成混淆
: 不過對初學者來說的話這麼多才是混淆吧,我猜 :p
(下略)

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
推 godfat:感謝指導,雖然我覺得跟我講的沒啥衝突 XD 大概是太隨便了   03/13 01:44
※ 編輯: cppOrz          來自: 59.120.214.120       (03/13 04:50)
※ crazying:轉錄至看板 NTUGIEE_EDA                                 03/13 11:58
推 abovelight:推一個~了解更深入了                                  03/14 18:16

2011年11月8日 星期二

strtol

http://www.cplusplus.com/reference/clibrary/cstdlib/strtol/


Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* strtol example */
#include 
#include 

int main ()
{
  char szNumbers[] = "2001 60c0c0 -1101110100110100100000 0x6fffff";
  char * pEnd;
  long int li1, li2, li3, li4;
  li1 = strtol (szNumbers,&pEnd,10);
  li2 = strtol (pEnd,&pEnd,16);
  li3 = strtol (pEnd,&pEnd,2);
  li4 = strtol (pEnd,NULL,0);
  printf ("The decimal equivalents are: %ld, %ld, %ld and %ld.\n", li1, li2, li3, li4);
  return 0;
}