程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C Primer Plus(十六)

C Primer Plus(十六)

編輯:關於C語言

16.1 翻譯程序的第一步

對程序做預處理前,編譯器會對它進行幾次翻譯處理。
編譯器首先把源代碼中出現的字符映射到源字符集。
第二,編譯器查找反斜線後緊跟換行符的實例並刪除這些實例。也就是將兩個物理行
printf("That's wond\
erful!\n");
轉換成一個邏輯行:printf("That's wonderful!\n");
接下來,編譯器將文本劃分成預處理的語言符號序列和空白字符及注釋序列。編譯器用一個空格字符代替每一個注釋。
最後程序進入預處理階段。預處理器尋找可能存在的預處理指定。
這些指令由一行開始出的#符號標識。

16.2 明顯常量:#define

預處理指定用#符號作為行的開頭。
指令可出現在源文件的任何地方。指令定義的作用域從定義出現的位置開始直到文件的結尾。
每個#define行由三部分組成。
第一部分為指令#define自身。
第二部分為所選擇的縮略語,這些縮略語被稱為宏。像本例中的這些宏用來代表值,它們被成為類對象宏。宏的名字中不允許有空格,而且必須遵循C變量命名規則。
第三部分稱為替換列表或主體。
預處理器在程序中發現了宏的實例後,總會用實體代替該宏。從宏變成最終的替換文本的過程成為宏展開。

使用#define的時候可以使用多個物理行,一行結尾加反斜線符號以使該行擴展至下一行,注意第二行要左對齊,否開頭空格也會作為主體的一部分。
一般而言,預處理器發現程序中的宏後,會用它的等價替換文本代替宏,如果字串中還包括紅,則繼續替換。
例外情況是雙引號中的宏printf("TWO:OW");將輸出TWO:OW而不是主體。

16.2.1 語言符號

從技術方面看,系統把宏的主體當作語言符號類型字符串,而不是字符型字符串。
#define FOUR 2*2有一個語言符號:2*2。
#define SIX 2    *     3有三個語言符號:2、*和3。當主體解釋為字符型字符串時,預處理器用2   *   3替換SIX,也就是額外的空格也當作替換文本的一部分。但是當主體解釋為語言符號類型時,預處理用由單個空格分隔的三個語言符號,即2 * 3來代替SIX。

C編譯器處理語言符號的方式比預處理器的處理方式更加復雜。

16.2.2 重定義常量

假設您把LIMIT定義為20,後來在該文件中又把LIMIT定義為25.這個過程被稱為重定義常量。

下面兩個定義相同:
#define SIX 2 * 3
#define SIX 2      *      3
兩者都有三個相同的語言符號,而且額外的空格不是主體的一部分,下面的定義則被認為是不同的:
#define SIX 2*3
上式只有一個語言符號,因此與前面兩個定義不相同。
可以使用#undef指令重新定義宏。

16.3 在#define使用參數

通過使用參數,可以創建外形和作用都與函數相似的類函數宏。宏的參數也用圓括號括起來。
例如#define SQUARE(X)  X*X
在程序中可以使用z=SQUARE(2);
一個例子程序:

#include<stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n",X)
int main(void)
{
int x=4;
int z;
printf("x=%d\n",x);
z=SQUARE(x);
PR(z);
PR(SQUARE(x+2));
PR(100/SQUARE(2));
printf("x=%d\n",x);
PR(SQUARE(++x));
return 0;
}

這和我們期待的結果不一樣,原因在於預處理器不進行計算,而只進行字符串替換。早出現x的地方,預處理器都用字符串x+2進行替換,因此x*x變成x+2*x+2
想改變結果,只需要多加幾個圓括號 例如#define SQUARE(X) (X)*(X)

16.3.1 利用宏參數創建字符串:#運算符

引號中的字符串中的宏被看作普通文本,而不是被看作一個可被替換的語言符號。
假設您確實希望在字符串中包含宏參數,可以使用#。如果x是一個宏參量,那麼#x可以把參數名轉化為相應的字符串。這個過程稱為字符串化。

#define PR(X) printf("The square of " #X  " is %d.\n",(X)*(X))
int y=5;PR(y);
它的替換結果為printf("The square of " “y” " is %d.\n",(y)*(y));
接著,字符串連接功能將這三個相鄰的字符串轉換為一個字符串:
輸出The square of y is 25.

16.3.2 預處理器的粘合劑:##運算符

和#運算符一樣,##運算符可以用於類函數宏的替換部分。另外##還可用於類對象宏的替換部分。這個運算符把兩個語言符號組合成單個語言符號。
例如 #define XNAME(n) x ## n
通過這個XNAME(4)的宏調用會展開成下列形式:x4

16.3.3 可變宏:...和_ _VA_ARGS_ _

實現思想就是(也就是三個句號)。這樣預定義_ _VA_ARGS_ _就可以被用在替換部分中,以表明省略號代表什麼。
例如#define PR(...) printf(_ _VA_ARGS_ _)
PR("Hello");就展開一個參數“Hello”;
#define PR(X,...) printf("Message " #X ": "_ _VA_ARGS_ _)
double x=48;PR(1,"x=%g\n",x);
輸出如下 Message 1: x=48

16.4 宏,還是函數

宏與函數間的選擇實際上是時間與空間的權衡。
宏產生內聯代碼,也就是在程序中產生語句。如果使用宏20次則插入20行代碼。如果使用函數20次,那麼程序中只有一份函數語句的拷貝,因此節省了空間。
但是函數設計到程序控制的轉移,因此花費時間更多。
宏的一個優點是它不檢查其中的變量類型,但是不注意的話會產生奇怪的現象。

16.5 文件包含:#include

預處理器發現#include指令後,就會尋找後跟的文件名並把這個文件的內容包含到當前文件中,就行您把被包含文件中的全部內容鍵入到源文件的這個特定位置一樣。
習慣上使用後綴.h表示頭文件,這類文件包含置於程序頭部的信息。
包含大型頭文件並不一定顯著增加程序的大小。很多情況下,頭文件中的內容是編譯器產生最終代碼所需的信息,而不是加到最終代碼裡的具體語句。
浏覽任何一個標准頭文件都會使您對頭文件中信息的類型有一個清晰的概念。
另外,許多使用頭文件來聲明多個文件共享的外部變量。

16.6 其它指令

16.6.1 #undef指令

#undef指令取消定義一個給定的#define,即使沒有定義,也可以取消。
例如#define LIMIT 400 #undef LIMIT

16.6.2 已定義:C預處理器的觀點

如果C中的標識符是該文件簽名的#define指令創建的宏名,並且沒有用#undef指令關閉該標識符,則標識符是已定義的。
如果標識符不是宏,而是一個具有文件作用域的C變量,那麼預處理器把標識符當作未定義的。例如 int q;//q未定義

16.6.3 條件編譯

一、#ifdef、#else和#endif指令

#ifdef MAVIS
statement1;
#else
statement2;
#endif
如果預處理定義了標識符MAVIS則執行statement1,否則執行statement2。

二、#ifndef指令

#ifdef的反義指令
#ifndef  SIZE
#define SIZE 100
#endif

三、#if和#elif指令

#if指令更像常規的C中的if;#if後跟常量整數表達式。
許多新的方法提高另一種方法來判斷一個名字是否已經定義。
不需要用 #ifdef VAX
而是采用 #if defined(VAX)

16.7 內聯函數

調用函數需要一定時間開銷。使用類函數宏的一個原因是減少執行時間。
C99還提供另一方發:內聯函數。
創建內聯函數的方法是在函數聲明中使用函數說明符inline。
例如 inline  void eatline()
內聯函數應該比較短小,對於很長的函數,調用函數的時間少於執行函數主體的時間;此時,使用內聯函數不會節省多少時間。
內聯函數的定義和對該函數的調用必須在同一文件中。
C只允許對函數進行惟一的一次定義,但是對內聯函數卻放松了這個限制。
C允許混合使用內聯函數定義和外部函數定義。

16.8 C庫

16.8.1 訪問C庫

一、自動訪問

在許多系統上,您只需編譯程序,一些常見的庫函數自動可用。
記住,應該聲明所使用的函數的類型,通常包含適當的頭文件即可。

二、文件包含

如果函數定義為宏,可使用#include指令來包含擁有該定義的文件。

三、庫包含

庫選項告訴系統到哪兒尋找函數代碼

16.9 通用工具庫

16.9.1 exit()和atexit()

atexit()函數使用函數指針作為參數。由atexit()注冊的函數的類型應該為不接受任何參數的void函數。
調用exit()函數時,按先進後出的順序執行這些函數。
exit()將做一些自身清理工作,刷新所有輸出流,關閉打開流等。

16.10 診斷庫

由頭文件assert.h支持的診斷庫是設計用於輔助調試程序的小型庫。
它有宏assert()構成,該宏接受整數表達式作為參數。如果為假,向標准錯誤流寫一條錯誤消息並調用abort()函數以終止程序。
例如assert(z>=2)
可以使用#define NEDBUG 來禁用文件中所有的assert()語句。

16.11 string.h庫中的memcpy()和memmove()

兩個函數原型為
void *memcoy(void *restrict s1,const void *restrict s2,size_t n);
void *memmove(void *s1,const void * s2,size_t n);
這兩個函數均從s2指向的位置復制n字節數據到s1指向的位置,切均返回s1的值。
兩者間的差別在於第一個函數不允許重疊區域。
第三個參數可以為 num*sizeof(int)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved