学习书籍《数据结构、算法与应用-C++算法描述》(原数第二版)
文章目录
(20191228)第一章 C++回顾
概述
利用“数据结构、算法和程序”,我们可以解决现实生活中的许多难题。
程序开发过程要求我们做到两点:一是高效的数据描述;二是步骤合理、可用程序实现的算法设计。
1.1 引言
我们从一些方面考察一个程序的性能
1.2 函数与参数
1.2.1 传值参数
int abc(int a, int b, int c)
{
return a+b*c;
}
调用:
z = abc(2,x,y);
说明:
- a、b、c 形参(说明传值的类型)
- 2、x、y 实参(传入的实际参数)
- 运行时,函数abc执行前,形参类型的复制构造函数把实参复制给形参
- 函数abc执行结束后,形参类型的析构函数把形式参数释放
- 因此,函数调用不会修改与形参对应的实参的值。
- 但能利用实参的值拿到你想要的结果
1.2.2 函数模板
template<class T>
T abc(T a, T b, T c)
{
return a+b*c;
}
调用:
z=abc< int >(2,3,4);
z=abc< float >(2.1,2.2,2.3);
……
说明:
- 编译器会在编译时,把T替换为我们想要的类型
- 通过函数模板,我们的函数不再关心形参的类型,对各种类型通用
1.2.3 引用参数
template<class T>
T abc(T& a, T& b, T& c)
{
return a+b*c;
}
调用:
int x=1,y=2,z=3;
int s;
s=abc< int >(x,y,z);
……
说明:
- 引用是一种别名,不管是a还是x,都是在同一个地址取用数据
- 函数在执行前和执行结束后不再调用复制构造函数和析构函数,减少运行时间
1.2.4 常量引用参数
template<class T>
T abc(const T& a,const T& b,const T& c)
{
return a+b*c;
}
调用:同引用
说明:
- 由 1.2.3 可知,引用参数不同于传值参数,引用参数可以在函数中改变实际参数的值,当我们的函数不会改变实际参数的值时,使用 const 来说明。
拓展
template<class Ta,class Tb, class Tc>
T abc(const Ta& a,const Tb& b,const Tc& c)
{
return a+b*c;
}
说明:
传入不同数据类型的3个形参,返回值的类型与第一个形参相同。
1.2.5 返回值
1.3 异常
1.3.1 抛出异常
#include <iostream>
using namespace std;
int abc(int a, int b, int c)
{
if(a <= 0 || b <= 0 || c <= 0)
{
throw "All parameters should be >0";
}
return a+b*c;
}
int main()
{
cout << abc(2,0,3) << endl;
return 0;
}
运行结果:
terminate called after throwing an instance of 'char const*'
说明:
- 程序抛出 char* 异常,终止运行
- 抛出异常后并没有进行处理
1.3.2 处理异常
#include <iostream>
using namespace std;
int abc(int a, int b, int c)
{
if(a <= 0 || b <= 0 || c <= 0)
{
throw "All parameters should be >0";
}
return a+b*c;
}
int main()
{
try { cout << abc(2,0,3) << endl; }
catch(char* e)
{
cout<< e <<endl;
return 1;
}
return 0;
}
运行结果:
All parameters should be >0
说明:
- try 处理代码抛出的异常
- catch 捕捉异常
- catch 后的参数表明这个 catch块要捕捉的异常类型
catch(char * e),捕捉 char异常,如举例所示
catch(bad_alloc e),捕捉 bad_alloc异常
catch(exception e) , 捕捉exception以及所有从exception*派生的异常
catch(…) , 捕捉所有异常
1.4 动态存储空间分配
1.4.1 操作符new
C++操作符 new 用来进行动态存储分配或者运行时存储分配,它的值是一个指针,指向所分配空间。例如,要给一个整数动态分配存储空间,可用下面3种方式:
int *y;
y = new int;
*y=10
int *y = new int;
*y=10
int *y = new int(10);
说明:
- new 的值是一个指针,如举例中的y
- *y是对整数本身的引用
1.4.2 一维数组
动态存储分配的意义在于一些数组的大小随着函数调用的变化而变化
一个长度为n的浮点数组可以用如下方式来创建:
float *x = new float[n];
1.4.3 异常处理
对于n个浮点数,如果计算机没有足够的内存分配,会抛出 bad_alloc异常,使用 try-catch捕捉异常
float *x;
try { x = new float[n]; }
catch(bad_alloc e )
{
cerr<<"Out of Memory"<<endl;
exit(1);
}
备注:
- cerr 不可以重定向,只能输出到显示屏,cout 可以重定向,可以输出到文件
- exit() 会退出整个程序,并将参数返给主调进程
1.4.4 操作符delete
操作符 delete 用来释放有操作符 new 分配的空间
delete y; //释放*y
delete []x; //释放一维数组x
1.4.5 二维数组
二维数组的创建分3种情况,已知行列、已知列未知行、未知列已\未知行;
已知行列:
char c[7][5];
已知列未知行:
char (*c)[5];
try { c= new[n][5]; }
catch(bad_alloc e)
catch(bad_alloc e )
{
cerr<<"Out of Memory"<<endl;
exit(1);
}
未知行已\未知列时,首先使用动态存储空间分配的方式创建一个一维指针数组,用来指向每一行,在每行 new 一个一维数组。
#include <iostream>
using namespace std;
//创建二维数组
template<class T>
void make2dArray( T ** &x, int numberOfRaws, int numberOfColumns)
{
x = new T* [numberOfRaws];
for(int i=0;i<numberOfRaws;i++)
{
x[i]=new T[numberOfColumns];
}
}
//释放二维数组空间
template<class T>
void delete2dArray( T ** &x,int numbnumberOfRaws)
{
for(int i=0;i<numbnumberOfRaws;i++){
delete []x[i];
}
delete []x;
x = NULL;
}
int main()
{
char **x;
int raws=3,cols=3;
try
{
make2dArray(x,raws,cols);
for(int i=0;i<raws;i++){
for(int j=0;j<cols;j++){
x[i][j]=char(i+j+1);
}
}
for(int i=0;i<raws;i++){
for(int j=0;j<cols;j++){
cout<<x[i][j]<<" ";
}
cout<<endl;
}
delete2dArray(x,raws);
}
catch(bad_alloc e)
{
cerr<<"Could not create x"<<endl;
exit(1);
}
return 0;
}
理解:
- 使用动态分配存储空间的方式来创建二维数组时;
- 每一行是一个动态分配的一位数组,回想一维数组的构建,只需要一个指针就可以代表一个数组,指针指向数组的第一个元素。int * y=new int[10]
- 每一行是一个用一个指针表示的一维数组,有几行就有指针,把这些指针放到一个一维指针数组中。
- new 创建的空间记得 delete
1.5 自有数据类型
1.5.1 类currency
定义自有数据类型最灵活的方式就是使用C++的 类(class) 结构;
本节我们通过实现一个 货币(currency)类的实现来学习类的使用,由于涉及的代码量较大,我们在另一篇博客中具体讨论。
1.5.1 类currency的初步实现
1.5.2 类currency的另一种描述
暂略
1.5.3 操作符重载
类currency有若干成员函数与C++标准操作符类似。例如,add 实施的是 + 操作,increment 实施的是 += 操作。使用这些标准的C++标准操作符,要比定义诸如 add 与 increment 这样的成员函数要自然得多。操作符重载 可以扩大C++操作符的使用范围,使其操作新的数据类型和或类。
1.5.4 友元和保护性类成员
友元:可以访问类的私有或公有成员的其它类或函数,我们定义为友元。
保护性类成员: 派生类不能访问基类的私有成员,但可以访问基类的保护性类成员。
1.5.5 增加 #ifndef、#define和#endif语句
这种操作称为头文件的防卫式声明,包含在这句语句中的代码只编译一次。
1.6 异常类
1.7 递归函数
1.7.1 递归的数学函数
例如,斐波那契数列:
基础部分: F0=0;F1=1;
递归部分: Fn=Fn-1+Fn-2;(n>1)
递归部分的每一次应用都使我们更接近 基础部分,最后应用 基础部分。
F3=F2+F1=F1+F0+F1;
1.7.2 归纳
归纳证明 有三个部分:归纳基础、归纳假设 与 归纳步骤。
1.7.3 C++递归函数
例1-1 [阶乘]
int factorial(int n){
if(n<=1)return 1;
else return n*factorial(n-1);
}
例1-2 [数组元素求和]
方法一:数组累加求和
template<class T>
T sum(T a[],int n){
T theSum=0;
for(int i=0;i<n;i++){
theSum += a[i];
}
return theSum;
}
方法二:递归求和
template<class T>
T rsum(T a[],int n){
if(n>0){
return rsum(a,n-1)+a[n-1];
}
return 0;
}
例1-3 [排列]
输出n个元素的所有排列,例如a、b、c三个元素的所有排列是 abc acb bac bca cab cba