1.函數的聲明與定義
函數的聲明就是聲稱一個函數的名字,隻是說明函數的名字,不涉及函數的實作,即沒有函數體,是以函數的聲明隻包括前三個部分。
函數的定義就是确定一個函數的意義,即讓函數具有某項功能,但是這裡可不是隻有函數體,你要指明這個函數體就是那個函數,是以函數的定義包含了一個函數的是以部分。
2.形式參數與實際參數的差別
形式參數就是定義函數時候的參數表,隻是定義了調用時參數的個數、類型和用來引用的名字,并沒有具體的内容。形參未被調用時,不占存儲單元。形參隻在調用過程中占用存儲單元。
在調用函數時,給形參配置設定存儲單元,實參可以是常量、變量或者表達式,且要與形參類型一緻!而且實參要有确定的值,在調用過程中實參将值賦給形參,并将實際參數對應的數值傳遞給形式參數;
調用結束後,形參單元被釋放,實參單元仍然保留 并且維持原值。是以說,實參是調用函數傳遞的具體資料。實參對形參資料傳遞時時單向傳遞。在存儲單元中是不同的單元。
下面這段程式中
#include<stdio.h>
int fun(int a,int b)
{a+=10; b=a+b*2;
return a+b;
}
void main()
{
int x=3,y=5,z;
z=fun(x,y);
printf("%d %d %d\n",x,y,z);
}
a和b都是形式參數,x和y都是實際參數。
程式從主函數開始運作,等到運作到z=fun(x,y)開始調用被調用函數,以被調用函數的形式進行運算,然後把計算的值傳回後指派給z,這樣一個運算就算完成了。在調用過程中,形參a和b的值都發生了改變,但是在main函數中,x和y的值都未發生變化。是以說實參向形參的值的傳遞是單向的。
3.函數中參數是如何傳遞的
實參出現在主調函數當中,當函數調用時,主調函數把實參的值傳送給被調函數的形參,進而實作函數間的資料傳遞。傳遞方式有兩種:值傳遞和位址傳遞方式。當形參定義為變量時,實參可以是常量、變量和表達式,這種函數間的參數傳遞為值傳遞方式。值傳遞的特點是參數的“單向傳遞”;
數組元素(下标變量)作為函數的參數進行的資料傳遞是值傳遞方式,數組名(數組首位址)、數組元素的位址(&arr[0])作為函數參數進行的資料傳遞是位址傳遞方式。
實參為數組名是,形參接收時可以有三種形式:帶下标的數組名(arr[0])。不帶下标的數組名(arr)、可接收位址值的指針變量名(*a)。由于是參數組和形參數組都指向同一段記憶體單元,故它們操作的是同一批資料,是以形參的改變就是改變了實參中的資料。
4.如何編寫有多個傳回值的函數
方法1:利用全局變量
分析:全局變量作為C語言的一個知識點,雖然我們都了解它的特點,但在實際教學過程中應用得并不是很多。由于全局變量的作用域是從定義變量開始直到程式結束,而對于編寫有多個傳回值的C語言函數,我們可以考慮把要傳回的多個值定義成全局變量。當函數被調用時,全局變量被更改,我們再把更改後的全局變量值應用于主調函數中。函數被調用後被更改後的全局變量值即為函數的數個傳回值。下面以一個執行個體示範該方法的應用。
執行個體1:編寫函數求3個數中的最大值與最小值。
方法:把最大值、最小值分别定義成2個全局變量max、min,在使用者自定義函數中把求出來的最大值與最小值分别賦給全局變量max、min。函數調用完畢後全局變量的max、min值即儲存了函數要求傳回的值。程式參考代碼如下:
#include "stdio.h"
#include "conio.h"
int max,min;
void max_min(int a,int b,int c)
{max=min=a;
if(max if(max if(min>b)min=b;
if(min>c)min=c;
}
main()
{int x,y,z;
printf(" 請輸入3個整數:\n");
scanf("%d,%d,%d",&x,&y,&z);
max_min(x,y,z) ;
printf("三個數中的最大值為:%d;最小值為:%d",max,min);
getch();
}
調試結果如下:
請輸入3個整數:
5,-6,2
三個數中的最大值為:5;最小值為:-6
注意:該方法雖然可以實作有多個傳回值的函數,但由于全局變量不能保證值的正确性(因為其作用域是全局,是以程式範圍内都可以修改它的值,如果出現錯誤将非常難以發現),并且全局變量增加了程式間子產品的耦合,是以該方法要慎用。
3方法2:傳遞數組指針
分析:在教學過程中,我們知道C語言函數參數的傳遞方式有值傳遞與位址傳遞。當進行值傳遞時,主調函數把實參的值複制給形參,形參獲得從主調函數傳遞過來的值運作函數。在值傳遞過程中被調函數參數值的更改不能導緻實參值的更改。而如果是位址傳遞,由于傳遞過程中從實參傳遞過來的是位址,是以被調函數中形參值的更改會直接導緻實參值的更改。是以,我們可以考慮把多個傳回值作為數組元素定義成一個數組的形式,并使該數組的位址作為函數的形式參數,以傳址方式傳遞數組參數。函數被調用後,形參數組元素改變導緻實參改變,我們再從改變後的實參數組元素中獲得函數的多個傳回值。以下執行個體示範該方法的應用。
執行個體2:編寫函數求一維整形數組的最大值與最小值,并把最大值與最小值傳回給主調函數。
方法:以指針方式傳遞該一維數組的位址,然後把數組的最大值與數組的第一個元素交換,把數組的最小值與最後一個元素交換。函數被調用完畢後,實參數組中的第一進制素為數組的最大值,實參數組中最後一個元素為數組的最小值,進而實作傳回數組的最大值與最小值的功能。程式參考代碼如下:
#include "stdio.h"
#include "conio.h"
void max_min(int *ptr,int n)
{int i,j,k;
int *temp;
*temp=*ptr;
for(i=0;i {
if(*ptr<*(ptr+i))
{
k=i;
*temp=*ptr;
*ptr=*(ptr+k);
*(ptr+k)=*temp ;
}
if(*(ptr+n-1)>*(ptr+i))
{
j=i;
*temp =*(ptr+n-1);
*(ptr+n-1)=*(ptr+j);
*(ptr+j)= *temp ;}
}
}
main()
{
int A[6],i;
for(i=0;i<6;i++)
scanf("%d",&A[i]);
max_min(A,6);
printf("max=%d, min=%d\n \n",A[0],A[5]);
getch();
}
調試結果如下:
請輸入6個整形數,以空格隔開:
5 8 9 32 -6 4
max=32,min=-6
注意:該方法适用于多個傳回值的資料類型一緻的情況。當傳回值資料類型不一緻時,不适用該方法。
4方法3:傳遞結構體指針
分析:結構體作為教學中的一個難點,教材對它介紹的内容并不多,應用的執行個體更是少之又少,是以學生對于結構體普遍掌握情況不理想。其實,編寫傳回多個值的C語言函數,也可以考慮采用結構體的方式去實作。通過方法2,我們知道如果傳回的數個數值的資料類型不一緻,可以通過定義全局變量實作有多個傳回值的C語言函數,也可以考慮把要求傳回的數個值定義成一個結構體,然後同樣以傳遞結構體指針方式把結構體的指針傳遞給形參結構體指針,那麼函數中對形參結構體的修改即是對實參結構體的修改,函數被調用後擷取的實參結構體成員即為函數的多個傳回值,下面以執行個體示範該方法的應用。
執行個體3:編寫一個使用者自定義函數,允許使用者錄入學生的基本資訊(包括學号、姓名、所屬班級、總評成績),并傳回這些基本資訊給主調函數。
方法:把學生基本資訊定義成一個結構體,在使用者自定義函數中傳遞該結構體的指針,則自定義函數中對結構體成員的錄入操作即是對實參結構體成員的錄入操作,進而實作多個傳回值。參考代碼如下:
#include "stdio.h"
#include "conio.h"
struct inf{
char xh[12];
char name[20];
char class[15];
int chj;
};
main(void)
{
struct inf a1;
void xxxx(struct inf *ptr);
printf("請輸入學号,姓名,班别,總評成績,以空格隔開:\n") ;
xxxx(&a1);
printf("學号:%s,姓名: %s,班别:%s,總評成績:%d",a1.xh, a1.name,a1.class,a1.chj);
getch();
}
void xxxx(struct inf *ptr)
{
char xh1[12],name1[20],class1[15];
int chj1;
scanf("%s%s%s%d",xh1,name1,class1,&chj1);
strcpy(ptr->xh,xh1);
strcpy(ptr->name,name1);
strcpy(ptr->class,class1);
ptr->chj=chj1;
}
調試結果如下:
請輸入學号,姓名,班别,總評成績,以空格隔開:
200102LiLi200185
學号:200102,姓名: LiLi,班别:2001,總評成績:85
注意:當函數要求傳回的多個值是互相聯系的或者傳回的多個值資料類型不一緻時可以采用該方法。
5結束語
對于以上這三種方法,如果想要傳回的數個值資料類型一緻,可以考慮采用方法2;而對于不同資料類型的傳回值,如果各個數值之間是互相聯系的,則方法3較為合适;方法1雖然在很多情況下都可以實作多個傳回值的C語言函數,但畢竟全局變量應用過程中有很多危險,要慎重使用。
通過對以上幾種方法的分析講解,在教學過程中,學生再遇到這樣的問題時,就能根據傳回值的情況選擇合适的途徑去實作多個傳回值的C語言函數。另外,如果再遇到類似的無法用教材知識點去直接解決的問題時,他們基本都能舉一反三地嘗試采用間接方式去解決。
5.為什麼要使用回調函數?
首先,回調函數也是函數,就像白馬也是馬一樣。它具有函數的所有特征,它可以有參數和傳回值。其實,單獨給出一個函數是看不出來它是不是回調函數的。回調函數差別于普通函數在于它的調用方式。隻有當某個函數(更确切的說是函數的指針)被作為參數,被另一個函數調用時,它才是回調函數。就像給你一碗飯,你并不能說它是中飯還是晚飯一樣,隻有當你在某個時候把它吃掉了你才明确它是中飯還是晚飯(這個比喻貌似有點挫。領會精神就好,哈哈)。
那麼問題來了,為什麼我們要把函數作為參數來調用呢,直接在函數體裡面調用不好嗎?這個問題問的好。在這個意義上,“把函數做成參數”和“把變量做成參數”目的是一緻的,就是以不變應萬變。形參是不變的,而實參是變的。唯一不同的是,普通的實參可以由計算機程式自動産生,而函數這種參數計算機程式是無法自己寫出來的,因為函數本身就是程式(要是程式可以寫程式的話那就是超級人工智能了),它必須由人來寫。是以對于回調函數這種參數而言,它的“變”在于人有變或者人的需求有變。
C++ Primer裡面舉了個例子就是排序算法。為了使排序算法适應不同類型的資料,并且能夠按各種要求進行排序,機智的人類把排序算法做成了一個模版(在标準模版庫STL裡),并且把判斷兩個資料之間的“大小”(也可以是“位元組數”,或者其他某種可以比較的屬性)這個任務(即函數)當成一個參數放在排序算法這個函數的參數清單裡,而把它的具體實作就交給了使用排序算法的人。這個判斷大小的函數就是一個回調函數。
6.函數中的可變參數問題
C語言程式設計中有時會遇到一些參數個數可變的函數,例如printf()函數,其函數原型為:
int printf( const char* format, ...);
它除了有一個參數format固定以外,後面跟的參數的個數和類型是可變的(用三個點“…”做參數占位符),實際調用時可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
⑴由于在程式中将用到以下這些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在這裡是variable-argument(可變參數)的意思。
這些宏定義在stdarg.h中,是以用到可變參數的程式應該包含這個頭檔案。
⑵函數裡首先定義一個va_list型的變量,這裡是arg_ptr,這個變量是存儲參數位址的指針.因為得到參數的位址之後,再結合參數的類型,才能得到參數的值。
⑶然後用va_start宏初始化⑵中定義的變量arg_ptr,這個宏的第二個參數是可變參數清單的前一個參數,即最後一個固定參數。
⑷然後依次用va_arg宏使arg_ptr傳回可變參數的位址,得到這個位址之後,結合參數的類型,就可以得到參數的值。
⑸設定結束條件,這裡的條件就是判斷參數值是否為-1。注意被調的函數在調用時是不知道可變參數的正确數目的,程式員必須自己在代碼中指明結束條件。至于為什麼它不會知道參數的數目,在看完這幾個宏的内部實作機制後,自然就會明白。