目录
前言
我们在编写程序时如何来组织程序呢?对于C来说,是把函数用作构件块,也就是说C语言是由函数组成的。我们在前面的介绍中,已经使用过printf()、scanf()等函数,这些函数都是C语言库中的函数,而我们现在需要进一步地学习如何创建自己的函数。
一、函数的定义及重要性
维基百科对函数的定义:子程序
- 在计算机科学中,子程序(英语: Subroutine, procedure, function, routine, method,subprogram, callable unit),是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏(功能的实现)。这些代码通常被集成为软件库。
简单来说,函数就是完成特点功能的代码块。
那么我们为什么要使用函数呢?一方面,使用函数可以省去许多编写重复的代码,如果程序要多次完成某项功能,那么只需要编写一个实现该功能的函数,使用时调用该函数即可。另一方面,即使在不需要重复执行该功能时,使用函数也能使程序更加模块化,提高程序的可读性。
二、函数的分类
其主要分为两大类:
- 库函数
- 自定义函数
为什么会有库函数呢?
在编写一段代码时,我们迫不及待地想知道结果是否正确,想把这个结果打印到我们的屏幕上,这需要频繁地使用printf()函数;有时我们也会频繁地做一些字符串的拷贝工作(strcpy);又或者我们通过编程实现一些计算,经常会计算n的k次方这样的运算(pow)。像以上这些功能,它们都是我们描述的基础功能,并不是业务性的代码。也就是说,在编写代码时,这些功能会频繁使用,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列的库函数。
C语言中常见的库函数:
- IO函数(printf、scanf、getchar、putchar)
- 字符串操作函数(strcmp、strlen)
- 字符操作函数(toupper)
- 内存操作函数(memcpy、memcmp、memset)
- 时间/日期函数(time)
- 数学函数(sqrt、pow)
- 其他库函数
以strcpy和memset为例:(注意:需要引用相应函数的头文件)
int main()
{
//函数strcpy
char arr1[20] = { 0 };
char arr2[] = { "hello bit" };
strcpy(arr1, arr2);
printf("%s", arr1);
//函数memset
char arr[] = "hello sb";
memset(arr, 'x', 5);
printf("%s", arr);
return 0;
}
自定义函数:
我们通过一个例子来进入对自定义函数的学习:创建一个打印40个星号的函数,并在一个打印菜单的程序中调用该函数。
#include<stdio.h>
#define WIDTH 40
void printf_star(void)
{
int count = 0;
for (count = 1; count <= WIDTH; count++)
{
putchar('*');
}
putchar('\n');
}
int main()
{
printf_star();
printf(" 1.enter the game\n");
printf(" 2.the game settings\n");
printf(" 3.exit the game\n");
printf_star();
return 0;
}
其运行结果为:
自定义函数一共包括三个部分:函数名、返回值类型、函数参数。函数和变量一样有多种类型。任何程序在使用函数之前都要声明该函数类型,上述程序的函数定义为:void printf_star(void) 圆括号表明printf_star是一个函数名,第一个void是函数类型,void类型表明函数没有返回值,而第二个void,即括号中的void表示该函数不带参数。
三、函数的参数
1、实际参数(实参)
函数调用中的参数为实参。 真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
2、形式参数(形参)
函数定义中的参数为形参。 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
形参实例化之后其实相当于实参的一份临时拷贝。
以下面一个例子来讲解实际参数与形式参数
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int sum1(int a, int b)
{
int c = 0;
a = a + 1;
b = b + 1;
c = a + b;
return c;
}
int sum2(int* pa, int* pb)
{
int c = 0;
*pa = *pa + 1;
*pb = *pb + 1;
c = *pa + *pb;
return c;
}
int main()
{
int x = 0;
int y = 0;
int num1 = 0;
int num2 = 0;
scanf("%d %d", &x, &y);
num1 = sum1(x, y);
printf("sum1:x = %d y = %d sum1 = %d\n", x, y, num1);
num2 = sum2(&x, &y);
printf("sum2:x = %d y = %d sum1 = %d\n", x, y, num2);
return 0;
}
上述程序中a,b与*pa,*pb为我们所说的形式参数,而x,y与&x,&y为实际参数。对于sum1函数的形参与实参,通过监视功能我们可以看出形参与实参所占用的是两个不同的内存空间,这便更加明确了形参实例化之后其实相当于实参的一份临时拷贝。如下图所示:
四、函数的调用及链式访问
1、传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。在函数参数的那个例子中 sum1函数就是使用的传值调用,实参与形参占用的空间不同。
2、传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。所以形参需要使用指针接收。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。内部与外部相互关联。如下图:
3、嵌套调用
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的,一个函数内部可以调用其他函数。见如下例子:
#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()函数,来实现打印。
注意:函数可以嵌套调用,都是不允许嵌套定义。
4、链式访问
把一个函数的返回值作为另外一个函数的参数,称为链式访问。
示例一:
#include <stdio.h> #include <string.h> int main() { char arr[20] = "hello "; int ret = strlen(strcat(arr, "world")); printf("%d\n", ret); return 0; }
函数介绍:strcat()函数是字符串拼接函数,strlen()函数是计算字符串长度函数。main函数中的第二行语句;int ret = strlen(strcat(arr, "world"));便是将strcat()函数的返回值作为strlen()函数的参数。
示例二:
#include <stdio.h> int main() { printf("%d", printf("%d", printf("%d", 43))); return 0; }
该程序运行结果为4321。(一个小的知识点:printf()函数的返回值为打印在屏幕上的字符的个数)程序先由最内层的printf()函数打印出34,该printf()函数的返回值为2做为第二层printf()函数的参数,在屏幕上继续打印出2,因为此时只打印了一个字符,故第二层printf()函数的返回值为1,1做为最外层printf()函数的参数继续打印在屏幕上。
五、函数的声明和定义
1、函数的声明
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的。
函数声明的基本格式:函数类型+函数名+参数+分号
int sum(int a, int b);
在上述的程序中我们并没有见到声明的代码该程序也能正常运行,这与函数定义处在整个程序中的位置有关。当函数定义位于函数使用之前,则可以不用进行声明;当函数定义位于函数使用之后,则需要在函数使用之前对函数进行声明。
2、函数的定义
函数的定义是指函数的具体实现,交待函数的功能实现。如下代码段便是一个函数定义,与声明相比少了分号,多了函数功能的实现。
int sum(int a, int b)
{
int c = 0;
c = a + b;
return c;
)
一个好习惯:一般我们会将函数的声明放入头文件(.h)中,而函数功能的实现放入另一个头文件中,在.c文件中实现程序的测试。