CC++语言7|函数、库、内联函数

28 篇文章 0 订阅
25 篇文章 0 订阅
本文详细介绍了C++中的函数概念,包括函数的定义、调用、参数传递方式(值传递与地址传递)、库函数的使用以及内联函数的优势。讨论了函数作为模块化设计的主要工具,强调了函数接口与实现的分离,以及函数声明和定义的区别。文中还提到了函数指针、回调函数和递归,并探讨了如何通过头文件实现代码重用和信息隐藏。
摘要由CSDN通过智能技术生成

在程序中,将一段代码封装起来,在需要的时候可以直接调用,这些代码可以完成一定的功能和操作,并且可以操纵参数,这就是函数。函数是程序代码最主要的组成部分之一,它将完整的程序分为不同的程序块,有着不同的返回结果。

函数可以看作是由程序员来定义的操作,是划分程序的各个程序块,与内置操作符(一种特殊的函数)相同的是,每个函数都会实现一系列的计算,然后(大多数时候)生成一个计算结果。但与操作符不同的是,函数有自己的函数名,而且操作数没有数量限制。与操作符一样,函数可以重载(C++),这意味着同样的函数名可以对应多个不同的函数。

由程序员来编写完成指定任务的函数是用户定义的函数。标准函数库是C提供的可以在任何程序中使用的公共函数,而程序总是从main()函数开始启动的。

函数由函数名以及一组操作数(形参)类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号进行分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都必须有一个相关联的返回类型,定义或者声明函数时,没有显示指定函数的返回类型是不合法的。

在任何编程语言中都会使用函数概念,因为函数结构容易理解,方便调试,函数中的变量都是私有局部变量,是对数据的一种保护,函数之间要通信,需要使用指针或引用。通常,如果不想修改指针或引用对应的数据,通常不把其作为左值,如果想在函数体中更新其值,通常用额外的临时变量来实现。如果不想改变指针或引用对应的数据,可以用const修饰为只读。

函数是程序设计语言中最重要的部分,是模块化设计的主要工具。每一个程序都要用到函数。

即使你自己不定义新的函数, 在每一个完整的C程序中都必须有一个main() 函数。

在C语言中,字符处理、字符串处理和数学计算都是用函数的方式提供的。

函数站在函数设计的角度来看,需要考虑函数的定义,站在函数使用者的角度来看,需要考虑函数的定义和调用;

库函数在调用前需要#include相应的头文件。

自定义的函数在调用时需要进行函数原型说明。

函数原型说明与函数首部写法上需要保持一致,即函数类型、函数名、参数个数和参数顺序必须相同。

如果被调函数的定义在主调函数之前,可以不必加声明。

如果在所有函数定义之前,在函数外部已经做了函数声明,则在主调函数中无须再作声明。

函数包括函数声明、函数定义、函数调用,如果需要修改函数时,最好不需要三个方面都要修改,只需要修改一个地方而保持另外两部分的稳定是最好的。如果接口设计良好的话,则只需要更改函数定义即可。

函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所在的;实现部分描述了具体的行为,这正是函数定义所做的。

函数的声明就类似于变量的声明:

存储类型标识符 数据类型标识符 函数名(形式参数列表及类型数据);

我们知道数组名实际上是数组第一个元素在内存中的地址。类似地,函数名实际上是执行这个函数任务在内存中的开始地址。

因为是函数,所以有另外的参数列表。

1.1 库:接口与实现的分离

函数原型的声明是为了在多文件的项目中,让项目的语法符合“多次声明,一次定义”的要求。

把函数接口是写给使用者看的,当使用者与实现者分离时,就特别有用。

设计库的接口:

库的用户必须了解的内容,包括库中函数的原型、这些函数用到的符号常量和自定义类型。

接口表现为一个头文件。

设计库中的函数的实现:表现为一个源文件。

库的这种实现方法称为信息隐藏。

为什么要使用库?

库可以实现代码重用。某个项目中各个程序员需要共享一组工具函数时可以将这组函数组成一个库,这些函数的代码在项目中得到了重用。如果另一个项目中也许要这样的一组工具函数,那么这个项目的程序员就不必重新编写这些函数而可以直接使用这个库,这样这组代码在多个项目中得到了重用。

调用库函数要包含文件,调用自定义函数也是如此,当然头文件也可以是包含一些全局常量。

1.2 函数原型声明和函数定义的区别。

函数原型声明只是说明了该函数应该如何使用,函数调用时应该给它传递哪些数据,函数调用的结果又应该如何使用。函数定义除了给出函数的使用信息外,还需要给出了函数如何实现预期功能,即如何从输入得到输出的完整过程。

在声明函数时,参数列表部分可以不指定参数的名称,但是必须指定参数的类型。

如果函数具有多个参数,需要为某些参数提供默认值时(C++),要保证默认值参数应位于非默认值参数的右方,否则将导致编译错误。

如果参数的数据类型是指针类型、引用类型或数组类型,则函数是引用传递,其他情况下是值传递。

函数的参数一般可以看成是函数运行时的输入。形式参数指出函数调用时应该给它传递几个数据,这些数据是什么类型的。实际参数是函数某次调用时的真正的输入数据,是形式参数的初值。

函数调用做了两件事情:用对应的实参初始化函数的形参(创建变量并赋值),并将控制权转移给被调用函数。主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的(隐式)定义和初始化开始。

函数的按值传递或引用、指针传递,区别在于是否在函数体内改变参数的情形下。

函数参数除了数组以外默认是以值来传递?为什么是这样,为了数据保护的需要,而数组为什么不是值传递而是按址传递?因为数组包含的数据如果过多时,特别是当数组元素是对象时,所需要的空间和时间都可能会很大,所以,传址就有优势了。

2.1 值传递

在值传递中,形式参数有自己的存储空间,实际参数是形式参数的初值。参数传递完成后,形式参数和实际参数再无任何关联。

在“传值调用”方式下,一个函数只能产生一个返回值,这个返回值是通过return语句传递回主调用函数中的。如果在一个函数中需要产生一个以上的结果值,可以通过使用全局变量的方式。

在C语言中,函数的参数是“按值”传递的。假设使用变量num作为参数调用函数test。

test(num);

num的值被复制到一个临时位置,这个位置被传递到test。在这种情况下,test无法访问原始参数num,因此无法以任何方式更改它。

这是否意味着一个函数永远不能改变另一个函数中变量的值?它可以,但要做到这一点,它必须能够访问变量的地址——存储变量的内存中的位置(指针或引用)。

2.2 址传递

传址的实质也是传值,只是其值是一个地址值,函数体内操作的是地址值指向的数据。地址值可以由指针传递,也可以由引用来传递。

引用的本质是一个由编译器实现了解引用的指针常量,所以使用引用时就如同使用变量一样,而使用指针的值则需要程序员自己解引用。

值传递是因为变量之间没有关联。而引用或指针传递是因为引用只是变量的别名,而指针是指向某个变量,所以变量之间有关系,这样在修改函数参数时,没有关联的就没有影响,而有关联的就有影响。

传递一个数组为什么需要两个参数?

因为数组传递本质上只是传递了数组的起始地址,数组中的元素个数需要另一个变量来指出。

编写一个比较两个 string 对象长度的函数作为例子。这个函数需要访问每个 string 对象的 size,但不必修改这些对象。由于 string 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:

其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。

函数的返回值可以是基本类型,也可以是自定义类型。当然也可以是指针。指针函数,是指返回一个指针的函数:

函数如何返回一个指针?

1) 参数有一指针pc,在函数内再新建一指针p,p=pc,再返回p;

2) 不考虑参数是否有指针,在函数内用指针p动态申请(malloc或new)一块内存,再返回p。

函数的返回也可以是一个引用,当返回一个引用时,可以用其做左值,如返回数组元素的下标引用。

函数调用有三种形式:

1) 把函数调用作为一条语句;

2) 函数调用出现在一个表达式中;

3) 函数调用作为一个函数的实际参数;

函数被调用时,系统为每个形参分配内存单元,也就是相当于一个局部变量的声明后的初始化。

函数可以通过函数指针被调用,被调用的函数称为回调函数。

在主程序中计算每个实际参数值。

将实际参数赋给对应的形式参数。在赋值的过程中完成自动类型转换。

依次执行函数体的每个语句,直到遇见return语句或函数体结束

return后面的表达式的值,如果表达式的值与函数的返回类型不一致,则完成类型的转换。

用函数的返回值置换函数,继续主程序的执行

形参:函数定义时定义的参数;实参:函数调用时定义的参数;

参数和返回值(如果参数是指针或引用,则参数即是输入也是输出,因为他形成了同函数外数据的修改);

通常,函数调用都有一定的开销,因为函数的调用过程包括建立调用、传递参数、跳转到函数代码并返回。使用宏使代码内联,可以避免这样的开销。

函数之间实现数据共享有以下几种方式:局部变量、全局变量、类的数据成员、类的静态成员和友元。如何共享局部变量呢?可以在主调函数和被调函数之间通过参数传递来共享。全局变量具有文件作用域,所以作用域中的各个函数都能共享全局变量。类的数据成员具有类作用域,能够被类的函数成员共享。

函数之间共享数据也就是此函数访问彼函数的数据主要是通过局部变量(参数传递)、全局变量、类的数据成员(数据成员可以被同一个类中的所有函数成员访问)、类的静态成员(类的所有对象共享)及友元(某个普通函数或者类的成员函数可以访问某个类中的私有数据)实现的。

数据的封装实现了数据的隐藏,让数据更安全,但是前面讲到的通过局部变量、全局变量、类的数据成员、类的静态成员及友元实现了数据的共享,这样又降低了数据的安全性。有些数据是需要共享而又不能被改变的,那么这时候我们就要将其声明为常量。

递归程序设计:将一个大问题简化为同样形式的较小问题。

在一个递归求解中,分解的子问题与最初的问题具有一样的形式

作为处理问题的工具,递归技术是一种非常有力的工具。利用递归不但可以使得书写复杂度降低,而且使程序看上去更加美观

递归调用:在一个函数中直接或间接地调用函数本身

必须有递归终止的条件

函数决定终止的参数有规略地递增或递减

有可对函数的入口进行测试的基本情况。

对于大多数常用的递归都有简单、等价的迭代程序。究竟使用哪一种,凭你的经验选择。

迭代程序复杂,但效率高。

递归程序逻辑清晰,但往往效率较低。

C99还提供另一种方法:内联函数(inline function)。

C++语言支持函数内联,其目的是为了提高执行效率。对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用内联函数时,编译器直接用内联函数的代码替换函数调用,于是省去了函数调用的开销。

在内联函数内不允许用循环语句和开关语句。如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。对一个含有许多语句的大函数,函数调用和返回的开销相对来说微不足道,所以也没有必要用内联函数实现。 内联函数的定义必须出现在内联函数第一次被调用之前。

C++ 提供了两种特殊的函数:内联函数和成员函数。将函数指定为内联是建议编译器在调用点直接把函数代码展开。内联函数避免了调用函数的代价。成员函数则是身为类成员的函数。

对于类,如果在声明的同时即定义了函数,即使没有使用关键字inline,编译器也将其做内联函数处理。

system()会调用shell,而exec()则不会调用shell。system是在单独的进程中执行命令,执行完毕还会回到程序中;而exec()则直接在进程中执行新的程序,新的程序会把原程序覆盖,除非调用出错,否则再也回不到exec()函数后面的代码。

system()会产生新的pid(生成新的shell),而exec()则不会。

C下面的头文件用于处理字符函数,如strstr()、strncat()、strncpy()等;

#include "string.h"

在开发应用程序时,通常将函数的声明放置在头文件(.h文件)中,将函数的定义放置在源文件中(.cpp文件)。这样,如果需要在其他文件中访问函数,只需要引用该函数声明的头文件就可以了。

头文件的内容一般包含一些公用的或常用的宏定义、构造型数据类型的定义以及全局变量的定义等。当源文件需要这些定义时,不必重新定义,只需要把所对应的头文件包含进来,相当于把头文件的全部内容插到当前源文件的开头,合二为一后再进行统一的编译。

在调用系统库函数时,要在源文件的开头把库函数对应的头文件包含进来,这样库函数才能被正常调用。一般属于同一类型的库函数对应一个头文件。比如:数学类函数的头文件是math.h,字符类函数的头文件是ctype.h,字符串类函数的头文件是string.h,输入输出类函数的头文件是stdio.h。

定义一个函数指针:

void (*pFunc)(int);

如果要定义多个同一类型的指针,还可以使用typedef定义一种新的函数指针的数据类型:

这样就可以使用这种新的数据类型定义函数指针:

这些函数指针可以指向多个相同类型的函数。

函数指针可以使用函数名来赋值,如有一func()函数:

用函数指针实现回调函数289/pdf304

回调函数就是函数指针指向的函数。

主调函数使用函数指针作为参数,相当于就是将回调函数嵌入到了主调函数之中。

回调函数可以实现算法的通用性。例如排序算法,你可以定义好算法的通用框架,至于其中核心的算法逻辑,则留待回调函数去完成,用户可以通过不同的回调函数,轻松简单地实现各种算法,对算法进行自定义。

函数指针数组:

函数指针加入结构体中可以实现简单的“方法”。

main()函数对于变量定义的作用域与其它函数是一样的,唯一不同的是,它是整个程序的唯一入口和出口,不需要原型声明。是程序与操作系统交互的界面。

全局变量可以在不同函数之间共享数据,但另一方面,函数中因为使用了全局变量,也让函数的独立性大大降低了。

函数作用域的概念跟变量的存储位置和生命期有关。

如果函数嵌套两层以上,建立用函数的形式解决嵌套层次太多的问题。

就享到这啦,喜欢此篇文章或觉得这篇文章对你有帮助的读者可以分享给身边的朋友们。如果你是小白也可以私信回复“资料”领取大礼包一份,以及开发工具一份。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值