什么是函数
在数学中,我们学过函数的概念,都知道y=f(x),但是在C语言中,所谓的函数跟数学中的函数还是有很大的区别的,我们看看维基百科中对于函数的定义:
函数又叫做子程序,在计算机科学中,子程序是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
简单的理解就是在C语言中函数就是用来专门完成某项任务的部分代码,和其他函数相对独立,各司其职。通常需要输入和输出,需要使用某个函数时,申明引用即可。
函数的分类——库函数和自定义函数
库函数
什么是库函数?举个非常常见的例子,我们在输入输出时都会使用printf、scanf,而这些函数都在<stdio.h>这个库函数中,类似的还有很多,为什么会需要库函数呢?我们在编程时,会运用到一些常用的操作,比如输入输出,字符对比,内存设置等等,如果没有库函数,每个程序员都需要自己编写函数,不仅麻烦,而且可移植性差,因此我们就需要库函数的存在,将我们常用的函数进行封装,当我们需要引用时直接使用就可以了。
简单总结一下我们可以将库函数分为一下几种
- IO函数 (printf 、scanf)
- 字符串操作函数(strlen、strcmp)
- 字符操作函数 (tolower 、toupper)
- 内存操作函数 (memset、memcpy、memmove)
- 时间/日期函数 (time)
- 数学函数 (sin 、sqrt、abs、pow)
- 其他库函数
特别需要注意的是,在使用库函数的时候必须要使用#include来引用头文件。
自定义函数
在现实生活中,即使我们将库函数学习的特别牛,也是没用太大用处的,因为我们会遇到各种各样的问题,而这些问题库函数不可能全部概括到,因此我们就需要自定义函数。顾名思义,自定义函数就是自己设置的函数用来解决各式各样的问题。
那么函数的基本形式是什么样子的呢?
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
我们来看个例子:
#include <stdio.h>
int get_max(int x, int y)
{
return (x > y ? x: y);
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int max = get_max(a, b);
printf("max=%d", max);
return 0;
}
这个代码是求两个数的较大值,不用函数的方法我们都会写,用函数是不是也会呢?在这段代码中我们自定义了一个get_max的函数,我们可以看到他包含了函数名,函数参数,返回类型。在主函数中用到时需要声明一下,如此才能够引用。a和b我们叫做实际参数(实参),x和y叫做形式参数(形参)。
我们再看一个例子:编写代码,交换两个变量里的内容。
我们不用函数来写:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = 0;
printf("交换前:a=%d,b=%d\n", a, b);
c = a;
a = b;
b = c;
printf("交换后:a=%d,b=%d\n", a, b);
return 0;
}
这种代码我们都会写,那么如何用函数编写这种问题的代码呢?
我们先看这样一组代码
#include <stdio.h>
void Swap(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d,b=%d\n", a, b);
Swap(a, b);
printf("交换后:a=%d,b=%d\n", a, b);
return 0;
}
我们看看打印的结果是什么?
我们发现结果前后没有发生变化,为什么呢?这是大家都会经常犯的一个错误,在这个代码中,a和b将各自的值传给了对应的x和y,也就是说函数Swap()仅仅是将x和y的值进行了交换对a和b的值没有进行任何的操作,这就导致交换前后没有变化,我们可以这样理解,当实参传给形参值的时候,他们空间上是没有联系的,因此对形参的操作对实参没有影响,形参就像是实参的copy一样。那么我们如何解决呢?既然没有联系,我们就可以建立联系,我们采用指针,不就可以将实参和形参建立联系了嘛?我们来看这样一组代码。
#include <stdio.h>
void Swap(int* px, int* py)
{
int z = 0;
z = *px;
*px = *py;
*py = z;
}
int main()
{
int a = 20;
int b = 10;
printf("交换前:a=%d,b=%d\n", a, b);
Swap(&a, &b);
printf("交换后:a=%d,b=%d\n", a, b);
return 0;
}
我们可以看到,通过指针传参可以实现效果,很好理解,在使用指针传参时我们发现,我们将a和b的地址传给px和py,因此px和py指向a和b的内容,所以在Swap()函数中我们就能够完成a和b的交换。
函数参数
经过上面两个例子我们直到了形参和实参,那么具体怎么理解呢?
- 实际参数(实参)
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
- 形式参数(形参)
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
函数调用
函数调用分为两种:传值调用和传址调用。
根据我们前面例子的介绍我们知道传值调用就是传数值,传址调用就是传地址。具体解释如下:
- 传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
- 传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。
根据解释我们知道了两者的区别,那么具体我们如何使用呢?当我们只需要进行传值给形参,而不需要对实参进行修改时,我们应该采用传值操作;当我们需要对实参进行数值的修改时,我们采用传址调用。
函数的嵌套调用和链式访问
嵌套调用
举个例子:
#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
我们可以看到,在函数three_line()中调用了new_line函数,在函数main()中调用three_line()函数,这就是嵌套调用,但是函数不能嵌套定义,什么意思呢?举个例子:
//错误示范
#include <stdio.h>
int main()
{
viod menu()
{
printf("hehe\n");
}
return 0;
}
这样是错误的。
链式访问
把一个函数的返回值作为另外一个函数的参数。举个例子:
#include <stdio.h>
int main()
{
int len = strlen("abc");
printf("%d\n", len);
printf("%d\n", strlen("abc"));//链式访问
return 0;
}
我们可以看到strlen()函数将返回值作为了printf函数的参数,所以叫做链式访问。
好了,这次的分享就到这里,如有错误,希望大家能指正,谢谢各位!