龙空技术网

C语言"函数"使用释疑

深空深处 200

前言:

而今同学们对“c语言文件处理函数”大体比较关怀,兄弟们都需要了解一些“c语言文件处理函数”的相关文章。那么小编同时在网络上收集了一些对于“c语言文件处理函数””的相关资讯,希望咱们能喜欢,看官们快快来学习一下吧!

由于工作的原因,接触不少C语言的初学者,新手在刚刚接触C语言的时候,由于基础不牢,往往不能很好的把握函数的使用方法。因此在这里对C语言的函数做一个详细介绍。

初学者对于跟函数有关的一些基本编程原则、框架往往缺少了解,大多数教科书上对此介绍也比较少,而这些规则对于理解c语言的编程框架,又十分重要。比如:

1、所有可执行代码都要写在函数内部。除了全局变量/常量的声明、头文件的包含、函数声明等代码外,所有的最终要生成二进制的代码,都是要写在函数内部。函数外部的这部分代码,要么是#define、#include等这些不生成代码的宏定义,要么是变量定义、函数声明之类的给编译器看的内存分配代码。除此之外的所有可执行代码,要么写在main()函数内部,要么其他的自定义函数内部,无一例外。

2、函数定义、函数声明不能嵌套,所有的函数声明、函数的定义都不能写在任何一个函数的内部,包括main()函数在内。

3、c的代码有且只能有一个main()函数。

这些对于编程老鸟肯定不是什么问题,但是对于初学者来说,却都是最容易犯错,却很少有人去讲解的地方。

了解了这些,再去看函数具体用法。函数的三个基本要素:函数使用、函数定义、函数声明。要想写好c代码,除了上面的几个原则外,还需要对函数的这三个要素有清晰、透彻的理解。

void func1(void);//函数声明

func1();//函数使用

func1(){… }//函数定义

先从函数使用开始。如果在c语言里做一个函数调用,只要在调用位置写上函数名字就可以。其中函数名字后面的括号,是用来写函数参数的,如果没有参数,则写空括号即可。

func1();//调用函数func1

如果去读汇编语言的函数调用就会发现,函数调用实际上就是一条跳转指令加一条要跳转的地址,特别是arm汇编,如:

B func1 ;停止执行下一条代码,跳转到func1代表的地址去执行新代码,执行完毕再返回继续

51汇编可能特殊一些,有专门的函数调用指令"call func1"。但是其本质也是跳转,只不过相对真正跳转指令,call的时候由cpu做了一些保存现场的工作。

看完汇编语言再回到c语言看函数调用,就会发现,调用函数的时候,写函数的名字,其实可以理解成是写下了函数的地址,函数名字就代表函数代码-也就是函数定义的所在地址。从这个角度看函数调用的话,就不会跟函数声明混淆,就不会在调用函数的时候,写出来void func1();这样的错误。

说到了函数调用,就顺便说下函数的形参和实参。这也是初学者容易困惑的地方。特别是对于形参跟实参是同名变量这样的代码实例,初学的时候特别容易迷糊。

实际上同样的,要是从汇编的角度看的话,问题就简单的多。ATPCS(ARM-THUMB procedure call standard)规定了arm汇编跟c语言的参数传递原则:当函数参数小于三个的时候,c代码编译成汇编代码的时候,使用R0、R1、R2寄存器传递参数。也就是说对于一个带三个参数的函数func1(1,2,3);

//c code

void func1(1,2,3){}

// code

mov r0,#1

mov r1,#1

mov r2,#1

func1

Bx lr

C代码编译成汇编代码以后,函数代码真正执行以前,要先把1、2、3三个常数的值传递给R0、R1、R2三个寄存器,然后在函数内部真正运算的时候,使用寄存器操作。如果把函数的三个参数换成变量,在运算的时候也

func1(x,y,z);

还是同样,先将变量的值传给R0、R1、R2,然后再使用R0、R1、R2运算,而不是在函数内部直接修改x、y、z三个原始的变量。

对于其他的编译器,也是同样的机理。理解了这点后,再去看课本的关于形参和实参的同名变量实例,对于为什么函数内部交换形参变量值以后,外部实参变量值没有改变,相信会有更加直观的理解。

然后说下函数声明,函数声明从形式上少了函数实体的大括号以及里面的代码,多了一个分号,但实质上跟编译器的多文件管理、编译有关联。对于初学者,往往还停留在在一个源文件里堆代码的阶段,还没有开始使用多个源文件,对于编译原理也很难有很深的了解。所以容易忽略或者不能理解函数声明的作用。

前面已经讲到了,函数的调用相当于二进制代码的一个跳转语句,函数名字就相当于一个跳转地址。这样在编译的时候,就要求函数的跳转地址必须要确定,否则一条指令是不完整的,无法生成代码。也就是说调用函数之前,函数实体定义必须存在,这样函数代码才会有具体的地址。在单个源文件的时候,没有这问题。因为函数地址有就是有,没有就是没有。没有的话,编译器会提示编译错误--符号未定义。

但是到了多个源文件就有问题了,因为我的函数实体虽然在这个源文件中没有定义,但是有可能在别的源文件定义了,这种情况编译器如何处理呢。于是函数声明出现了。编译器在编译某个源文件的时候,如果在函数调用之前没有对函数进行定义,可以先声明一下,等于告诉编译器:我这个函数虽然在这个源文件没有定义,但是在其他的源文件定义了,你先把函数地址空着,等链接的时候在别的源文件里找到这个地址的时候再填上。

这样,通过函数声明的方式,完美的解决了编译的时候,各个源文件之间函数符号地址不同步的问题。但是要注意,声明了以后,要真正有函数的实体定义,不能骗编译器。否则的话,链接的时候编译器找不到地址就会报链接错误-符号未定义。

那如果其他的文件中有了函数定义,但是用之前没有声明会怎么样呢?就会出编译错误—隐式声明。

这是关于函数声明的一些解析,最后,说下函数的定义。相对于其他的两个要素,函数定义就容易理解一些,教材上基本都说的很详细,只是注意一下函数定义/声明的类型与返回值之间的关系,以及函数特有的类型void的含义就可以了。

以上是一些个人对于c语言函数的理解,希望对于c语言的初学者,能起到一定参考作用。

标签: #c语言文件处理函数