模塊化程序設計:實現編寫好一批經常使用的函數,須要使用時可直接調用,而沒必要重複在寫,減小了程序的冗餘,使得程序變得更加精煉,編寫一次,就能夠屢次調用。數組
函數聲明的做用:吧有關函數的信息(函數名、函數類型、函數參數的個數與類型)通知編譯系統,以便在編譯系統對程序進行編譯時,在進行到Main函數中調用其它函數時,知道它們是定義的函數而不是變量或其餘對象。ide
說明:模塊化
(1) 一個C程序由一個或多個程序模塊組成,每個程序模塊做爲一個源程序文件。對於較大的程序,通常不但願把全部的內容全放在一個文件中,而是將它們分別編寫成若干個源文件中,由若干個源程序文件組成一個C程序。函數
(2) 一個源程序文件由一個胡多個函數以及其餘有關內容(如指令、數據聲明與定義)組成。一個源程序文件是一個編譯單位,在程序編譯時是以源程序文件爲單位進行編譯的,而不是以函數爲單位進行編譯的。spa
(3) C程序的執行是從main函數開始的,若是在main函數中調用它其餘函數,在調用後流程返回到main函數,在main函數找那個結束整個程序的運行。設計
(4) 全部函數都是平行的,即在定義函數時是分別進行的,是相互獨立的;一個函數並不從屬於另外一個函數,即函數不能嵌套定義。3d
(5) 從用戶使用的角度看,函數有兩種形式指針
① 庫函數調試
② 用戶本身定義的函數code
(6) 從函數的角度看,函數有兩種形式
① 無參函數
② 有參函數
定義函數包含如下內容:
(1) 指定函數名字,使後續可按名字調用
(2) 指定函數的類型,即函數返回值的類型
(3) 指定函數的參數的名字和類型,以便在調用函數時向它們傳遞數據。無參參數該項不須要指定
(4) 指定函數應當完成什麼操做,即函數的功能
說明:對於C編譯系統提供的庫函數,是由編譯系統事先定義好的,庫文件中包括了個函數的定義。咱們沒必要本身定義,只須用#include指令把有關的頭文件中包含到本文件模塊中便可。形式:#include <stdio.h>
1.定義無參函數
定義無參函數的通常形式爲:
①類型名 函數名()
{
函數體
}
或者
②類型名 函數名(void)
{
函數體
}
函數名後面括號內的void表示「空」,即函數沒有參數。
函數體包括聲明部分和語句部分
在定義函數時要用「類型標識符」(類型名int float double等)指定函數值的類型,換一個說法就是指定函數返回值的類型
2.定義有參函數
定義有參函數的形式:
類型名 函數名(形式參數列表)
{
函數體
}
int function(int x,int y)
{
Int z;
Z=x>y?x:y;
Return z;
}
有參構造函數的特色:有返回值類型typeof(如int float duble 等),也有返回值語句return.可經過這兩個標誌識別該函數是有參函數仍是無參函數。
3.定義空函數
定義空函數的形式:
類型名 函數名()
{
}
如:
Void dummy()
{}
空函數是函數體是空的函數。即什麼都不作,沒有任何實際做用。
那爲何還要定義函數空函數呢?若當前不清楚具體實現的功能,或者當前沒有實現該功能的能力;將空函數放在特定的位置,往後再來實現也是能夠的,也能夠在擴充程序功能時用一個編寫好的函數替代空函數,這樣程序的結構很是清楚,可讀性好,便於擴展新功能。
函數調用的通常形式爲:
函數名(實參表列)
1.函數調用語句
把函數調用語句單獨做爲一個語句
2.函數表達式
函數調用出如今另外一個表達式中。如「c=max(a,b)」
3.函數參數
函數調用做爲另外一個函數調用時的實參,如:
M=max(a,max(b,c));
換一個說話就是函數嵌套調用,函數裏調用函數
說明:調用函數並不必定要求包括分號;只有做爲函數調用語句才須要有分號。若是做爲函數表達式或函數參數,函數調用時沒必要有分號的。如:
Printf(「%d」,max(a,b));
該max函數後面不能跟「;」分號
Max(a,b);
該max函數做爲函數語句調用時,必須後面跟着「;」
1.形式參數和實際參數
在調用有參函數時,主調函數和被調用函數之間有數據的傳遞關係。
在定義函數時函數名後面括號中的變量名稱爲「形式參數」(簡稱「形參」),在主調函數中調用一個函數時,函數名後面括號中傳入的函數稱爲「實際參數」(簡稱「實參」)。實際參數能夠是常量、變量或表達式
2.實參和形參間的數據傳遞
在調用函數過程當中,系統會把實參的值傳遞給被調用函數的形參。
(1)在定義函數中指定的形參,在未出現函數調用時,並不佔用內存中的存儲單元。只有在發生函數調用時,函數max的形參被臨時分配內存單元。
(2)將實參對應的值傳遞給形參。
(3)在執行函數期間,因爲形參已經有值,就能夠利用形參進行有關的運算
(4)經過return語句將在調用函數內獲得的結果帶回到主函數。
若是函數不須要返回值,則不須要return語句。這時函數的類型應定義爲void類型
(5)調用結束,形參單元被釋放。注意:實參單元仍保留並維持原值,沒有改變。即調用函數的過程當中,形參會發生改變,而主調函數的實參不會發生改變。
注意:實參向形參的數據傳遞是「值傳遞」,單向傳遞,只能由實參傳給形參,而不能由形參傳給實參。換一個說法就是,形參的改變並不能影響到實參,而實參的改變會影響到形參。
(1)函數的返回值是經過函數中的return語句得到的。
一個函數中能夠有多個return,可是隻能執行到一個return
(2)函數值的類型。函數值有返回值,那麼這個返回值應當屬於一種肯定的類型,應當在定義函數時指定函數值的類型。
注意:在定義函數時要指定函數的類型
(3)在定義函數時指定指定的函數類型通常應該和return語句中的表達式類型一致。說明:若是函數值的類型和return語句中表達式的值不一致,則以函數類型爲準。對數值型數據,能夠自動進行類型轉換,即函數類型決定返回值的類型
在一個函數中調用另外一個函數(即被調用函數)須要具有以下條件:
(1)首先被調用的函數必須是已經定義的函數(庫函數或者用戶本身定義的函數)。
(2)若是使用庫函數,應該在本文件開頭中用#include指令將調用有關庫函數時所需用到的信息「包含」到本文件中來
#include <stdio.h> 中.h是頭文件所用的後綴,表示是頭文件(header file)
(3)若是使用用戶本身定義的函數,而該函數的位置在調用它的函數(即主調函數)的後面(在同一個文件中),應該對被調用的函數作聲明(declaration)。聲明的做用是把函數名、函數參數的個數和參數類型等信息經過編譯系統,以便在遇到函數調用時,編譯系統能正確識別函數並檢查調用是否合法。
說明:在運行階段發現錯誤並從新調試程序,是比較麻煩的,工做量也較大。故應在編譯階段儘量多地發現錯誤,隨之糾正錯誤。
函數聲明的通常形式:
(1)函數類型 函數名(參數類型1 參數名1,參數類型2 參數名2,……);
(2)函數類型 函數名(參數類型1,參數類型2 ,……);
注意:函數聲明是方面帶有分號「;」,函數定義是後面無分號。如定義的格式爲:
(1)函數類型 函數名(參數類型1 參數名1,參數類型2 參數名2,……)
{
}
(2)函數類型 函數名(參數類型1,參數類型2 ,……)
{
}
在定義函數時,一個函數能不能在定義另外一個函數。即函數不能嵌套定義。可是能夠嵌套調用函數,也就是說在調用一個函數的過程當中,又調用另外一個函數。
在調用一個函數過程當中又出現直接或間接地調用該函數自己,稱爲函數的遞歸調用
數據元素能夠用做函數實參,不能用做形參。在用數組元素做函數實參時,把實參的值傳給形參,是「值傳遞」方式。數據傳遞的方向是從實參傳到形參,單向傳遞。
用數組元素做實參時,向形參變量傳遞的是數組元素的值,而用數組名做函數實參時,向形參(數組名或指針變量)傳遞可是數組首元素的地址。
可用多維數組做爲函數的實參和形參,在被調用函數中對形參數組定義時能夠指定每一維的大小,也能夠省略第一維的大小(對於二維數組來講);如:
int array[3][10];
或
int array[][10];
可是下面這個定義時不合法的
int array[][];
咱們首先提出一個問題:在一個函數中定義的變量,在其餘函數中可否被引用?在不一樣位置定義的變量,在什麼範圍內有效?
以上的問題就是該節討論的變量的做用域的問題。每個變量都有一個做用域問題,即它們在什麼範圍內有效。
定義變量可能有3種狀況:
(1)在函數的開頭定義
(2)在函數內的符合語句內定義
(3)在函數的外部定義
在一個函數內部定義的變量只在本函數範圍內有效,也就是說只有在本函數內才能引用它們,在此函數之外是不能使用這些變量的。在複合語句內定義的變量只在本複合語句範圍內有效,只有在本複合語句內才能引用它們。在該複合語句之外是不能使用這些變量的,以上這些稱爲「局部變量」
說明:
(1)主函數中定義的變量(如m,n)也只能在主函數中有效,並不由於在主函數中定義而在整個文件或程序中有效。主函數不能使用其餘函數中定義的變量,如f1中的b,c;f2中的m,n都不能使用
(2)不一樣函數中可使用同名的變量,它們表明不一樣的對象,互不干擾。如f1中定義的b,c也可在f2中定義
(3)形式參數也是局部變量
(4)在一個函數內部,能夠在複合語句中定義變量,這些變量只在複合語句中有效,這些複合語句也稱爲「分程序」或「程序塊」
程序的編譯單位是源程序文件,一個源文件能夠包含一個或若干個函數。
在函數內定義的變量是局部變量,而在函數以外定義的變量稱爲外部變量,外部變量是全局變量。全局變量能夠爲本文件中其餘函數所共用,它的有效範圍爲從定義變量的位置開始到本源文件結束。
注意:在函數內定義的變量是局部變量,在函數外定義的變量是全局變量。
P,q,c1,c2都是全局變量,它們的做用範圍不一樣,在main函數和f2函數中可使用全局變量p,q,c1,c2,但在函數f1中只能使用p,q而不能使用c1,c2.
說明:設置全局變量的做用是增長了函數間數據聯繫的渠道。因爲同一個文件中的全部函數都能引用全局變量的值,所以若是在一個函數中改變了全局變量的值,就能影響到其餘函數中全局變量的值。至關於各個函數間有直接的傳遞通道。因爲函數的調用只能待會一個函數返回值,一次有時能夠利用全局變量來對增長函數建的聯繫渠道,經過函數調用能獲得一個以上的值。
爲了便於區別全局變量和局部變量,在C程序設計中有一個習慣(並不是規定)。將全局變量名的第1個字母用大寫表示。
說明:建議再也不必要時不要使用全局變量,理由以下:
(1)全局變量在程序的所有執行過程當中都佔用存儲單元,而不是僅在須要時纔開闢單元。故消耗內存單元
(2)它使函數的通用性下降了,若是在函數中引用了全局變量,那麼執行狀況會受到有關的外部變量影響,若是有一個函數移到另外一個文件中,還要考慮把有關的外部變量及其值一塊兒轉移過去。但若該外部變量與其餘文件的變量同名時,就會出現問題。這就下降了程序的可靠性和通用性。在程序設計中,在劃分模塊時要求模塊的「內聚性」強,與其餘模塊的「耦合性」弱。即模塊的功能要單一,不相互影響或者影響較小。
(3)使用全局變量過多,會下降程序的清晰性,難以判斷瞬間各個外部變量的值。因爲在各個函數執行過程當中均可能改變外部變量的值,故程序容易出錯。
注意:若是在同一個源文件中,全局變量與局部變量同名,這時會出現什麼狀況呢?
答案是,在局部變量的做用範圍內,局部變量有效,全局變量被「屏蔽」,即全局變量不起做用。簡單說,就是局部變量在該範圍內覆蓋了全局變量。
從變量的做用域(即從空間)的角度來觀察,變量能夠分爲全局變量和局部變量
從變量的生存週期(即變量存在的時間、生存期)的角度來觀察,變量可分爲靜態存儲方式和動態存儲方式。
靜態存儲方式:指在程序運行期間由系統分配固定的存儲空間的方式
動態存儲方式:在程序運行期間格局須要進行動態的分配存儲空間的方式
存儲空間分爲3部分
(1)程序區
(2)靜態存儲區
(3)動態存儲區
數據分別存儲在靜態存儲區和動態存儲區中。全局變量所有存放在靜態存儲區中,即在程序執行過程當中佔據固定的存儲單元,而不是動態地進行分配和釋放。
動態存儲區中存放如下數據:
(1)函數形式參數。在調用函數時給形參分配存儲空間。
(2)函數中定義的沒有用關鍵字static聲明,即自動變量(auto)
(3)函數調用時的現場保護和返回地址等
以上數據,在函數調用開始時分配動態存儲空間,函數結束時釋放這些空間。在一個程序中兩次調用同一函數,而在此函數中定義了局部變量,在兩次調用時分配給這些局部變量的存儲空間的地址多是不相同的。
在C語言中,每個變量和函數都有兩個屬性:數據類型和數據的存儲類別。數據類型是int double short char double float等類型。存儲類別指的是數據在內存中存儲的方式(靜態存儲和動態存儲)。
在定義和聲明變量和函數時,通常應同時指定數據類型和存儲類別,也能夠採用默認方式指定(即若是用戶不指定,系統會隱含地指定爲某一種存儲類別)auto
C的存儲類別有4種:自動的(auto)、靜態的(static)、寄存器的(register)、外部的(extern)。咱們能夠根據存儲類別,知道變量的做用域和生存週期。
1.自動變量(auto變量)
函數中的局部變量,若是不專門聲明爲static(靜態)存儲類別,都是動態地分配存儲空間的,數據存儲在動態存儲區中。
函數中的形參和在函數中定義的局部變量,都屬於自動變量。
在調用該函數時,系統會給這些變量分配存儲空間,在函數調用結束時就自動釋放這些存儲空間。所以這類局部變量稱爲自動變量。自動變量用關鍵字auto作存儲類別的聲明;如:
int f(int a)
{
auto int b,c; //定義b,c爲自動變量
....
}
其中,a是形參,b、c是自動變量。執行完f函數後,自動釋放a,b,c所佔的存儲單元。
實際上,關鍵字」auto」能夠省略,不寫auto則隱含指定爲「自動存儲類別」,它屬於動態存儲方式。程序中大多變量屬於自動變量。
在函數定義的變量都沒有聲明爲auto,起始都隱含指定爲自動變量。
Int a,b;
和 auto int a,b;
等價。
2.靜態局部變量(static局部變量)
有時但願函數中的局部變量的值在函數調用結束後不消失而繼續保留原值,即佔用的存儲單元不釋放,在下一次在調用該函數時,改變了的值爲上一次的值。這時須要將該局部變量指定爲「靜態局部變量」,用關鍵字static進行聲明。
範例:考察靜態局部變量的值
1 #include <stdio.h> 2 int main() 3 { int f(int); //函數聲明 4 int a=2,i; //自動局部變量 5 for(i=0;i<3;i++) 6 printf("%d\n",f(a)); //輸出f(a)的值 7 return 0; 8 } 9 10 int f(int a) 11 { auto int b=0; //自動局部變量 12 static int c=3; //靜態局部變量 13 b=b+1; 14 c=c+1; 15 return(a+b+c); 16 }
說明:
(1)靜態局部變量屬於靜態存儲類別,在靜態存儲區內分配存儲單元。在程序整個執行期間都不會釋放。而自動變量(即動態局部變量)屬於動態存儲類別,分配在動態存儲區空間而再也不靜態存儲區空間,函數調用結束後即釋放
(2)對靜態局部變量是在編譯時賦初值的,即只賦初值一次,在程序運行時它已有初值。之後每次調用函數時再也不從新賦初值而只是保留上次函數調用結束時的值。而對自動變量賦初值,不是再編譯時進行的,而是在函數調用時進行的,每調用一次函數從新賦一次初值。至關於執行一次賦值語句。
(3)若是在定義局部變量時不賦初值的話,則對靜態局部變量來講,編譯時自動賦初值0(固然這是對於數值型變量而言)或空字符’\0’(對於字符變量而言)。而對自動變量來講,它的值是一個不肯定的值,這是因爲每次函數調用結束後存儲單元已釋放,下次調用時有從新另分配存儲單元,而所分配的單元中的內容是不可預知的。
(4)雖然靜態局部變量在函數調用結束後仍然存在,但其餘函數時不能引用它的。由於它是局部變量,只能被本函數引用,而不能被其餘函數引用。
3.寄存器變量(register變量)
通常狀況下,變量(包括靜態、動態存儲方式)的值是存放在內存中的,當程序用到那個變量時,將該變量從內存中送到運算器中運算。
若是有一些變量使用頻繁,則爲存取變量的值要花費很多時間,爲提升執行效率,容許將局部變量的值放在CPU中的寄存器中,須要用時直接從寄存器取出參加運算,沒必要再到內存中去存取。因爲對寄存器的存取速度遠高於對內存的存取速度,所以這樣作能夠提升執行效率。這種變量叫作寄存器變量。
Register int f; //定義f爲寄存器變量
以上3中局部變量存儲的位置是不一樣的,自動變量存儲在動態存儲區中;靜態局部變量存儲在靜態存儲區中;寄存器存儲雜技CPU中的寄存器中。
通常來講,外部變量是在函數外部定義的全局變量,它的做用域是從變量定義的位置開始,到本程序文件的末尾。在此做用域內,全局變量能夠爲程序中各個函數所引用。有時但願可以擴展外部變量的做用域,有如下幾種狀況可擴展做用域:
1.在一個文件內擴展外部變量的做用域
若是外部變量不在文件的開頭定義,其有效的做用範圍只限於定義處到文件結束。在定義該變量前的函數不能引用該外部變量。若是想要在定義該變量前引用該變量,就須要用關鍵字extern對該變量作「外部變量聲明」,表示把該外部變量的做用域擴展到此位置。
注意:提倡將外部變量的定義放在引用它的全部函數以前,這樣能夠避免在函數中多加一個extern聲明。
用extern聲明外部變量時,類型名能夠寫也能夠不寫。如:
「extern int A,B,C;」也能夠寫成「extern A,B,C;」。由於它不是定義變量,能夠不指定類型,只須寫出外部變量名便可。
2.將外部變量的做用域擴展到其餘文件
若是程序由多個源程序文件組成,那麼在一個文件中想引用另外一個文件中已定義的外部變量(假設爲var),有什麼辦法?
解決方法:在任一個文件中定義外部變量,而在另外一個文件中用extern對該變量(var)作「外部變量聲明」,即extern Num;
在編譯和鏈接時,系統會由此知道該變量(var)有「外部連接」,可從別處找到已定義的外部變量(var),並將在另外一個文件中定義的外部變量(var)的做用域擴展到本文件,在本文件中能夠合法地引用外部變量(var).
3.將外部變量的做用域限制在本文件中
有時在程序設計中但願某些外部變量只限於被本文件引用,而不能被其餘文件引用。這時能夠在定義外部變量時加一個static聲明。
如:
在file1.c中定義了一個全局變量Var,但用了static聲明,所以只能用於本文件(file1.c),故在file2.c中使用會出錯
這種加上static的聲明,只能用於本文件的外部變量稱爲靜態外部變量
說明:不要誤認爲對外部變量加static聲明後才採起靜態存儲方式(存放在靜態存儲區中),而不加static的是採用動態存儲方式(存放在動態存儲區中)。聲明局部變量的存儲類型和聲明全局變量的存儲類型的含義是不一樣的。
對於局部變量來講,聲明存儲類型的做用是指定變量存儲的區域(靜態存儲區或動態存儲區)以及由此產生的生存週期的問題。
對於全局變量來講,聲明存儲類型的做用是指定變量做用域擴展的問題,而不是在編譯時分配內存靜態、動態存儲區的問題,由於全局變量都存放在靜態存儲區。
用static聲明一個變量的做用是:
(1)對局部變量用static聲明,是將該變量分配在靜態存儲區中,該變量在整個程序執行期間不釋放,其所分配的空間在程序結束前都存在。
(2)對全局變量用static聲明,是將該變量的做用域限定在必定的範圍內;做用是將該變量的做用域只限於本文件模塊中(即被聲明的文件中)。
注意:用auto、register和static聲明變量時,是在定義變量的基礎上加上這些關鍵字,而不能單獨使用。
Int a;
Static a;
這是錯誤的,只能在定義時加上以上關鍵字,厚澤編譯時會被認爲「從新定義」。
static int a;//這是正確的定義的方式
對一個數據的定義,須要指定兩種屬性:數據類型和存儲類別。
下面從不一樣角度作些概括:
(1)從做用域角度分,有局部變量和全局變量。它們採用的存儲類別以下:
(2)從變量存在的實際(生存週期)來區分,有動態存儲和靜態存儲兩種類型。靜態存儲是程序整個運行時間都存在,而動態存儲是在調用函數時臨時分配單元。
(3)從變量值存放的位置來區分,可分爲
(4)關於做用域和生存週期的概念,對於變量的屬性來分析,一是變量的做用域,另外一是變量值存在時間的長短,即生存週期。前者是從空間的角度,後者是從時間的角度。
一個函數由兩部分組成:聲明部分和執行語句
聲明部分的做用:對有關的標識符(變量、函數、結構體、共用體)的屬性進行聲明。
函數的聲明是函數的原型,而函數的定義是對函數功能的定義。
對於變量而言,聲明與定義的關係複雜一些。不過總結出來就是,創建存儲空間的聲明稱定義,而把不須要創建存儲空間的聲明稱爲聲明。
函數本質上是全局的,由於定義一個函數的目的就是要被另外的函數調用。若是不加聲明的話,一個文件中的函數既能夠被本文件中其餘函數調用,也能夠被其餘文件中的函數調用。可是,凡事都有特例,也可指定某些函數不能被其餘文件調用。
根據函數可否被其餘源文件調用,將函數區分爲內部函數和外部函數。
若是一個函數只能被本文件中其餘函數鎖調用,它稱爲內部函數。在定義內部函數時,在函數名和函數類型的前面加static,即
static 類型名 函數名(形參表);
如:
static int fun(int vara,int varb);
表示fun是一個內部函數,不能被其餘文件調用
內部函數又稱爲靜態函數,由於它是用static聲明的。被static聲明的函數做用域只侷限於所在的文件。
一般把只能由本文件使用的函數和外部變量放在文件的開頭,前面都冠以static使之局部化,其餘文件不能引用。這就提升了程序的可靠性
若是在定義函數時,在函數首部的最左端加關鍵字extern,則此函數是外部函數,可供其餘文件調用,即
static 類型名 函數名(形參表);
如:
extern int function(int vara,int varb);
表示function是一個外部函數,可被其餘文件調用。
C語言規定,若是在定義函數時省略extern,則默認爲外部函數。
說明:在本章中接觸到一些重要的概念和方法,這些對於往後程序的工做來講,是很是重要的,是必須瞭解和掌握的。儘量把概念給記下來,多看多複習。