一、概念
函数是将一行或多行的代码组织在一起,可以实现一定功能的程序。函数在C/C++语言中也叫做子程序,在JAVA等一些面向对象的语言中也叫方法。
二、定义
所谓定义一个函数,就是要设计和编写一个函数的意思。一个完整的函数定义,包括函数头和函数体两部分。函数头就是函数大括号上方的第一行,函数体就是大括号内的一行或多行的代码。
---------------------------有返回值
int test(int a){----声明具体类型:int、double......
int b=0;
b=b+a;
return b; ------返回值与函数定义时一致
}
---------------------------无返回值
void test(int a){---------声明为void
int b=0;
b=b+a;
printf("%d",b);
}
三、参数
一个函数的参数列表中可能没有参数,也可能有1个、2个或者多个参数
形式参数,就是在定义函数时在函数头的参数列表内的参数。在C语言中,形参可以认为是被调函数内,用来接收主调函数传递数据的临时变量。
实际参数,在主调函数中调用一个函数时,函数名后面括弧中的参数,实参可以是常量、变量、表达式或者有返回值的函数等。实参的个数必须与被调函数的形参个数一致,并且每个参数的类型也尽量一致。
简单地说,实参与形参在个数和类型上,必须要一一对应的,否则编译的时候就可能会出现错误。
当形参是整数或浮点数,如果实参的类型小于形参时编译也不出错。如果实参的类型大于形参时(例如:形参是整数实参是浮点数),编译会出现警告(warring); 调用返回值是void类型的函数时,如果将返回值打印或赋值给其他变量,编译时会出错。
形式参数原理
形式参数的本质,是被调函数中的一些临时变量。形式参数在被调用前,先由主调函数用实际参数给予赋值。而其他被调函数内的普通临时变量,则是在程序进入被调函数之后再执行初始化或赋值。
四、返回值
一、有返回值的函数的特点是: a)函数的返回值类型是具体的类型(如:int、short和double等),而不是void类型; b)有返回值类型的函数内,必须有return语句用于返回运算结果给主调函数; c)有返回值类型的函数内,如果没有return语句的话代码编译时会出错。 d)有返回值类型的函数返回后,往往会把返回值传递给一个变量保存起来。 二、无返回值的函数的特点是: a)函数的返回值类型是void类型,而不是其他任何具体的类型; b)无返回值类型的函数内,即使没有return语句编译也不会出错; c)无返回值类型的函数内也可以有return语句,但return之后也不附带任何变量或表达式; d)主调函数在调用无返回值类型的函数时,不允许使用变量或表达式来接收被调函数的返回值。
返回值原理
被调函数向主调函数传递返回值时,是先把要返回的数据存入到CPU寄存器EAX中。等被调函数结束后主调函数再从寄存器EAX中,取出该数据用于赋值、计算以及打印等
a)当程序在被调函数中执行了return语句之后,要返回的数值会被放入到eax寄存器中; b)当程序离开主调函数回到主调函数之后,eax基础器内的数值就是被调函数传递过来的返回值; c)当返回值的空间大于寄存器的空间时,return语句将要返回的数据直接传递回到主调函数的栈空间内。例如,32位操作系统的CPU寄存器是4个字节,如果返回值是double类型eax就存放不下了。
五、软件工程管理
1.函数声明
被调函数函数的定义必须是在主调函数的上方编写,这叫做程序的可见性(主调函数可以看见被调函数)。否则,如果把将被调函数的定义,编写在主调函数的下方编译时会出错。
C语言编译系统是由上往下编译的。一般被调函数放在主调函数下方的话,主调函数的上方就要有被调函数的声明。否则由上往下的编译系统将无法识别,就如通变量必须先声明后使用一样。主调函数之前要么有被调函数的定义,要么有被调函数的声明,否则将无法调用。
可见性测试
main.c
------------------------------------------
------错误
#include <stdio.h>
int main(){
tl();
return 0;
}
void tl(){ ------函数定义
printf("i am tl()");
}
-------------------------------------------
------正确
#include <stdio.h>
void tl(){ ------函数定义 放在主调函数上
printf("i am tl()");
}
int main(){
tl();
return 0;
}
函数声明就如同一本书籍的目录一样。虽然声明中没有函数的实际代码,函数声明可以引导编译器找到被调函数的函数定义(函数体)。 为一个函数编写函数声明很简单,只要将一个函数的函数头复制一份并在其尾部加分号即可。
函数声明测试
main.c
------------------------------------------
#include <stdio.h>
void tl(); ------函数声明
int main(){
tl();
return 0;
}
void tl(){ ------函数定义
printf("i am tl()");
}
此外,编写函数声明还有以下几个要点:
a)在函数的定义中,每个形式参数都必须有变量名,而这些变量名在声明中省略(不省略视乎也可以); 例如:double power(double,int); double power(double a,int b);
b)要十分注意的问题是:函数的声明与定义在参数类型与返回值上,必须保持完全一致;
c)如果一个软件工程中,有函数的声明没有函数定义编译时将会出现连接错误;
d)一个软件工程中一个函数的定义只能一份,但一个函数的声明可以有多份。
2.跨文件的函数调用
每个源文件的上方都有或多或少的函数声明,这些函数声明如同一座座桥梁,在各个源文件之间的起到了相互连接的作用。如果删除掉这些源文件中的各个函数声明,函数之间的相互调用将被断开,编译时会出现错误或运行时出现混乱。
---------------------
main.c
#include <stdio.h>
void tl();--------如果没有,就报错
int main(){
tl();
return 0;
}
-------------------------------------
kua.c
#include <stdio.h>
void tl(){
printf("i am kua.c tl()");
}
3.头文件
在C语言大型软件工程中,通常由头文件【Header files】和源文件【Source files】组成。源文件的后缀是.c,而头文件的后缀是.h,主要用来统一存放函数的声明。头文件在被源文件包含之后,再进行跨文件调用函数就会非常方便。
添加一个头文件menu.h。将test.c源文件中的函数声明,转移到新建立的menu.h中去进行统一管理,并在test.c源文件中包含menu.h。
-------------------------------头文件
menu.h
void test();--------函数声明
-------------------------------
main.c
#include "menu.h"
int main(){
return 0;
}
--------------------------------
test.c
#include "menu.h"
void test(){ ---------------------------函数定义
}
使用尖括号<>包含头文件,代表系统只在系统中查找这个头文件,如果系统中没有这个头文件编译时会出现错误
使用双引号””包含头文件,代表优先在系统中查找这个头文件,如果系统中没有这个头文件再到工程目录下找。
六、内存四区
在C语言中,有4个常用的内存区域,分别是堆(自由存储区)、栈、全局/静态区和常量区。
栈区: 在函数内定义的普通变量或数组等,使用的就是栈内存区域;
在一般的C/C++编译器中,栈内存的总额缺省是1MB左右。如果函数调用层次太深的话,可能造成满栈(栈内存不足)之后发生程序崩溃。
全局/静态区: 在函数内定义静态变量,或者在函数外定义全局变量,使用的就是全局/静态的内存区域。
常量内存区: 全称是字符串常量区。在任何一个C语言软件工程的代码中,任何一个字符串使用的都是常量内存区域。换句话说,除了预编译语句以外,一个工程中所有被双引号包含的文字使用的都是字符串常量区内存。
堆: 是在程序运行过程中,根据需要任意地分配或大或小的内存区域。你可以在堆内申请1个字节的内存空间来使用,也可以申请100MB甚至几个GB的空间来使用。
除了栈、全局和常量三区以外的所有剩余的内存区域,都可以被堆申请过来使用。因此,堆区也叫做动态内存区域或自由存储区。
总结
|