摘要
- 复习计算机基础四大件,打牢基础。
第一章 C++回顾
- 第一章讨论C++语言的一些特性。因为本章不是C++入门,所以没有介绍诸如赋值语句,if语句和循环语句(例如for和while)等基本结构。本章要复习的C++特性如下:
- 参数传递的不同方式(例如值传递,引用传递和常量引用传递)
- 函数或方法返回的不同方式(例如值返回,引用返回和常量引用返回)
- 模板函数
- 递归函数
- 常量函数
- 内存分配和释放函数: new,delete
- 异常处理结构: try, catch, throw
- 类与模板类
- 类的公有成员,保护成员和私有成员
- 友元
- 操作符重载
- 标准模板库
- 本章包含如下应用程序的代码:
- 动态分配和释放一维和二维数组
- 求解二次方程
- 生成n个元素的所有排列方式
- 寻找n个元素的最大值
1.1 引言
- 在检查一个程序时,我们应该问如下几个问题
- 它正确吗?
- 它容易读懂吗?
- 它有完善的文档吗?
- 它容易修改吗?
- 它在运行时需要多大的内存?
- 它的运行时间有多长?
- 它的通用性如何?能否不加修改就可以解决更大范围的数据?
- 它可以直接在多种计算机上编译和运行吗?或者说它需要修改之后才能运行?
- 上述一些问题的重要性是相对的,取决于应用环境。但不管具体的应用环境是什么,程序最重要的特性是正确。
1.2 函数与参数
1.2.1 传值参数
1 | int abc(int a, int b, int c) |
a,b和c是函数abc的形参(formal parameter),每一个形参都是整型的。
如果在下面的语句中调用函数abc
- z = abc(2, x, y);
那么, 2, x和y便分别是a,c和c对应的实参(actual parameter)
形参a,b,c实际上是传值参数(value parameter)。在运行时,函数abc执行前,把实参复制给形参,复制过程是由形参类型的复制构造函数(copy constructor)来完成的。如果实参和形参的类型不同,必须进行类型转换,把实参转换为形参的类型,当然,前提是这样的类型转换是允许的。
当函数运行结束时,形参类型的析构函数(destructor)负责释放形式参数。当一个函数运行结束时,形参的值不会被复制到对应的实参中。因此,函数调用不会修改与形参对应的实参的值。
1.2.2 模板函数
1 | template<class T> |
1.2.3 引用参数
- 使用形参会增加程序的运行时间。例如,我们来考察一下函数被调用以及返回时所涉及的操作。当a,b和c是传值参数时,一进入函数调用,类型T的复制构造函数便把相应的实参分别复制给形参a,b和c,以供函数使用。当函数返回时,类型T的析构函数被启用,以释放形式参数a,b和c的空间。
1
2
3
4
5template<class T>
T abc(T& a, T& b, T& c)
{
return a + b * c;
} - 与传值参数的情况不同,当函数被调用时,这个程序没有复制实参的值,在函数返回时,也没有调用析构函数。
1.2.4 常量引用参数
- C++还提供了另外一种参数传递模式–常量引用(const reference)。这种模式指明的引用参数不能被函数修改。
- 用关键字const来指明函数不可修改的引用参数,这在软件工程方面具有重要意义。函数头告诉用户该函数不会修改实参。
1.2.5 返回值
- 一个函数可以返回一个值,一个引用或一个常量引用。前面的例子都是返回一个值。在这种情况下,返回的对象被复制到调用环境中。对于函数abc的所有版本来说,这种复制过程都是必要的,因为函数所计算出的表达式结果被存储在一个局部的临时变量中,当函数结束时,这个临时变量(以及所有其他的临时变量,局部变量和传值参数)所占用的空间将被释放,其值当然也不再有效。为了不丢失这个值,在释放临时变量,局部变量以及传值参数的空间之前,要把这个值从临时变量复制到调用该函数的环境中去。
- 给函数返回类型增加一个后缀 &,我们便指定了一个引用返回(reference return)。函数头: T& mystery(int i, T& z);
- 定义了一个函数 mystery,它返回的是类型T的一个引用。例如,可以使用下面的语句返回Z: return z;
- 这种返回形式不会把z的值复制到返回环境中。当函数结束时,形参i以及所有局部变量的空间都被释放。因为z仅仅是对一个实参的引用,所以它不受影响。
- 如果把关键字const加载函数头上,便得到const型引用返回(const reference return): const T& mystery(int i, T& z);
- const引用返回与引用返回是类似的,不同之处在于,const引用返回在返回调用环境时,必须将值赋给const常量。
1.2.6 重载函数
- 一个函数的签名(signature)是由这个函数的形参类型以及形参个数确定的。C++可以定义两个或更多的同名函数,但是任何两个同名函数不能有同样的签名。定义多个同名函数的机制称为函数重载(function overloading)。
1.3 异常
- 异常是表示程序出现错误的信息。
1.3.2 处理异常
- 一段代码抛出的异常由包含这段代码的try块来处理。紧跟在try块之后的是catch块。每一个catch块都有一个参数,参数的类型决定了这个catch块要捕捉的异常的类型。
- catch(char *e){} : 捕捉的异常类型是 char *
- catch(bad_alloc e){} : 捕捉的异常类型是 bad_alloc
- catch(exception& e){} : 捕捉的异常类型是基类型exception以及所有从exception派生的类型(例如 bad_alloc, bad_typeid)
- catch(…) {} : 捕捉所有异常,不管是什么类型。
- catch块一般包含异常改正之后所恢复的代码。如果不可能恢复,那么catch块的代码输出报错信息。
1.4 动态存储空间分配
1.4.1 操作符 new
- C++操作符new用来进行动态存储分配或运行时存储分配,它的值是一个指针,指向所分配空间。
1.4.4 操作符delete
- 动态分配的存储空间不再需要时应该把它释放。释放的空间可重新用来动态分配。C++操作符delete用来释放由操作符new所分配的空间。
1.7 递归函数
- 递归函数(recursive function)或方法自己调用自己。在直接递归(direct recursion)中,递归函数f的代码包含了调用f的语句,而在间接递归(indirect recursion)中,递归函数f调用了函数g,g又调用了函数h,如此进行下去,直至又调用了f。
1.7.3 C++递归函数
- 使用C++可以编写递归函数。正确的递归函数必须包含基础部分。每一次递归调用,其参数值都比上一次的参数值要小,从而重复调用递归函数使参数值达到基础部分的值。
1.8 标准模板库
- C++标准模板库(STL)是一个容器,适配器,迭代器,函数对象(也称仿函数)和算法的集合。有效使用STL,应用程序的设计会简单许多。
1.9 测试与调试
1.9.1 什么是测试
- 正确性是程序最重要的属性。
- 在程序测试(program testing)中,我们在目标计算机上,用一组输入数据来实际运行一个程序,然后将运行结果与期望结果相比较。这组输入数据称为测试数据(test data)。如果两个结果不同,就可以判定程序有问题。
- 测试的目的不是要证明程序是否正确,而是要暴露程序的错误。因此,选择的测试集一定要能暴露程序的错误。测试集不同,暴露的程序错误也不同。
1.9.3 调试
- 测试可以暴露程序的错误。一旦测试结果与期望结果不同,就说明程序有错误。确定并纠正程序错误的过程称为调试(debugging)。