若函数定义在调用点之前,可以不另外声明;

若函数定义在调用点之后,必须要在调用函数前声明函数原型:

函数原型:类型标识符 被调用函数名(含类型说明的形参表)

一、函数调用

函数调用形式 :函数名(实参列表)

嵌套调用

每一次调用,系统会在栈中保存场景,和程序断点,等进入子函数执行完再回复外层函数的场景。这里涉及到转子函数的空间时间消耗。
1195549444330885120.png

递归调用

基本上递归可以认为是 函数的递归基+函数规模减少。

一个经典的模板就是:

  1. void recursive(){
  2. if( 递归基条件 )
  3. 已知递归基结果
  4. else
  5. 规模减少;基础操作;
  6. }

这里有一个经典的递归问题叫做汉诺塔问题tower of Hanoi

有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置n个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

1235044766396321792.png

分析:对于这样一个问题,任何人都不可能直接写出移动盘子的每一步,但我们可以利用下面的方法来解决。设移动盘子数为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可变参数函数

  1. 参数类型相同:initializer_list
  2. 参数类型不同:可变参数的模板

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声明,也可以直接在类内定义好实现.

使用注意事项:

  1. 内联函数中不能有循环语句和分支语句
  2. 内联第一次函数被调用之前必须已经被定义
  3. 对内联函数不能使用异常接口声明

意义

如此简单的处理为什么要写成函数?还是为了提高复用性和可读性。

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函数参数的默认值

要求:

  1. 某一位被赋予默认值,则起右边所有参数都得被赋予默认值。
  2. 若函数原型声明在定义前,则默认参数要在声明中给出。

传参过程是从左到右一一对应。

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后面跟的括号是调用运算符,用来传递参数。