存储类型标识符 数据类型标识符 函数名(形式参数列表及类型数据);
我们知道数组名实际上是数组第一个元素在内存中的地址。类似地,函数名实际上是执行这个函数任务在内存中的开始地址。
因为是函数,所以有另外的参数列表。
1.1 库:接口与实现的分离
函数原型的声明是为了在多文件的项目中,让项目的语法符合“多次声明,一次定义”的要求。
把函数接口是写给使用者看的,当使用者与实现者分离时,就特别有用。
设计库的接口:
库的用户必须了解的内容,包括库中函数的原型、这些函数用到的符号常量和自定义类型。
接口表现为一个头文件。
设计库中的函数的实现:表现为一个源文件。
库的这种实现方法称为信息隐藏。
为什么要使用库?
库可以实现代码重用。某个项目中各个程序员需要共享一组工具函数时可以将这组函数组成一个库,这些函数的代码在项目中得到了重用。如果另一个项目中也许要这样的一组工具函数,那么这个项目的程序员就不必重新编写这些函数而可以直接使用这个库,这样这组代码在多个项目中得到了重用。
调用库函数要包含文件,调用自定义函数也是如此,当然头文件也可以是包含一些全局常量。
1.2 函数原型声明和函数定义的区别。
函数原型声明只是说明了该函数应该如何使用,函数调用时应该给它传递哪些数据,函数调用的结果又应该如何使用。函数定义除了给出函数的使用信息外,还需要给出了函数如何实现预期功能,即如何从输入得到输出的完整过程。
作为一名程序开发人员,不可能每次编写都从最底层开发。如要输入一串字符到输出设备上,我们需要做的仅是调用printf()函数,至于其参数是怎样显示的,我们并不关心。
2 参数传递
在声明函数时,参数列表部分可以不指定参数的名称,但是必须指定参数的类型。
如果函数具有多个参数,需要为某些参数提供默认值时(C++),要保证默认值参数应位于非默认值参数的右方,否则将导致编译错误。
如果参数的数据类型是指针类型、引用类型或数组类型,则函数是引用传递,其他情况下是值传递。
函数的参数一般可以看成是函数运行时的输入。形式参数指出函数调用时应该给它传递几个数据,这些数据是什么类型的。实际参数是函数某次调用时的真正的输入数据,是形式参数的初值。
函数调用做了两件事情:用对应的实参初始化函数的形参(创建变量并赋值),并将控制权转移给被调用函数。主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。
函数的按值传递或引用、指针传递,区别在于是否在函数体内改变参数的情形下。
函数参数除了数组以外默认是以值来传递?为什么是这样,为了数据保护的需要,而数组为什么不是值传递而是按址传递?因为数组包含的数据如果过多时,特别是当数组元素是对象时,所需要的空间和时间都可能会很大,所以,传址就有优势了。
2.1 值传递
在值传递中,形式参数有自己的存储空间,实际参数是形式参数的初值。参数传递完成后,形式参数和实际参数再无任何关联。
在“传值调用”方式下,一个函数只能产生一个返回值,这个返回值是通过语句传递回主调用函数中的。如果在一个函数中需要产生一个以上的结果值,可以通过使用全局变量的方式。
在C语言中,函数的参数是“按值”传递的。假设使用变量num作为参数调用函数test。
test(num);
num的值被复制到一个临时位置,这个位置被传递到test。在这种情况下,test无法访问原始参数num,因此无法以任何方式更改它。
这是否意味着一个函数永远不能改变另一个函数中变量的值?它可以,但要做到这一点,它必须能够访问变量的地址——存储变量的内存中的位置(指针或引用)。
2.2 址传递
传址的实质也是传值,只是其值是一个地址值,函数体内操作的是地址值指向的数据。地址值可以由指针传递,也可以由引用来传递。
引用的本质是一个由编译器实现了解引用的指针常量,所以使用引用时就如同使用变量一样,而使用指针的值则需要程序员自己解引用。
值传递是因为变量之间没有关联。而引用或指针传递是因为引用只是变量的别名,而指针是指向某个变量,所以变量之间有关系,这样在修改函数参数时,没有关联的就没有影响,而有关联的就有影响。
传递一个数组为什么需要两个参数?
因为数组传递本质上只是传递了数组的起始地址,数组中的元素个数需要另一个变量来指出。
编写一个比较两个 对象长度的函数作为例子。这个函数需要访问每个 对象的 size,但不必修改这些对象。由于 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:
// compare the length of two strings bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
其每一个形参都是 const 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 函数不能使用该引用来修改实参。
值传递作为函数的输入; 指针或引用传递作为函数的输出;
3 函数返回值与指针函数
函数的返回值可以是基本类型,也可以是自定义类型。当然也可以是指针。指针函数,是指返回一个指针的函数:
1) 可以返回赋值是参数指针或引用的变量; 2) 可以返回堆中申请的动态变量; 3) 不要返回局部变量地址(一个原则就是在返回调用结束后其值需要仍然存在)。
函数如何返回一个指针?
1) 参数有一指针pc,在函数内再新建一指针p,p=pc,再返回p;
2) 不考虑参数是否有指针,在函数内用指针p动态申请(或new)一块内存,再返回p。
函数的返回也可以是一个引用,当返回一个引用时,可以用其做左值,如返回数组元素的下标引用。
4 函数调用
函数调用有三种形式:
1) 把函数调用作为一条语句;
2) 函数调用出现在一个表达式中;
3) 函数调用作为一个函数的实际参数;
函数被调用时,系统为每个形参分配内存单元,也就是相当于一个局部变量的声明后的初始化。
函数可以通过函数指针被调用,被调用的函数称为回调函数。
5 函数执行过程
在主程序中计算每个实际参数值。
将实际参数赋给对应的形式参数。在赋值的过程中完成自动类型转换。
依次执行函数体的每个语句,直到遇见语句或函数体结束
后面的表达式的值,如果表达式的值与函数的返回类型不一致,则完成类型的转换。
用函数的返回值置换函数,继续主程序的执行
形参:函数定义时定义的参数;实参:函数调用时定义的参数;
6 函数之间的通信和数据共享
参数和返回值(如果参数是指针或引用,则参数即是输入也是输出,因为他形成了同函数外数据的修改);
通常,函数调用都有一定的开销,因为函数的调用过程包括建立调用、传递参数、跳转到函数代码并返回。使用宏使代码内联,可以避免这样的开销。
函数之间实现数据共享有以下几种方式:局部变量、全局变量、类的数据成员、类的静态成员和友元。如何共享局部变量呢?可以在主调函数和被调函数之间通过参数传递来共享。全局变量具有文件作用域,所以作用域中的各个函数都能共享全局变量。类的数据成员具有类作用域,能够被类的函数成员共享。
函数之间共享数据也就是此函数访问彼函数的数据主要是通过局部变量(参数传递)、全局变量、类的数据成员(数据成员可以被同一个类中的所有函数成员访问)、类的静态成员(类的所有对象共享)及友元(某个普通函数或者类的成员函数可以访问某个类中的私有数据)实现的。
数据的封装实现了数据的隐藏,让数据更安全,但是前面讲到的通过局部变量、全局变量、类的数据成员、类的静态成员及友元实现了数据的共享,这样又降低了数据的安全性。有些数据是需要共享而又不能被改变的,那么这时候我们就要将其声明为常量。
因为函数在执行时要依赖于其所在的外部变量。如果将一个函数移到另一个文件中,还要将有关的外部变量及其值一起移过去。但若该外部变量与其他文件的变量同名时,就会出现问题,降低了程序的可靠性和通用性。一般要求把C程序中的函数做成一个封闭体,除了可以通过“实参——形参”的渠道与外界发生联系外,没有其他渠道。
7 递归函数
递归程序设计:将一个大问题简化为同样形式的较小问题。
在一个递归求解中,分解的子问题与最初的问题具有一样的形式
作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观
递归调用:在一个函数中直接或间接地调用函数本身
必须有递归终止的条件
函数决定终止的参数有规略地递增或递减
有可对函数的入口进行测试的基本情况。
if (条件) return (不需要递归的简单答案); else return (递归调用同一函数);
对于大多数常用的递归都有简单、等价的迭代程序。究竟使用哪一种,凭你的经验选择。
迭代程序复杂,但效率高。
递归程序逻辑清晰,但往往效率较低。
8 内联函数
C99还提供另一种方法:内联函数( )。
C++语言支持函数内联,其目的是为了提高执行效率。对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用内联函数时,编译器直接用内联函数的代码替换函数调用,于是省去了函数调用的开销。
在内联函数内不允许用循环语句和开关语句。如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。 内联函数的定义必须出现在内联函数第一次被调用之前。
C++ 提供了两种特殊的函数:内联函数和成员函数。将函数指定为内联是建议编译器在调用点直接把函数代码展开。内联函数避免了调用函数的代价。成员函数则是身为类成员的函数。
对于类,如果在声明的同时即定义了函数,即使没有使用关键字,编译器也将其做内联函数处理。
9 库函数
()会调用shell,而exec()则不会调用shell。是在单独的进程中执行命令,执行完毕还会回到程序中;而exec()则直接在进程中执行新的程序,新的程序会把原程序覆盖,除非调用出错,否则再也回不到exec()函数后面的代码。
()会产生新的pid(生成新的shell),而exec()则不会。
C下面的头文件用于处理字符函数,如()、()、()等;
# “.h”
在开发应用程序时,通常将函数的声明放置在头文件(.h文件)中,将函数的定义放置在源文件中(.cpp文件)。这样,如果需要在其他文件中访问函数,只需要引用该函数声明的头文件就可以了。
头文件的内容一般包含一些公用的或常用的宏定义、构造型数据类型的定义以及全局变量的定义等。当源文件需要这些定义时,不必重新定义,只需要把所对应的头文件包含进来,相当于把头文件的全部内容插到当前源文件的开头,合二为一后再进行统一的编译。
在调用系统库函数时,要在源文件的开头把库函数对应的头文件包含进来,这样库函数才能被正常调用。一般属于同一类型的库函数对应一个头文件。比如:数学类函数的头文件是math.h,字符类函数的头文件是ctype.h,字符串类函数的头文件是.h,输入输出类函数的头文件是stdio.h。
10 函数指针
定义一个函数指针:
void (*pFunc)(int);
如果要定义多个同一类型的指针,还可以使用定义一种新的函数指针的数据类型:
typedef void(*PFUNC)(int);
这样就可以使用这种新的数据类型定义函数指针:
PFUNC pFunc1; PFUNC pFunc2;
这些函数指针可以指向多个相同类型的函数。
函数指针可以使用函数名来赋值,如有一func()函数:
pFunc1 = func(); //前面也可以添加&
用函数指针实现回调函数289/
回调函数就是函数指针指向的函数。
主调函数使用函数指针作为参数,相当于就是将回调函数嵌入到了主调函数之中。
回调函数可以实现算法的通用性。例如排序算法,你可以定义好算法的通用框架,至于其中核心的算法逻辑,则留待回调函数去完成,用户可以通过不同的回调函数,轻松简单地实现各种算法,对算法进行自定义。
函数指针数组:
double (*p[3])()={sin,cos,tan};
11 其它
函数指针加入结构体中可以实现简单的“方法”。
main()函数对于变量定义的作用域与其它函数是一样的,唯一不同的是,它是整个程序的唯一入口和出口,不需要原型声明。是程序与操作系统交互的界面。
全局变量可以在不同函数之间共享数据,但另一方面,函数中因为使用了全局变量,也让函数的独立性大大降低了。
函数作用域的概念跟变量的存储位置和生命期有关。
如果函数嵌套两层以上,建立用函数的形式解决嵌套层次太多的问题。
C++中的函数可以重载,并有泛型编程的函数模板机制,详情见后面的文章。
-End-
———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666