根据模块化程序设计的原则,一个较大的程序一般要分为若干个小模块,每个模块实现一个比较简单的功能。在c语言中,函数是一个基本的程序模块。
(1)函数的基本概念
基本介绍:
任何一个结构化的程序都可以用三种基本结构组成:顺序结构、选择结构和循环结构,为了利用这三种结构编写程序,通常采用自顶向下,逐步细化和模块化的程序设计方法。
*函数是按规定格式书写的能完成特定功能的一段程序。
*c语言是以源文件为单位进行编译的,一个源文件由一个或多个函数组成。
*一个c程序由一个或多个源程序文件组成,可以利用c语言分别编译的特点,将源文件分别编译成目标文件,然后将这些目标文件链接到一起,形成可执行文件。
*函数与函数之间是相互独立的,没有从属关系,不能嵌套定义,但可以相互调用,主函数可以调用任何函数,而其他函数不能调用主函数。
*在c语言中,程序总是从主函数开始执行,调用完其他函数后,最终在主函数中结束,而不管主函数在程序中的位置如何。
函数的定义:
函数由函数名,参数,函数体组成。
*函数体中可以既无变量也无语句,但是不可以省略包含函数体的一对花括号,此时构成空函数。例如undo(){};空函数不从产生任何操作。在模块化程序设计中,往往首先设计基本模块,对一些次要功能的模块,可以设计成为空函数,使得程序具有完整的结构,以后再逐步填补这些函数。
编写函数,将一个给定的整数转化成相应的字符串后显示出来:
1 #include<stdio.h> 2 3 void toStr(int n){ 4 char string[10]; 5 int i=0; 6 if(n<0){ 7 putchar(‘-‘); 8 n=-n; 9 } 10 while(n>0){ 11 string[i++]=n%10+‘0‘;//将取出的整数加上ASCII码的字符‘0‘,转化为字符 12 n=n/10;//从低位到高位依次存入 ,最终i的值要比字符数组的大小大1; 13 } 14 while(--i>=0){//所以这里要首先减1; 15 putchar(string[i]); 16 } 17 } 18 int main(){ 19 20 printf("The converted string:\n"); 21 toStr(-124); 22 return 0; 23 }
函数的调用
所谓函数的调用,是指一个函数(调用函数)暂停中断本函数的运行,转去执行其他函数的过程。被调用函数执行完之后,返回到调用函数中断处继续调用函数的执行,这是一个返回过程。在调用和返回的过程中,两个函数之间通常会发生信息的交换。
*第一种调用格式是以语句的形式调用函数,一般用于调用无返回值的函数。第二种调用格式是以表达式的形式调用函数,一般用于调用有返回值的函数。
例如 y=sin(x);和toStr(‘zifu‘);
*在c的标准中,实参表的求值顺序并不是确定的。有的系统按照自右向左的顺序计算,而有的系统则相反。
实参表求值顺序的影响:
1 #include<stdio.h> 2 3 int sum(int x,int y){ 4 return (x+y); 5 } 6 7 int main(){ 8 int a=6; 9 int b; 10 b=sum(a,a+=4); 11 printf("b=%d\n",b); 12 return 0; 13 }
在上式中实参表按从右向左的方式晕眩,两个实参的值均为10,则最终结果为20。若系统按自左向右的顺序对实参表进行运算,则第一个实参为6,第二个是实参为10,程序的运行结果为16。
因此在实际中应避免这种不确定性。
例如:x=a+4;b=sum(a,x);
函数参数的传递方式:
*值传递
在函数调用时,实参将其值传递给形参,这种传递方式为值传递。在调用函数过程中,形参和实参具有不同的内存空间,实参将值传递给形参,形参的值如果发生变化并不会改变调用函数中实参的值。
*地址传递
指的是调用函数时,实参将某些量的地址传递给形参,这样实参和形参指向同一个内存空间。在地址传递下还可以是指针变量或数组名,其中,实参还可以是变量的地址。
函数的返回值:
return的作用有两个:一个是从调用的函数中退出并返回值,另一个是仅仅退出。return ();和return;另外带有返回值的return语句中的值可以没有圆括号包围。
若函数中没有return语句,函数也并非没有返回值,而是返回一个不确定的值。为了明确表示函数没有返回值,可以用void函数将函数定义为"空类型":
void output(int a,int b){
printf("%d",a+b);
}
如果函数既没有返回值又没有形参,可以定义为如下形式:
void 函数名(void){
}
例子:编写函数,求1+1/2+1/3+......+1/n的值,并在主函数中调用它:
1 #include<stdio.h> 2 3 double count(int n){ 4 int i; 5 double sum=0; 6 if(n<=0){ 7 printf("Data error!"); 8 return 0; 9 }else{ 10 for(int i=1;i<=n;i++){ 11 sum+=1.0/i;//这里不能写成1/i,否则将会进行整除运算 12 } 13 return sum; 14 } 15 } 16 17 int main(){ 18 int m; 19 double s; 20 printf("Please input the value:\n"); 21 scanf("%d",&m); 22 s=count(m); 23 printf("s=%6.2lf\n",s); 24 return 0; 25 }
函数的原型声明
c语言要求函数先定义后使用,就像变量先定义后使用的那样。如果被调用函数的定义位于调用函数之前可以不用声明,而如果自定义的函数被放在调用函数的后面则就需要在函数调用之前加上原型声明。
函数的定义是对函数功能的描述,而声明则是在调用函数中根据此信息进行相应的语法检查。函数的声明可以忽略形参的名称,只保留形参类型。
(2)数组作为函数参数
单个数组元素可以作为函数参数,此时是"值传递",而数组名作为函数实参时,向形参传递的是数组的首地址,是"地址传递"。
一维数组作为函数参数时可以不指定数组大小,而二维数组作为函数参数时可以不指定第一维的数组大小。
例子:编写函数int fun(char str[]),它的功能是判断字符串str是否是"回文数",如果是返回1,否则返回0:
1 #include<stdio.h> 2 3 int fun(char str[]){ 4 int n,k,flag=1; 5 for(n=0;str[n]!=‘\0‘;n++);//计算字符串的长度 6 for(k=0;k<n/2;k++){ //判断字符串是否为回文 7 if(str[k]!=str[n-k-1]){ 8 flag=0; 9 break; 10 } 11 } 12 return 0; 13 } 14 int main(){ 15 char s[60]; 16 printf("Please input a string:\n"); 17 gets(s); 18 if(fun(s)==1){ 19 printf("%s是回文数!\n",s); 20 }else{ 21 printf("%s不是回文数!",s); 22 } 23 return 0; 24 }
书本上的选择法给数组排序:
1 #include<stdio.h> 2 3 void sort(int array[],int n){ 4 int i,j,k,t; 5 for(i=0;i<n-1;i++){//最后一次不需要进行选择了,因此只需n-1; 6 k=i; 7 for(j=i+1;j<n;j++){ 8 if(array[k]<array[j]) 9 k=j;//用下标的方式比较灵活 10 } 11 if(k!=i){ 12 t=array[k]; 13 array[k]=array[i]; 14 array[i]=t; 15 } 16 } 17 } 18 int main(){ 19 int a[10],i; 20 printf("Enter Array:\n"); 21 for(i=0;i<10;i++){ 22 scanf("%d",&a[i]); 23 } 24 sort(a,10); 25 printf("The sorted array:\n"); 26 for(i=0;i<10;i++){ 27 printf("%4d",a[i]); 28 } 29 printf("\n"); 30 return 0; 31 }
注意选择法和冒泡法的区别。
(3)函数的嵌套调用与递归调用
c语言中函数的定义是相互平行的,函数之间没有从属关系,但是,一个函数在被调用的过程中可以调用其它函数,这就是函数的嵌套调用。
例子:任何整数n的立方都可以表示成n个相邻奇数之和,其中最大奇数为d=2m-1,而m=1+2+3+....n。试编写程序,由键盘输入n,求n的立方是那些奇数之和:
1 #include<stdio.h> 2 3 int add(int n){ 4 int i,sum=0; 5 for(i=0;i<=n;i++){ 6 sum+=i; 7 } 8 return sum; 9 } 10 11 int maxodd(int n){ 12 int m,d; 13 m=add(n); 14 d=2*m-1; 15 return d; 16 } 17 int main(){ 18 int i,n,d; 19 int flag=0; 20 printf("please input a number!\n"); 21 scanf("%d",&n); 22 d=maxodd(n); 23 for(i=0;i<n;i++){ 24 printf("%5d",d); 25 d-=2; 26 flag++; 27 if(flag==5){ 28 printf("\n"); 29 flag=0; 30 } 31 } 32 printf("\n"); 33 return 0; 34 }
函数的递归调用是c语言的一个重要的特点之一,即在调用一个函数的过程中又直接或间接的调用该函数本身。
例子:用递归的方法将一个整数n转换成字符串:
1 #include<stdio.h> 2 #include<stdlib.h> 3 void tranvers(int n){ 4 if(n/10!=0){ 5 tranvers(n/10); 6 } 7 printf("%c",n%10+‘0‘); 8 } 9 10 int main(){ 11 int n; 12 printf("Please input a inter number:\n"); 13 scanf("%d",&n); 14 printf("The string is:\n"); 15 if(n<0){ 16 printf("-"); 17 n=-1*n; 18 } 19 tranvers(n); 20 exit(0);//在stdlib.h文件中,直接退出程序。 21 return 0; 22 }
(4)变量的作用域与作用方法
变量的作用域是指一个变量能够起作用的程序范围。变量的生存期是指变量存在时间的长短。
按作用域角度分,有局部变量和全局变量;按变量存在的时间划分,有静态存储变量和动态存储变量。
局部变量是在函数内部或者复合语句中定义的变量;
全局变量对话是在函数体外定义的变量。
若要在定义全局变量之前的函数中使用该变量,则需要在该函数中使用关键字extern对全局变量进行说明:
1 #include<stdio.h> 2 3 int main(){ 4 extern int a,b; 5 int max; 6 scanf("%d%d",&a,&b); 7 max=a>b?a:b; 8 printf("max=%d",max); 9 return 0; 10 } 11 int a,b;
设置全局变量可以加强函数间的联系。可以用全局变量在函数间传递数据,从而减少函数形参的数目并增加函数返回值的数目。
变量的存储方法
在C语言中,供用户使用的存储空间共分为三部分,即程序区,静态存储区,动态存储区。其中,程序区存放的是可执行程序的机器指令;静态存储区存放的是在程序运行期间需要占用固定存储单元的变量,如全局变量;动态存储区存放的是程序运行期间根据需要动态分配存储空间的变量。
变量的存储属性就是数据在内存中的存储方法。存储方法可分为两大类:动态存储和静态存储,具体分为四种:自动型(auto),静态型(static),寄存器型(register),外部型(extern)。
1.自动变量
函数中的局部变量,如不进行专门的说明,则对他们分配和释放存储空间的工作由系统自动处理,这类局部变量称为自动变量。
void input(){
auto int a,b;
}c语言中规定,函数内定义的变量的默认存储类型是自动型,所以关键字auto可以省略。
2.局部静态变量
如果希望在函数调用结束后仍然保留其中定义的局部变量的值,则可将局部变量定义为局部静态变量。
static 类型说明符 变量名
局部静态变量是在静态存储区分配内存单元的,在整个程序运行期间都不释放。因此,在函数调用结束后,它的值并不消失,其值能保持连续性。
3.寄存器变量
寄存器变量是c语言所具有的汇编语言的特性之一。它保存在CPU的通用寄存器中,和计算机硬件有着密切的关系。寄存器变量通常用关键字“Register”进行说明。
void f(register int a){
register char ch;
}
使用寄存器变量可以缩短存取时间,通常将使用频率较高的变量设定为寄存器变量,如循环控制变量。
只有局部自动变量和形参可以作为寄存器变量;
只有int、char和指针类型变量可定义为寄存器行,而long,double,和float型变量不能设定为寄存器型,因为他们的数据长度已经超过了通用寄存器的本身的位长。
寄存器变量定义符register对编译器来说是一种请求,而不是命令。根据程序的具体情况,编译器可能自动将某些寄存器变量改为非寄存器变量。
*全局变量的存取方法
外部全局变量
在对个源程序文件的情况下,如果在一个文件中要引用其他文件中定义的全局变量,则应该在需要引入此变量的文件中,用extern进行说明。
extern只能用来说明变量,不能用来定义变量,因为它不产生新的变量,只是宣布该变量在其他地方有过定义。
*静态全局变量
在程序设计时,如果希望在一个文件中定义的全局变量仅限于被本文件引用,而不能被其他文件访问,则可以在定义此全局变量时在前面加上关键字static。此时全局变量的作用于仅限于本文件。static int x;
extern不能初始化变量:extern int a=1是错误的;
(5)内部函数和外部函数
c程序是由函数组成的,这些函数既可以在一个文件中,也可以在多个不同的文件中。根据函数的使用范围,可以将其分为内部函数和外部函数。
1.内部函数
内部函数又称静态函数,它只能被本文件中的其他函数所调用。内部函数定义的一般形式:
static 类型说明符 函数名(形式参数声明)
在定义函数时,如果使函数的使用范围仅局限于本文件,如果在不同文件中有同名的内部函数也互不干扰。这样就有利于不同的人分工编写不同的函数,而不必大担心函数是否同名。
2.外部函数
在定义函数时,如果使用关键字extern,表明此函数是外部函数。
由于函数是外部性质的,因此在定义函数时,关键字extern可以省略。在调用函数的文件中,一般要用extern说明所用到的函数是外部函数。
3.标号的生存期和作用域
在c语言中,由于函数的生存期是全程的,即从程序开始至程序结束,标号是函数的一部分,标号的生存期自然也是全程的。c语言中规定,标号的作用域仅为定义标号的函数,即不允许用goto语句从一个函数转向另一个函数。
(6)编译预处理
编译预处理是c语言编译程序的组成部分,它用于解释处理c语言源程序中的各种预处理命令。如前边程序中经常见到的#include和#define命令。该功能不属于c语言语句的组成部分,是在c编译之前对程序中的特殊命令进行的"预处理",处理的结果和程序一起再进行编译处理,最终得到目标代码。
1.宏定义
宏定义是指用一个指定的标识符来定义一个字符序列。宏定义是由源程序中的宏定义命令完成的,宏替换是由预处理程序完成的。宏定义有无参宏定义和有参宏定义两种
*无参宏定义
如果程序中使用了宏定义,在对源程序进行编译预处理时,自动将程序中所有出现的"宏定义",用宏定义中的替换文本去替换。通常称为"宏替换"或者"宏展开",宏替换是纯文本替换。
宏名一般用大写字母表示,无参宏定义常用来定义符号常量。
替换文本是一个字符序列,可以是变量,表达式,格式串等。为保证运算结果的正确性,在替换文本中若出现运算符,通常在合适的位置加括号:
宏名与替换文本之间用空格分隔;
宏定义可以出现在程序的任何位置,但必须在引用宏名之前;
在进行宏定义时,可以用以前已经定义过的宏名。
如果程序中用双撇号括起来的字符串内包含有与宏名相同的名字,预编译时并不进行宏替换:
1 #include<stdio.h> 2 #define BOOK "The Red and The Black" 3 4 int main(){ 5 printf("%s\n","BOOK"); 6 return 0; 7 }
*带有参数的宏定义
c语言允许宏带有参数,在宏定义中的参数称为形式参数,简称为形参。在宏调用中的参数称为实际参数,简称为实参。对带参数的宏,在调用时不仅要将宏展开,而且还要用实参去替换形参。
带参数的宏的定义的一般形式:
#define 宏名(形参表) 替换文本
如果定义带参数的宏,在对源程序进行预处理时,将程序中出现宏名的地方均用替换文本替换,并用实参代替替换文本中的形参:
1 #include<stdio.h> 2 #define MAX(a,b) a>b?a:b //定义带参数的宏MAX 3 #define SQL(c) c*c //定义带参数的宏 SQL 4 5 int main(){ 6 int x=3,y=4; 7 x=MAX(x,y); //引用带参数的宏MAX(a,b),并用x和y替换a和b 8 y=SQL(x); //引用带参数的宏SQR(c),并用x替换c 9 printf("x=%d,y=%d\n ",x,y); 10 return 0; 11 }
函数在定义和使用过程中形参和实参都受到数据类型的限制,宏定义中的形参和实参可以是任意数据类型。
同一个带有参数的宏,随着使用实参类型的不同,其运算结果的类型也不同。
对于共定义的形参要根据需要加上圆括号,以免发生运算错误。
在定义带参数的宏时,在宏名和带参数的括号之间不应该有空格,不然空格之后的字符序列都将被作为替换文本。
#define MAX (x,y) (x>y?x:y)
2.文件包含
文件包含也是一种预处理语句,它的作用是使一个源程序文件将另一个源程序文件全部包含进来,其一般形式为:
#include<文件名> 或 #include"文件名"
采用<>方式c编译系统将在系统指定的路径(即c库函数头文件所在的子目录)下搜索<>中制定文件,称为标准方式
采用""方式,系统首先在用户当前工作的目录中搜索包含的文件,若找不到,再按系统指定的路径搜索包含文件
根据需要,用户可以自定包含类型声明、函数原型、全局变量、符号常量等内容的头文件,采用这种方法包含到文件中,可以减少不必要的重复工作,提高编程效率。
3.条件编译
一般情况下,c源程序的所有行都参与编译过程,所有的c语句都生成到目标程序中,如果只是想把源程序中的一部分语句生成目标代码,可以使用条件编译
利用条件编译可以方便程序的调试,增强程序的可移植性,从而使程序在不同的软硬件环境下运行。此外在大型应用车程序中,还可以利用条件编译选取某些功能进行编译,生成不同的应用程序,供不同用户使用。
主要有两种条件编译命令的形式:
*if格式
#if 表达式
程序段1
[*else
#else
程序段2]
#endif
功能:首先计算表达式的值,如果为非0,就编译"程序段1",否则编译"程序段2"。
*ifdef格式
#ifdef 宏名
程序段1
[#else
程序段2]
#endif
功能:首先判断"宏名"是否被定义过,若已经定义过,则编译程序段1,否则编译程序段2。
1 #include<stdio.h> 2 3 int main(){ 4 float r=4.5,s; 5 #ifdef PI 6 s=PI*r*r; 7 #else 8 #define PI 3.14 9 s=PI*r*r; 10 #endif 11 printf("s=%f\n",s); 12 return 0; 13 }
(7)函数应用举例
1.通过键盘输入一个较大的正整数n(n大于等于6),并验证从6~n之间所有的偶数都可以分为两个素数之和的
1 #include<stdio.h> 2 #include<math.h> 3 //此函数判断一个正整数是否是素数 4 int prime(int n){ 5 int i,k; 6 k=sqrt(n); 7 for(i=2;i<=k;i++) 8 if(n%i==0) 9 return 0; 10 return 1; 11 } 12 13 int main(){ 14 int a,b,n,k; 15 //用来强制用户输入一个大于6的整数 16 while(1){ 17 printf("Please input a number>=6:\n"); 18 scanf("%d",&n); 19 if(n>=6){ 20 break; 21 } 22 } 23 for(k=6;k<=n;k+=2){//一次取6~n之间的所有整数 24 for(a=3;a<=k/2;a+=2){ 25 if(prime(a)){ 26 b=k-a; 27 if(prime(b)){ 28 printf("%d=%d+%d\n",k,a,b); 29 break; 30 } 31 } 32 } 33 } 34 return 0; 35 }
原文:http://www.cnblogs.com/MenAngel/p/5362238.html