若函数定义在调用点之前,可以不另外声明;
若函数定义在调用点之后,必须要在调用函数前声明函数原型:
函数原型:类型标识符 被调用函数名(含类型说明的形参表)
一、函数调用
函数调用形式 :函数名(实参列表)
嵌套调用
每一次调用,系统会在栈中保存场景,和程序断点,等进入子函数执行完再回复外层函数的场景。这里涉及到转子函数的空间时间消耗。
递归调用
基本上递归可以认为是 函数的递归基+函数规模减少。
一个经典的模板就是:
void recursive(){
if( 递归基条件 )
已知递归基结果
else
规模减少;基础操作;
}
这里有一个经典的递归问题叫做汉诺塔问题tower of Hanoi
有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置n个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
分析:对于这样一个问题,任何人都不可能直接写出移动盘子的每一步,但我们可以利用下面的方法来解决。设移动盘子数为n,为了将这n个盘子从A杆移动到C杆,可以做以下三步:
(1)以C盘为中介,从A杆将1至n-1号盘移至B杆;
(2)将A杆中剩下的第n号盘移至C杆;
(3)以A杆为中介;从B杆将1至n-1号盘移至C杆。
#include <iostream>
using namespace std;
void move(char a, char c){ //a位置代表来源,c位置代表目标
cout<<a<< "---->"<<c<<endl;
}
void hanoi( int n ,char a , char b, char c){
//a位置代表来源,b位置代表中介,c位置代表目标 认真理解!!!
if(n==1)
move(a,c);
else
{
hanoi(n-1,a,c,b);
move(a,c);
hanoi(n-1,b,a,c);
}
}
int main(){
int n = 3;
hanoi(n,'A','B','C');
return 0;
}
https://www.bilibili.com/video/BV1jJ411a7AS 这个视频讲解的比较好
二、传值
2.1引用传值(双向传值)&
引用不是对象,引用并没有在程序中占据内存空间,故没有地址的说法???.不可以定义指向引用的指针.
引用本质上是常量指针。
引用只是某个对象的别名,可以实现函数的双向传值,也一般用于此目的。
引用和指针的区别
//定义
int c;
int &a = c; //引用必须绑定一个实际对象,并且一旦绑定不可解绑。
//传值
void splitFloat(float x, int &intPart, float &fracPart) {/*.....*/}
float x, f;
int n;
splitFloat(x, n, f);
2.2可变参数函数
- 参数类型相同:initializer_list
- 参数类型不同:可变参数的模板
initializer_list
要注意的是il对象中的元素永远是const,无法改变。
class FooVec
{
public:
std::vector<int> m_vec;
FooVec(std::initializer_list<int> list)
{
for (auto it = list.begin(); it != list.end(); it++)
m_vec.push_back(*it);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
FooVec foo1 { 1, 2, 3, 4, 5, 6 };
FooVec foo2 { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return 0;
}
2.3内联函数 inline
针对简单的函数,为了节省系统的开销,希望避免转子函数再返回的过程消耗。
内联函数是编译器帮助实现的。它把函数调用直接替换成函数语句。相当于直接拷贝语句。至于实际上是否会执行inline,取决于编译器。(写了不一定inline,没写也可能是inline)
定义内联函数,可以显式用inline声明,也可以直接在类内定义好实现.
使用注意事项:
- 内联函数中不能有循环语句和分支语句
- 内联第一次函数被调用之前必须已经被定义
- 对内联函数不能使用异常接口声明
意义
如此简单的处理为什么要写成函数?还是为了提高复用性和可读性。
2.4常量表达式函数 constexpr
编译期间可计算的一个返回值
constexpr修饰的函数在其所有参数都是constexpr时,一定返回constexpr;
函数体中必须有且仅有一条return语句。
constexpr int get_size() { return 20; }
constexpr int foo = get_size(); //正确:foo是一个常量表达式
int a = 10;
const const_a = a; //正确,const可以由非const常量初始化
constexpr conex_a = const_a; //错误,constexpr不能由一个被变量赋值的const初始化
2.5函数参数的默认值
要求:
- 某一位被赋予默认值,则起右边所有参数都得被赋予默认值。
- 若函数原型声明在定义前,则默认参数要在声明中给出。
传参过程是从左到右一一对应。
void func (int a , int c = 1; int b){ /*...*/} //错误,b应该也有默认值
对于构造函数:既可以在构造函数的声明中,也可以在构造函数的实现中,提供缺省值,但,不能在两者同时提供缺省默认值。
2.6 函数重载
具有静态多态性
——在编译阶段完成的(编译成不同函数了)
形参必须不同——数量 / 类型 ,返回类型和形参名称不是区分的指标。
但是const是区分指标。
void print(T &t) {}
void print(T &t) const {}
1、被标记为const的函数在编译器编译的时候会审查是否有状态改变的语句
如果有,就会报语法错误;因此可以保证函数不改变对象状态
2、常对象只能调用常函数,常函数可以被普通对象调用
3、针对第2点,经常把不改变对象状态的函数声明为常函数方便所有对象调用
不要将不同功能的函数设计成重载函数,它只是用来为类型多态准备的。
我认为重载其实只是为了方便人的记忆和使用,机器肯定还是会处理成不同的函数名的。
2.7 C++标准库函数
需要学会使用,避免重复造轮子,系统函数的算法一般是非常优异的。
2.8 函数调用
成员函数,就是类的方法
使用点运算符(.)比如
A.B();
来表达我们需要调用A类的B方法,点运算符只能用于类类型的对象
B后面跟的括号是调用运算符,用来传递参数。