目录
1.函数的概念
“函数”这个词我们大多是在数学中听到的,比如:一次函数 y = kx + b ,其中 k 和 b 都是常数,给自变量 x 一个值,就得到应变量 y 。
在C语言中也引入了函数(function)的概念,在一些地方翻译为子程序。C语言中的函数就是一个完成某项特定任务的一小段代码。这样的代码是有一定写法和调用方法的。
C语言的程序其实是由无数个小的函数组合而成的,也就是说,一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项任务的话,这个函数也可以复用的,比如你想判断闰年可以用,他想判断闰年也可以用,提升了开发软件的效率。
总结函数的好处:
1.可以拆分任务
2.可以复用,提升开发效率
在C语言中我们一般会见到两类函数:
1.库函数——是现成的,可以直接使用的函数
2.自定义函数——是根据需要,自己设计、创造的函数
2.库函数
2.1 标准库和头文件
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;C语言的国际标准ANSI C规定了一些常用的函数的标准,比如 scanf ——它的名字、参数、返回类型、功能等等,被称为标准库。
不同的编译器厂商根据ANSI提供的C语言标准就给出一系列函数的实现,像微软的 MSVC ,苹果的 clang 等等,不同厂商函数的实现可能有差异,但是程序员使用时基本无感,这些函数就被称为库函数。
我们常用的 printf ,scanf 等等都是库函数,库函数属于是已经现成的函数,我们只需要学会就可以直接使用。库函数的使用使得一些简单的功能就不需要程序员自己实现了,一定程度上提升了效率;同时库函数的执行效率和质量都更有保障。
各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,像字符相关的、内存相关的,等等,然后不同的这样一系列的、功能类似的库函数会放在同一个头文件里进行声明。
这里提供库函数的相关头文件:https://zh.cppreference.com/w/c/header
2.2 库函数的使用方法
库函数的学习和查看工具很多,比如:
C / C++ 官方的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
举例:sqrt
double sqrt (double x);
//sqrt 函数名
//x 是函数的参数,表示调用sqrt函数需要传递一个double类型的值
//double 是返回值类型 - 表示函数计算的结果是double类型的值
2.2.1 功能
Compute square root 计算平方根
Returns the square root of x.(返回平方根)
2.2.2 头文件包含
使用库函数必须包含#include 对应的头文件
使用举例:
#include <stdio.h>
#include <math.h>
int main()
{
double ret = sqrt(16.0);
printf("%lf\n", ret);
return 0;
}
3.自定义函数
如果库函数能干所有的事情,那还要程序员干什么?所以更加重要的是自定义函数。
自定义函数和库函数一样,有函数名,返回值类型和函数参数。但不一样的是这些都是我们自己来设计的。
3.1 函数的语法形式
ret_type fun_name(形式参数)
{
}
ret_type 返回类型
fun_name 函数名
{}内是函数体
3.2 函数的举例
1.写一个函数可以找出两个整数中的最大值。代码如下:
#include <stdio.h>
//设计的函数
int get_max(int x, int y)
{
return (x > y) ? (x) : (y);
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max = %d\n", max);
return 0;
}
函数的参数部分需要交代清楚:参数的个数,每个参数的类型是啥,形参的名字等等
我们可以根据实际需要来设计函数,它的函数名,参数,返回类型都是可以灵活变换的
小结:1.函数名是自定义的,根据实际情况起名字;
2.参数的个数也可以根据实际情况来确定,可以有零个参数,也可以有多个参数;
3.函数可以返回值,也可以不返回,函数不返回时,返回类型写 void。
4.函数的参数
在函数的使用过程中,把函数的参数分为实参和形参。
这里放一个例子:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r = Add(a, b);
printf("%d\n", r);
return 0;
}
4.1 实参
在如上的代码中,2~5 行是Add函数的定义,有了函数以后,在12行调用Add函数。
我们将12行调用Add函数时,传递给函数的参数 a 和 b ,称为实际参数,简称实参。实参就是真实传递给函数的参数。
4.2 形参
在如上的代码中,第2行定义函数的时候,在函数名Add后的括号中写的 x 和 y,称为形式参数,简称形参。
之所以被称为形式参数是因为如果只定义了Add函数,而不去调用的话,Add函数的参数 x 和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才能向内存申请空间,这个过程就是形式的实例化。
这里我们按F11进入调试界面监视变化,这里需要知道一点 :
X86 表示32位环境——编译生成的32位的程序
X64 表示64位环境——编译生成的64位的程序
F10 逐步监视,进入Add函数按F11,从上图可知:当实参传递给形参的时候,形参其实是实参的一份临时拷贝,对形参的修改是不会影响实参的。
5.return语句
在函数的设计中,经常会用到return语句,这里讲一下return语句的注意事项。
· return 后边可以是一i个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果。
· return 后边也可以什么都没有,直接写 return ;这种写法适合函数返回类型时 void 的情况。
· return 返回的值和函数返回类型不一致,系统会自动将返回的值影式转换为函数的返回类型。
· return 语句执行后,函数就彻底返回,后边的代码不再执行。
· 如果函数中存在 if 等分支的语句,则要保证每种情况下都有 return 返回,否则会出现编译错误。
6.数组做函数参数
在使用函数解决问题时,有时会将数组作为参数传递给函数,在函数内部对数组进行操作。
比如,写一个函数将一个整型数组的内容全部置为1,再写一个内容打印数组的内容。
基本函数形式如下:
#include <stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
set_arr();//将数组的元素全部置为1
print_arr();//打印数组的内容
return 0;
}
补充几个重点知识:
1.函数的形参和函数的实参个数匹配;
2.函数的实参是数组,形参也可以写成数组形式;
3.形参如果是一维数组,数组的大小可以忽略不计;
4.形参如果是二维数组,行可以省略,但是列不能省略;
5.数组传参,形参是不会创建新的数组的;
6.形参操作的数组和实参的数组和实参的数组是同一个数组。
完整如下:
#include <stdio.h>
void set_arr(int arr2[], int sz2)
{
for (int i = 0; i < sz2; i++)
{
arr2[i] = -1;
}
}
void print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d", arr[i]);
}
printf("\n");
}
int main()
{
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
print_arr(arr1, sz1);
set_arr(arr1,sz1);
print_arr(arr1, sz1);
return 0;
}
7.嵌套调用和链式访问
7.1 嵌套调用
嵌套调用就是函数之间互相调用,就像乐高积木一样,众多积木相互连接配合才能搭建出精美的成品,各个函数就像零件,函数间相互调用才能写出更大的程序。a函数调用了b函数,而b函数又调用了c函数,这就叫做嵌套调用。
假设现在需要计算某年某月有多少天,如下:
#include <stdio.h>
//一年的基本天数分别为31 28/29 31 30 31 30 31 31 30 31 30 31
int get_days_of_month (int y, int m)
{
int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[m];
//闰年的二月是29
if (leap_year(y) && m == 2)
{
day++;
}
return day;
}
int leap_year(int y)
{
if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
return 1;
else
return 0;
}
int main()
{
int year = 0;
int month = 0;
scanf("%d %d", &year, &month);
int d = get_days_of_month(year, month);
printf("%d", d);
return 0;
}
7.2 链式访问
链式访问指的是将一个函数的返回值作为另一个函数的参数,像链条一样将函数串联起来。上面的嵌套调用是在函数定义的时候调用一个函数。举例:
#include <stdio.h>
#include <string.h>
int main()
{
//size_t len = strlen("abcdef");
//printf("%d", len);
printf("%d", strlen("abcdef"));
return 0;
}
将 strlen 返回的值直接作为 printf 的参数,像链条一样引动。这里应用一个先前讲过的例子(详见http://t.csdnimg.cn/U2k4O):
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
输出结果为4321(注意:“ ”也是字符)。
8. 函数的声明和定义
8.1 单个文件
依然还是这个程序:
#include <stdio.h>
//一年的基本天数分别为31 28/29 31 30 31 30 31 31 30 31 30 31
//函数的定义
int get_days_of_month (int y, int m)
{
int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[m];
//闰年的二月是29
if (leap_year(y) && m == 2)
{
day++;
}
return day;
}
//函数的定义
int leap_year(int y)
{
if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
return 1;
else
return 0;
}
int main()
{
int year = 0;
int month = 0;
scanf("%d %d", &year, &month);
int d = get_days_of_month(year, month);//函数的调用
printf("%d", d);
return 0;
}
注意,在这里函数的定义是在函数的调用之前,没有警告或报错;如果将函数的定义移到函数调用之后,会这样(虽然还是可以运行):
所以,函数的使用一定要满足先声明再使用。
8.2 多个文件
当代码较多的时候,不会将代码全部放在一个文件当中,往往根据程序的功能将代码拆分成多个文件。
一般地,我们将函数的声明、类型的声明放在头文件(.h)中,将函数的实现放在源文件(.c)中。
Add.c
函数的定义
int Add(int x, int y)
{
return x + y;
}
Add.h
//函数的声明
int Add(int x, int y);
test.c
#include <stdio.h>
#include "Add.h"//自定义的函数头文件使用“”
int main()
{
int a = 10;
int b = 20;
//函数调用
int c = Add(a, b);
printf("%d", c);
return 0;
}
函数的相关知识先总结到这里(汗流浃背……),后续详见 C——函数(2)(努力学习ing)