一、传值参数 int add(int a, int b ,int c)
当函数运行结束时,形式参数所属数据类型的析构函数负责释放该形式参数。
当一个函数返回时,形式参数的值不会被复制到对应的实际参数中。因此,函数调用不会修改
实际参数的值。
二、模板函数
假定要编写几个浮点数相加,和上面函数差不多,只是数据类型不同,故可用模板函数。
template<class T> T Abc(T a, T b, T c) { return a+b+b*c+(a+b-c)/(a+b)+4; }
T可用替换成float ,int,long都可用。
三、引用参数
上面的函数执行消耗时间太多,假如为三个参数1000个元素的矩阵,那么调用时要进行很多次的复制构造函数,当函数调用结束时,要调用析构函数花很多次来释放a,b,c,故用引用参数。与传值参数的情况不同,在函数被调用时,本程序并没有复制实际参数的值,在函数返回时也没有调用析构函数。
template<class T> T Abc(T& a, T& b, T& c) { return a+b+b*c+(a+b-c)/(a+b)+4; }
四、常量引用参数
这种模式指出函数不得修改引用的参数。
template<class T> T Abc(const T& a, const T& b, const T& c) { return a+b+b*c+(a+b-c)/(a+b)+4; }
a,b 和c 的值不能被修改。对于int、float 和char 的简单数据类型,当函数不会修改实际参数值的时候可以采用传值参数;对于所有其他的数据类型(包括模板类型),当函数不会修改实际参数值的时候可以采用常量引用参数。
参数类型为多种的模板类:
template<class Ta, class Tb, class Tc > Ta Abc (const Ta& a, const Tb& b, const Tc& c) { return a+b+b*c+(a+b-c)/(a+b)+4; }
返回值类型与第一个参数相同。
五、返回值
返回值可以是具体的值,引用或常量引用。
对于返回值是具体值的:被返回的对象均被复制到调用环境中。因为函数所计算出的表达式的结果被存储在一个局部临时变量
中,当函数返回时,这个临时变量(以及所有其他的临时变量和传值形式参数)所占用的空间将被释放,其值当然也不再有效。为了避免丢失这个值,在释放临时变量以及传值形式参数的
空间之前,必须把这个值从临时变量复制到调用该函数的环境中去。
返回一个引用:
T& X(int i, T& z) return z;
返回引用参数Z
这种返回形式不会把z 的值复制到返回环境中。当函数X返回时,传值形式参数i 以及所有局部变量所占用的空间都将被释放。由于z 是对一个实际参数的引用,因此,它不会受影响。
返回一个常量引用:
const T& X (int i, T& z)
与返回常量引用的不同点:除了返回的结果是一个不变化的对象之外,返回一个常量引用与返回一个引用是相同的。
六、递归函数
递归函数包括两种:直接递归(direct recursion)和间接递归(indirect recursion)。直接递归是指函数F的代码中直接包含了调用F的语句,而间接递归是指函数F调用了函数G,G又调用了H,如此进行下去,直到F又被调用。
一个简单的例子是计算阶乘:
int Factorial (int n) { / /计算n! if (n<=1) return 1; else return n * Factorial(n- 1 ) ; }
七、动态存储分配
操作符n e w:该操作符返回一个指向所分配空间的指针。
为了给一个整数动态分配存储空间,可以使用下面的语句来说明一个整型指针变量:int *y ; 当程序需要使用该整数时,可以使用如下语法来为它分配存储空间:y = new int;
操作符n e w分配了一块能存储一个整数的空间,并将指向该空间的指针返回给y,y是对整数指针的引用,而* y则是对整数本身的引用。
下面三个动态分配空间等价:
int *y = new int; *y = 10; 或 int *y = new int (10); 或 int *y; y = new int (10);
一维数组:数组的大小它们可能随着函数调用的变化而变化。
在运行时创建一个大小为n 的一维浮点数组 :float *x = new float [n]; 操作符n e w分配n 个浮点数所需要的空间,并返回指向第一个浮点数的指针。
异常处理:采用try - catch 结构来捕获(c a t c h)new 所引发的异常:
float *x; try {x = new float [n];} catch (xalloc) { // 仅当new 失败时才会进入 cerr << "Out of Memory" << endl; e x i t ( 1 ) ; }
当一个异常出现时,程序进入与该异常相对应的c a t c h语句块中执行。在上面的例子中,只有在执行try 语句时产生了xalloc 异常,才会进入catch (xalloc) 语句块,由语句exit (1) 终止程序的运行。(exit () 在stdlib.h 中定义。)
语法catch (...) 定义了一个能捕获所有异常的c a t c h块。当一个异常被引发时,检查在程序执行过程中所遇到的最接近的try-catch 代码,如果其中的一个catch 块能够处理所产生的异常,程序将从这个catch 块中继续执行,否则,继续检查直到找到与异常相匹配的块。
操作符d e l e t e
动态分配的存储空间不再需要时应该被释放。以释放分配给* y的空间以及一维数组x:
delete y;
delete [ ] x;
二维数组
当形式参数是一个二维数组时,必须指定其第二维的大小。例如,a[ ][10]是一个合法的形式参数,而a[ ][ ] 不是。克服这种限制的一条有效途径就是对于所有的二维数组使用动态存储分配。
当一个二维数组每一维的大小在编译时都是已知时,可以采用类似于创建一维数组的语法来创建二维数组。例如,一个类型为char的7×5数组可用如下语法来定义:
char c[7][5];
如果在编译时至少有一维是未知的,必须在运行时使用操作符n e w来创建该数组。一个二维字符型数组,假定在编译时已知其列数为5。
动态分配二维数组如下:
char (*c)[5]; try { c = new char [n][5];} catch (xalloc) {//仅当n e w失败时才会进入 cerr << "Out of Memory" << endl; exit (1);}
数组的行数n要么通过计算来确定,要么由用户来指定。如果在编译时数组的列数也是未知的,那么不可能调用一次n e w就能创建该数组(即使数组的行数是已知的)。构造二维数组时,可以把它看成是由若干行组合起来的,每一行都是一个一维数组,可以按照前
面讨论的方式用n e w来创建,指向每一行的指针可以保存在另外一个一维数组之中。
x[0], x[1], x[2]分别指向第0行,第1行和第2行的第一个元素。所以,如果x是一个字符数组,那么x [ 0 : 2 ]是指向字符的指针,而x本身是一个指向指针的指针。
其结构可用用下图来表示:
可用如下语法来说明x :
char **x;
为了创建如图1 - 1所示的存储结构,可用如下代码:
template <class T> bool Make2DArray ( T ** &x, int rows, int cols) {// 创建一个二维数组 t r y { / /创建行指针 x = new T * [rows]; / /为每一行分配空间 for (int i = 0 ; i < rows; i++) x[i] = new int [cols]; return true; } catch (xalloc) {return false;} }
该程序创建一个类型为T的二维数组,这个数组有r o w s行和c o l s列。程序首先为指针x [ 0 ] , . . . , x [ r o w s - 1 ]申请空间,然后为数组的每一行申请空间。如果没有出现异常,数组将被成功创
建,函数M a k e 2 D A r r a y返回t r u e。对于所创建的数组x中的元素,可以使用标准的用法来引用,
如x [ i ] [ j ] ,其中0≤i<r o w s , 0≤j<c o l s。
释放上面申请的数组空间:首先释放在f o r循环中为每一行所分配的空间,然后释放为行指针所分配的空间。x被置为0,以便阻止用户继续访问已被释放的空间。
template <class T> void Delete2DArray( T ** &x, int rows) {// 删除二维数组x / /释放为每一行所分配的空间 for (int i = 0 ; i < rows ; i++) delete [ ] x[i]; / /删除行指针 delete [] x; x = 0; }
八、类
class Currency {
public:
Currency(sign s = plus, unsigned long d = 0, unsigned int c = 0);//构造函数
// 析构函数
~Currency() {}
//这个函数被称为析构函数。每当一个Currency对象超出作用域时将自动调用析构函数。这个函数用来删除对象。
bool Set(sign s, unsigned long d, unsigned int c);
bool Set(float a);
sign Sign() const {return sgn;}
unsigned long Dollars() const {return dollars;}
unsigned int Cents() const {return cents;}
Currency Add(const Currency& x) const;//Add函数不会修改当前对象,是一个常元函数
Currency& Increment(const Currency& x);//函数Increment把对象x 的货币数量添加到当前对象上,这个函数修改了当前对象,因此不是一个常元函数.。它返回的是一个全局对象,因而不需要复制。
void Output() const;//不会修改当前对象,因此是一个常元函数。
private:
sign sgn;
unsigned long dollars;
unsigned int cents;} ;
采用如下两种方式来创建C u r r e n c y类对象:
Currency f, g (plus, 3,45), h (minus, 10);
Currency *m = new Currency ( plus, 8, 12);
复制构造函数被用来执行返回值的复制及传值参数的复制。程序1 - 1 5中没有给出复制构造
函数,所以C + +将使用缺省的复制构造函数,它仅可进行数据成员的复制。对于类C u r r e n c y来
说,使用省缺的复制构造函数已经足够。后面还将看到许多类,对于这些类缺省的复制构造函数已难以胜任它们的复制工作。
Currency类的构造函数
Currency::Currency(sign s, unsigned long d, unsigned int c) {// 创建一个C u r r e n c y对象 if(c > 99) { / /美分数目过多 cerr << "Cents should be < 100" << endl; e x i t ( 1 ) ; } sgn = s; dollars = d; cents = c; }
看一下调用g.Increment(h);函数I n c r e m e n t的第一行调用了p u b l i c成员函数A d d,它把x (这里是h) 加到当前对象上(这里是g),所得结果被返回,并被赋给* t h i s,* t h i s就是当前对象。由于该对象不是函数I n c r e m e n t的局部对象,因此当函数结束时,该对象不会自动被删除。所以可以返回一个引用。
程序 Increment与O u t p u tCurrency& Currency::Increment(const Currency& x) {// 增加量x . *this = Add(x); return *this; } void Currency::Output () const {// 输出currency 的值 if (sgn == minus) cout << '-'; cout << '<< dollars << '.'; if (cents < 10) cout << "0"; cout << cents; }
使用不同的描述方法
现在我们想要对C u r r e n c y类的描述进行修改,使其应用频率最高的两个函数A d d和I n c r e m e n t可以运行得更快,从而提高应用程序的执行速度。由于用户仅能通过p u b l i c部分所提供的接口与C u r r e n c y类进行交互,因此对p r i v a t e部分的修改并不会影响应用代码的正确性。
Currency类的新定义
- <span style="font-weight: normal;">class Currency {
- p u b l i c :
- // 构造函数
- Currency(sign s = plus, unsigned long d = 0, unsigned int c = 0);
- // 析构函数
- ~Currency() {}
- bool Set(sign s, unsigned long d, unsigned int c);
- bool Set(float a);
- sign Sign() const
- {if (amount < 0) return minus;
- else return plus;}
- unsigned long Dollars() const
- {if (amount < 0) return (-amount) / 100;
- else return amount / 100;}
- unsigned int Cents() const
- {if (amount < 0)
- return -amount - Dollars() * 100;
- else return amount - Dollars() * 100;}
- Currency Add(const Currency& x) const;
- Currency& Increment(const Currency& x)
- {amount += x.amount; return *this;}
- void Output() const;
- p r i v a t e :
- long amount;</span>
- } ;
- Currency::Currency(sign s, unsigned long d, unsigned int c)
- {// 创建Currency 对象
- if (c > 99)
- {// 美分数目过多
- cerr << "Cents should be < 100" << endl;
- e x i t ( 1 ) ; }
- amount = d * 100 + c;
- if (s == minus) amount = -amount;
- }
- bool Currency::Set(sign s, unsigned long d,
- unsigned int c)
- {// 取值
- if (c > 99) return false;