上一章<一起学习C语言:初谈指针> 中,我们了解了指针和进程中的内存区域,以及动态管理内存空间方式。本章节,我们分析函数的用法,以及编写不同形式的函数。
函数
1. 函数的概念
2. 函数定义
3. 函数声明与程序编译过程
目录预览
在“1.6.4 main函数用法”小节中,我们了解到“C语言大部分程序由main函数主导着程序正常执行”,也可以理解为“C语言大部分程序由main函数作为程序执行入口(出口)”。但是到目前为止,我们还没有分析函数是什么,除main函数外还有哪几种函数。
接下来,我们通过本章节内容了解函数的多种形式,编写出自定义函数。
在接触本章节内容之前,我们已经了解了部分函数功能。比如printf(函数)用于向标准输出设备按规定格式输出信息,scanf(函数)由标准输入设备向变量空间中输入信息。对于这些函数来说,我们不需要了解函数的内部实现,也可以完成函数的使用。另外,我们也可以编写自定义函数,用来完成程序所需要的功能。下面我们来编写第一个自定义函数:
【例8.1】 编写一个名称为“Add”的加法运算函数,并且在函数中输出运算结果。
#include
void Add() (1)
{
int a = 2, b = 6, c;
c = a + b;
printf(“运算结果:%d”, c);
}
int main()
{
Add();
return 0;
}
示例结果:
运算结果:8
(1):严格意义上函数应写成void Add(void),表示无返回值、无参函数,并且不接受任何参数。
上述示例中,我们根据功能要求编写了加法运算函数。当然,如果功能要求为减法、乘法、除法运算函数,我们也可以采用类似的形式来实现。
从总体来看,函数属于功能载体,即代码的封装。
编写函数适用在以下场景:
(2):C语言工程支持库形式提供或使用函数。当我们为别人提供外部库时,库功能接口只以函数声明的形式暴露在外,而函数功能实现部分由编译器封装在动态库文件中。
在 C 语言中,函数由函数头和函数体两部分组成。其中函数头包括:返回类型(返回值)、函数名称和参数列表。
函数定义形式:
返回类型 函数名称(参数列表)
{
函数体
}
函数定义举例:
void func(int a)
{
printf(“a的值为:%d”, a);
}
返回类型:
函数执行完成后返回指定类型的值,一般用来表示函数执行成功或函数执行失败。比如函数返回类型为int,通常情况下函数返回值为0表示函数执行成功。在部分情况下,也可以指定返回类型为void,表示无返回值函数。
函数名称:
当我们编写一个函数时,一般通过函数名称标识函数功能。比如函数名称为Add,可以理解为这个函数是加法运算函数。
参数列表:
当我们的函数需要由外部传入参数时,可以在参数列表中声明形式参数。当函数被调用时,应根据形式参数类型向参数列表传入值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。另外,参数列表允许不声明形式参数。
函数体:
函数功能实现部分,由函数体起始位置、函数体实现、函数返回值、函数体结束位置四部分构成。
【例8.2】 编写一个名称为“Add”的加法运算函数,增加形式参数int a和int b,并返回运算结果。
int Add(int a, int b)
{
return a + b;
}
int main()
{
int value = Add(2, 3);
printf(“函数执行结果:%d”, value);
return 0;
}
示例结果:
函数执行结果:5
上述示例中,Add函数头组成部分:
函数体起始位置:
标示函数从这里开始执行,由大括号左部分构成( { )。
函数体实现:
函数体实现部分一般包括类型定义、执行语句、函数调用三部分构成,这些都不属于必要条件(编译器允许函数体实现部分不存在)。
函数返回值:
函数返回值一般用来判断函数是否正确执行或者返回指定类型结果。当然,支持C99或以上版本的编译器允许不增加return语句,但是通常不建议这样编写。
函数体结束位置:
标示函数在这里结束执行,由大括号右部分构成( } )。
在编写函数功能时,如果函数中声明和形参同名的变量,程序编译时会提示形参“XX”重定义错误。针对这种情况,可以通过不同的命名风格来区分。比如形参变量为int a,在函数中可以声明int na来区分。实际编程时,函数和变量应根据使用的平台或编译器命名规范编写。
当我们调用自定义函数时,如果把函数定义写在被调用函数下方,在不同的编译器中编译程序都会产生异常信息 (3),参考下面代码:
int main()
{
int value = Add(2, 3);
printf(“函数执行结果:%d”, value);
return 0;
}
int Add(int a, int b)
{
return a + b;
}
gcc编译工程提示:
warning: implicit declaration of function ‘Add’ [-Wimplicit-function-declaration]
int value = Add(2, 3);
MSVC2010编译提示:
error C3861: “Add”: 找不到标识符
针对上述情况,我们可以通过在被调用函数上方增加函数声明 (4) 来解决。函数声明由返回类型(返回值)、函数名称、参数列表和分号组成,通常称为函数原型(函数头)部分,而函数定义通常称为函数实现部分。
函数声明形式:
返回类型 函数名称(参数列表);
函数声明举例:
int Add(int, int); //参数列表中,只声明形参类型
int Add(int a, int b); //参数列表中,声明形参类型和对象名称
编译一个同时具有声明和定义的函数时,函数声明中的返回类型以及形参类型、形参个数和顺序要与函数定义保持一致,形参对象名称允许不相同。
接下来,在参考代码中增加Add函数声明:
int Add(int a, int b);
int main()
{
int value = Add(2, 3);
printf(“函数执行结果:%d”, value);
return 0;
}
int Add(int a, int b)
{
return a + b;
}
再次编译程序,没有产生异常信息。
(3):异常信息主要分为“警告”和“错误”提示信息,其中“警告”信息一般用来提示某一行或某一段代码编写不规范并继续编译之后的代码,“错误”信息一般用来提示某一行或某一段代码编写格式错误并停止编译之后的代码。
(4):在C语言中,函数的“定义”和“声明”属于两个概念。“函数定义”属于函数功能的实现(实体),它由函数头(函数名称、返回类型和参数列表)和函数体组成的一个完整的、独立的函数单位。而“函数声明”的作用则是把函数名称、返回类型以及参数列表中的形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。
现在我们明白了函数定义和函数声明的用法,但编译过程中如何找到我们的程序调用的函数呢?接下来,我们简单分析一下C程序编译过程。
首先,我们在ubuntu系统中创建一个名为test.c文件(在控制台中输入touch test.c),参考图3-1:
接下来,我们在test.c文件中编写执行代码,参考图3-2:
#include
int Add(int , int); //int Add(int a, int b)
int main()
{
int value = Add(2, 3);
printf(“result:%d.”, value);
return 0;
}
int Add(int a, int b)
{
return a + b;
}
C程序编译分为四部分:
一. 预处理:
预处理器属于文本替换程序,用来把所有以“#”开头的预处理指令替换为实际内容并保存在目标文件中。在我们的test.c文件中有一行“#include
接下来,在控制台中输入gcc -E test.c -i test.i //这句指令表示为test.c文件做预处理工作,并把处理后的代码保存在test.i文件中
二. 编译为汇编代码:
编译器属于高级语言替换程序,用来把预处理后的代码转换为汇编代码并保存在目标文件中。
接下来,在控制台中输入gcc -S test.i -o test.s //这句指令表示为test.i文件中的代码转为汇编代码,并保存在test.s文件中
三. 生成为目标文件:
汇编器属于汇编语言翻译程序,用来把汇编代码转换为机器码(机器语言)并保存在目标文件中。
程序编译过程中,如果某个函数声明或函数定义在被调用函数之后找到或不存在时,一般在这一步产生警告信息,比如“warning: implicit declaration of function ‘Add’ [-Wimplicit-function-declaration]”。
接下来,在控制台中输入gcc -c test.s -o test.o //这句指令表示为test.s文件中的代码转为机器码,并把保存在test.o文件中
四. 链接:
链接器属于模块整合程序,用来把所有目标文件链接起来生成一个可执行程序(执行文件,executable file)或外部库文件。
程序编译过程中,如果调用到未定义的函数时,一般在这一步产生错误信息,比如“undefined reference to `Add’”。
接下来,在控制台中输入gcc -o test test.o //生成执行程序test
当然,C程序编译过程不仅仅如此,当某个函数被调用时,链接器会根据函数原型进行在所有目标库中匹配,如果匹配到多个形同函数定义(函数原型与函数定义相同),将会出现函数重定义等情况。
(5):C标准中,文件后缀以.h命名的文件称为C语言标准头文件,文件后缀以.c命名的文件称为C语言标准源文件。其中,头文件通常用来保存宏定义、函数声明、全区变量声明等内容,而源文件通常用来保存函数定义。当然,头文件中允许保存函数定义,源文件中也允许保存宏定义、函数声明、全区变量声明等内容。另外,主文件(包含main函数)必须是源文件。
<一起学习C语言:C语言发展历程以及定制学习计划>
<一起学习C语言:初步进入编程世界(一)>
<一起学习C语言:初步进入编程世界(二)>
<一起学习C语言:初步进入编程世界(三)>
<一起学习C语言:C语言数据类型(一)>
<一起学习C语言:C语言数据类型(二)>
<一起学习C语言:C语言数据类型(三)>
<一起学习C语言:C语言基本语法(一)>
<一起学习C语言:C语言基本语法(二)>
<一起学习C语言:C语言基本语法(三)>
<一起学习C语言:C语言基本语法(四)>
<一起学习C语言:C语言基本语法(五)>
<一起学习C语言:C语言循环结构(一)>
<一起学习C语言:C语言循环结构(二)>
<一起学习C语言:C语言循环结构(三)>
<一起学习C语言:数组(一)>
<一起学习C语言:数组(二)>
<一起学习C语言:数组(三)>
<一起学习C语言:初谈指针(一)>
<一起学习C语言:初谈指针(二)>
<一起学习C语言:初谈指针(三)>