C語言函數的定義 全球熱頭條
C語言函數的定義
引導語:函數表示每個輸入值對應唯一輸出值的一種對應關系。這種關系使一個集合里的每一個元素對應到另一個(可能相同的)集合里的唯一元素。以下是小編分享給大家的C語言函數的定義,歡迎參考學習!
一、函數的定義
【資料圖】
一個函數包括函數頭和語句體兩部分。
函數頭由下列三不分組成:
函數返回值類型
函數名
參數表
一個完整的函數應該是這樣的:
函數返回值類型 函數名(參數表)
{
語句體;
}
函數返回值類型可以是前面說到的某個數據類型、或者是某個數據類型的指針、指向結構的指針、指向數組的指針。指針概念到以后再介紹。
函數名在程序中必須是唯一的,它也遵循標識符命名規則。
參數表可以沒有也可以有多個,在函數調用的時候,實際參數將被拷貝到這些變量中。語句體包括局部變量的聲明和可執行代碼。
我們在前面其實已經接觸過函數了,如abs(),sqrt(),我們并不知道它的內部是什么,我們只要會使用它即可。
這一節主要講解無參數無返回值的函數調用。
二、函數的聲明和調用
為了調用一個函數,必須事先聲明該函數的返回值類型和參數類型,這和使用變量的道理是一樣的(有一種可以例外,就是函數的定義在調用之前,下面再講述)。
看一個簡單的例子:
void a(); /*函數聲明*/
main()
{
a(); /*函數調用*/
}
void a() /*函數定義*/
{
int num;
scanf(%d,&num);
printf(%d ,num);
}
在main()的前面聲明了一個函數,函數類型是void型,函數名為a,無參數。然后在main()函數里面調用這個函數,該函數的作用很簡單,就是輸入一個整數然后再顯示它。在調用函數之前聲明了該函數其實它和下面這個程序的功能是一樣的:
main()
{
int num;
scanf(%d,&num);
printf(%d ,num);
}
可以看出,實際上就是把a()函數里面的所有內容直接搬到main()函數里面(注意,這句話不是絕對的。)
我們前面已經說了,當定義在調用之前時,可以不聲明函數。所以上面的程序和下面這個也是等價的:
void a()
{
int num;
scanf(%d,&num);
printf(%d ,num);
}
main()
{
a();
}
因為定義在調用之前,所以可以不聲明函數,這是因為編譯器在編譯的時候,已經發現a是一個函數名,是無返回值類型無參數的函數了。
那么很多人也許就會想,那我們何必還要聲明這一步呢?我們只要把所有的函數的定義都放在前面不就可以了嗎?這種想法是不可取的,一個好的程序員總是在程序的開頭聲明所有用到的函數和變量,這是為了以后好檢查。
前面說了,在調用之前,必須先聲明函數,所以下面的做法也是正確的(但在這里我個人并不提倡)。
main()
{
void a();
a();
}
v oid a()
{
int num;
scanf(%d,&num);
printf(%d ,num);
}
一般來說,比較好的程序書寫順序是,先聲明函數,然后寫主函數,然后再寫那些自定義的函數。
既然main()函數可以調用別的函數,那么我們自己定義的函數能不能再調用其他函數呢?答案是可以的??聪旅娴睦樱?/p>
void a();
void b();
main()
{
a();
}
void a()
{
b();
}
void b()
{
int num;
scanf(%d,&num);
printf(%d ,num);
}
三、C語言讀書筆記--函數
先來看看函數的一般形式,嘗試寫一個加法的函數:
思路是這樣的:首先得有頭文件,頭文件之后就得寫主函數,主函數的內部應該就是加法的過程,我們將所有加法的語句都拿出來組成一個函數。代碼如下:
#include
int add(int a, int b);
int main()
{
int result = add(3,5);
printf("sum is %d ", result);
return 0;
}
int add(int a, int b)
{
int sum;
sum = a+b;
return sum;
}
這是一個最簡單的函數,描述了一個加法函數的定義和調用的過程。
int add(int a, int b) 成為函數的首部。
有了首部之后,就得考慮一件事情,將首部復制之后,加上一個分號,粘貼在主函數之前,作為函數的原型聲明。試想,我們在主函數里邊是不是要先定義變量result才能使用result?那么函數的道理也是一樣的,當程序運行到主函數中語句“int result = add(3,5);”的時候,如果向上沒有尋找到add()的定義,那么編譯器一定就會報錯。所以要不然添加函數的原型聲明,要不然就將函數的定義直接寫在主函數之前。
函數首部int add(int a, int b)中的第一個int,即add之前的這個int稱為函數的類型。表明這個函數將要返回一個整數類型的值。這個類型可以是C語言中任何被允許的數據類型,包括void,意為無返回值類型,即這個函數不需要返回任何的值。
函數首部int add(int a, int b)中的add稱為函數的名字,簡稱函數名。
函數首部int add(int a, int b)中int a和int b稱為函數的形式參數。這里形式參數理論上可以有無窮多個,當然,現實情況下3-5個就已經算是很多了;形式參數中,即使a和b都是int類型的,也要分別定義才行;形式參數可以在函數中直接使用,無須再次定義;形式參數是用來告訴調用者,你應該給我傳遞來什么樣子的數據,我好利用你給我的數據在函數中進行計算。
int add(int a, int b){}中的{}就是函數體的內容了。函數需要進行的所有的操作都要放在這對大括號中。想必大家也看到了函數體中最后有一條語句是return,這條語句起到的作用就是返回函數計算的結果,在這個程序中就是將加法的結果返回給主函數。需要注意的是,函數的類型和返回值的類型必須嚴格一致!
函數的定義到此為止,接下來講講函數的調用方式。只要定義好函數,通過函數名(實際參數1,實際參數2,實際參數n)這種方式就可以調用函數了。例如主函數中的“int result = add(3,5);”,就是調用了add函數。這里,3和5稱為實際參數,即你究竟想讓函數幫你計算哪兩個數的加法結果,你就在這個括號里邊寫哪幾個數字。必須要嚴格遵守的`規定:實際參數和形式參數必須一一對應,數量應該相同,類型也保持一致。
理解了這幾點之后,一個基本的函數就已經可以寫出來了。接下來來個題目嘗試一下:
輸入精度e,使用公式求π的近似值,精確到最后一項的絕對值小于e。公式:π=1-1/3+1/5-1/7+...
代碼:
//首先得有頭文件
#include
#include//后邊要使用到fabs絕對值函數
//然后就是主函數了
int main(void)
{
double pi, e; //定義所需變量
double f_pi(double e); //原型聲明。函數名只要符合命名規則即可 //因為要求小于e,所以也將這個e傳遞過去
printf("enter e: "); //輸入的提示
scanf("%lf", &e); // double類型的e對應%lf,記住不要缺少&
printf("pi=%lf ", f_pi(e) ); // 函數返回的是個double類型的值,直接輸出
return 0;
}
double f_pi(double e) //函數首部,形參和實參一定要對應,可以重名
{
int denominator, flag;
double item, sum;
//請注意“先定義,然后賦初值再使用”的好習慣!!!
flag = 1; //負責變換正負符號的變量
denominator = 1; //分母初值為1,第一項的1為1/1
item=1.0; //存放每一項的值
sum=0;
while(fabs(item)>=e) //滿足條件就循環
{
item=flag*1.0/denominator; //計算每一項的值。flag控制符號
//1.0必須寫出小數位,否則整項就變成一個整型值
sum+=item; //累加
flag = -flag; //符號正負切換
denominator = denominator + 2;//分母遞增
}
return sum; //sum的類型和函數的類型必須一致
}
函數的定義和調用其實并不難理解,相信很多人困擾在參數的傳遞上,接下來總結一下函數參數傳遞的幾種方式:
正常的參數調用,例如int、float、double等一一對應的傳遞。
無參數,也無返回值。例如下列代碼就只是為了輸出一些語句。這種做法在語法上是被允許的,但是并不推薦這么寫。
void printf()
{
printf("hello world!");
}
3. 參數是數組的名字。我們知道數組的名字是個地址,那么如果實參是數組名的話,我們可以將形參設置成指針,指向實參傳遞過來的數組的首地址。
4. 參數是指針。如果實參是指針,那么形參肯定也得是指針。保持類型一致即可,然后在函數內部再對指針進行操作。
5. 參數是結構體。如果實參是結構體,一般來說我們使用結構體指針來做形參比較合適。
還是在此分割一下吧,說了這么多,可能很多人在問問什么函數定義這么麻煩,還要定義函數,直接都寫在main函數中多方便?
非也!
C語言是一個過程化的語言,C語言中的主函數其實是用來主導程序的進程和數據的流動方向的。如果將主函數寫的過于復雜,我們閱讀程序的結構就會非常的費力。
四、C語言中函數回調
什么是回調函數?
簡而言之,回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。
為什么要使用回調函數?
因為可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,只是存在一個具有某種特定原型、某些限制條件(如返回值為int)的被調用函數。
如果想知道回調函數在實際中有什么作用,先假設有這樣一種情況,我們要編寫一個庫,它提供了某些排序算法的實現,如冒泡排序、快速排序、shell排序、shake排序等等,但為使庫更加通用,不想在函數中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,想讓庫可用于多種數據類型(int、float、string),此時,該怎么辦呢?可以使用函數指針,并進行回調。
回調可用于通知機制,例如,有時要在程序中設置一個計時器,每到一定時間,程序會得到相應的通知,但通知機制的實現者對我們的程序一無所知。而此時,就需有一個特定原型的函數指針,用這個指針來進行回調,來通知我們的程序事件已經發生。
下面是自己寫的一個簡單的回調函數,相比其他的那些復雜的代碼,這個更容易理解:
#include
#include
void perfect(int n)
{
int i=1;
int count=0;
for(i=1;i { if(0==n%i) { count+=i; } } if(count==n) printf("%d是完數 ",n); else printf("%d不是完數 ",n); } void myCallback(void (*perfect)(int ),int n) { perfect(n); } int main() { int n; printf("請輸入一個正整數 "); scanf("%d",&n); myCallback(perfect,n); return 0; } 五、C語言中的刷新和定位函數 一.fflush 1.fflush的原型如下: intfflush(FILE *stream); 2.當需要立即把輸出緩沖區的數據進行物理寫入時,應該使用這個函數。例如調用fflush函數保證調試信息實際打印出來,而不是保存在緩沖區中直到以后才打印。 二.定位函數 1.在正常情況下,數據以線性的方式寫入,這意味著后面寫入的數據在文件中的位置是在以前所有寫入數據的后面。C同時支持隨機訪問I/O,也就是以任意順序訪問文件的不同位置。隨機訪問是通過在讀取或寫入前,先定位到文件中需要的位置來實現的。 2.定位函數原型: 1>long ftell(FILE*stream); 2>intfseek(FILE *steam,long offset,intfrom); 3.ftell函數返回流的當前位置。即:下一個讀取或寫入將要開始的位置距離文件起始位置的偏移量。該函數允許保存一個文件的當前位置。 1>在二進制流中,這個值就是當前位置距離文件起始位置之間的字節數。 2>在文本流中,這個值表示一個位置,但它并不一定準確地表示當前位置和文件起始位置之間的字符數,因為有些系統將對行末字符進行翻譯轉換。但是,ftell函數返回的值總是可以用于fseek函數中,作為一個距離文件起始位置的偏移量。 4.fseek函數允許你一個流中定位。這個函數將改變下一個讀取或寫入操作的位置。它的第 1個參數是需要改變的流。它的第2和第3個參數標識文件中需要定位的位置。 1>試圖定位到一個文件的起始位置之前是一個錯誤。定位到文件尾并進行寫入將擴展這個文件。定位到文件尾之后并進行讀取將導致返回一條“到達文件尾”的信息。 2>在二進制流中,從SEEK_END進行定位可能不被支持,所以應該避免。 3>在文本流中,如果from是SEEK_CUR或SEEK_END,offset必須是零。如果from是SEEK_SET,offset必須是一個從同一個流中以前調用ftell所返回的值。 5.用fseek改變一個流的位置會帶來三個副作用。 1>首先,行末指示字符被清除。 2>其次,如果在fseek之前使用ungetc把一個字符返回到流中,那么這個被退回的字符會被丟棄,因為在定位操作以后,它不再是“下一個字符”。 3>最后,定位允許你從寫入模式切換到讀取模式,或者回到打開的流以便更新。
詞條內容僅供參考,如果您需要解決具體問題
(尤其在法律、醫學等領域),建議您咨詢相關領域專業人士。