《王道》第6章 函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27022241/article/details/80112029

                                        《王道》第6章 函数

  

     函数是有名字的计算单元,对程序(就算是小程序)的结构化至关重要。

    在C++中函数原型就是函数的声明,所以,函数原型除了向用户说明如何使用一个函数以外,还告诉编译器存在这样一个可以使用的函数。函数原型的结构是:

    返回值类型    函数名(参数表);

    函数的声明同变量的声明一样,是一个语句,所以在语句结束要加上分号。函数名、参数名的规则和注意事项同变量名一样。

    函数的定义由返回类型、函数名、形参表(可能为空)以及函数体组成。函数体是调用函数时执行的语句块。函数定义的格式为:

    返回值类型    函数名(参数表){

                   语句块;

    }

1 参数传递

1.1 函数参数

    函数参数分为形参和实参两种。

    形参出现在函数定义中,在整个函数体内都可以使用,离开该函数与则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传送。

    函数调用时,C++里面有三种传递方法:

    1) 值传递(pass by value)

    2) 指针传递(pass by pointer)

    3) 引用传递(pass by reference)

1.2 值传递

    按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把已求出的实参表达式的值一一存入到给形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。

    值传递的特点是被调函数对形参的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

1.3 指针传递

    如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。

    地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。

1.4 引用传递

    按值传递方式容易理解,但形参值的改变不能对实参产生影响。

    地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。

    如果以引用为参数,则既可以使得对形参的任何操作都能改变相应的数据,又使得函数调用显得方便、自然。引用传递方式是在函数定义时在形参前面加上引用运算符“&”。

    示例:

/*
*测试函数参数传递机制
*/
class CRect {

public:
	int height;
	int widht;

	CRect() {
		height = 0;
		widht = 0;
	}

	CRect(int height, int widht) {
		this->height = height;
		this->widht = widht;
	}

};

//(1)传址调用(传指针)
int RectAreaPoint(CRect *rect) {
	int result = rect->height * rect->widht;
	rect->height = 20;
	return result;
}

//(2)引用传递
int RectAreaRefer(CRect &rect) {
	int result = rect.height * rect.widht;
	rect.height = 30;
	return result;

}

//(3)传值调用
int RectArea(CRect rect) {
	int result = rect.height * rect.widht;
	rect.height = 40;
	return result;
}

//测试代码逻辑
void testPoint() {
	CRect rect(10, 10);
	cout << "面积:" << RectAreaPoint(&rect) << endl;
	cout << "面积:" << RectAreaRefer(rect) << endl;
	cout << "rect.height:" << rect.height << endl;
	cout << "面积:" << RectArea(rect) << endl;
	cout << "rect.height:" << rect.height << endl;
}

//测试结果
面积:100
面积:200
rect.height:30
面积:300
rect.height:30

*1.5 传递指针的引用

    假设我们想编写一个与交换两个整数的swap类似的函数,实现两个指针的交换。已知需用*定义指针,用&定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。

    这里给出一个例子:

void ptrswap(int *&v1, int *&v2) {
	int *tmp = v1;
	v1 = v2;
	v2 = temp;
}

    形参int *&v1的定义应从右至左理解:v1是一个引用,与指向int型对象的指针相关联。也就是说,v1只是传递进ptrswap函数的任意指针的别名。

2 内联函数

2.1 为什么使用内联函数

2.2 内联函数分类

    内联函数有两种:

    1.成员函数成为内联函数

    在类中定义的成员函数全部默认为内联函数,可以显式加上inline标识符,或者不加。在类中声明的成员函数,如果加了inline,则其为内联函数;如果没加inline,而在类外定义该成员函数时加了inline,也为内联函数。

    2.普通函数成为内联函数

    在普通函数声明或定义前加inline使其成为内联函数。

2.3 慎用内联

    当函数体比较小的时候, 内联该函数可以令目标代码更加高效。对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联。

    滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小。现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。

    大多数编译器不支持递归函数的内联。

3 默认参数

    在函数声明或定义时,直接对参数赋值,这就是默认参数。在函数调用时没有指定与形参相对应的实参时,就自动使用默认参数。

4 函数重载

    函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

    函数的重载的规则:

    1)函数名称必须相同。

    2)参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。

    3)函数的返回类型可以相同也可以不相同。

    4)仅仅返回类型不同不足以成为函数的重载。

    C++ 是如何做到函数重载的:

    C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_int,void Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。

    不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此。

    从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

5 函数的递归

    函数是一种重要的程序设计方法。简单地说,如果在一个函数、过程或数据结构的定义中又调用了它自身,那么这个函数、过程或数据结构称为是递归定义的,简称递归。

    它通常把一个大型的复杂问题,层层转化为一个与原问题相似的规模较小的问题求解,递归策略只需较少的代码就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。但在通常情况下,效率并不是很高。

    以递归实现斐波那契数列是典型例子。

    必须注意递归模型不能是循环定义的,其必须满足下面的两个条件:

    1)递归表达式(递归体)

    2)边界条件(递归出口)

    递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。

    在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数太多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。

    可以将递归算法转换为非递归算法,通常需要借助栈来实现这种转换。

    示例:单链表的逆向打印

6 函数模板与泛型

参考博客

猜你喜欢

转载自blog.csdn.net/qq_27022241/article/details/80112029
今日推荐