【C語言】函數和自定義函數


 

 

 

 函數,我之前也提到過一點點內容。其實函數是很好理解的,但是寫起來又十分麻煩。

一、     函數引入

我們知道,C源程序是由函數組成的。請看下面的簡單函數例子

#include <stdio.h>

main()

{

  printf(“Hello World!”);

}

在這個C程序中,main函數是一切程序的主函數,程序必須是從main函數開始執行,到main函數結束。

函數體里面只有一個輸出語句,而這個輸出語句也是調用了一個printf庫函數。

改為用戶自定義函數形式

1

2

3

4

5

6

7

8

9

#include <stdio.h>

void pr1()

{

  printf(“Hello World!”);

}

main()

{

  pr1();

}

在這個C程序中,除了main函數外還有一個程序員自己定義的函數,函數名是pr1

整個程序的編譯是從上到下。

這個程序的執行過程是先執行第6行的main函數,執行到第8行要作pr1(),此時發生了函數調用進行到第2行,然后是345pr1函數執行到第五行結束后,返回到第8行函數調用點,繼續往下執行。

 

 

幾個術語:函數定義 函數調用 函數聲明 函數返回

pr1()函數是用戶自定義函數,詳細資料看《函數定義的基本形式》

函數調用是指在main函數里面有一句pr1(),此時發生函數的調用,程序轉向執行用戶自定義函數的函數體部分。

函數返回是指pr1執行完后返回到函數調用點。

       這些術語要結合無參調用、有參調用、函數返回類型等來綜合考慮。

函數聲明是指函數的定義原則上必須在函數調用前完成,比如pr1()函數必須在main函數前完成定義,如果不是的話,就必須進行函數的聲明。

 

 

二、 函數的分類
1. 從函數定義的角度看,函數可分為庫函數和用戶定義函數兩種。
(1)庫函數
  由C系統提供,用戶無須定義, 也不必在程序中作類型說明,只需在程序前包含有該函數原型的頭文件即可在程序中直接調用。在前面各章的例題中反復用到printf 、 scanf 等函數均屬此類。
(2)用戶定義函數
  由用戶按需要寫的函數。對於用戶自定義函數, 不僅要在程序中定義函數本身, 而且在主調函數模塊中還必須對該被調函數進行類型說明,然后才能使用。
2. C語言的函數兼有其它語言中的函數和過程兩種功能,從這個角度看,又可把函數分為有返回值函數和無返回值函數兩種。
(1)有返回值函數
  此類函數被調用執行完后將向調用者返回一個執行結果, 稱為函數返回值。如數學函數即屬於此類函數。 由用戶定義的這種要返回函數值的函數,必須在函數定義和函數說明中明確返回值的類型。
(2)無返回值函數
  此類函數用於完成某項特定的處理任務, 執行完成后不向調用者返回函數值。這類函數類似於其它語言的過程。 由於函數無須返回值,用戶在定義此類函數時可指定它的返回為“空類型”, 空類型的說明符為“void”。
3. 從主調函數和被調函數之間數據傳送的角度看又可分為無參函數和有參函數兩種。
(1)無參函數
  函數定義、函數說明及函數調用中均不帶參數。 主調函數和被調函數之間不進行參數傳送。 此類函數通常用來完成一組指定的功能,可以返回或不返回函數值。
(2)有參函數
  也稱為帶參函數。在函數定義及函數說明時都有參數, 稱為形式參數(簡稱為形參)。在函數調用時也必須給出參數, 稱為實際參數(簡稱為實參)。 進行函數調用時,主調函數將把實參的值傳送給形參,供被調函數使用。

三、 函數定義的基本形式
無參函數的一般形式 
類型說明符 函數名() 

  函數體;
}
無參函數例子 
#include <stdio.h>
void pr1()
{
  printf(“Hello World!”);
}
main()
{
  pr1();
} ? 
有參函數的一般形式 
類型說明符 函數名(形式參數表) 

  函數體;
}
函數名只需遵循普通變量名規則即可。
#include <stdio.h>
void pr1(int i,int j )
{  
printf(“Hello,%d,%d”,i,j);
}
 main()
{
  int a=5,b=2;
  pr1(a,b);

#include <stdio.h>
void pr1(i,j )
int i,j;
{  
printf(“Hello,%d,%d”,i,j);
}
 main()
{
  int a=5,b=2;
  pr1(a,b);
}
有參時,發生函數調用,此時把a的值傳遞給i,把b的值傳遞給j。這種形式的函數傳遞稱為值傳遞。i,j稱為形式參數,a,b稱為實際參數。
四、 函數的返回值
發生函數調用時,允許把實參的值傳遞給形參。在函數定義時,我們還發現有函數類型說明符;允許函數有返回值。
1. 無返回值函數   本章上面的程序都屬於是無返回值函數。
2. 有返回值函數
程序一:
#include <stdio.h>
void add1(int a, int b)
{
  printf(“%d”,a+b);
}
main()
{
  add1(5,2);
}
從這個例子可以看出,實參可以是變量,也可以是表達式,或者是最直接的值。目的都是把實參的值傳遞給自定義函數中的形參。 程序二:
#include  <stdio.h>
int add2(int a,int b)
{
   int sum;
   sum=a+b;
   return sum;
}
main()
{
  int c;
  c=add2(5,2);
  printf(“%d”,c);

程序三:
#include <stdio.h>
int add3(int a,int b)
{   
   return a+b;
}
main()
{
  printf(“%d”,add3(5,2));
}
在程序二中,add2(5,2)發生了函數調用,此時把5傳給a變量,2傳給了b變量,然后完成求和的計算,最后把7作為函數值返回,返回到調用點,此時相當於c=7;然后最后輸出。
在程序三中只是對程序二進行了化簡,最后printf(“%d”,add3(5,2))完成函數調用后相當於執行了printf(“%d”,7);
注意:
1. 函數的值只能通過return語句返回主調函數。
return 語句的一般形式為: return 表達式; 
或者為:return (表達式);
該語句的功能是計算表達式的值,並返回給主調函數。 在函數中允許有多個return語句,但每次調用只能有一個return 語句被執行, 因此只能返回一個函數值。
如:
#include <stdio.h>
int add4(int a,int b,int c)
{
   if(c==1) return a+b;
   else if(c==0) return a-b;
   else return 0;
}
main()
{
  printf(“%d”,add3(5,2,1));
} add4()函數內有三個return語句,但只能執行其中的一條。

函數的類型、返回值的類型必須保持一致,如本例中都是int類型。
調用后,因為返回的是int類型,所以輸出這個返回值時,也必須是%d輸出。
或在執行c=add2(5,2);時,必須用一個int類型的變量來獲取這個返回值。即c變量應該是int c。

2. 函數值的類型和函數定義中函數的類型應保持一致。
3.不返回函數值的函數,可以明確定義為“空類型”, 類型說明符為“void”。

五、     其他幾點說明

C語言中,可以用以下幾種方式調用函數:
1.函數表達式
  函數作表達式中的一項出現在表達式中,以函數返回值參與表達式的運算。這種方式要求函數是有返回值的。例如: z=add(x,y)是一個賦值表達式,把max的返回值賦予變量z
2.
函數語句
  函數調用的一般形式加上分號即構成函數語句。例如: printf ("%D",a);scanf ("%d",&b);都是以函數語句的方式調用函數。
3.
函數實參
  函數作為另一個函數調用的實際參數出現。 這種情況是把該函數的返回值作為實參進行傳送,因此要求該函數必須是有返回值的。例如: printf("%d",max(x,y)); 即是把max調用的返回值又作為printf函數的實參來使用的。

 

函數的參數分為形參和實參兩種,形參出現在函數定義中,實參出現在函數調用中,發生函數調用時,將把實參的值傳送給形參。 函數的值是指函數的返回值,它是在函數中由return語句返回的。

六、 函數實例
1. 輸出50行hello world
程序一:
#include <stdio.h>
main()
{
  int i;
  for(i=0;i<50;i++)
    printf(“Hello World!\n”);
}
程序二: 
#include <stdio.h>
void he1()
{
  printf(“Hello World!\n”);
}
main()
{
   int i;
   for(i=0;i<50;i++)
      he1();

程序三:
#include  <stdio.h>
void he2()
{
  int i;
  for(i=0;i<50;i++)
printf(“Hello World!\n”);
}
main()
{
   he2();
}
2. 素數程序:輸入一個正整數,判斷其是否是素數。
程序一:
#include  <stdio.h>
main()
{
  int n,i;
do
{
   scanf("%d",&n);
}while(n<0);
for(i=2;i<n;i++)
  if(n%i==0)
    break;
if(i<n)  printf("No,%d 不是一個素數",n);
else    printf("Yes,%d 是一個素數",n);
}
程序二: 
#include <stdio.h>
void su1(int x)
{
  int i;
  for(i=2;i<x;i++)
  if(x%i==0)
    break;
if(i<x)  printf("No,%d 不是一個素數",n);
else    printf("Yes,%d 是一個素數",n);
}
main()
{
  int n;
do
{
   scanf("%d",&n);
}while(n<0);
su1(n);
}
程序三:
#include <stdio.h>
int su2(int x)
{
  int i;
  for(i=2;i<x;i++)
  if(x%i==0)
    break;
if(i<x)  return 0;
else    return 1;
}
main()
{
  int n;
do
{
   scanf("%d",&n);
}while(n<0);
if(su2(n))  printf("Yes,%d 是一個素數",n);
else printf("No,%d 不是一個素數",n);

程序四:
#include <stdio.h>
int su3(int x)
{
  int i;
  for(i=2;i<x;i++)
  if(x%i==0)
break;
  return x-i;
}
main()
{
  int n;
do
{
   scanf("%d",&n);
}while(n<0);
if(su3(n)>0)  printf("No,%d 不是一個素數",n);
else printf("Yes,%d 是一個素數",n);
}
寫成函數的形式的好處是如果需要判斷輸入的兩個數或者多個數是否是素數時,函數可以重復的調用。
如:
#include<stdio.h>
int su3(int x)
{
  int i;
  for(i=2;i<x;i++)
  if(x%i==0)
break;
  return x-i;
}
main()
{
  int n,m;
do
{
   scanf("%d",&n);
}while(n<0);
if(su3(n)>0)  printf("No,%d 不是一個素數",n);
else printf("Yes,%d 是一個素數",n);
do
{
   scanf("%d",&m);
}while(m<0);
if(su3(m))  printf("No,%d 不是一個素數",n);
else printf("Yes,%d 是一個素數",n);
}

函數深入分析
一、 變量的作用域
對於有多個函數的程序而言,每個函數都可以定義屬於自己的變量,那么就需要探討一
下這些變量的作用域(即作用范圍)。
在討論函數的形參變量時曾經提到, 形參變量只在被調用期間才分配內存單元,調用
結束立即釋放。 這一點表明形參變量只有在函數內才是有效的, 離開該函數就不能再使用了。這種變量有效性的范圍稱變量的作用域。不僅對於形參變量, C語言中所有的量都有自己的作用域。變量說明的方式不同,其作用域也不同。 C語言中的變量,按作用域范圍可分為兩種, 即局部變量和全局變量。
1. 局部變量
局部變量也稱為內部變量。局部變量是在函數內作定義說明的。其作用域僅限於函數內, 離開該函數后再使用這種變量是非法的。
如:
#include<stdio.h>
void pr1(int i,int j )
{  
printf(“Hello,%d,%d”,i,j);
}
 main()
{
  int a=5,b=2;
  pr1(a,b);

pr1()函數定義了形式參數,同時也屬於pr1函數的變量i,j,則此時i,j的作用域就僅僅屬於pr1這個函數的范圍。
同理, main()函數內也定義了a,b兩個變量,這兩個變量的作用域也僅僅局限於main函數內使用。
即main不能調用i的值,如
printf(“%d”,i); 這樣的語句是有問題的。

理論延伸一下,當不同的函數使用相同的變量名時,作用域的范圍是一樣的。
#include"stdio.h"
int add(int a,int b)
{
  a=a+3;
  b=b-5;
  printf("a=%d,b=%d\n",a,b);
  return a+b;
}
void main()
{
  int a,b=2;
  a=5;
  printf("a=%d,b=%d\n",a,b);
  printf("a+b=%d\n",add(a,b));
  printf("a=%d,b=%d",a,b);

程序結果:
a=5,b=2
a=8,b=-3
a+b=5
a=5,b=2
第一次輸出是main函數中的printf語句,此時輸出剛開始的main函數中的a,b的值;
然后調用add函數,第二次輸出是add函數中的a,b的值,然后返回,第三個輸出是main函數的第二個printf語句,輸出函數調用后的結果a+b;
最后輸出main函數的第三個printf語句。

雖然add函數中,a,b變量的值發生了改變,但並不影響main中的a,b的值。

2. 全局變量
如果把變量定義在函數的外面,則它屬於全局變量,全局變量也稱為外部變量,它是在
函數外部定義的變量。 它不屬於哪一個函數,它屬於一個源程序文件。其作用域是整個源程序。如:
#include"stdio.h"
int sum;
void add2(int a,int b)
{
  sum=a+b; 
}
void main()
{
  int a=5,b=2;
  add2(a,b);
  printf("a+b=%d",sum);    

sum 變量是定義在add2和main函數的外面,則它的作用域是從定義開始到程序最后。

程序結果:a+b=7

當全局變量名和局部變量名相同時,要特別注意,如:
#include"stdio.h"
int sum;
void add2(int a,int b)
{
  int sum;
  sum=a+b; 
}
void main()
{
  int a=5,b=2;
  sum=0;
  add2(a,b);
  printf("a+b=%d",sum);    
} 程序結果:a+b=0
第二行的sum是一個全局變量
add2中的sum是一個局部變量
此時add2中的sum是指這個局部變量
全局變量先賦初值為0。
調用無返回值函數,把局部變量的sum變為了7,但是全局變量的sum不變
故最后輸出的sum是全局變量的值。
二、 用數組名作為實參(引用傳遞、地址傳遞)
回顧一下,值傳遞是指把實參的值傳遞給形參,而形參的值的改變並不同時能改變實參,如:
#include"stdio.h"
int add(int x,int y)
{
  x=x+3;
  y=y-5;
  printf("x=%d,y=%d\n",x,y);
  return x+y;
}
void main()
{
  int a,b=2;
  a=5;
  printf("a=%d,b=%d\n",a,b);
  printf("a+b=%d\n",add(a,b));
  printf("a=%d,b=%d",a,b);

程序結果:
a=5,b=2
x=8,y=-3
a+b=5
a=5,b=2
此例屬於值傳遞,發生函數調用時add(a,b)把實參a的值(5)傳遞給形參x,此時x=5;把實參b的值(2)傳遞給形參y,此時y=2,然后運行add函數體,此時x,y的值發生了改變,但這種改變不會影響到main函數的a,b實參的值。
那么,下面的實例中又會是什么結果呢?
#include"stdio.h"
void change(int x,int y)
{
  int temp;
  printf("@2:x=%d,y=%d\n",x,y);
  temp=x;
  x=y;
  y=temp; 
  printf("@3:x=%d,y=%d\n",x,y);
}
void main()
{
 int a[6]={3,2,9,6,11,4},x,y;
 x=a[0];
 y=a[1];
 printf("@1:x=%d,y=%d\n",x,y);
 change(x,y);
 printf("@4:x=%d,y=%d\n",x,y);    

輸出結果:
@1:x=3,y=2
@2:x=3,y=2
@3:x=2,y=3
@4:x=3,y=2
為什么呢?
第一次@1直接輸出main函數中的x,y值。
發生函數調用,把main中的x值傳遞給change中的x,即x=3; 把main中的y值傳遞給change中的y,即y=2;
第一次@2直接輸出change函數中的x,y的初值。即@2:x=3,y=2
change函數的作用是把x和y的值互換,
然后輸出互換后的結果,即@3:x=2,y=3

函數返回后,執行main函數最后的printf,此時要注意,x和y的值是指main中的x和y,所以輸出@4:x=3,y=2
上面的例子都是屬於值傳遞類型。

當傳遞的實參變為數組名時,情況就不一樣了,如:
#include"stdio.h"
float aver(float a[5])
{int i;
float av,s=a[0]; 
for(i=1;i<5;i++) 
s=s+a[i];
av=s/5.0;
return av;
}
void main()
{
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco);
printf("average score is %5.2f",av);

程序從main函數開始運行,先讀入了5個實數到sco數組中,然后發生函數調用
av=aver(sco);把sco作為實參傳遞給了a[5],此時a數組跟sco數組是一樣的,而aver的功能是運算這個數組的平均分,返回這個平均分給av變量,最后輸出。

值得注意的是:aver函數只是利用了a數組或者是sco數組的值,並沒有改變任何數組中的數據。
#include"stdio.h"
float aver(float a[],int n)
{int i;
float av,s=a[0]; 
for(i=1;i<n;i++) 
s=s+a[i];
av=s/n;
return av;
}
void main()
{
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco[i]);
av=aver(sco,5);
printf("average score is %5.2f",av);

這個程序跟上一個程序最大的不同在於函數調用的參數上,上一個程序的調用只有一個實參,而這個程序有兩個實參,分別是數組名和一個數字,這個數字的作用是傳遞這個數組的空間大小。

同樣值得注意的是:aver函數只是利用了a數組或者是sco數組的值,並沒有改變任何數組中的數據。
要注意的是下面的一個例子:
#include"stdio.h"
void aver(float a[],int n)
{
int i;
for(i=0;i<5;i++)
printf("%5.1f ",a[i]);
}
void main()
{
float sco[5]={1.2,3,4.6,5,9.6};
int i;
for(i=0;i<5;i++)
 printf("%5.1f ",sco[i]);
printf("\n");
aver(sco,3);

#include"stdio.h"
void aver(float a[],int n)
{
int i;
for(i=0;i<5;i++)
printf("%5.1f ",a[i]);
}
void main()
{
float sco[5]={1.2,3,4.6,5,9.6};
int i;
for(i=0;i<5;i++)
 printf("%5.1f ",sco[i]);
printf("\n");
aver(sco,5);
}
上述兩個程序的結果是一樣的,都是輸出數組的結果,主要的區別是第二個實參的值。從這個例子可以看出,一旦把數組名傳遞過去,則整個數組都會全部傳遞過去,而不僅僅是數組的一部分。

這樣的數組名傳遞會產生新的問題,一旦在函數中把數組的內容發生的改變,此時會不會對原來的main數組產生影響呢?如:
#include"stdio.h"
void aver(float a[],int n)
{
int i;
float temp;
temp=a[0];
a[0]=a[1];
a[1]=temp;
for(i=0;i<5;i++)
  printf("%5.1f ",a[i]);
}
void main()
{
float sco[5]={1.2,3,4.6,5,9.6};
int i;
for(i=0;i<5;i++)
 printf("%5.1f ",sco[i]);
printf("\n");
aver(sco,3);
printf("\n");
for(i=0;i<5;i++)
 printf("%5.1f ",sco[i]);

先輸出第一次main函數的初始數組的值,然后換行,發生函數調用,把sco數組以數組名傳遞的形式傳遞給了a數組,aver函數主要把a數組中的a[0]和a[1]的值進行了互換,此時輸出互換后的a數組的全部值,然后函數返回。
最后把sco數組的值輸出,此時要注意的是,sco的數組值sco[0]和sco[1]也發生了變化。也就是說,aver函數中數組a的值的變化影響到了main函數的sco數組,這是跟我們之前講的值傳遞是不一樣的。
輸出結果:
1.2  3.0   4.6  5.0  9.6 
3.0  1.2   4.6  5.0  9.6
3.0  1.2   4.6  5.0  9.6 
三、 函數的嵌套調用
#include"stdio.h"
int fx2(int x)
{
 return x*x;
}
int fx1(int n)
{
  return 2*n+fx2(n-1)+1;
}
void main()
{
    printf("%d",fx1(3));

在main函數中調用fx1,在fx1函數中調用fx2,轉向執行fx2。
注意執行步驟。
本程序通過C/C++程序設計學習與試驗系統編譯運行。
輸出 11

四、 函數的遞歸調用
已知1!=1, 2!=1*2, 3!=3*2!  依次類推
n!=n*(n-1)!   當n>=2時
n!=1        當n=1時
#include"stdio.h"
int digui1(int n)
{
  if(n>1)
    return n*digui1(n-1);
  else
    return 1;
}

void main()
{
  int n;
  do
  {
   printf("input n>0:n=");
    scanf("%d",&n);
  }while(n<0);
  printf("%d",digui1(n));

本程序通過C/C++程序設計學習與試驗系統編譯運行。
輸入5
輸出120
五、 實例分析
1. 求歲數。
有10個同學圍坐在一起,有人問10號同學多少歲,10號說比9號大2歲,9號又說比
8號大兩歲,以此類推,1號同學說他今年12歲,請問10號同學多少歲?
即f(x)=2+f(x-1) x>=2
f(x)=1  x=1
#include "stdio.h"
int digui2(int n)
{
  if(n>1)
    return 2+digui2(n-1);
  else
    return 12;
}
void main()
{
    printf("%d",digui2(10));
}
本程序通過C/C++程序設計學習與試驗系統編譯運行。
輸出30


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM