RSS   



  可打印版本 | 推薦給朋友 | 訂閱主題 | 收藏主題 | 純文字版  


 
 35  1/3  1  2  3  > 


 
主題: [資訊電機] [原創] 小神童C 語言筆記之一   字型大小:||| 
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#1 : 2004-6-13 03:01 PM     只看本作者 引言回覆

這麼多人想看我筆記
不幸的是.. 隨著時間流逝, 很多內容現在就算公佈, 意義也不是很大
因為計算機在快速演進當中
這一篇是還具備價值的一篇文章
當初受邀寫這篇文章時, 耗費了我將近兩個月查證資料&驗證結果
裏面的內容, 多數工程師級的人, 也始終沒搞清楚過
so, 就貼這麼一篇吧 ^^"

要特別聲明的是, 一直以來, 小神童任何發言 or 發文都是歡迎轉貼
這篇是例外, 任何情形下, 請勿轉貼



哇哩勒... 發文居然有限制長度... 這篇文章有38K... 一次貼不完...  昏倒中

Acute.

A-0 緣起
    西元1978年K&R 出版 The C Programming Language 之後, C 語
言很快的成為許多程式設計人員的最愛。由於計算機科學的迅速進步
, C 語言不再是UNIX作業系統下的一個語言, 它被廣泛的移植到許多
的作業系統中使用, 各家編譯器製作公司製作出來的C 語言編譯器逐
漸的改變C 語言的原始定義, C 語言的可攜性逐漸的受到考驗, 重新
為C 語言制定一個更精密、更全面性的定義變得刻不容緩。

    西元1983年, ANSI成立了一個委員會, 他們以編定一份語法明確
、與機器無關的C 語言定義為目標, 前後歷時多年而制定了C 語言的
新標準 ── ANSI C。

    西元1988年底, K&R 根據ANSI即將定案的C 語言建議草案, 重新
編寫The C Programming Language的第二版, 從該書的前後版本間,
可發現歷經十年的歷練後, C 語言的定義更成熟了, 原本受人詬病的
許多問題都已經給予明確的定義。令人激賞的是C 語言依舊維持它原
有的簡潔、自由、易用之特性, 而C 語言編譯器的製作公司也有一定
的規範可以依循。

    為了避免疏漏, 筆者將採用 The C Programming Language 的順
序, 該書的每一章將成為本文的章節名稱, 有些內容原本是列在該書
的附錄A 中, 筆者則視情況將之納入各章節之中。而為了加強效果,
筆者會在可能的情況下, 將前後期之定義予以列出, 再詳述其差異性
, 並適時的溶入個人之淺見。

A-1 型別、運算與運算式
A-1-1 關鍵字
    關鍵字 (保留字, Keyword)幾乎是每一種程式語言都會有, 在
K&R C 中, 共定義了28個關鍵字, 而ANSI C則定義了32個, 它們分
別詳列於表一及表二之中。

  ┌─────────────────────────┐
  │auto      break     case      char      continue  │
  │default   do        double    else      entry     │
  │extern    float     for       goto      if        │
  │int       long      register  return    short     │
  │sizeof    static    struct    switch    typedef   │
  │union     unsigned  while                         │
  └─────────────────────────┘
   表一 K&R C 中所定義的關鍵字

  ┌─────────────────────────┐
  │auto      break     case      char      const     │
  │continue  default   do        double    else      │
  │enum      extern    float     for       goto      │
  │if        int       long      register  return    │
  │short     signed    sizeof    static    struct    │
  │switch    typedef   union     unsigned  void      │
  │volatile  while                                   │
  └─────────────────────────┘
   表二 ANSI C 中所定義的關鍵字

    由上表中可發現, ANSI C共增加了const、enum、signed、void
及volatile等五個關鍵字, 而entry 則被刪除了。另外, K&R C 及
ANSI C都特別說明:有些編譯器可能保留fortran 及asm。

A-1-2 資料型別與常數
    在資料型別與常數表示法中, ANSI C擴充及新增了許多常用的、
明確的表示方式, 許多不曾在K&R C 中予以明確定義的用法, 都在
ANSI C中合法化了。

    在傳統K&R C 中, signed限定字是不存在的, 也沒有明確指出具
有unsigned char 的用法, 而ANSI C則明確的定義可以使用。同時
ANSI C為顧及亞洲語系的Double Bytes文字表示法, 特別定義一種稱
為wchar_t(寬文字型態, Wide Character) 的資料型態, 此種資料型
態定義於標準標頭檔 <stddef.h> 中, 它事實上是一種整數型態。

    K&R C 中對常數所能使用的附加字僅有'L' 及'l',而且這兩個附
加字限用於整數型態。我們假設int 為16個位元, 則下列的宣告式很
難決定其資料型態。

    #define MAXVALUE  65535    /* 該視為-1 或 65535 ? */

    ANSI C則新增了'U' 及'u' 兩個附加字, 明確的表示一個常數是
有號數或無號數, 以減少許多無謂的困擾。對於無號的長整數, 則混
用U 及L 成為'UL'。至於浮點數, 則新增'F' 及'f' 表示float。 並
且擴充'L' 及'l' 用以表示額外精度的浮點數(long double)。 對於
wchar_t 型態的常數表示法, 則使用'L' 為導前字。在下面的範例中
, 我們假設int 為16個位元。

    #define MAXVALUE  65535U   /* U 表示 unsigned        */
    #define SVALUE    65535    /* ANSI C 中將會被視為 -1 */
    #define LVALUE    65535L   /* long int 型態          */
    #define ULVALUE   65535UL  /* unsigned long 型態     */
    #define DLVALUE   12.34L   /* long double 型態       */
    #define DVALUE    12.34    /* double 型態            */
    #define FVALUE    12.34F   /* float  型態            */
    wchar_t ch=L'x' ;          /* L 為wchar_t 的導前字   */
    wchar_t str[]=L"張先生";   /* 寬字串表示法           */

    在K&R C 中, 浮點數有float 及double兩種, 而long限定字可對
float 作用, 亦即long float, 但long float與double具有相同的容
量, 因此浮點數至多可以有兩種精確度。float 與double的關係為:
後者的精確度一定大於或等於前者。

    ANSI C對於浮點數則有些修正, 它不再提及有關long float的用
法與容量, 但是增加了long double 的使用說明。換句話說, 對ANSI
C而言, 浮點數至多可以有三種精確度, 分別為:float、double、
long double 三種, 而後者的精確度一定大於或等於前者。

[Acute 在 2004-6-14 12:51 AM 作了最後編輯]



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#2 : 2004-6-13 03:02 PM     只看本作者 引言回覆

A-1-3 逸出序列
    逸出序列(Escape Sequencess) 的表示法上, ANSI C除了多增加
幾個碼外, 最大的改變在於定義十六進制表示法。表三及表四分別列
出K&R C 及ANSI C的逸出序列。

┌──────────────┬──────────────┐
│符號     表示法 說明        │ 符號     表示法 說明       │
├──────────────┼──────────────┤
│NL (LF)  \n     新列        │ HT       \t     水平定位   │
│BS       \b     退位        │ CR       \r     歸位       │
│FF       \f     換頁        │ \        \\     反斜線     │
│'        \'     單引號      │ 000      \000   八進位數值 │
│NUL      \0     空字元      │                            │
└──────────────┴──────────────┘
表三  K&R C 定義的逸出序列

┌──────────────┬──────────────┐
│符號     表示法 說明        │ 符號     表示法 說明       │
├──────────────┼──────────────┤
│NL (LF)  \n     新列        │ HT       \t     水平定位   │
│VT       \v     垂直定位    │ BS       \b     退位       │
│CR       \r     歸位        │ FF       \f     換頁       │
│BEL      \a     發出警告訊息│ \        \\     反斜線     │
│?        \?     問號        │ '        \'     單引號     │
│"        \"     雙引號      │ 000      \000   八進位數值 │
│hh       \xhh   十六進位數值│ NUL      \0     空字元     │
└──────────────┴──────────────┘
表四  ANSI C定義的逸出序列

A-1-4 三符號序列
    ANSI C為了國際標準的問題, 特別增加了『三符號序列』, 這是
在K&C C 中找不到的。

    C 原始程式中的符號集是七個位元的ASCII 符號的一部份, 但卻
又是ISO 646-1983 Invariant Code Set 的一個超集合(Superset),
為了讓程式可以在ISO 簡化過後的符號集之下作業, 下面所列出來的
三符號序列, 都會轉換為與它們相對應的單一符號。此一轉換工作,
會在進行任何其他處理之前完成。

    ??=    #             ??(    [             ??<    {
    ??/    \             ??)    ]             ??>    }
    ??"    ^             ??!    |             ??-    ~

A-1-5 宣告
    在宣告上, ANSI C改變了K&R C 的許多內容, 除了前面曾經提過
的新增或擴充的資料型態外, 另外還有兩個重要的新增限定字 ──
const、volatile。

    const 限定字用來表示一個資料項目『僅能讀取』, 但是不能改
變。賦予此類型之資料項目的數值內容, 必須在『初值設定』時給予
。至於若程式設計人員企圖更改具有const 特性的資料項目, 該如何
處理? ANSI C則未予指定。volatile限定字則與const 的用途相反,
它表示該資料項目是『可更改的』。

    const 限定字對程式設計上最大的用途在於:當程式採用傳址呼
叫(call by address) 傳遞參數時, 為避免程式設計人員一時疏忽誤
改參數內容, 可在函式參數列的宣告上使用此限定字, 讓C 編譯器幫
您做檢查動作。

    const 與volatile對C 編譯器則有較大的用途, 它除了上述的檢
查用途外, 另外一項很重要的用途在於最佳化處理上, 下面的例子說
明編譯器『可能的』動作現象。

    /* 範例一 */
    char  *Name1="Emperor";
    char  *Name2="Emperor";

    /* 範例二 */
    volatile char *Name1="Emperor";
    volatile char *Name2="Emperor";

    /* 範例三 */
    const char *Name1="Emperor";
    const char *Name2="Emperor";

    在範例一中, 編譯器無法辨識兩個指標是否可以指到同一個位址
上, 因為編譯器無法獲知程式是否會在執行時期改變字串的內容, 此
時編譯器僅能將它比照範例二處理, 假設程式會改變字串內容, 而在
記憶體中存放兩個"Emperor" 字串。

    在範例三中, 編譯器知道這兩個字串均屬不能改變的內容, 因此
僅需在記憶體中存放一個"Emperor" 字串即可。

A-1-6 型別轉換
    在K&R C 的定義中, 如果未曾使用鑄形(cast)進行型別轉換, 數
值運算中的型別轉換會自動根據運算元的型別, 轉換成精度較高的運
算元之型別, 再進行運算。K&R C 對於算術轉換(Arithmetic Conversion)
的完整描述如下:

    首先, 若運算元的型態為char或short int 則轉換為 int, 如果
運算元的型態為float 則轉換為double。
    然後, 若有一個運算元為double, 則另一個運算元也轉換為
          double, 運算結果的型態也為double。
    否則, 若有一個運算元為long, 另一個運算元就轉換為long, 運
          算結果的型態也為long。
    否則, 若有一個運算元是unsigned, 另一個運算元也轉換為
          unsigned, 運算結果的型態也為unsigned。
    否則, 兩個運算元都一定是 int, 運算結果也為整數。

    在ANSI C中則將整個型別轉換變得較合理化, 但也相對的複雜化
。ANSI C對算術轉換的過程定義如下:

    首先, 若有一個運算元為long double , 則另一個運算元也轉換
          為long double。
    否則, 若有一個運算元為double, 則另一個運算元也轉換為double。
    否則, 若有一個運算元為float , 則另一個運算元也轉換為float。
    否則, 就對兩個運算元進行整數的提昇。若有一個運算元為
          unsigned long,則另一個運算元也轉換為 unsigned long。
    否則, 如果有一個運算元是long, 而另一個是unsigned, 則轉換
          的效果就由long是否能表示所有的unsigned的值而定。
          若可以, 則unsigned轉換為long,
          否則, 兩者都轉換為 unsigned long。
    否則, 若有一個運算元為long, 另一個運算元就轉換為long。
    否則, 若有一個運算元是unsigned, 另一個運算元就轉換為unsigned。
    否則, 兩個運算元都一定是int。

    由上面的描述中, 您可發現ANSI C去除float 必須轉換為double
的問題; 不再指定unsigned一定得繼承, 一切依照情形而定; 增加
unsigned long 的轉換法則。

    若您用慣了PC上16 bit的C 編譯器, 或許會對『如果有一個運算
元是long, 而另一個是unsigned, 則轉換的效果就由long是否能表示
所有的unsigned的值而定。』這句話感到相當不可思議, 因為在您的
觀念裡:『long比int 大』是必然的, 但是在C 的定義中, long只要
不比int 小就可以了。以32 bit的WC或MSC[註一] 而言, 他們都是讓
long與int 一樣大。因此, 下面的例子將會因編譯器而異。

          long l;
          unsigned int i;

          l*i ;

    在PC上, 對16 bit的編譯器而言, long型態大於int 型態, 因此
運算式被解釋成:
                         l * (long)i;    /* K&R  C */
                         l * (long)i;    /* ANSI C */

    同樣在PC上, 對32 bit的編譯器而言, long型態等於int 型態,
因此運算式就解釋成:
                         l * (long)i;            /* K&R C */
          (unsigned long)l * (unsigned long)i;   /* ANSI C*/

    您可以發現, 對某些範圍的數值而言, 不同的編譯器將會得到不
同的結果。

   [註一] 筆者撰寫本文時, MSC 的32 bit編譯器並未正式發表, 筆
          者僅根據Microsoft 公司於Microsoft Optimizing C/C++
          Compiler V7.0 中, 對其32 bit編譯器規格的說明資料中
          找尋答案。將來MSC 的32 bit編譯器正式發表時, 是否會
          更改long int的定義則為未知數。



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#3 : 2004-6-13 03:02 PM     只看本作者 引言回覆

A-2 函式與程式結構
    由於C 語言本身非常的簡潔, 因此對一個C 的程式而言, 函式佔
了相當重要的地位, 許多基本的工作, 如:I/O 動作、記憶體管理等
等, 都是要透過函式來達成。函式的觀念, 讓我們可以把龐大的動作
予以細分, 使程式能模組化、結構化, 讓程式可較容易維護; 而一些
通用的函式更可供許多程式共用, 減輕程式設計上的負擔, 不必一切
重頭開始。

    由於函式的重要性, ANSI C對於函式採用較嚴謹的方式定義, 許
多K&R C 中令人爭議的話題都有些改變, 這些改變該稱之為好或壞可
能有歧見, 但可以肯定的一點是, 對於那些粗心大意的程式設計人員
而言, 這些改變絕對有益無害。當然, ANSI C僅是擴充原來的定義,
使它變得更嚴謹, 如果程式設計人員不採用這些擴充定義, 則結果將
跟K&R C 相同。

A-2-1 函式宣告
    在K&R C 中, 函式的宣告僅用於傳回值的型態不為int 時, 而且
僅需要針對傳回值進行宣告即可, 其餘的並不重要。這點可以輕易的
從K&R C 的範例中輕易看出。底下是擷取自K&R C 第一版之範例程式
的片段。

    /* 範例四 */
    #define MAXLINE         100

    main ()     /* rudimentary desk calculator */
    {
       double sum, atof ();
       char line[MAXLINE];

       sum=0;
       while (getline(line, MAXLINE) > 0)
          printf("\t%.2f\n", sum += atof(line));
    }

    在ANSI C中, 函式的宣告已經不再只是用來表達傳回值的型態,
它還涵蓋了傳遞參數的型態, ANSI C稱呼這種宣告方式為『函式原型
(Function Prototype)』。

    有了函式原型之後, 編譯器不但可以了解函式的傳回值為何, 更
可以知道呼叫該函式時, 應該傳遞的參數數目及參數型態, 編譯器可
藉此檢查程式採用的參數是否有錯、是否需要做型別轉換等潛伏性的
錯誤。

    範例四的程式片段在ANSI C中, K&R 將它改寫為底下的樣子。

    /* 範例五 */
    #include <stdio.h>   /* 新增 */

    #define  MAXLINE     100

    main ()
    {
       double sum, atof(char []);  /* 增加參數型態 */
       char line[MAXLINE];
       int getline(char line[], int max);  /* 新增 */

       sum=0;
       while (getline(line, MAXLINE) >0)
          printf ("\t%g\n",sum += atof(line));
       return 0;      /* 新增 */
    }

    仔細觀看範例四及範例五, 這兩個程式在寫法上有相當大的差異
。首先, 範例五有 #include <stdio.h> 的動作, 原因在於printf()
的函式原型是放在stdio.h 檔中。其次, 對於atof()及getline() 的
宣告上, 都改為完整的函式原型。最後, main()也必須傳回一個數值
, 因為在沒有指定傳回值型態的情形下, 編譯器會自動將之視為int。


A-2-2 宣告形式參數
    在傳統K&R C 的形式參數之宣告方式是撰寫在函式名稱與大括號
之間, 而函式名稱後方的小括號內僅存放形式參數的名稱。參數的宣
告語法及表示方式為:

       函式名稱 (形式參數列)
       宣告形式參數的型態
       {
          /* 函式內容 */
       }

    在ANSI C中, 為了加強型態檢查, 乃將宣告形式參數的動作搬移
到函式名稱後方的小括號內。這種宣告形式參數的作法, 事實上是參
考C++ 的作法。ANSI C之參數的宣告語法及表示方式為:

       傳回值型態 函式名稱 (宣告形式參數的型態)
       {
          /* 函式內容 */
       }

    為了達到與K&R C 相容, 使全球為數眾多的C 原始程式不必因標
準的改變而大幅改寫, ANSI C並不強制規定形式參數的宣告方式必須
以新的方式撰寫, 而將此種宣告方式視為一種『建議』。換句話說,
傳統K&R C 中的形式參數之宣告法則, 是被ANSI C繼續認可的, 只是
ANSI C並不希望程式設計人員繼續沿用它。



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#4 : 2004-6-13 03:03 PM     只看本作者 引言回覆

A-2-3 可變動的參數列
    函式的參數數目不定, 是C 語言的一項相當重要之特性, 在傳統
K&R C 中, 由於不做參數的型態檢查, 也沒有定義函式原型的用法,
因此並不會對可變動的參數列產生任何困擾。但是對ANSI C而言, 則
必須有一種特定的表示方法, 讓C 編譯器曉得函式的參數數目不定,
否則會形成困擾。為此, 在新的標準中, 制定了『可變動的參數列』
的宣告方法, 其方式如下:

       傳回值型態 函式名稱 (固定參數部份的參數型態 ,...);

    從上面式子中可知, ANSI C以 ",..." 表示參數數目不定。儘管
函式的參數數目不定, 但是通常都會有幾個一定要有, 而且固定要有
的參數也一定得擺在參數列的前面, 因此, 程式仍舊可宣告這幾個參
數的型態, 讓C 編譯器幫忙做型態檢查。例如:printf()這個標準函
式的函式原形通常寫為:

    int printf (char *,...);

A-2-4 void與函式
    "void"是ANSI C增加的一個關鍵字, 這個關鍵字對C 而言用途頗
廣, 舉凡宣告皆能派上用場。在C 程式的函式原型宣告上, void具有
兩種用途。

    第一、當函式沒有任何形式參數時, 則函式原型的小括號中應擺
          放void。如果將void省略掉, 則編譯器會將之視為傳統
          K&R C 的表示方法, 而忽略參數型態檢查。換句話說, 如
          果程式企圖傳遞參數給一個不需要參數的函式, 在K&R C
          的領域裡, C 編譯器無法發現這種錯誤; 對ANSI C而言,
          只要遵守函式原型的宣告法則, 則C 編譯器就能發現這種
          錯誤。

    第二、當函式沒有傳回值時, 函式傳回值的型態也是用void進行
          宣告。如果將void省略, 則根據C 語言的規則, 表示此函
          式會傳回一個整數。

    善用void於函式原型的宣告上, 會帶來許多意想不到的好處。尤
其對上述的第二點而言, 如果程式企圖去使用一個不會傳回任何數值
的函式的傳回值, 則該動作將是一種『危險的、未定義的、不可預知
』的動作。在K&R C 中, 要想找出這種錯誤是相當困難的; 對ANSI C
而言, 只要程式設計人員有妥善的宣告函式原型, 則C 編譯器會自動
檢查這種錯誤。

A-2-5 函式呼叫時
    由於ANSI C引進了函式原型的觀念, 因此, 對於是否有使用函式
原型, 就形成了引數傳遞時, 引數轉換的差異性, 當然, 也嚴重的影
響到函式取得之參數的正確性。

    基本上, 程式撰寫時, 應保持一致的風格, 如此可讓程式容易觀
看, 也較不容易引起無謂的困擾; 但是, 在一個大型軟體程式的製作
過程中, 通常是由許多人共同完成, 如果加上中途的人員異動、或是
程式的維護時期一併計算, 則參與一個軟體程式發展或維護的人員將
不計其數。最令人困擾的, 莫過於這些人很可能都擁有個人的寫作風
格, 如果這種寫作風格牽涉到新、舊函式的表示方法, 可就令人相當
憂心, 因為整個軟體程式很可能因為這個原因而隱藏著潛伏性的錯誤


    到底混用新、舊函式的寫法會引發什麼問題呢? 底下筆者將詳細
介紹K&R C 與ANSI C對於引數及參數 [註二] 轉換的差異性。您將發
現, 這些轉換過程類似於前面介紹過的:『算術轉換』, 只是, 問題
在這個地方會顯得很棘手, 因為它與程式寫作風格息息相關。

    如果函式宣告是使用舊的寫法, 亦即採用K&R C 的寫法, 則引數
(或參數) 在傳遞之前所做的提昇或轉換動作為:

    首先, 所有的float 型別之引數均轉換為double型別;
    其次, 所有的char或是short 提昇為int;
    以及, 所有的陣列名稱轉換為指標。

    在ANSI C所定義的函式寫法則有所不同, 由於函式原型的存在,
因此, 所有的引數轉換動作, 都會以轉換成函式原型所定義的型態為
準。如果函式原型的宣告式上, 宣告參數列時最後是用",..."結束,
則引數的提昇動作會以K&R C 所定義的方式進行。

    此外, ANSI C特別強調, 『如果在叫用的時候, 引數個數與在函
式定義中參數的數目不同, 或是經過提昇後的引數型別與對應參數的
型別不相同, 則叫用的結果是沒有定義的。』

    至於實體函式的部份, 則與函式原型無太大牽連, 純粹看程式採
用何種寫法, 若採用K&R C 之寫法, 則按照K&R 所定義的方式提昇參
數; 若採用ANSI C的寫法, 則完全依照宣告之型態進行, 不做任何的
轉換提昇動作。有些C 編譯器對於有函式原型存在的時候, 會比對提
昇後的參數是否與函式原型所定義之參數是否相同, 但是, 也僅是發
出警告訊息而已。

   [註二] 通常, 函式叫用中傳遞的資料稱為『引數(Argument)』,
          函式定義中或函式實體所指出用來接收外來資料者, 稱之
          為『參數(Parameter)』。

    從上面的比較中可以發現一件令人很頭痛的問題, 如果函式宣告
是使用K&R C 的寫法, 則所有轉換動作都是C 語言內定的; 如果函式
宣告是使用ANSI C的寫法, 則轉換過程依照函式原型而定。

    如果我們沿用A-1-6 節中, 有關16 bits 及32 bits C 編譯器對
基本資料容量的定義, 有兩個很嚴重的問題就會發生。首先, 有關
float 參數的問題會發生在PC上16 bits 及32 bits C 編譯器上, 範
例六、範例七、範例八是有關新、舊函式寫法上的問題。而32 bits
的C 編譯器則另有一個更嚴重的問題在於『整數提昇』上, 其情形與
float、double 的關係是相同的, 因此不再舉例。但是, 因為程式中
通常對『整數類別』的資料使用頻繁, 所以特別容易出錯, 不得不特
別小心。

    /******************** 範例六 ********************/
    /* 檔案一 */
    float func1 (float, float);
    main ()
    {
        float x=1.03, y=2.04, z;

        printf ("%f\n",func1 (x,y));
    }

    /* 檔案二 */
    float func1 (x,y)
    float x,y;
    {
        printf ("%f\n%f\n",x,y);
        return (x+y);
    }

    /* 用MSC V6.0 編譯後執行之結果為 */
    2.320000
    0.000000
    2.320000
    結果顯然錯了

    /******************** 範例七 ********************/
    /* 檔案二改為 */
    float func1 (float, float);
    float func1 (x,y)
    float x,y;
    {
        printf ("%f\n%f\n",x,y);
        return (x+y);
    }

    /* 用MSC V6.0 編譯後執行之結果為 */
    編譯時有警告訊息出現, 執行結果與範例六相同。


    /******************** 範例八 ********************/
    /* 檔案二改為 */
    float func1 (float x, float y)
    {
        printf ("%f\n%f\n",x,y);
        return (x+y);
    }

    /* 用MSC V6.0 編譯後執行之結果為 */
    1.030000
    2.040000
    3.070000
    結果總算對了

    由這三個範例可輕易發現, 由於第一個檔案有宣告函式原型, 因
此, 引數不會由float 提昇為double; 但是, 範例六的檔案二是純粹
的K&R C 之寫法, 因此, C 編譯器會對參數做提昇動作, 亦即, 它會
把參數當成double處理, 所以會得到錯誤的執行結果。在範例七中,
我們嘗試著採用K&R C 的寫法, 但是增加函式原型的定義, 此時, C
編譯器會發現參數提昇後與函式原型的定義不符合, 因此發出警告訊
息, 但是結果依然錯誤, 這點與ANSI C所定義之『參數型別不符合,
則叫用函式之結果為沒有定義』是相符合的。到了範例八, 則全面採
用ANSI C所定義之格式撰寫, 即使函式原型並不曾在檔案二中宣告,
但是結果依然正確。

    至此, 您是否警覺到混用新、舊寫法是一種很危險的動作了, 無
論個人之寫作風格如何, 當集體創作一項軟體程式時, 切記! 採用何
種寫法一定要事先規範, 而且強制要求所有參與人員均需遵守, 否則
除錯工作將會令人受不了, 況且, 有可能發生透過除錯器也很難找到
錯誤的現象。此外, 養成宣告函式原型是一種相當好的習慣, 無論用
哪一種方式撰寫C 程式, 引數傳遞上的錯誤, 都能藉由C 編譯器的型
態檢查發現。



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#5 : 2004-6-13 03:04 PM     只看本作者 引言回覆

A-2-6 初值設定
    賦予變數初值對絕大部分的程式而言, 是一種經常且必要的動作
。然而, 在K&R C 的定義中, 變數的初值設定有相當大的限制, 甚至
在很多情形下, 變數是無法賦予初值的。在ANSI C的定義中, 這些問
題有了戲劇性的變化, ANSI C允許程式對所有的變數賦予初值, 不過
有些較特殊的資料型態, 仍舊有限定初值的設定方式。

    對K&R C 的定義而言, 賦予變數初值的規則可簡單歸納為:

     1) 除了等位(union) 外, 所有的外部變數均可設定初值。
     2) 除了等位外, 所有的靜態變數均可設定初值; 不論該靜態變
        數是屬於外部變數或自動變數。
     3) 對自動變數而言, 僅有基本型態的變數, 如:char、int、
        float..., 等少數變數可設定初值外, 其餘的延伸或組合型
        態, 如:陣列、struct、union..., 等自動變數均不可設定
        初值。

    對ANSI C的定義而言, 則全面允許程式賦予變數初值。但是對於
等位賦予初值的方式則有限制。ANSI C規定, 賦予等位之變數初值時
, 僅能以等位中第一個元素的型態賦予初值。

    底下是一個簡單的範例, 每個宣告式的後面都會有一個註解, 標
示該動作於K&R C 及ANSI C中, 是否屬於合法的宣告式。為了方便起
見, 筆者先定義幾個結構及等位。

    /***************************/
    /** define struct & union **/
    /***************************/
    typedef struct tagPOINT {
       int x;
       int y;
    } POINT ;

    typedef struct tagREG1 {
       char al;
       char ah;
    } REG1;

    typedef union tagREG {
       int  ax;
       REG1 h;
    } REG;

    /******************************************/
    /*** init-declarator in K&R C or ANSI C ***/
    /******************************************/
    /****** global variable ******/
                              /* K&R C      ANSI C */
    char  gc='a';             /* pass       pass   */
    char  gs="Emperor";       /* pass       pass   */
    int   gi=123;             /* pass       pass   */
    int   gia[]={10,20,30};   /* pass       pass   */
    long  gl=123L;            /* pass       pass   */
    POINT gp={10,20};         /* pass       pass   */
    REG   gu1=100;            /* ERROR      pass   */
    REG   gu2={1,2};          /* ERROR      ERROR  */

    /****** static variable ******/
                                     /* K&R C      ANSI C */
    static char  gsc='a';            /* pass       pass   */
    static char  gss="Emperor";      /* pass       pass   */
    static int   gsi=123;            /* pass       pass   */
    static int   gsia[]={10,20,30};  /* pass       pass   */
    static long  gsl=123L;           /* pass       pass   */
    static POINT gsp={10,20};        /* pass       pass   */
    static REG   gsu1=100;           /* ERROR      pass   */
    static REG   gsu2={1,2};         /* ERROR      ERROR  */

    func ()
    {
       /****** local variable ******/
                                 /* K&R C      ANSI C */
       char  lc='a';             /* pass       pass   */
       char  ls="Emperor";       /* ERROR      pass   */
       int   li=123;             /* pass       pass   */
       int   lia[]={10,20,30};   /* ERROR      pass   */
       long  ll=123L;            /* pass       pass   */
       POINT lp={10,20};         /* ERROR      pass   */
       REG   lu1=100;            /* ERROR      pass   */
       REG   lu2={1,2};          /* ERROR      ERROR  */

       /****** static variable ******/
                                       /* K&R C    ANSI C */
       static char  lsc='a';           /* pass     pass   */
       static char  lss="Emperor";     /* pass     pass   */
       static int   lsi=123;           /* pass     pass   */
       static int   lsia[]={10,20,30}; /* pass     pass   */
       static long  lsl=123L;          /* pass     pass   */
       static POINT lsp={10,20};       /* pass     pass   */
       static REG   lsu1=100;          /* ERROR    pass   */
       static REG   lsu2={1,2};        /* ERROR    ERROR  */

       /* ...... */
    }

A-2-7 前置處理器
    前置處理器 (Preprocessor) 是C 語言相當重要的一項能力, 幾
乎每個程式都會用到的#include與#define 都是屬於前置處理器的處
理範圍。ANSI C在前置處理器上的變動並不多, 但是對K&R 的著作而
言, 前後兩版卻有相當大的變動, 原因是K&R 第一版將前置處理器的
許多內容都放在附錄A中。也造成許多人搞不清楚K&R C 所定義的前
置處理器與ANSI C所定義的有多大之差距。在本節中, 筆者將詳細介
紹此一內容, 而不論是否為ANSI C新增或修正過的內容。

A-2-7-1 引入檔案
    引入檔案(File Include)的動作, 在K&R C 中, #include的用法
相當簡單, 程式僅需將欲引入的檔案用一組雙引號("")括起來即可。
請看下面的程式片段:

    /* 範例九 */
    #include "stdio.h"
    #include "extio.h"

    上面的例子對一個經驗豐富的程式設計人員而言, 不會產生任何
困擾, 因為"stdio.h" 是屬於標準 [註三] 的引入檔, 而"extio.h"
不是。但是對一個並不很了解C 語言的程式設計人員而言, 他很難分
辨兩個檔案的歸屬問題。

    針對上述的問題, K&R 在制定C 語言時已經考慮到了, 他們用角
括號(<>)來表示標準的引入檔, 而原來的雙引號用法, 則保留給使用
者自定的引入檔使用。但是不知道為什麼, K&R 在他們的著作之第一
版時, 並未明確的說明具有角括號的用法, 僅在該書的附錄A中簡略
介紹一番, 直到第二版時, 才在介紹#include時將角括號及雙引號的
差別予以詳細介紹。事實上, 有不少程式設計人員在拜讀完K&R 的第
一版後, 並不曉得雙引號與角括號對#include敘述有何差異性。上述
的範例九採用符合C 標準的寫法為:

    /* 範例十 */
    #include <stdio.h>
    #include "extio.h"

    如此, 範例十的可讀性變得比範例九高, 而且任何人都可輕易看
出這兩個檔案的來源, 不會感到疑惑。

   [註三] 標準的引入檔, 意指:此檔是標準C 語言所定義的引入檔
          , 諸如:stdio.h、stdlib.h、limits.h 等。但是, 大部
          分的C 編譯器都將它擴展為:舉凡C 編譯器提供的引入檔
          , 均屬『標準的』引入檔。例如:dos.h 等。

    對一個符合C 標準的C 編譯器而言, 遇到使用角括號的#include
敘述, 編譯器只會在存放標準引入檔的目錄中找尋, 找不到則發出錯
誤訊息。遇到使用雙引號的#include敘述, 編譯器則會先在目前的工
作目錄中找尋, 找不到時再到存放標準引入檔的目錄中找尋, 再找不
到才會發出錯誤訊息。採用這麼複雜的法則, 大概是為了使許多偷懶
的人可從頭到尾都用雙引號, 但是, 這實在是一個很差勁的壞習慣,
開發程式時應遵照標準, 除了程式容易了解外, 編譯器編譯程式的速
度也會比較快。

    有一種很容易犯的錯誤, 常會發生在一個剛開始使用C 語言的使
用者身上, 請思考下面這個範例, 答案可在上面的文句中找到。

    /* 範例十一 */
    #include <stdio.h>
    #include <extio.h>

A-2-7-2 巨集替換
    『巨集替換』是C 語言一項很重要的特色, 雖然它有一些無法彌
補的缺陷、有許多容易令人犯錯的缺點、還有一點功能上的限制, 使
得使用時必須非常小心, 否則相當不容易找到因巨集而引發的併發症
。但是, 幾乎每個C 程式都必須使用它, 小到定義一個常數, 大到定
義一群『簡易的、明確的』程式碼, 巨集讓C 程式的可讀性增色不少
、也實質的提昇程式維護的簡易性。

    K&R C 對巨集替換的定義相當單純, 大部分懂C 語言的人都曉得
如何運用; 而ANSI C則額外的擴充了幾個使用方法, 使得巨集的能力
更加無遠弗屆, 讓許多從前做不到的動作變成可能。可惜的是, 對於
巨集容易引發錯誤的現象依舊無法消除; 或許, 除非採用類似C++ 的
inline function 作法, 讓編譯器在編譯程式時, 將巨集當作實際的
函式, 根據語言規則解釋、編譯, 否則就既有的替代模式而言, 根本
無法解決因直接替換而引發的併發症。

    ANSI C在巨集定義中增加了# 及##兩個運算。這兩個運算主要都
是針對字串常數或文字常數而設計。由於ANSI C對於巨集的說明相當
繁瑣, 而且前半部與舊規則是相同的, 因此筆者僅擷取其後半段的說
明, 解釋這兩個運算的用途。

    首先, 若在巨集替換過程中, 巨集參數的前方有一個#, 則會用
雙引號(") 括住該參數的內容, 若該參數的內容中具有\ 或" , 則自
動加入一個\ 字元。

    其次, 若在巨集替換過程中, 包含了一個##運算, 則巨集替換後
, 會刪除每一個##以及兩邊的任何空白符號, 因此, 就把兩個相鄰的
語法單元連接起來, 構成一個新的語法單元。若因而造成不正確的語
法單元, 或是結果會因處理##運算的次序而異, 則結果將是沒有定義
的。另外, 替換用的巨集之開始或結尾處都不得有##。

    在這兩個巨集指令中, 都會一再反覆進行替換工作, 以進一步找
出被定義過的辨識名稱。但是, 如果在某一次展開過程中, 辨識名稱
被替換過了, 而在反覆檢查過程中又出現了相同的識別名稱, 則不再
替換該識別名稱。

    即使巨集展開後, 其結果是由#開始, 它也不會被當成事前置處
理程式的指引敘述看待。

    /* 範例十二 */
    #define Cat1(str1,str2) #str1 ## #str2
    #define Cat2(str1,str2) str1 ## str2
    #define Cat3(str1,str2) Cat2(str1,str2)
    #define dPrint(arg) printf(#arg " = %d\n",arg)
    #define sPrint(arg) printf(#arg " = " arg ## "\n")

    main ()
    {
        int abc=1000;

        /* 巨集參數的前方有一個#, 則會用
           雙引號(") 括住該參數的內容      */
        sPrint (Cat1(c:\\,test.dat));
        sPrint (Cat1("c:\\","test.dat"));  /* error */

        /* ##運算, 會刪除每一個##以及兩邊的任何空
           白符號, 把兩個相鄰的語法單元連接起來   */
        sPrint (Cat2("c:\\  ","  test.dat"));

        dPrint (100/5);

        /* 如果在某次展開過程中, 辨識名稱被替換過了,
           而在反覆檢查......, 則不再替換該識別名稱,
           因此, 不可直接使用Cat2                    */
        dPrint (Cat3(Cat3(a,b),c));
    }

    執行結果
    Cat1(c:\,test.dat) = c:\test.dat
    Cat1("c:\\","test.dat") = "c:\\""test.dat"
    Cat2("c:\\","test.dat") = c:\test.dat
    100/5 = 20
    Cat3(Cat3(a,b),c) = 1000



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#6 : 2004-6-13 03:04 PM     只看本作者 引言回覆

A-2-7-3 條件編譯
    條件編譯是使一份原始程式能具多用途的基本條件, 例如:可根
據不同的編譯器之需求、或根據同一軟體的不同版本, 讓編譯器編譯
不大相同的程式碼。因此, 在軟體程式的維護上, 條件編譯是不可或
缺的功能。

    K&R C 所定義的條件編譯語法相當簡單, 其內容如下:

    判斷條件的指令
        #if 運算式          或
        #ifdef 常數名稱     或
        #ifndef 常數名稱

    條件不成立時
        #else

    終止條件編譯之區段
        #endif

    ANSI C除了沿用K&R 定義的部份外, 增加了#elif 及defined 兩
道指令, 其完整內容如下:

    判斷條件的指令
        #if 運算式          或
        #ifdef  常數名稱     或
        #ifndef 常數名稱

    條件不成立時
        #elif 運算式        或       /* 新增 */
        #else

    終止條件編譯之區段
        #endif

    在上述『運算式』中, 可引用新增的指令 defined。例如:

       #ifdef MSC70
       #if defined(MSC70)

    是完全相同的意思, 但是:

       #if defined(MSC70) || defined(BC31) || defined(TC31)

    卻無法用K&R 所定義的條件編譯法則表示出來。

A-2-7-4 ANSI C新增的部份
    底下的前置處理控制列, 會讓編譯器顯示一道包含『語法序列』
內容的訊息。

    #error    語法序列

    底下的前置處理控制列, 會讓前置處理器進行一些與系統有關的
動作, 不認識的語法序列內容則不予理會。在此所指之『與系統有關
』是由各編譯器依實際需求而制定, 亦即, 並無一定準則。

    #pragma   語法序列

    底下的前置處理控制列, 不會有任何效應。

    #

A-2-7-5 預先定義的名稱
    ANSI C定義了幾個預定的常識名稱及內容, 這些內容的變更由編
譯器負責, 無法用其他的前置處理命令更動。

    __LINE__  十進位常數, 內容為:目前的原始程式的列號。
    __FILE__  文字常數, 內容為:目前正在編譯的原始檔案之名稱。
    __DATE__  字串常數, 內容為:進行編譯的日期, 其格式為,
              "Mmm dd yyyy"。
    __TIME__  字串常數, 內容為:進行編譯的時間, 其格式為,
              "hh:mm:ss"。
    __STDC__  此常數值為1, 只有在合乎ANSI標準的系統中, 這個
              辨識名稱才會被定義為1。



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#7 : 2004-6-13 03:04 PM     只看本作者 引言回覆

A-3 指標與陣列
    指標與陣列一直是學習C 語言的重點之一, 尤其是指標, 它強大
的功能及彈性, 讓許多程式設計人員對其又愛又恨。妥善的使用指標
, 可讓程式看起來簡潔, 更能讓C 程式做到許多不容易達成的動作;
相對地, 如果指標使用過度或不當, 程式將令人不易看懂, 而且若不
慎引起錯誤也不容易除錯。

    基本上, ANSI C並沒有對指標做明顯的異動, 除了擴充兩種指標
的使用方法外, 唯一真正更動指標定義的只有兩個指標運算, 它把原
本K&R C 認為不當的指標運算正式合法化。

A-3-1 void *
    void型別之指標是ANSI C新增的指標型態, ANSI C對這種指標的
說明為:『任何指標在不遺失資訊的大前提下, 可能可以轉換微型別
void *; 如果把結果再轉換成原來的指標型別的話, 就會得到相同的
指標。在討論指標型別轉換時, 曾經討論到需要有明顯的轉型運算,
在此, 則允許指標可以存入型別void *, 也可以接受void *型別的指
標, 而且兩者還可以互相比較。』

    由上面的說明中可發現, ANSI C允許void *型別的指標直接使用
在設定及比較運算中, 至於其他對指標的混和運算, 則需使用轉型。

    在K&R C 中, 遇到需要一般性的指標需求時, 通常都以char *表
示之, 因為char型別的資料項目具有最低的記憶體校正需求 [註四];
在ANSI C中, 顯然不需要再借用char *表示一般性指標, 可以直接使
用『沒有型別』的void *指標。

   [註四] 對某些系統而言, 有些資料項目必須在記憶體中適當的邊
          界上(boundry),因此指標的轉型過程中, 必須考慮到新的
          指標型態是否會造成記憶體校正的錯誤。對任何系統而言
          , char是系統中最小的資料型態, 因此它具有『最低的記
          憶體校正需求』。在PC上, 我們感受不到記憶體校正的困
          擾, 不過如果熟習組合語言的人必定曉得, 在80x86 CPU
          的架構下, 資料項目或指令如果是在word boundry的情形
          下, 則程式的執行效率會較高; 只是, 80x86 CPU 並未強
          制要求而已, 不要求的原因, 筆者猜測試因為考量80x88
          的byte boundry系統架構。


A-3-2 函式指標
    指到函式的指標, 是一種相當重要、好用的指標, 在傳統程式設
計觀念裡, 它可以幫助程式設計人員寫出『通用函式』; 在新的OOP
觀念裡, 如果想用C 語言模擬C++ 的CLASS 資料型態, 則函式指標更
是不能或缺。

    對K&R C 而言, 一個指到函式的指標, 其型別就限制在 "函式"
上, 使用時必須明顯的使用* 運算來呼叫一個函式。這種用法固然沒
有什麼不對, 但是如果程式中函式指標使用頻繁, 則程式將顯得很凌
亂, 到處都會看到* 運算, 真可謂是『指標滿天飛』。

    在ANSI C中, 它解除了原本K&R C 的限制, 讓透過指標呼叫函式
時, 可用一般的函式呼叫語法, 亦即, 不在強制要求必須具有* 運算
存在。

    範例十二及範例十三是兩個完全相同的函式, 這兩個函式的差別
在於使用舊語法及新語法, 而函式的用途則在於:可對任何資料型態
進行排序動作。

/* 範例十二 */
static unsigned width ;
static int (* compare)();

quicksort (array, number, size, comp)
char *array;
unsigned number;
unsigned size;
int (*comp)();
{
    if (number)
    {
        width = size;
        compare = comp;
        quick (array, array + (number - 1) * width);
    }
}

static quick (lo, hi)
char *lo;
char *hi;
{
    char *higuy = hi + width;
    char *loguy = lo;

    if ( lo > hi )
       return ;
    for ( ; ; )
    {
       do
          loguy += width;
       while (loguy < hi && (*compare) (loguy, lo) <= 0);
                            /* * 運算不可或缺 */
       do
          higuy -= width;
       while (higuy > lo && (*compare) (higuy, lo) >= 0);
                            /* * 運算不可或缺 */
       if (higuy <= loguy)
          break;

       swap (loguy, higuy, width);
    }

    swap (lo, higuy, width);
    quick (higuy + width, hi);
    quick (lo, higuy - width);
}

static swap (one, two, w)
char *one;
char *two;
unsigned w;
{
    char temp;

    while (w--)
    {
       temp = *one;
       *one++ = *two;
       *two++ = temp;
    }
}


/* 範例十三 */
static unsigned width ;
static int (* compare)();

quicksort (  char *array,
             unsigned number,
             unsigned size,
             int (*comp)()    )
{
    if (number)
    {
        width = size;
        compare = comp;
        quick (array, array + (number - 1) * width);
    }
}

static quick (char *lo, char *hi)
{
    char *higuy = hi + width;
    char *loguy = lo;

    if ( lo > hi )
       return;

    for ( ; ; )
    {
       do
          loguy += width;
       while (loguy < hi && compare (loguy, lo) <= 0);
                            /* 不再需要 *運算 */
       do
          higuy -= width;
       while (higuy > lo && compare (higuy, lo) >= 0);
                            /* 不再需要 *運算 */
       if (higuy <= loguy)
          break;

       swap (loguy, higuy, width);
    }

    swap (lo, higuy, width);
    quick (higuy + width, hi);
    quick (lo, higuy - width);
}

static swap (char *one, char *two, unsigned w)
{
    char temp;

    while (w--)
    {
       temp = *one;
       *one++ = *two;
       *two++ = temp;
    }
}

/* 主程式 */
double ard[10]={3.5,4.7,6.9,2.8,4.2,6.6,1.5,6.7,3.8,4.9};
int    ari[10]={35, 47, 69, 28, 42, 66, 15, 67, 38, 49};

compd (d1,d2)
double *d1,*d2;
{
    return (*d1>*d2)?1 :(*d1==*d2)?0 :-1 ;
}

compi (i1,i2)
int *i1,*i2;
{
    return (*i1-*i2);
}

main ()
{
    int i;

    quicksort (ard,10,sizeof(double),compd);
    quicksort (ari,10,sizeof(int),compi);

    for ( i=0 ; i < 10 ; i++ )
       printf ("%lf %d\n",ard,ari);
}

[Acute 在 2004-6-14 12:47 AM 作了最後編輯]



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#8 : 2004-6-13 03:05 PM     只看本作者 引言回覆

A-3-3 指標運算
    K&R C 中, 允許指標進行加法、減法、比較三種運算, 但是對於
指標的運算則有嚴格的限制, 在加減法中, 指標必須是指向同一個資
料陣列方屬有意義, 指標加上一個整數值後, 也不應該超過陣列的邊
界。而指標之比較, 也僅限於指向同一個陣列的指標方可比較, 否則
即屬於未定義的動作。

    對於允許對指標做哪些運算, 這點在ANSI C中並未改變。不過,
ANSI C放寬了指標運算的限制。

    首先, 指標加上一個整數值後, 可以指向陣列邊界的下一個位置
。 [註五]

    其次, 指標之比較, 不在侷限於指向同一個陣列的指標, 改為,
只要指向相同資料項目或相同型別之資料即可比較。如果指標指向同
一個結構之不同元素, 則結構中排列在後的元素之指標較大; 如果指
向同一個等位的元素, 則指標相等。


   [註五] 這點, 我們已經在範例十二及範例十三中採用了。儘管早
          期K&R C 未曾說明可以這麼做, 但是這麼做也不會錯。事
          實上, C 並不會做陣列的邊界檢查, 語法定義上不允許指
          標指到陣列以外的地方, 只是一種邏輯上的限制而已, 如
          果程式設計人員一定要這麼做, 並不會發生錯誤。但是,
          有何理由必須讓指標指到陣列以外的地方呢?


A-4 結構
    在結構方面, ANSI C幾乎沒有更動K&R C 的定義, 唯一的一項改
變就是增加了 ── 列舉型別"enum"。所有的C 語言的入門書都會說
明列舉型別的用途、用法、... 等等相關資料, 在此筆者不予贅述。

A-5 後言
    雖然C 語言的標準已然確立, 但是C 語言究竟改變了哪些東西?
相信可以很完整的將這些變遷說出來的人並不多, 即使在本章中, 筆
者企圖將它們全盤列出, 但仍恐會有所疏漏, 為此, 吾人在撰寫本文
之際, 曾不斷的反覆詳讀The C Programming Language的第一版及第
二版。用心之餘, 仍舊難免有所疏失, 冀望各位先進能不吝指正。



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Pika123
論壇第二呆
等級: 10等級: 10等級: 10


今日心情

 . 積分: 178
 . 文章: 1565
 . 收花: 775 支
 . 送花: 883 支
 . 比例: 1.14
 . 在線: 1281 小時
 . 瀏覽: 22934 頁
 . 註冊: 8212
 . 失蹤: 1405
 . 台中
#9 : 2004-6-13 11:56 PM     只看本作者 引言回覆

ㄜ...吐個槽一下...上面出現了一個哭臉^^"
不過這篇印成書的話好像滿薄的喔....



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
H.T.DEMON
論壇第一呆
等級: 11等級: 11等級: 11等級: 11
天堂的惡魔

 . 積分: 321
 . 文章: 2497
 . 收花: 1170 支
 . 送花: 629 支
 . 比例: 0.54
 . 在線: 2355 小時
 . 瀏覽: 24805 頁
 . 註冊: 8197
 . 失蹤: 272
#10 : 2004-6-14 12:09 AM     只看本作者 引言回覆

看到眼花...
這篇是章A 那麼還會有BCDE..... 期待啊 收集後裝定發行



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  訪問主頁  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#11 : 2004-6-14 12:30 AM     只看本作者 引言回覆


引用:
Pika123寫到:
ㄜ...吐個槽一下...上面出現了一個哭臉^^"
不過這篇印成書的話好像滿薄的喔....


那是論壇自己轉換語法
我只是把舊文拿出來貼而已
so, 我也沒發現, 哈

Acute.



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#12 : 2004-6-14 12:32 AM     只看本作者 引言回覆


引用:
H.T.DEMON寫到:
看到眼花...
這篇是章A 那麼還會有BCDE..... 期待啊 收集後裝定發行


這是給學過C 的人釐清觀念用的
沒學過的人, 看也是白看

不會張貼其餘的部份了啦
其餘的部份, 現在都已經不具備價值了
張貼也沒啥用, 哈

Acute.



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
pokitw0912
銅驢友〔高級〕
等級: 10等級: 10等級: 10
poki

 . 積分: 161
 . 文章: 1573
 . 收花: 788 支
 . 送花: 810 支
 . 比例: 1.03
 . 在線: 2432 小時
 . 瀏覽: 12241 頁
 . 註冊: 8154
 . 失蹤: 186
#13 : 2004-6-14 12:36 AM     只看本作者 引言回覆


引用:
Acute寫到:

引用:
Pika123寫到:
ㄜ...吐個槽一下...上面出現了一個哭臉^^"
不過這篇印成書的話好像滿薄的喔....


那是論壇自己轉換語法
我只是把舊文拿出來貼而已
so, 我也沒發現, 哈

Acute.


編輯文章選" 關閉表情符號 " ..
不知道有沒有用  麻煩毒王改一下了
有個哭臉  還真的好怪 ^^""



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
DK
版主
等級: 30等級: 30等級: 30等級: 30等級: 30等級: 30等級: 30等級: 30
dk01

 . 積分: 772
 . 文章: 3983
 . 收花: 3244 支
 . 送花: 1029 支
 . 比例: 0.32
 . 在線: 393 小時
 . 瀏覽: 5760 頁
 . 註冊: 8210
 . 失蹤: 1228
#14 : 2004-6-14 08:13 PM     只看本作者 引言回覆

自己留一份不外傳可以嗎


[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記
Acute
論壇第一大毒王
等級: 18等級: 18等級: 18等級: 18等級: 18
論壇第一小神童

 . 積分: 3281
 . 精華: 8
 . 文章: 11574
 . 收花: 14037 支
 . 送花: 3260 支
 . 比例: 0.23
 . 在線: 323 小時
 . 瀏覽: 2250 頁
 . 註冊: 8215
 . 失蹤: 5568
#15 : 2004-6-14 08:19 PM     只看本作者 引言回覆


引用:
DK寫到:
自己留一份不外傳可以嗎


可以阿, 貼出來就是要給人看的咩
看了喜歡就自己存檔吧 ^^"
不拿去其他地方轉貼即可, 呵呵

Acute.



[如果你喜歡本文章,就按本文章之鮮花~送花給作者吧,你的支持就是別人的動力來源]
本文連接  
檢閱個人資料  發私人訊息  Blog  新增/修改 爬文標記

 35  1/3  1  2  3  > 
   



 



所在時區為 GMT+8, 現在時間是 2024-11-21 09:19 PM
清除 Cookies - 連絡我們 - TWed2k © 2001-2046 - 純文字版 - 說明
Discuz! 0.1 | Processed in 0.045628 second(s), 6 queries , Qzip disabled