1.函数重载
在使用函数之前,我们应该声明它。函数的声明被称为函数的原型,表示函数的调用约定,包括调用参数的个数和类型,返回值,以及由谁去堆栈等等….
关于函数,我们首先介绍一下函数重载。函数重载时 C++引入的一个新的特性。多个函数具有相同的函数名,这些函数中只是参数个数和类型不同,它们就构成了重载。
我们来定义一个函数,返回两个数之间的最大值。
int max(int a, int b);
double max(double a, double b);
如果我们通过 max(20,40);来调用的话,它会根据参数的个数和类型来调用第一个函数。我们也可以看通过 max(20.1,40.0);来调用第二个函数。
2.内联函数
函数调用,尤其是频繁的函数调用是有一定代价的,因为它伴随着参数的传递,代码的入栈,堆栈平衡等等….为了避免这种代价,我们可以将函数声明为内联函数。在声明为内联函数之后,编译器会将调用内联函数的地方展开,将内联函数的代码嵌入到调用内联函数的地方。
内联函数的使用方法,在函数声明的前面加上 inline 。如:
inline void func(){
int i=0; i++;
}
如果我们在下面这样调用它:
func();
编译器就会将代码嵌入到这个地方,成了这样的形式,这样就没有了函数的调用。
int i=0; i++;
注意:使用内联函数可以节省运行时间,但是它有一个缺点,它会使你的应用程序体积增大。一般只是将代码段比较短(三到五行),调用比较频繁的函数声明为内联函数。即使你将一些复杂的函数声明为内联函数,具体是否嵌入代码这个是由编译器来决定的。编译器会根据一定的办法来判断它。
3.函数调用
C++的内存分为两部分,堆和栈。关于局部变量,函数参数的都是从栈中分配的。栈的特性是先入后出。大家学习过 C ,对函数的调用原理也比较清楚,函数的调用是通过栈来实现的。
下面我们来回顾一下栈:
比如 a 调用了 B 函数,首先 a 会讲 B 函数需要的参数入栈,接着会压入 A 中的返回地址,然后在栈中分配局部变量给函数使用。在函数执行完毕之后,在栈中会弹出函数返回地址到 a 中,此时分配给 B 函数的栈空间被回收。局部变量,函数参数占用的栈空间被释放。
下面我们来回顾一下堆:
内存是有操作系统管理的,应用程序可以向操作系统申请。但是应用程序向操作系统申请的代价是很大的,需要考虑到多线程的一些东西。这个时候就引入了堆。我们可以把堆比喻成向操作系统批发内存的零售商,它一次性从操作系统批发了一大片的内存,然后零售给我们的应用程序使用。因为没有多次从操作系统申请,所以操作代价比较小。堆一般随着应用程序的启动而分配,随着应用程序的退出而销毁。所以堆在整个运行期间都是可以使用的。
在 C 中,我们通过 malloc 来分配空间,用 free 来释放空间。
下面来举一个例子。
int *p=(int *)malloc(sizeof(int)*100); //申请一片可以存储一百个 int 变量的值的内存
if(!p){ //判断空间分配是否成功,分配失败就退出
return 0;
}
free (p); //释放内存
p=NULL; //当释放完这一片内存之后, p 就已经不应该再指向这一片内存了。
为了防止误用,我们就把 p 指向 NULL 。如果不指向 NULL , p 就被称为野指针,野指针会导致一些问题。
在 C++中,我们使用 new 来分配空间,用 delete 来释放空间。
int *p=new int[100]; //new 会自动计算需要的长度,分配一连片的内存
if(!p){ //判断空间分配是否成功,分配失败就退出
return 0;
}
delete []p; //上面是数组的分配空间,所以释放的时候需要在变量名前加[]
p=NULL;
如果只是普通的变量申请和释放,只需要如此:
int *p=new int;
delete p; //不需要[],对应的申请内存的方式一定要匹配。
注意:使用 new 申请的内存在必须使用 delete 才可以释放,而释放的形式也必须匹配。