C Java Tutor 客服在線

代做國外C C++ Java程序 QQ: 1067665373 Email:cjtutor@foxmail.com

« 有沒有代做C程序的【已解決】Linux的C開發環境的構成和安裝 »

很好的C語言教程


C語言教程第一章: C語言概論
C語言的發展過程 
 
    C語言是在 70 年代初問世的。一九七八年由美國電話電報公司(AT&T)貝爾實驗室正式
發表了C語言。同時由 B.W.Kernighan 和D.M.Ritchit 合著了著名的“THE C PROGRAMMING
LANGUAGE”一書。通常簡稱為《K&R》,也有人稱之為《K&R》標準。但是,在《K&R》中并
沒有定義一個完整的標準 C 語言,后來由美國國家標準學會在此基礎上制定了一個 C 語言
標準,于一九八三年發表。通常稱之為 ANSI C。
當代最優秀的程序設計語言
    早期的 C 語言主要是用于 UNIX 系統。由于C語言的強大功能和各方面的優點逐漸為人
們認識,到了八十年代,C 開始進入其它操作系統,并很快在各類大、中、小和微型計算機
上得到了廣泛的使用。成為當代最優秀的程序設計語言之一。
C語言的特點
    C語言是一種結構化語言。它層次清晰,便于按模塊化方式組織程序,易于調試和維護。
C語言的表現能力和處理能力極強。它不僅具有豐富的運算符和數據類型,便于實現各類復
雜的數據結構。它還可以直接訪問內存的物理地址,進行位(bit)一級的操作。由于C語言
實現了對硬件的編程操作,因此C語言集高級語言和低級語言的功能于一體。既可用于系統
軟件的開發,也適合于應用軟件的開發。此外,C語言還具有效率高,可移植性強等特點。
因此廣泛地移植到了各類各型計算機上,從而形成了多種版本的C語言。
C語言版本
    目前最流行的C語言有以下幾種:
   ·Microsoft C 或稱 MS C
   ·Borland Turbo C 或稱 Turbo C
   ·AT&T C
    這些C語言版本不僅實現了 ANSI C 標準,而且在此基礎上各自作了一些擴充,使之更
加方便、完美。
面向對象的程序設計語言
    在 C 的基礎上,一九八三年又由貝爾實驗室的 Bjarne Strou-strup推出了 C++。 C++
進一步擴充和完善了C語言,成為一種面向 對象的程序設計語言。C++目前流行的最新版本
是 Borland C++4.5,Symantec C++6.1,和 Microsoft VisualC++ 2.0。 C++提出了一些更為深
入的概念,它所支持的這些面向對象的概念容易將問題空間直接地映射到程序空間,為程序
員提供了一種與傳統結構程序設計不同的思維方式和編程方法。因而也增加了整個語言的復
雜性,掌握起來有一定難度。
總計164頁  當前為第1頁
C和C++
    但是,C是 C++的基礎,C++語言和C語言在很多方面是兼容的。因此,掌握了C語言,
再進一步學習 C++就能以一種熟悉的語法來學習面向對象的語言,從而達到事半功倍的目
的。
C源程序的結構特點
    為了說明C語言源程序結構的特點,先看以下幾個程序。這幾個程 序由簡到難,表現
了C語言源程序在組成結構上的特點。雖然有關內容還未介紹,但可從這些例子中了解到組
成一個 C 源程序的基本部分和書寫格式。
main()
{
  printf("c語言世界 ,您好!\n");
}
    main 是主函數的函數名,表示這是一個主函數。每一個 C 源程序都必須有,且只能有
一個主函數(main 函數)。函數調用語句,printf函數的功能是把要輸出的內容送到顯示器
去顯示。printf 函數是一個由系統定義的標準函數,可在程序中直接調用。
#include "stdio.h"
#include "math.h"
main()
{
  double x,s;
  printf("input number:\n");
  scanf("%lf",&x);
  s=sin(x);
  printf("sine of %lf is %lf\n",x,s);
}
 
每行注釋
 
include 稱為文件包含命令擴展名為.h 的文件也稱為頭文件或首部文件
定義兩個實數變量,以被后面程序使用
顯示提示信息
從鍵盤獲得一個實數 x
求 x 的正弦,并把它賦給變量 s
顯示程序運算結果
main 函數結束
  
    程序的功能是從鍵盤輸入一個數 x,求x 的正弦值,然后輸出結果。在 main()之前的兩
行稱為預處理命令(詳見后面)。預處理命令還有其它幾種,這里的 include 稱為文件包含
命令,其意義是把尖括號""或引號<>內指定的文件包含到本程序來,成為本程序的一部分。
被包含的文件通常是由系統提供的,其擴展名為.h。因此也稱為頭文件或首部文件。C語言
總計164頁  當前為第2頁
的頭文件中包括了各個標準庫函數的函數原型。因此,凡是在程序中調用一個庫函數時,都
必須包含該函數原型所在的頭文件。在本例中,使用了三個庫函數:輸入函數 scanf,正弦
函數 sin,輸出函數 printf。sin 函數是數學函數,其頭文件為 math.h 文件,因此在程序的
主函數前用 include 命令包含了 math.h。scanf和printf 是標準輸入輸出函數,其頭文件
為 stdio.h,在主函數前也用 include 命令包含了 stdio.h文件。
    需要說明的是,C 語言規定對 scanf和 printf這兩個函數可以省去對其頭文件的包含
命令。所以在本例中也可以刪去第二行的包含命令#include。同樣,在例 1.1 中使用了 pri
ntf 函數,也省略了包含命令。
    在例題中的主函數體中又分為兩部分,一部分為說明部分,另一部分執行部分。說明是
指變量的類型說明。例題中未使用任何變量,因此無說明部分。C語言規定,源程序中所有
用到的變量都必須先說明,后使用,否則將會出錯。這一點是編譯型高級程序設計語言的一
個特點,與解釋型的 BASIC 語言是不同的。說明部分是 C 源程序結構中很重要的組成部分。
本例中使用了兩個變量 x,s,用來表示輸入的自變量和 sin函數值。由于 sin 函數要求這
兩個量必須是雙精度浮點型,故用類型說明符 double 來說明這兩個變量。說明部分后的四
行為執行部分或稱為執行語句部分,用以完成程序的功能。執行部分的第一行是輸出語句,
調用 printf函數在顯示器上輸出提示字符串,請操作人員輸入自變量 x 的值。第二行為輸
入語句,調用 scanf 函數,接受鍵盤上輸入的數并存入變量 x 中。第三行是調用 sin 函數并
把函數值送到變量 s 中。第四行是用 printf 函數輸出變量 s 的值,即 x的正弦值。程序結
束。
printf("input number:\n");
scanf("%lf",'C10F10&x);
s=sin(x);
printf("sine of %lf is %lf\n",'C10F10x,s);
運行本程序時,首先在顯示器屏幕上給出提示串 input number,這是由執行部分的第一行
完成的。用戶在提示下從鍵盤上鍵入某一數,如 5,按下回車鍵,接著在屏幕上給出計算結
果。
輸入和輸出函數
 
    在前兩個例子中用到了輸入和輸出函數 scanf 和 printf, 在第三章中我們要詳細介紹。
這里我們先簡單介紹一下它們的格式,以便下面使用。scanf 和 printf 這兩個函數分別稱
為格式輸入函數和格式輸出函數。其意義是按指定的格式輸入輸出值。因此,這兩個函數在
括號中的參數表都由以下兩部分組成:  “格式控制串”,參數表   格式控制串是一個字
符串,必須用雙引號括起來,它表示了輸入輸出量的數據類型。各種類型的格式表示法可參
閱第三章。在 printf 函數中還可以在格式控制串內出現非格式控制字符,這時在顯示屏幕
上將原文照印。參數表中給出了輸入或輸出的量。當有多個量時,用逗號間隔。例如:
printf("sine of %lf is %lf\n",x,s);
    其中%lf為格式字符,表示按雙精度浮點數處理。它在格式串中兩次現,對應了 x 和s
兩個變量。其余字符為非格式字符則照原樣輸出在屏幕上
int max(int a,int b);
總計164頁  當前為第3頁
main()
{
  int x,y,z;
  printf("input two numbers:\n");
  scanf("%d%d",&x,&y);
  z=max(x,y);
  printf("maxmum=%d",z);
}
int max(int a,int b)
{
  if(a>b)return a;
   else return b;
}
此函數的功能是輸入兩個整數,輸出其中的大數。
/*函數說明*/
/*主函數*/
/*變量說明*/
/*輸入x,y值*/
/*調用max 函數*/ 
/*輸出*/
/*定義max函數*/
/*把結果返回主調函數*/
    上面例中程序的功能是由用戶輸入兩個整數,程序執行后輸出其中較大的數。本程序由
兩個函數組成,主函數和 max 函數。函數之間是并列關系。可從主函數中調用其它函數。m
ax 函數的功能是比較兩個數,然后把較大的數返回給主函數。max 函數是一個用戶自定義
函數。因此在主函數中要給出說明(程序第三行)。可見,在程序的說明部分中,不僅可以有
變量說明,還可以有函數說明。關于函數的詳細內容將在第五章介紹。在程序的每行后用/
*和*/括起來的內容為注釋部分,程序不執行注釋部分。
    上例中程序的執行過程是,首先在屏幕上顯示提示串,請用戶輸入兩個數,回車后由 s
canf 函數語句接收這兩個數送入變量 x,y 中,然后調用 max 函數,并把 x,y 的值傳送給 ma
x 函數的參數 a,b。在max 函數中比較 a,b 的大小,把大者返回給主函數的變量 z,最后在
屏幕上輸出 z 的值。
C源程序的結構特點
1.一個C語言源程序可以由一個或多個源文件組成。
2.每個源文件可由一個或多個函數組成。
3.一個源程序不論由多少個文件組成,都有一個且只能有一個 main 函數,即主函數。
4.源程序中可以有預處理命令(include 命令僅為其中的一種), 預處理命令通常應放在源文
件或源程序的最前面。
總計164頁  當前為第4頁
5.每一個說明,每一個語句都必須以分號結尾。但預處理命令,函數頭和花括號“}”之后
不能加分號。
6.標識符,關鍵字之間必須至少加一個空格以示間隔。若已有明顯的間隔符,也可不再加空
格來間隔。
書寫程序時應遵循的規則
    從書寫清晰,便于閱讀,理解,維護的角度出發,在書寫程序時 應遵循以下規則:
 
1.一個說明或一個語句占一行。
2.用{} 括起來的部分,通常表示了程序的某一層次結構。{}一般與該結構語句的第一個字
母對齊,并單獨占一行。
3.低一層次的語句或說明可比高一層次的語句或說明縮進若干格后書寫。 以便看起來更加清
晰,增加程序的可讀性。在編程時應力求遵循這些規則,以養成良好的編程風格。
C語言的字符集
    字符是組成語言的最基本的元素。C語言字符集由字母,數字,空格,標點和特殊字符
組成。在字符常量,字符串常量和注釋中還可以使用漢字或其它可表示的圖形符號。
1.字母    小寫字母 a~z 共26 個,大寫字母 A~Z 共26 個
2.數字    0~9 共10 個
3.空白符  空格符、制表符、換行符等統稱為空白符。空白符只在字符常量和字符串常量中
起作用。在其它地方出現時,只起間隔作用, 編譯程序對它們忽略。因此在程序中使用空
白符與否,對程序的編譯不發生影響,但在程序中適當的地方使用空白符將增加程序的清晰
性和可讀性。
4.標點和特殊字符
C語言詞匯
    在C語言中使用的詞匯分為六類:標識符,關鍵字,運算符,分隔符,常量,注釋符等。
1.標識符
 
    在程序中使用的變量名、函數名、標號等統稱為標識符。除庫函數的函數名由系統定義
外,其余都由用戶自定義。C 規定,標識符只能是字母(A~Z,a~z)、數字(0~9)、下劃線
()組成的字符串,并且其第一個字符必須是字母或下劃線。
以下標識符是合法的:
總計164頁  當前為第5頁
a,x,_3x,BOOK_1,sum5
以下標識符是非法的:
3s 以數字開頭
s*T 出現非法字符*
-3x 以減號開頭
bowy-1 出現非法字符-(減號)
    在使用標識符時還必須注意以下幾點:
(1)標準C 不限制標識符的長度,但它受各種版本的 C 語言編譯系統限制,同時也受到具體
機器的限制。例如在某版本 C 中規定標識符前八位有效,當兩個標識符前八位相同時,則
被認為是同一個標識符。
(2)在標識符中,大小寫是有區別的。例如 BOOK和 book 是兩個不同的標識符。
(3)標識符雖然可由程序員隨意定義,但標識符是用于標識某個量的符號。因此,命名應盡
量有相應的意義,以便閱讀理解,作到“顧名思義”。
 
2.關鍵字
 
    關鍵字是由C語言規定的具有特定意義的字符串,通常也稱為保留字。用戶定義的標識
符不應與關鍵字相同。C語言的關鍵字分為以下幾類:
(1)類型說明符
用于定義、說明變量、函數或其它數據結構的類型。如前面例題中用到的 int,double 等
(2)語句定義符
用于表示一個語句的功能。如例 1.3 中用到的if else 就是條件語句的語句定義符。
(3)預處理命令字
用于表示一個預處理命令。如前面各例中用到的 include。
 
3.運算符
 
    C語言中含有相當豐富的運算符。運算符與變量,函數一起組成表達式,表示各種運算
功能。運算符由一個或多個字符組成。
 
4.分隔符
 
    在C語言中采用的分隔符有逗號和空格兩種。逗號主要用在類型說明和函數參數表中,
分隔各個變量。空格多用于語句各單詞之間,作間隔符。在關鍵字,標識符之間必須要有一
個以上的空格符作間隔, 否則將會出現語法錯誤,例如把 int a;寫成 inta;C 編譯器會把
inta 當成一個標識符處理,其結果必然出錯。 
 
5.常量
 
    C 語言中使用的常量可分為數字常量、字符常量、字符串常量、符號常量、轉義字符等
多種。在第二章中將專門給予介紹。
總計164頁  當前為第6頁
6.注釋符
 
    C 語言的注釋符是以“/*”開頭并以“*/”結尾的串。在“/*”和“*/”之間的即為注
釋。程序編譯時,不對注釋作任何處理。注釋可出現在程序中的任何位置。注釋用來向用戶
提示或解釋程序的意義。在調試程序中對暫不使用的語句也可用注釋符括起來,使翻譯跳過
不作處理,待調試結束后再去掉注釋符。
總計164頁  當前為第7頁
C語言教程第二章: 數據類型、運算符、表達

C語言的數據類型
 
    在第一課中,我們已經看到程序中使用的各種變量都應預先加以說明,即先說明,后使
用。對變量的說明可以包括三個方面:
·數據類型
·存儲類型
·作用域
    在本課中,我們只介紹數據類型說明。其它說明在以后各章中陸續介紹。所謂數據類型
是按被說明量的性質,表示形式,占據存儲空間的多少,構造特點來劃分的。在C語言中,
數據類型可分為:基本數據類型,構造數據類型,指針類型,空類型四大類。
 
1.基本數據類型
 
    基本數據類型最主要的特點是,其值不可以再分解為其它類型。也就是說,基本數據類
型是自我說明的。
 
2.構造數據類型構造數據類型
 
    是根據已定義的一個或多個數據類型用構造的方法來定義的。也就是說,一個構造類型
的值可以分解成若干個“成員”或“元素”。每個“成員”都是一個基本數據類型或又是一
個構造類型。在 C 語言中,構造類型有以下幾種: 
·數組類型
·結構類型
·聯合類型
 
3.指針類型
 
    指針是一種特殊的,同時又是具有重要作用的數據類型。其值用來表示某個量在內存儲
器中的地址。雖然指針變量的取值類似于整型量,但這是兩個類型完全不同的量,因此不能
混為一談。4.空類型在調用函數值時,通常應向調用者返回一個函數值。這個返回的函數值
是具有一定的數據類型的,應在函數定義及函數說明中給以說明,例如在例題中給出的 max
函數定義中,函數頭為: int max(int a,int b);其中“int ”類型說明符即表示該函數的
返回值為整型量。又如在例題中,使用了庫函數 sin,由于系統規定其函數返回值為雙精度
浮點型,因此在賦值語句 s=sin (x);中,s 也必須是雙精度浮點型,以便與 sin 函數的返
回值一致。所以在說明部分,把 s說明為雙精度浮點型。但是,也有一類函數,調用后并不
需要向調用者返回函數值, 這種函數可以定義為“空類型”。其類型說明符為 void。在第
總計164頁  當前為第8頁
五章函數中還要詳細介紹。在本章中,我們先介紹基本數據類型中的整型、浮點型和字符型。
其余類型在以后各章中陸續介紹。
 
    對于基本數據類型量,按其取值是否可改變又分為常量和變量兩種。在程序執行過程中,
其值不發生改變的量稱為常量,取值可變的量稱為變量。它們可與數據類型結合起來分類。
例如,可分為整型常量、整型變量、浮點常量、浮點變量、字符常量、字符變量、枚舉常量、
枚舉變量。在程序中,常量是可以不經說明而直接引用的,而變量則必須先說明后使用。
整型量
 
整型量包括整型常量、整型變量。整型常量就是整常數。在C語言中,使用的整常數有八進
制、十六進制和十進制三種。
 
整型常量
 
1.八進制整常數八進制整常數必須以 0 開頭,即以 0 作為八進制數的前綴。數碼取值為 0~
7。八進制數通常是無符號數。
以下各數是合法的八進制數: 
015(十進制為 13)  0101(十進制為 65)  0177777(十進制為 65535)
以下各數不是合法的八進制數: 
256(無前綴0) 03A2(包含了非八進制數碼)  -0127(出現了負號)
 
2.十六進制整常數
十六進制整常數的前綴為 0X 或 0x。其數碼取值為 0~9,A~F或 a~f。
以下各數是合法的十六進制整常數:
0X2A(十進制為42)    0XA0 (十進制為160)    0XFFFF (十進制為65535)
以下各數不是合法的十六進制整常數: 
5A (無前綴0X)    0X3H (含有非十六進制數碼)
 
3.十進制整常數
十進制整常數沒有前綴。其數碼為 0~9。
以下各數是合法的十進制整常數:
237 -568 65535 1627
以下各數不是合法的十進制整常數:
023 (不能有前導 0)  23D (含有非十進制數碼)
在程序中是根據前綴來區分各種進制數的。因此在書寫常數時不要把前綴弄錯造成結果不正
確。4.整型常數的后綴在 16 位字長的機器上,基本整型的長度也為 16位,因此表示的數的
范圍也是有限定的。十進制無符號整常數的范圍為 0~65535,有符號數為-32768~+32767。
八進制無符號數的表示范圍為 0~0177777。十六進制無符號數的表示范圍為 0X0~0XFFFF
或 0x0~0xFFFF。如果使用的數超過了上述范圍,就必須用長整型數來表示。長整型數是用
后綴“L”或“l”來表示的。例如:
十進制長整常數 158L (十進制為 158) 358000L (十進制為-358000)
總計164頁  當前為第9頁
八進制長整常數 012L (十進制為 10) 077L (十進制為 63)  0200000L (十進制為 65536)
十六進制長整常數 0X15L (十進制為21) 0XA5L (十進制為165)  0X10000L (十進制為655
36)
  
    長整數 158L 和基本整常數 158 在數值上并無區別。但對 158L,因為是長整型量,C編
譯系統將為它分配 4 個字節存儲空間。而對 158,因為是基本整型,只分配 2 個字節的存儲
空間。因此在運算和輸出格式上要予以注意,避免出錯。無符號數也可用后綴表示,整型常
數的無符號數的后綴為“U”或“u”。例如: 358u,0x38Au,235Lu 均為無符號數。前綴,
后綴可同時使用以表示各種類型的數。如 0XA5Lu 表示十六進制無符號長整數 A5,其十進制
為165。
 
整型變量
 
整型變量可分為以下幾類:
1.基本型
類型說明符為 int,在內存中占 2 個字節,其取值為基本整常數。
2.短整量
類型說明符為 short int 或short'C110F1。所占字節和取值范圍均與基本型相同。
3.長整型
類型說明符為 long int或 long ,在內存中占 4個字節,其取值為長整常數。
4.無符號型
類型說明符為 unsigned。
無符號型又可與上述三種類型匹配而構成:
(1)無符號基本型 類型說明符為 unsigned int 或unsigned。
(2)無符號短整型 類型說明符為 unsigned short
(3)無符號長整型 類型說明符為 unsigned long 
各種無符號類型量所占的內存空間字節數與相應的有符號類型量相同。但由于省去了符號
位,故不能表示負數。 下表列出了 Turbo C 中各類整型量所分配的內存字節數及數的表示
范圍。
類型說明符     數的范圍         分配字節數
int       -32768~32767     ■■ 
short int    -32768~32767     ■■
signed int      -32768~32767         ■■
unsigned int   0~65535        ■■
long int   -2147483648~2147483647    ■■■■
unsigned long   0~4294967295         ■■■■
整型變量的說明
變量說明的一般形式為: 類型說明符 變量名標識符,變量名標識符,...; 例如:
int a,b,c; (a,b,c 為整型變量)
long x,y; (x,y 為長整型變量)
unsigned p,q; (p,q 為無符號整型變量)
總計164頁  當前為第10頁
在書寫變量說明時,應注意以下幾點:
1.允許在一個類型說明符后,說明多個相同類型的變量。各變量名之間用逗號間隔。類型說
明符與變量名之間至少用一個空格間隔。
2.最后一個變量名之后必須以“;”號結尾。
3.變量說明必須放在變量使用之前。一般放在函數體的開頭部分。
[Practice] //1int a,b;
short int c;
short d=100;
a=d-20;
b=a+d;
c=a+b+d;
d=d-a+c-b;'Vtable
a,2,0
b,2,0
c,2,0
d,2,100
of Vtable
'Vupdate
1,0;2,0
3,0
4,100
1,80
2,180
3,360
4,200
of Vupdate
of Practice
[Practice] //2int a=5;
int b=9;
long int c;
long d;
c=a+b-7;
d=a*b*c;
c=d*d*d;
a=c-d;'Vtable
a,2,5
b,2,9
c,4,0
d,4,0
of Vtable
'Vupdate
1,5
2,9
總計164頁  當前為第11頁
3,0
4,0
3,7
4,315
3,31255875
1,-5112
of Vupdate
of Practice
[Practice] //3int a=6,b=19;
unsigned int c;
int d;
c=a-b+7;
d=b*c;
a=b+c+d;
b=-a;'Vtable
a,2,6
b,2,19
c,2,0
d,2,0
of Vtable
'Vupdate
1,6;2,19
3,0
4,0
3,65530
4,-114
1,-101
2,101
of Vupdate
of Practice
void main(){
long x,y;
int a,b,c,d;
x=5;
y=6;
a=7;
b=8;
c=x+a;
d=y+b;
printf("c=x+a=%d,d=y+b=%d\n",c,d);
}
將 main 說明為返回 void,即不返回任何類型的值
x,y 被定義為 long 型
總計164頁  當前為第12頁
a,b,c,d 被定義為int 型
5->x
6->y
7->a
8->b
x+a->c
y+b->d
顯示程序運行結果 of long x,y;
int a,b,c,d;
c=x+a;
d=y+b;
    從程序中可以看到:x, y 是長整型變量,a, b 是基本整型變量。它們之間允許進行運
算,運算結果為長整型。但 c,d 被定義為基本整型,因此最后結果為基本整型。本例說明,
不同類型的量可以參與運算并相互賦值。其中的類型轉換是由編譯系統自動完成的。有關類
型轉換的規則將在以后介紹。
實型量
 
實型常量
 
實型也稱為浮點型。實型常量也稱為實數或者浮點數。在C語言中,實數只采用十進制。它
有二種形式:  十進制數形式指數形式
1.十進制數形式
由數碼 0~ 9 和小數點組成。例如:0.0,.25,5.789,0.13,5.0,300.,-267.8230 等均為合
法的實數。
2.指數形式
由十進制數,加階碼標志“e”或“E”以及階碼(只能為整數,可以帶符號)組成。其一般形式
為 a E n (a為十進制數, n 為十進制整數)其值為 a*10,n 如:  2.1E5 (等于 2.1*10,5), 3.7E-2
(等于 3.7*10,)-2*)  0.5E7 (等于 0.5*10,7), -2.8E-2 (等于-2.8*10,)-2*)以下不是合法的實數
345 (無小數點)  E7 (階碼標志 E 之前無數字)   -5 (無階碼標志)  53.-E3 (負號位置不對)
2.7E (無階碼)
標準C允許浮點數使用后綴。后綴為“f”或“F”即表示該數為浮點數。如 356f 和 356.是等價的。
例 2.2 說明了這種情況:
void main()
{
 printf("%f\n%f\n",356.,356f);
}
void  指明 main 不返回任何值  利用 printf顯示結果  結束
 
實型變量
 
實型變量分為兩類:單精度型和雙精度型,
其類型說明符為 float  單精度說明符,double  雙精度說明符。在 Turbo C中單精度型占 4 個
字節(32 位)內存空間,其數值范圍為 3.4E-38~3.4E+38,只能提供七位有效數字。雙精
總計164頁  當前為第13頁
度型占 8  個字節(64 位)內存空間,其數值范圍為 1.7E-308~1.7E+308,可提供 16 位有效
數字。
實型變量說明的格式和書寫規則與整型相同。
例如: float x,y; (x,y為單精度實型量)
      double a,b,c; (a,b,c為雙精度實型量)
實型常數不分單、雙精度,都按雙精度 double型處理。
void main()
{
  float a;
  double b;
  a=33333.33333;
  b=33333.33333333333333;
  printf("%f\n%f\n",a,b);
}
此程序說明 float、double的不同
a ■■■■
b ■■■■■■■■
a<---33333.33333
b<---33333.33333333333;;
顯示程序結果
此程序說明 float、double的不同
float a;
double b;
a=33333.33333;
b=33333.33333333333333; 從本例可以看出,由于 a  是單精度浮點型,有效位數只有七位。
而整數已占五位,故小數二位后之后均為無效數字。b  是雙精度型,有效位為十六位。但
Turbo C  規定小數后最多保留六位,其余部分四舍五入。
[Practice] //floatint a=32;
float b;
double d;
b=12345678;
d=b*100;
d=d+a;
d=d+58.123456;'Vtable
a,2,32
b,4,0.0
d,8,0.0
of Vtable
'Vupdate
1,32
2,0
3,0
2,12345678.00000
3,1234567800
總計164頁  當前為第14頁
3,1234567832
3,1234567890.123456
of Vupdate
of Practice
[Practice] //1int a=543;
float b;
b=123.123962+a;
b=b-100;
a=b;'Vtable
a,2,543
b,4,0.0
of Vtable
'Vupdate
1,543
2,0.0
2,123.123962
2,23.123962
1,23
of Vupdate
of Practice
字符型量
字符型量包括字符常量和字符變量。
 
字符常量
字符常量是用單引號括起來的一個字符。例如'a','b','=','+','?'都是合法字符常量。在
C語言中,字符常量有以下特點: 
1.字符常量只能用單引號括起來,不能用雙引號或其它括號。
2.字符常量只能是單個字符,不能是字符串。
3.字符可以是字符集中任意字符。但數字被定義為字符型之后就
不能參與數值運算。如'5'和5 是不同的。'5'是字符常量,不能參與運算。
 
轉義字符
轉義字符是一種特殊的字符常量。轉義字符以反斜線"\"開頭,后跟一個或幾個字符。轉義
字符具有特定的含義,不同于字符原有的意義,故稱“轉義”字符。例如,在前面各例題 p
rintf 函數的格式串中用到的“\n”就是一個轉義字符,其意義是“回車換行”。轉義字符
主要用來表示那些用一般字符不便于表示的控制代碼。
常用的轉義字符及其含義
轉義字符   轉義字符的意義
\n       回車換行
\t       橫向跳到下一制表位置
\v       豎向跳格
\b      退格
總計164頁  當前為第15頁
\r      回車
\f       走紙換頁
\\       反斜線符"\"
\'       單引號符
\a      鳴鈴
\ddd         1~3 位八進制數所代表的字符
\xhh         1~2 位十六進制數所代表的字符
廣義地講,C語言字符集中的任何一個字符均可用轉義字符來表示。表 2.2 中的\ddd 和\xh
h 正是為此而提出的。ddd和 hh 分別為八進制和十六進制的 ASCII 代碼。如\101 表示字?qu
ot;A" ,\102 表示字母"B",\134 表示反斜線,\XOA 表示換行等。轉義字符的使用
void main()
{
int a,b,c;
a=5; b=6; c=7;
printf("%d\n\t%d %d\n %d %d\t\b%d\n",a,b,c,a,b,c);
}
此程序練習轉義字符的使用
a、b、c 為整數  5->a,6->b,7->c
調用 printf顯示程序運行結果 
printf("%d\n\t%d %d\n %d %d\t\b%d\n",a,b,c,a,b,c);
程序在第一列輸出 a 值5 之后就是“\n”,故回車換行;接著又是“\t”,于是跳到下一制
表位置(設制表位置間隔為 8),再輸出 b 值6;空二格再輸出 c 值 7 后又是"\n",因此再
回車換行;再空二格之后又輸出 a值 5;再空三格又輸出 b的值 6;再次后"\t"跳到下一制
表位置(與上一行的 6 對齊),但下一轉義字符“\b”又使退回一格,故緊挨著 6 再輸出c
值7。
 
字符變量
字符變量的取值是字符常量,即單個字符。字符變量的類型說明符是 char。字符變量類型
說明的格式和書寫規則都與整型變量相同。
例如:
char a,b; 每個字符變量被分配一個字節的內存空間,因此只能存放一個字符。字符值是以
ASCII 碼的形式存放在變量的內存單元之中的。如 x 的
十進制 ASCII 碼是120,y 的十進制ASCII 碼是 121。對字符變量 a,b 賦予'x'和'y'值: a=
'x';b='y';實際上是在 a,b 兩個單元內存放 120和 121 的二進制代碼:  a  0 1 1 1 1 0 0
0
     b 0 1 1 1 1 0 0 1
所以也可以把它們看成是整型量。 C語言允許對整型變量賦以字符值,也允許對字符變量
賦以整型值。在輸出時, 允許把字符變量按整型量輸出,也允許把整型量按字符量輸出。
整型量為二字節量,字符量為單字節量,當整型量按字符型量處理時, 只有低八位字節參
與處理。
main()
{
  char a,b;
總計164頁  當前為第16頁
  a=120;
  b=121;
  printf("%c,%c\n%d,%d\n",a,b,a,b);

a ■ b ■
a <-- 120
b <--- 121
顯示程序結果
 
本程序中說明 a,b 為字符型,但在賦值語句中賦以整型值。從結果看,a,b 值的輸出形式
取決于 printf 函數格式串中的格式符,當格式符為"c"時,對應輸出的變量值為字符,當格
式符為"d"時,對應輸出的變量值為整數。
void main()
{
  char a,b;
  a='x';
  b='y';
  a=a-32;
  b=b-32;
  printf("%c,%c\n%d,%d\n",a,b,a,b);
}
a,b 被說明為字符變量并賦予字符值
把小寫字母換成大寫字母
以整型和字符型輸出 
本例中,a,b 被說明為字符變量并賦予字符值,C語言允許字符變量參與數值運算,即用
字符的 ASCII 碼參與運算。由于大小寫字母的 ASCII 碼相差 32,因此運算后把小寫字母換
成大寫字母。然后分別以整型和字符型輸出。
[Practice] //charint a=49;
char b;
char d;
b=a+10;
d=a+b;'Vtable
a,2,49
b,1,隨機
d,1,隨機
of Vtable
'Vupdate
1,49
2,隨機
3,隨機
2,';'
3,'l'
of Vupdate
總計164頁  當前為第17頁
of Practice
[Practice] //char c1,c2;
c1='a';c2='b';
c1=c1-32;c2=c2-32;'Vtable
c1,1,隨機
c2,1,隨機
of Vtable
'Vupdate
1,隨機;2,隨機
1,'a';2,'b'
1,'A';2,'B'
of Vupdate
of Practice
字符串常量
字符串常量是由一對雙引號括起的字符序列。例如:  "CHINA"  , "C program:"  ,  "$12.5"  等
都是合法的字符串常量。字符串常量和字符常量是不同的量。它們之間主要有以下區別:
1.字符常量由單引號括起來,字符串常量由雙引號括起來。
2.字符常量只能是單個字符,字符串常量則可以含一個或多個字符。
3.可以把一個字符常量賦予一個字符變量,但不能把一個字符串常量賦予一個字符變量。在
C語言中沒有相應的字符串變量。
這是與 BASIC  語言不同的。但是可以用一個字符數組來存放一個字符串常量。在數組一章
內予以介紹。
4.字符常量占一個字節的內存空間。字符串常量占的內存字節數等于字符串中字節數加 1。
增加的一個字節中存放字符"\0"(ASCII 碼為 0)。這是字符串結束的標志。例如,字符串 "C
program"在內存中所占的字節為:C program\0。字符常量'a'和字符串常量"a"雖然都只有一個
字符,但在內存中的情況是不同的。 
'a'在內存中占一個字節,可表示為:a
"a"在內存中占二個字節,可表示為:a\0 符號常量
 
符號常量  
在C語言中,可以用一個標識符來表示一個常量,稱之為符號常量。符號常量在使用之前必
須先定義,其一般形式為: 
#define  標識符  常量 
其中#define 也是一條預處理命令(預處理命令都?quot;#"開頭) ,稱為宏定義命令(在第九
章預處理程序中將進一步介紹),其功能是把該標識符定義為其后的常量值。一經定義,以
后在程序中所有出現該標識符的地方均代之以該常量值。習慣上符號常量的標識符用大寫字
母,變量標識符用小寫字母,以示區別。
#define PI 3.14159
void main()
{
  float s,r;
  r=5;
  s=PI*r*r;
總計164頁  當前為第18頁
  printf("s=%f\n",s);
}
由宏定義命令定義 PI  為 3.14159 s,r 定義為實數  5->r  PI*r*r->s
顯示程序結果 float s,r;  r=5;  s=PI*r*r;  本程序在主函數之前由宏定義命令定義 PI  為
3.14159,在程序中即以該值代替 PI  。s=PI*r*r等效于 s=3.14159*r*r。應該注意的是,符號
常量不是變量,它所代表的值在整個作用域內不能再改變。也就是說,在程序中,不能再用
賦值語句對它重新賦值。
 
變量的初值和類型轉換
 
變量賦初值
在程序中常常需要對變量賦初值,以便使用變量。語言程序中可有多種方法,在定義時賦以
初值的方法,這種方法稱為初始化。在變量說明中賦初值的一般形式為: 
類型說明符 變量 1=  值 1,變量 2= 值 2,……;  例如:
int a=b=c=5;
float x=3.2,y=3f,z=0.75;
char ch1='K',ch2='P';
應注意,在說明中不允許連續賦值,如 a=b=c=5 是不合法的。
void main()
{
  int a=3,b,c=5;
  b=a+c;
  printf("a=%d,b=%d,c=%d\n",a,b,c);
}
a<---3,b<--0,c<---5
b<--a+c
顯示程序運行結果
 
變量類型的轉換
變量的數據類型是可以轉換的。轉換的方法有兩種,  一種是自動轉換,一種是強制轉換。 
 
自動轉換  
自動轉換發生在不同數據類型的量混合運算時,由編譯系統自動完成。自動轉換遵循以下規
則: 
1.若參與運算量的類型不同,則先轉換成同一類型,然后進行運算。
2.轉換按數據長度增加的方向進行,以保證精度不降低。如 int 型和long型運算時,先把 int
量轉成 long型后再進行運算。
3.所有的浮點運算都是以雙精度進行的,即使僅含 float 單精度量運算的表達式,也要先轉
換成 double型,再作運算。
4.char 型和 short 型參與運算時,必須先轉換成 int 型。
5.在賦值運算中,賦值號兩邊量的數據類型不同時,  賦值號右邊量的類型將轉換為左邊量
的類型。  如果右邊量的數據類型長度左邊長時,將丟失一部分數據,這樣會降低精度,  丟
失的部分按四舍五入向前舍入。圖 21   表示了類型自動轉換的規則。
void main()
總計164頁  當前為第19頁
{
  float PI=3.14159;
  int s,r=5;
  s=r*r*PI;
  printf("s=%d\n",s);
}
PI<--3.14159
s<--0,r<--5
s<--r*r*PI
顯示程序運行結果
float PI=3.14159;
int s,r=5;
s=r*r*PI; 
本例程序中,PI 為實型;s,r 為整型。在執行 s=r*r*PI 語句時,r 和 PI 都轉換成 double 型
計算,結果也為 double型。但由于 s 為整型,故賦值結果仍為整型,舍去了小數部分。
強制類型轉換
強制類型轉換是通過類型轉換運算來實現的。其一般形式為: (類型說明符) (表達式) 其
功能是把表達式的運算結果強制轉換成類型說明符所表示的類型。例如: (float) a 把a
轉換為實型(int)(x+y) 把 x+y 的結果轉換為整型在使用強制轉換時應注意以下問題:
1.類型說明符和表達式都必須加括號(單個變量可以不加括號),如把(int)(x+y)寫成(int)
x+y 則成了把 x 轉換成int 型之后再與 y 相加了。
2.無論是強制轉換或是自動轉換, 都只是為了本次運算的需要而對變量的數據長度進行的臨
時性轉換,而不改變數據說明時對該變量定義的類型。
main()
{
  float f=5.75;
  printf("(int)f=%d,f=%f\n",(int)f,f);

f<--5.75
將 float f強制轉換成 int f float f=5.75;printf("(int)f=%d,f=%f\n",(int)f,f); 本
例表明,f 雖強制轉為 int 型,但只在運算中起作用, 是臨時的,而 f 本身的類型并不改
變。因此,(int)f 的值為 5(刪去了小數)而f 的值仍為 5.75。
 
基本運算符和表達式
 
運算符的種類、優先級和結合性
C語言中運算符和表達式數量之多, 在高級語言中是少見的。正是豐富的運算符和表達式
使C語言功能十分完善。 這也是C語言的主要特點之一。
C語言的運算符不僅具有不同的優先級, 而且還有一個特點,就是它的結合性。在表達式
中, 各運算量參與運算的先后順序不僅要遵守運算符優先級別的規定,還要受運算符結合
性的制約, 以便確定是自左向右進行運算還是自右向左進行運算。 這種結合性是其它高級
語言的運算符所沒有的,因此也增加了C語言的復雜性。
 
總計164頁  當前為第20頁
運算符的種類C語言的運算符可分為以下幾類:
1.算術運算符
用于各類數值運算。包括加(+)、減(-)、乘(*)、除(/)、求余(或稱模運算,%)、自增(++)、
自減(--)共七種。
2.關系運算符
用于比較運算。包括大于(>)、小于(<)、等于(==)、 大于等于(>=)、小于等于(<=)和不等
于(!=)六種。
3.邏輯運算符
用于邏輯運算。包括與(&&)、或(||)、非(!)三種。
4.位操作運算符
參與運算的量,按二進制位進行運算。包括位與(&)、位或(|)、位非(~)、位異或(^)、左移
(<<)、右移(>>)六種。
5.賦值運算符
用于賦值運算,分為簡單賦值(=)、復合算術賦值(+=,-=,*=,/=,%=)和復合位運算賦值(&=,
|=,^=,>>=,<<=)三類共十一種。
6.條件運算符
這是一個三目運算符,用于條件求值(?:)。
7.逗號運算符
用于把若干表達式組合成一個表達式(,)。
8.指針運算符
用于取內容(*)和取地址(&)二種運算。
9.求字節數運算符
用于計算數據類型所占的字節數(sizeof)。
10.特殊運算符
有括號(),下標[],成員(→,.)等幾種。
 
優先級和結合性
C語言中,運算符的運算優先級共分為 15 級。1 級最高,15 級最低。在表達式中,優先級
較高的先于優先級較低的進行運算。 而在一個運算量兩側的運算符優先級相同時, 則按運
算符的結合性所規定的結合方向處理。 C語言中各運算符的結合性分為兩種,即左結合性
(自左至右)和右結合性(自右至左)。例如算術運算符的結合性是自左至右,即先左后右。如
有表達式 x-y+z則 y 應先與“-”號結合, 執行 x-y 運算,然后再執行+z 的運算。這種自
左至右的結合方向就稱為“左結合性”。而自右至左的結合方向稱為“右結合性”。 最典
型的右結合性運算符是賦值運算符。如 x=y=z,由于“=”的右結合性,應先執行 y=z 再執行
x=(y=z)運算。 C語言運算符中有不少為右結合性,應注意區別,以避免理解錯誤。
 
算術運算符和算術表達式基本的算術運算符
1.加法運算符“+”加法運算符為雙目運算符,即應有兩個量參與加法運算。如 a+b,4+8 等。
具有右結合性。
2.減法運算符“-”減法運算符為雙目運算符。但“-”也可作負值運算符,此時為單目運算,
如-x,-5 等具有左結合性。
3.乘法運算符“*”雙目運算,具有左結合性。
4.除法運算符“/”雙目運算具有左結合性。參與運算量均為整型時, 結果也為整型,舍去
總計164頁  當前為第21頁
小數。如果運算量中有一個是實型,則結果為雙精度實型。
void main(){
printf("\n\n%d,%d\n",20/7,-20/7);
printf("%f,%f\n",20.0/7,-20.0/7);
}
雙目運算具有左結合性。參與運算量均為整型時, 結果也為整型,舍去小數。如果運算量
中有一個是實型,則結果為雙精度實型。 printf("\n\n%d,%d\n",20/7,-20/7);
printf("%f,%f\n",20.0/7,-20.0/7);
本例中,20/7,-20/7 的結果均為整型,小數全部舍去。而 20.0/7 和-20.0/7 由于有實數參
與運算,因此結果也為實型。
5.求余運算符(模運算符)“%”雙目運算,具有左結合性。要求參與運算的量均為整型。
求余運算的結果等于兩數相除后的余數。 
void main(){
printf("%d\n",100%3);
}
雙目運算,具有左結合性。求余運算符% 要求參與運算的量均為整型。本例輸出 100 除以 3
所得的余數 1。
 
自增1,自減1運算符
自增 1 運算符記為“++”,其功能是使變量的值自增 1。自減 1 運算符記為“--”,其功能
是使變量值自減 1。自增 1,自減1運算符均為單目運算,都具有右結合性。可有以下幾種
形式: ++i i 自增1 后再參與其它運算。--i i 自減 1 后再參與其它運算。
i++   i 參與運算后,i 的值再自增 1。
i--   i 參與運算后,i 的值再自減 1。 
在理解和使用上容易出錯的是 i++和 i--。 特別是當它們出在較復雜的表達式或語句中時,
常常難于弄清,因此應仔細分析。
void main(){
int i=8;
printf("%d\n",++i);
printf("%d\n",--i);
printf("%d\n",i++);
printf("%d\n",i--);
printf("%d\n",-i++);
printf("%d\n",-i--);
} i<--8
i<--i+1
i<--i-1
i<--i+1
i<--i-1
i<--i+1
i<--i-1 int i=8;
printf("%d\n",++i);
printf("%d\n",--i);
總計164頁  當前為第22頁
printf("%d\n",i++);
printf("%d\n",i--);
printf("%d\n",-i++);
printf("%d\n",-i--); 
i 的初值為8
第 2 行i 加1 后輸出故為 9;
第 3 行減1后輸出故為 8;
第 4 行輸出i 為8 之后再加 1(為 9);
第 5 行輸出i 為9 之后再減 1(為 8) ;
第 6 行輸出-8 之后再加1(為 9);
第 7 行輸出-9 之后再減1(為 8)
void main(){
int i=5,j=5,p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
printf("%d,%d,%d,%d",p,q,i,j);
}
i<--5,j<--5,p<--0,q<--0
i+i+i--->p,i+1-->i,i+1-->i,i+1-->i
j+1->j,j+1->j,j+1->j,j+j+j->q int i=5,j=5,p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
這個程序中,對 P=(i++)+(i++)+(i++)應理解為三個 i 相加,故 P 值為15。然后 i 再自增 1
三次相當于加 3 故i 的最后值為 8。而對于 q 的值則不然,q=(++j)+(++j)+(++j)應理解為
q 先自增1,再參與運算,由于 q 自增 1 三次后值為 8,三個 8 相加的和為 24,j的最后值
仍為 8。算術表達式表達式是由常量、變量、函數和運算符組合起來的式子。 一個表達式
有一個值及其類型, 它們等于計算表達式所得結果的值和類型。表達式求值按運算符的優
先級和結合性規定的順序進行。 單個的常量、變量、函數可以看作是表達式的特例。
算術表達式
是由算術運算符和括號連接起來的式子,  以下是算術表達式的例子: 
a+b    (a*2)/c  (x+r)*8-(a+b)/7   ++i  sin(x)+sin(y)    (++i)-(j++)+(k--)
 
賦值運算符和賦值表達式
簡單賦值運算符和表達式,簡單賦值運算符記為“=”。由“= ”連接的式子稱為賦值表達式。
其一般形式為:  變量=表達式  例如:
x=a+b
w=sin(a)+sin(b)
y=i+++--j  賦值表達式的功能是計算表達式的值再賦予左邊的變量。賦值運算符具有右結合
性。因此
a=b=c=5
可理解為
a=(b=(c=5))
在其它高級語言中,賦值構成了一個語句,稱為賦值語句。  而在 C 中,把“=”定義為運算
總計164頁  當前為第23頁
符,從而組成賦值表達式。  凡是表達式可以出現的地方均可出現賦值表達式。例如,式子
x=(a=5)+(b=8)是合法的。它的意義是把 5 賦予a,8 賦予b,再把 a,b 相加,和賦予 x  ,故 x
應等于 13。
在C語言中也可以組成賦值語句,按照C語言規定,  任何表達式在其未尾加上分號就構成
為語句。因此如 x=8;a=b=c=5;都是賦值語句,在前面各例中我們已大量使用過了。
如果賦值運算符兩邊的數據類型不相同,  系統將自動進行類型轉換,即把賦值號右邊的類
型換成左邊的類型。具體規定如下:
1.實型賦予整型,舍去小數部分。前面的例 2.9 已經說明了這種情況。
2.整型賦予實型,數值不變,但將以浮點形式存放,  即增加小數部分(小數部分的值為 0)。
3.字符型賦予整型,由于字符型為一個字節,  而整型為二個字節,故將字符的 ASCII 碼值
放到整型量的低八位中,高八位為 0。
4.整型賦予字符型,只把低八位賦予字符量。
void main(){
int a,b=322;
float x,y=8.88;
char c1='k',c2;
a=y;
x=b;
a=c1;
c2=b;
printf("%d,%f,%d,%c",a,x,a,c2);
}
int a,b=322;
float x,y=8.88;
char c1='k',c2;
printf("%d,%f,%d,%c",a=y,x=b,a=c1,c2=b);
本例表明了上述賦值運算中類型轉換的規則。a為整型,賦予實型量 y值 888   后只取整數
8。x 為實型,賦予整型量 b 值322,  后增加了小數部分。字符型量 c1 賦予 a 變為整型,整
型量 b 賦予 c2  后取其低八位成為字符型(b 的低八位為 01000010,即十進制 66,按 ASCII
碼對應于字符 B)。
 
復合賦值符及表達式
在賦值符“=”之前加上其它二目運算符可構成復合賦值符。如 
+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=。  構成復合賦值表達式的一般形式為:  變量  雙目運算
符=表達式 它等效于  變量=變量  運算符  表達式  例如:  a+=5  等價于a=a+5   x*=y+7  等
價于 x=x*(y+7)   r%=p 等價于 r=r%p
復合賦值符這種寫法,對初學者可能不習慣,  但十分有利于編譯處理,能提高編譯效率并
產生質量較高的目標代碼。逗號運算符和逗號表達式在
逗號運算符
C語言中逗號“,”也是一種運算符,稱為逗號運算符。  其功能是把兩個表達式連接起來組
成一個表達式,  稱為逗號表達式。
其一般形式為:  表達式 1,表達式 2  其求值過程是分別求兩個表達式的值,并以表達式 2
的值作為整個逗號表達式的值。
void main(){
總計164頁  當前為第24頁
int a=2,b=4,c=6,x,y;
x=a+b,y=b+c;
printf("y=%d,x=%d",y,x);
}
a<--2,b<--4,c<--6,x<--0,y<--0
x<--a+b,y<---b+c 
本例中,y 等于整個逗號表達式的值,也就是表達式 2 的值,x 是第一個表達式的值。對于
逗號表達式還要說明兩點:
1.逗號表達式一般形式中的表達式 1和表達式 2 也可以又是逗號表達式。例如:  表達式 1,
(表達式 2, 表達式 3)  形成了嵌套情形。因此可以把逗號表達式擴展為以下形式:   表達式 1,
表達式 2,…表達式 n  整個逗號表達式的值等于表達式 n 的值。
2.程序中使用逗號表達式,通常是要分別求逗號表達式內各表達式的值,并不一定要求整個
逗號表達式的值。
3.并不是在所有出現逗號的地方都組成逗號表達式,如在變量說明中,函數參數表中逗號只
是用作各變量之間的間隔符。
[Practice] //arithmeticint a,b,c;
float d;
a=11;
b=235;
c=a+b-a*b;
d=(float)c/(float)a;
a=c/a;'Vtable
a,2,0
b,2,0
c,2,0
d,4,0.0
of Vtable
'Vupdate
1,0;2,0;3,0
4,0.0
1,11
2,235
3,-2339
4,-212.636368
1,-212
of Vupdate
of Practice
[Practice] //1int a,b,c1,c2;
a=25;
b=3243;
c1=b/a;
c2=b%a;'Vtable
a,2,0
b,2,0
總計164頁  當前為第25頁
c1,2,0
c2,2,0
of Vtable
'Vupdate
1,0;2,0;3,0;4,0
1,25
2,3243
3,129
4,18
of Vupdate
of Practice
[Practice] //1int a,b,c;
a=25;
b=40;
c=a+b,c+35;'Vtable
a,2,0
b,2,0
c,2,0
of Vtable
'Vupdate
1,0;2,0;3,0
1,25
2,40
3,65
of Vupdate
of Practice
 
小結
 
1.C的數據類型
基本類型,構造類型,指針類型,空類型
2.基本類型的分類及特點
類型說明符       字節              數值范圍
字符型 char            1          C 字符集
基本整型 int         2          -32768~32767
短整型 short int       2               -32768~32767
長整型 long int       4       -214783648~214783647
無符號型 unsigned      2         0~65535
無符號長整型  unsigned long 4        0~4294967295
單精度實型 float      4        3/4E-38~3/4E+38
雙精度實型 double    8        1/7E-308~1/7E+308
3.常量后綴
L 或 l  長整型
U或 u  無符號數
總計164頁  當前為第26頁
F 或 f  浮點數
4.常量類型
整數,長整數,無符號數,浮點數,字符,字符串,符號常數,轉義字符。 
5.數據類型轉換
·自動轉換
在不同類型數據的混合運算中,由系統自動實現轉換,   由少字節類型向多字節類型轉換。   不
同類型的量相互賦值時也由系統自動進行轉換,把賦值號右邊的類型轉換為左邊的類型。
·強制轉換
由強制轉換運算符完成轉換。
6.運算符優先級和結合性
一般而言,單目運算符優先級較高,賦值運算符優先級低。  算術運算符優先級較高,關系
和邏輯運算符優先級較低。  多數運算符具有左結合性,單目運算符、三目運算符、  賦值
7.表達式
表達式是由運算符連接常量、變量、函數所組成的式子。   每個表達式都有一個值和類型。   表
達式求值按運算符的優先級和結合性所規定的順序進行。
總計164頁  當前為第27頁
C語言教程第三章: C語言程序設計初步
C語言程序設計
 
本課介紹C語言程序設計的基本方法和基本的程序語句。
從程序流程的角度來看,程序可以分為三種基本結構, 即順序結構、分支結構、循環結構。
 這三種基本結構可以組成所有的各種復雜程序。C語言提供了多種語句來實現這些程序結
構。 本章介紹這些基本語句及其應用,使讀者對C程序有一個初步的認識, 為后面各章的
學習打下基礎。
 
C程序的語句
 
C程序的執行部分是由語句組成的。 程序的功能也是由執行語句實現的。
C 語句可分為以下五類:
1.表達式語句
2.函數調用語句
3.控制語句
4.復合語句
5.空語句
 
1.表達式語句
 
表達式語句由表達式加上分號“;”組成。其一般形式為: 表達式; 執行表達式語句就是
計算表達式的值。例如: x=y+z; 賦值語句 y+z; 加法運算語句,但計算結果不能保留,無
實際意義 i++; 自增1 語句,i 值增1
2.函數調用語句
 
由函數名、實際參數加上分號“;”組成。其一般形式為: 函數名(實際參數表); 執行函
數語句就是調用函數體并把實際參數賦予函數定義中的形式參數,然后執行被調函數體中的
語句,求取函數值。(在第五章函數中再詳細介紹)例如 printf("C Program");調用庫函數,
輸出字符串。
3.控制語句
 
控制語句用于控制程序的流程, 以實現程序的各種結構方式。
它們由特定的語句定義符組成。C語言有九種控制語句。 可分成以下三類:
(1) 條件判斷語句
    if 語句,switch 語句
(2) 循環執行語句
    do while 語句,while 語句,for 語句
總計164頁  當前為第28頁
(3) 轉向語句
    break 語句,goto 語句,continue 語句,return 語句
 
4.復合語句
 
把多個語句用括號{}括起來組成的一個語句稱復合語句。 在程序中應把復合語句看成是單
條語句,而不是多條語句,例如 
{
x=y+z;
a=b+c;
printf(“%d%d”,x,a);
}
是一條復合語句。復合語句內的各條語句都必須以分號“;”結尾,在括號“}”外不能加分
號。
 
5.空語句
 
只有分號“;”組成的語句稱為空語句。 空語句是什么也不執行的語句。在程序中空語句
可用來作空循環體。例如 while(getchar()!='\n'); 本語句的功能是,只要從鍵盤輸入的
字符不是回車則重新輸入。這里的循環體為空語句。
 
賦值語句
 
賦值語句是由賦值表達式再加上分號構成的表達式語句。 其一般形式為: 變量=表達式;
賦值語句的功能和特點都與賦值表達式相同。 它是程序中使用最多的語句之一。 在賦值語
句的使用中需要注意以下幾點:
 
1.由于在賦值符“=”右邊的表達式也可以又是一個賦值表達式,因此,下述形式 變量=(變
量=表達式); 是成立的,從而形成嵌套的情形。其展開之后的一般形式為: 變量=變量=…=
表達式; 
例如:
a=b=c=d=e=5;按照賦值運算符的右接合性,因此實際上等效于: 
e=5;
d=e;
c=d;
b=c;
a=b;
2.注意在變量說明中給變量賦初值和賦值語句的區別。給變量賦初值是變量說明的一部分,
賦初值后的變量與其后的其它同類變量之間仍必須用逗號間隔,而賦值語句則必須用分號結
尾。 
3.在變量說明中,不允許連續給多個變量賦初值。 如下述說明是錯誤的: int a=b=c=5
必須寫為 int a=5,b=5,c=5; 而賦值語句允許連續賦值
4.注意賦值表達式和賦值語句的區別。賦值表達式是一種表達式,它可以出現在任何允許表
總計164頁  當前為第29頁
達式出現的地方,而賦值語句則不能。
下述語句是合法的: if((x=y+5)>0) z=x; 語句的功能是,若表達式 x=y+5 大于 0則z=x。
下述語句是非法的: if((x=y+5;)>0) z=x; 因為=y+5;是語句,不能出現在表達式中。
 
數據輸出語句
 
本小節介紹的是向標準輸出設備顯示器輸出數據的語句。在C語言中,所有的數據輸入/輸
出都是由庫函數完成的。 因此都是函數語句。本小節先介紹 printf 函數和 putchar 函數。
printf 函數printf 函數稱為格式輸出函數,其關鍵字最末一個字母 f即為“格式”(forma
t)之意。其功能是按用戶指定的格式, 把指定的數據顯示到顯示器屏幕上。在前面的例題
中我們已多次使用過這個函數。
 
一、printf函數調用的一般形式
 
printf 函數是一個標準庫函數,它的函數原型在頭文件“stdio.h”中。但作為一個特例,
不要求在使用 printf 函數之前必須包含 stdio.h 文件。printf 函數調用的一般形式為:
printf(“格式控制字符串”,輸出表列)其中格式控制字符串用于指定輸出格式。 格式控
制串可由格式字符串和非格式字符串兩種組成。格式字符串是以%開頭的字符串,在%后面跟
有各種格式字符,以說明輸出數據的類型、形式、長度、小數位數等。如“%d”表示按十進
制整型輸出,“%ld”表示按十進制長整型輸出,“%c”表示按字符型輸出等。后面將專門
給予討論。
 
非格式字符串在輸出時原樣照印,在顯示中起提示作用。 輸出表列中給出了各個輸出項,
要求格式字符串和各輸出項在數量和類型上應該一一對應。
void main()
{
int a=88,b=89;
printf("%d %d\n",a,b);
printf("%d,%d\n",a,b);
printf("%c,%c\n",a,b);
printf("a=%d,b=%d",a,b);
}
a<--8,b<--89
printf("%d %d\n",a,b);
printf("%d,%d\n",a,b);
printf("%c,%c\n",a,b);
printf("a=%d,b=%d",a,b);
本例中四次輸出了 a,b的值,但由于格式控制串不同,輸出的結果也不相同。第四行的輸出
語句格式控制串中,兩格式串%d 之間加了一個空格(非格式字符),所以輸出的 a,b值之間
有一個空格。 第五行的 printf 語句格式控制串中加入的是非格式字符逗號,  因此輸出的a,
b 值之間加了一個逗號。第六行的格式串要求按字符型輸出 a,b 值。第七行中為了提示輸出
結果又增加了非格式字符串。
總計164頁  當前為第30頁
二、格式字符串
 
在 Turbo C 中格式字符串的一般形式為: [標志][輸出最小寬度][.精度][長度]類型  其中方括
號[]中的項為可選項。各項的意義介紹如下:
1.類型類型字符用以表示輸出數據的類型,其格式符和意義下表所示:
表示輸出類型的格式字符         格式字符意義
d                   以十進制形式輸出帶符號整數(正數不輸出符號)
o                   以八進制形式輸出無符號整數(不輸出前綴 O)
x                   以十六進制形式輸出無符號整數(不輸出前綴 OX)
u                   以十進制形式輸出無符號整數
f                   以小數形式輸出單、雙精度實數
e                   以指數形式輸出單、雙精度實數
g                  以%f%e中較短的輸出寬度輸出單、雙精度實數
c                   輸出單個字符
s                   輸出字符串
2.標志
標志字符為-、+、#、空格四種,其意義下表所示: 
標志格式字符       標  志  意 義
-            結果左對齊,右邊填空格
+            輸出符號(正號或負號)空格輸出值為正時冠以空格,為負時冠以負號
#           對 c,s,d,u 類無影響;對 o 類,  在輸出時加前
綴 o           對x 類,在輸出時加前綴 0x;對 e,g,f  類當結果有小數時才給出小數

3.輸出最小寬度
用十進制整數來表示輸出的最少位數。   若實際位數多于定義的寬度,則按實際位數輸出,   若
實際位數少于定義的寬度則補以空格或 0。
4.精度
精度格式符以“.”開頭,后跟十進制整數。本項的意義是:如果輸出數字,則表示小數的位
數;如果輸出的是字符,  則表示輸出字符的個數;若實際位數大于所定義的精度數,則截
去超過的部分。
5.長度
長度格式符為 h,l 兩種,h 表示按短整型量輸出,l表示按長整型量輸出。
void main(){
int a=15;
float b=138.3576278;
double c=35648256.3645687;
char d='p';
printf("a=%d,%5d,%o,%x\n",a,a,a,a);
printf("b=%f,%lf,%5.4lf,%e\n",b,b,b,b);
printf("c=%lf,%f,%8.4lf\n",c,c,c);
printf("d=%c,%8c\n",d,d);
} a<--15
b<--138.3576278
c<--35648256.3645687
總計164頁  當前為第31頁
d<--'p'
 
main()
{
int a=29;
float b=1243.2341;
double c=24212345.24232;
char d='h';
printf("a=%d,%5d,%o,%x\n",a,a,a,a);
printf("b=%f,%lf,%5.4lf,%e\n",b,b,b,b);
printf("c=%lf,%f,%8.4lf\n",c,c,c);
printf("d=%c,%8c\n",d,d);

本例第七行中以四種格式輸出整型變量 a 的值,其中“%5d ”要求輸出寬度為 5,而 a值為 15
只有兩位故補三個空格。  第八行中以四種格式輸出實型量 b 的值。其中“%f”和“%lf ”格式
的輸出相同,說明“l”符對“f”類型無影響。“%5.4lf”指定輸出寬度為 5,精度為 4,由于實際
長度超過 5故應該按實際位數輸出,小數位數超過 4 位部分被截去。第九行輸出雙精度實數,
“%8.4lf ”由于指定精度為 4 位故截去了超過 4位的部分。第十行輸出字符量 d,其中“%bc ”
指定輸出寬度為 8 故在輸出字符 p之前補加 7個空格。
 
使用 printf 函數時還要注意一個問題,  那就是輸出表列中的求值順序。不同的編譯系統不
一定相同,可以從左到右,  也可從右到左。Turbo C 是按從右到左進行的。如把例 2.13 改
寫如下述形式:
void main(){
int i=8;
printf("%d\n%d\n%d\n%d\n%d\n%d\n",++i,--i,i--,i++,-i--);
} i<--8
 
這個程序與例 2.13 相比只是把多個 printf語句改一個 printf 語句輸出。但從結果可以看出是
不同的。為什么結果會不同呢?就是因為 printf 函數對輸出表中各量求值的順序是自右至左
進行  的。在式中,先對最后一項“-i--”求值,結果為-8,然后 i 自減 1 后為 7。  再對“-i++”項
求值得-7,然后 i 自增1 后為 8。再對“i--”項求值得 8,然后 i 再自減 1 后為7。再求“i++”項
得 7,然后 I 再自增 1 后為 8。  再求“--i”項,i 先自減 1 后輸出,輸出值為 7。  最后才求輸
出表列中的第一項“++i”,此時 i 自增 1 后輸出8。但是必須注意,  求值順序雖是自右至左,
但是輸出順序還是從左至右,  因此得到的結果是上述輸出結果。
 
字符輸出函數
 
putchar  函數
 
putchar  函數是字符輸出函數,  其功能是在顯示器上輸出單個字符。其一般形式為:
putchar(字符變量)  例如:
putchar('A'); 輸出大寫字母 A
putchar(x); 輸出字符變量 x 的值
總計164頁  當前為第32頁
putchar('\n');  換行  對控制字符則執行控制功能,不在屏幕上顯示。  使用本函數前必須要用
文件包含命令:
#include<stdio.h>
void main(){
char a='B',b='o',c='k';
putchar(a);putchar(b);putchar(b);putchar(c);putchar('\t');
putchar(a);putchar(b);
putchar('\n');
putchar(b);putchar(c);
}
 
數據輸入語句
 
C語言的數據輸入也是由函數語句完成的。  本節介紹從標準輸入設備—鍵盤上輸入數據的
函數 scanf 和 getchar。 scanf 函數 scanf 函數稱為格式輸入函數,即按用戶指定的格式從鍵
盤上把數據輸入到指定的變量之中。
 
一、scanf 函數的一般形式  
 
scanf 函數是一個標準庫函數,它的函數原型在頭文件“stdio.h”中,與 printf函數相同,C語
言也允許在使用 scanf函數之前不必包含 stdio.h文件。scanf函數的一般形式為: scanf(“格
式控制字符串”,地址表列);  其中,格式控制字符串的作用與 printf 函數相同,但不能顯示
非格式字符串,  也就是不能顯示提示字符串。地址表列中給出各變量的地址。  地址是由地
址運算符“&”后跟變量名組成的。例如,&a,&b分別表示變量 a 和變量b  的地址。這個地址
就是編譯系統在內存中給 a,b 變量分配的地址。在C語言中,使用了地址這個概念,這是與
其它語言不同的。  應該把變量的值和變量的地址這兩個不同的概念區別開來。變量的地址
是 C 編譯系統分配的,用戶不必關心具體的地址是多少。   變量的地址和變量值的關系如下: 
&a--->a567 a為變量名, 567 是變量的值, &a 是變量a 的地址。在賦值表達式中給變量賦值,
如: a=567  在賦值號左邊是變量名,不能寫地址,而 scanf函數在本質上也是給變量賦值,
但要求寫變量的地址,如&a。  這兩者在形式上是不同的。&是一個取地址運算符,&a 是一
個表達式,其功能是求變量的地址。
void main(){
int a,b,c;
printf("input a,b,c\n");
scanf("%d%d%d",&a,&b,&c);
printf("a=%d,b=%d,c=%d",a,b,c);

注意&的用法!
在本例中,由于 scanf 函數本身不能顯示提示串,故先用 printf 語句在屏幕上輸出提示,請
用戶輸入 a、b、c 的值。執行 scanf 語句,則退出 TC 屏幕進入用戶屏幕等待用戶輸入。用
戶輸入 7、8、9 后按下回車鍵,此時,系統又將返回 TC 屏幕。在 scanf 語句的格式串中由
于沒有非格式字符在“%d%d%d”之間作輸入時的間隔,  因此在輸入時要用一個以上的空格
或回車鍵作為每兩個輸入數之間的間隔。
如:  7 8 9
總計164頁  當前為第33頁

7
8
9
格式字符串
 
格式字符串的一般形式為: %[*][輸入數據寬度][長度]類型  其中有方括號[]的項為任選項。
各項的意義如下:
1.類型
表示輸入數據的類型,其格式符和意義下表所示。
格式        字符意義 
d       輸入十進制整數
o       輸入八進制整數
x       輸入十六進制整數
u       輸入無符號十進制整數
f 或 e      輸入實型數(用小數形式或指數形式)
c       輸入單個字符
s       輸入字符串
2.“*”符
用以表示該輸入項讀入后不賦予相應的變量,即跳過該輸入值。  如 scanf("%d %*d
%d",&a,&b);當輸入為:1 2 3  時,把1賦予 a,2 被跳過,3 賦予 b。
3.寬度
用十進制整數指定輸入的寬度(即字符數)。例如: scanf("%5d",&a);
輸入:
12345678
只把 12345賦予變量 a,其余部分被截去。又如: scanf("%4d%4d",&a,&b);
輸入:
12345678 將把 1234 賦予a,而把 5678 賦予b。
4.長度
長度格式符為 l 和h,l表示輸入長整型數據(如%ld)  和雙精度浮點數(如%lf)。h 表示輸入短
整型數據。
使用 scanf 函數還必須注意以下幾點:
a. scanf 函數中沒有精度控制,如: scanf("%5.2f",&a);  是非法的。不能企圖用此語句輸入
小數為 2 位的實數。
b. scanf 中要求給出變量地址,如給出變量名則會出錯。如 scanf("%d",a);是非法的,應改為
scnaf("%d",&a);才是合法的。
c.  在輸入多個數值數據時,若格式控制串中沒有非格式字符作輸入數據之間的間隔則可用
空格,TAB 或回車作間隔。C 編譯在碰到空格,TAB,回車或非法數據(如對“%d”輸入“12A”
時,A即為非法數據)時即認為該數據結束。
d.  在輸入字符數據時,若格式控制串中無非格式字符,則認為所有輸入的字符均為有效字
符。例如:
scanf("%c%c%c",&a,&b,&c);
輸入為:
d e f
總計164頁  當前為第34頁
則把'd'賦予 a, 'f'賦予 b,'e'賦予 c。只有當輸入為:
def
時,才能把'd'賦于 a,'e'賦予 b,'f'賦予 c。   如果在格式控制中加入空格作為間隔,如 scanf ("%c
%c %c",&a,&b,&c);則輸入時各數據之間可加空格。
void main(){
char a,b;
printf("input character a,b\n");
scanf("%c%c",&a,&b);
printf("%c%c\n",a,b);

scanf("'C14F14%c%c",&a,&b);
printf("%c%c\n",a,b); 由于scanf 函數"%c%c"中沒有空格,輸入 M N,結果輸出只有 M。
而輸入改為 MN時則可輸出 MN兩字符,見下面的輸入運行情況: input character a,b
MN
MN
void main(){
char a,b;
printf("input character a,b\n");
scanf("%c %c",&a,&b);
printf("\n%c%c\n",a,b);
}
scanf("%c %c",&a,&b); 本例表示 scanf格式控制串"%c %c"之間有空格時,  輸入的數據之間
可以有空格間隔。e.  如果格式控制串中有非格式字符則輸入時也要輸入該非格式字符。
例如:
scanf("%d,%d,%d",&a,&b,&c);  其中用非格式符“ , ”作間隔符,故輸入時應為: 5,6,7
又如: scanf("a=%d,b=%d,c=%d",&a,&b,&c);
則輸入應為
a=5,b=6,c=7g.  如輸入的數據與輸出的類型不一致時,雖然編譯能夠通過,但結果將不正確。 
void main(){
int a;
printf("input a number\n");
scanf("%d",&a);
printf("%ld",a);
}
由于輸入數據類型為整型,  而輸出語句的格式串中說明為長整型,因此輸出結果和輸入數
據不符。如改動程序如下: 
void main(){
long a;
printf("input a long integer\n");
scanf("%ld",&a);
printf("%ld",a);
}
運行結果為:
input a long integer
總計164頁  當前為第35頁
1234567890
1234567890 當輸入數據改為長整型后,輸入輸出數據相等。
 
鍵盤輸入函數
getchar 函數 getchar 函數的功能是從鍵盤上輸入一個字符。其一般形式為: getchar();  通常
把輸入的字符賦予一個字符變量,構成賦值語句,如:
char c;
c=getchar();
#include<stdio.h>
void main(){
char c;
printf("input a character\n");
c=getchar();
putchar(c);
}
使用 getchar函數還應注意幾個問題:
1.getchar函數只能接受單個字符,輸入數字也按字符處理。輸入多于一個字符時,只接收第
一個字符。 
2.使用本函數前必須包含文件“stdio.h”。 
3.在 TC 屏幕下運行含本函數程序時,將退出 TC  屏幕進入用戶屏幕等待用戶輸入。輸入完
畢再返回 TC屏幕。
void main(){
char a,b,c;
printf("input character a,b,c\n");
scanf("%c %c %c",&a,&b,&c);
printf("%d,%d,%d\n%c,%c,%c\n",a,b,c,a-32,b-32,c-32);
}
輸入三個小寫字母
輸出其 ASCII碼和對應的大寫字母。 
void main(){
int a;
long b;
float f;
double d;
char c;
printf("%d,%d,%d,%d,%d",sizeof(a),sizeof(b),sizeof(f)
,sizeof(d),sizeof(c));
}
輸出各種數據類型的字節長度。
分支結構程序 
 
關系運算符和表達式
 
總計164頁  當前為第36頁
在程序中經常需要比較兩個量的大小關系, 以決定程序下一步的工作。比較兩個量的運算
符稱為關系運算符。 在C語言中有以下關系運算符:
< 小于
<= 小于或等于 
> 大于
>= 大于或等于
== 等于
!= 不等于 
關系運算符都是雙目運算符,其結合性均為左結合。 關系運算符的優先級低于算術運算符,
高于賦值運算符。 在六個關系運算符中,<,<=,>,>=的優先級相同,高于==和!=,==和!=
的優先級相同。
關系表達式
關系表達式的一般形式為: 表達式 關系運算符 表達式 例如:a+b>c-d,x>3/2,'a'+1<c,-
i-5*j==k+1;都是合法的關系表達式。由于表達式也可以又是關系表達式。 因此也允許出現
嵌套的情況,例如:a>(b>c),a!=(c==d)等。關系表達式的值是“真”和“假”,用“1”和
“0”表示。
如: 5>0 的值為“真”,即為 1。(a=3)>(b=5)由于 3>5 不成立,故其值為假,即為 0。
void main(){
char c='k';
int i=1,j=2,k=3;
float x=3e+5,y=0.85;
printf("%d,%d\n",'a'+5<c,-i-2*j>=k+1);
printf("%d,%d\n",1<j<5,x-5.25<=x+y);
printf("%d,%d\n",i+j+k==-2*j,k==j==i+5);
}
char c='k';
int i=1,j=2,k=3;
float x=3e+5,y=0.85;
printf("%d,%d\n",'a'+5<c,-i-2*j>=k+1);
printf("%d,%d\n",1<j<5,x-5.25<=x+y);
printf("%d,%d\n",i+j+k==-2*j,k==j==i+5); 
在本例中求出了各種關系運算符的值。 字符變量是以它對應的 ASCII碼參與運算的。對于
含多個關系運算符的表達式,如 k==j==i+5,根據運算符的左結合性,先計算 k==j,該式不成
立,其值為 0,再計算 0==i+5,也不成立,故表達式值為 0。
 
邏輯運算符和表達式
 
邏輯運算符C語言中提供了三種邏輯運算符 && 與運算  || 或運算  ! 非運算 與運算符&
&和或運算符||均為雙目運算符。具有左結合性。 非
運算符!為單目運算符,具有右結合性。邏輯運算符和其它運算符優先級的關系可表示如下:
 
按照運算符的優先順序可以得出:
a>b && c>d等價于(a>b) && (c>d)
總計164頁  當前為第37頁
!b==c||d<a等價于((!b)==c)||(d<a)
a+b>c && x+y<b 等價于((a+b)>c) && ((x+y)<b)
邏輯運算的值
邏輯運算的值也為“真”和“假”兩種,用“1”和“0 ”來表示。其求值規則如下:
1.與運算&&參與運算的兩個量都為真時,結果才為真,否則為假。例如,5>0 && 4>2,由于
5>0 為真,4>2 也為真,相與的結果也為真。
2.或運算||參與運算的兩個量只要有一個為真,結果就為真。 兩個量都為假時,結果為假。
例如:5>0||5>8,由于5>0 為真,相或的結果也就為真
3.非運算!參與運算量為真時,結果為假;參與運算量為假時,結果為真。
例如:!(5>0)的結果為假。
雖然C編譯在給出邏輯運算值時,以“1”代表“真”,“0 ”代表“假”。 但反過來在判
斷一個量是為“真”還是為“假”時,以“0”代表“假”,以非“0”的數值作為“真”。
例如:由于 5 和3 均為非“0”因此 5&&3 的值為“真”,即為 1。
又如:5||0的值為“真”,即為 1。
邏輯表達式邏輯表達式的一般形式為: 表達式 邏輯運算符 表達式 其中的表達式可以又是
邏輯表達式,從而組成了嵌套的情形。例如:(a&&b)&&c 根據邏輯運算符的左結合性,上式
也可寫為: a&&b&&c 邏輯表達式的值是式中各種邏輯運算的最后值,以“1”和“0”分別
代表“真”和“假”。
void main(){
char c='k';
int i=1,j=2,k=3;
float x=3e+5,y=0.85;
printf("%d,%d\n",!x*!y,!!!x);
printf("%d,%d\n",x||i&&j-3,i<j&&x<y);
printf("%d,%d\n",i==5&&c&&(j=8),x+y||i+j+k);
}
本例中!x 和!y 分別為0,!x*!y 也為0,故其輸出值為 0。由于 x 為非0,故!!!x 的邏輯值
為 0。對x|| i && j-3 式,先計算 j-3 的值為非0,再求 i && j-3 的邏輯值為 1,故x||i
&&j-3 的邏輯值為 1。對i<j&&x<y 式,由于 i<j 的值為 1,而x<y為 0 故表達式的值為 1,
0 相與,最后為 0,對i==5&&c&&(j=8)式,由于 i==5 為假,即值為 0, 該表達式由兩個與
運算組成,所以整個表達式的值為 0。對于式 x+ y||i+j+k  由于 x+y 的值為非 0,故整個
或表達式的值為 1。
if語句
 
用 if 語句可以構成分支結構。它根據給定的條件進行判斷, 以決定執行某個分支程序段。
C語言的 if語句有三種基本形式。
1.第一種形式為基本形式 if(表達式) 語句; 其語義是:如果表達式的值為真,則執行其
后的語句, 否則不執行該語句。其過程可表示為下圖
void main(){
int a,b,max;
printf("\n input two numbers: ");
總計164頁  當前為第38頁
scanf("%d%d",&a,&b);
max=a;
if (max<b) max=b;
printf("max=%d",max);
}
輸入兩個整數,輸出其中的大數。 
scanf("%d%d",&a,&b);
max=a;
if (max<b) max=b;
printf("max=%d",max);
本例程序中,輸入兩個數 a,b。把 a先賦予變量 max,再用 if語句判別 max 和b 的大小,如
max 小于b,則把 b 賦予max。因此max 中總是大數,最后輸出 max 的值。
2.第二種形式為 if-else 形式 
if(表達式) 
語句 1; 
else 
語句 2;
其語義是:如果表達式的值為真,則執行語句 1,否則執行語句 2 。
void main(){
int a, b;
printf("input two numbers: ");
scanf("%d%d",&a,&b);
if(a>b)
printf("max=%d\n",a);
else
printf("max=%d\n",b);
}
輸入兩個整數,輸出其中的大數。改用 if-else 語句判別a,b 的大小,若 a 大,則輸出 a,
否則輸出 b。
3.第三種形式為 if-else-if形式
前二種形式的 if 語句一般都用于兩個分支的情況。  當有多個分支選擇時,可采用 if-else-if
語句,其一般形式為: 
if(表達式 1) 
語句 1; 
else if(表達式 2) 
語句 2; 
else if(表達式 3) 
語句 3; 
… 
else if(表達式 m) 
語句 m; 
else 
總計164頁  當前為第39頁
語句 n; 
其語義是:依次判斷表達式的值,當出現某個值為真時,  則執行其對應的語句。然后跳到
整個 if 語句之外繼續執行程序。  如果所有的表達式均為假,則執行語句 n  。  然后繼續執
行后續程序。 if-else-if語句的執行過程如圖 3—3 所示。
#include"stdio.h"
void main(){
char c;
printf("input a character: ");
c=getchar();
if(c<32)
printf("This is a control character\n");
else if(c>='0'&&c<='9')
printf("This is a digit\n");
else if(c>='A'&&c<='Z')
printf("This is a capital letter\n");
else if(c>='a'&&c<='z')
printf("This is a small letter\n");
else
printf("This is an other character\n");
}
本例要求判別鍵盤輸入字符的類別。可以根據輸入字符的 ASCII 碼來判別類型。由 ASCII
碼表可知 ASCII 值小于 32 的為控制字符。  在“0”和“9”之間的為數字,在“A”和“Z”之間為大
寫字母,  在“a”和“z”之間為小寫字母,其余則為其它字符。 這是一個多分支選擇的問題,
用 if-else-if 語句編程,判斷輸入字符 ASCII 碼所在的范圍,分別給出不同的輸出。例如輸
入為“g”,輸出顯示它為小寫字符。
 
4.在使用 if語句中還應注意以下問題
 
(1)  在三種形式的 if 語句中,在 if 關鍵字之后均為表達式。 該表達式通常是邏輯表達式或
關系表達式,  但也可以是其它表達式,如賦值表達式等,甚至也可以是一個變量。例如:
if(a=5)  語句;if(b)  語句;  都是允許的。只要表達式的值為非 0,即為“真”。如在 if(a=5)…;
中表達式的值永遠為非 0,所以其后的語句總是要執行的,當然這種情況在程序中不一定會
出現,但在語法上是合法的。
又如,有程序段: if(a=b)
printf("%d",a);
else
printf("a=0");  本語句的語義是,把 b 值賦予 a,如為非 0 則輸出該值,否則輸出“a=0”字符
串。這種用法在程序中是經常出現的。
 
(2)  在 if語句中,條件判斷表達式必須用括號括起來,  在語句之后必須加分號。
 
(3)  在 if語句的三種形式中,所有的語句應為單個語句,如果要想在滿足條件時執行一組(多
個)語句,則必須把這一組語句用{}  括起來組成一個復合語句。但要注意的是在}之后不能
再加分號。
總計164頁  當前為第40頁
例如:
if(a>b){
a++;
b++;
}
else{
a=0;
b=10;
}
 
if語句的嵌套
 
當 if語句中的執行語句又是 if語句時,則構成了 if  語句嵌套的情形。其一般形式可表示如
下: 
if(表達式) 
if語句; 
或者為
if(表達式) 
if語句; 
else 
if語句; 
在嵌套內的 if 語句可能又是 if-else 型的,這將會出現多個 if 和多個 else 重疊的情況,這時
要特別注意 if和 else 的配對問題。例如:
if(表達式 1)
if(表達式 2)
語句 1;
else
語句 2;
其中的 else究竟是與哪一個 if配對呢?
應該理解為:      還是應理解為: 
if(表達式 1)      if(表達式 1)
  if(表達式 2)       if(表達式 2)
  語句 1;        語句 1;
else           else
  語句 2;        語句 2; 
為了避免這種二義性,C語言規定,else  總是與它前面最近的 if 配對,因此對上述例子應
按前一種情況理解。
比較兩個數的大小關系。
void main(){
int a,b;
printf("please input A,B: ");
scanf("%d%d",&a,&b);
if(a!=b)
if(a>b) printf("A>B\n");
總計164頁  當前為第41頁
else printf("A<B\n");
else printf("A=B\n");
}
本例中用了 if語句的嵌套結構。  采用嵌套結構實質上是為了進行多分支選擇,例 3.16 實際
上有三種選擇即 A>B、A<B 或 A=B。這種問題用 if-else-if語句也可以完成。而且程序更加
清晰。因此,  在一般情況下較少使用 if語句的嵌套結構。 以使程序更便于閱讀理解。 
void main(){
int a,b;
printf("please input A,B: ");
scanf("%d%d",&a,&b);
if(a==b) printf("A=B\n");
else if(a>b) printf("A>B\n");
else printf("A<B\n");
}
 
條件運算符和條件表達式
 
如果在條件語句中,只執行單個的賦值語句時,  常可使用條件表達式來實現。不但使程序
簡潔,也提高了運行效率。
條件運算符為?和: ,它是一個三目運算符,即有三個參與運算的量。由條件運算符組成條件
表達式的一般形式為:
表達式 1? 表達式 2:  表達式 3 
其求值規則為:如果表達式 1 的值為真,則以表達式 2  的值作為條件表達式的值,否則以
表達式 2 的值作為整個條件表達式的值。  條件表達式通常用于賦值語句之中。
例如條件語句: 
if(a>b) max=a;
else max=b;
可用條件表達式寫為 max=(a>b)?a:b; 執行該語句的語義是:如 a>b 為真,則把 a 賦予 max,
否則把 b  賦予 max。
使用條件表達式時,還應注意以下幾點:
1.  條件運算符的運算優先級低于關系運算符和算術運算符,但高于賦值符。因此
max=(a>b)?a:b 可以去掉括號而寫為 max=a>b?a:b
2.  條件運算符?和:是一對運算符,不能分開單獨使用。
3.  條件運算符的結合方向是自右至左。
例如:
a>b?a:c>d?c:d 應理解為
a>b?a:(c>d?c:d)  這也就是條件表達式嵌套的情形,即其中的表達式 3 又是一個條
件表達式。
void main(){
int a,b,max;
printf("\n input two numbers: ");
scanf("%d%d",&a,&b);
printf("max=%d",a>b?a:b);
}
總計164頁  當前為第42頁
用條件表達式對上例重新編程,輸出兩個數中的大數。
switch語句
 
C語言還提供了另一種用于多分支選擇的 switch 語句, 其一般形式為: 
switch(表達式){ 
case 常量表達式 1: 語句 1; 
case 常量表達式 2: 語句 2; 
… 
case 常量表達式 n: 語句 n; 
default : 語句n+1; 

其語義是:計算表達式的值。 并逐個與其后的常量表達式值相比較,當表達式的值與某個
常量表達式的值相等時, 即執行其后的語句,然后不再進行判斷,繼續執行后面所有 case
后的語句。 如表達式的值與所有 case 后的常量表達式均不相同時,則執行 default后的語
句。
void main(){
int a;
printf("input integer number: ");
scanf("%d",&a);
switch (a){ 
case 1:printf("Monday\n");
case 2:printf("Tuesday\n");
case 3:printf("Wednesday\n");
case 4:printf("Thursday\n");
case 5:printf("Friday\n");
case 6:printf("Saturday\n");
case 7:printf("Sunday\n");
default:printf("error\n");
}
}
本程序是要求輸入一個數字,輸出一個英文單詞。但是當輸入 3 之后,卻執行了 case3 以及
以后的所有語句,輸出了 Wednesday 及以后的所有單詞。這當然是不希望的。為什么會出
現這種情況呢?這恰恰反應了 switch語句的一個特點。在 switch 語句中, “case 常量表達
式”只相當于一個語句標號, 表達式的值和某標號相等則轉向該標號執行,但不能在執行
完該標號的語句后自動跳出整個 switch 語句,所以出現了繼續執行所有后面 case語句的
情況。 這是與前面介紹的 if 語句完全不同的,應特別注意。為了避免上述情況, C語言
還提供了一種 break 語句,專用于跳出 switch語句,break 語句只有關鍵字 break,沒有
參數。在后面還將詳細介紹。修改例題的程序,在每一 case 語句之后增加 break 語句,
使每一次執行之后均可跳出 switch語句,從而避免輸出不應有的結果。
void main(){
int a;
總計164頁  當前為第43頁
printf("input integer number: ");
scanf("%d",&a);
switch (a){
case 1:printf("Monday\n");break;
case 2:printf("Tuesday\n"); break;
case 3:printf("Wednesday\n");break;
case 4:printf("Thursday\n");break;
case 5:printf("Friday\n");break;
case 6:printf("Saturday\n");break;
case 7:printf("Sunday\n");break;
default:printf("error\n");
}
}
在使用 switch 語句時還應注意以下幾點:
1.在case 后的各常量表達式的值不能相同,否則會出現錯誤。
2.在case 后,允許有多個語句,可以不用{}括起來。
3.各case 和default 子句的先后順序可以變動,而不會影響程序執行結果。
4.default 子句可以省略不用。程序舉例
輸入三個整數,輸出最大數和最小數。
void main(){
int a,b,c,max,min;
printf("input three numbers: ");
scanf("%d%d%d",&a,&b,&c);
if(a>b)
{max=a;min=b;}
else
{max=b;min=a;}
if(max<c)
max=c;
else
if(min>c)
min=c;
printf("max=%d\nmin=%d",max,min);
}
本程序中,首先比較輸入的 a,b 的大小,并把大數裝入 max, 小數裝入 min 中,然后再與 c
比較,若 max 小于c,則把 c 賦予max;如果 c 小于 min,則把 c 賦予min。因此 max內總是
最大數,而 min 內總是最小數。最后輸出 max 和min 的值即可。 計算器程序。用戶輸入運
算數和四則運算符, 輸出計算結果。
void main(){
float a,b,s;
char c;
printf("input expression: a+(-,*,/)b \n");
總計164頁  當前為第44頁
scanf("%f%c%f",&a,&c,&b);
switch(c){
case '+': printf("%f\n",a+b);break;
case '-': printf("%f\n",a-b);break;
case '*': printf("%f\n",a*b);break;
case '/': printf("%f\n",a/b);break;
default: printf("input error\n");
}
}
 
本例可用于四則運算求值。switch語句用于判斷運算符, 然后輸出運算值。當輸入運算符
不是+,-,*,/時給出錯誤提示。
 
循環結構程序
 
循環結構是程序中一種很重要的結構。其特點是, 在給定條件成立時,反復執行某程序段,
直到條件不成立為止。 給定的條件稱為循環條件,反復執行的程序段稱為循環體。 C語言
提供了多種循環語句,可以組成各種不同形式的循環結構。
 
while語句
 
while 語句的一般形式為: while(表達式)語句; 其中表達式是循環條件,語句為循環體。 
while 語句的語義是:計算表達式的值,當值為真(非 0)時, 執行循環體語句。其執行過程
可用圖 3—4表示。 統計從鍵盤輸入一行字符的個數。
#include <stdio.h>
void main(){
int n=0;
printf("input a string:\n");
while(getchar()!='\n') n++;
printf("%d",n);
}
本例程序中的循環條件為 getchar()!='\n',其意義是, 只要從鍵盤輸入的字符不是回車就
繼續循環。循環體 n++完成對輸入字符個數計數。從而程序實現了對輸入一行字符的字符個
數計數。
使用 while語句應注意以下幾點:
1.while 語句中的表達式一般是關系表達或邏輯表達式,只要表達式的值為真(非 0)即可繼
續循環。
void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
while (n--)
printf("%d ",a++*2);
總計164頁  當前為第45頁
}
本例程序將執行 n 次循環,每執行一次,n 值減 1。循環體輸出表達式 a++*2 的值。該表達
式等效于(a*2;a++)
2.循環體如包括有一個以上的語句,則必須用{}括起來, 組成復合語句。
3.應注意循環條件的選擇以避免死循環。
void main(){
int a,n=0;
while(a=5)
printf("%d ",n++);
}
本例中 while 語句的循環條件為賦值表達式 a=5, 因此該表達式的值永遠為真,而循環體
中又沒有其它中止循環的手段, 因此該循環將無休止地進行下去,形成死循環。4.允許 wh
ile 語句的循環體又是 while 語句,從而形成雙重循環。
do-while語句
 
do-while 語句的一般形式為: 
do
語句; 
while(表達式); 
其中語句是循環體,表達式是循環條件。
do-while 語句的語義是:
先執行循環體語句一次, 再判別表達式的值,若為真(非 0)則繼續循環,否則終止循環。
do-while 語句和 while語句的區別在于 do-while 是先執行后判斷,因此 do-while 至少要
執行一次循環體。 而 while 是先判斷后執行, 如果條件不滿足,則一次循環體語句也不執行。
while 語句和 do-while語句一般都可以相互改寫。
void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
do printf("%d ",a++*2);
while (--n);

 
在本例中,循環條件改為--n,否則將多執行一次循環。這是由于先執行后判斷而造成的。
對于 do-while 語句還應注意以下幾點:
1.在if 語句,while 語句中, 表達式后面都不能加分號, 而在 do-while語句的表達式后
面則必須加分號。
2.do-while語句也可以組成多重循環,而且也可以和 while 語句相互嵌套。
3.在do 和 while 之間的循環體由多個語句組成時,也必須用{}括起來組成一個復合語句。
4.do-while和 while 語句相互替換時,要注意修改循環控制條件。
 
for語句
總計164頁  當前為第46頁
 
for 語句是C語言所提供的功能更強,使用更廣泛的一種循環語句。其一般形式為: 
for(表達式1;表達式 2;表達 3) 
語句; 
表達式 1  通常用來給循環變量賦初值,一般是賦值表達式。也允許在 for 語句外給循環變
量賦初值,此時可以省略該表達式。
表達式 2  通常是循環條件,一般為關系表達式或邏輯表達式。
表達式 3  通常可用來修改循環變量的值,一般是賦值語句。
這三個表達式都可以是逗號表達式, 即每個表達式都可由多個表達式組成。三個表達式都
是任選項,都可以省略。
一般形式中的“語句”即為循環體語句。for 語句的語義是:
1.首先計算表達式 1 的值。
2.再計算表達式 2 的值,若值為真(非 0)則執行循環體一次, 否則跳出循環。 
3.然后再計算表達式 3的值,轉回第 2 步重復執行。在整個 for 循環過程中,表達式 1 只計
算一次,表達式 2 和表達式,3 則可能計算多次。循環體可能多次執行,也可能一次都不執
行。for 語句的執行過程如圖所示。
void main(){
int n,s=0;
for(n=1;n<=100;n++)
s=s+n;
printf("s=%d\n",s);

用 for 語句計算 s=1+2+3+...+99+100
int n,s=0;
for(n=1;n<=100;n++)
s=s+n;
printf("s=%d\n",s); 
本例 for 語句中的表達式 3 為n++,實際上也是一種賦值語句,相當于 n=n+1,以改變循環
變量的值。
void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;a++,n--)
printf("%d ",a*2);
}
用 for 語句修改例題。從 0 開始,輸出 n 個連續的偶數。
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;a++,n--)
printf("%d ",a*2);
總計164頁  當前為第47頁
本例的 for語句中,表達式 1 已省去,循環變量的初值在 for 語句之前由 scanf 語句取得,
表達式 3 是一個逗號表達式,由 a++,n-- 兩個表達式組成。每循環一次 a自增 1,n 自減 1。
a 的變化使輸出的偶數遞增,n 的變化控制循次數。
在使用 for語句中要注意以下幾點
1.for 語句中的各表達式都可省略,但分號間隔符不能少。如:for(;表達式;表達式)省
去了表達式 1。for(表達式;;表達式)省去了表達式 2。
for(表達式;表達式;)省去了表達式 3。for(;;)省去了全部表達式。
2.在循環變量已賦初值時,可省去表達式 1,如例 3.27 即屬于這種情形。如省去表達式 2
或表達式 3則將造成無限循環, 這時應在循環體內設法結束循環。例題即屬于此情況。
void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;)
{ a++;n--;
printf("%d ",a*2);
}
}
本例中省略了表達式 1和表達式 3,由循環體內的 n--語句進行循環變量 n 的遞減,以控制
循環次數。
void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;;){
a++;n--;
printf("%d ",a*2);
if(n==0)break;
}
}
本例中 for語句的表達式全部省去。由循環體中的語句實現循環變量的遞減和循環條件的判
斷。當 n 值為 0 時,由break 語句中止循環,轉去執行 for以后的程序。在此情況下,for
語句已等效于 while( 1)語句。如在循環體中沒有相應的控制手段,則造成死循環。
3.循環體可以是空語句。
#include"stdio.h"
void main(){
int n=0;
printf("input a string:\n");
for(;getchar()!='\n';n++);
printf("%d",n);
}
本例中,省去了 for 語句的表達式 1,表達式 3也不是用來修改循環變量,而是用作輸入字
符的計數。這樣, 就把本應在循環體中完成的計數放在表達式中完成了。因此循環體是空
總計164頁  當前為第48頁
語句。應注意的是,空語句后的分號不可少,如缺少此分號,則把后面的 printf 語句當成
循環體來執行。反過來說,如循環體不為空語句時, 決不能在表達式的括號后加分號,
這樣又會認為循環體是空語句而不能反復執行。這些都是編程中常見的錯誤,要十分注意。
4.for 語句也可與 while,do-while 語句相互嵌套,構成多重循環。以下形成都合法的嵌套。 
(1)for(){…
    while()
   {…}
  …
    }
(2)do{
   …
    for()
   {…}
  …
    }while();
(3)while(){
      …
      for()
       {…}
      …
     }
(4)for(){
    …
    for(){
    …
     }
    }
void main(){
int i,j,k;
for(i=1;i<=3;i++)
{ for(j=1;j<=3-i+5;j++)
printf(" ");
for(k=1;k<=2*i-1+5;k++)
{
if(k<=5) printf(" ");
else printf("*");
}
printf("\n");
}
}
轉移語句
 
總計164頁  當前為第49頁
程序中的語句通常總是按順序方向,  或按語句功能所定義的方向執行的。如果需要改變程
序的正常流向,  可以使用本小節介紹的轉移語句。在C語言中提供了 4 種轉移語句:
goto,break, continue 和 return。
其中的 return 語句只能出現在被調函數中,  用于返回主調函數,我們將在函數一章中具體
介紹。  本小節介紹前三種轉移語句。
 
1.goto 語句
 
goto 語句也稱為無條件轉移語句,其一般格式如下: goto 語句標號; 其中語句標號是按
標識符規定書寫的符號,  放在某一語句行的
前面,標號后加冒號(:)。語句標號起標識語句的作用,與 goto  語句配合使用。
如: label: i++; 
loop: while(x<7);  
C語言不限制程序中使用標號的次數,但各標號不得重名。 goto 語句的語義是改變程序流向, 
轉去執行語句標號所標識的語句。
goto 語句通常與條件語句配合使用。可用來實現條件轉移,  構成循環,跳出循環體等功能。 
但是,在結構化程序設計中一般不主張使用 goto 語句,  以免造成程序流程的混亂,使理解
和調試程序都產生困難。
統計從鍵盤輸入一行字符的個數。
#include"stdio.h"
void main(){
int n=0;
printf("input a string\n");
loop: if(getchar()!='\n')
{ n++;
goto loop;
}
printf("%d",n);
}
本例用 if 語句和 goto 語句構成循環結構。當輸入字符不為'\n'時即執行 n++進行計數,然后
轉移至 if語句循環執行。直至輸入字符為'\n'才停止循環。
 
break語句
 
break 語句只能用在 switch 語句或循環語句中,  其作用是跳出 switch 語句或跳出本層循環,
轉去執行后面的程序。 由于 break 語句的轉移方向是明確的,所以不需要語句標號與之配合。
break語句的一般形式為:  break;  上面例題中分別在switch語句和for語句中使用了break  語
句作為跳轉。使用 break語句可以使循環語句有多個出口,在一些場合下使編程更加靈活、
方便。
 
continue語句  
 
continue 語句只能用在循環體中,其一般格式是:
continue;
總計164頁  當前為第50頁
其語義是:結束本次循環,即不再執行循環體中 continue 語句之后的語句,轉入下一次循
環條件的判斷與執行。應注意的是,  本語句只結束本層本次的循環,并不跳出循環。
void main(){
int n;
for(n=7;n<=100;n++)
{
if (n%7!=0)
continue;
printf("%d ",n);
}
}
輸出 100 以內能被 7 整除的數。 
int n;
for(n=7;n<=100;n++)
{
if (n%7!=0)
continue;
printf("%d ",n);
}
本例中,對7~100的每一個數進行測試,如該數不能被7整除,即模運算不為0,則由continus
語句轉去下一次循環。只有模運算為 0 時,才能執行后面的 printf語句,輸出能被 7 整除的
數。
#include"stdio.h"
void main(){
char a,b;
printf("input a string:\n");
b=getchar();
while((a=getchar())!='\n'){
if(a==b){
printf("same character\n");
break;
}b=a;
}
}
檢查輸入的一行中有無相鄰兩字符相同。
char a,b;
printf("input a string:\n");
b=getchar();
while((a=getchar())!='\n'){
if(a==b){
printf("same character\n");
break;
}b=a;
}
總計164頁  當前為第51頁
本例程序中,把第一個讀入的字符送入 b。然后進入循環,把下一字符讀入 a,比較 a,b 是
否相等,若相等則輸出提示串并中止循環,若不相等則把 a 中的字符賦予 b,輸入下一次循
環。 
輸出 100 以內的素數。素數是只能被 1  和本身整除的數。可用窮舉法來判斷一個數是否是
素數。
void main(){
int n,i;
for(n=2;n<=100;n++){
for(i=2;i<n;i++)
if(n%i==0) break;
if(i>=n) printf("\t%d",n);
}
} int n,i;
for(n=2;n<=100;n++){
for(i=2;i<n;i++)
if(n%i==0) break;
if(i>=n) printf("\t%d",n);
}
本例程序中,第一層循環表示對 1~100 這 100 個數逐個判斷是否是素數,共循環 100 次,
在第二層循環中則對數 n 用2~n-1逐個去除,若某次除盡則跳出該層循環,說明不是素數。 
如果在所有的數都是未除盡的情況下結束循環,則為素數,此時有 i>=n,  故可經此判斷后
輸出素數。然后轉入下一次大循環。實際上,2 以上的所有偶數均不是素數,因此可以使循
環變量的步長值改為 2,即每次增加 2,此外只需對數 n 用2~n去除就可判斷該數是否素數。
這樣將大大減少循環次數,減少程序運行時間。
#include"math.h"
void main(){
int n,i,k;
for(n=2;n<=100;n+=2){
k=sqrt(n);
for(i=2;i<k;i++)
if(n%i==0) break;
if(i>=k) printf("\t%2d",n);
}
}
小結
 
1.從程序執行的流程來看,  程序可分為三種最基本的結構:  順序結構,分支結構以及循環
結構 
 
2.程序中執行部分最基本的單位是語句。C語言的語句可分為五類:
(1)表達式語句    任何表達式末尾加上分號即可構成表達式語句,  常用的表達式語句為賦
值語句。
(2)函數調用語句    由函數調用加上分號即組成函數調用語句。
(3)控制語句    用于控制程序流程,由專門的語句定義符及所需的表達式組成。主要有條
總計164頁  當前為第52頁
件判斷執行語句,循環執行語句,轉向語句等。
(4)復合語句    由{}把多個語句括起來組成一個語句。  復合語句被認為是單條語句,它可
出現在所有允許出現語句的地方,如循環體等。
(5)空語句    僅由分號組成,無實際功能。
 
3.C語言中沒有提供專門的輸入輸出語句,  所有的輸入輸出都是由調用標準庫函數中的輸
入輸出函數來實現的。
scanf 和getchar函數是輸入函數,接收來自鍵盤的輸入數據。
scanf 是格式輸入函數, 可按指定的格式輸入任意類型數據。
getchar函數是字符輸入函數,  只能接收單個字符。 
printf和 putchar函數是輸出函數,向顯示器屏幕輸出數據。
printf是格式輸出函數,可按指定的格式顯示任意類型的數據。
putchar是字符顯示函數,只能顯示單個字符。 
 
4.關系表達式和邏輯表達式是兩種重要的表達式,  主要用于條件執行的判斷和循環執行的
判斷。
 
5.C語言提供了多種形式的條件語句以構成分支結構。
(1)if 語句主要用于單向選擇。
(2)if-else 語句主要用于雙向選擇。
(3)if-else-if語和 switch語句用于多向選擇。
這幾種形式的條件語句一般來說是可以互相替代的。
 
6.C語言提供了三種循環語句。
(1)for 語句主要用于給定循環變量初值,  步長增量以及循環次數的循環結構。
(2)循環次數及控制條件要在循環過程中才能確定的循環可用 while 或do-while 語句。
(3)三種循環語句可以相互嵌套組成多重循環。循環之間可以并列但不能交叉。
(4)可用轉移語句把流程轉出循環體外,但不能從外面轉向循環體內。
(5)在循環程序中應避免出現死循環,即應保證循環變量的值在運行過程中可以得到修改,
并使循環條件逐步變為假,從而結束循環。
 
7.C語言語句小結
名  稱         一  般  形 式
簡單語句        表達式語句表達式; 
空語句; 
復合語句          {  語句 }
條件語句        if(表達式)語句; 
            if(表達式)語句 1; else語句 2; 
            if(表達式 1)語句 1; else if(表達式 2)  語句 2…else語句 n;
開關語句          switch(表達式){ case 常量表達式:  語句…default: 語句; }
循環語句        while 語句
            while(表達式)語句; 
            for語句 for(表達式 1; 表達式 2;  表達式 3)語句; 
            break 語句 break; 
總計164頁  當前為第53頁
            goto 語句 goto; 
            continue 語句 continue; 
            return  語句 return(表達式);
總計164頁  當前為第54頁
C語言教程第四章: 數組
數 組
 
    數組在程序設計中,為了處理方便, 把具有相同類型的若干變量按有序的形式組織起
來。這些按序排列的同類數據元素的集合稱為數組。在C語言中, 數組屬于構造數據類型。
一個數組可以分解為多個數組元素,這些數組元素可以是基本數據類型或是構造類型。因此
按數組元素的類型不同,數組又可分為數值數組、字符數組、指針數組、結構數組等各種類
別。
 
    本章介紹數值數組和字符數組,其余的在以后各章陸續介紹。數組類型說明 在C語言
中使用數組必須先進行類型說明。 數組說明的一般形
式為: 類型說明符 數組名 [常量表達式],……; 其中,類型說明符是任一種基本數據類
型或構造數據類型。 數組名是用戶定義的數組標識符。 方括號中的常量表達式表示數據元
素的個數,也稱為數組的長度。
例如:
int a[10]; 說明整型數組 a,有10個元素。
float b[10],c[20]; 說明實型數組 b,有 10 個元素,實型數組 c,有20個元素。
char ch[20]; 說明字符數組 ch,有20 個元素。
 
對于數組類型說明應注意以下幾點:
1.數組的類型實際上是指數組元素的取值類型。對于同一個數組,其所有元素的數據類型都
是相同的。
2.數組名的書寫規則應符合標識符的書寫規定。 
3.數組名不能與其它變量名相同,例如: 
void main()

int a;
float a[10];
……
}
是錯誤的。
4.方括號中常量表達式表示數組元素的個數,如 a[5]表示數組 a 有5個元素。但是其下標
從 0 開始計算。因此 5個元素分別為 a[0],a[1],a[2],a[3],a[4]。
5.不能在方括號中用變量來表示元素的個數, 但是可以是符號常數或常量表達式。例如:
#define FD 5
void main()

int a[3+2],b[7+FD];
……
}
總計164頁  當前為第55頁
是合法的。但是下述說明方式是錯誤的。 
void main()

int n=5;
int a[n];
……
}
6.允許在同一個類型說明中,說明多個數組和多個變量。
例如: int a,b,c,d,k1[10],k2[20];
數組元素的表示方法
 
    數組元素是組成數組的基本單元。數組元素也是一種變量,  其標識方法為數組名后跟
一個下標。  下標表示了元素在數組中的順序號。數組元素的一般形式為:  數組名[下標]  其
中的下標只能為整型常量或整型表達式。如為小數時,C 編譯將自動取整。例如,
a[5],a[i+j],a[i++]都是合法的數組元素。   數組元素通常也稱為下標變量。必須先定義數組,   才
能使用下標變量。在C語言中只能逐個地使用下標變量,   而不能一次引用整個數組。   例如,
輸出有 10  個元素的數組必須使用循環語句逐個輸出各下標變量: 
for(i=0; i<10; i++)   printf("%d",a[i]); 而不能用一個語句輸出整個數組,下面的寫法是錯誤
的: printf("%d",a);
void main()
{
int i,a[10];
for(i=0;i<10;i++)
a[i++]=2*i+1;
for(i=9;i>=0;i--)
printf("%d",a[i]);
printf("\n%d %d\n",a[5.2],a[5.8]);
}
    本例中用一個循環語句給 a 數組各元素送入奇數值,然后用第二個循環語句從大到小輸
出各個奇數。在第一個 for語句中,表達式 3 省略了。在下標變量中使用了表達式 i++,用
以修改循環變量。當然第二個 for語句也可以這樣作,  C語言允許用表達式表示下標。  程
序中最后一個 printf 語句輸出了兩次 a[5]的值,  可以看出當下標不為整數時將自動取整。
數組的賦值給數組賦值的方法除了用賦值語句對數組元素逐個賦值外,  還可采用初始化賦
值和動態賦值的方法。數組初始化賦值數組初始化賦值是指在數組說明時給數組元素賦予初
值。  數組初始化是在編譯階段進行的。這樣將減少運行時間,提高效率。
 
    初始化賦值的一般形式為: static  類型說明符  數組名[常量表達式]={值,值……值};
其中 static 表示是靜態存儲類型, C語言規定只有靜態存儲數組和外部存儲數組才可作初
始化賦值(有關靜態存儲,外部存儲的概念在第五章中介紹)。在{ }中的各數據值即為各元素
的初值,  各值之間用逗號間隔。例如: static int a[10]={ 0,1,2,3,4,5,6,7,8,9 };  相當于
a[0]=0;a[1]=1...a[9]=9;
 
    C語言對數組的初始賦值還有以下幾點規定:
總計164頁  當前為第56頁
1.可以只給部分元素賦初值。當{ }中值的個數少于元素個數時,只給前面部分元素賦值。例
如:  static int a[10]={0,1,2,3,4};表示只給 a[0]~a[4]5 個元素賦值,而后 5 個元素自動賦 0值。 
2.只能給元素逐個賦值,不能給數組整體賦值。 例如給十個元素全部賦 1 值,只能寫為:
static int a[10]={1,1,1,1,1,1,1,1,1,1};而不能寫為: static int a[10]=1;
3.如不給可初始化的數組賦初值,則全部元素均為 0 值。
4.如給全部元素賦值,則在數組說明中,  可以不給出數組元素的個數。例如: static int
a[5]={1,2,3,4,5};可寫為:  static int a[]={1,2,3,4,5};動態賦值可以在程序執行過程中,對數組
作動態賦值。  這時可用循環語句配合 scanf函數逐個對數組元素賦值。
void main()
{
int i,max,a[10];
printf("input 10 numbers:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
max=a[0];
for(i=1;i<10;i++)
if(a[i]>max) max=a[i];
printf("maxmum=%d\n",max);
}
    本例程序中第一個 for語句逐個輸入 10 個數到數組 a 中。  然后把 a[0]送入 max 中。在
第二個 for語句中,從 a[1]到 a[9]逐個與 max 中的內容比較,若比 max 的值大,則把該下標
變量送入 max 中,因此 max 總是在已比較過的下標變量中為最大者。比較結束,輸出 max
的值。
void main()
{
int i,j,p,q,s,a[10];
printf("\n input 10 numbers:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<10;i++){
p=i;q=a[i];
for(j=i+1;j<10;j++)
if(q<a[j]) { p=j;q=a[j]; }
if(i!=p)
{s=a[i];
a[i]=a[p];
a[p]=s; }
printf("%d",a[i]);
}
}
    本例程序中用了兩個并列的for循環語句,在第二個for  語句中又嵌套了一個循環語句。
第一個 for語句用于輸入 10 個元素的初值。第二個 for語句用于排序。本程序的排序采用逐
個比較的方法進行。在 i 次循環時,把第一個元素的下標 i 賦于 p,而把該下標變量值 a[i]
賦于 q。然后進入小循環,從 a[i+1]起到最后一個元素止逐個與 a[i]作比較,有比 a[i]大者則
總計164頁  當前為第57頁
將其下標送 p,元素值送 q。  一次循環結束后,p 即為最大元素的下標,q 則為該元素值。
若此時 i≠p,說明 p,q 值均已不是進入小循環之前所賦之值,則交換 a[i]和 a[p]之值。  此時
a[i]為已排序完畢的元素。輸出該值之后轉入下一次循環。對 i+1 以后各個元素排序。
二維數組
 
    前面介紹的數組只有一個下標,稱為一維數組,  其數組元素也稱為單下標變量。在實
際問題中有很多量是二維的或多維的,  因此C語言允許構造多維數組。多維數組元素有多
個下標,  以標識它在數組中的位置,所以也稱為多下標變量。  本小節只介紹二維數組,多
維數組可由二維數組類推而得到。二維數組類型說明二維數組類型說明的一般形式是: 
類型說明符 數組名[常量表達式 1][常量表達式 2]…; 
其中常量表達式 1 表示第一維下標的長度,常量表達式 2 表示第二維下標的長度。例如: 
int a[3][4]; 說明了一個三行四列的數組,數組名為 a,其下標變量的類型為整型。該數組的
下標變量共有 3×4 個,即: a[0][0],a[0][1],a[0][2],a[0][3]
a[1][0],a[1][1],a[1][2],a[1][3]
a[2][0],a[2][1],a[2][2],a[2][3]
    二維數組在概念上是二維的,即是說其下標在兩個方向上變化,  下標變量在數組中的
位置也處于一個平面之中,  而不是象一維數組只是一個向量。但是,實際的硬件存儲器卻
是連續編址的,  也就是說存儲器單元是按一維線性排列的。  如何在一維存儲器中存放二維
數組,可有兩種方式:一種是按行排列,  即放完一行之后順次放入第二行。另一種是按列
排列,  即放完一列之后再順次放入第二列。在C語言中,二維數組是按行排列的。  在圖
4.1 中,按行順次存放,先存放 a[0]行,再存放 a[1]行,最后存放 a[2]行。每行中有四個元
素也是依次存放。由于數組 a 說明為
int 類型,該類型占兩個字節的內存空間,所以每個元素均占有兩個  字節(圖中每一格為一
字節)。
 
二維數組元素的表示方法
 
    二維數組的元素也稱為雙下標變量,其表示的形式為:  數組名[下標][下標]  其中下標
應為整型常量或整型表達式。例如: a[3][4]  表示 a 數組三行四列的元素。下標變量和數組
說明在形式中有些相似,但這兩者具有完全不同的含義。  數組說明的方括號中給出的是某
一維的長度,即可取下標的最大值;  而數組元素中的下標是該元素在數組中的位置標識。
前者只能是常量,  后者可以是常量,變量或表達式。
一個學習小組有 5 個人,每個人有三門課的考試成績。求全組分科的平均成績和各科總平均
成績。 
課程  成績姓名 Math  C  DBASE
張       80   75  92
王       61   65  71
李       59   63  70
趙       85   87  90
周       76   77  85
    可設一個二維數組 a[5][3]存放五個人三門課的成績。再設一個一維數組 v[3]存放所求得
各分科平均成績,設變量 l 為全組各科總平均成績。編程如下:
void main()
{
總計164頁  當前為第58頁
int i,j,s=0,l,v[3],a[5][3];
printf("input score\n");
for(i=0;i<3;i++){
for(j=0;j<5;j++)
{ scanf("%d",&a[j][i]);
s=s+a[j][i];}
v[i]=s/5;
s=0;
}
l=(v[0]+v[1]+v[2])/3;
printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);
printf("total:%d\n",l);
}
    程序中首先用了一個雙重循環。  在內循環中依次讀入某一門課程的各個學生的成績,
并把這些成績累加起來,  退出內循環后再把該累加成績除以 5 送入 v[i]之中,這就是該門
課程的平均成績。外循環共循環三次,分別求出三門課各自的平均成績并存放在 v 數組之中。
退出外循環之后,把 v[0],v[1],v[2]相加除以 3 即得到各科總平均成績。最后按題意輸出各個
成績。
二維數組的初始化
    二維數組初始化也是在類型說明時給各下標變量賦以初值。   二維數組可按行分段賦值,
也可按行連續賦值。  例如對數組 a[5][3]:
1. 按行分段賦值可寫為 static int
a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };  
2.按行連續賦值可寫為 static int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85 };  
    這兩種賦初值的結果是完全相同的。
void main()
{
int i,j,s=0,l,v[3];
static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},
{85,87,90},{76,77,85} };
for(i=0;i<3;i++)
{ for(j=0;j<5;j++)
s=s+a[j][i];
v[i]=s/5;
s=0;
}
l=(v[0]+v[1]+v[2])/3;
printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);
printf("total:%d\n",l);
}
    對于二維數組初始化賦值還有以下說明:
1.可以只對部分元素賦初值,未賦初值的元素自動取 0 值。
例如: static int a[3][3]={{1},{2},{3}}; 是對每一行的第一列元素賦值,未賦值的元素取 0
值。  賦值后各元素的值為:  1 0 02 0 03 0 0  
總計164頁  當前為第59頁
static int a [3][3]={{0,1},{0,0,2},{3}}; 賦值后的元素值為  0 1 00 0 23 0 0  
2.如對全部元素賦初值,則第一維的長度可以不給出。
例如:  static int a[3][3]={1,2,3,4,5,6,7,8,9}; 可以寫為:static int a[][3]={1,2,3,4,5,6,7,8,9};
    數組是一種構造類型的數據。  二維數組可以看作是由一維數組的嵌套而構成的。設一
維數組的每個元素都又是一個數組,  就組成了二維數組。當然,前提是各元素類型必須相
同。根據這樣的分析,一個二維數組也可以分解為多個一維數組。  C語言允許這種分解有
二維數組 a[3][4],可分解為三個一維數組,其數組名分別為 a[0],a[1],a[2]。對這三個一維數
組不需另作說明即可使用。這三個一維數組都有 4 個元素,例如:一維數組 a[0]的元素為
a[0][0],a[0][1],a[0][2],a[0][3]。必須強調的是,a[0],a[1],a[2]不能當作下標變量使用,它們是
數組名,不是一個單純的下標變量。
 
字符數組
 
    用來存放字符量的數組稱為字符數組。  字符數組類型說明的形式與前面介紹的數值數
組相同。例如: char c[10];  由于字符型和整型通用,也可以定義為 int c[10]但這時每個數組
元素占 2 個字節的內存單元。字符數組也可以是二維或多維數組,例如: char c[5][10];即為
二維字符數組。  字符數組也允許在類型說明時作初始化賦值。例如:  static char c[10]={`c`,`
`,`p`,`r`,o`,g`,r`,`a`,`m`};賦值后各元素的值為:   數組 C c[0]c[1]c[2]c[3]c[4]c [5]c[6]c[7]c[8]c[9]
其中 c[9]未賦值,由系統自動賦予 0 值。  當對全體元素賦初值時也可以省去長度說明。例
如: static char c[]={`c`,` `,`p`,`r`,`o`,`g`,`r`,`a`,`m`};這時 C 數組的長度自動定為 9。
main()
{
int i,j;
char a[][5]={{'B','A','S','I','C',},{'d','B','A','S','E'}};
for(i=0;i<=1;i++)
{
for(j=0;j<=4;j++)
printf("%c",a[i][j]);
printf("\n");
}
}
    本例的二維字符數組由于在初始化時全部元素都賦以初值,  因此一維下標的長度可以
不加以說明。字符串在C語言中沒有專門的字符串變量,  通常用一個字符數組來存放一個
字符串。在 2.1.4 節介紹字符串常量時,已說明字符串總是以'\0'作為串的結束符。因此當把
一個字符串存入一個數組時,  也把結束符'\0'存入數組,并以此作為該字符串是否結束的標
志。  有了'\0'標志后,就不必再用字符數組的長度來判斷字符串的長度了。 
    C語言允許用字符串的方式對數組作初始化賦值。例如: 
static char c[]={'c', ' ','p','r','o','g','r','a','m'};  可寫為:
static char c[]={"C program"};  或去掉{}寫為:
sratic char c[]="C program";
用字符串方式賦值比用字符逐個賦值要多占一個字節,  用于存放字符串結束標志'\0'。上面
的數組 c 在內存中的實際存放情況為: C program\0`\0'是由 C 編譯系統自動加上的。由于采
用了`\0'標志,所以在用字符串賦初值時一般無須指定數組的長度,  而由系統自行處理。在
采用字符串方式后,字符數組的輸入輸出將變得簡單方便。  除了上述用字符串賦初值的辦
總計164頁  當前為第60頁
法外,還可用 printf函數和 scanf 函數一次性輸出輸入一個字符數組中的字符串,  而不必使
用循環語句逐個地輸入輸出每個字符。
void main()
{
static char c[]="BASIC\ndBASE";
printf("%s\n",c);
}
注意在本例的 printf 函數中,使用的格式字符串為“%s”,  表示輸出的是一個字符串。而在
輸出表列中給出數組名則可。  不能寫為: printf("%s",c[]);
void main()
{
char st[15];
printf("input string:\n");
scanf("%s",st);
printf("%s\n",st);
}
    本例中由于定義數組長度為 15,  因此輸入的字符串長度必須小于 15,以留出一個字節
用于存放字符串結束標志`\0`。  應該說明的是,對一個字符數組,如果不作初始化賦值,則
必須說明數組長度。還應該特別注意的是,當用 scanf函數輸入字符串時,字符串中不能含
有空格,否則將以空格作為串的結束符。例如運行例 4.8,當輸入的字符串中含有空格時,
運行情況為:  input string:this is a book this  從輸出結果可以看出空格以后的字符都未能輸
出。  為了避免這種情況,  可多設幾個字符數組分段存放含空格的串。程序可改寫如下:
Lesson
void main()
{
char st1[6],st2[6],st3[6],st4[6];
printf("input string:\n");
scanf("%s%s%s%s",st1,st2,st3,st4);
printf("%s %s %s %s\n",st1,st2,st3,st4);
}
    本程序分別設了四個數組,  輸入的一行字符的空格分段分別裝入四個數組。然后分別
輸出這四個數組中的字符串。在前面介紹過,scanf 的各輸入項必須以地址方式出現,如
&a,&b 等。但在例 4.8 中卻是以數組名方式出現的,這是為什么呢?這是由于在C語言中規
定,數組名就代表了該數組的首地址。  整個數組是以首地址開頭的一塊連續的內存單元。
如有字符數組 char c[10],在內存可表示如圖 4.2。設數組 c 的首地址為 2000,也就是說c[0]
單元地址為 2000。則數組名 c 就代表這個首地址。因此在 c 前面不能再加地址運算符&。如
寫作 scanf("%s",&c);則是錯誤的。  在執行函數 printf("%s",c)  時,按數組名 c 找到首地址,
然后逐個輸出數組中各個字符直到遇到字符串終止標志'\0'為止。
字符串常用函數
 
    C語言提供了豐富的字符串處理函數,  大致可分為字符串的輸入、輸出、合并、修改、
比較、轉換、復制、搜索幾類。  使用這些函數可大大減輕編程的負擔。用于輸入輸出的字
符串函數,  在使用前應包含頭文件"stdio.h"  ;  使用其它字符串函數則應包含頭文件
"string.h"。 下面介紹幾個最常用的字符串函數。
總計164頁  當前為第61頁
1.字符串輸出函數 puts  格式: puts (字符數組名)  功能:把字符數組中的字符串輸出到顯示
器。  即在屏幕上顯示該字符串
#include"stdio.h"
main()
{
static char c[]="BASIC\ndBASE";
puts(c);
}
 
    從程序中可以看出 puts 函數中可以使用轉義字符,  因此輸出結果成為兩行。puts 函數
完全可以由 printf函數取代。  當需要按一定格式輸出時,通常使用 printf函數。
2.字符串輸入函數 gets  格式: gets (字符數組名)  功能:從標準輸入設備鍵盤上輸入一個字
符串。  本函數得到一個函數值,即為該字符數組的首地址。
#include"stdio.h"
main()
{
char st[15];
printf("input string:\n");
gets(st);
puts(st);
}
    可以看出當輸入的字符串中含有空格時,輸出仍為全部字符串。說明 gets 函數并不以
空格作為字符串輸入結束的標志, 而只以回車作為輸入結束。這是與 scanf 函數不同的。
3.字符串連接函數 strcat 格式: strcat (字符數組名 1,字符數組名 2)  功能:把字符數組 2
中的字符串連接到字符數組 1  中字符串的后面,并刪去字符串 1 后的串標志“\0”。本函數返
回值是字符數組 1 的首地址。
#include"string.h"
main()
{
static char st1[30]="My name is ";
int st2[10];
printf("input your name:\n");
gets(st2);
strcat(st1,st2);
puts(st1);
}
 
本程序把初始化賦值的字符數組與動態賦值的字符串連接起來。  要注意的是,字符數組 1
應定義足夠的長度,否則不能全部裝入被連接的字符串
4.字符串拷貝函數 strcpy 格式: strcpy (字符數組名 1,字符數組名 2)  功能:把字符數組 2
中的字符串拷貝到字符數組 1 中。串結束標志“\0”也一同拷貝。字符數名 2,  也可以是一個
字符串常量。這時相當于把一個字符串賦予一個字符數組。
#include"string.h"
main()
總計164頁  當前為第62頁
{
static char st1[15],st2[]="C Language";
strcpy(st1,st2);
puts(st1);printf("\n");
}
本函數要求字符數組 1應有足夠的長度,否則不能全部裝入所拷貝的字符串。
5.字符串比較函數 strcmp  格式: strcmp(字符數組名 1,字符數組名 2)  功能:按照 ASCII
碼順序比較兩個數組中的字符串,并由函數返回值返回比較結果。 
字符串 1=字符串 2,返回值=0;
字符串 2〉字符串 2,返回值〉0;
字符串 1〈字符串 2,返回值〈0。
本函數也可用于比較兩個字符串常量,或比較數組和字符串常量。
#include"string.h"
main()
{ int k;
static char st1[15],st2[]="C Language";
printf("input a string:\n");
gets(st1);
k=strcmp(st1,st2);
if(k==0) printf("st1=st2\n");
if(k>0) printf("st1>st2\n");
if(k<0) printf("st1<st2\n");
}
    本程序中把輸入的字符串和數組 st2 中的串比較,比較結果返回到 k 中,根據k值再輸
出結果提示串。當輸入為 dbase 時,由 ASCII  碼可知“dBASE”大于“C Language”故 k〉0,輸
出結果“st1>st2”。
6.測字符串長度函數 strlen  格式: strlen(字符數組名)  功能:測字符串的實際長度(不含字符
串結束標志‘\0’)  并作為函數返回值。
#include"string.h"
main()
{ int k;
static char st[]="C language";
k=strlen(st);
printf("The lenth of the string is %d\n",k);
程序舉例
 
    把一個整數按大小順序插入已排好序的數組中。  為了把一個數按大小插入已排好序的
數組中,  應首先確定排序是從大到小還是從小到大進行的。設排序是從大到小進序的,  則
可把欲插入的數與數組中各數逐個比較,  當找到第一個比插入數小的元素 i 時,該元素之
前即為插入位置。然后從數組最后一個元素開始到該元素為止,逐個后移一個單元。最后把
插入數賦予元素 i 即可。如果被插入數比所有的元素值都小則插入最后位置。
main()
{
int i,j,p,q,s,n,a[11]={127,3,6,28,54,68,87,105,162,18};
總計164頁  當前為第63頁
for(i=0;i<10;i++)
{ p=i;q=a[i];
for(j=i+1;j<10;j++)
if(q<a[j]) {p=j;q=a[j];}
if(p!=i)
{
s=a[i];
a[i]=a[p];
a[p]=s;
}
printf("%d ",a[i]);
}
printf("\ninput number:\n");
scanf("%d",&n);
for(i=0;i<10;i++)
if(n>a[i])
{for(s=9;s>=i;s--) a[s+1]=a[s];
break;}
a[i]=n;
for(i=0;i<=10;i++)
printf("%d ",a[i]);
printf("\n");
}
本程序首先對數組 a 中的 10 個數從大到小排序并輸出排序結果。然后輸入要插入的整數 n。
再用一個 for 語句把 n 和數組元素逐個比較,如果發現有 n>a[i]時,則由一個內循環把 i 以
下各元素值順次后移一個單元。后移應從后向前進行(從 a[9]開始到 a[i]為止)。  后移結束跳
出外循環。插入點為 i,把 n 賦予a[i]即可。  如所有的元素均大于被插入數,則并未進行過
后移工作。此時 i=10,結果是把 n賦于 a[10]。最后一個循環輸出插入數后的數組各元素值。
程序運行時,輸入數 47。從結果中可以看出 47已插入到 54和 28 之間。
在二維數組a中選出各行最大的元素組成一個一維數組b。   a=3 16 87 65 4 32 11 108 10 25 12
37b=(87 108 37)  本題的編程思路是,在數組 A的每一行中尋找最大的元素,找到之后把該
值賦予數組 B 相應的元素即可。程序如下:
main()
{
static int a[][4]={3,16,87,65,4,32,11,108,10,25,12,27};
int b[3],i,j,l;
for(i=0;i<=2;i++)
{ l=a[i][0];
for(j=1;j<=3;j++)
if(a[i][j]>l) l=a[i][j];
b[i]=l;}
printf("\narray a:\n");
for(i=0;i<=2;i++)
{ for(j=0;j<=3;j++)
總計164頁  當前為第64頁
printf("%5d",a[i][j]);
printf("\n");}
printf("\narray b:\n");
for(i=0;i<=2;i++)
printf("%5d",b[i]);
printf("\n");
}
 
    程序中第一個 for語句中又嵌套了一個 for語句組成了雙重循環。外循環控制逐行處理,
并把每行的第 0 列元素賦予 l。進入內循環后,把 l 與后面各列元素比較,并把比 l 大者賦
予 l。內循環結束時 l  即為該行最大的元素,然后把 l 值賦予 b[i]。等外循環全部完成時,數
組 b 中已裝入了 a 各行中的最大值。后面的兩個 for語句分別輸出數組 a 和數組b。
 
    輸入五個國家的名稱按字母順序排列輸出。 
    本題編程思路如下:五個國家名應由一個二維字符數組來處理。然而C語言規定可以把
一個二維數組當成多個一維數組處理。  因此本題又可以按五個一維數組處理,  而每一個一
維數組就是一個國家名字符串。用字符串比較函數比較各一維數組的大小,并排序,  輸出
結果即可。
編程如下:
void main()
{
char st[20],cs[5][20];
int i,j,p;
printf("input country's name:\n");
for(i=0;i<5;i++)
gets(cs[i]);
printf("\n");
for(i=0;i<5;i++)
{ p=i;strcpy(st,cs[i]);
for(j=i+1;j<5;j++)
if(strcmp(cs[j],st)<0) {p=j;strcpy(st,cs[j]);}
if(p!=i)
{
strcpy(st,cs[i]);
strcpy(cs[i],cs[p]);
strcpy(cs[p],st);
}
puts(cs[i]);}printf("\n");
}
 
    本程序的第一個 for語句中,用 gets 函數輸入五個國家名字符串。上面說過C語言允許
把一個二維數組按多個一維數組處理,  本程序說明 cs[5][20]為二維字符數組,可分為五個
一維數組 cs[0],cs[1],cs[2],cs[3],cs[4]。因此在gets 函數中使用 cs[i]是合法的。  在第二
個 for語句中又嵌套了一個 for語句組成雙重循環。   這個雙重循環完成按字母順序排序的工
總計164頁  當前為第65頁
作。在外層循環中把字符數組 cs[i]中的國名字符串拷貝到數組 st 中,并把下標 i 賦予 P。  進
入內層循環后,把 st 與 cs[i]以后的各字符串作比較,若有比 st 小者則把該字符串拷貝到 st
中,并把其下標賦予 p。內循環完成后如 p 不等于 i  說明有比 cs[i]更小的字符串出現,因
此交換 cs[i]和 st 的內容。   至此已確定了數組 cs 的第 i 號元素的排序值。然后輸出該字符串。
在外循環全部完成之后即完成全部排序和輸出。
 
本章小結  
 
1.數組是程序設計中最常用的數據結構。數組可分為數值數組(整數組,實數組),字符數組
以及后面將要介紹的指針數組,結構數組等。
 
2.數組可以是一維的,二維的或多維的。
 
3.數組類型說明由類型說明符、數組名、數組長度 (數組元素個數)三部分組成。數組元素又
稱為下標變量。  數組的類型是指下標變量取值的類型。
 
4.對數組的賦值可以用數組初始化賦值,  輸入函數動態賦值和賦值語句賦值三種方法實現。 
對數值數組不能用賦值語句整體賦值、輸入或輸出,而必須用循環語句逐個對數組元素進行
操作。
總計164頁  當前為第66頁
C語言教程第五章:函數
概述
 
    在第一章中已經介紹過,C源程序是由函數組成的。  雖然在前面各章的程序中都只有
一個主函數 main(),  但實用程序往往由多個函數組成。函數是C源程序的基本模塊,  通過
對函數模塊的調用實現特定的功能。C語言中的函數相當于其它高級語言的子程序。  C語
言不僅提供了極為豐富的庫函數(如 Turbo C,MS C  都提供了三百多個庫函數),還允許用
戶建立自己定義的函數。用戶可把自己的算法編成一個個相對獨立的函數模塊,然后用調用
的方法來使用函數。
 
    可以說C程序的全部工作都是由各式各樣的函數完成的,  所以也把C語言稱為函數式
語言。  由于采用了函數模塊式的結構,  C語言易于實現結構化程序設計。使程序的層次結
構清晰,便于程序的編寫、閱讀、調試。
 
    在C語言中可從不同的角度對函數分類。
 
1.  從函數定義的角度看,函數可分為庫函數和用戶定義函數兩種。
 
(1)庫函數
    由C系統提供,用戶無須定義,  也不必在程序中作類型說明,只需在程序前包含有該
函數原型的頭文件即可在程序中直接調用。在前面各章的例題中反復用到 printf 、 scanf 、 
getchar  、putchar、gets、puts、strcat 等函數均屬此類。
 
(2)用戶定義函數
    由用戶按需要寫的函數。對于用戶自定義函數,  不僅要在程序中定義函數本身,  而且
在主調函數模塊中還必須對該被調函數進行類型說明,然后才能使用。
 
2.  C語言的函數兼有其它語言中的函數和過程兩種功能,從這個角度看,又可把函數分為
有返回值函數和無返回值函數兩種。
 
(1)有返回值函數
    此類函數被調用執行完后將向調用者返回一個執行結果,  稱為函數返回值。如數學函
數即屬于此類函數。  由用戶定義的這種要返回函數值的函數,必須在函數定義和函數說明
中明確返回值的類型。
 
(2)無返回值函數
    此類函數用于完成某項特定的處理任務,  執行完成后不向調用者返回函數值。這類函
數類似于其它語言的過程。  由于函數無須返回值,用戶在定義此類函數時可指定它的返回
為“空類型”,  空類型的說明符為“void”。
 
3.  從主調函數和被調函數之間數據傳送的角度看又可分為無參函數和有參函數兩種。
總計164頁  當前為第67頁
 
(1)無參函數
    函數定義、函數說明及函數調用中均不帶參數。  主調函數和被調函數之間不進行參數
傳送。  此類函數通常用來完成一組指定的功能,可以返回或不返回函數值。
 
(2)有參函數
    也稱為帶參函數。在函數定義及函數說明時都有參數,  稱為形式參數(簡稱為形參)。
在函數調用時也必須給出參數,  稱為實際參數(簡稱為實參)。  進行函數調用時,主調函數
將把實參的值傳送給形參,供被調函數使用。
 
4.  C語言提供了極為豐富的庫函數,  這些庫函數又可從功能角度作以下分類。
(1)字符類型分類函數
    用于對字符按 ASCII 碼分類:字母,數字,控制字符,分隔符,大小寫字母等。
(2)轉換函數
    用于字符或字符串的轉換;在字符量和各類數字量 (整型,  實型等)之間進行轉換;在
大、小寫之間進行轉換。
(3)目錄路徑函數
    用于文件目錄和路徑操作。
(4)診斷函數
    用于內部錯誤檢測。
(5)圖形函數
    用于屏幕管理和各種圖形功能。 
(6)輸入輸出函數
    用于完成輸入輸出功能。
(7)接口函數
    用于與 DOS,BIOS 和硬件的接口。
(8)字符串函數 
    用于字符串操作和處理。
(9)內存管理函數
    用于內存管理。
(10)數學函數
    用于數學函數計算。
(11)日期和時間函數
    用于日期,時間轉換操作。
(12)進程控制函數
    用于進程管理和控制。
(13)其它函數
    用于其它各種功能。
   
    以上各類函數不僅數量多,而且有的還需要硬件知識才會使用,因此要想全部掌握則需
要一個較長的學習過程。  應首先掌握一些最基本、  最常用的函數,再逐步深入。由于篇幅
關系,本書只介紹了很少一部分庫函數,  其余部分讀者可根據需要查閱有關手冊。
 
    還應該指出的是,在C語言中,所有的函數定義,包括主函數 main 在內,都是平行的。
總計164頁  當前為第68頁
也就是說,在一個函數的函數體內,  不能再定義另一個函數,  即不能嵌套定義。但是函數
之間允許相互調用,也允許嵌套調用。習慣上把調用者稱為主調函數。  函數還可以自己調
用自己,稱為遞歸調用。main  函數是主函數,它可以調用其它函數,而不允許被其它函數
調用。   因此,C程序的執行總是從 main 函數開始,   完成對其它函數的調用后再返回到 main
函數,最后由 main 函數結束整個程序。一個C源程序必須有,也只能有一個主函數 main。
函數定義的一般形式
 
1.無參函數的一般形式 
類型說明符 函數名() 

類型說明 
語句 
}
    其中類型說明符和函數名稱為函數頭。  類型說明符指明了本函數的類型,函數的類型
實際上是函數返回值的類型。  該類型說明符與第二章介紹的各種說明符相同。  函數名是由
用戶定義的標識符,函數名后有一個空括號,其中無參數,但括號不可少。{}  中的內容稱
為函數體。在函數體中也有類型說明,  這是對函數體內部所用到的變量的類型說明。在很
多情況下都不要求無參函數有返回值,  此時函數類型符可以寫為 void。
我們可以改為一個函數定義: 
void Hello()
{
printf ("Hello,world \n");
}
  這里,只把 main 改為Hello 作為函數名,其余不變。Hello 函數是一個無參函數,當被其
它函數調用時,輸出 Hello world 字符串。
 
2.有參函數的一般形式 
類型說明符 函數名(形式參數表) 
型式參數類型說明 

類型說明 
語句 
}
    有參函數比無參函數多了兩個內容,其一是形式參數表,  其二是形式參數類型說明。
在形參表中給出的參數稱為形式參數,  它們可以是各種類型的變量, 各參數之間用逗號間
隔。在進行函數調用時,主調函數將賦予這些形式參數實際的值。  形參既然是變量,當然
必須給以類型說明。例如,定義一個函數,  用于求兩個數中的大數,可寫為:
int max(a,b)
int a,b;
{
if (a>b) return a;
else return b;

    第一行說明 max 函數是一個整型函數,其返回的函數值是一個整數。形參為 a,b。第二
總計164頁  當前為第69頁
行說明 a,b 均為整型量。 a,b  的具體值是由主調函數在調用時傳送過來的。在{}中的函數體
內,  除形參外沒有使用其它變量,因此只有語句而沒有變量類型說明。  上邊這種定義方法
稱為“傳統格式”。  這種格式不易于編譯系統檢查,從而會引起一些非常細微而且難于跟蹤
的錯誤。ANSI C  的新標準中把對形參的類型說明合并到形參表中,稱為“現代格式”。
  例如 max 函數用現代格式可定義為:
int max(int a,int b)
{
if(a>b) return a;
else return b;

    現代格式在函數定義和函數說明(后面將要介紹)時,  給出了形式參數及其類型,在編
譯時易于對它們進行查錯,  從而保證了函數說明和定義的一致性。例 1.3 即采用了這種現
代格式。  在 max 函數體中的 return語句是把 a(或 b)的值作為函數的值返回給主調函數。有
返回值函數中至少應有一個 return語句。  在C程序中,一個函數的定義可以放在任意位置, 
既可放在主函數 main 之前,也可放在 main 之后。例如例 1.3 中定義了一個 max  函數,其
位置在 main之后,  也可以把它放在 main 之前。
修改后的程序如下所示。
int max(int a,int b)
{
if(a>b)return a;
else return b;
}
void main()
{
int max(int a,int b);
int x,y,z;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z=max(x,y);
printf("maxmum=%d",z);
}
    現在我們可以從函數定義、  函數說明及函數調用的角度來分析整個程序,從中進一步
了解函數的各種特點。程序的第 1 行至第 5 行為 max 函數定義。進入主函數后,因為準備
調用 max 函數,故先對 max 函數進行說明(程序第 8 行)。函數定義和函數說明并不是一回
事,在后面還要專門討論。  可以看出函數說明與函數定義中的函數頭部分相同,但是末尾
要加分號。程序第 12  行為調用 max 函數,并把 x,y 中的值傳送給 max 的形參 a,b。max 函
數執行的
結果 (a 或b)將返回給變量 z。最后由主函數輸出 z 的值。
 
    函數調用的一般形式前面已經說過,在程序中是通過對函數的調用來執行函數體的,其
過程與其它語言的子程序調用相似。C語言中,  函數調用的一般形式為: 
 
    函數名(實際參數表)  對無參函數調用時則無實際參數表。  實際參數表中的參數可以是
常數,變量或其它構造類型數據及表達式。  各實參之間用逗號分隔。'Next of Page 在C語
總計164頁  當前為第70頁
言中,可以用以下幾種方式調用函數:
1.函數表達式
    函數作表達式中的一項出現在表達式中,以函數返回值參與表達式的運算。這種方式要
求函數是有返回值的。例如: z=max(x,y)是一個賦值表達式,把 max 的返回值賦予變量 z。
'Next of Page
2.函數語句
    函數調用的一般形式加上分號即構成函數語句。例如:  printf ("%D",a);scanf ("%d",&b);
都是以函數語句的方式調用函數。
3.函數實參
    函數作為另一個函數調用的實際參數出現。  這種情況是把該函數的返回值作為實參進
行傳送,因此要求該函數必須是有返回值的。例如: printf("%d",max(x,y));  即是把 max 調
用的返回值又作為 printf函數的實參來使用的。在函數調用中還應該注意的一個問題是求值
順序的問題。   所謂求值順序是指對實參表中各量是自左至右使用呢,還是自右至左使用。   對
此,  各系統的規定不一定相同。在 3.1.3 節介紹printf 函數時已提
到過,這里從函數調用的角度再強調一下。  看例 5.2 程序。
void main()
{
int i=8;
printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);
}
如按照從右至左的順序求值。例 5.2的運行結果應為:
8
7
7
8
如對 printf語句中的++i,--i,i++,i--從左至右求值,結果應為:
9
8
8
9
    應特別注意的是,無論是從左至右求值, 還是自右至左求值,其輸出順序都是不變的, 
即輸出順序總是和實參表中實參的順序相同。由于 Turbo C現定是自右至左求值,所以結果
為 8,7,7,8。上述問題如還不理解,上機一試就明白了。函數的參數和函數的值
一、函數的參數
    前面已經介紹過,函數的參數分為形參和實參兩種。  在本小節中,進一步介紹形參、
實參的特點和兩者的關系。  形參出現在函數定義中,在整個函數體內都可以使用,  離開該
函數則不能使用。實參出現在主調函數中,進入被調函數后,實參變量也不能使用。  形參
和實參的功能是作數據傳送。發生函數調用時,  主調函數把實參的值傳送給被調函數的形
參從而實現主調函數向被調函數的數據傳送。
 
    函數的形參和實參具有以下特點:
1.形參變量只有在被調用時才分配內存單元,在調用結束時,  即刻釋放所分配的內存單元。
因此,形參只有在函數內部有效。  函數調用結束返回主調函數后則不能再使用該形參變量。 
 
總計164頁  當前為第71頁
2.實參可以是常量、變量、表達式、函數等,  無論實參是何種類型的量,在進行函數調用
時,它們都必須具有確定的值,  以便把這些值傳送給形參。  因此應預先用賦值,輸入等辦
法使實參獲得確定值。
 
3.實參和形參在數量上,類型上,順序上應嚴格一致,  否則會發生“類型不匹配”的錯誤。
 
4.函數調用中發生的數據傳送是單向的。  即只能把實參的值傳送給形參,而不能把形參的
值反向地傳送給實參。  因此在函數調用過程中,形參的值發生改變,而實參中的值不會變
化。例 5.3 可以說明這個問題。
void main()
{
int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);
}
int s(int n)
{
int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
}
本程序中定義了一個函數 s,該函數的功能是求∑ni=1i  的值。在主函數中輸入 n 值,并作為
實參,在調用時傳送給 s  函數的形參量 n(  注意,本例的形參變量和實參變量的標識符都為
n,  但這是兩個不同的量,各自的作用域不同)。  在主函數中用 printf 語句輸出一次 n 值,
這個 n 值是實參 n 的值。在函數 s 中也用 printf 語句輸出了一次 n 值,這個 n 值是形參最后
取得的 n 值 0。從運行情況看,輸入 n 值為 100。即實參 n 的值為 100。把此值傳給函數 s
時,形參 n 的初值也為 100,在執行函數過程中,形參 n 的值變為 5050。  返回主函數之后,
輸出實參 n的值仍為 100。可見實參的值不隨形參的變化而變化。
二、函數的值
 
    函數的值是指函數被調用之后,  執行函數體中的程序段所取得的并返回給主調函數的
值。如調用正弦函數取得正弦值,調用例 5.1 的 max 函數取得的最大數等。對函數的值(或
稱函數返回值)有以下一些說明:
 
1.  函數的值只能通過 return 語句返回主調函數。return  語句的一般形式為: 
return  表達式; 
或者為:
return (表達式);
該語句的功能是計算表達式的值,并返回給主調函數。  在函數中允許有多個 return 語句,
但每次調用只能有一個 return  語句被執行,  因此只能返回一個函數值。
 
總計164頁  當前為第72頁
2.  函數值的類型和函數定義中函數的類型應保持一致。  如果兩者不一致,則以函數類型為
準,自動進行類型轉換。
 
3.  如函數值為整型,在函數定義時可以省去類型說明。
 
4.  不返回函數值的函數,可以明確定義為“空類型”,  類型說明符為“void”。如例 5.3 中函
數 s 并不向主函數返函數值,因此可定義為:
void s(int n)
{ ……
}
 
    一旦函數被定義為空類型后,  就不能在主調函數中使用被調函數的函數值了。例如,
在定義 s 為空類型后,在主函數中寫下述語句 sum=s(n);  就是錯誤的。為了使程序有良好的
可讀性并減少出錯,  凡不要求返回值的函數都應定義為空類型。函數說明在主調函數中調
用某函數之前應對該被調函數進行說明,  這與使用變量之前要先進行變量說明是一樣的。
在主調函數中對被調函數作說明的目的是使編譯系統知道被調函數返回值的類型,  以便在
主調函數中按此種類型對返回值作相應的處理。  對被調函數的說明也有兩種格式,一種為
傳統格式,其一般格式為:  類型說明符  被調函數名();  這種格式只給出函數返回值的類
型,被調函數名及一個空括號。
 
    這種格式由于在括號中沒有任何參數信息,  因此不便于編譯系統進行錯誤檢查,易于
發生錯誤。另一種為現代格式,其一般形式為: 
類型說明符 被調函數名(類型  形參,類型  形參…); 
或為:
類型說明符 被調函數名(類型,類型…); 
    現代格式的括號內給出了形參的類型和形參名,  或只給出形參類型。這便于編譯系統
進行檢錯,以防止可能出現的錯誤。例 5.1 main 函數中對 max 函數的說明若
用傳統格式可寫為:
int max();
用現代格式可寫為:
int max(int a,int b);
或寫為:
int max(int,int);
    C語言中又規定在以下幾種情況時可以省去主調函數中對被調函數的函數說明。
1.  如果被調函數的返回值是整型或字符型時,  可以不對被調函數作說明,而直接調用。這
時系統將自動對被調函數返回值按整型處理。例 5.3 的主函數中未對函數 s 作說明而直接調
用即屬此種情形。
 
2.  當被調函數的函數定義出現在主調函數之前時,  在主調函數中也可以不對被調函數再作
說明而直接調用。例如例 5.1 中, 函數 max 的定義放在 main  函數之前,因此可在 main 函
數中省去對 max 函數的函數說明 int max(int a,int b)。
 
3.  如在所有函數定義之前,  在函數外預先說明了各個函數的類型,則在以后的各主調函數
中,可不再對被調函數作說明。例如:
總計164頁  當前為第73頁
char str(int a);
float f(float b);
main()
{
……
}
char str(int a)
{
……
}
float f(float b)
{
……
}
其中第一,二行對 str 函數和 f 函數預先作了說明。  因此在以后各函數中無須對 str 和 f 函
數再作說明就可直接調用。
 
4.  對庫函數的調用不需要再作說明,  但必須把該函數的頭文件用 include 命令包含在源文
件前部。數組作為函數參數數組可以作為函數的參數使用,進行數據傳送。  數組用作函數
參數有兩種形式,一種是把數組元素(下標變量)作為實參使用;  另一種是把數組名作為函
數的形參和實參使用。一、數組元素作函數實參數組元素就是下標變量,它與普通變量并無
區別。  因此它作為函數實參使用與普通變量是完全相同的,在發生函數調用時, 把作為實
參的數組元素的值傳送給形參,實現單向的值傳送。例 5.4說明了這種情況。[例5.4]判別一
個整數數組中各元素的值,若大于 0  則輸出該值,若小于等于 0 則輸出0 值。編程如下:
void nzp(int v)
{
if(v>0)
printf("%d ",v);
else
printf("%d ",0);
}
main()
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{
scanf("%d",&a[i]);
nzp(a[i]);
}
}void nzp(int v)
{ ……
}
main()
總計164頁  當前為第74頁
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{ scanf("%d",&a[i]);
nzp(a[i]);
}

    本程序中首先定義一個無返回值函數 nzp,并說明其形參 v  為整型變量。在函數體中根
據 v 值輸出相應的結果。在 main 函數中用一個 for  語句輸入數組各元素,  每輸入一個就以
該元素作實參調用一次 nzp 函數,即把 a[i]的值傳送給形參 v,供 nzp 函數使用。
二、數組名作為函數參數
 
    用數組名作函數參數與用數組元素作實參有幾點不同:
1.  用數組元素作實參時,只要數組類型和函數的形參變量的類型一致,那么作為下標變量
的數組元素的類型也和函數形參變量的類型是一致的。因此,  并不要求函數的形參也是下
標變量。  換句話說,對數組元素的處理是按普通變量對待的。用數組名作函數參數時,  則
要求形參和相對應的實參都必須是類型相同的數組,都必須有明確的數組說明。當形參和實
參二者不一致時,即會發生錯誤。
 
2.  在普通變量或下標變量作函數參數時,形參變量和實參變量是由編譯系統分配的兩個不
同的內存單元。在函數調用時發生的值傳送是把實參變量的值賦予形參變量。在用數組名作
函數參數時,不是進行值的傳送,即不是把實參數組的每一個元素的值都賦予形參數組的各
個元素。因為實際上形參數組并不存在,編譯系統不為形參數組分配內存。那么,數據的傳
送是如何實現的呢?  在第四章中我們曾介紹過,數組名就是數組的首地址。因此在數組名作
函數參數時所進行的傳送只是地址的傳送,   也就是說把實參數組的首地址賦予形參數組名。
形參數組名取得該首地址之后,也就等于有了實在的數組。實際上是形參數組和實參數組為
同一數組,共同擁有一段內存空間。圖 5.1 說明了這種情形。圖中設 a為實參數組,類型為
整型。a 占有以 2000  為首地址的一塊內存區。b 為形參數組名。當發生函數調用時,進行
地址傳送, 把實參數  組 a 的首地址傳送給形參數組名 b,于是 b 也取得該地址 2000。  于
是 a,b 兩數組共同占有以 2000  為首地址的一段連續內存單元。從圖中還可以看出 a 和 b
下標相同的元素實際上也占相同的兩個內
存單元(整型數組每個元素占二字節)。例如 a[0]和 b[0]都占用 2000 和 2001 單元,當然 a[0]
等于 b[0]。類推則有 a[i]等于 b[i]。
[例 5.5]數組a 中存放了一個學生 5 門課程的成績,求平均成績。
float aver(float a[5])
{
int i;
float av,s=a[0];  
for(i=1;i<5;i++) 
s=s+a[i];
av=s/5;
return av;
}
總計164頁  當前為第75頁
void main()
{
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average score is %5.2f",av);
}
float aver(float a[5])
{ ……
}
void main()
{
……
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco);
……

    本程序首先定義了一個實型函數 aver,有一個形參為實型數組 a,長度為 5。在函數 aver
中,把各元素值相加求出平均值,返回給主函數。主函數 main  中首先完成數組 sco 的輸入,
然后以 sco 作為實參調用 aver 函數,函數返回值送 av,最后輸出 av 值。  從運行情況可以
看出,程序實現了所要求的功能
 
3.  前面已經討論過,在變量作函數參數時,所進行的值傳送是單向的。即只能從實參傳向
形參,不能從形參傳回實參。形參的初值和實參相同,  而形參的值發生改變后,實參并不
變化,  兩者的終值是不同的。例 5.3證實了這個結論。  而當用數組名作函數參數時,情況
則不同。  由于實際上形參和實參為同一數組, 因此當形參數組發生變化時,實參數組也隨
之變化。  當然這種情況不能理解為發生了“雙向”的值傳遞。但從實際情況來看,調用函數
之后實參數組的值將由于形參數組值的變化而變化。為了說明這種情況,把例 5.4 改為例 5.6
的形式。[例5.6]題目同5.4 例。改用數組名作函數參數。
void nzp(int a[5])
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<5;i++)
{
if(a[i]<0) a[i]=0;
printf("%d ",a[i]);
}
}
main()
總計164頁  當前為第76頁
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
}
void nzp(int a[5])
{ ……  
}
main()
{
int b[5],i;
……
nzp(b);
……
}
    本程序中函數 nzp 的形參為整數組 a,長度為 5。  主函數中實參數組 b 也為整型,長
度也為 5。在主函數中首先輸入數組 b 的值,然后輸出數組 b 的初始值。 然后以數組名 b
為實參調用 nzp 函數。在 nzp 中,按要求把負值單元清 0,并輸出形參數組 a 的值。  返回
主函數之后,再次輸出數組 b 的值。從運行結果可以看出,數組 b  的初值和終值是不同的,
數組 b  的終值和數組 a是相同的。這說明實參形參為同一數組,它們的值同時得以改變。   用
數組名作為函數參數時還應注意以下幾點:
a.  形參數組和實參數組的類型必須一致,否則將引起錯誤。
b.  形參數組和實參數組的長度可以不相同,因為在調用時,只傳送首地址而不檢查形參數
組的長度。當形參數組的長度與實參數組不一致時,雖不至于出現語法錯誤(編譯能通過),
但程序執行結果將與實際不符,這是應予以注意的。如把例 5.6 修改如下:
void nzp(int a[8])
{
int i;
printf("\nvalues of array aare:\n");
for(i=0;i<8;i++)
{
if(a[i]<0)a[i]=0;
printf("%d",a[i]);
}
}
main()
總計164頁  當前為第77頁
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b[i]);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b[i]);
}
    本程序與例 5.6 程序比,nzp 函數的形參數組長度改為 8,函數體中,for語句的循環條
件也改為 i<8。因此,形參數組 a 和實參數組 b 的長度不一致。編譯能夠通過,但從結果看,
數組 a 的元素 a[5],a[6],a[7]顯然是無意義的。c.  在函數形參表中,允許不給出形參數組
的長度,或用一個變量來表示數組元素的個數。
例如:可以寫為:
void nzp(int a[])
或寫為
void nzp(int a[],int n)
    其中形參數組 a 沒有給出長度,而由 n 值動態地表示數組的長度。n 的值由主調函數的
實參進行傳送。
由此,例 5.6又可改為例 5.7 的形式。
[例 5.7]
void nzp(int a[],int n)
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<n;i++)
{
if(a[i]<0) a[i]=0;
printf("%d ",a[i]);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b[i]);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
總計164頁  當前為第78頁
nzp(b,5);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b[i]);
}
void nzp(int a[],int n)
{ ……
}
main()
{
……
nzp(b,5);
……
}
   本程序 nzp 函數形參數組 a 沒有給出長度,由 n  動態確定該長度。在 main 函數中,函
數調用語句為 nzp(b,5),其中實參 5 將賦予形參 n 作為形參數組的長度。
d.  多維數組也可以作為函數的參數。  在函數定義時對形參數組可以指定每一維的長度,也
可省去第一維的長度。因此,以下寫法都是合法的。 
int MA(int a[3][10])

int MA(int a[][10])
函數的嵌套調用
 
    C語言中不允許作嵌套的函數定義。因此各函數之間是平行的,不存在上一級函數和下
一級函數的問題。  但是C語言允許在一個函數的定義中出現對另一個函數的調用。  這樣就
出現了函數的嵌套調用。即在被調函數中又調用其它函數。  這與其它語言的子程序嵌套的
情形是類似的。其關系可表示如圖 5.2。
 
  圖 5.2 表示了兩層嵌套的情形。其執行過程是:執行 main 函數中調用 a 函數的語句時,
即轉去執行 a 函數,在 a函數中調用 b  函數時,又轉去執行 b函數,b 函數執行完畢返回 a
函數的斷點繼續執行,a  函數執行完畢返回 main函數的斷點繼續執行。
[例 5.8]計算 s=22!+32!    
本題可編寫兩個函數,一個是用來計算平方值的函數 f1, 另一個是用來計算階乘值的函數
f2。主函數先調 f1 計算出平方值,  再在 f1 中以平方值為實參,調用 f2 計算其階乘值,然
后返回 f1,再返回主函數,在循環程序中計算累加和。
long f1(int p)
{
int k;
long r;
long f2(int);
k=p*p;
r=f2(k);
return r;
}
總計164頁  當前為第79頁
long f2(int q)
{
long c=1;
int i;
for(i=1;i<=q;i++)
c=c*i;
return c;
}
main()
{
int i;
long s=0;
for (i=2;i<=3;i++)
s=s+f1(i);
printf("\ns=%ld\n",s);
}
long f1(int p)
{
……
long f2(int);
r=f2(k);
……
}
long f2(int q)

……
}
main()
{ ……
s=s+f1(i);
……
}
    在程序中,函數 f1 和 f2 均為長整型,都在主函數之前定義,  故不必再在主函數中對
f1 和 f2 加以說明。在主程序中,  執行循環程序依次把 i 值作為實參調用函數 f1 求 i2   值。
在 f1 中又發生對函數 f2的調用,這時是把 i2   的值作為實參去調 f2,在 f2  中完成求i2!   
的計算。f2執行完畢把 C 值(即 i2!)   返回給 f1,再由 f1  返回主函數實現累加。至此,由函
數的嵌套調用實現了題目的要求。 由于數值很大,  所以函數和一些變量的類型都說明為長
整型,否則會造成計算錯誤。
函數的遞歸調用
 
    一個函數在它的函數體內調用它自身稱為遞歸調用。  這種函數稱為遞歸函數。C語言
允許函數的遞歸調用。在遞歸調用中,  主調函數又是被調函數。執行遞歸函數將反復調用
其自身。  每調用一次就進入新的一層。例如有函數 f 如下:
int f (int x)
總計164頁  當前為第80頁
{
int y;
z=f(y);
return z;
}
    這個函數是一個遞歸函數。  但是運行該函數將無休止地調用其自身,這當然是不正確
的。為了防止遞歸調用無終止地進行,  必須在函數內有終止遞歸調用的手段。常用的辦法
是加條件判斷,  滿足某種條件后就不再作遞歸調用,然后逐層返回。 下面舉例說明遞歸調
用的執行過程。
[例 5.9]用遞歸法計算 n!用遞歸法計算 n!可用下述公式表示:
n!=1 (n=0,1)
n×(n-1)! (n>1)
按公式可編程如下:
long ff(int n)
{
long f;
if(n<0) printf("n<0,input error");
else if(n==0||n==1) f=1;
else f=ff(n-1)*n;
return(f);
}
main()
{
int n;
long y;
printf("\ninput a inteager number:\n");
scanf("%d",&n);
y=ff(n);
printf("%d!=%ld",n,y);
}
long ff(int n)
{ ……
else f=ff(n-1)*n;
……
}
main()
{ ……
y=ff(n);
……

    程序中給出的函數 ff 是一個遞歸函數。主函數調用 ff  后即進入函數 ff 執行,如果
n<0,n==0 或 n=1 時都將結束函數的執行,否則就遞歸調用 ff 函數自身。由于每次遞歸調用
的實參為 n-1,即把 n-1 的值賦予形參 n,最后當 n-1 的值為 1 時再作遞歸調用,形參 n 的
值也為 1,將使遞歸終止。然后可逐層退回。下面我們再舉例說明該過程。  設執行本程序
總計164頁  當前為第81頁
時輸入為 5,  即求 5!。在主函數中的調用語句即為 y=ff(5),進入 ff函數后,由于 n=5,不等
于 0 或 1,故應執行 f=ff(n-1)*n,即 f=ff(5-1)*5。該語句對 ff 作遞歸調用即 ff(4)。  逐次遞歸
展開如圖 5.3 所示。進行四次遞歸調用后,ff 函數形參取得的值變為 1,故不再繼續遞歸調
用而開始逐層返回主調函數。ff(1)的函數返回值為 1,ff(2)的返回值為 1*2=2,ff(3)的返回值
為 2*3=6,ff(4)  的返
回值為 6*4=24,最后返回值 ff(5)為 24*5=120。
 
  例 5. 9也可以不用遞歸的方法來完成。如可以用遞推法,即從 1 開始乘以 2,再乘以 3…
直到 n。遞推法比遞歸法更容易理解和實現。但是有些問題則只能用遞歸算法才能實現。典
型的問題是 Hanoi 塔問題。
   
   [例5.10]Hanoi 塔問題
一塊板上有三根針,A,B,C。A針上套有 64 個大小不等的圓盤,  大的在下,小的在上。
如圖 5.4 所示。要把這 64個圓盤從 A針移動 C 針上,每次只能移動一個圓盤,移動可以借
助 B 針進行。但在任何時候,任何針上的圓盤都必須保持大盤在下,小盤在上。求移動的
步驟。
本題算法分析如下,設 A上有 n 個盤子。
如果 n=1,則將圓盤從 A直接移動到 C。
如果 n=2,則:
1.將 A上的n-1(等于 1)個圓盤移到 B上;
2.再將 A上的一個圓盤移到 C 上;
3.最后將B上的 n-1(等于 1)個圓盤移到 C 上。
如果 n=3,則:
A.  將 A上的 n-1(等于 2,令其為 n`)個圓盤移到 B(借助于 C), 
步驟如下:
(1)將 A上的 n`-1(等于 1)個圓盤移到 C 上,見圖 5.5(b)。
(2)將 A上的一個圓盤移到 B,見圖 5.5(c)
(3)將 C 上的n`-1(等于 1)個圓盤移到 B,見圖 5.5(d)
B.  將 A上的一個圓盤移到 C,見圖 5.5(e)
C.  將B 上的n-1(等于 2,令其為 n`)個圓盤移到 C(借助 A),
步驟如下:
(1)將 B 上的n`-1(等于 1)個圓盤移到 A,見圖 5.5(f)
(2)將 B 上的一個盤子移到 C,見圖 5.5(g)
(3)將 A上的 n`-1(等于 1)個圓盤移到 C,見圖 5.5(h)。
到此,完成了三個圓盤的移動過程。
從上面分析可以看出,當 n 大于等于 2 時,  移動的過程可分解為
三個步驟:
第一步  把 A上的 n-1 個圓盤移到 B 上;
第二步  把 A上的一個圓盤移到 C 上;
第三步  把 B上的 n-1 個圓盤移到 C 上;其中第一步和第三步是類同的。 
當 n=3 時,第一步和第三步又分解為類同的三步,即把 n`-1 個圓盤從一個針移到另一個針
上,這里的 n`=n-1。  顯然這是一個遞歸過
程,據此算法可編程如下:
move(int n,int x,int y,int z)
總計164頁  當前為第82頁
{
if(n==1)
printf("%c-->%c\n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c\n",x,z);
move(n-1,y,x,z);
}
}
main()
{
int h;
printf("\ninput number:\n");
scanf("%d",&h);
printf("the step to moving %2d diskes:\n",h);
move(h,'a','b','c');
}
move(int n,int x,int y,int z)
{
if(n==1)
printf("%-->%c\n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c\n",x,z);
move(n-1,y,x,z);
}
}
main()
{ ……
move(h,'a','b','c');
}
    從程序中可以看出,move 函數是一個遞歸函數,它有四個形參 n,x,y,z。n 表示圓盤數,
x,y,z 分別表示三根針。move  函數的功能是把 x上的 n 個圓盤移動到 z 上。當 n==1時,直
接把 x 上的圓盤移至 z 上,輸出 x→z。如 n!=1 則分為三步:遞歸調用 move 函數,把 n-1
個圓盤從 x 移到 y;輸出 x→z;遞歸調用 move 函數,把 n-1 個圓盤從 y 移到 z。在遞歸調
用過程中 n=n-1,故 n 的值逐次遞減,最后 n=1 時,終止遞歸,逐層返回。當 n=4  時程序運
行的結果為
input number:
4
the step to moving 4 diskes:
a→b
a→c
總計164頁  當前為第83頁
b→c
a→b
c→a
c→b
a→b
a→c
b→c
b→a
c→a
b→c
a→b
a→c
b→c
 
變量的作用域
 
    在討論函數的形參變量時曾經提到,  形參變量只在被調用期間才分配內存單元,調用
結束立即釋放。  這一點表明形參變量只有在函數內才是有效的,  離開該函數就不能再使用
了。這種變量有效性的范圍稱變量的作用域。不僅對于形參變量,  C語言中所有的量都有
自己的作用域。變量說明的方式不同,其作用域也不同。  C語言中的變量,按作用域范圍
可分為兩種,  即局部變量和全局變量。
一、局部變量
 
    局部變量也稱為內部變量。局部變量是在函數內作定義說明的。其作用域僅限于函數內, 
離開該函數后再使用這種變量是非法的。
例如:
int f1(int a) /*函數 f1*/
{
int b,c;  
……
}a,b,c 作用域
int f2(int x) /*函數 f2*/
{
int y,z;  
}x,y,z 作用域
main()
{
int m,n;  
}
m,n 作用域  在函數 f1 內定義了三個變量,a 為形參,b,c 為一般變量。在 f1 的范圍內 a,b,c
有效,或者說 a,b,c 變量的作用域限于 f1 內。同理,x,y,z 的作用域限于 f2 內。 m,n 的作用
域限于 main函數內。關于局部變量的作用域還要說明以下幾點:
 
1.  主函數中定義的變量也只能在主函數中使用,不能在其它函數中使用。同時,主函數中
總計164頁  當前為第84頁
也不能使用其它函數中定義的變量。因為主函數也是一個函數,它與其它函數是平行關系。
這一點是與其它語言不同的,應予以注意。
 
2.  形參變量是屬于被調函數的局部變量,實參變量是屬于主調函數的局部變量。
 
3.  允許在不同的函數中使用相同的變量名,它們代表不同的對象,分配不同的單元,互不
干擾,也不會發生混淆。如在例 5.3  中,形參和實參的變量名都為 n,是完全允許的。4.  在
復合語句中也可定義變量,其作用域只在復合語句范圍內。例如:
main()
{
int s,a;
……
{
int b;
s=a+b; 
……b 作用域 
}
……s,a 作用域
}[例 5.11]main()
{
int i=2,j=3,k;
k=i+j;
{
int k=8;
if(i==3) printf("%d\n",k);
}
printf("%d\n%d\n",i,k);
}
main()
{
int i=2,j=3,k;
k=i+j;
{
int k=8;
if(i=3) printf("%d\n",k);
}
printf("%d\n%d\n",i,k);

    本程序在 main 中定義了 i,j,k 三個變量,其中 k 未賦初值。  而在復合語句內又定義了
一個變量 k,并賦初值為 8。應該注意這兩個 k 不是同一個變量。在復合語句外由 main 定義
的 k 起作用,而在復合語句內則由在復合語句內定義的 k 起作用。因此程序第 4 行的 k 為
main 所定義,其值應為 5。第 7 行輸出 k 值,該行在復合語句內,由復合語句內定義的 k
起作用,其初值為 8,故輸出值為 8,第 9 行輸出 i,k 值。i 是在整個程序中有效的,第 7
行對 i 賦值為 3,故以輸出也為 3。而第 9 行已在復合語句之外,輸出的 k 應為 main 所定義
總計164頁  當前為第85頁
的 k,此k 值由第 4  行已獲得為 5,故輸出也為 5。
 
二、全局變量
 
全局變量也稱為外部變量,它是在函數外部定義的變量。  它不屬于哪一個函數,它屬于一
個源程序文件。其作用域是整個源程序。在函數中使用全局變量,一般應作全局變量說明。 
只有在函數內經過說明的全局變量才能使用。全局變量的說明符為 extern。  但在一個函數
之前定義的全局變量,在該函數內使用可不再加以說明。 例如:
int a,b; /*外部變量*/
void f1() /*函數 f1*/
{
……
}
float x,y; /*外部變量*/ 
int fz() /*函數 fz*/
{
……
}
main() /*主函數*/
{
……
}/*全局變量x,y作用域 全局變量 a,b作用域*/
    從上例可以看出 a、b、x、y  都是在函數外部定義的外部變量,都是全局變量。但 x,y  定
義在函數 f1之后,而在 f1 內又無對x,y的說明,所以它們在 f1內無效。 a,b 定義在源程序
最前面,因此在 f1,f2 及main 內不加說明也可使用。
 
[例 5.12]輸入正方體的長寬高 l,w,h。求體積及三個面 x*y,x*z,y*z 的面積。
int s1,s2,s3;
int vs( int a,int b,int c)
{
int v;
v=a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;
return v;
}
main()
{
int v,l,w,h;
printf("\ninput length,width and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3);
總計164頁  當前為第86頁
}
    本程序中定義了三個外部變量 s1,s2,s3,  用來存放三個面積,其作用域為整個程序。函
數 vs 用來求正方體體積和三個面積,  函數的返回值為體積 v。由主函數完成長寬高的輸入
及結果輸出。由于C語言規定函數返回值只有一個,  當需要增加函數的返回數據時,用外
部變量是一種很好的方式。本例中,如不使用外部變量,   在主函數中就不可能取得 v,s1,s2,s3
四個值。而采用了外部變量,  在函數 vs 中求得的 s1,s2,s3 值在 main  中仍然有效。因此外
部變量是實現函數之間數據通訊的有效手段。對于全局變量還有以下幾點說明:
 
1.  對于局部變量的定義和說明,可以不加區分。而對于外部變量則不然,外部變量的定義
和外部變量的說明并不是一回事。外部變量定義必須在所有的函數之外,且只能定義一次。
其一般形式為: [extern]  類型說明符  變量名,變量名…  其中方括號內的 extern 可以省去
不寫。
例如: int a,b;
等效于:
extern int a,b;
    而外部變量說明出現在要使用該外部變量的各個函數內,  在整個程序內,可能出現多
次,外部變量說明的一般形式為: extern  類型說明符  變量名,變量名,…;  外部變量在
定義時就已分配了內存單元,  外部變量定義可作初始賦值,外部變量說明不能再賦初始值, 
只是表明在函數內要使用某外部變量。
 
2.  外部變量可加強函數模塊之間的數據聯系, 但是又使函數要依賴這些變量,因而使得函
數的獨立性降低。從模塊化程序設計的觀點來看這是不利的,  因此在不必要時盡量不要使
用全局變量。
 
3.  在同一源文件中,允許全局變量和局部變量同名。在局部變量的作用域內,全局變量不
起作用。
[例 5.13]int vs(int l,int w)
{
extern int h;
int v;
v=l*w*h;
return v;
}
main()
{
extern int w,h;
int l=5;
printf("v=%d",vs(l,w));
}
int l=3,w=4,h=5;
    本例程序中,外部變量在最后定義,  因此在前面函數中對要用的外部變量必須進行說
明。外部變量 l,w和 vs 函數的形參 l,w同名。外部變量都作了初始賦值,mian 函數中也
對 l 作了初始化賦值。執行程序時,在 printf 語句中調用 vs 函數,實參 l 的值應為 main 中
定義的 l 值,等于 5,外部變量 l 在 main 內不起作用;實參 w的值為外部變量 w的值為 4,
總計164頁  當前為第87頁
進入 vs 后這兩個值傳送給形參 l,wvs 函數中使用的 h  為外部變量,其值為 5,因此v的計
算結果為 100,返回主函數后輸出。變量的存儲類型各種變量的作用域不同,  就其本質來
說是因變量的存儲類型相同。所謂存儲類型是指變量占用內存空間的方式,  也稱為存儲方
式。
變量的存儲方式可分為“靜態存儲”和“動態存儲”兩種。 
 
    靜態存儲變量通常是在變量定義時就分定存儲單元并一直保持不變,  直至整個程序結
束。5.5.1 節中介紹的全局變量即屬于此類存儲方式。動態存儲變量是在程序執行過程中,
使用它時才分配存儲單元,  使用完畢立即釋放。  典型的例子是函數的形式參數,在函數定
義時并不給形參分配存儲單元,只是在函數被調用時,才予以分配,  調用函數完畢立即釋
放。如果一個函數被多次調用,則反復地分配、  釋放形參變量的存儲單元。從以上分析可
知,  靜態存儲變量是一直存在的, 而動態存儲變量則時而存在時而消失。我們又把這種由
于變量存儲方式不同而產生的特性稱變量的生存期。  生存期表示了變量存在的時間。  生存
期和作用域是從時間和空間這兩個不同的角度來描述變量的特性,這兩者既有聯系,又有區
別。  一個變量究竟屬于哪一種存儲方式,  并不能僅從其作用域來判斷,還應有明確的存儲
類型說明。
 
    在C語言中,對變量的存儲類型說明有以下四種:
auto       自動變量
register      寄存器變量
extern        外部變量
static        靜態變量 
    自動變量和寄存器變量屬于動態存儲方式,  外部變量和靜態變量屬于靜態存儲方式。
在介紹了變量的存儲類型之后,  可以知道對一個變量的說明不僅應說明其數據類型,還應
說明其存儲類型。  因此變量說明的完整形式應為:  存儲類型說明符  數據類型說明符  變量
名,變量名…;  例如:
static int a,b;              說明 a,b 為靜態類型變量
auto char c1,c2;           說明 c1,c2 為自動字符變量
static int a[5]={1,2,3,4,5};     說明 a 為靜整型數組
extern int x,y;              說明 x,y為外部整型變量
下面分別介紹以上四種存儲類型:
 
一、自動變量的類型說明符為 auto。
    這種存儲類型是C語言程序中使用最廣泛的一種類型。C語言規定,  函數內凡未加存
儲類型說明的變量均視為自動變量,  也就是說自動變量可省去說明符 auto。  在前面各章的
程序中所定義的變量凡未加存儲類型說明符的都是自動變量。例如:
{ int i,j,k;
char c;
……
}等價于:  { auto int i,j,k;
auto char c;
……
}
    自動變量具有以下特點:
總計164頁  當前為第88頁
1.  自動變量的作用域僅限于定義該變量的個體內。在函數中定義的自動變量,只在該函數
內有效。在復合語句中定義的自動變量只在該復合語句中有效。  例如: 
int kv(int a)
{
auto int x,y;
{ auto char c;  
} /*c 的作用域*/
……
} /*a,x,y的作用域*/
 
2.  自動變量屬于動態存儲方式,只有在使用它,即定義該變量的函數被調用時才給它分配
存儲單元,開始它的生存期。函數調用結束,釋放存儲單元,結束生存期。因此函數調用結
束之后,自動變量的值不能保留。在復合語句中定義的自動變量,在退出復合語句后也不能
再使用,否則將引起錯誤。例如以下程序: 
main()
{ auto int a,s,p;
printf("\ninput a number:\n");
scanf("%d",&a);
if(a>0){
s=a+a;
p=a*a;
}
printf("s=%d p=%d\n",s,p);
}
 
s,p 是在復合語句內定義的自動變量,只能在該復合語句內有效。而程序的第 9 行卻是退出
復合語句之后用 printf語句輸出 s,p 的值,這顯然會引起錯誤。
 
3.  由于自動變量的作用域和生存期都局限于定義它的個體內(  函數或復合語句內),   因此不
同的個體中允許使用同名的變量而不會混淆。  即使在函數內定義的自動變量也可與該函數
內部的復合語句中定義的自動變量同名。例 5.14 表明了這種情況。
[例 5.14]
main()
{
auto int a,s=100,p=100;
printf("\ninput a number:\n");
scanf("%d",&a);
if(a>0)
{
auto int s,p;
s=a+a;
p=a*a;
printf("s=%d p=%d\n",s,p);
}
總計164頁  當前為第89頁
printf("s=%d p=%d\n",s,p);
}
    本程序在 main 函數中和復合語句內兩次定義了變量 s,p 為自動變量。按照C語言的規
定,在復合語句內,應由復合語句中定義的 s,p起作用,故 s 的值應為 a+ a,p 的值為 a*a。
退出復合語句后的 s,p 應為 main 所定義的 s,p,其值在初始化時給定,均為 100。從輸出結
果可以分析出兩個 s 和兩個 p 雖變量名相同, 但卻是兩個不同的變量。
 
4.  對構造類型的自動變量如數組等,不可作初始化賦值。
 
二、外部變量外部變量的類型說明符為 extern。
 
在前面介紹全局變量時已介紹過外部變量。這里再補充說明外部變量的幾個特點:
1.  外部變量和全局變量是對同一類變量的兩種不同角度的提法。全局變是是從它的作用域
提出的,外部變量從它的存儲方式提出的,表示了它的生存期。
 
2.  當一個源程序由若干個源文件組成時,  在一個源文件中定義的外部變量在其它的源文件
中也有效。例如有一個源程序由源文件 F1.C 和F2.C 組成:  F1.C
int a,b; /*外部變量定義*/
char c; /*外部變量定義*/
main()

……
}
F2.C
extern int a,b; /*外部變量說明*/
extern char c; /*外部變量說明*/
func (int x,y)
{
……
}
在 F1.C 和 F2.C 兩個文件中都要使用 a,b,c 三個變量。在 F1.C 文件中把 a,b,c 都定義為外部
變量。在 F2.C 文件中用 extern 把三個變量說明為外部變量,表示這些變量已在其它文件中
定義,并把這些變量的類型和變量名,編譯系統不再為它們分配內存空間。  對構造類型的
外部變量,  如數組等可以在說明時作初始化賦值,若不賦初值,則系統自動定義它們的初
值為 0。
三、靜態變量
 
    靜態變量的類型說明符是 static。  靜態變量當然是屬于靜態存儲方式,但是屬于靜態存
儲方式的量不一定就是靜態變量,  例如外部變量雖屬于靜態存儲方式,但不一定是靜態變
量,必須由 static 加以定義后才能成為靜態外部變量,或稱靜態全局變量。  對于自動變量,
前面已經介紹它屬于動態存儲方式。  但是也可以用 static 定義它為靜態自動變量,或稱靜
態局部變量,從而成為靜態存儲方式。
由此看來, 一個變量可由 static 進行再說明,并改變其原有的存儲方式。
 
總計164頁  當前為第90頁
1.  靜態局部變量
    在局部變量的說明前再加上 static 說明符就構成靜態局部變量。
例如:
static int a,b;
static float array[5]={1,2,3,4,5};
   
    靜態局部變量屬于靜態存儲方式,它具有以下特點:
(1)靜態局部變量在函數內定義,但不象自動變量那樣,當調用時就存在,退出函數時就消
失。靜態局部變量始終存在著,也就是說它的生存期為整個源程序。
 
(2)靜態局部變量的生存期雖然為整個源程序,但是其作用域仍與自動變量相同,即只能在
定義該變量的函數內使用該變量。退出該函數后,  盡管該變量還繼續存在,但不能使用它。 
 
(3)允許對構造類靜態局部量賦初值。在數組一章中,介紹數組初始化時已作過說明。若未
賦以初值,則由系統自動賦以 0 值。
 
(4)對基本類型的靜態局部變量若在說明時未賦以初值,則系統自動賦予 0 值。而對自動變
量不賦初值,則其值是不定的。  根據靜態局部變量的特點,  可以看出它是一種生存期為整
個源程序的量。雖然離開定義它的函數后不能使用,但如再次調用定義它的函數時,它又可
繼續使用, 而且保存了前次被調用后留下的值。  因此,當多次調用一個函數且要求在調用
之間保留某些變量的值時,可考慮采用靜態局部變量。雖然用全局變量也可以達到上述目的,
但全局變量有時會造成意外的副作用,因此仍以采用局部靜態變量為宜。
[例 5.15]main()
{
int i;
void f(); /*函數說明*/
for(i=1;i<=5;i++)
f(); /*函數調用*/
}
void f() /*函數定義*/
{
auto int j=0;
++j;
printf("%d\n",j);
}
    程序中定義了函數 f,其中的變量 j  說明為自動變量并賦予初始值為 0。當 main 中多次
調用 f時,j均賦初值為 0,故每次輸出值均為 1。現在把 j 改為靜態局部變量,程序如下:
main()
{
int i;
void f();
for (i=1;i<=5;i++)
f();
}
總計164頁  當前為第91頁
void f()
{
static int j=0;
++j;
printf("%d\n",j);
}
void f()
{
static int j=0;
++j;
printf("%d/n",j);
}
由于 j 為靜態變量,能在每次調用后保留其值并在下一次調用時繼續使用,所以輸出值成為
累加的結果。讀者可自行分析其執行過程。
 
2.靜態全局變量
    全局變量(外部變量)的說明之前再冠以 static  就構成了靜態的全局變量。全局變量本身
就是靜態存儲方式,   靜態全局變量當然也是靜態存儲方式。   這兩者在存儲方式上并無不同。
這兩者的區別雖在于非靜態全局變量的作用域是整個源程序,  當一個源程序由多個源文件
組成時,非靜態的全局變量在各個源文件中都是有效的。  而靜態全局變量則限制了其作用
域,  即只在定義該變量的源文件內有效,  在同一源程序的其它源文件中不能使用它。由于
靜態全局變量的作用域局限于一個源文件內,只能為該源文件內的函數公用,  因此可以避
免在其它源文件中引起錯誤。從以上分析可以看出,  把局部變量改變為靜態變量后是改變
了它的存儲方式即改變了它的生存期。把全局變量改變為靜態變量后是改變了它的作用域, 
限制了它
的使用范圍。因此 static  這個說明符在不同的地方所起的作用是不同的。應予以注意。
 
四、寄存器變量
 
    上述各類變量都存放在存儲器內,  因此當對一個變量頻繁讀寫時,必須要反復訪問內
存儲器,從而花費大量的存取時間。  為此,C語言提供了另一種變量,即寄存器變量。這
種變量存放在 CPU的寄存器中,使用時,不需要訪問內存,而直接從寄存器中讀寫,  這樣
可提高效率。寄存器變量的說明符是 register。 對于循環次數較多的循環控制變量及循環體
內反復使用的變量均可定義為寄存器變量。
[例 5.16]求∑200i=1imain()
{
register i,s=0;
for(i=1;i<=200;i++)
s=s+i;
printf("s=%d\n",s);
}
本程序循環 200 次,i 和 s都將頻繁使用,因此可定義為寄存器變量。
對寄存器變量還要說明以下幾點:
 
總計164頁  當前為第92頁
1.  只有局部自動變量和形式參數才可以定義為寄存器變量。因為寄存器變量屬于動態存儲
方式。凡需要采用靜態存儲方式的量不能定義為寄存器變量。
 
2. 在 Turbo C,MS C 等微機上使用的C語言中,  實際上是把寄存器變量當成自動變量處理
的。因此速度并不能提高。  而在程序中允許使用寄存器變量只是為了與標準 C 保持一致。
3.  即使能真正使用寄存器變量的機器,由于 CPU  中寄存器的個數是有限的,因此使用寄存
器變量的個數也是有限的。
內部函數和外部函數
 
    函數一旦定義后就可被其它函數調用。  但當一個源程序由多個源文件組成時,  在一個
源文件中定義的函數能否被其它源文件中的函數調用呢?為此,C語言又把函數分為兩類:
 
一、內部函數
 
    如果在一個源文件中定義的函數只能被本文件中的函數調用,而不能被同一源程序其它
文件中的函數調用,  這種函數稱為內部函 
數。定義內部函數的一般形式是: static  類型說明符  函數名(形參表)  例如:
static int f(int a,int b) 內部函數也稱為靜態函數。但此處靜態 static  的含義已不是指存儲方
式,而是指對函數的調用范圍只局限于本文件。  因此在不同的源文件中定義同名的靜態函
數不會引起混淆。
 
二、外部函數
    外部函數在整個源程序中都有效,其定義的一般形式為:  extern  類型說明符  函數名(形
參表)  例如:
extern int f(int a,int b)如在函數定義中沒有說明 extern 或 static則隱含為 extern。在一個源文
件的函數中調用其它源文件中定義的外部函數時,應  用 extern 說明被調函數為外部函數。
例如:
F1.C (源文件一)
main()
{
extern int f1(int i); /*外部函數說明,表示 f1 函
數在其它源文件中*/
……
}
F2.C (源文件二)
extern int f1(int i); /*外部函數定義*/
{
……
}
 
本章小結
 
1.  函數的分類
(1)庫函數:由 C 系統提供的函數;
總計164頁  當前為第93頁
(2)用戶定義函數:由用戶自己定義的函數;
(3)有返回值的函數向調用者返回函數值,應說明函數類型(  即返回值的類型 );
(4)無返回值的函數:不返回函數值,說明為空(void)類型;
(5)有參函數:主調函數向被調函數傳送數據;
(6)無參函數:主調函數與被調函數間無數據傳送;
(7)內部函數:只能在本源文件中使用的函數;
(8)外部函數:可在整個源程序中使用的函數。
 
2.  函數定義的一般形式 
[extern/static] 類型說明符  函數名([形參表])  方括號內為可選項。
 
3.  函數說明的一般形式 [extern]  類型說明符  函數名([形參表]); 
 
4.  函數調用的一般形式 函數名([實參表]) 
 
5.  函數的參數分為形參和實參兩種,形參出現在函數定義中,實參出現在函數調用中,發
生函數調用時,將把實參的值傳送給形參。
 
6.  函數的值是指函數的返回值,它是在函數中由 return 語句返回的。
 
7.  數組名作為函數參數時不進行值傳送而進行地址傳送。形參和實參實際上為同一數組的
兩個名稱。因此形參數組的值發生變化,實參數組的值當然也變化。
 
8.  C語言中,允許函數的嵌套調用和函數的遞歸調用。
 
9.  可從三個方面對變量分類,即變量的數據類型,變量作用域和變量的存儲類型。在第二
章中主要介紹變量的數據類型,本章中介紹了變量的作用域和變量的存儲類型。
 
10.變量的作用域是指變量在程序中的有效范圍,  分為局部變量和全局變量。
 
11.變量的存儲類型是指變量在內存中的存儲方式,分為靜態存儲和動態存儲,表示了變量
的生存期。
 
12.變量分類特性表存儲方式存儲類型說明符何處定義生存期作用域賦值前的值可賦初值類
型動態存儲自動變量 auto  寄存器變量  register  函數或復合語句內被調用時在定義它的函
數或復合語句內不定基本類型int或char外部變量extern函數之外整個源程序整個源程序靜
態局部變量static  函數或復合語句內靜態全局變量static  函數之外整個源程序在定義它的函
數或復合語句內在定義它的源文件內 0 任何類型
總計164頁  當前為第94頁
C語言教程第六章:指針
指針簡介
 
    指針是C語言中廣泛使用的一種數據類型。  運用指針編程是C語言最主要的風格之一。
利用指針變量可以表示各種數據結構; 能很方便地使用數組和字符串; 并能象匯編語言一
樣處理內存地址,從而編出精練而高效的程序。指針極大地豐富了C語言的功能。 學習指
針是學習C語言中最重要的一環, 能否正確理解和使用指針是我們是否掌握C語言的一個
標志。同時, 指針也是C語言中最為困難的一部分,在學習中除了要正確理解基本概念,
還必須要多編程,上機調試。只要作到這些,指針也是不難掌握的。
 
    指針的基本概念 在計算機中,所有的數據都是存放在存儲器中的。 一般把存儲器中的
一個字節稱為一個內存單元, 不同的數據類型所占用的內存單元數不等,如整型量占 2 個
單元,字符量占 1 個單元等, 在第二章中已有詳細的介紹。為了正確地訪問這些內存單元,
 必須為每個內存單元編上號。 根據一個內存單元的編號即可準確地找到該內存單元。內存
單元的編號也叫做地址。 既然根據內存單元的編號或地址就可以找到所需的內存單元,所
以通常也把這個地址稱為指針。 內存單元的指針和內存單元的內容是兩個不同的概念。
可以用一個通俗的例子來說明它們之間的關系。我們到銀行去存取款時, 銀行工作人員將
根據我們的帳號去找我們的存款單, 找到之后在存單上寫入存款、取款的金額。在這里,
帳號就是存單的指針, 存款數是存單的內容。對于一個內存單元來說,單元的地址即為指
針, 其中存放的數據才是該單元的內容。在C語言中, 允許用一個變量來存放指針,這種
變量稱為指針變量。因此, 一個指針變量的值就是某個內存單元的地址或稱為某內存單元
的指針。圖中,設有字符變量 C,其內容為“K”(ASCII 碼為十進制數 75),C 占用了011A
號單元(地址用十六進數表示)。設有指針變量 P,內容為 011A, 這種情況我們稱為 P指向
變量 C,或說 P 是指向變量 C 的指針。 嚴格地說,一個指針是一個地址, 是一個常量。而
一個指針變量卻可以被賦予不同的指針值,是變。 但在常把指針變量簡稱為指針。為了避
免混淆,我們中約定:“指針”是指地址, 是常量,“指針變量”是指取值為地址的變量。
 定義指針的目的是為了通過指針去訪問內存單元。
 
    既然指針變量的值是一個地址, 那么這個地址不僅可以是變量的地址, 也可以是其它
數據結構的地址。在一個指針變量中存放一
個數組或一個函數的首地址有何意義呢? 因為數組或函數都是連續存放的。通過訪問指針
變量取得了數組或函數的首地址, 也就找到了該數組或函數。這樣一來, 凡是出現數組,
函數的地方都可以用一個指針變量來表示, 只要該指針變量中賦予數組或函數的首地址即
可。這樣做, 將會使程序的概念十分清楚,程序本身也精練,高效。在C語言中, 一種數
據類型或數據結構往往都占有一組連續的內存單元。 用“地址”這個概念并不能很好地描
述一種數據類型或數據結構, 而“指針”雖然實際上也是一個地址,但它卻是一個數據結
構的首地址, 它是“指向”一個數據結構的,因而概念更為清楚,表示更為明確。 這也是
引入“指針”概念的一個重要原因。
 
指針變量的類型說明
總計164頁  當前為第95頁
 
    對指針變量的類型說明包括三個內容:
(1)指針類型說明,即定義變量為一個指針變量; 
(2)指針變量名;
(3)變量值(指針)所指向的變量的數據類型。
    其一般形式為: 類型說明符 *變量名; 
    其中,*表示這是一個指針變量,變量名即為定義的指針變量名,類型說明符表示本指
針變量所指向的變量的數據類型。
    例如: int *p1;表示 p1 是一個指針變量,它的值是某個整型變量的地址。 或者說 p1
指向一個整型變量。至于 p1 究竟指向哪一個整型變量, 應由向 p1 賦予的地址來決定。
    再如:
staic int *p2; /*p2 是指向靜態整型變量的指針變量*/
float *p3; /*p3 是指向浮點變量的指針變量*/
char *p4; /*p4 是指向字符變量的指針變量*/ 應該注意的是,一個指針變量只能指向同類
型的變量,如 P3 只能指向浮點變量,不能時而指向一個浮點變量, 時而又指向一個字符
變量。
 
指針變量的賦值
 
    指針變量同普通變量一樣,使用之前不僅要定義說明, 而且必須賦予具體的值。未經
賦值的指針變量不能使用, 否則將造成系統混亂,甚至死機。指針變量的賦值只能賦予地
址, 決不能賦予任何其它數據,否則將引起錯誤。在C語言中, 變量的地址是由編譯系統
分配的,對用戶完全透明,用戶不知道變量的具體地址。 C語言中提供了地址運算符&來表
示變量的地址。其一般形式為: & 變量名; 如&a 變示變量a 的地址,&b 表示變量b的地
址。 變量本身必須預先說明。設有指向整型變量的指針變量 p,如要把整型變量 a 的地址
賦予 p 可以有以下兩種方式:
(1)指針變量初始化的方法 int a;
int *p=&a;
(2)賦值語句的方法 int a;
int *p;
p=&a;
不允許把一個數賦予指針變量,故下面的賦值是錯誤的: int *p;p=1000; 被賦值的指針變
量前不能再加“*”說明符,如寫為*p=&a 也是錯誤的
 
指針變量的運算
 
    指針變量可以進行某些運算,但其運算的種類是有限的。 它只能進行賦值運算和部分
算術運算及關系運算。
1.指針運算符
 
(1)取地址運算符&
    取地址運算符&是單目運算符,其結合性為自右至左,其功能是取變量的地址。在 scan
f 函數及前面介紹指針變量賦值中,我們已經了解并使用了&運算符。
總計164頁  當前為第96頁
 
(2)取內容運算符*
    取內容運算符*是單目運算符,其結合性為自右至左,用來表示指針變量所指的變量。
在*運算符之后跟的變量必須是指針變量。 需要注意的是指針運算符*和指針變量說明中的指
針說明符* 不是一回事。在指針變量說明中,“*”是類型說明符,表示其后的變量是指針
類型。而表達式中出現的“*”則是一個運算符用以表示指針變量所指的變量。
main(){
int a=5,*p=&a;
printf ("%d",*p);
}
......
表示指針變量 p 取得了整型變量 a的地址。本語句表示輸出變量 a 的值。
2.指針變量的運算
 
(1)賦值運算
 
指針變量的賦值運算有以下幾種形式:
①指針變量初始化賦值,前面已作介紹。
 
②把一個變量的地址賦予指向相同數據類型的指針變量。例如:
int a,*pa;
pa=&a; /*把整型變量 a的地址賦予整型指針變量 pa*/
 
③把一個指針變量的值賦予指向相同類型變量的另一個指針變量。如:
int a,*pa=&a,*pb;
pb=pa; /*把a 的地址賦予指針變量 pb*/
由于 pa,pb均為指向整型變量的指針變量,因此可以相互賦值。
 
④把數組的首地址賦予指向數組的指針變量。
例如: int a[5],*pa;
pa=a; (數組名表示數組的首地址,故可賦予指向數組的指針變量 pa)
也可寫為:
pa=&a[0]; /*數組第一個元素的地址也是整個數組的首地址,
也可賦予 pa*/
當然也可采取初始化賦值的方法:
int a[5],*pa=a;
 
⑤把字符串的首地址賦予指向字符類型的指針變量。例如: char *pc;pc="c language";
或用初始化賦值的方法寫為:  char *pc="C Language"; 這里應說明的是并不是把整個字符
串裝入指針變量, 而是把存放該字符串的字符數組的首地址裝入指針變量。 在后面還將詳
細介紹。
 
總計164頁  當前為第97頁
⑥把函數的入口地址賦予指向函數的指針變量。例如: int (*pf)();pf=f; /*f 為函數名*
/
 
(2)加減算術運算
 
    對于指向數組的指針變量,可以加上或減去一個整數 n。設 pa 是指向數組 a 的指針變
量,則 pa+n,pa-n,pa++,++pa,pa--,--pa 運算都是合法的。指針變量加或減一個整數 n 的
意義是把指針指向的當前位置(指向某數組元素)向前或向后移動 n 個位置。應該注意,數組
指針變量向前或向后移動一個位置和地址加 1或減 1 在概念上是不同的。因為數組可以有
不同的類型, 各種類型的數組元素所占的字節長度是不同的。如指針變量加 1,即向后移
動 1 個位置表示指針變量指向下一個數據元素的首地址。而不是在原地址基礎上加 1。
例如:
int a[5],*pa;
pa=a; /*pa指向數組 a,也是指向 a[0]*/
pa=pa+2; /*pa 指向a[2], 即 pa 的值為&pa[2]*/ 指針變量的加減運算只能對數組指針變量
進行, 對指向其它類型變量的指針變量作加減運算是毫無意義的。(3)兩個指針變量之間的
運算只有指向同一數組的兩個指針變量之間才能進行運算, 否則運算毫無意義。
 
①兩指針變量相減
兩指針變量相減所得之差是兩個指針所指數組元素之間相差的元素個數。實際上是兩個指針
值(地址) 相減之差再除以該數組元素的長度(字節數)。例如 pf1 和pf2 是指向同一浮點數
組的兩個指針變量,設 pf1 的值為 2010H,pf2 的值為 2000H,而浮點數組每個元素占 4 個
字節,所以 pf1-pf2 的結果為(2000H-2010H)/4=4,表示 pf1 和 pf2 之間相差 4 個元素。兩
個指針變量不能進行加法運算。 例如, pf1+pf2 是什么意思呢?毫無實際意義。
 
②兩指針變量進行關系運算
指向同一數組的兩指針變量進行關系運算可表示它們所指數組元素之間的關系。例如:
pf1==pf2 表示 pf1 和pf2 指向同一數組元素
pf1>pf2 表示 pf1 處于高地址位置
pf1<pf2 表示 pf2 處于低地址位置
main(){
int a=10,b=20,s,t,*pa,*pb; 
pa=&a;
pb=&b;
s=*pa+*pb;
t=*pa**pb;
printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b);
printf("s=%d\nt=%d\n",s,t);
}
......
說明 pa,pb為整型指針變量
給指針變量 pa 賦值,pa指向變量 a。
給指針變量 pb 賦值,pb指向變量 b。
總計164頁  當前為第98頁
本行的意義是求 a+b 之和,(*pa 就是 a,*pb 就是 b)。
本行是求 a*b 之積。
輸出結果。
輸出結果。
...... 
指針變量還可以與 0 比較。設 p 為指針變量,則 p==0 表明p 是空指針,它不指向任何變量;
p!=0 表示 p不是空指針。空指針是由對指針變量賦予 0 值而得到的。例如: #define NULL
 0  int *p=NULL; 對指針變量賦 0值和不賦值是不同的。指針變量未賦值時,可以是任意
值,是不能使用的。否則將造成意外錯誤。而指針變量賦 0 值后,則可以使用,只是它不指
向具體的變量而已。
main(){
int a,b,c,*pmax,*pmin;
printf("input three numbers:\n");
scanf("%d%d%d",&a,&b,&c);
if(a>b){
pmax=&a;
pmin=&b;}
else{
pmax=&b;
pmin=&a;}
if(c>*pmax) pmax=&c;
if(c<*pmin) pmin=&c;
printf("max=%d\nmin=%d\n",*pmax,*pmin);
}
...... 
pmax,pmin 為整型指針變量。
輸入提示。
輸入三個數字。
如果第一個數字大于第二個數字...
指針變量賦值
指針變量賦值
指針變量賦值
指針變量賦值
判斷并賦值
判斷并賦值 
輸出結果
......
數組指針變量的說明和使用
 
    指向數組的指針變量稱為數組指針變量。  在討論數組指針變量的說明和使用之前,我
們先明確幾個關系。
一個數組是由連續的一塊內存單元組成的。  數組名就是這塊連續內存單元的首地址。一個
總計164頁  當前為第99頁
數組也是由各個數組元素(下標變量)  組成的。每個數組元素按其類型不同占有幾個連續的
內存單元。 一個數組元素的首地址也是指它所占有的幾個內存單元的首地址。  一個指針變
量既可以指向一個數組,也可以指向一個數組元素,  可把數組名或第一個元素的地址賦予
它。如要使指針變量指向第 i 號元素可以把 i 元素的首地址賦予它或把數組名加 i 賦予它。
 
    設有實數組 a,指向 a 的指針變量為 pa,從圖 6.3 中我們可以看出有以下關系:
pa,a,&a[0]均指向同一單元,它們是數組 a 的首地址,也是 0  號元素 a[0]的首地址。
pa+1,a+1,&a[1]均指向 1 號元素 a[1]。類推可知 a+i,a+i,&a[i]
指向 i 號元素 a[i]。應該說明的是 pa是變量,而 a,&a[i]都是常量。在編程時應予以注意。
main(){
int a[5],i;
for(i=0;i<5;i++){
a[i]=i;
printf("a[%d]=%d\n",i,a[i]);
}
printf("\n");
}
主函數
定義一個整型數組和一個整型變量
循環語句 
給數組賦值
打印每一個數組的值 
......
輸出換行
......
數組指針變量說明的一般形式為:
類型說明符 *  指針變量名 
    其中類型說明符表示所指數組的類型。  從一般形式可以看出指向數組的指針變量和指
向普通變量的指針變量的說明是相同的。
引入指針變量后,就可以用兩種方法來訪問數組元素了。
    第一種方法為下標法,即用 a[i]形式訪問數組元素。  在第四章中介紹數組時都是采用
這種方法。
    第二種方法為指針法,即采用*(pa+i)形式,用間接訪問的方法來訪問數組元素。
main(){
int a[5],i,*pa;
pa=a;
for(i=0;i<5;i++){
*pa=i;
pa++;
}
pa=a;
for(i=0;i<5;i++){
printf("a[%d]=%d\n",i,*pa);
pa++;
總計164頁  當前為第100 頁
}
}
主函數
定義整型數組和指針
將指針 pa 指向數組 a
循環
將變量 i 的值賦給由指針 pa 指向的 a[]的數組單元
將指針 pa 指向 a[]的下一個單元
......
指針 pa 重新取得數組 a的首地址
循環
用數組方式輸出數組 a中的所有元素
將指針 pa 指向 a[]的下一個單元
......
...... 
下面,另舉一例,該例與上例本意相同,但是實現方式不同。
main(){
int a[5],i,*pa=a;
for(i=0;i<5;){
*pa=i;
printf("a[%d]=%d\n",i++,*pa++);
}
}
主函數
定義整型數組和指針,并使指針指向數組 a
循環
將變量 i 的值賦給由指針 pa 指向的 a[]的數組單元
用指針輸出數組 a 中的所有元素,同時指針 pa指向 a[]的下一個單元
......
......
 
數組名和數組指針變量作函數參數
 
    在第五章中曾經介紹過用數組名作函數的實參和形參的問題。在學習指針變量之后就更
容易理解這個問題了。  數組名就是數組的首地址,實參向形參傳送數組名實際上就是傳送
數組的地址,  形參得到該地址后也指向同一數組。  這就好象同一件物品有兩個彼此不同的
名稱一樣。同樣,指針變量的值也是地址,  數組指針變量的值即為數組的首地址,當然也
可作為函數的參數使用。
float aver(float *pa);
main(){
float sco[5],av,*sp;
int i;
sp=sco;
printf("\ninput 5 scores:\n");
總計164頁  當前為第101 頁
for(i=0;i<5;i++) scanf("%f",&sco[i]);
av=aver(sp);
printf("average score is %5.2f",av);
}
float aver(float *pa)
{
int i;
float av,s=0;
for(i=0;i<5;i++) s=s+*pa++;
av=s/5;
return av;
}
 
指向多維數組的指針變量
 
本小節以二維數組為例介紹多維數組的指針變量。
 
一、多維數組地址的表示方法
設有整型二維數組 a[3][4]如下: 
0 1 2 3
4 5 6 7
8 9 10 11  
    設數組 a 的首地址為 1000,各下標變量的首地址及其值如圖所示。在第四章中介紹過, 
C語言允許把一個二維數組分解為多個一維數組來處理。因此數組 a 可分解為三個一維數
組,即 a[0], a[1], a[2]。每一個一維數組又含有四個元素。例如 a[0]數組,含有 a[0][0], a[0][1],
a[0][2],a[0][3]四個元素。  數組及數組元素的地址表示如下:a是二維數組名,也是二維數
組 0 行的首地址,等于 1000。a[0]是第一個一維數組的數組名和首地址,因此也為 1000。
*(a+0)或*a是與 a[0]等效的,   它表示一維數組 a[0]0  號元素的首地址。   也為 1000。 &a[0][0]
是二維數組 a 的 0 行 0 列元素首地址,同樣是 1000。因此,a,a[0],*(a+0),*a,&a[0][0]
是相等的。同理,a+1 是二維數組 1行的首地址,等于 1008。a[1]是第二個一維數組的數組
名和首地址,因此也為 1008。 &a[1][0]是二維數組 a 的 1 行 0 列元素地址,也是 1008。因
此 a+1,a[1],*(a+1),&a[1][0]是等同的。 由此可得出:a+i,a[i],*(a+i),&a[i][0]是等同的。 此
外,&a[i]和a[i]也是等同的。因為在二維數組中不能把&a[i]理解為元素 a[i]的地址,不存在
元素 a[i]。
 
    C語言規定,它是一種地址計算方法,表示數組 a 第i 行首地址。由此,我們得出:a[i],
&a[i],*(a+i)和 a+i 也都是等同的。另外,a[0]也
可以看成是 a[0]+0 是一維數組 a[0]的0 號元素的首地址,  而 a[0]+1 則是 a[0]的 1 號元素首
地址,由此可得出 a[i]+j則是一維數組 a[i]的 j 號元素首地址,它等于&a[i][j]。由 a[i]=*(a+i)
得 a[i]+j=*(a+i)+j,由于*(a+i)+j 是二維數組 a 的 i 行 j 列元素的首地址。該元素的值等于
*(*(a+i)+j)。
[Explain]#define PF "%d,%d,%d,%d,%d,\n"
main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
總計164頁  當前為第102 頁
printf(PF,a,*a,a[0],&a[0],&a[0][0]);
printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);
printf("%d,%d\n",a[1]+1,*(a+1)+1);
printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));
}
二、多維數組的指針變量  
 
    把二維數組 a  分解為一維數組 a[0],a[1],a[2]之后,設 p為指向二維數組的指針變量。可
定義為: int (*p)[4]  它表示 p 是一個指針變量,它指向二維數組 a  或指向第一個一維數組
a[0],其值等于 a,a[0],或&a[0][0]等。而p+i 則指向一維數組 a[i]。從前面的分析可得出*(p+i)+j
是二維數組 i行 j  列的元素的地址,而*(*(p+i)+j)則是 i 行j 列元素的值。
 
    二維數組指針變量說明的一般形式為:  類型說明符 (*指針變量名)[長度]  其中“類型說
明符”為所指數組的數據類型。“*”表示其后的變量是指針類型。 “長度”表示二維數組分解
為多個一維數組時,  一維數組的長度,也就是二維數組的列數。應注意“(*指針變量名)”兩
邊的括號不可少,如缺少括號則表示是指針數組(本章后面介紹),意義就完全不同了。
[Explain]main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0;i<3;i++)
for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j));
}
'Expain 字符串指針變量的說明和使用字符串指針變量的定義說明與指向字符變量的指針變
量說明是相同的。只能按對指針變量的賦值不同來區別。  對指向字符變量的指針變量應賦
予該字符變量的地址。如:  char c,*p=&c;表示p 是一個指向字符變量 c 的指針變量。而:  char
*s="C Language";則表示 s是一個指向字符串的指針變量。把字符串的首地址賦予 s。
請看下面一例。
main(){
char *ps;
ps="C Language";
printf("%s",ps);
}
運行結果為:
C Language  
上例中,首先定義 ps 是一個字符指針變量,  然后把字符串的首地址賦予 ps(應寫出整個字
符串,以便編譯系統把該串裝入連續的一塊內存單元),并把首地址送入 ps。程序中的:  char
*ps;ps="C Language";等效于:  char *ps="C Language";輸出字符串中 n 個字符后的所有字符。 
main(){
char *ps="this is a book";
int n=10;
ps=ps+n;
總計164頁  當前為第103 頁
printf("%s\n",ps);
}
運行結果為:
book  在程序中對 ps 初始化時,即把字符串首地址賦予 ps,當 ps= ps+10之后,ps 指向字符
“b”,因此輸出為"book"。
main(){
char st[20],*ps;
int i;
printf("input a string:\n");
ps=st;
scanf("%s",ps);
for(i=0;ps[i]!='\0';i++)
if(ps[i]=='k'){
printf("there is a 'k' in the string\n");
break;
}
if(ps[i]=='\0') printf("There is no 'k' in the string\n");
}
    本例是在輸入的字符串中查找有無‘k’字符。  下面這個例子是將指針變量指向一個格式
字符串,用在 printf 函數中,用于輸出二維數組的各種地址表示的值。但在 printf 語句中用
指針變量 PF代替了格式串。  這也是程序中常用的方法。
main(){
static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
char *PF;
PF="%d,%d,%d,%d,%d\n";
printf(PF,a,*a,a[0],&a[0],&a[0][0]);
printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);
printf("%d,%d\n",a[1]+1,*(a+1)+1);
printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));
}
    在下例是講解,把字符串指針作為函數參數的使用。要求把一個字符串的內容復制到另
一個字符串中,并且不能使用 strcpy 函數。函數 cprstr 的形參為兩個字符指針變量。pss 指
向源字符串,pds 指向目標字符串。表達式: 
(*pds=*pss)!=`\0'
cpystr(char *pss,char *pds){
while((*pds=*pss)!='\0'){
pds++;
pss++; }
}
main(){
char *pa="CHINA",b[10],*pb;
pb=b;
cpystr(pa,pb);
總計164頁  當前為第104 頁
printf("string a=%s\nstring b=%s\n",pa,pb);

    在上例中,程序完成了兩項工作:一是把 pss 指向的源字符復制到 pds 所指向的目標字
符中,二是判斷所復制的字符是否為`\0',若是則表明源字符串結束,不再循環。否則,pds
和 pss 都加 1,指向下一字符。在主函數中,以指針變量 pa,pb 為實參,分別取得確定值后
調用 cprstr 函數。由于采用的指針變量 pa 和 pss,pb 和 pds 均指向同一字符串,因此在主函
數和 cprstr函數中均可使用這些字符串。也可以把 cprstr函數簡化為以下形式: 
cprstr(char *pss,char*pds)
{while ((*pds++=*pss++)!=`\0');}
    即把指針的移動和賦值合并在一個語句中。  進一步分析還可發現`\0'的 ASCⅡ碼為 0,
對于 while 語句只看表達式的值為非 0 就循環,為 0 則結束循環,因此也可省去“!=`\0'”這一
判斷部分,而寫為以下形式:
cprstr (char *pss,char *pds)
{while (*pdss++=*pss++);}  
表達式的意義可解釋為,源字符向目標字符賦值,  移動指針,若所賦值為非 0 則循環,否
則結束循環。這樣使程序更加簡潔。簡化后的程序如下所示。
cpystr(char *pss,char *pds){
while(*pds++=*pss++);
}
main(){
char *pa="CHINA",b[10],*pb;
pb=b;
cpystr(pa,pb);
printf("string a=%s\nstring b=%s\n",pa,pb);
}
使用字符串指針變量與字符數組的區別
 
用字符數組和字符指針變量都可實現字符串的存儲和運算。  但是兩者是有區別的。在使用
時應注意以下幾個問題:
 
1.  字符串指針變量本身是一個變量,用于存放字符串的首地址。而字符串本身是存放在以
該首地址為首的一塊連續的內存空間中并以‘\0’作為串的結束。字符數組是由于若干個數組
元素組成的,它可用來存放整個字符串。
 
2.  對字符數組作初始化賦值,必須采用外部類型或靜態類型,如: static char st[]={“C
Language”};而對字符串指針變量則無此限制,如:  char *ps="C Language";
 
3.  對字符串指針方式  char *ps="C Language";可以寫為: char *ps;  ps="C Language";而對
數組方式: 
static char st[]={"C Language"};
不能寫為:
char st[20];st={"C Language"};
而只能對字符數組的各元素逐個賦值。
 
總計164頁  當前為第105 頁
    從以上幾點可以看出字符串指針變量與字符數組在使用時的區別,同時也可看出使用指
針變量更加方便。前面說過,當一個指針變量在未取得確定地址前使用是危險的,容易引起
錯誤。但是對指針變量直接賦值是可以的。因為 C 系統對指針變量賦值時要給以確定的地
址。因此, 
char *ps="C Langage";
或者 char *ps;
ps="C Language";都是合法的。
 
函數指針變量
 
    在C語言中規定,一個函數總是占用一段連續的內存區,  而函數名就是該函數所占內
存區的首地址。  我們可以把函數的這個首地址(或稱入口地址)賦予一個指針變量,  使該指
針變量指向該函數。然后通過指針變量就可以找到并調用這個函數。  我們把這種指向函數
的指針變量稱為“函數指針變量”。
函數指針變量定義的一般形式為:
類型說明符 (*指針變量名)(); 
其中“類型說明符”表示被指函數的返回值的類型。 “(*  指針變量名)”表示“*”后面的變量是定
義的指針變量。  最后的空括號表示指針變量所指的是一個函數。
例如: int (*pf)();
表示 pf 是一個指向函數入口的指針變量,該函數的返回值(函數值)是整型。
下面通過例子來說明用指針形式實現對函數調用的方法。
int max(int a,int b){
if(a>b)return a;
else return b;
}
main(){
int max(int a,int b);
int(*pmax)();
int x,y,z;
pmax=max;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z=(*pmax)(x,y);
printf("maxmum=%d",z);
}
    從上述程序可以看出用,函數指針變量形式調用函數的步驟如下:1.  先定義函數指針
變量,如后一程序中第 9 行 int (*pmax)();定義 pmax 為函數指針變量。
 
2.  把被調函數的入口地址(函數名)賦予該函數指針變量,如程序中第 11行 pmax=max;
 
3.  用函數指針變量形式調用函數,如程序第 14 行 z=(*pmax)(x,y);  調用函數的一般形式
為: (*指針變量名) (實參表)使用函數指針變量還應注意以下兩點:
 
a.  函數指針變量不能進行算術運算,這是與數組指針變量不同的。數組指針變量加減一個
總計164頁  當前為第106 頁
整數可使指針移動指向后面或前面的數組元素,而函數指針的移動是毫無意義的。
 
b.  函數調用中"(*指針變量名)"的兩邊的括號不可少,其中的*不應該理解為求值運算,在此
處它只是一種表示符號。
 
指針型函數
 
前面我們介紹過,所謂函數類型是指函數返回值的類型。  在C語言中允許一個函數的返回
值是一個指針(即地址),  這種返回指針值的函數稱為指針型函數。
定義指針型函數的一般形式為: 
類型說明符 *函數名(形參表) 

…… /*函數體*/

其中函數名之前加了“*”號表明這是一個指針型函數,即返回值是一個指針。類型說明符表
示了返回的指針值所指向的數據類型。
如:
int *ap(int x,int y)
{
...... /*函數體*/
}
  表示 ap是一個返回指針值的指針型函數, 它返回的指針指向一個整型變量。下例中定
義了一個指針型函數 day_name,它的返回值指向一個字符串。該函數中定義了一個靜態指
針數組 name。name  數組初始化賦值為八個字符串,分別表示各個星期名及出錯提示。形
參 n 表示與星期名所對應的整數。在主函數中,  把輸入的整數 i 作為實參,  在 printf 語句
中調用 day_name 函數并把 i 值傳送給形參 n。day_name 函數中的 return語句包含一個條件
表達式, n 值若大于 7或小于 1 則把 name[0]  指針返回主函數輸出出錯提示字符串“Illegal
day”。否則返回主函數輸出對應的星期名。主函數中的第 7行是個條件語句,其語義是,如
輸入為負數(i<0)則中止程序運行退出程序。exit 是一個庫函數,exit(1)表示發生錯誤后退出
程序, exit(0)表示正常退出。
 
    應該特別注意的是函數指針變量和指針型函數這兩者在寫法和意義上的區別。如
int(*p)()和 int *p()是兩個完全不同的量。int(*p)()是一個變量說明,說明 p  是一個指向函數
入口的指針變量,該函數的返回值是整型量,(*p)的兩邊的括號不能少。int *p()  則不是變
量說明而是函數說明,說明 p 是一個指針型函數,其返回值是一個指向整型量的指針,*p
兩邊沒有括號。作為函數說明,   在括號內最好寫入形式參數,這樣便于與變量說明區別。   對
于指針型函數定義,int *p()只是函數頭部分,一般還應該有函數體部分。
main(){
int i;
char *day_name(int n);  
printf("input Day No:\n");
scanf("%d",&i);
if(i<0) exit(1);
printf("Day No:%2d-->%s\n",i,day_name(i));
總計164頁  當前為第107 頁
}
char *day_name(int n){
static char *name[]={ "Illegal day",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"};
return((n<1||n>7) ? name[0] : name[n]);
}
    本程序是通過指針函數,輸入一個 1~7 之間的整數, 輸出對應的星期名。指針數組的
說明與使用一個數組的元素值為指針則是指針數組。  指針數組是一組有序的指針的集合。
指針數組的所有元素都必須是具有相同存儲類型和指向相同數據類型的指針變量。
指針數組說明的一般形式為:  類型說明符*數組名[數組長度] 
    其中類型說明符為指針值所指向的變量的類型。例如: int *pa[3]  表示 pa 是一個指針
數組,它有三個數組元素,  每個元素值都是一個指針,指向整型變量。通常可用一個指針
數組來指向一個二維數組。  指針數組中的每個元素被賦予二維數組每一行的首地址,  因此
也可理解為指向一個一維數組。圖 6—6 表示了這種關系。
int a[3][3]={1,2,3,4,5,6,7,8,9};
int *pa[3]={a[0],a[1],a[2]};
int *p=a[0];
main(){
int i;
for(i=0;i<3;i++)
printf("%d,%d,%d\n",a[i][2-i],*a[i],*(*(a+i)+i));
for(i=0;i<3;i++)
printf("%d,%d,%d\n",*pa[i],p[i],*(p+i));
}
    本例程序中,pa 是一個指針數組,三個元素分別指向二維數組 a 的各行。然后用循環
語句輸出指定的數組元素。其中*a[i]表示 i 行0 列元素值; *(*(a+i)+i)表示i行 i 列的元素值;
*pa[i]表示i行 0 列元素值;由于 p與 a[0]相同,故 p[i]表示 0 行 i 列的值;*(p+i)表示 0 行 i
列的值。讀者可仔細領會元素值的各種不同的表示方法。  應該注意指針數組和二維數組指
針變量的區別。  這兩者雖然都可用來表示二維數組,但是其表示方法和意義是不同的。
 
    二維數組指針變量是單個的變量,其一般形式中"(*指針變量名)"兩邊的括號不可少。而
指針數組類型表示的是多個指針(  一組有序指針)在一般形式中"*指針數組名"兩邊不能有括
號。例如: int (*p)[3];表示一個指向二維數組的指針變量。該二維數組的列數為 3或分解為
一維數組的長度為 3。 int *p[3]  表示 p 是一個指針數組,有三個下標變量 p[0],p[1],p[2]
均為指針變量。
 
    指針數組也常用來表示一組字符串,  這時指針數組的每個元素被賦予一個字符串的首
地址。  指向字符串的指針數組的初始化更為簡單。例如在例 6.20 中即采用指針數組來表示
總計164頁  當前為第108 頁
一組字符串。  其初始化賦值為: 
char *name[]={"Illagal day",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"};
    完成這個初始化賦值之后, name[0] 即指向字符串 "Illegal day" , name[1]
指?quot;Monday"......。
 
    指針數組也可以用作函數參數。在本例主函數中,定義了一個指針數組 name, 并對 name
作了初始化賦值。其每個元素都指向一個字符串。然后又以 name  作為實參調用指針型函數
day name,在調用時把數組名 name  賦予形參變量 name,輸入的整數 i 作為第二個實參賦
予形參 n。在 day name 函數中定義了兩個指針變量 pp1 和pp2,pp1 被賦予 name[0]的值(即
*name),pp2被賦予 name[n]的值即*(name+ n)。由條件表達式決定返回 pp1或 pp2 指針給主
函數中的指針變量 ps。最后輸出 i和 ps 的值。
 
指針數組作指針型函數的參數
main(){
static char *name[]={ "Illegal day",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"};
char *ps;
int i;
char *day_name(char *name[],int n);
printf("input Day No:\n");
scanf("%d",&i);
if(i<0) exit(1);
ps=day_name(name,i);
printf("Day No:%2d-->%s\n",i,ps);
}
char *day_name(char *name[],int n)
{
char *pp1,*pp2;
pp1=*name;
pp2=*(name+n);
return((n<1||n>7)? pp1:pp2);
總計164頁  當前為第109 頁

下例要求輸入 5 個國名并按字母順序排列后輸出。在以前的例子中采用了普通的排序方法, 
逐個比較之后交換字符串的位置。交換字符串的物理位置是通過字符串復制函數完成的。   反
復的交換將使程序執行的速度很慢,同時由于各字符串(國名)  的長度不同,又增加了存儲
管理的負擔。  用指針數組能很好地解決這些問題。把所有的字符串存放在一個數組中,  把
這些字符數組的首地址放在一個指針數組中,當需要交換兩個字符串時,  只須交換指針數
組相應兩元素的內容(地址)即可,而不必交換字符串本身。程序中定義了兩個函數,一個名
為 sort 完成排序,  其形參為指
針數組 name,即為待排序的各字符串數組的指針。形參 n 為字符串的個數。另一個函數名
為 print,用于排序后字符串的輸出,其形參與 sort 的形參相同。主函數 main 中,定義了指
針數組 name  并作了初始化賦值。然后分別調用 sort 函數和 print 函數完成排序和輸出。值
得說明的是在 sort 函數中,對兩個字符串比較,采用了 strcmp  函數,strcmp 函數允許參與
比較的串以指針方式出現。name[k]和 name[ j]均為指針,因此是合法的。字符串比較后需要
交換時,   只交換指針數組元素的值,而不交換具體的字符串,   這樣將大大減少時間的開銷,
提高了運行效率。
現編程如下:
#include"string.h"
main(){
void sort(char *name[],int n);
void print(char *name[],int n);
static char *name[]={ "CHINA","AMERICA","AUSTRALIA",
"FRANCE","GERMAN"};
int n=5;
sort(name,n);
print(name,n);
}
void sort(char *name[],int n){
char *pt;
int i,j,k;
for(i=0;i<n-1;i++){
k=i;
for(j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0) k=j;
if(k!=i){
pt=name[i];
name[i]=name[k];
name[k]=pt;
}
}
}
void print(char *name[],int n){
int i;
for (i=0;i<n;i++) printf("%s\n",name[i]);
}
總計164頁  當前為第110 頁
main函數的參數
 
    前面介紹的 main 函數都是不帶參數的。因此 main  后的括號都是空括號。實際上,main
函數可以帶參數,這個參數可以認為是 main 函數的形式參數。C語言規定 main 函數的參
數只能有兩個,   習慣上這兩個參數寫為 argc 和argv。因此, main 函數的函數頭可寫為:  main
(argc,argv)C語言還規定 argc(第一個形參)必須是整型變量,argv(  第二個形參)必須是指向字
符串的指針數組。加上形參說明后,main 函數的函數頭應寫為: 
main (argc,argv)
int argv;
char *argv[];或寫成:
main (int argc,char *argv[])
  由于 main 函數不能被其它函數調用,  因此不可能在程序內部取得實際值。那么,在何
處把實參值賦予 main 函數的形參呢?  實際上,main 函數的參數值是從操作系統命令行上獲
得的。當我們要運行一個可執行文件時,在 DOS 提示符下鍵入文件名,再輸入實際參數即
可把這些實參傳送到 main 的形參中去。
 
   DOS 提示符下命令行的一般形式為: C:\>可執行文件名  參數  參數……;  但是應該特
別注意的是,main  的兩個形參和命令行中的參數在
位置上不是一一對應的。因為,main 的形參只有二個,而命令行中的參數個數原則上未加限
制。argc 參數表示了命令行中參數的個數(注意:文件名本身也算一個參數),argc的值是在
輸入命令行時由系統按實際參數的個數自動賦予的。例如有命令行為: C:\>E6 24 BASIC
dbase FORTRAN 由于文件名 E6 24 本身也算一個參數,所以共有 4 個參數,因此 argc 取得
的值為 4。argv 參數是字符串指針數組,其各元素值為命令行中各字符串(參數均按字符串
處理)的首地址。  指針數組的長度即為參數個數。數組元素初值由系統自動賦予。其表示如
圖 6.8 所示:
main(int argc,char *argv){
while(argc-->1)
printf("%s\n",*++argv);
}
本例是顯示命令行中輸入的參數如果上例的可執行文件名為 e24.exe,存放在 A驅動器的盤
內。
因此輸入的命令行為:  C:\>a:e24 BASIC dBASE FORTRAN  
則運行結果為:
BASIC
dBASE
FORTRAN
    該行共有 4 個參數,執行 main 時,argc 的初值即為 4。argv 的 4 個元素分為 4 個字符
串的首地址。執行 while 語句,每循環一次 argv 值減 1,當 argv 等于 1 時停止循環,共循
環三次,  因此共可輸出三個參數。在 printf函數中,由于打印項*++argv是先加 1 再打印, 
故第一次打印的是 argv[1]所指的字符串 BASIC。第二、  三次循環分別打印后二個字符串。
而參數 e24是文件名,不必輸出。
 
    下例的命令行中有兩個參數,第二個參數 20 即為輸入的 n 值。在程序中*++argv 的值
為字符串“20”,然后用函數"atoi"把它換為整型作為 while 語句中的循環控制變量,輸出 20
總計164頁  當前為第111 頁
個偶數。
#include"stdlib.h"
main(int argc,char*argv[]){
int a=0,n;
n=atoi(*++argv);
while(n--) printf("%d ",a++*2);
}
    本程序是從 0 開始輸出 n 個偶數。指向指針的指針變量如果一個指針變量存放的又是另
一個指針變量的地址, 則稱這個指針變量為指向指針的指針變量。
 
    在前面已經介紹過,通過指針訪問變量稱為間接訪問,  簡稱間訪。由于指針變量直接
指向變量,所以稱為單級間訪。  而如果通過指向指針的指針變量來訪問變量則構成了二級
或多級間訪。在C語言程序中,對間訪的級數并未明確限制,  但是間訪級數太多時不容易
理解解,也容易出錯,因此,一般很少超過二級間訪。  指向指針的指針變量說明的一般形
式為: 
類型說明符**  指針變量名; 
例如: int ** pp;  表示pp是一個指針變量,它指向另一個指針變量,  而這個指針變量指向
一個整型量。下面舉一個例子來說明這種關系。
main(){
int x,*p,**pp;
x=10;
p=&x;
pp=&p;
printf("x=%d\n",**pp);

    上例程序中 p  是一個指針變量,指向整型量 x;pp 也是一個指針變量,  它指向指針變
量 p。通過 pp 變量訪問 x 的寫法是**pp。程序最后輸出 x 的值為 10。通過上例,讀者可以
學習指向指針的指針變量的說明和使用方法。
 
    下述程序中首先定義說明了指針數組 ps 并作了初始化賦值。  又說明了 pps 是一個指向
指針的指針變量。在 5次循環中, pps  分別取得了 ps[0],ps[1],ps[2],ps[3],ps[4]的地址
值(如圖 6.10所示)。再通過這些地址即可找到該字符串。
main(){
static char *ps[]={ "BASIC","DBASE","C","FORTRAN",
"PASCAL"};
char **pps;
int i;
for(i=0;i<5;i++){
pps=ps+i;
printf("%s\n",*pps);
}
}
本程序是用指向指針的指針變量編程,輸出多個字符串。
本章小結
總計164頁  當前為第112 頁
1.  指針是C語言中一個重要的組成部分,使用指針編程有以下優點:
(1)提高程序的編譯效率和執行速度。
(2)通過指針可使用主調函數和被調函數之間共享變量或數據結構,便于實現雙向數據通訊。 
(3)可以實現動態的存儲分配。
(4)便于表示各種數據結構,編寫高質量的程序。
 
2.  指針的運算
(1)取地址運算符&:求變量的地址
(2)取內容運算符*:表示指針所指的變量
(3)賦值運算
·把變量地址賦予指針變量
·同類型指針變量相互賦值
·把數組,字符串的首地址賦予指針變量
·把函數入口地址賦予指針變量
(4)加減運算
對指向數組,字符串的指針變量可以進行加減運算,如 p+n,p-n,p++,p--等。對指向同一數組
的兩個指針變量可以相減。對指向其它類型的指針變量作加減運算是無意義的。 
(5)關系運算
指向同一數組的兩個指針變量之間可以進行大于、小于、 等于比較運算。指針可與 0 比較,
p==0 表示p為空指針。
 
3.  與指針有關的各種說明和意義見下表。
int *p;       p 為指向整型量的指針變量
int *p[n];     p 為指針數組,由 n 個指向整型量的指針元素組成。
int (*p)[n];    p 為指向整型二維數組的指針變量,二維數組的列數為 n
int *p()      p 為返回指針值的函數,該指針指向整型量
int (*p)()     p 為指向函數的指針,該函數返回整型量
int **p       p 為一個指向另一指針的指針變量,該指針指向一個整型量。
 
4.  有關指針的說明很多是由指針,數組,函數說明組合而成的。
但并不是可以任意組合,例如數組不能由函數組成,即數組元素不能是一個函數;函數也不
能返回一個數組或返回另一個函數。例如
int a[5]();就是錯誤的。
 
5.  關于括號
在解釋組合說明符時, 標識符右邊的方括號和圓括號優先于標識符左邊的“*”號,而方括號
和圓括號以相同的優先級從左到右結合。但可以用圓括號改變約定的結合順序。
 
6.  閱讀組合說明符的規則是“從里向外”。
從標識符開始,先看它右邊有無方括號或園括號,如有則先作出解釋,再看左邊有無*號。   如
果在任何時候遇到了閉括號,則在繼續之前必須用相同的規則處理括號內的內容。例如: 
int*(*(*a)())[10]
↑ ↑↑↑↑↑↑
7 6 4 2 1 3 5
總計164頁  當前為第113 頁
上面給出了由內向外的閱讀順序,下面來解釋它:
(1)標識符 a被說明為;
(2)一個指針變量,它指向;
(3)一個函數,它返回;
(4)一個指針,該指針指向;
(5)一個有 10個元素的數組,其類型為;
(6)指針型,它指向;
(7)int 型數據。
因此 a 是一個函數指針變量,該函數返回的一個指針值又指向一個指針數組,該指針數組的
元素指向整型量。
總計164頁  當前為第114 頁
C語言教程第七章:結構與聯合
結構類型定義和結構變量說明
 
    在實際問題中,一組數據往往具有不同的數據類型。例如,  在學生登記表中,姓名應
為字符型;學號可為整型或字符型;  年齡應為整型;性別應為字符型;成績可為整型或實
型。   顯然不能用一個數組來存放這一組數據。   因為數組中各元素的類型和長度都必須一致,
以便于編譯系統處理。為了解決這個問題,C語言中給出了另一種構造數據類型——“結構”。 
它相當于其它高級語言中的記錄。
 
   “結構”是一種構造類型,它是由若干“成員”組成的。  每一個成員可以是一個基本數據
類型或者又是一個構造類型。  結構既是一種“構造”而成的數據類型,  那么在說明和使用之
前必須先定義它,也就是構造它。如同在說明和調用函數之前要先定義函數一樣。
 
一、結構的定義
 
定義一個結構的一般形式為: 
struct  結構名 

成員表列 
};
成員表由若干個成員組成,  每個成員都是該結構的一個組成部分。對每個成員也必須作類
型說明,其形式為:
類型說明符 成員名; 
成員名的命名應符合標識符的書寫規定。例如: 
struct stu
{
int num;
char name[20];
char sex;
float score;
};  
    在這個結構定義中,結構名為 stu,該結構由4 個成員組成。  第一個成員為 num,整型
變量;第二個成員為 name,字符數組;第三個成員為 sex,字符變量;第四個成員為 score,
實型變量。  應注意在括號后的分號是不可少的。結構定義之后,即可進行變量說明。  凡說
明為結構 stu 的變量都由上述 4 個成員組成。由此可見,  結構是一種復雜的數據類型,是
數目固定,類型不同的若干有序變量的集合。
 
二、結構類型變量的說明
 
說明結構變量有以下三種方法。以上面定義的 stu 為例來加以說明。
1.  先定義結構,再說明結構變量。如: 
總計164頁  當前為第115 頁
struct stu
{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1,boy2;
說明了兩個變量 boy1 和boy2為 stu結構類型。也可以用宏定義使一個符號常量來表示一個
結構類型,例如: 
#define STU struct stu
STU
{
int num;
char name[20];
char sex;
float score;
};
STU boy1,boy2;
 
2.  在定義結構類型的同時說明結構變量。例如: 
struct stu
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;
 
3.  直接說明結構變量。例如: 
struct
{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;  
 
    第三種方法與第二種方法的區別在于第三種方法中省去了結構名,而直接給出結構變
量。三種方法中說明的 boy1,boy2 變量都具有圖 7.1 所示的結構。說明了 boy1,boy2 變量為
stu 類型后,即可向這兩個變量中的各個成員賦值。在上述 stu 結構定義中,所有的成員都
是基本數據類型或數組類型。成員也可以又是一個結構,  即構成了嵌套的結構。例如,圖
7.2 給出了另一個數據結構。  按圖 7.2 可給出以下結構定義: 
struct date{
總計164頁  當前為第116 頁
int month;
int day;
int year;
}
struct{
int num;
char name[20];
char sex;
struct date birthday;
float score;
}boy1,boy2;
    首先定義一個結構 date,由 month(月)、day(日)、year(年)  三個成員組成。  在定義并說
明變量 boy1  和 boy2  時,  其中的成員 birthday 被說明為 data 結構類型。成員名可與程序
中其它變量同名,互不干擾。結構變量成員的表示方法在程序中使用結構變量時,  往往不
把它作為一個整體來使用。
在 ANSI C 中除了允許具有相同類型的結構變量相互賦值以外,  一般對結構變量的使用,
包括賦值、輸入、輸出、  運算等都是通過結構變量的成員來實現的。
 
  表示結構變量成員的一般形式是:  結構變量名.成員名 例如:boy1.num  即第一個人的
學號  boy2.sex  即第二個人的性別  如果成員本身又是一個結構則必須逐級找到最低級的
成員才能使用。例如:boy1.birthday.month  即第一個人出生的月份成員可以在程序中單獨使
用,與普通變量完全相同。
 
結構變量的賦值
 
前面已經介紹,結構變量的賦值就是給各成員賦值。  可用輸入語句或賦值語句來完成。
[例 7.1]給結構變量賦值并輸出其值。
main(){
struct stu
{
int num;
char *name;
char sex;
float score;
} boy1,boy2;
boy1.num=102;
boy1.name="Zhang ping";
printf("input sex and score\n");
scanf("%c %f",&boy1.sex,&boy1.score);
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
 
總計164頁  當前為第117 頁
    本程序中用賦值語句給 num 和 name 兩個成員賦值,name 是一個字符串指針變量。用
scanf 函數動態地輸入 sex和 score 成員值,然后把 boy1 的所有成員的值整體賦予 boy2。最
后分別輸出 boy2  的各個成員值。本例表示了結構變量的賦值、輸入和輸出的方法。
 
結構變量的初始化
    如果結構變量是全局變量或為靜態變量,  則可對它作初始化賦值。對局部或自動結構
變量不能作初始化賦值。
[例 7.2]外部結構變量初始化。
struct stu /*定義結構*/
{
int num;
char *name;
char sex;
float score;
} boy2,boy1={102,"Zhang ping",'M',78.5};
main()
{
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
struct stu
{
int num;
char *name;
char sex;
float score;
}boy2,boy1={102,"Zhang ping",'M',78.5};
main()

boy2=boy1;
……
}
本例中,boy2,boy1 均被定義為外部結構變量,并對 boy1 作了初始化賦值。在 main函數中,
把 boy1 的值整體賦予 boy2,  然后用兩個 printf語句輸出 boy2各成員的值。
[例 7.3]靜態結構變量初始化。
main()
{
static struct stu /*定義靜態結構變量*/
{
int num;
char *name;
char sex;
float score;
總計164頁  當前為第118 頁
}boy2,boy1={102,"Zhang ping",'M',78.5};
boy2=boy1;
printf("Number=%d\nName=%s\n",boy2.num,boy2.name);
printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);
}
static struct stu
{
int num;
char *name;
char sex;
float score;
}boy2,boy1={102,"Zhang ping",'M',78.5}; 
    本例是把 boy1,boy2 都定義為靜態局部的結構變量,  同樣可以作初始化賦值。
結構數組
 
數組的元素也可以是結構類型的。  因此可以構成結構型數組。結構數組的每一個元素都是
具有相同結構類型的下標結構變量。  在實際應用中,經常用結構數組來表示具有相同數據
結構的一個群體。如一個班的學生檔案,一個車間職工的工資表等。 
結構數組的定義方法和結構變量相似,只需說明它為數組類型即可。例如: 
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]; 
定義了一個結構數組 boy1,共有5 個元素,boy[0]~boy[4]。每個數組元素都具有 struct stu
的結構形式。  對外部結構數組或靜態結構數組可以作初始化賦值,例如: 
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping","M",45},
{102,"Zhang ping","M",62.5},
{103,"He fang","F",92.5},
{104,"Cheng ling","F",87},
{105,"Wang ming","M",58};
}
當對全部元素作初始化賦值時,也可不給出數組長度。
[例 7.4]計算學生的平均成績和不及格的人數。
struct stu
總計164頁  當前為第119 頁
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Li ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"He fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
int i,c=0;
float ave,s=0;
for(i=0;i<5;i++)
{
s+=boy[i].score;
if(boy[i].score<60) c+=1;
}
printf("s=%f\n",s);
ave=s/5;
printf("average=%f\ncount=%d\n",ave,c);
}
本例程序中定義了一個外部結構數組 boy,共 5個元素,  并作了初始化賦值。在 main 函數
中用 for語句逐個累加各元素的 score  成員值存于s 之中,如 score 的值小于 60(不及格)即計
數器 C 加1,  循環完畢后計算平均成績,并輸出全班總分,平均分及不及格人數。
 
[例 7.5]建立同學通訊錄
#include"stdio.h"
#define NUM 3
struct mem
{
char name[20];
char phone[10];
};
main()
{
struct mem man[NUM];
int i;
for(i=0;i<NUM;i++)
{
printf("input name:\n");
總計164頁  當前為第120 頁
gets(man[i].name);
printf("input phone:\n");
gets(man[i].phone);
}
printf("name\t\t\tphone\n\n");
for(i=0;i<NUM;i++)
printf("%s\t\t\t%s\n",man[i].name,man[i].phone);
}
    本程序中定義了一個結構 mem,它有兩個成員 name 和 phone  用來表示姓名和電話號
碼。在主函數中定義 man 為具有 mem  類型的結構數組。在 for語句中,用 gets 函數分別輸
入各個元素中兩個成員的值。然后又在 for語句中用 printf語句輸出各元素中兩個成員值。
 
結構指針變量
 
結構指針變量的說明和使用一個指針變量當用來指向一個結構變量時,  稱之為結構指針變
量。
結構指針變量中的值是所指向的結構變量的首地址。  通過結構指針即可訪問該結構變量,
這與數組指針和函數指針的情況是相同的。結構指針變量說明的一般形式為: 
struct  結構名*結構指針變量名 
例如,在前面的例 7.1 中定義了 stu 這個結構,  如要說明一個指向 stu 的指針變量 pstu,可
寫為: 
struct stu *pstu;  
 
    當然也可在定義 stu結構時同時說明 pstu。與前面討論的各類指針變量相同,結構指針
變量也必須要先賦值后才能使用。賦值是把結構變量的首地址賦予該指針變量,  不能把結
構名賦予該指針變量。如果 boy是被說明為 stu 類型的結構變量,則:  pstu=&boy是正確的,
而: pstu=&stu 是錯誤的。
 
    結構名和結構變量是兩個不同的概念,不能混淆。  結構名只能表示一個結構形式,編
譯系統并不對它分配內存空間。  只有當某變量被說明為這種類型的結構時,才對該變量分
配存儲空間。  因此上面&stu 這種寫法是錯誤的,不可能去取一個結構名的首地址。  有了
結構指針變量,就能更方便地訪問結構變量的各個成員。
 
其訪問的一般形式為: (*結構指針變量).成員名 或為:
結構指針變量->成員名 
例如: (*pstu).num或者: pstu->num
應該注意(*pstu)兩側的括號不可少,  因為成員符“.”的優先級高于“*”。如去掉括號寫作
*pstu.num則等效于*(pstu.num),這樣,意義就完全不對了。  下面通過例子來說明結構指針
變量的具體說明和使用方法。
[例 7.6]
struct stu
{
int num;
char *name;
總計164頁  當前為第121 頁
char sex;
float score;
} boy1={102,"Zhang ping",'M',78.5},*pstu;
main()
{
pstu=&boy1;
printf("Number=%d\nName=%s\n",boy1.num,boy1.name);
printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score);
printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name);
printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score);
printf("Number=%d\nName=%s\n",pstu->num,pstu->name);
printf("Sex=%c\nScore=%f\n\n",pstu->sex,pstu->score);
}
 
    本例程序定義了一個結構 stu,定義了 stu 類型結構變量 boy1  并作了初始化賦值,還定
義了一個指向 stu 類型結構的指針變量 pstu。在 main 函數中,pstu 被賦予 boy1 的地址,因
此 pstu 指向boy1  。然后在 printf語句內用三種形式輸出 boy1的各個成員值。  從運行結果
可以看出: 
結構變量.成員名
(*結構指針變量).成員名
結構指針變量->成員名
這三種用于表示結構成員的形式是完全等效的。結構數組指針變量結構指針變量可以指向一
個結構數組,  這時結構指針變量的值是整個結構數組的首地址。  結構指針變量也可指向結
構數組的一個元素,這時結構指針變量的值是該結構數組元素的首地址。設 ps 為指向結構
數組的指針變量,則 ps 也指向該結構數組的 0 號元素,ps+1 指向 1 號元素,ps+i 則指向 i
號元素。  這與普通數組的情況是一致的。
[例 7.7]用指針變量輸出結構數組。
struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Zhou ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"Liou fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;
printf("No\tName\t\t\tSex\tScore\t\n");
總計164頁  當前為第122 頁
for(ps=boy;ps<boy+5;ps++)
printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->
score);
}
    在程序中,定義了 stu 結構類型的外部數組 boy  并作了初始化賦值。在 main 函數內定
義 ps 為指向stu 類型的指針。在循環語句 for的表達式 1 中,ps 被賦予 boy的首地址,然后
循環 5 次,輸出 boy數組中各成員值。  應該注意的是,  一個結構指針變量雖然可以用來訪
問結構變量或結構數組元素的成員,但是,不能使它指向一個成員。  也就是說不允許取一
個成員的地址來賦予它。因此,下面的賦值是錯誤的。  ps=&boy[1].sex;而只能是: ps=boy;(賦
予數組首地址)
或者是:
ps=&boy[0];(賦予 0 號元素首地址)
 
結構指針變量作函數參數
 
  在 ANSI C 標準中允許用結構變量作函數參數進行整體傳送。  但是這種傳送要將全部
成員逐個傳送,  特別是成員為數組時將會使傳送的時間和空間開銷很大,嚴重地降低了程
序的效率。 因此最好的辦法就是使用指針,即用指針變量作函數參數進行傳送。 這時由實
參傳向形參的只是地址,從而減少了時間和空間的開銷。
[例 7.8]題目與例 7.4 相同,計算一組學生的平均成績和不及格人數。
用結構指針變量作函數參數編程。
struct stu
{
int num;
char *name;
char sex;
float score;}boy[5]={
{101,"Li ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"He fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;
void ave(struct stu *ps);
ps=boy;
ave(ps);
}
void ave(struct stu *ps)
{
int c=0,i;
float ave,s=0;
總計164頁  當前為第123 頁
for(i=0;i<5;i++,ps++)
{
s+=ps->score;
if(ps->score<60) c+=1;
}
printf("s=%f\n",s);
ave=s/5;
printf("average=%f\ncount=%d\n",ave,c);
}
    本程序中定義了函數 ave,其形參為結構指針變量 ps。boy  被定義為外部結構數組,因
此在整個源程序中有效。在 main  函數中定義說明了結構指針變量 ps,并把 boy的首地址賦
予它,使 ps指向 boy  數組。然后以 ps 作實參調用函數 ave。在函數 ave  中完成計算平均成
績和統計不及格人數的工作并輸出結果。與例 7.4 程序相比,由于本程序全部采用指針變量
作運算和處理,故速度更快,程序效率更高。.
topoic=動態存儲分配
 
    在數組一章中,曾介紹過數組的長度是預先定義好的,  在整個程序中固定不變。C語
言中不允許動態數組類型。例如:  int n;scanf("%d",&n);int a[n];  用變量表示長度,想對數組
的大小作動態說明,  這是錯誤的。但是在實際的編程中,往往會發生這種情況,  即所需的
內存空間取決于實際輸入的數據,而無法預先確定。對于這種問題,  用數組的辦法很難解
決。為了解決上述問題,C語言提供了一些內存管理函數,這些內存管理函數可以按需要動
態地分配內存空間,  也可把不再使用的空間回收待用,為有效地利用內存資源提供了手段。 
常用的內存管理函數有以下三個:
 
1.分配內存空間函數 malloc
調用形式: (類型說明符*) malloc (size)  功能:在內存的動態存儲區中分配一塊長度為"size"
字節的連續區域。函數的返回值為該區域的首地址。 “類型說明符”表示把該區域用于何種
數據類型。(類型說明符*)表示把返回值強制轉換為該類型指針。“size”是一個無符號數。例
如:  pc=(char *) malloc (100); 表示分配 100 個字節的內存空間,并強制轉換為字符數組類
型,  函數的返回值為指向該字符數組的指針, 把該指針賦予指針變量 pc。
 
2.分配內存空間函數 calloc
calloc  也用于分配內存空間。調用形式: (類型說明符*)calloc(n,size)  功能:在內存動態存
儲區中分配 n 塊長度為“size”字節的連續區域。函數的返回值為該區域的首地址。(類型說明
符*)用于強制類型轉換。calloc 函數與 malloc  函數的區別僅在于一次可以分配 n 塊區域。例
如:  ps=(struet stu*) calloc(2,sizeof (struct stu));  其中的 sizeof(struct stu)是求 stu 的結構長度。
因此該語句的意思是:按 stu 的長度分配 2 塊連續區域,強制轉換為 stu 類型,并把其首地
址賦予指針變量 ps。
 
3.釋放內存空間函數 free
調用形式: free(void*ptr);  功能:釋放 ptr 所指向的一塊內存空間,ptr 是一個任意類型的
指針變量,它指向被釋放區域的首地址。被釋放區應是由 malloc 或 calloc 函數所分配的區
域:[例7.9]分配一塊區域,輸入一個學生數據。
main()
總計164頁  當前為第124 頁
{
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%f\n",ps->sex,ps->score);
free(ps);
}
    本例中,定義了結構 stu,定義了 stu 類型指針變量 ps。  然后分配一塊 stu 大內存區,
并把首地址賦予 ps,使ps 指向該區域。再以 ps 為指向結構的指針變量對各成員賦值,并用
printf  輸出各成員值。最后用 free 函數釋放 ps 指向的內存空間。  整個程序包含了申請內存
空間、使用內存空間、釋放內存空間三個步驟,  實現存儲空間的動態分配。鏈表的概念在
例 7.9 中采用了動態分配的辦法為一個結構分配內存空間。每一次分配一塊空間可用來存放
一個學生的數據,  我們可稱之為一個結點。有多少個學生就應該申請分配多少塊內存空間, 
也就是說要建立多少個結點。當然用結構數組也可以完成上述工作,  但如果預先不能準確
把握學生人數,也就無法確定數組大小。  而且當學生留級、退學之后也不能把該元素占用
的空間從數組中釋放出來。  用動態存儲的方法可以很好地解決這些問題。  有一個學生就分
配一個結點,無須預先確定學生的準確人數,某學生退學,  可刪去該結點,并釋放該結點
占用的存儲空間。從而節約了寶貴的內存資源。  另一方面,用數組的方法必須占用一塊連
續的內存區域。  而使用動態分配時,每個結點之間可以是不連續的(結點內是連續的)。  結
點之間的聯系可以用指針實現。  即在結點結構中定義一個成員項用來存放下一結點的首地
址,這個用于存放地址的成員,常把它稱為指針域。可在第一個結點的指針域內存入第二個
結點的首地址,  在第二個結點的指針域內又存放第三個結點的首地址,  如此串連下去直到
最后一個結點。最后一個結點因無后續結點連接,其指針域可賦為 0。這樣一種連接方式,
在數據結構中稱為“鏈表”。圖 7.3 為鏈表的示意圖。
 
  在圖 7.3 中,第 0 個結點稱為頭結點,  它存放有第一個結點的首地址,它沒有數據,
只是一個指針變量。  以下的每個結點都分為兩個域,一個是數據域,存放各種實際的數據,
如學號 num,姓名 name,性別 sex 和成績 score 等。另一個域為指針域,  存放下一結點的
首地址。鏈表中的每一個結點都是同一種結構類型。例如,  一個存放學生學號和成績的結
點應為以下結構:
struct stu
{ int num;
int score;
struct stu *next;
總計164頁  當前為第125 頁
}
    前兩個成員項組成數據域,后一個成員項 next 構成指針域,  它是一個指向 stu 類型結
構的指針變量。鏈表的基本操作對鏈表的主要操作有以下幾種: 
1.建立鏈表;
2.結構的查找與輸出;
3.插入一個結點;
4.刪除一個結點;
下面通過例題來說明這些操作。
[例 7.10]建立一個三個結點的鏈表,存放學生數據。  為簡單起見,  我們假定學生數據結構
中只有學號和年齡兩項。
可編寫一個建立鏈表的函數 creat。程序如下:
#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{
int num;
int age;
struct stu *next;
};
TYPE *creat(int n)
{
struct stu *head,*pf,*pb;
int i;
for(i=0;i<n;i++)

pb=(TYPE*) malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pb->num,&pb->age);
if(i==0)
pf=head=pb;
else pf->next=pb;
pb->next=NULL;
pf=pb;
}
return(head);
}
    在函數外首先用宏定義對三個符號常量作了定義。這里用 TYPE 表示 struct stu,用 LEN
表示 sizeof(struct stu)主要的目的是為了在以下程序內減少書寫并使閱讀更加方便。結構 stu
定義為外部類型,程序中的各個函數均可使用該定義。
creat 函數用于建立一個有 n 個結點的鏈表,它是一個指針函數,它返回的指針指向 stu結構。
在 creat 函數內定義了三個 stu 結構的指針變量。head 為頭指針,pf  為指向兩相鄰結點的前
一結點的指針變量。pb 為后一結點的指針變量。在 for 語句內,用 malloc 函數建立長度與
stu 長度相等的空間作為一結點,首地址賦予 pb。然后輸入結點數據。如果當前結點為第一
總計164頁  當前為第126 頁
結點(i==0),則把 pb 值 (該結點指針)賦予 head 和pf。如非第一結點,則把 pb 值賦予pf 所
指結點的指針域成員 next。而 pb 所指結點為當前的最后結點,其指針域賦 NULL。  再把
pb 值賦予 pf以作下一次循環準備。
   creat 函數的形參 n,表示所建鏈表的結點數,作為 for 語句的循環次數。圖 7.4 表示了
creat 函數的執行過程。
 
[例 7.11]寫一個函數,在鏈表中按學號查找該結點。
TYPE * search (TYPE *head,int n)
{
TYPE *p;
int i;
p=head;
while (p->num!=n && p->next!=NULL)
p=p->next; /*  不是要找的結點后移一步*/
if (p->num==n) return (p);
if (p->num!=n&& p->next==NULL)
printf ("Node %d has not been found!\n",n
}
    本函數中使用的符號常量 TYPE 與例 7.10的宏定義相同,等于 struct  stu。函數有兩個
形參,head是指向鏈表的指針變量,n 為要查找的學號。進入 while 語句,逐個檢查結點的
num成員是否等于 n,如果不等于 n且指針域不等于 NULL(不是最后結點)則后移一個結點,
繼續循環。如找到該結點則返回結點指針。  如循環結束仍未找到該結點則輸出“未找到”的
提示信息。
 
[例 7.12]寫一個函數,刪除鏈表中的指定結點。刪除一個結點有兩種情況:
1.  被刪除結點是第一個結點。這種情況只需使 head 指向第二個結點即可。即head=pb->next。
其過程如圖 7.5 所示。
2.  被刪結點不是第一個結點,這種情況使被刪結點的前一結點指向被刪結點的后一結點即
可。即 pf->next=pb->next。其過程如圖 7.6 所示。
函數編程如下:
TYPE * delete(TYPE * head,int num)
{
TYPE *pf,*pb;
if(head==NULL) /*如為空表,  輸出提示信息*/
{ printf("\nempty list!\n");
goto end;}
pb=head;
while (pb->num!=num && pb->next!=NULL)
/*當不是要刪除的結點,而且也不是最后一個結點時,繼續循環*/
{pf=pb;pb=pb->next;}/*pf指向當前結點,pb 指向下一結點*/
if(pb->num==num)
{if(pb==head) head=pb->next;
/*如找到被刪結點,且為第一結點,則使 head 指向第二個結點,
否則使 pf 所指結點的指針指向下一結點*/
總計164頁  當前為第127 頁
else pf->next=pb->next;
free(pb);
printf("The node is deleted\n");}
else
printf("The node not been foud!\n");
end:
return head;

    函數有兩個形參,head 為指向鏈表第一結點的指針變量,num刪結點的學號。  首先判
斷鏈表是否為空,為空則不可能有被刪結點。若不為空,則使 pb 指針指向鏈表的第一個結
點。進入 while 語句后逐個查找被刪結點。找到被刪結點之后再看是否為第一結點,若是則
使 head 指向第二結點(即把第一結點從鏈中刪去),否則使被刪結點的前一結點(pf所指)指向
被刪結點的后一結點(被刪結點的指針域所指)。如若循環結束未找到要刪的結點,  則輸出
“末找到”的提示信息。最后返回 head 值。
 
[例 7.13]寫一個函數,在鏈表中指定位置插入一個結點。在一個鏈表的指定位置插入結點,
要求鏈表本身必須是已按某種規律排好序的。例如,在學生數據鏈表中,  要求學號順序插
入一個結點。設被插結點的指針為 pi。  可在三種不同情況下插入。
1.  原表是空表,只需使 head 指向被插結點即可。見圖 7.7(a)
2.  被插結點值最小,應插入第一結點之前。這種情況下使 head 指向被插結點,被插結點的
指針域指向原來的第一結點則可。即:pi->next=pb;
head=pi;  見圖 7.7(b)
3.  在其它位置插入,見圖 7.7(c)。這種情況下,使插入位置的前一結點的指針域指向被插結
點,使被插結點的指針域指向插入位置的后一結點。即為:pi->next=pb;pf->next=pi;
4.  在表末插入,見圖 7.7(d)。這種情況下使原表末結點指針域指向被插結點,被插結點指針
域置為 NULL。即:
pb->next=pi;
pi->next=NULL;  TYPE * insert(TYPE * head,TYPE *pi)
{
TYPE *pf,*pb;
pb=head;
if(head==NULL) /*空表插入*/
(head=pi;
pi->next=NULL;}
else
{
while((pi->num>pb->num)&&(pb->next!=NULL))
{pf=pb;
pb=pb->next; }/*找插入位置*/
if(pi->num<=pb->num)
{if(head==pb)head=pi;/*在第一結點之前插入*/
else pf->next=pi;/*在其它位置插入*/
pi->next=pb; }
else
總計164頁  當前為第128 頁
{pb->next=pi;
pi->next=NULL;} /*在表末插入*/
}
return head;}
    本函數有兩個形參均為指針變量,head 指向鏈表,pi 指向被插結點。函數中首先判斷
鏈表是否為空,為空則使 head 指向被插結點。表若不空,則用 while 語句循環查找插入位
置。找到之后再判斷是否在第一結點之前插入,若是則使 head  指向被插結點被插結點指針
域指向原第一結點,否則在其它位置插入,  若插入的結點大于表中所有結點,則在表末插
入。本函數返回一個指針,  是鏈表的頭指針。  當插入的位置在第一個結點之前時,  插入
的新結點成為鏈表的第一個結點,因此 head 的值也有了改變,  故需要把這個指針返回主調
函數。
[例 7.14]將以上建立鏈表,刪除結點,插入結點的函數組織在一起,再建一個輸出全部結點
的函數,然后用 main 函數調用它們。
#define NULL 0
#define TYPE struct stu
#define LEN sizeof(struct stu)
struct stu
{
int num;
int age;
struct stu *next;
};
TYPE * creat(int n)
{
struct stu *head,*pf,*pb;
int i;
for(i=0;i<n;i++)
{
pb=(TYPE *)malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pb->num,&pb->age);
if(i==0)
pf=head=pb;
else pf->next=pb;
pb->next=NULL;
pf=pb;
}
return(head);
}
TYPE * delete(TYPE * head,int num)
{
TYPE *pf,*pb;
if(head==NULL)
{ printf("\nempty list!\n");
總計164頁  當前為第129 頁
goto end;}
pb=head;
while (pb->num!=num && pb->next!=NULL)
{pf=pb;pb=pb->next;}
if(pb->num==num)
{ if(pb==head) head=pb->next;
else pf->next=pb->next;
printf("The node is deleted\n"); }
else
free(pb);
printf("The node not been found!\n");
end:
return head;
}
TYPE * insert(TYPE * head,TYPE * pi)
{
TYPE *pb ,*pf;
pb=head;
if(head==NULL)
{ head=pi;
pi->next=NULL; }
else
{
while((pi->num>pb->num)&&(pb->next!=NULL))
{ pf=pb;
pb=pb->next; }
if(pi->num<=pb->num)
{ if(head==pb) head=pi;
else pf->next=pi;
pi->next=pb; }
else
{ pb->next=pi;
pi->next=NULL; }
}
return head;
}
void print(TYPE * head)
{
printf("Number\t\tAge\n");
while(head!=NULL)
{
printf("%d\t\t%d\n",head->num,head->age);
head=head->next;
}
總計164頁  當前為第130 頁
}
main()
{
TYPE * head,*pnum;
int n,num;
printf("input number of node: ");
scanf("%d",&n);
head=creat(n);
print(head);
printf("Input the deleted number: ");
scanf("%d",&num);
head=delete(head,num);
print(head);
printf("Input the inserted number and age: ");
pnum=(TYPE *)malloc(LEN);
scanf("%d%d",&pnum->num,&pnum->age);
head=insert(head,pnum);
print(head);
}
    本例中,print 函數用于輸出鏈表中各個結點數據域值。函數的形參 head 的初值指向鏈
表第一個結點。在 while語句中,輸出結點值后,head 值被改變,指向下一結點。若保留頭
指針 head, 則應另設一個指針變量,把 head 值賦予它,再用它來替代 head。在 main 函數
中,n 為建立結點的數目, num為待刪結點的數據域值;head 為指向鏈表的頭指針,pnum
為指向待插結點的指針。 main 函數中各行的意義是:
第六行輸入所建鏈表的結點數;
第七行調 creat 函數建立鏈表并把頭指針返回給 head;
第八行調 print 函數輸出鏈表;
第十行輸入待刪結點的學號;
第十一行調 delete 函數刪除一個結點;
第十二行調 print 函數輸出鏈表;
第十四行調 malloc 函數分配一個結點的內存空間,  并把其地址賦予 pnum;
第十五行輸入待插入結點的數據域值;
第十六行調 insert 函數插入 pnum所指的結點;
第十七行再次調 print 函數輸出鏈表。
 
    從運行結果看,首先建立起 3 個結點的鏈表,并輸出其值;再刪 103 號結點,只剩下
105,108 號結點;又輸入 106 號結點數據,  插入后鏈表中的結點為 105,106,108。聯合“聯
合”也是一種構造類型的數據結構。  在一個“聯合”內可以定義多種不同的數據類型,  一個
被說明為該“聯合”類型的變量中,允許裝入該“聯合”所定義的任何一種數據。  這在前面的
各種數據類型中都是辦不到的。例如,  定義為整型的變量只能裝入整型數據,定義為實型
的變量只能賦予實型數據。
 
    在實際問題中有很多這樣的例子。  例如在學校的教師和學生中填寫以下表格:  姓  名
年  齡  職  業  單位 “職業”一項可分為“教師”和“學生”兩類。 對“單位”一項學生應填入班級
總計164頁  當前為第131 頁
編號,教師應填入某系某教研室。  班級可用整型量表示,教研室只能用字符類型。  要求把
這兩種類型不同的數據都填入“單位”這個變量中,  就必須把“單位”定義為包含整型和字符
型數組這兩種類型的“聯合”。 
 
   “聯合”與“結構”有一些相似之處。但兩者有本質上的不同。在結構中各成員有各自的內
存空間,  一個結構變量的總長度是各成員長度之和。而在“聯合”中,各成員共享一段內存
空間,  一個聯合變量的長度等于各成員中最長的長度。應該說明的是,  這里所謂的共享不
是指把多個成員同時裝入一個聯合變量內,  而是指該聯合變量可被賦予任一成員值,但每
次只能賦一種值,  賦入新值則沖去舊值。如前面介紹的“單位”變量,  如定義為一個可裝入
“班級”或“教研室”的聯合后,就允許賦予整型值(班級)或字符串(教研室)。要么賦予整型
值,要么賦予字符串,不能把兩者同時賦予它。聯合類型的定義和聯合變量的說明一個聯合
類型必須經過定義之后,  才能把變量說明為該聯合類型。
一、聯合的定義
 
定義一個聯合類型的一般形式為: 
union  聯合名 

成員表 
};
成員表中含有若干成員,成員的一般形式為:  類型說明符  成員名  成員名的命名應符合標
識符的規定。
例如: 
union perdata
{
int class;
char office[10];
};
    定義了一個名為 perdata 的聯合類型,它含有兩個成員,一個為整型,成員名為 class;
另一個為字符數組,數組名為office。聯合定義之后,即可進行聯合變量說明,被說明為perdata
類型的變量,可以存放整型量 class或存放字符數組 office。
 
二、聯合變量的說明
 
    聯合變量的說明和結構變量的說明方式相同,  也有三種形式。即先定義,再說明;定
義同時說明和直接說明。以 perdata類型為例,說明如下: 
union perdata
{
int class;
char officae[10];
};
union perdata a,b; /*說明 a,b 為perdata類型*/
或者可同時說明為: 
union perdata
{ int class;
總計164頁  當前為第132 頁
char office[10]; }a,b;或直接說明為: union
{ int class;
char office[10]; }a,b  
經說明后的 a,b 變量均為perdata 類型。  它們的內存分配示意圖如圖 7—8 所示。a,b 變量的
長度應等于 perdata  的成員中最長的長度,  即等于
office 數組的長度,共 10 個字節。從圖中可見,a,b 變量如賦予整型值時,只使用了 2 個字
節,而賦予字符數組時,可用 10 個字節。
 
聯合變量的賦值和使用
 
    對聯合變量的賦值,使用都只能是對變量的成員進行。  聯合變量的成員表示為:  聯合
變量名.成員名  例如,a 被說明為 perdata 類型的變量之后,可使用 a.class  a.office 不允許
只用聯合變量名作賦值或其它操作。  也不允許對聯合變量作初始化賦值,賦值只能在程序
中進行。還要再強調說明的是,一個聯合變量,  每次只能賦予一個成員值。換句話說,一
個聯合變量的值就是聯合變員的某一個成員值。
[例 7.15]設有一個教師與學生通用的表格,教師數據有姓名,年齡,職業,教研室四項。學
生有姓名,年齡,職業,班級四項。
編程輸入人員數據,  再以表格輸出。
main()
{
struct
{
char name[10];
int age;
char job;
union
{
int class;
char office[10];
} depa;
}body[2];
int n,i;
for(i=0;i<2;i++)
{
printf("input name,age,job and department\n");
scanf("%s %d %c",body[i].name,&body[i].age,&body[i].job);
if(body[i].job=='s')
scanf("%d",&body[i].depa.class);
else
scanf("%s",body[i].depa.office);
}
printf("name\tage job class/office\n");
for(i=0;i<2;i++)
{
總計164頁  當前為第133 頁
if(body[i].job=='s')
printf("%s\t%3d %3c %d\n",body[i].name,body[i].age
,body[i].job,body[i].depa.class);
else
printf("%s\t%3d %3c %s\n",body[i].name,body[i].age,
body[i].job,body[i].depa.office);
}
}
    本例程序用一個結構數組 body 來存放人員數據,  該結構共有四個成員。其中成員項
depa 是一個聯合類型, 這個聯合又由兩個成員組成,一個為整型量 class,一個為字符數組
office。在程序的第一個 for語句中,輸入人員的各項數據,先輸入結構的前三個成員 name,age
和 job,然后判別 job 成員項,如為"s"則對聯合 depa·class 輸入(對學生賦班級編號)否則對
depa·office 輸入(對教師賦教研組名)。
 
  在用 scanf語句輸入時要注意,凡為數組類型的成員,無論是結構成員還是聯合成員,
在該項前不能再加"&"運算符。如程序第 18 行中
body[i].name 是一個數組類型,第 22 行中的 body[i].depa.office 也是數組類型,因此在這兩
項之間不能加"&"運算符。程序中的第二個 for語句用于輸出各成員項的值:
 
本章小結
 
1.  結構和聯合是兩種構造類型數據,是用戶定義新數據類型的重要手段。結構和聯合有很
多的相似之處,它們都由成員組成。成員可以具有不同的數據類型。成員的表示方法相同。
都可用三種方式作變量說明。
 
2.  在結構中,各成員都占有自己的內存空間,它們是同時存在的。一個結構變量的總長度
等于所有成員長度之和。在聯合中,所有成員不能同時占用它的內存空間,它們不能同時存
在。聯合變量的長度等于最長的成員的長度。
 
3. “.”是成員運算符,可用它表示成員項,成員還可用“->”運算符來表示。
 
4.  結構變量可以作為函數參數,函數也可返回指向結構的指針變量。而聯合變量不能作為
函數參數,函數也不能返回指向聯合的指針變量。但可以使用指向聯合變量的指針,也可使
用聯合數組。
 
5.  結構定義允許嵌套,結構中也可用聯合作為成員,形成結構和聯合的嵌套。
 
6.  鏈表是一種重要的數據結構,它便于實現動態的存儲分配。本章介紹是單向鏈表,還可
組成雙向鏈表,循環鏈表等。
總計164頁  當前為第134 頁
C語言教程第八章:枚舉,位運算
枚舉
 
    在實際問題中, 有些變量的取值被限定在一個有限的范圍內。例如,一個星期內只有
七天,一年只有十二個月, 一個班每周有六門課程等等。如果把這些量說明為整型, 字符
型或其它類型顯然是不妥當的。 為此,C語言提供了一種稱為“枚舉”的類型。在“枚舉”
類型的定義中列舉出所有可能的取值, 被說明為該“枚舉”類型的變量取值不能超過定義
的范圍。應該說明的是, 枚舉類型是一種基本數據類型,而不是一種構造類型, 因為它不
能再分解為任何基本類型。
 
枚舉類型的定義和枚舉變量的說明
 
一、枚舉的定義枚舉類型定義的一般形式為: 
enum 枚舉名 
{ 枚舉值表 };
在枚舉值表中應羅列出所有可用值。這些值也稱為枚舉元素。
例如: enum weekday
{ sun,mou,tue,wed,thu,fri,sat };
該枚舉名為 weekday,枚舉值共有 7個,即一周中的七天。 凡被說明為 weekday 類型變量
的取值只能是七天中的某一天。
 
二、枚舉變量的說明 如同結構和聯合一樣,枚舉變量也可用不同的方式說明, 即先定義后
說明,同時定義說明或直接說明。設有變量 a,b,c 被說明為上述的 weekday,可采用下述任
一種方式: 
enum weekday
{
......
};
enum weekday a,b,c;或者為: enum weekday
{
......
}a,b,c;或者為: enum
{
......
}a,b,c;
枚舉類型變量的賦值和使用
 
枚舉類型在使用中有以下規定:
1.  枚舉值是常量,不是變量。不能在程序中用賦值語句再對它賦值。例如對枚舉 weekday
總計164頁  當前為第135 頁
的元素再作以下賦值: sun=5;mon=2;sun=mon;  都是錯誤的。
 
2.  枚舉元素本身由系統定義了一個表示序號的數值,從 0 開始順序定義為 0,1,2…。如
在 weekday中,sun 值為 0,mon 值為 1, …,sat 值為 6。
main(){
enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
}
3.  只能把枚舉值賦予枚舉變量,不能把元素的數值直接賦予枚舉變量。如: a=sum;b=mon;
是正確的。而: a=0;b=1;  是錯誤的。如一定要把數值賦予枚舉變量,則必須用強制類型轉
換,如: a=(enum weekday)2;其意義是將順序號為 2 的枚舉元素賦予枚舉變量 a,相當于:
a=tue;  還應該說明的是枚舉元素不是字符常量也不是字符串常量,  使用時不要加單、雙引
號。
main(){
enum body
{ a,b,c,d } month[31],j;
int i;
j=a;
for(i=1;i<=30;i++){
month[i]=j;
j++;
if (j>d) j=a;
}
for(i=1;i<=30;i++){
switch(month[i])
{
case a:printf(" %2d %c\t",i,'a'); break;
case b:printf(" %2d %c\t",i,'b'); break;
case c:printf(" %2d %c\t",i,'c'); break;
case d:printf(" %2d %c\t",i,'d'); break;
default:break;
}
}
printf("\n");
}
位運算
    前面介紹的各種運算都是以字節作為最基本位進行的。 但在很多系統程序中常要求在
位(bit)一級進行運算或處理。C語言提供了位運算的功能, 這使得C語言也能像匯編語言
總計164頁  當前為第136 頁
一樣用來編寫系統程序。
一、位運算符C語言提供了六種位運算符:
& 按位與
| 按位或
^ 按位異或
~ 取反
<< 左移
>> 右移
 
1. 按位與運算 按位與運算符"&"是雙目運算符。其功能是參與運算的兩數各對應的二進位
相與。只有對應的兩個二進位均為 1 時,結果位才為 1 ,否則為 0。參與運算的數以補碼方
式出現。
例如:9&5 可寫算式如下: 00001001 (9 的二進制補碼)&00000101 (5 的二進制補碼)  000
00001 (1 的二進制補碼)可見 9&5=1。
 
    按位與運算通常用來對某些位清 0 或保留某些位。例如把 a 的高八位清 0 , 保留低
八位, 可作 a&255 運算 ( 255 的二進制數為 0000000011111111)。
main(){
int a=9,b=5,c;
c=a&b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
 
2. 按位或運算 按位或運算符“|”是雙目運算符。其功能是參與運算的兩數各對應的二進
位相或。只要對應的二個二進位有一個為 1 時,結果位就為 1。參與運算的兩個數均以補碼
出現。
例如:9|5 可寫算式如下: 00001001|00000101
00001101 (十進制為 13)可見 9|5=13
main(){
int a=9,b=5,c;
c=a|b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
 
3. 按位異或運算 按位異或運算符“^”是雙目運算符。其功能是參與運算的兩數各對應的
二進位相異或,當兩對應的二進位相異時,結果為 1。參與運算數仍以補碼出現,例如 9^5
可寫成算式如下: 00001001^00000101 00001100 (十進制為12)
main(){
int a=9;
a=a^15;
printf("a=%d\n",a);
}
 
總計164頁  當前為第137 頁
4. 求反運算 求反運算符~為單目運算符,具有右結合性。 其功能是對參與運算的數的各
二進位按位求反。例如~9 的運算為: ~(0000000000001001)結果為:1111111111110110
 
5. 左移運算 左移運算符“<<”是雙目運算符。其功能把“<< ”左邊的運算數的各二進位
全部左移若干位,由“<<”右邊的數指定移動的位數,
高位丟棄,低位補 0。例如: a<<4 指把 a 的各二進位向左移動 4 位。如 a=00000011(十進
制 3),左移4 位后為00110000(十進制 48)。6. 右移運算 右移運算符“>>”是雙目運算符。
其功能是把“>> ”左邊的運算數的各二進位全部右移若干位,“>>”右邊的數指定移動的
位數。 
例如:設 a=15,a>>2  表示把 000001111 右移為00000011(十進制 3)。 應該說明的是,對
于有符號數,在右移時,符號位將隨同移動。當為正數時, 最高位補0,而為負數時,符
號位為 1,最高位是補 0或是補 1 取決于編譯系統的規定。Turbo C 和很多系統規定為補 1。 
main(){
unsigned a,b;
printf("input a number: ");
scanf("%d",&a);
b=a>>5;
b=b&15;
printf("a=%d\tb=%d\n",a,b);
}
請再看一例!
main(){
char a='a',b='b';
int p,c,d;
p=a;
p=(p<<8)|b;
d=p&0xff;
c=(p&0xff00)>>8;
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);
}
位域
 
有些信息在存儲時,并不需要占用一個完整的字節,  而只需占幾個或一個二進制位。例如
在存放一個開關量時,只有 0 和1 兩種狀態,  用一位二進位即可。為了節省存儲空間,并
使處理簡便,C語言又提供了一種數據結構,稱為“位域”或“位段”。所謂“位域”是把一個字
節中的二進位劃分為幾個不同的區域,  并說明每個區域的位數。每個域有一個域名,允許
在程序中按域名進行操作。   這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。
一、位域的定義和位域變量的說明位域定義與結構定義相仿,其形式為: 
struct  位域結構名 
{  位域列表 };
其中位域列表的形式為:  類型說明符  位域名:位域長度 
例如: 
struct bs
總計164頁  當前為第138 頁
{
int a:8;
int b:2;
int c:6;
};
位域變量的說明與結構變量說明的方式相同。  可采用先定義后說明,同時定義說明或者直
接說明這三種方式。例如: 
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
說明 data 為 bs 變量,共占兩個字節。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。
對于位域的定義尚有以下幾點說明:
 
1.  一個位域必須存儲在同一個字節中,不能跨兩個字節。如一個字節所剩空間不夠存放另
一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如: 
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*從下一單元開始存放*/
unsigned c:4
}
在這個位域定義中,a 占第一字節的 4 位,后 4 位填 0 表示不使用,b 從第二字節開始,占
用 4 位,c 占用 4 位。
 
2.  由于位域不允許跨兩個字節,因此位域的長度不能大于一個字節的長度,也就是說不能
超過 8 位二進位。
 
3.  位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:  
struct k
{
int a:1
int :2 /*該2位不能使用*/
int b:3
int c:2
};
從以上分析可以看出,位域在本質上就是一種結構類型, 不過其成員是按二進位分配的。
二、位域的使用位域的使用和結構成員的使用相同,其一般形式為:  位域變量名·位域名  位
域允許用各種格式輸出。
main(){
struct bs
總計164頁  當前為第139 頁
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);

上例程序中定義了位域結構 bs,三個位域為 a,b,c。說明了 bs類型的變量 bit 和指向bs類型
的指針變量 pbit。這表示位域也是可以使用指針的。
程序的 9、10、11 三行分別給三個位域賦值。(  應注意賦值不能超過該位域的允許范圍)程
序第12行以整型量格式輸出三個域的內容。第13行把位域變量bit的地址送給指針變量pbit。
第 14 行用指針方式給位域 a 重新賦值,賦為 0。第 15 行使用了復合的位運算符"&=",  該
行相當于: pbit->b=pbit->b&3 位域 b 中原有值為 7,與 3 作按位與運算的結果為
3(111&011=011,十進制值為 3)。同樣,程序第 16 行中使用了復合位運算"|=",  相當于:
pbit->c=pbit->c|1 其結果為 15。程序第 17 行用指針方式輸出了這三個域的值。
 
類型定義符typedef 
 
C語言不僅提供了豐富的數據類型,而且還允許由用戶自己定義類型說明符,也就是說允許
由用戶為數據類型取“別名”。  類型定義符 typedef 即可用來完成此功能。例如,有整型量
a,b,其說明如下: int aa,b;  其中 int 是整型變量的類型說明符。int 的完整寫法為 integer,
為了增加程序的可讀性,可把整型說明符用 typedef定義為:  typedef int INTEGER 這以后
就可用 INTEGER 來代替 int 作整型變量的類型說明了。   例如:  INTEGER a,b;它等效于:  int
a,b;  用 typedef 定義數組、指針、結構等類型將帶來很大的方便,不僅使程序書寫簡單而且
使意義更為明確,因而增強了可讀性。例如:
typedef char NAME[20];  表示 NAME 是字符數組類型,數組長度為 20。
然后可用 NAME  說明變量,如:  NAME a1,a2,s1,s2; 完全等效于:  char
a1[20],a2[20],s1[20],s2[20]
又如: 
typedef struct stu{ char name[20];
int age;
char sex;
} STU;
定義 STU表示 stu 的結構類型,然后可用 STU來說明結構變量: STU body1,body2;
typedef 定義的一般形式為: typedef  原類型名  新類型名  其中原類型名中含有定義部分,
新類型名一般用大寫表示,  以
總計164頁  當前為第140 頁
便于區別。在有時也可用宏定義來代替 typedef 的功能,但是宏定義是由預處理完成的,而
typedef則是在編譯時完成的,后者更為靈活方便。
 
本章小結
 
1.  枚舉是一種基本數據類型。枚舉變量的取值是有限的,枚舉元素是常量,不是變量。
 
2.  枚舉變量通常由賦值語句賦值,而不由動態輸入賦值。枚舉元素雖可由系統或用戶定義
一個順序值,但枚舉元素和整數并不相同,它們屬于不同的類型。因此,也不能用 printf語
句來輸出元素值(可輸出順序值)。
 
3.  位運算是C語言的一種特殊運算功能,  它是以二進制位為單位進行運算的。位運算符只
有邏輯運算和移位運算兩類。位運算符可以與賦值符一起組成復合賦值符。如
&=,|=,^=,>>=,<<=等。
 
4.  利用位運算可以完成匯編語言的某些功能,如置位,位清零,移位等。還可進行數據的
壓縮存儲和并行運算。
 
5.  位域在本質上也是結構類型,不過它的成員按二進制位分配內存。其定義、說明及使用
的方式都與結構相同。
 
6.  位域提供了一種手段,使得可在高級語言中實現數據的壓縮,節省了存儲空間,同時也
提高了程序的效率。
 
7.  類型定義 typedef  向用戶提供了一種自定義類型說明符的手段,照顧了用戶編程使用詞匯
的習慣,又增加了程序的可讀性。
總計164頁  當前為第141 頁
C語言教程第九章:預處理
預處理
 
概述
    在前面各章中,已多次使用過以“#”號開頭的預處理命令。如包含命令# include,宏
定義命令# define 等。在源程序中這些命令都放在函數之外, 而且一般都放在源文件的前
面,它們稱為預處理部分。
 
    所謂預處理是指在進行編譯的第一遍掃描(詞法掃描和語法分析)之前所作的工作。預處
理是C語言的一個重要功能, 它由預處理程序負責完成。當對一個源文件進行編譯時,
系統將自動引用預處理程序對源程序中的預處理部分作處理, 處理完畢自動進入對源程序
的編譯。
 
    C語言提供了多種預處理功能,如宏定義、文件包含、 條件編譯等。合理地使用預處
理功能編寫的程序便于閱讀、修改、 移植和調試,也有利于模塊化程序設計。本章介紹常
用的幾種預處理功能。
 
宏定義
    在C語言源程序中允許用一個標識符來表示一個字符串, 稱為“宏”。被定義為“宏”
的標識符稱為“宏名”。在編譯預處理時,對程序中所有出現的“宏名”,都用宏定義中的
字符串去代換, 這稱為“宏代換”或“宏展開”。
 
    宏定義是由源程序中的宏定義命令完成的。 宏代換是由預處理程序自動完成的。在C
語言中,“宏”分為有參數和無參數兩種。 下面分別討論這兩種“宏”的定義和調用。
 
無參宏定義
    無參宏的宏名后不帶參數。其定義的一般形式為:  #define 標識符 字符串 其中的 “#”
表示這是一條預處理命令。凡是以“#”開頭的均為預處理命令。“define”為宏定義命令。
 “標識符”為所定義的宏名。“字符串”可以是常數、表達式、格式串等。在前面介紹過
的符號常量的定義就是一種無參宏定義。 此外,常對程序中反復使用的表達式進行宏定義。
例如: # define M (y*y+3*y) 定義M 表達式(y*y+3*y)。在編寫源程序時,所有的(y*y+3
*y)都可由M代替,而對源程序作編譯時,將先由預處理程序進行宏代換,即用(y*y+3*y)
表達式去置換所有的宏名 M,然后再進行編譯。
#define M (y*y+3*y)
main(){
int s,y;
printf("input a number: ");
scanf("%d",&y);
s=3*M+4*M+5*M;
printf("s=%d\n",s);
總計164頁  當前為第142 頁
}
    上例程序中首先進行宏定義, 定義 M 表達式(y*y+3*y),在s= 3*M+4*M+5* M 中作了宏調
用。在預處理時經宏展開后該語句變為:s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意
的是,在宏定義中表達式(y*y+3*y)兩邊的括號不能少。否則會發生錯誤。
    當作以下定義后: #difine M y*y+3*y 在宏展開時將得到下述語句: s=3*y*y+3*y+4*
y*y+3*y+5*y*y+3*y;這相當于; 3y  2+3y+4y  2+3y+5y  2+3y;顯然與原題意要求不
符。計算結果當然是錯誤的。 因此在作宏定義時必須十分注意。應保證在宏代換之后不發
生錯誤。對于宏定義還要說明以下幾點:
 
1. 宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡
單的代換,字符串中可以含任何字符,可以是常數,也可以是表達式,預處理程序對它不作
任何檢查。如有錯誤,只能在編譯已被宏展開后的源程序時發現。
 
2. 宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。
 
3. 宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結 束。如要終止其作用域
可使用# undef 命令,例如: # define PI 3.14159
main() 
{
……
}
# undef PIPI 的作用域
f1()
....表示PI只在 main 函數中有效,在 f1 中無效。
4. 宏名在源程序中若用引號括起來,則預處理程序不對其作宏代換。
#define OK 100
main()
{
printf("OK");
printf("\n");
}
上例中定義宏名 OK 表示100,但在 printf 語句中OK 被引號括起來,因此不作宏代換。程
序的運行結果為:OK 這表示把“OK”當字符串處理。
 
5. 宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名。在宏展開時由預處理
程序層層代換。例如: #define PI 3.1415926
#define S PI*y*y /* PI是已定義的宏名*/對語句: printf("%f",s);在宏代換后變為:
printf("%f",3.1415926*y*y);
 
6. 習慣上宏名用大寫字母表示,以便于與變量區別。但也允許用小寫字母。
 
7. 可用宏定義表示數據類型,使書寫方便。例如: #define STU struct stu 在程序中可
用 STU 作變量說明: STU body[5],*p;#define INTEGER int  在程序中即可用 INTEGER 作
總計164頁  當前為第143 頁
整型變量說明:  INTEGER a,b; 應注意用宏定義表示數據類型和用 typedef 定義數據說明符
的區別。宏定義只是簡單的字符串代換,是在預處理完成的, 而 typedef是在編譯時處理的,
它不是作簡單的代換, 而是對類型說明符重新命名。被命名的標識符具有類型定義說明的
功能。請看下面的例子: #define PIN1 int*  typedef (int*) PIN2;從形式上看這兩者相
似, 但在實際使用中卻不相同。下面用 PIN1,PIN2 說明變量時就可以看出它們的區別:
PIN1 a,b;在宏代換后變成 int *a,b;表示a 是指向整型的指針變量,而 b 是整型變量。然
而:PIN2 a,b;表示a,b都是指向整型的指針變量。因為 PIN2 是一個類型說明符。由這個例
子可見,宏定義雖然也可表示數據類型, 但畢竟是作字符
代換。在使用時要分外小心,以避出錯。
 
8. 對“輸出格式”作宏定義,可以減少書寫麻煩。例 9.3 中就采用了這種方法。
#define P printf
#define D "%d\n"
#define F "%f\n"
main(){
int a=5, c=8, e=11;
float b=3.8, d=9.7, f=21.08;
P(D F,a,b);
P(D F,c,d);
P(D F,e,f);
}
帶參宏定義
 
    C語言允許宏帶有參數。在宏定義中的參數稱為形式參數,  在宏調用中的參數稱為實
際參數。對帶參數的宏,在調用中,不僅要宏展開,  而且要用實參去代換形參。
 
    帶參宏定義的一般形式為: #define  宏名(形參表)  字符串  在字符串中含有各個形參。
帶參宏調用的一般形式為:  宏名(實參表); 
例如: 
#define M(y) y*y+3*y /*宏定義*/
:
k=M(5); /*宏調用*/
:  在宏調用時,用實參 5去代替形參 y,  經預處理宏展開后的語句
為: k=5*5+3*5
#define MAX(a,b) (a>b)?a:b
main(){
int x,y,max;
printf("input two numbers: ");
scanf("%d%d",&x,&y);
max=MAX(x,y);
printf("max=%d\n",max);
}
    上例程序的第一行進行帶參宏定義,用宏名 MAX 表示條件表達式(a>b)?a:b,形參 a,b
總計164頁  當前為第144 頁
均出現在條件表達式中。程序第七行 max=MAX(x,
y)為宏調用,實參 x,y,將代換形參 a,b。宏展開后該語句為: max=(x>y)?x:y;用于計算 x,y
中的大數。對于帶參的宏定義有以下問題需要說明:
 
1.  帶參宏定義中,宏名和形參表之間不能有空格出現。
例如把: #define MAX(a,b) (a>b)?a:b寫為:  #define MAX (a,b) (a>b)?a:b  將被認為是無參
宏定義,宏名 MAX代表字符串 (a,b)(a>b)?a:b。
宏展開時,宏調用語句:  max=MAX(x,y);將變為:  max=(a,b)(a>b)?a:b(x,y);這顯然是錯誤的。 
 
2.  在帶參宏定義中,形式參數不分配內存單元,因此不必作類型定義。而宏調用中的實參
有具體的值。要用它們去代換形參,因此必須作類型說明。這是與函數中的情況不同的。在
函數中,形參和實參是兩個不同的量,各有自己的作用域,調用時要把實參值賦予形參,進
行“值傳遞”。而在帶參宏中,只是符號代換,不存在值傳遞的問題。
 
3.  在宏定義中的形參是標識符,而宏調用中的實參可以是表達式。
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
    上例中第一行為宏定義,形參為 y。程序第七行宏調用中實參為 a+1,是一個表達式,
在宏展開時,用 a+1 代換 y,再用(y)*(y)  代換 SQ,得到如下語句: sq=(a+1)*(a+1);  這與
函數的調用是不同的, 函數調用時要把實參表達式的值求出來再賦予形參。  而宏代換中對
實參表達式不作計算直接地照原樣代換。
 
4.  在宏定義中,字符串內的形參通常要用括號括起來以避免出錯。  在上例中的宏定義中
(y)*(y)表達式的 y都用括號括起來,因此結果是正確的。如果去掉括號,把程序改為以下形
式:
#define SQ(y) y*y
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
運行結果為:input a number:3
sq=7  同樣輸入 3,但結果卻是不一樣的。問題在哪里呢? 這是由于代換只作符號代換而不
作其它處理而造成的。  宏代換后將得到以下語句:  sq=a+1*a+1;  由于a為 3 故 sq 的值為 7。
這顯然與題意相違,因此參數兩邊的括號是不能少的。即使在參數兩邊加括號還是不夠的,
請看下面程序:
總計164頁  當前為第145 頁
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
    本程序與前例相比,只把宏調用語句改為: sq=160/SQ(a+1); 運行本程序如輸入值仍為
3 時,希望結果為 10。但實際運行的結果如下:input a number:3  sq=160 為什么會得這樣的
結果呢?分析宏調用語句,在宏代換之后變為: sq=160/(a+1)*(a+1);a 為 3時,由于“/”和“*”
運算符優先級和結合性相同,  則先作 160/(3+1)得 40,再作40*(3+1)最后得 160。為了得到
正確答案應在宏定義中的整個字符串外加括號,  程序修改如下
#define SQ(y) ((y)*(y))
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
以上討論說明,對于宏定義不僅應在參數兩側加括號,  也應在整個字符串外加括號。
 
5.  帶參的宏和帶參函數很相似,但有本質上的不同,除上面已談到的各點外,把同一表達
式用函數處理與用宏處理兩者的結果有可能是不同的。main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));
}
SQ(int y)
{
return((y)*(y));
}#define SQ(y) ((y)*(y))
main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));

    在上例中函數名為 SQ,形參為 Y,函數體表達式為((y)*(y))。在例 9.6 中宏名為 SQ,
形參也為 y,字符串表達式為(y)*(y))。  兩例是相同的。例 9.6的函數調用為 SQ(i++),例 9.7
的宏調用為 SQ(i++),實參也是相同的。從輸出結果來看,卻大不相同。分析如下:在例 9.6
中,函數調用是把實參 i值傳給形參 y后自增 1。  然后輸出函數值。因而要循環 5 次。輸出
1~5 的平方值。而在例 9.7 中宏調用時,只作代換。SQ(i++)被代換為((i++)*(i++))。在第一
次循環時,由于 i 等于1,其計算過程為:表達式中前一個 i初值為 1,然后 i 自增1 變為 2,
總計164頁  當前為第146 頁
因此表達式中第 2 個i 初值為 2,兩相乘的結果也為 2,然后i值再自增 1,得 3。在第二次
循環時,i 值已有初值為 3,因此表達式中前一個 i為 3,后一個 i 為4, 乘積為 12,然后 i
再自增 1 變為 5。進入第三次循環,由于 i  值已為 5,所以這將是最后一次循環。計算表達
式的值為 5*6 等于 30。i值再自增 1 變為 6,不再滿足循環條件,停止循環。從以上分析可
以看出函數調用和宏調用二者在形式上相似, 在本質上是完全不同的。
 
6.  宏定義也可用來定義多個語句,在宏調用時,把這些語句又代換到源程序內。看下面的
例子。
#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
main(){
int l=3,w=4,h=5,sa,sb,sc,vv;
SSSV(sa,sb,sc,vv);
printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);
}
    程序第一行為宏定義,用宏名 SSSV表示 4 個賦值語句,4  個形參分別為 4 個賦值符左
部的變量。在宏調用時,把 4  個語句展開并用實參代替形參。使計算結果送入實參之中。
文件包含 
 
    文件包含是 C 預處理程序的另一個重要功能。文件包含命令行的一般形式為: #inclu
de"文件名" 在前面我們已多次用此命令包含過庫函數的頭文件。例如: 
#include"stdio.h"
#include"math.h" 
文件包含命令的功能是把指定的文件插入該命令行位置取代該命令行, 從而把指定的文件
和當前的源程序文件連成一個源文件。在程序設計中,文件包含是很有用的。 一個大的程
序可以分為多個模塊,由多個程序員分別編程。 有些公用的符號常量或宏定義等可單獨組
成一個文件, 在其它文件的開頭用包含命令包含該文件即可使用。這樣,可避免在每個文
件開頭都去書寫那些公用量, 從而節省時間,并減少出錯。
 
對文件包含命令還要說明以下幾點:
1. 包含命令中的文件名可以用雙引號括起來,也可以用尖括號括起來。例如以下寫法都是
允許的: #include"stdio.h"  #include<math.h> 但是這兩種形式是有區別的:使用尖括
號表示在包含文件目錄中去查找(包含目錄是由用戶在設置環境時設置的), 而不在源文件
目錄去查找; 使用雙引號則表示首先在當前的源文件目錄中查找,若未找到才到包含目錄
中去查找。 用戶編程時可根據自己文件所在的目錄來選擇某一種命令形式。
 
2. 一個 include 命令只能指定一個被包含文件, 若有多個文件要包含,則需用多個 inclu
de 命令。3. 文件包含允許嵌套,即在一個被包含的文件中又可以包含另一個文件。
 
條件編譯
 
預處理程序提供了條件編譯的功能。 可以按不同的條件去編譯不同的程序部分,因而產生
不同的目標代碼文件。 這對于程序的移植和調試是很有用的。 條件編譯有三種形式,下面
分別介紹:
總計164頁  當前為第147 頁
1. 第一種形式: 
#ifdef 標識符 
程序段 1 
#else 
程序段 2 
#endif 
它的功能是,如果標識符已被 #define 命令定義過則對程序段 1 進行編譯;否則對程序段 2
進行編譯。如果沒有程序段 2(它為空),本格式中的#else 可以沒有, 即可以寫為: 
#ifdef 標識符 
程序段 #endif 
#define NUM ok
main(){
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
#ifdef NUM
printf("Number=%d\nScore=%f\n",ps->num,ps->score);
#else
printf("Name=%s\nSex=%c\n",ps->name,ps->sex);
#endif
free(ps);

    由于在程序的第 16行插入了條件編譯預處理命令,  因此要根據 NUM 是否被定義過來決
定編譯那一個 printf 語句。而在程序的第一行已對 NUM 作過宏定義,因此應對第一個 prin
tf 語句作編譯故運行結果是輸出了學號和成績。在程序的第一行宏定義中,定義 NUM 表示
字符串 OK,其實也可以為任何字符串,甚至不給出任何字符串,寫為: #define NUM 也具
有同樣的意義。 只有取消程序的第一行才會去編譯第二個 printf 語句。讀者可上機試作。 
2.  第二種形式: 
#ifndef  標識符 
程序段 1 
#else 
程序段 2 
#endif 
總計164頁  當前為第148 頁
與第一種形式的區別是將“ifdef”改為“ifndef”。它的功能是,如果標識符未被#define 命令定
義過則對程序段 1 進行編譯,  否則對程序段 2進行編譯。這與第一種形式的功能正相反。 
 
3.  第三種形式: 
#if  常量表達式 
程序段 1 
#else 
程序段 2 
#endif 
它的功能是,如常量表達式的值為真(非 0),則對程序段 1 進行編譯,否則對程序段 2 進行
編譯。因此可以使程序在不同條件下,完成不同的功能
#define R 1
main(){
float c,r,s;
printf ("input a number: ");
scanf("%f",&c);
#if R
r=3.14159*c*c;
printf("area of round is: %f\n",r);
#else
s=c*c;
printf("area of square is: %f\n",s);
#endif
}
    本例中采用了第三種形式的條件編譯。在程序第一行宏定義中,定義 R 為1,因此在條
件編譯時,常量表達式的值為真,  故計算并輸出圓面積。上面介紹的條件編譯當然也可以
用條件語句來實現。  但是用條件語句將會對整個源程序進行編譯,生成的目標代碼程序很
長,而采用條件編譯,則根據條件只編譯其中的程序段 1 或程序段 2,  生成的目標程序較
短。如果條件選擇的程序段很長, 采用條件編譯的方法是十分必要的。
 
本章小結
1.  預處理功能是C語言特有的功能,它是在對源程序正式編譯前由預處理程序完成的。程
序員在程序中用預處理命令來調用這些功能。
 
2.  宏定義是用一個標識符來表示一個字符串,這個字符串可以是常量、變量或表達式。在
宏調用中將用該字符串代換宏名。
 
3.  宏定義可以帶有參數,宏調用時是以實參代換形參。而不是“值傳送”。
 
4.  為了避免宏代換時發生錯誤,宏定義中的字符串應加括號,字符串中出現的形式參數兩
邊也應加括號。
 
5.  文件包含是預處理的一個重要功能,它可用來把多個源文件連接成一個源文件進行編譯,
結果將生成一個目標文件。
總計164頁  當前為第149 頁
 
 
6.  條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標程序較短,從而減少了
內存的開銷并提高了程序的效率。
 
7.  使用預處理功能便于程序的修改、閱讀、移植和調試,也便于實現模塊化程序設計。
總計164頁  當前為第150 頁
文件
 
文件的基本概念
  所謂“文件”是指一組相關數據的有序集合。  這個數據集有一個名稱,叫做文件名。  實
際上在前面的各章中我們已經多次使用了文件,例如源程序文件、目標文件、可執行文件、
庫文件 (頭文件)等。文件通常是駐留在外部介質(如磁盤等)上的,  在使用時才調入內存中
來。從不同的角度可對文件作不同的分類。從用戶的角度看,文件可分為普通文件和設備文
件兩種。
 
    普通文件是指駐留在磁盤或其它外部介質上的一個有序數據集,可以是源文件、目標文
件、可執行程序;  也可以是一組待輸入處理的原始數據,或者是一組輸出的結果。對于源
文件、目標文件、  可執行程序可以稱作程序文件,對輸入輸出數據可稱作數據文件。
 
    設備文件是指與主機相聯的各種外部設備,如顯示器、打印機、鍵盤等。在操作系統中,
把外部設備也看作是一個文件來進行管理,把它們的輸入、輸出等同于對磁盤文件的讀和寫。 
通常把顯示器定義為標準輸出文件,  一般情況下在屏幕上顯示有關信息就是向標準輸出文
件輸出。如前面經常使用的 printf,putchar  函數就是這類輸出。鍵盤通常被指定標準的輸入
文件,   從鍵盤上輸入就意味著從標準輸入文件上輸入數據。 scanf,getchar函數就屬于這類輸
入。 
 
    從文件編碼的方式來看,文件可分為 ASCII 碼文件和二進制碼文件兩種。
 
   ASCII 文件也稱為文本文件,這種文件在磁盤中存放時每個字符對應一個字節,用于存
放對應的 ASCII碼。例如,數 5678 的存儲形式為:
ASC 碼:    00110101 00110110 00110111 00111000
      ↓       ↓     ↓      ↓
十進制碼: 5      6     7     8  共占用 4 個字節。ASCII碼文件可在屏幕上
按字符顯示,   例如源程序文件就是 ASCII文件,用 DOS 命令 TYPE 可顯示文件的內容。   由
于是按字符顯示,因此能讀懂文件內容。
 
    二進制文件是按二進制的編碼方式來存放文件的。  例如,  數 5678 的存儲形式為:
00010110 00101110 只占二個字節。二進制文件雖然也可在屏幕上顯示,   但其內容無法讀懂。
C 系統在處理這些文件時,并不區分類型,都看成是字符流,按字節進行處理。 輸入輸出
字符流的開始和結束只由程序控制而不受物理符號(如回車符)的控制。  因此也把這種文件
稱作“流式文件”。
 
    本章討論流式文件的打開、關閉、讀、寫、  定位等各種操作。文件指針在C語言中用
一個指針變量指向一個文件,  這個指針稱為文件指針。通過文件指針就可對它所指的文件
進行各種操作。  定義說明文件指針的一般形式為: FILE*  指針變量標識符;  其中 FILE
應為大寫,它實際上是由系統定義的一個結構,  該結構中含有文件名、文件狀態和文件當
前位置等信息。  在編寫源程序時不必關心 FILE 結構的細節。例如:FILE *fp;  表示 fp 是
指向 FILE 結構的指針變量,通過 fp  即可找存放某個文件信息的結構變量,然后按結構變
量提供的信息找到該文件,  實施對文件的操作。習慣上也籠統地把 fp稱為指向一個文件的
指針。文件的打開與關閉文件在進行讀寫操作之前要先打開,使用完畢要關閉。  所謂打開
總計164頁  當前為第151 頁
文件,實際上是建立文件的各種有關信息,  并使文件指針指向該文件,以便進行其它操作。
關閉文件則斷開指針與文件之間的聯系,也就禁止再對該文件進行操作。
 
    在C語言中,文件操作都是由庫函數來完成的。  在本章內將介紹主要的文件操作函數。 
文件打開函數fopen
 
   fopen 函數用來打開一個文件,其調用的一般形式為: 文件指針名=fopen(文件名,使
用文件方式)  其中,“文件指針名”必須是被說明為 FILE  類型的指針變量,“文件名”是被打
開文件的文件名。 “使用文件方式”是指文件的類型和操作要求。“文件名”是字符串常量或
字符串數組。例如: 
FILE *fp;
fp=("file a","r");
其意義是在當前目錄下打開文件 file a,  只允許進行“讀”操作,并使 fp指向該文件。
又如:
FILE *fphzk
fphzk=("c:\\hzk16',"rb")
其意義是打開 C 驅動器磁盤的根目錄下的文件 hzk16,  這是一個二進制文件,只允許按二
進制方式進行讀操作。兩個反斜線“\\ ”中的第一個表示轉義字符,第二個表示根目錄。使用
文件的方式共有 12 種,下面給出了它們的符號和意義。 
文件使用方式         意  義
“rt”       只讀打開一個文本文件,只允許讀數據 
“wt”       只寫打開或建立一個文本文件,只允許寫數據
“at”       追加打開一個文本文件,并在文件末尾寫數據
“rb”       只讀打開一個二進制文件,只允許讀數據
“wb”           只寫打開或建立一個二進制文件,只允許寫數據
“ab”           追加打開一個二進制文件,并在文件末尾寫數據
“rt+”        讀寫打開一個文本文件,允許讀和寫
“wt+”        讀寫打開或建立一個文本文件,允許讀寫
“at+”        讀寫打開一個文本文件,允許讀,或在文件末追加數 據
“rb+”        讀寫打開一個二進制文件,允許讀和寫 
“wb+”        讀寫打開或建立一個二進制文件,允許讀和寫
“ab+”            讀寫打開一個二進制文件,允許讀,或在文件末追加數據
 
對于文件使用方式有以下幾點說明:
1.  文件使用方式由 r,w,a,t,b,+六個字符拼成,各字符的含義是:
r(read):  讀
w(write):  寫
a(append):  追加
t(text):  文本文件,可省略不寫
b(banary):  二進制文件
+:  讀和寫
 
2.  凡用“r”打開一個文件時,該文件必須已經存在,  且只能從該文件讀出。
 
總計164頁  當前為第152 頁
3.  用“w”打開的文件只能向該文件寫入。  若打開的文件不存在,則以指定的文件名建立該
文件,若打開的文件已經存在,則將該文件刪去,重建一個新文件。
 
4.  若要向一個已存在的文件追加新的信息,只能用“a ”方式打開文件。但此時該文件必須是
存在的,否則將會出錯。
 
5.  在打開一個文件時,如果出錯,fopen 將返回一個空指針值 NULL。在程序中可以用這一
信息來判別是否完成打開文件的工作,并作相應的處理。因此常用以下程序段打開文件:
if((fp=fopen("c:\\hzk16","rb")==NULL)
{
printf("\nerror on open c:\\hzk16 file!");
getch();
exit(1);
}
    這段程序的意義是,如果返回的指針為空,表示不能打開 C 盤根目錄下的 hzk16 文件,
則給出提示信息“error on open c:\ hzk16file!”,下一行 getch()的功能是從鍵盤輸入一個字符,
但不在屏幕上顯示。在這里,該行的作用是等待,  只有當用戶從鍵盤敲任一鍵時,程序才
繼續執行, 因此用戶可利用這個等待時間閱讀出錯提示。敲鍵后執行 exit(1)退出程序。
 
6.  把一個文本文件讀入內存時,要將 ASCII 碼轉換成二進制碼,  而把文件以文本方式寫入
磁盤時,也要把二進制碼轉換成 ASCII 碼,因此文本文件的讀寫要花費較多的轉換時間。
對二進制文件的讀寫不存在這種轉換。
 
7.  標準輸入文件(鍵盤),標準輸出文件(顯示器 ),標準出錯輸出(出錯信息)是由系統打開的,
可直接使用。文件關閉函數fclose文件一旦使用完畢,應用關閉文件函數把文件關閉, 
以避免文件的數據丟失等錯誤。
fclose 函數
 
調用的一般形式是: fclose(文件指針);  例如:
fclose(fp);  正常完成關閉文件操作時, fclose 函數返回值為 0。如返回非零值則表示有錯誤發
生。文件的讀寫對文件的讀和寫是最常用的文件操作。 
 
在C語言中提供了多種文件讀寫的函數: 
·字符讀寫函數  :fgetc和 fputc
·字符串讀寫函數:fgets和 fputs
·數據塊讀寫函數:freed和 fwrite
·格式化讀寫函數:fscanf和 fprinf
 
    下面分別予以介紹。使用以上函數都要求包含頭文件 stdio.h。字符讀寫函數fgetc
和fputc字符讀寫函數是以字符(字節)為單位的讀寫函數。  每次可從文件讀出或向文
件寫入一個字符。
 
一、讀字符函數 fgetc
 
總計164頁  當前為第153 頁
   fgetc函數的功能是從指定的文件中讀一個字符,函數調用的形式為:   字符變量=fgetc(文
件指針);  例如:ch=fgetc(fp);其意義是從打開的文件 fp 中讀取一個字符并送入 ch 中。
 
  對于 fgetc 函數的使用有以下幾點說明:
1. 在 fgetc 函數調用中,讀取的文件必須是以讀或讀寫方式打開的。
 
2.  讀取字符的結果也可以不向字符變量賦值,例如:fgetc(fp);但是讀出的字符不能保存。
 
3.  在文件內部有一個位置指針。用來指向文件的當前讀寫字節。在文件打開時,該指針總
是指向文件的第一個字節。使用 fgetc 函數后, 該位置指針將向后移動一個字節。 因此可
連續多次使用 fgetc 函數,讀取多個字符。  應注意文件指針和文件內部的位置指針不是一回
事。文件指針是指向整個文件的,須在程序中定義說明,只要不重新賦值,文件指針的值是
不變的。文件內部的位置指針用以指示文件內部的當前讀寫位置,每讀寫一次,該指針均向
后移動,它不需在程序中定義說明,而是由系統自動設置的。
 
[例 10.1]讀入文件 e10-1.c,在屏幕上輸出。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
ch=fgetc(fp);
while (ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
    本例程序的功能是從文件中逐個讀取字符,在屏幕上顯示。  程序定義了文件指針 fp,
以讀文本文件方式打開文件“e10_1.c”,  并使 fp 指向該文件。如打開文件出錯,  給出提示
并退出程序。程序第 12行先讀出一個字符,然后進入循環,  只要讀出的字符不是文件結束
標志(每個文件末有一結束標志 EOF)就把該字符顯示在屏幕上,再讀入下一字符。每讀一次,
文件內部的位置指針向后移動一個字符,文件結束時,該指針指向 EOF。執行本程序將顯
示整個文件。
二、寫字符函數 fputc
 
   fputc 函數的功能是把一個字符寫入指定的文件中,函數調用的  形式為:  fputc(字符量,
總計164頁  當前為第154 頁
文件指針); 其中,待寫入的字符量可以是字符常量或變量,例如:fputc('a',fp);其意義是把
字符 a 寫入 fp 所指向的文件中。
 
  對于 fputc 函數的使用也要說明幾點:
1.  被寫入的文件可以用、寫、讀寫,追加方式打開,用寫或讀寫方式打開一個已存在的文
件時將清除原有的文件內容,寫入字符從文件首開始。如需保留原有文件內容,希望寫入的
字符以文件末開始存放,必須以追加方式打開文件。被寫入的文件若不存在,則創建該文件。 
 
2.  每寫入一個字符,文件內部位置指針向后移動一個字節。
 
3. fputc 函數有一個返回值,如寫入成功則返回寫入的字符,  否則返回一個 EOF。可用此來
判斷寫入是否成功。
 
[例 10.2]從鍵盤輸入一行字符,寫入一個文件, 再把該文件內容讀出顯示在屏幕上。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("string","wt+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:\n");
ch=getchar();
while (ch!='\n')
{
fputc(ch,fp);
ch=getchar();
}
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
    程序中第 6 行以讀寫文本文件方式打開文件 string。 程序第 13 行從鍵盤讀入一個字符后
進入循環,當讀入字符不為回車符時,  則把該字符寫入文件之中,然后繼續從鍵盤讀入下
總計164頁  當前為第155 頁
一字符。  每輸入一個字符,文件內部位置指針向后移動一個字節。寫入完畢,  該指針已指
向文件末。如要把文件從頭讀出,須把指針移向文件頭, 程序第 19 行 rewind 函數用于把
fp 所指文件的內部位置指針移到文件頭。  第 20 至25 行用于讀出文件中的一行內容。
 
[例 10.3]把命令行參數中的前一個文件名標識的文件,  復制到后一個文件名標識的文件中, 
如命令行中只有一個文件名則把該文件寫到標準輸出文件(顯示器)中。
#include<stdio.h>
main(int argc,char *argv[])
{
FILE *fp1,*fp2;
char ch;
if(argc==1)
{
printf("have not enter file name strike any key exit");
getch();
exit(0);
}
if((fp1=fopen(argv[1],"rt"))==NULL)
{
printf("Cannot open %s\n",argv[1]);
getch();
exit(1);
}
if(argc==2) fp2=stdout;
else if((fp2=fopen(argv[2],"wt+"))==NULL)
{
printf("Cannot open %s\n",argv[1]);
getch();
exit(1);
}
while((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
}
    本程序為帶參的 main 函數。程序中定義了兩個文件指針 fp1  和 fp2,分別指向命令行
參數中給出的文件。如命令行參數中沒有給出文件名,則給出提示信息。程序第 18 行表示
如果只給出一個文件名,則使 fp2 指向標準輸出文件(即顯示器)。程序第 25 行至 28 行用循
環語句逐個讀出文件 1中的字符再送到文件 2中。再次運行時,給出了一個文件名(由例 10.2
所建立的文件),  故輸出給標準輸出文件 stdout,即在顯示器上顯示文件內容。第三次運行,
給出了二個文件名,因此把 string 中的內容讀出,寫入到 OK 之中。可用 DOS 命令 type 顯
示 OK的內容:字符串讀寫函數fgets和fputs
一、讀字符串函數 fgets 函數的功能是從指定的文件中讀一個字符串到字符數組中,函數調
用的形式為: fgets(字符數組名,n,文件指針);  其中的 n 是一個正整數。表示從文件中
總計164頁  當前為第156 頁
讀出的字符串不超過 n-1 個字符。在讀入的最后一個字符后加上串結束標志'\0'。例如:
fgets(str,n,fp);的意義是從 fp 所指的文件中讀出 n-1 個字符送入字符數組 str中。
[例 10.4]從 e10_1.c 文件中讀入一個含 10 個字符的字符串。
#include<stdio.h>
main()
{
FILE *fp;
char str[11];
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
fgets(str,11,fp);
printf("%s",str);
fclose(fp);
}
    本例定義了一個字符數組 str 共 11 個字節,在以讀文本文件方式打開文件 e101.c 后,
從中讀出 10 個字符送入 str 數組,在數組最后一個單元內將加上'\0',然后在屏幕上顯示輸
出 str數組。輸出的十個字符正是例 10.1 程序的前十個字符。
 
  對 fgets函數有兩點說明:
1.  在讀出 n-1 個字符之前,如遇到了換行符或 EOF,則讀出結束。
2. fgets 函數也有返回值,其返回值是字符數組的首地址。
 
二、寫字符串函數 fputs
 
fputs 函數的功能是向指定的文件寫入一個字符串,其調用形式為: fputs(字符串,文件指
針)  其中字符串可以是字符串常量,也可以是字符數組名, 或指針  變量,例如:
fputs(“abcd“,fp);
其意義是把字符串“abcd”寫入 fp 所指的文件之中。 [例 10.5]在例 10.2 中建立的文件 string 中
追加一個字符串。
#include<stdio.h>
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen("string","at+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
總計164頁  當前為第157 頁
printf("input a string:\n");
scanf("%s",st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("\n");
fclose(fp);
}
    本例要求在 string 文件末加寫字符串,因此,在程序第 6行以追加讀寫文本文件的方式
打開文件 string  。  然后輸入字符串,  并用 fputs 函數把該串寫入文件 string。在程序15 行
用 rewind 函數把文件內部位置指針移到文件首。  再進入循環逐個顯示當前文件中的全部內
容。
數據塊讀寫函數fread和fwrite
 
    C語言還提供了用于整塊數據的讀寫函數。  可用來讀寫一組數據,如一個數組元素,
一個結構變量的值等。讀數據塊函數調用的一般形式為: fread(buffer,size,count,fp); 寫數據
塊函數調用的一般形式為: fwrite(buffer,size,count,fp); 其中buffer是一個指針,在 fread 函
數中,它表示存放輸入數據的首地址。在 fwrite 函數中,它表示存放輸出數據的首地址。  size
表示數據塊的字節數。count  表示要讀寫的數據塊塊數。fp 表示文件指針。
例如:
fread(fa,4,5,fp); 其意義是從 fp 所指的文件中,每次讀 4 個字節(一個實數)送入實數組 fa 中,
連續讀 5 次,即讀 5 個實數到 fa 中。
[例 10.6]從鍵盤輸入兩個學生數據,寫入一個文件中,  再讀出這兩個學生的數據顯示在屏
幕上。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
總計164頁  當前為第158 頁
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf("\n\nname\tnumber age addr\n");
for(i=0;i<2;i++,qq++)
printf("%s\t%5d%7d%s\n",qq->name,qq->num,qq->age,qq->addr);
fclose(fp);
}
    本例程序定義了一個結構stu,說明了兩個結構數組boya和 boyb以及兩個結構指針變量
pp和 qq。pp指向 boya,qq指向 boyb。程序第 16 行以讀寫方式打開二進制文件“stu_list”,輸
入二個學生數據之后,寫入該文件中,  然后把文件內部位置指針移到文件首,讀出兩塊學
生數據后,在屏幕上顯示。
 
格式化讀寫函數fscanf和fprintf
 
fscanf 函數,fprintf 函數與前面使用的 scanf 和 printf  函數的功能相似,都是格式化讀寫函
數。  兩者的區別在于 fscanf  函數和 fprintf 函數的讀寫對象不是鍵盤和顯示器,而是磁盤文
件。這兩個函數的調用格式為: fscanf(文件指針,格式字符串,輸入表列); fprintf(文件指
針,格式字符串,輸出表列);  例如:
fscanf(fp,"%d%s",&i,s);
fprintf(fp,"%d%c",j,ch); 
用 fscanf和 fprintf 函數也可以完成例 10.6 的問題。修改后的程序如例 10.7所示。
[例 10.7]
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
總計164頁  當前為第159 頁
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("\ninput data\n");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
for(i=0;i<2;i++,pp++)
fprintf(fp,"%s %d %d %s\n",pp->name,pp->num,pp->age,pp->
addr);
rewind(fp);
for(i=0;i<2;i++,qq++)
fscanf(fp,"%s %d %d %s\n",qq->name,&qq->num,&qq->age,qq->addr);
printf("\n\nname\tnumber age addr\n");
qq=boyb;
for(i=0;i<2;i++,qq++)
printf("%s\t%5d %7d %s\n",qq->name,qq->num, qq->age,
qq->addr);
fclose(fp);
}
  與例 10.6 相比,本程序中 fscanf 和 fprintf 函數每次只能讀寫一個結構數組元素,因此
采用了循環語句來讀寫全部數組元素。  還要注意指針變量 pp,qq 由于循環改變了它們的值,
因此在程序的 25 和 32行分別對它們重新賦予了數組的首地址。
文件的隨機讀寫
 
    前面介紹的對文件的讀寫方式都是順序讀寫,  即讀寫文件只能從頭開始,順序讀寫各
個數據。  但在實際問題中常要求只讀寫文件中某一指定的部分。  為了解決這個問題可移動
文件內部的位置指針到需要讀寫的位置,再進行讀寫,這種讀寫稱為隨機讀寫。  實現隨機
讀寫的關鍵是要按要求移動位置指針,這稱為文件的定位。文件定位移動文件內部位置指針
的函數主要有兩個,  即 rewind  函數和 fseek 函數。
 
   rewind 函數前面已多次使用過,其調用形式為: rewind(文件指針);  它的功能是把文
件內部的位置指針移到文件首。  下面主要介紹
fseek 函數。
 
   fseek 函數用來移動文件內部位置指針,其調用形式為: fseek(文件指針,位移量,起
始點);  其中:“文件指針”指向被移動的文件。 “位移量”表示移動的字節數,要求位移量
總計164頁  當前為第160 頁
是 long 型數據,以便在文件長度大于 64KB  時不會出錯。當用常量表示位移量時,要求加
后綴“L”。“起始點”表示從何處開始計算位移量,規定的起始點有三種:文件首,當前位置
和文件尾。
其表示方法如表 10.2。 
起始點        表示符號        數字表示
──────────────────────────
文件首      SEEK—SET     0
當前位置     SEEK—CUR     1
文件末尾     SEEK—END       2
例如:
fseek(fp,100L,0);其意義是把位置指針移到離文件首 100 個字節處。還要說明的是 fseek 函數
一般用于二進制文件。在文本文件中由于要進行轉換,故往往計算的位置會出現錯誤。文件
的隨機讀寫在移動位置指針之后,  即可用前面介紹的任一種讀寫函數進行讀寫。由于一般
是讀寫一個數據據塊,因此常用 fread 和 fwrite 函數。下面用例題來說明文件的隨機讀寫。
 
[例 10.8]在學生文件 stu list 中讀出第二個學生的數據。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boy,*qq;
main()
{
FILE *fp;
char ch;
int i=1;
qq=&boy;
if((fp=fopen("stu_list","rb"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
rewind(fp);
fseek(fp,i*sizeof(struct stu),0);
fread(qq,sizeof(struct stu),1,fp);
printf("\n\nname\tnumber age addr\n");
printf("%s\t%5d %7d %s\n",qq->name,qq->num,qq->age,
qq->addr);
}
  文件stu_list已由例10.6的程序建立,本程序用隨機讀出的方法讀出第二個學生的數據。
總計164頁  當前為第161 頁
程序中定義 boy為 stu 類型變量,qq為指向 boy的指針。以讀二進制文件方式打開文件,程
序第 22 行移動文件位置指針。其中的 i 值為1,表示從文件頭開始,移動一個 stu 類型的長
度,  然后再讀出的數據即為第二個學生的數據。
文件檢測函數
 
C語言中常用的文件檢測函數有以下幾個。
一、文件結束檢測函數 feof函數調用格式: feof(文件指針); 
功能:判斷文件是否處于文件結束位置,如文件結束,則返回值為 1,否則為 0。
 
二、讀寫文件出錯檢測函數 ferror函數調用格式: ferror(文件指針); 
功能:檢查文件在用各種輸入輸出函數進行讀寫時是否出錯。  如 ferror 返回值為 0 表示未
出錯,否則表示有錯。
 
三、文件出錯標志和文件結束標志置 0 函數clearerr 函數調用格式: clearerr(文件指針); 
功能:本函數用于清除出錯標志和文件結束標志,使它們為 0 值。
 
C庫文件
 
C系統提供了豐富的系統文件,稱為庫文件,C 的庫文件分為兩類,一類是擴展名為".h"的
文件,稱為頭文件,  在前面的包含命令中我們已多次使用過。在".h"文件中包含了常量定
義、  類型定義、宏定義、函數原型以及各種編譯選擇設置等信息。另一類是函數庫,包括
了各種函數的目標代碼,供用戶在程序中調用。  通常在程序中調用一個庫函數時,要在調
用之前包含該函數原型所在的".h"  文件。
在附錄中給出了全部庫函數。
ALLOC.H        說明內存管理函數(分配、釋放等)。
ASSERT.H       定義 assert 調試宏。
BIOS.H         說明調用IBM—PC ROM BIOS子程序的各個函數。
CONIO.H        說明調用 DOS 控制臺 I/O子程序的各個函數。
CTYPE.H        包含有關字符分類及轉換的名類信息(如 isalpha和 toascii 等)。
DIR.H       包含有關目錄和路徑的結構、宏定義和函數。
DOS.H          定義和說明 MSDOS 和 8086 調用的一些常量和函數。
ERRON.H        定義錯誤代碼的助記符。
FCNTL.H        定義在與 open 庫子程序連接時的符號常量。
FLOAT.H        包含有關浮點運算的一些參數和函數。
GRAPHICS.H      說明有關圖形功能的各個函數,圖形錯誤代碼的常量定義,正對不同驅動
程序的各種顏色值,及函數用到的一些特殊結構。
IO.H         包含低級I/O子程序的結構和說明。
LIMIT.H        包含各環境參數、編譯時間限制、數的范圍等信息。
MATH.H        說明數學運算函數,還定了 HUGE VAL  宏,  說明了 matherr 和 matherr
子程序用到的特殊結構。
MEM.H          說明一些內存操作函數(其中大多數也在 STRING.H  中說明)。
PROCESS.H      說明進程管理的各個函數,spawn…和 EXEC …函數的結構說明。
SETJMP.H       定義 longjmp 和 setjmp 函數用到的 jmp buf 類型,  說明這兩個函數。
SHARE.H        定義文件共享函數的參數。
總計164頁  當前為第162 頁
SIGNAL.H       定義SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,說明rajse和signal
兩個函數。
STDARG.H       定義讀函數參數表的宏。(如 vprintf,vscarf函數)。
STDDEF.H       定義一些公共數據類型和宏。
STDIO.H     定義 Kernighan 和Ritchie 在 Unix System V  中定義的標準和擴展的類型和
宏。還定義標準 I/O  預定義流:stdin,stdout 和 stderr,說明 I/O 流子程序。
STDLIB.H       說明一些常用的子程序:轉換子程序、搜索/  排序子程序等。
STRING.H       說明一些串操作和內存操作函數。
SYS\STAT.H      定義在打開和創建文件時用到的一些符號常量。
SYS\TYPES.H   說明 ftime 函數和 timeb 結構。
SYS\TIME.H      定義時間的類型 time[ZZ(Z] [ZZ)]t。
TIME.H        定義時間轉換子程序 asctime、 localtime 和 gmtime 的結構, ctime、  difftime、 
gmtime、 localtime 和 stime 用到的類型,并提供這些函數的原型。
VALUE.H        定義一些重要常量,   包括依賴于機器硬件的和為與 Unix System V 相兼容
而說明的一些常量,包括浮點和雙精度值的范圍。
 
本章小結
 
1.  C系統把文件當作一個“流”,按字節進行處理。 
 
2.  C文件按編碼方式分為二進制文件和 ASCII 文件。
 
3.  C語言中,用文件指針標識文件,當一個文件被  打開時,  可取得該文件指針。
 
4.  文件在讀寫之前必須打開,讀寫結束必須關閉。
 
5.  文件可按只讀、只寫、讀寫、追加四種操作方式打開,同時還必須指定文件的類型是二
進制文件還是文本文件。
 
6.  文件可按字節,字符串,數據塊為單位讀寫,文件也可按指定的格式進行讀寫。
 
7.  文件內部的位置指針可指示當前的讀寫位置,移動該指針可以對文件實現隨機讀寫。
總計164頁  當前為第163 頁

  • 相關文章:

發表評論:

澳大利亚代写assignment,代写论文,essay代写推荐-cs小码神代写◎歡迎參與討論,請在這里發表您的看法、交流您的觀點。

最新評論及回復

最近發表

Powered By

Copyright 代寫C.

在線客服

售前咨詢
售后咨詢
微信號
Essay_Cheery
微信
悉尼assignment代写,北美作业代写,代写毕业论文-100%原创 北美代写,Homework代写,Essay代寫-准时✔️高质✔最【靠谱】 墨尔本assignment代写,代写毕业论文,paper代写-51作业君 北美代写,程序代做,程序代写,java代写,python代写,c++代写,c代写 英国代写paper,python代写,Report代写,编程代写-程序代写网 北美代写essay,程序代写,Java代写代做,Java代考-焦点论文 澳大利亚essay代写,编程代写,代码代写,程序代写-三洋编程 加拿大essay代写|程序代写代做||Python代写|Matlab代写-Meeloun 澳大利亚代写,代写essay,代写毕业论文,留学生代写-小马代写 日本代写,北美作业代写,新加坡代写,essay代写-无时差服务