C語言函數基礎預習

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;/*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。注意被調的函數在調用時是不知道可變參數的正確數目的,程序員必須自己在代碼中指明結束條件。至於爲什麼它不會知道參數的數目,在看完這幾個宏的內部實現機制後,自然就會明白。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章