一. 为什么需要函数
避免了重复性操作
有利于程序的模块化
函数是C语言的基本单位,类是Java、C#、C++的基本单位
C语言没有子程序概念,C语言有函数概念,函数就相当于一个子程序,允许函数单独进行编译,可以实现模块化
二. 什么叫函数
逻辑上: 能够完成特定功能的独立的代码块
物理上:
- 能够接受数据(当然也可以不接受)
- 对接受的数据进行处理
- 能够将数据处理的结果返回(当然也可以不返回)
总结:函数是个工具,为了解决大量类似问题而设计的,当做一个黑匣子
三. 函数概述
对于一个C程序而言,它所有的命令都包含在函数内。每个函数都会执行特定的任务。
一个C程序可由一个主函数和若干个其他函数构成,并且只能有一个主函数。
主函数可以调用其他普通函数,普通函数不能调用主函数,其他普通函数之间也可以相互调用。
主函数是程序的入口,也是程序的出口。
一个函数中不能定义另一个函数,只能调用其他函数
C程序的执行总是从main()函数开始,调用其他函数完毕后,程序流程回到main()函数,继续执行主函数中的其他语句,直到main()函数结束,则整个程序运行结束。
四. 如何定义函数
1. C 语言中的函数定义的一般形式如下:
return_type function_name( parameter list ) //函数头
{
body of the function //函数体( {}之间的内容 )
}
或
函数返回值类型 函数名(函数的形参列表)
{
函数的执行体
}
函数由一个函数头和一个函数主体组成。
2. 示例:
int add(int a, int b)
{
int result;
rusult = a + b;
return result;
}
下面列出一个函数的所有组成部分:
- 返回类型: 一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不要返回值,在这种情况下,return_type 是关键字 void。
- 函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 形参列表: 参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体: 函数主体包含一组定义函数执行任务的语句。
3. 定义函数的本质
详细描述函数之所以能够实现某个特定功能的具体方法
4. 形式参数和实际参数
-
在定义函数时,函数名后面括号中的变量称为:形参
-
在主调函数中,函数名后面括号中的参数称为:实参
-
参数的传递
传递值: 函数名(实参列表) 传递引用 函数名(&参数)
5. return 表达式; 的含义
- 终止被调函数,向主调函数返回表达式的值
- 如果表达式为空,则只终止函数,不向主调函数返回任何值
- break是用来终止循环和switch语句的,return是用来终止函数的
6. 返回值类型问题
-
函数的返回值的类型也称为函数的类型,因为如果函数名前面的返回值类型和函数执行体中的return 表达式中表达式的类型不同的话,则最终函数返回值的类型以函数名前面的返回值类型为准,由系统自动进行强制转换。
-
当函数没有指明返回值,或没有返回语句时,函数返回一个不确定的值。为了使函数不返回任何值,可以使用void定义无返回值类型函数。
五. 函数的分类
六. 函数的声明
注:函数调用和函数定义的顺序
若函数调用写在了函数定义的前面,则必须加函数前置声明
函数前置声明:
- 告诉编译器即将可能出现的若干个字母代表的是一个函数
- 告诉编译器即将可能出现若干个字母所代表的函数的形参和返回值的具体情况
- 函数声明是一个语句,末尾必须加分号
- 对于库函数的声明通过 #include <库函数所在的文件的名字.h> 来实现
七. 函数的调用
-
函数调用的一般形式:函数名(实参列表);
-
函数调用分类:
-
调用无参函数:函数名();
-
调用有参函数:函数名(实参列表);
若实参列表中有多个实参,各参数间用逗号隔开 C调用函数是,只能传值给函数形参 形参和实参必须个数相同、位置一一对应、数据类型必须相互兼容
-
举例
#include <stdio.h> /* 函数声明 */ int max(int num1, int num2); int main() { /* 局部变量定义 */ int a = 100; int b = 200; int ret; /* 调用函数来获取最大值 */ ret = max(a, b); printf("Max value is : %d\n", ret); return 0; } /* 函数:返回两个数中较大的那个数 */ int max(int num1, int num2) { /* 局部变量声明 */ int result; if (num1 > num2) result = num1; else result = num2; return result; }
-
-
函数的嵌套调用
C语言的函数定义都是独立的、互相平行的,C语句不允许嵌套定义函数,即一个函数内不能定义另一个函数,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。
-
函数的递归调用
-
定义:在调用一个函数的过程中又出现直接或间接调用该函数自己的。
-
递归满足的三个条件:
递归必须得有一个明确的中止条件; 该函数所处理的数据规模必须在递减(值可以增加); 这个转化必须是可解的;
-
举例
//下面的实例使用递归函数生成一个给定的数的斐波那契数列: #include <stdio.h> int fibonaci(int i) { if(i == 0) { return 0; } if(i == 1) { return 1; } return fibonaci(i-1) + fibonaci(i-2); } int main() { int i; for (i = 0; i < 10; i++) { printf("%d\t\n", fibonaci(i)); } return 0; }
-
八. 数组作为函数参数
调用有参函数时,需要提供实参
例如:sin(x)、sqrt(2.0)、max(a,b)
实参可以是常量、变量或表达式
数组元素的作用与变量相当,一般来说,凡是变量可以出现的地方,都可以用数组元素代替
1.数组元素也可以用作函数实参,其用法与变量相同,向形参传递数组元素的值
2. 数组名也可以作为实参和形参,传递的是数组第一个元素的地址
1. 数组元素作为函数实参
数组元素可以作为函数的实参,与用变量作为实参一样,按照单向值传递的方式进行传递
数组元素不能作为形参,因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元
/*
输入10个数,要求输出其中值最大的元素和该数是第几个数
*/
#include <stdio.h>
int max(int x, int y)
{
return (x>y? x:y);
}
int main(void)
{
int a[10],m,n,i;
for(i=0; i<10; i++)
{
scanf("%d", &a[i]);
}
printf("\n");
for(i=1,m=a[0],n=0;i<10;i++)
{
if(max(m,a[i])>m) //若max函数返回的值大于m
{
m=max(m,a[i]); //max函数返回的值取代m原值
n=i;
}
}
printf("The largest number is %d\n it is the %dth number.\n", m, n+1);
return 0;
}
2. 数组名可以作为函数参数
可以用数组名作为函数参数,此时实参与形参都应该用数组名,此时的数组名是整个数组的首地址
向形参(数组名或指针变量)传递的是数组首元素的地址
(1). 一维数组名作函数参数
/*
题目:有一个一维数组score,内放10个学生成绩,求平均成绩
结论:
1. 用数组名作函数参数,应该在主调函数和被调函数分别定义数组
2. 实参数组和形参数组类型应一致
3. 形参数组名获得实参数组的首元素的地址,因为数组名代表数组的首元素的地址
所以可以认为,形参数组首元素(array[0])和实参数组首元素(score[0])具有同一地址
他们共同占用同一存储单元,score[n]和array[n]指的是同一单元,它两具有相同的值
形参数组中各元素的值发生变化会使得实参数组元素的值同时发生变化
4. 形参数组可以不指定大小,在定义数组时再数组名后面跟一个空的方括号
*/
#include <stdio.h>
float average(float array[10]) //array形参数组名
//如下写法正确:float average(float array[]) 定义的average函数,形参数组不指定大小
/*
以上原因的本质是:系统对源程序进行编译时,编译系统会把形参数组处理为指针变量
即把float array[] 转换为float * array,该指针变量用来接收从实参数组传过来的地址
所以以上两者是等价的,对数组元素的访问,用下标和指针也是完全等价的
使用形参数组是为了便于理解,形参数组与实参数组各元素一一对应
*/
{
int i;
float aver;
float sum=array[0];
for(i=1;i<10;i++)
{
sum=sum+array[i];
}
aver=sum/10;
return aver;
}
int main(void)
{
float score[10];
float aver;
int i;
for(i=0;i<10;i++)
{
scanf("%f", &score[i]); //score是实参数组名
}
printf("\n");
aver=average(score); //调度average函数
printf("average score is %5.2f\n", aver);
return 0;
return 0;
}
(2). 多维数组名作函数参数
多维数组名作为函数的实参和形参,在被调函数中对形参数组定义时可以指定每一维的大小
例如:int array[3][10];
也可以省略第一维的大小说明
例如:int array[][10];
原因是:二维数组是由若干个一维数组组成的,在内存中,数组是按行存放的,因此在定义二维数组时,必须指定列数(即一行中包含几个元素)由于形参数组与实参数组类型相同,所以他们是由具有相同长度的一维数组所组成的,不能只指定第一维,而省略第二维
在第二维大小相同的前提下,形参数组的第一维可以与实参数组不同
例如:
实参数组定义为 int score[5][10]
形参数组定义为 int array[][10] 或 int array[8][10]
这时候形参数组和实参数组都是由相同类型和大小的一维数组组成,C语言编译不检查第一维大小
/*
题目:有一个3*4的矩阵,求所有元素中的最大值
结论:
在主调函数,把实参二维数组a的第1行的起始地址传递给形参数组array
所以array数组第一行起始地址和a数组第一行起始地址相同
由于两个数组列数相同,所以a[i][j]与array[i][j]同占一个存储单元,
他们本质上是相同的,对形参数组操作就是对实参数组操作
*/
#include <stdio.h>
int max_value(int array[][4])
{
int i,j,max;
max=array[0][0];
for(i=0;i<3;i++)
for(j=0;i<3;j++)
{
if(array[i][j]>max)
max=array[i][j] //把最大值放在max中
}
return 0;
}
int main(void)
{
int a[3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; //对数组元素进行赋值
printf("Max value is %d\n", max_value(a)); //调用函数
return 0;
}
九. 变量的作用域和存储方式
一个变量的生存期(作用域)在一个块内存在/生效
块就是一个{ }
一个块里面只能定义一个名字的变量
1. 按作用域分:
-
全局变量
定义:在所有函数外部定义的变量 使用范围:从定义位置开始到整个程序结束
-
局部变量
定义:在一个函数内部定义的变量或者函数的形参
/* 例如 */ void f(int i) { int j=20; } /* i和j都是局部变量 */
使用范围:只能在本函数内部使用
-
注:全局变量和局部变量命名冲突问题
在一个函数内部如果定义的局部变量的名字和全局变量的名字一样时,局部变量会屏蔽掉全局变量
-
举例
#include <stdio.h> /* 此程序用来判断主函数里面实参变量i和子函数里面的形参变量i是不是同一个变量 说明形参和实参不是一个变量 修改形参的值,不影响实参的值,因为他们作用的范围不同 程序从main函数进入之后,分配了i变量的空间,当进行f()函数调用,又为f函数分配了另外一块存储空间, 当f函数执行完之后,分配给f的函数空间就会被释放,所以普通子函数不能改变主函数的值 */ void f(int i) { i = 99; //i是局部变量 } int main(void) { int i = 6; //i是局部变量 printf("i = %d\n", i); f(i); printf("i = %d\n", i); return 0; } /* 结果: i = 6 i = 6 */
2. 按变量的存储方式分:
-
静态变量
当函数体内部用static来说明一个变量时,可以称该变量为静态局部变量。它与auto变量、register变量的本质区别:
在整个程序运行期间,静态局部变量在内存中的静态存储区中占据着永久性的存储单元, 即使退出函数后下次再进入该函数时,静态局部变量仍然使用原来的存储单元。 由于不释放这些存储单元,这些存储单元中的值得以保存,因而可以继续使用存储单元中原来的值 静态局部变量的初值是在编译时赋予的,在程序执行期间不再赋以初值, 对未赋值的局部变量,C语言编译程序自动给他赋初值为0.
-
自动变量(auto变量)
当函数内部或复合语句内定义变量是,如果没有指定存储类型,或使用了auto说明符,系统就认为所定义的变量具有自动类别。 -
寄存器变量(register变量)
寄存器变量也是自动类变量,它与auto变量的区别仅在于:用register说明变量是建议编译程序将变量的值保存在CPU的寄存器中,而不是像一般变量那样占用内存单元。
十. 如何在软件开发中合理设计函数来解决实际问题
方法要求:
一个函数的功能尽量独立、单一
多学习,多模仿牛人的代码
十一. 常用的系统函数
double sqrt(double x); 求x的平方根
int sbs(int x); 求整数x的绝对值
double fabs(double x) 求浮点数x的绝对值