二、 函数#
1. 函数介绍#
在大多数地方,c++ 和 python的函数是一样的,都是用来包裹定义好的语句,避免重复拷贝粘贴。不过还是有些许不一样的地方。
- python的函数是以回车换行结尾,c++的函数是以 大括号结尾
- python的函数通常使用缩进方式来表示函数体, ,c++使用大括号区域来表示
- python是动态类型语言,而c++是静态类型语言,所以有时候需要像声明变量一样,声明函数。
2. 定义函数#
函数的定义一般可以包含以下几个部分:
方法名称
、方法参数
、返回值
、方法体
, 根据可有可无的设置,函数一般会有以下4种方式体现。
- 声明并调用函数
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1. 无返回值无参数#
1 2 3 4 5 6 7 8 |
|
2. 无返回值有参数#
1 2 3 4 5 6 7 8 9 10 11 12 |
|
3. 有返回值无参数#
1 2 3 4 5 6 7 8 9 10 11 12 |
|
4. 有返回值有参数#
1 2 3 4 5 6 7 8 9 10 11 12 |
|
3. 函数原型#
般来说,c++的函数一般包含声明和定义两个部分。因为c++是静态类型语言,程序属于自上而下编译,所以在使用函数前,必须先表示函数的存在,告诉编译器函数所需要的参数以及函数的返回值是什么。
1. 函数定义在前#
在调用函数之前,事先先定义好函数。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
2. 使用函数原型#
把函数分成声明和定义两部分,函数的原型定义在调用的前面,具体实现可以放在后面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
4. 分离式编译#
一般说来,函数的声明 ( 函数原型 )通常都会放到头文件中,之所以称之为头文件是因为它总是在main函数的前面就引入进来。头文件一般以 .h 或者 .hpp 结尾,通常用于 写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现
- math.h
为了能够让声明和定义能够快速的被关联上,通常它们的名称会被定义成一样的,这已经成为了一种默认的规定
1 2 |
|
- math.cpp
1 2 3 4 5 6 |
|
- main.cpp
1 2 3 4 5 6 7 |
|
5. 函数重载#
在许多语言中,经常会见到两个或者两个以上的函数名称是一样的,当然他们的 参数个数 或者 参数类型 或者是 参数的顺序 是不一样的。这种现象有一个学名叫做 重载 overload, 由于python属于动态类型语言,不区分数据类型,参数可以是任意类型,所以它没有重载。
下面的示例代码即是对加法运行进行了重载,以便能够针对不同的数据类型,不同的参数个数做出匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
思考: 为什么python没有函数重载?
6. 函数参数#
python的函数,在传递参数的时候,有可变对象和不可变对象的现象,那么在C++里面也有类似的说法。只不过是另一种说辞罢了。
python中传递不可变对象,在C++中,对应的是值的拷贝,也就是传递的只是数据的一份拷贝而已。在函数内部修改数据,并不会改变外部数据
python中传递可变对象,在c++中,对应的是引用传递,也就是传递的是对象的引用,而不是拷贝。在函数内部修改数据,会导致外部数据也发生改变。
1. 值传递#
C++默认情况下,处理函数参数传递时,多数使用的是值的拷贝,少数部分除外。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
2. 传递数组#
函数的参数除了能传递普通简单的数据之外,数组也是可以传递的。但是数组稍微有点特殊,这里多做讲解。
- 前面提过,形参实际上就是实参的一份拷贝,就是一个局部变量。
- 数组的数据太大,如果都进行拷贝,那么比较麻烦,也造成了浪费
- 所以实际上传递数组的时候,并不会进行整个数组的拷贝,而只是传递数组的第一个元素内存地址 (指针 ) 进来。
- 数组的数据还是在内存中,只是把第一个元素(也就是数组的起始)内存地址传进来而已。
- 这就造成了函数的内部根本无法知道这个数组的元素有多少个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
3. 传递引用#
目前为止,我们所有函数的参数传递,都是对数据进行了一份拷贝(数组除外)。那么在函数的内部是不能修改值的,因为这仅仅是一份值得拷贝而已(函数外部的值并不会受到影响)。如果真的想在函数内部修改值,那么除了数组之外,还有一种方式就是传递
引用
。引用实际上只是原有数据的一种别名称呼而已,使用
&
定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
7 . 函数是如何被调用工作的#
- 函数是使用函数调用栈来管理函数调用工作的。
- 类似盒子的栈
- 遵循后进先出
- 可以往里面执行压栈和出栈动作(push 和 pop)
- 栈的结构和激活记录
- 函数必须把它的返回值返回给调用它的函数(A ---> B)
- 每次函数的调用都需要创建一次激活记录,然后把它压入栈中(push)
- 当一个函数被调用完毕的时候,就需要从栈中弹出(pop)
- 函数的参数以及内部的局部变量都是存储在栈中。
- 函数栈有可能抛出栈溢出异常(Stack Overflow)
- 一旦函数调用栈中被push进来的函数记录过多,就有可能出现。(例如:无限循环调用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
8 . 内联函数#
函数可以使我们复用代码,但是一个函数的执行,需要开辟空间、形参和实参进行值得拷贝,还要指明函数返回、以及最后回收释放资源的动作,这个过程是要消耗时间的。
- 作为特别注重程序执行效率,适合编写底层系统软件的高级程序设计语言,如果函数中只有简单的几行代码,那么可以使用
inline
关键字来解决了函数调用开销的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
增加了 inline 关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数体在调用处被重写了一遍一样。
有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。
9. 范围规则#
在学习过程,我们会定义很多变量或者引用、这些变量由于定义的位置不同,所以它们的作用域范围也不同。一般会划分成几种类型:
代码块
|局部变量
|静态变量
|全局变量
- 单独代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
- 函数中局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
-
静态本地变量
-
只会初始化一次
- 重复调用函数也只会初始化一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- 全局变量
通常声明在所有函数和类的外部 ,若存在局部变量和全局变量同名情况下,可以使用 域操作符
::
来访问全局变量
1 2 3 4 5 6 7 8 9 10 |
|