C++函数

一、函数的声明和定义

  函数在编译时是有实际的地址的,函数的定义中的代码将会存入到该地址空间中,而函数的声明告诉编译器这个函数。在C++程序中调用函数之前,首先要对函数进行定义。如果调用此函数在前,函数定义在后,就会产生编译错误。为了使函数的调用不受函数定义位置的影响,可以在调用函数前进行函数的声明。这样,不管函数是在哪里定义的,只要在调用前进行函数声明,就可以保证函数调用的合法性。

  声明函数,就是告诉编译器函数的返回类型,名称和形参表构成,以便编译系统对函数的调用进行检查。例如:

double power(double x,int n);也可以写成下列形式

double power(double,int);

注意,函数内部不能定义和声明函数。

二、函数调用

  在函数调用过程中,将实参传给形参有三种方式:按值传递,传递指针,传递引用

  传递指针参数:如果将函数定义时将形参的类型说明成指针,对这样的函数进行调用时就需要指定地址形式的实参。传递指针实参仍然像按值传递一样,它把实参的地址传给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,在被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。其实,所谓传递指针,只是传递指针的值。例如,如果有变量a作为实参调用函数,则传递给函数的是变量a的地址,在函数体内可以通过这个地址使用间接运算符"*"修改a的内容。当修改了函数体内的形参,也并不能修改a的地址。在类似Create(*&L)函数中,通常要求在函数体内修改一个指针的值,使其指向另外一个变量,按照上面的原则,需要传递该指针变量它自己才可以在函数体内直接修改该指针的值,所以只有传递指针的引用。

数组也可以用作函数的参数,此时并不会复制整个数组,而是将数组在内存中的地址传递给被调用函数。这样可以有效地减少因为复制大型数组而降低程序的性能。

三、函数的返回值

  在函数中返回指针时,必须确保在调用函数中返回的地址仍然有效,简单的说,不要在函数中返回局部变量的地址。不要从函数中返回局部变量的引用。

四、禁止函数返回局部变量的引用

当发生函数调用时,编译器首先把函数的输入/输出参数放到堆栈,指令寄存器IP放到堆栈(作为函数返回出口地址),然后是基址寄存器,接着是函数的局部变量。当函数返回时执行弹出操作,其顺序正好与放到堆栈的顺序相反(首先释放堆栈中的局部存储变量,然后是基址寄存器,IP寄存器地址和函数的输入/输出变量),同时把放到堆栈的IP寄存器地址作为函数的出口地址并退出函数。所以函数的所有局部变量都分配到了堆栈上,当函数退出时堆栈也就释放了,分配局部变量的内存被操作系统重新收回。而函数局部变量所在的内存段具体变成了什么,编程人员无法确定.

所以,函数返回时,保证返回数据超出函数范围后依然有效,如返回局部变量的引入就是不靠谱的事情

函数返回时,返回new生成的对象,同样不是一个可取的方法,因为这样的代码层次混乱,会让代码上层使用人员苦不堪言

五、函数传值。传指针及传引用的效率分析

值传递

char* GetMemory(int nNumber)

{

  char* pStr = new char[nNumber];

  return pStr;

}

int main()

{

  char* pHello = NULL;

  pHello = GetMemory(100);

  strcpy_s(pHello,"Hello,World");

  delete[] pHello;

  pHello = NULL;

}

值传递调用过程分为三步:

1.在堆栈创建形参副本及局部变量

2.函数执行

3.函数退出,释放副本和临时变量。

在副本生成时,如果数据类型为类类型,函数会调用类的构造函数进行类对象构造和数据拷贝,在堆栈释放副本时,如果数据类型为类类型,函数会调用类的析构函数进行类对象数据的释放。

引用传递

char GetMemory(char* &pStr,int nNumber)

{

  pStr = new char[nNumber];

  return pStr;

}

int main()

{

  char* pHello = NULL;

  GetMemory(pHello,100);

  strcpy_s(pHello,"Hello,World");

  delete[] pHello;

  pHello = NULL;

}

引用传值函数三步

1.在堆栈创建引用形参,普通形参副本及局部变量

2.函数执行

3.函数退出,释放(引用)副本和临时变量。

引用传值过程中,即使引用形参为类类型,在副本创建和释放时也不会发生构造和析构函数调用

指针调用3步

1.在堆栈创建指针形参,普通形参副本及局部变量

2.函数执行

3.函数退出,释放副本和临时变量

指针副本即使为类指针,也不会调用类的构造函数,因为创建的不是类对象,而是类指针

指针的解引用就是通常所说的间接寻址,即获取指针变量中内存地址处的数据

差异分析

1.值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟内存空间以存放由主调函数传进来的实参值,从而成为实参的一个副本。被调函数对形式参数的任何操作都作为局部变量进行,不会影响主调函数的实参变量的值。如果想通过传值方式实现两数据的交换,则这种做法不正确。

2.引用传递过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成指针间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响主调函数中的实参变量

3.指针传递是传值调用的特例,即传的值为主调函数变量的地址。被调函数的形式参数同样在堆栈中为局部变量开辟了内存空间,被调函数对局部变量的任何操作都会作用在主调函数变量的地址之上。因此被调函数通过局部形参所做的任何操作都会影响主调函数中的实参变量。

效率分析

1.从执行效率上讲(这里所说的执行效率,是指在被调用的函数体内执行时的效率)。因为传值调用时,当值被传到函数体内,临时对象生成后,所有的执行任务都是通过直接寻址的方式执行的,而指针和大多数情况下的引用则是以间接寻址的方式执行的,所以实际的执行效率会比传值调用要低。如果函数体内对参数传递过来的变量进行操作比较频繁,执行总次数又多的情况下,传址调用和大多数情况下的引用参数传递会造成比较明显的执行效率损失。

2.虽然函数的传值调用会比函数传地址和传引用效率高,但从整个程序执行角度考虑,传值调用未必就是效率最高的解决方案。例如,实参是数值比较大的数组

3.关于函数的调用有个特殊之处,即多态情况下,如果形参是父类,而实参是子类,在值传递进,临时对象构造只会构造父类的部份,是一个纯粹的父类对象,而不会构造子类的任何特有部分,因为只有虚的析构函数,而没有虚的构造函数。

4.关于函数的健壮性,值传递比使用指针传递要安全得多,因为你不可能传一个不存在的值参数或引用参数,而使用指针就可以,很可能传来的是一个非法的地址(没有初始化,指向已经deleter掉的对象的指针)。所以使用值传递和引用传递会使你的代码更健壮,具体是使用引用还是使用传值,最简单的原则就是看传递的是不是内建的数据类型,对内建的数据类型优先使用值传递,而对于自定义的数据类型,特别是传递比较大的对象,应先使用引用传递。

3种函数调用方式 ,各有各的好处,一般引用和指针效率相仿。

六、函数重载而考虑什么

C++引入函数重载主要有这几个方面的考虑

1.编程和阅读代码友好性的角度,试想如果没有重载机制,同一功能的函数必须取不同的函数名称。例Print_int,Print_string这样的名称以区别两个函数

2.函数的构造函数与类名相同,就是说构造函数都同名,如果没有函数重载机制,要想实例化不同的对象,是相当麻烦的

3.操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用。

并不是所有看似可以用函数重载的地方都能用函数重载。如某些情况下使用不同的函数名称可以提供较多的信息。使程序易于理解,此处建议不要滥用函数重载。

posted @ 2015-10-04 21:01  fenghuan  阅读(334)  评论(0编辑  收藏  举报