【C语言】函数和自定义函数

简介:   函数,我之前也提到过一点点内容。其实函数是很好理解的,但是写起来又十分麻烦。 一、     函数引入 我们知道,C源程序是由函数组成的。请看下面的简单函数例子 #include main() {   printf(“Hello World!”); } 在这个C程序中,main函数是一切程序的主函数,程序必须是从main函数开始执行,到main函数结束。

  函数,我之前也提到过一点点内容。其实函数是很好理解的,但是写起来又十分麻烦。

一、     函数引入

我们知道,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

相关文章
|
25天前
|
存储 编译器 C语言
爱上C语言:函数递归,青蛙跳台阶图文详解
爱上C语言:函数递归,青蛙跳台阶图文详解
|
25天前
|
编译器 程序员 C语言
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
爱上C语言:什么C语言函数不太会,那你千万不要错过这篇(函数篇)
|
11天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)
|
5天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧
|
8天前
|
C语言
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)
pta浙大版《C语言程序设计(第3版)》 习题6-4 使用函数输出指定范围内的Fibonacci数 (20分)
|
8天前
|
C语言
pta 浙大版《C语言程序设计(第3版)》题目集 习题6-6 使用函数输出一个整数的逆序数 (20分)
pta 浙大版《C语言程序设计(第3版)》题目集 习题6-6 使用函数输出一个整数的逆序数 (20分)
|
8天前
|
C语言
(浙大版《C语言程序设计(第3版)》 习题6-5 使用函数验证哥德巴赫猜想 (20分)
(浙大版《C语言程序设计(第3版)》 习题6-5 使用函数验证哥德巴赫猜想 (20分)
|
10天前
|
安全 C语言
【C语言】strcpy与strncpy函数的使用和模拟实现
【C语言】strcpy与strncpy函数的使用和模拟实现
5 0
|
10天前
|
C语言
【C语言】字符分类函数与字符转换函数
【C语言】字符分类函数与字符转换函数
9 1
|
10天前
|
程序员 编译器 C语言
C语言之函数与参数
C语言之函数与参数
7 0