C++ Primer 第三篇 基于过程的程序设计

来源:互联网 发布:常德网络推广公司 编辑:程序博客网 时间:2024/05/22 12:53

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

第三篇基于过程的程序设计

 

7 函 数

7.1 概述

 

函数声明以及inline 函数的定义最好放在头文件中这些头文件可以被包含include

在每个调用该函数的文件中通过这种方式所有文件共享一个公共的声明,如果需要修改

此声明则只有这一个实例需要被改变,程序的头文件可如下定义我们把它称做localMath.h

 

//main文件

#include <iostream>

#include <max.h>

using namespace std;

 

int main()

{

int a=5,b=4;

    cout<<max(a,b); 

return 0;

}

 

//max.h头文件

inline int max(int,int); //声明

inline int max(int c,int d) //定义

{

    return(c>d ? c:d); 

}

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 

7.3 参数传递

C++中参数传递的缺省初始化方法是把实参的值拷贝到参数的存储区中这被称为按值

传递pass-by-value

按值传递时函数不会访问当前调用的实参函数,处理的值是它本地的拷贝,这些拷贝被存储在运行栈中(因此效率比较低,特别是对于大型类对象)因此改变这些值不会影响实参的值一旦函数结束了函数的活动记录将从栈中弹出,这些局部值也就消失了。

 

// swap() 没有交换两个实参的值!

void swap( int v1, int v2 ) {

int tmp = v2;

v2 = v1;

v1 = tmp;

}

swap()交换实参的本地拷贝代表swap()实参的变量并没有被改变这将在下面调用

swap()的程序中可以看出来

#include <iostream>

void swap( int, int );

int main() {

int i = 10;

int j = 20;

cout << "Before swap():/ti: "

<< i << "/tj: " << j << endl;

swap( i, j );

cout << "After swap():/ti: "

<< i << "/tj: " << j << endl;

return 0;

}

编译并执行程序产生如下结果

Before swap(): i: 10 j: 20

After swap(): i: 10 j: 20

 

为了获得期望的行为程序员可以使用两种方法一种方法是参数被声明成指针例如swap()可重写如下

// pswap()交换v1 v2 指向的值

void pswap( int *v1, int *v2 ) {

int tmp = *v2;

*v2 = *v1;

*v1 = tmp;

}

我们必须修改main()来调用pswap() 现在程序员必须传递两个对象的地址而不是对象本身

pswap( &i, &j );

修改后的程序编译运行后的结果显示了它的正确性

// 使用指针使程序员能够访问当前调用的实参

Before swap(): i: 10 j: 20

After swap(): i: 20 j: 10

 

第二种方法是把参数声明成引用例如swap()可重写如下

// rswap() 交换v1 v2 引用的值

void rswap( int &v1, int &v2 ) {

int tmp = v2;

v2 = v1;

v1 = tmp;

}

main()rswap()的调用看起来像原来的swap()调用

rswap( i, j );

编译并运行这程序会显示i j 的值已经被正确交换了。

 

什么时候将一个参数指定为引用比较合适呢。像swap()的情况它必须将一个参数改变

成指针来允许改变实参的值时,就比较合适。引用参数的第二种普遍用法是向主调函数返回多个值。第三种用法是向函数传递大型类对象。

 

我们可以声明任意内置数据类型的引用参数。例如程序员想修改指针本身而不是指针引用的对象那么他可以声明一个参数该参数是一个指针的引用,例如下面是交换两个指针的函数

#include <iostream>

void ptrswap( int *&v1, int *&v2 ) {

int *tmp = v2;

v2 = v1;

v1 = tmp;

}

int main() {

int i = 10;

int j = 20;

int *pi = &i;

int *pj = &j;

cout << "Before ptrswap():/tpi: "<< *pi << "/tpj: " << *pj << endl;

ptrswap( pi, pj );

cout << "After ptrswap():/tpi: "<< *pi << "/tpj: " << *pj << endl;

return 0;

}

如下声明

int *&v1;

应该从右向左读v1 是一个引用它引用一个指针指针指向int 型的对象用函数main()

操纵函数rswap() 我们可以如下修改代码以便交换两个指针值

 

 

7.3.2 引用和指针参数的关系

确实这两种参数都允许函数修改实参指向的对象,两种类型的参数都允许有效地向函数传递大型类对象,所以怎样决定把函数参数声明成引用还是指针呢?

引用必须被初始化为指向一个对象,一旦初始化了它就不能再指向其他对象。指针可以指向一系列不同的对象也可以什么都不指向,因为指针可能指向一个对象或没有任何对象。所以函数在确定指针实际指向一个有效的对象之前不能安全地解引用dereference 一个指针例如

class X;

void manip( X *px )

{

// 在解引用指针之前确信它非0

if ( px != 0 )

// 解引用指针

}

另一方面对于引用参数,函数不需要保证它指向一个对象(也就是无对象),引用必须指向一个对象甚至在我们不希望这样时也是如此例如

class Type { };

void operate( const Type& p1, const Type& p2 );

int main() {

Type obj1;

// 设置 obj1 为某个值

// 错误: 引用参数的实参不能为0

Type obj2 = operate( obj1, 0 );

}

如果一个参数可能在函数中指向不同的对象或者这个参数可能不指向任何对象则必须使用指针参数

 

 

引用参数的一个重要用法是它允许我们在有效地实现重载操作符的同时还能保证用法的直观性。我们想支持两个Matrix 类对象的加法和赋值操作符使它们的用法同内置类型一样自然

Matrix a, b, c;

c = a + b;

Matrix 类对象的加法和赋值操作符用重载操作符来实现被重载的操作符是一个带有特殊名字的函数在加法操作符的例子中函数名是operator+ 让我们为这个重载操作符提供一个定义

Matrix operator+(Matrix m1, Matrix m2) // (按值传递)该实现支持两个Matrix 对象的加法如a + b

{

Matrix result;

// do the computation in result

return result;

}

但不幸的是它的效率低得让人难以接受。注意operator+()的参数不是引用这意味着operator+()的实参是按值传递的两个Matrix 对象a b 的内容被拷贝到opertor+()函数的参数区中因为Matrix 类对象非常大分配这样一个对象并把它拷贝到函数参数区中的时间和空间开销高得让人难以接受为了提高我们的操作符函数的效率假定我们决定把参数声明为指针下面是对operator+()新的实现代码

Matrix operator+( Matrix *m1, Matrix *m2 ) // 使用指针参数重新实现

{

Matrix result;

// result 中计算

return result;

}

但是这个实现代码有这样的问题虽然我们获得了效率但是它是以放弃加法操作符用法的直观性为代价的。现在指针参数要求我们传递地址作为实参,它们指向我们希望做加法操作的Matrix 对象,现在我们的加法操作必须如下编程

&a + &b; // 不太好, 但也不是不可能

 

但是这比较难看而且用户接口不友好,在一个复合表达式中加三个对象变得很困难

 &a + &b + &c; //! 这无法工作  &a + &b 的返回类型是 Matrix 对象

为了使在指针方案下三个对象的加法能够很好地实现程序必须这样写

&( &a + &b ) + &c; // ok: 这样能行, 但是当然没有人希望那样写。

 

引用参数提供了我们需要的方案。引用参数的实参是Matrix 对象本身,这允许我们像对内置数据类型的对象使用加法操作符一样自然地使用加法操作符。

下面是Matrix 类的重载加法操作符的修订版本

Matrix operator+( const Matrix &m1, const Matrix &m2 ) // 使用引用参数的新实现

{

Matrix result;

// result 中进行计算

return result;

}

该实现支持如下形式的Matrix 对象的加法

a + b + c

为了支持类class 类型——尤其是支持有效直观地实现重载操作符机制C++特别引入了引用机制

 

 

7.3.3 数组参数

C++中数组永远不会按值传递,它是传递第一个元素,准确地说是第0 个的指针例如如下声明

void putValues( int[ 10 ] );

被编译器视为

void putValues( int* );

数组的长度与参数声明无关因此下列三个声明是等价的

void putValues( int* );

void putValues( int[] );

void putValues( int[ 10 ] );

 

因为数组被传递为指针所以这对程序员有两个含义

·  在被调函数内对参数数组的改变将被应用到数组实参上而不是本地拷贝上,当用作实参的数组必须保持不变时,程序员需要保留原始数组的拷贝函数可以通过把参数类型声明为const 来表明不希望改变数组元素

void putValues( const int[ 10 ] );

· 数组长度不是参数类型的一部分函数不知道传递给它的数组的实际长度编泽器也不知道当编译器对实参类型进行参数类型检查时并不检查数组的长度

 

另外一种机制是将参数声明为数组的引用当参数是一个数组类型的引用时,数组长度成为参数和实参类型的一部分,编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配。

#include <iostream>

void putValues( int (&ia)[10] ) //传递过来的数组必须是内含10个元素的数组

{

cout << "( 10 )< ";

for ( int i = 0; i < 10; ++i ) {

cout << ia[ i ];

if ( i != 9 )

cout << ", ";// 用逗号分隔元素

}

cout << " >/n";

}

 

参数也可以是多维数组这样的参数必须指明第一维以外的所有维的长度例如

void putValues( int matrix[][10], int rowSize );

matrix 声明成一个二维数组每行由10 个列元素构成matrix 可以被等价地声明为

int (*matrix)[10] // matrix 的类型是指向10int 的数组的指针

 

 

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

 
原创粉丝点击