`
ydbc
  • 浏览: 766739 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

【C语言】11-函数的声明和定义

 
阅读更多

一、函数的声明

1.在C语言中,函数的定义顺序是有讲究的:默认情况下,只有后面定义的函数才可以调用前面定义过的函数

复制代码
1 int sum(int a, int b) {
2     return a + b;
3 }
4 
5 int main()
6 {
7     int c = sum(1, 4);
8     return 0;
9 }
复制代码

第5行定义的main函数调用了第1行的sum函数,这是合法的。如果调换sum函数和main函数的顺序,在标准的C编译器环境下是不合法的(不过在GCC编译器环境下只是一个警告)

2.如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明

复制代码
 1 // 只是做个函数声明,并不用实现
 2 int sum(int a, int b);
 3 
 4 int main()
 5 {
 6     int c = sum(1, 4);
 7     return 0;
 8 }
 9 
10 // 函数的定义(实现)
11 int sum(int a, int b) {
12     return a + b;
13 }
复制代码

在第11行定义了sum函数,在第2行对sum函数进行了声明,然后在第6行(main函数中)就可以正常调用sum函数了。

3.函数的声明格式

1> 格式

返回值类型  函数名 (参数1, 参数2, ...)

只要你在main函数前面声明过一个函数,main函数就知道这个函数的存在,就可以调用这个函数。而且只要知道函数名、函数的返回值、函数接收多少个参数、每个参数是什么类型的,就能够调用这个函数了,因此,声明函数的时候可以省略参数名称。比如上面的sum函数声明可以写成这样:

int sum(int, int);

究竟这个函数是做什么用的,还要看函数的定义。

2> 如果只有函数的声明,而没有函数的定义,那么程序将会在链接时出错

下面的写法是错误的:

复制代码
1 int sum(int a, int b);
2 
3 int main()
4 {
5     
6     sum(10, 11);
7 
8     return 0;
9 }
复制代码
  • 在第1行声明了一个sum函数,但是并没有对sum函数进行定义,接着在第6行调用sum函数
  • 这个程序是可以编译成功的,因为我们在main函数前面声明了sum函数(函数的声明和定义是两码事),这个函数声明可以理解为:在语法上,骗一下main函数,告诉它sum函数是存在的,所以从语法的角度上main函数是可以调用sum函数的。究竟这个sum函数存不存在呢,有没有被定义呢?编译器是不管的。在编译阶段,编译器并不检测函数有没有定义,只有在链接的时候才会检测这个函数存不存在,也就是检测函数有没有被定义。
  • 因此,这个程序会在链接的时候报错,错误信息如下:

  • 我这里的源文件是main.c文件,所以编译成功后生成一个main.o文件。链接的时候,链接器会检测main.o中的函数有没有被定义。
  • 上面的错误信息大致意思是:在main.o文件中找不到sum这个标识符
  • 错误信息中的linker是链接器的意思,下次看到这个linker,说明是链接阶段出错了。链接出错了,就不能生成可执行文件,程序就不能运行。
  • 这个错误的解决方案就是加上sum函数的定义。

二、多源文件开发

1.为什么要有多个源文件

1> 在编写第一个c语言程序的时候已经提到:我们编写的所有C语言代码都保存在拓展名为.c的源文件中,编写完毕后就进行编译、链接,最后运行程序。

2> 在前面的学习过程中,由于代码比较少,因此所有的代码都保存在一个.c源文件中。但是,在实际开发过程中,项目做大了,源代码肯定非常多,很容易就上万行代码了,甚至上十万、百万都有可能。这个时候如果把所有的代码都写到一个.c源文件中,那么这个文件将会非常庞大,也非常恶心,你可以想象一下,一个文件有十几万行文字,不要说调试程序了,连阅读代码都非常困难。

3> 而且,公司里面都是以团队开发为主,如果多个开发人员同时修改一个源文件,那就会带来很多麻烦的问题,比如张三修改的代码很有可能会抹掉李四之前添加的代码

4> 因此,为了模块化开发,一般会将不同的功能写到不同的.c源文件中,这样的话,每个开发人员都负责修改不同的源文件,达到分工合作的目的,能够大大提高开发效率。也就是说,一个正常的C语言项目是由多个.c源文件构成。

2.将sum函数写到其他源文件中

接下来就演示一下多个源文件的开发,我将前面定义的sum函数写在另一个源文件(命名为sum.c)中。这时候就有两个源文件:

1> main.c文件

1 int main()
2 {
3 
4     return 0;
5 }

2> sum.c文件

1 int sum(int a, int b)
2 {
3     return a + b;
4 }

3.在main函数中调用sum函数

1> 现在想在main函数中调用sum函数,那么你可能会直接这样写:

复制代码
1 int main()
2 {
3     int c = sum(10, 11);
4 
5     return 0;
6 }
复制代码

这种写法在标准C语言编译器中是直接报错的,因为main函数都不知道sum函数的存在,怎么可以调用它呢!!!

2> 我们应该骗一下main函数,sum函数是存在的,告诉它sum函数的返回值和参数类型即可。也就是说,应该在main函数前面,对sum函数进行声明。

main.c文件应该写成下面这样

复制代码
 1 #include <stdio.h>
 2 
 3 int sum(int, int);
 4 
 5 int main()
 6 {
 7     int c = sum(10, 11);
 8     
 9     printf("c is %d\n", c);
10     
11     return 0;
12 }
复制代码

注意第3行,加了一个sum函数的声明。为了检验sum函数的调用结果,在第9行用prinf函数将结果输出。

4.编译所有的源文件

sum.c和main.c都编写完毕后,就可以使用gcc指令进行编译了。同时编译两个文件的指令是:cc -c main.c sum.c

编译成功后,生成了2个.o目标文件

也可以单独编译:

cc -c main.c

cc -c sum.c

5.链接所有的目标文件

前面已经编译成功,生成了main.o和sum.o文件。现在应该把这2个.o文件进行链接,生成可执行文件。

1> 注意,一定要同时链接两个文件。如果你只是单独链接main.o或者sum.o都是不可能链接成功的。原因如下:

  • 如果只是链接main.o文件:cc main.o,错误信息是:在main.o中找到不到sum这个标识符,其实就是找不到sum函数的定义。因为sum函数的定义在sum.o文件中,main.o中只有sum函数的声明

  • 如果只是链接sum.o文件:cc sum.o,错误信息是:找不到main函数。一个C程序的入口点就是main函数,main函数定义在main.o中,sum.o中并没有定义main函数,连入口都没有,怎么能链接成功、生成可执行文件呢?

可以看出,main.o和sum.o有密不可分的关系,其实链接的目的就是将所有相关联的目标文件和C语言函数库组合在一起,生成可执行文件。

2> 链接main.o和sum.o文件:cc main.o sum.o,生成了可执行文件a.out

3> 运行a.out文件:./a.out,运行结果是在屏幕上输出了:

c is 21

说明函数调用成功,我们已经成功在main.c文件的main函数中调用了sum.c文件中的sum函数

4> 从中也可以得出一个结论:只要知道某个函数的声明,就可以调用这个函数,编译就能成功。不过想要这个程序能够运行成功,必须保证在链接的时候能找到函数的定义

三、#include

理解完前面的知识后,接下来就可以搞懂一个很久以前的问题:每次写在最前面的#include是干啥用的?

1.#include的作用

先来看一个最简单的C程序:

复制代码
1 #include <stdio.h>
2 
3 int main()
4 {
5     printf("Hello, World!\n");
6     return 0;
7 }
复制代码

这个程序的作用是在屏幕上输出Hello,World!这一串内容,我们主要关注第一行代码。

  • #include 是C语言的预处理指令之一,所谓预处理,就是在编译之前做的处理,预处理指令一般以 # 开头
  • #include 指令后面会跟着一个文件名,预处理器发现 #include 指令后,就会根据文件名去查找文件,并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源文件中的 #include 指令,就像你把被包含文件中的全部内容拷贝到这个 #include 指令所在的位置一样。所以第一行指令的作用是将stdio.h文件里面的所有内容拷贝到第一行中。
  • 如果被包含的文件拓展名为.h,我们称之为"头文件"(Header File),头文件可以用来声明函数,要想使用这些函数,就必须先用 #include 指令包含函数所在的头文件
  • #include 指令不仅仅限于.h头文件,可以包含任何编译器能识别的C/C++代码文件,包括.c、.hpp、.cpp等,甚至.txt、.abc等等都可以

也就是说你完全可以将第3行~第7行的代码放到其他文件中,然后用 #include 指令包含进来,比如:

1> 将第3行~第7行的代码放到my.txt中

2> 在main.c源文件中包含my.txt文件

  • 编译链接后,程序还是可以照常运行的,因为 #include 的功能就是将文件内容完全拷贝到 #include 指令所在的位置
  • 说明:这里用txt文件纯属演示,平时做项目不会这样做,除非吃饱了撑着,才会把代码都写到txt中去

2.#include可以使用绝对路径

上面的#include "my.txt"使用的是相对路径,其实也可以使用绝对路径。比如#include "/Users/apple/Desktop/my.txt"

3.#include<>和#include ""的区别

二者的区别在于:当被include的文件路径不是绝对路径的时候,有不同的搜索顺序

1>对于使用双引号""来include文件,搜索的时候按以下顺序:

  • 先在这条include指令的父文件所在文件夹内搜索,所谓的父文件,就是这条include指令所在的文件
  • 如果上一步找不到,则在父文件的父文件所在文件夹内搜索;
  • 如果上一步找不到,则在编译器设置的include路径内搜索;
  • 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

2>对于使用尖括号<>来include文件,搜索的时候按以下顺序:

  • 在编译器设置的include路径内搜索;
  • 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

我这里使用的是clang编译器,clang设置include路径是(4.2是编译器版本):/usr/lib/clang/4.2/include

Mac系统的include路径有:

  • /usr/include
  • /usr/local/include

4.stdio.h

我们已经知道#include指令的作用了,可是为什么要在第一行代码包含stdio.h呢?

  • stdio.h是C语言函数库中的一个头文件,里面声明了一些常用的输入输出函数,比如往屏幕上输出内容的printf函数
  • 这里之所以包含 stdio.h 文件,是因为在第5行中用到了在 stdio.h 内部声明的printf函数,这个函数可以向屏幕输出数据,第7行代码输出的内容是:Hello, World!
  • 注意:stdio.h里面只有printf函数的声明。前面已经提到:只要知道函数的声明,就可以调用这个函数,就能编译成功。不过想要这个程序能够运行成功,必须保证在链接的时候能找到函数的定义。其实链接除了会将所有的目标文件组合在一起,还会关联C语言的函数库,函数库中就有printf函数的定义。因此前面的程序是可以链接成功的。

5.头文件.h和源文件.c的分工

跟printf函数一样,我们在开发中会经常将函数的声明和定义写在不同的文件中,函数声明放在.h头文件中,函数定义放在.c源文件中。

下面我们将sum函数的声明和定义分别放在sum.h和sum.c中

这是sum.h文件

这是sum.c文件

然后在main.c中包含sum.h即可使用sum函数

其实sum.h和sum.c的文件名不一样要相同,可以随便写,只要文件名是合法的。但还是建议写成一样,因为一看文件名就知道sum.h和sum.c是有联系的。

运行步骤分析:

1> 在编译之前,预编译器会将sum.h文件中的内容拷贝到main.c中

2> 接着编译main.c和sum.c两个源文件,生成目标文件main.o和sum.o,这2个文件是不能被单独执行的,原因很简单:

* sum.o中不存在main函数,肯定不可以被执行

* main.o中虽然有main函数,但是它在main函数中调用了一个sum函数,而sum函数的定义却存在于sum.o中,因此main.o依赖于sum.o

3> 把main.o、sum.o链接在一起,生成可执行文件

4> 运行程序

说到这里,有人可能有疑惑:可不可以在main.c中包含sum.c文件,不要sum.h文件了?

大家都知道#include的功能是拷贝内容,因此上面的代码等效于:

这么一看,语法上是绝对没有问题的,main.c、sum.c都能编译成功,分别生成sum.o、main.o文件。但是当我们同时链接main.o和sum.o时会出错。原因:当链接这两个文件时链接器会发现sum.o和main.o里面都有sum函数的定义,于是报"标识符重复"的错误,也就是说sum函数被重复定义了。默认情况下,C语言不允许两个函数的名字相同。因此,不要尝试去#include那些.c源文件。

有人可能觉得分出sum.h和sum.c文件的这种做法好傻B,好端端多出2个文件,你把所有的东西都写到main.c不就可以了么?

  • 没错,整个C程序的代码是可以都写在main.c中。但是,如果项目做得很大,你可以想象得到,main.c这个文件会有多么庞大,会严重降低开发和调试效率。
  • 要想出色地完成一个大项目,需要一个团队的合作,不是一个人就可以搞的定的。如果把所有的代码都写在main.c中,那就导致代码冲突,因为整个团队的开发人员都在修改main.c文件,张三修改的代码很有可能会抹掉李四之前添加的代码。
  • 正常的模式应该是这样:假设张三负责编写 main函数,李四负责编写其他自定义函数,张三需要用到李四编写的某个函数,怎么办呢?李四可以将所有自定义函数的声明写在一个.h文件中,比如 lisi.h,然后张三在他自己的代码中用#include包含lisi.h文件,接着就可以调用lisi.h中声明的函数了,而李四呢,可以独立地在另外一个文件中(比如lisi.c)编写函数的定义,实现那些在lisi.h中声明的函数。这样子,张三和李四就可以相互协作、不会冲突。
分享到:
评论

相关推荐

    C语言--程序设计导论 C语言--程序设计导论

    - 定义与调用:函数是C语言的基本模块,可以实现特定功能并复用代码。 - 参数与返回值:函数可以接受参数,处理后返回结果。 - 主函数main():程序的入口点,C语言的执行始于main()。 4. **指针** - 指针变量:...

    C语言练习-函数.pdf

    在给定的内容中,可以看到一系列有关C语言函数练习的代码示例。虽然文档内容存在扫描识别错误,但我们可以提取出以下知识点: 1. 函数的定义和调用:C语言中的函数需要先声明(原型)后定义。例如在`int fun(int m)...

    C语言静态变量和静态函数

    C_语言静态变量和静态函数,可以让您对C语言静态变量和静态函数有更清晰的认识

    C语言-函数.pdf

    函数声明不包含函数体,只包括返回类型、函数名和参数类型列表。声明通常用于使得在函数定义之前的地方能够调用该函数。 例如,对于上述`factorial`函数,其声明如下: ```c int factorial(int n); ``` 这个声明让...

    关于C语言----七:函数

    在C语言中,函数是程序的基本模块,它们可以完成特定的任务并被其他代码部分调用。...通过理解函数的定义、调用、参数、返回值以及高级特性如函数指针和递归,我们可以编写更高效、更易于维护的C语言程序。

    C语言函数的定义、声明以及函数的调用方法_c语言函数学习实例_

    函数的定义、声明和调用是C语言编程中不可或缺的部分。本篇将深入探讨这三个概念,以及如何处理参数传递,特别是数组作为函数参数的情况。 1. **函数的定义**: 函数定义是创建一个函数的过程,它包括函数名、返回...

    C语言程序设计-函数定义.pptx

    `是一个函数声明,它告诉编译器`max`函数的存在,然后在后面定义该函数的具体实现。在`main`函数中,我们可以调用`max`函数并存储其返回值。 值得注意的是,函数定义不能嵌套,这意味着所有函数都是相互独立的,...

    C-C语言-函数1.ppt

    C语言函数概述 C语言中的函数是指可以重复使用的代码块,用于实现特定的功能。函数可以分为无参函数、有参函数和空函数三种。 无参函数定义的一般形式为: 类型标识符 函数名( ) { 声明部分 语句 } 例如: ...

    C语言--程序设计导论

    此外,还可能讨论函数指针,它是C语言的一个强大特性,可以用来实现回调函数和高阶函数。 第六章可能涵盖数组和字符串,这是C语言中重要的数据结构。数组是存储相同类型元素的集合,而字符串是特殊的字符数组。这一...

    C语言和C++在声明和定义之间的区别是什么

    ### C语言和C++在声明和定义之间的区别 在编程领域,尤其是在学习和使用C语言与C++时,理解和区分“声明”与“定义”的概念至关重要。这两种语言虽然有着密切的联系,但在处理声明和定义方面存在一些重要的差异。...

    c语言实例---自己筛选的

    1. **基本语法**:C语言实例可能包括变量声明、常量定义、运算符的使用、流程控制语句(如if-else,switch-case,for,while,do-while)等。 2. **数据类型**:例如,整型(int),浮点型(float, double),字符型...

    C语言中级-9再论函数

    定义一个函数的基本结构包括返回类型、函数名、参数列表和函数体。例如: ```c return_type function_name (parameter_type parameter1, parameter_type parameter2) { // 函数体 } ``` 函数调用时,通过函数名和...

    谢丽聪老师C语言课件-1C语言概述-2009.ppt

    "C语言概述" C语言是由丹尼斯·里奇(Dennis Ritchie)和...C语言的函数调用包括函数定义、函数调用、函数参数传递等。 C语言是一种功能强大、灵活性强的高级编程语言,广泛应用于各种领域,具有很高的实用价值。

    C语言函数库 C语言函数库和文件(完).doc

    C语言函数库包括标准库和其他自定义库。标准库提供了大量的基本操作,如输入输出、数学计算、字符串处理等。自定义库则允许程序员根据需要创建和组织自己的函数集合。 在处理函数时,有三种常见的处理方式: 1) 将...

    最经典的C语言教程--精通C语言必备

    - 定义与调用:了解如何定义和调用自定义函数,理解参数传递机制,包括传值和传址。 - 函数指针:理解函数指针的概念,以及如何使用它来实现回调函数和动态调度。 - 内联函数与预处理器宏:探讨内联函数以提高...

    C语言-函数及测试代码

    本资源包含有关C语言函数的详细讲解以及相关的测试代码,旨在帮助学习者深入理解和实践函数的使用。 1. **函数定义与声明** 函数定义定义了函数的行为,包括返回类型、函数名和参数列表。例如: ```c int add...

    边学边用C语言--超级经典教程

    4. **指针**:深入解析C语言中的指针概念,包括指针的声明、赋值、解引用,以及指针与数组、函数的关系。 5. **内存管理**:涉及动态内存分配(malloc、calloc、realloc、free)和栈与堆的概念。 6. **预处理器**...

    c语言课件--我自己的

    2. **熟悉C程序的基本结构**:了解如何组织C程序的基本框架,包括函数的定义和调用、主函数`main()`的作用以及如何通过函数实现模块化编程。 3. **学习程序设计的思想和方法**:通过实践项目和案例分析,学会运用...

    c语言中头文件的建立与使用-函数、全局变量、全局数组

    头文件本质上是一个包含了函数声明、变量声明、常量定义、宏定义等的文本文件。通过使用头文件,可以在多个源文件(`.c`文件)之间共享这些定义。创建头文件的方法非常简单,只需要使用文本编辑器编写相应的代码,并以...

    C语言教程-100例

    4. **数组与指针**:这是C语言的特色,学习如何声明、初始化、遍历数组,以及理解指针的概念,如何声明、使用和操作指针,包括指针作为函数参数。 5. **字符串处理**:学习使用字符数组和字符串函数(如strlen, ...

Global site tag (gtag.js) - Google Analytics