1、c語言的函數有以下特點:
(1)才源程序由函數組成,一個主函數main()+若干其他函數
C程序中的函數類似文章中的段落,但與段落不同的是,程序的執行是由main函數這一段起始,在main函數這一段中結束,而不是由第一個函數起始,在最后一個函數中結束。main外的其他函數,是由main調用執行的。
(2)函數之間是調用的關系,調用某函數的函數稱主調函數,被調用的函數稱為被調函數。main函數(我們)是主體,有權調用其他函數,其他函數之間也可以相互調用以協調工作,例如餐廳也調用了打車。但其他函數不能調用main函數。
(3)除main函數外,其他函數都不能獨立運行;其他函數只有在被調用時才運行,不調用不運行。
(4)同一函數,可被反復多次調用。
(5)函數的返回,誰調用的返回誰
(6)函數分為兩種,系統提供的庫函數和自定義的函數
系統提供的庫函數如printf()、getchar()、sqrt()等,這些函數是由系統提供的,隨時聽命由我們派遣。我們不必關心其內部細節,要調用他們,只要包含相應的頭文件(xxx.h)。
(7)函數的參數和返回值。有的函數有1~多個參數,有的沒有參數。有的函數有1個返回值,有的沒有返回值。
函數的返回值有些類似數學中函數的函數值,如sin(30)得到0.5,0.5就是函數sin(30)的返回值。60度的正弦值為0.866,0.866是sin(60)的返回值。這說明參數不同,函數的返回值也可能不同。而有些函數有沒有返回值,如上例的"打車"函數,它只用于實現一些功能,并不需要返回什么東西。注意函數如果有返回值,返回值最多只能有1個。
2、函數的定義和調用
自己的函數必須先定義,然后才能使用。
定義自己函數的一般形式如下所示:
函數返回值類型 ?函數名(參數類型1 ?參數名1,參數類型2 ?參數名2,...)
{
語句1;
語句2;
....
}
注意:各個函數的定義是互相平行和獨立的,以上內容必須與main函數或其他自定義函數并列。函數的定義不能嵌套,在一個函數體內部不允許在定義另一個函數。
在定義函數時,對于函數的頭部應該注意以下問題:
(1)返回值類型,應寫在函數名之前,規定函數所返回的數據的類型,如int、float、double、char等。如果不寫返回值類型,表示規定返回值類型為int,不是沒有返回值。要規定函數沒有返回值,這一部分應寫為關鍵字void,而不是省略。
(2)函數名是符合標識符命名規則的名稱,但最好"見名知意"。
(3)函數名后的()必不可少,即使函數沒有參數。
(4)在()內規定函數的參數,多個參數時參數之間以逗號分隔。參數的定義與變量的定義類似,也是類型+參數名的形式(實際上參數確實可以被看做變量),但與變量定義不同的是,每個參數都必須在參數名前寫有類型,且不寫分號(;)下面的定義是錯誤的int ?maxnum(int a,b){}在b之前也必須寫出b的類型,不能省略。
對于沒有參數的函數,在函數的()中也可寫上void強調。舉例:
void ?print(void){
}
兩個void含義是不同的,第一個void規定函數沒有返回值,第二個()中的void強調了函數沒有參數。無參數函數的()內寫不寫void都是可以的,但()萬萬不可省略。
函數的調用:
函數的調用方法:調用函數有兩種方式:(1)將函數調用作為獨立的語句:函數名(參數,參數,...)+分號(;),(2)在表達式中調用函數:表達式....函數名(參數,參數,...)。
對于有返回值的函數以上兩種調用方式均可,只是若采用第(1)中方式函數所返回的值就沒什么用了,返回值返回后會被直接丟掉(但函數中的語句還是被正常執行)。而對與無返回值的函數只能采用上面第(1)中調用方式,因為函數無返回值,表達式無法求值。如z=2*print()+1;
在表達式中調用函數:例如:z=sqrt(4)*3;表示計算4的平方根,在*3后將6存入變量z中,其中計算4的平方根是通過調用有返回值的系統庫函數sqrt完成的。對于有返回值的自定義函數也可以用類似的調用方式。這種調用方式就是講函數作為表達式的一部分,將來用函數的返回值替換這一部分參與表達式的計算。
注意:形參一定是變量,實參既可以是常量,也可以是變量或表達式或函數。如int x=25,y=73;maxnum(x*3,y);
在調用函數時,所給出的實參必須和形參在數量上、順序上、類型上嚴格一致,或類型上可以進行轉換。
參數傳遞時,實參和形參是按照位置的先后順序一一對應的,對應的實參和形參的類型應一致。如不一致,則以形參類型(函數定義頭部)為準,自動將實參轉換為改類型。
函數調用的過程:
(1)每個函數都有自己獨立的內存空間,函數中的變量包括形參都位于各自的內存空間中,互不干涉。被調函數的內存空間只能在函數被調用后運行時才存在,不調用不運行其空間不存在。
(2)在調用函數時,主調函數暫停運行,程序轉去執行被調函數。在轉去執行被調函數之前的準備工作為,將形參當做變量,在被調函數自己的內存空間中開辟這些形參變量的空間,然后將實參的值單項的傳遞給對應的形參變量,即用實參值給形參變量賦值。
(3)運行被調函數,逐條執行被調函數中的語句,這與在main函數中的執行方式一樣。被調函數運行結束后,被調函數的空間包括其中所有的變量,也包括形參變量全部消失,它們的空間即可被系統回收不在存在(但對有返回值的函數,返回值不會消失)。
(4)返回到主調函數中剛才暫停的位置繼續運行主調函數的后續程序。
(5)變量都要位于他所屬函數的空間中,而不能位于其他函數的空間中。形參是被調函數中的變量,應位于被調函數的空間中。由于不同的函數有各自不同的空間,因此在不同的函數中可以使用同名變量,若實參是主調函數中的變量,形參和實參也可以同名(形參是被調函數中的”變量“,二者分屬不同的空間)。
函數的返回值:
函數被調用后,可以沒有返回值,也可以有1個返回值。
無論有無返回值,函數均可被調用,其調用過程也一致。函數結束后也都能返回到主調函數的調用處繼續運行主調函數的后續程序。只不過對有返回值的函數,在表達式中調用時,一般還要用返回值替換其中函數的調用部分,然后在計算表達式,例如z=sqrt(4)*3;sqrt(4)的返回值為2,用2替換sqrt(4)部分得z=2*3;在執行此語句變量z被賦值為6.
sqrt是系統庫函數,它的返回值是系統自動算出的。而對于自定義函數,要返回值,就要由我們自己通過return語句讓函數返回一個值。return是關鍵字,return語句的形式為:return ?表達式;或return ?(表達式);
表達式的值將被算出,并將表達式的值作為函數的返回值返回。其執行過程是:計算表達式的值,然后開辟一個臨時空間,將表達式的值存入此臨時空間,再將此臨時空間中的值作為函數返回值返回給主調函數。
關于return語句,還應注意以下幾點:
(1)同一函數內允許出現多個return語句,但在函數每次被調用時只能有其中一個return語句被執行,函數只能返回一個值。
(2)一旦執行return,函數立即結束,如果本函數內在return后還有其他語句則這些語句也不會被 執行了。也就是說return兼有返回值和強行跳出函數的雙重作用,它會使程序即刻返回到主調函數的調用處繼續運行主調函數后面的程序。
(3)return語句還有一個用法,是:return;這種用法沒有表達式,不能返回值,僅起到強行跳出函數的這個作用。這種用法只能用于沒有返回值的函數。
(4)沒有返回值的函數既可以寫return;語句,也可以不寫return;語句。后者在函數語句全部運行完畢之后,函數會自動結束,自動返回到主調函數。因此函數有、無返回值和有無return;語句并沒有什么必然關系。
實際上,即使有返回值的函數,也可以沒有return語句,這樣在函數語句全部運行完畢之后,函數將返回一個隨機值。但不建議這樣做,對有返回值的函數應該寫有return 表達式;的語句讓它返回一個值。如希望不寫return,不如將函數定義為無返回值的(void)。
(5)如果函數有返回值,則實際返回值的類型應該和函數定義頭部的函數名前的類型一致,如不一致,則以函數定義頭部的函數名錢的類型為準,自動將return語句后的表達式值的類型轉換為定義頭部的類型然后在返回。
main函數實際也是一個自定義函數,我們通常對main函數的寫法是:
main()
{
}
函數名main前的返回值類型省略,表示main函數式有返回值的且返回值是一個int型的數據。所以也可以將main函數寫為:
int ?main()
{
}
以上二者是等效的。
main的返回值用于在程序結束后返回給操作系統(如Windows)表示程序運行情況。我們可以在main函數中用return 0;或return 1;等向操作系統返回一個值(程序若正常結束一般返回0)。一般我們不必關心甚至也可以不向操作系統返回值,或者干脆規定main函數沒有返回值,將main函數寫為下面的形式也是正確;
void ?main()
{
}
顯然一旦在main函數中執行了return語句,將跳出main函數,那么整個程序也就運行結束了。
函數的聲明:
程序中函數之間是調用與被調用的關系,因此函數彼此出現的先后順序無關緊要。。。。
解決這個問題有兩種方法:一是像例子中先寫函數fun的定義,然后在寫main函數,在main函數中調用;還有一種方法是使用函數的聲明。
函數的聲明寫法非常簡單,就是函數定義的頭部“照抄”,后面再加分號(;)就可以了。比如:void ?fun ?(int p);
它類似與一條語句,但不會產生任何操作,只是“告訴”編譯系統,存在這樣一個函數(可能在后面定義),讓編譯系統提前認識這個函數。
函數的聲明,也稱函數的原型聲明,也稱函數的原型。原型就是樣式,它表示了函數的返回值類型、函數名、形參個數、順序及每個形參的類型。聲明函數就是告訴編譯系統這種函數的樣式,因為編譯系統需要這些信息來認識這個函數。
注意在函數的原型中,有一樣東西是可有可無的,就是形參的名字。也就是說,在函數的聲明中,形參的名字可以省略,但不能省略形參類型,除了形參名字之外,其他任何內容均不可省略。如:void fun (int);
舉例:#includevoid fun(int p);
main()
{
int a=10;
fun(a);
printf("main:%d\n",a);
}
void fun(int p)
{
int a=2;
a=a*p;
printf("fun(1):%d\n",a);
if(a>0)return;
printf("fun(2):%d\n",a);
}
函數聲明的位置:
函數的聲明既可以出現在函數外,也可以出現在其他的函數體內。兩者的區別如下所示:
在函數外聲明:使編譯系統從聲明之處開始到本源程序文件的末尾的所有函數中,都認識該函數。
在函數內聲明:使編譯系統僅在本函數內、從聲明之處開始認識該函數,但在本函數之外有不認識該函數。
程序中函數的聲明不是一定要有的。只有先調用函數,后出現函數定義的時候,才需要在調用之前聲明函數。如果先出現函數的定義,后調用函數,就可以不必聲明,當然這種情況下聲明函數也不會出錯。無論如何,我們的目的是在調用函數之前讓編譯系統認識這個函數,定義可以讓他認識,聲明也可以讓他認識。對同一函數還可以聲明多次,但定義只能出現一次。
調用系統庫函數,也需要提前聲明函數。但系統庫函數的函數聲明已被事先寫到頭文件(.h)中了,我們通常用#include命令在程序中包含對應的頭文件,就是把對應函數的聲明包含到我們的程序中。這就是為什么在調用庫函數之前,一定要包括對應頭文件的原因。例如:要調用庫函數sqrt(),就需包含頭文件math.h,因為math.h中有sqrt函數的聲明。
函數的嵌套調用和遞歸調用:
函數的嵌套調用:定義函數不允許嵌套,但調用函數可以嵌套,即在被調函數中又調用其他函數。
函數的遞歸調用:在一個函數中可以調用其他函數,那么如果在一個函數中調用自己這個函數本身又會如何呢?在程序設計中,函數自己調用自己不僅是合法的,而且是一個非常重要的程序設計技巧稱為遞歸。例如:
int ?fun(int x)
{
.....
fun(y);
....
}
變量的作用域及存儲類別:
變量作用域:作用域是指變量在程序中能夠起作用的地域范圍。
變量既可以在函數內定義,也可以在函數外定義。在函數內定義的變量為局部變量(也稱內部變量),在函數外定義的變量為全局變量(也稱外部變量)。
局部變量:
形參在函數被調用時,也作為函數內的局部變量。
局部變量的特點:
(1)只在本函數內有效,在其他函數中都不能直接使用。
(2)局部變量若在定義時未賦初值,初值為隨機數。
(3)不同函數中可使用同名變量,它們互不干擾。形參和實參也可以同名。
(4)函數執行結束后,變量空間被回收。
局部變量是在函數內定義的變量,還可以讓它更局部一些,在復合語句(一對{}括起的語句)內還可以定義局部變量。這種局部變量的作用范圍更小,僅在所在的復合語句范圍內有效,而且其生存期也在復合語句范圍內,一旦復合語句執行結束,其空間就被回收。
全局變量:在函數外定義的變量為全局變量(也稱外部變量)。全局變量的重要特點:
(1)這種變量的作用范圍是“全局的”
,從它定義處開始到本源程序文件末尾的所有函數都共享此變量。
(2)如定義時未賦初值,初值自動為0,而不是隨機數。
如果函數內有局部變量與全局變量同名,則在該局部變量的作用范圍內將使用該局部變量,同名全局變量被屏蔽不起作用。
全局變量的優點是在多個函數中都能同時起作用,在一個函數中對某變量值的改變可被帶到其他函數中,所以通過全局變量可在不同函數間傳遞數據。
擴大全局變量的作用域:全局變量的作用域有一個限制,就是只能從變量的定義處之后才能被使用。
舉例:#includeint sum;
void fun1()
{
sum+=20;
}
int a;
void fun2()
{
a=20;sum+=a;
}
main()
{
sum=0;
fun1();
a=8;
fun2();
printf("sum=%d,a=%d",sum,a);
}
上例中fun1函數式不能使用全局變量a的,因為a在fun1之后才定義。如果一定要在fun1函數中使用全局變量a,能不能辦到呢?可以,這需要用關鍵字extern來擴大其作用范圍。extern是用來聲明全局變量的,在用extern聲明全局變量之后,就可以使用該全局變量了(盡管他可能在以后才被定義)。聲明的寫法與變量定義的寫法基本相同,只是在之前加extern,例如:extern ?int ?a; 舉例:如下
注意變量的聲明與變量的定義不同,聲明不會開辟變量的存儲空間,而定義就要開辟存儲空間了。因此全局變量的聲明可出現多次,而定義只能是一次。
#includeextern int a;
int sum;
void fun1()
{
sum+=20;
/*在fun1函數中可以使用全局變量a*/
}
int? a;
void fun2()
...
只有先使用全局變量,后定義的才有必要聲明。如果全局變量是先出現的定義,而后才使用的,可不必聲明,當然如果聲明了也不會出錯。
多文件編程:
當一個c程序由多個源文件組成時,注意多個源文件組成的是一個程序,而不是多個程序。因此只能在一個源文件中有main函數,且只能有一個main函數。
file1.c如下
int a;/*全局變量*/
extern void fun();
main()
{
a=10;
printf("(1)a=%d\n",a);
fun();
printf("(2)a=%d\n",a);
}
file2.c如下:
extern int a;
void fun()
{
a=20;
printf("fun中a=%d\n",a);
}
注意:(1)在一個文件中定義了全局變量,有時希望在其他文件中也能使用該全局變量。這時要在使用該變量的其他文件中聲明該變量。比如:file1.c中定義了全局變量a,在file2.c中應該先用extern聲明全局變量a,才能在file2.c中使用file1.c中的a。
(2)調用其他文件中定義的函數也要聲明。比如:在main函數中調用了其他文件的fun函數,因此在file1.c的第二行事先聲明了fun函數 extern ?void fun();這時的extern用在函數聲明前,它表示該函數是在其他文件中定義的。extern也可以省略,編譯系統會自動 先查找本文件中有無此函數的定義,如未找到再到其他文件中查找。
(3)在函數定義的頭部還可以加extern以強調該函數允許被其他文件調用,如上例file2.c中對函數fun的定義還可以寫為:extern void fun()
{
a=20;
printf("fun中a=%d\n",a);
}
通常函數定以前的extern可以省略,其效果是相同的。
總結extern的用法和作用:
(1)全局變量的聲明或函數的聲明前加extern,表示該全局變量或函數是其他文件中定義的,或是本文件稍后定義的,這里extern用于擴大作用域范圍。注意聲明并不開辟變量空間。
(2)函數定義前加extern或不寫extern,都表示該函數允許被其他文件調用。
如果不讓其他文件調用本文件定義的全局變量或函數,該如何做呢?在全局變量的定義或函數定義前加static,可限制其只能在本文件中使用,不允許在其他文件中使用,即使在其他文件中使用extern聲明也不行例如:static ?int a;
static ?int ?MyFun(int a, int b)
{
}
允許被其他文件調用的函數稱為外部函數,不允許被其他文件調用的函數稱為內部函數。
注意:若允許被其他文件使用,函數在定義時不寫static也可以,或加extern強調也可以。但對于全局變量,定義時只能不寫static,不能加extern強調。如果加了就是聲明變量而不是定義變量了,會導致變量未定義的錯誤。
總結static的用法和作用:
(1)全局變量定義時或函數定義時加static,表示限制其只允許在本文件內被使用,不允許在其他文件中被使用。
(2)局部變量定義前加static,表示靜態變量。
變量的存儲類別:
存儲類別表示變量在計算機中的存儲位置,有3中存儲位置分別是:內存動態存儲區、內存靜態存儲區、cpu寄存器。
在定義變量時,如何指定變量的存儲類別呢(這里僅指局部變量,全局變量除外)?
(1)在定義變量時,變量名前用關鍵字auto,則變量將位于內存動態存儲區,這種變量也稱自動型變量。
(2)在定義變量時,變量名前用關鍵字static,則變量將位于內存靜態存儲區,這種變量也稱靜態性變量。
(3)在定義變量時,變量名前用關鍵字register,則變量將位于CPU寄存器,這種變量也稱寄存器型變量。
auto可以省略,也就是說,如在定義變量時沒有寫出以上3中關鍵字的任何一種,則該變量為auto型,與寫出auto是等效的。
舉例:
auto ?int a;/*或寫為int ?a;auto a;*/
static int b;/*或寫為static b;*/
register int c;/*或寫為register c;*/
以上3個變量分別位于內存動態存儲區、內存靜態存儲區、CPU寄存器中,其中,auto可以省略,在寫出auto、static、register且變量為int型是,int也可以省略。
注意:位于CPU寄存器中的變量,是沒有在內存中的,當然也沒有地址,因此不能用&取地址。
不同存儲類別變量的特點:
注意:register型變量的速度要遠遠快于其他存儲類別的變量
重新初始化:是指如果函數中一個變量在定義時賦了初值,則每次調用這個函數都要重新為變量賦初值。
只初始化一次:是指在本函數被多次調用時,只有第一次被調用才給變量賦初值,該函數以后再被調用不會再給變量重新賦初值。
局部變量和全局變量的存儲類別:
(1)全局變量:只能是static型的,即只能位于內存靜態存儲區。
(2)函數中,或{}中的局部變量:可以使auto、static或register型的。
(3)函數的形參:可以是auto或register型的,不能為static型的。
注意:在定義全局變量時,有無static都是靜態的,如有static則是另外的含義,它不允許被其他源文件使用。
預編譯處理:
c源程序的運行要經過編譯、鏈接兩個階段。這里講的預編譯在編譯之前,也就是預編譯處理-編譯-鏈接。編譯系統一般都是把預編譯、編譯兩個階段一起完成,因此在上機操作時一般我們感覺不到預編譯的存在。直接單擊編譯按鈕就可以了,編譯系統會自動先預編譯,在編譯。
預編譯處理,也稱編譯預處理。顧名思義,就是在編譯之前所做的工作。
預編譯處理有三類:包含文件(#include)、宏定義(#define、#undef)和條件編譯(#if,#elif,#else,#ifedf,#ifndef,#endif)。預編譯處理不是執行語句,只能稱其為"命令"。
(1)預編譯命令單獨占一行,以#號開頭,后無分號(;)。
(2)先預處理,在編譯。
(3)預編譯命令本身不編譯。
宏定義:
我們學習過的符號常量就是一種宏定義,例如:#define PI 3.14。它的含義是將PI定義為文本3.14的代替符號,源程序中所有PI都將首先被替換為3.14,然后在編譯,這樣所編譯的內容中就不在有PI而只有3.14,編譯的是3.14而不是PI。
#define命令的用法是:#define ?宏名 ?替換文本
它是一個命令,而不是語句,稱為宏定義。宏定義的含義是用一個宏名去代替一個替換文本。宏名是個標識符,符合標識符命名規則的任意名稱都可以。
在編譯預處理時,將對源程序中的所有宏名都用替換文本去代換,稱為宏代換或宏展開,然后在對代換后的內容進行編譯。
#define命令必須寫在函數外,其作用域為從命令起到源程序結束。
宏定義的替換文本可以是任意文本,而不僅限于數字。預處理時不做語法檢查,只有在宏展開以后編譯時,在對宏展開以后的內容做語法檢查。
注意宏展開時沒有任何計算的過程。
在源程序中引號之內的宏名是不會被替換的。例如:#define N 3+5 則語句printf("N");仍在屏幕上輸出N本身,引號內的N不會被替換為3+5.
在宏定義命令的行尾是不加分號(;)的。若加分號(;),對宏定義命令本身系統并不報錯,只不過在宏展開時,分號也會被視為替換文本的一部分,將跟隨一起替換。只要保證替換后的內容無語法錯就可以了。
(1)無參宏定義
(2)帶參宏定義
文件包含:
文件包含命令是#include,它也是預編譯處理的一種。例如:#include <stdio.h> ? ? #inluce ?"Math.h"
文件包含是指將另一文件的內容包含到當前文件的#include命令的地方,取代#include命令行。如同將另一個文件打開、全選、復制,在到#include命令的地方粘貼一般。
所包含文件的文件名可用一對<>括起,也可用""括起。其區別是:<>表示所包含的文件位于系統文件夾中;""表示位于用戶文件夾中(一般與本C源程序同一文件夾),當使用""時,若在用戶文件夾中沒有找到要包含的文件,計算機會自動再去系統文件夾中查找。