简介
参考书目:《C++数据结构与程序设计》Robert L. Kruse, Alexander J. Ryba · 著,钱丽萍 · 译
在大型项目中,需要更多的程序设计,以满足客户程序对数据以及格式等等需求,或者是一个出乎意料的问题,或者甚至是一个紧急事件。
本书的主要目的是描述程序设计的方法和工具,这些程序比用来示范初级程序设计特征的那些普通程序大得多。由于将杂碎的方法用于解决大型问题注定会失败,因此首先必须采用一种一致的,统一的和逻辑的方法,也必须仔细遵守程序设计的重要原理。这些原理有时候在编写小程序时被忽略,但对大型项目,忽略他们将证明是灾难性的。
算法设计中可变性的最大余地通常在于存储程序的数据的办法,因此本书的第二个目的是为数据组织和操作提供一些一流的但实质上简单的思想。本书中,表、栈和队列是最先学习的 3 种结构,最后还将为数据处理的重要任务设计一些强大的算法,如排序和查找。
Life游戏
- 方法 :实际上,每个C++类由表示变量或函数的成员所组成。表示变量的成员称为数据成员,这些成员用于存储数据值。表示某个类的函数的成员称为方法或成员函数,类的方法一般用于访问或改变数据成员;
- 客户程序 :访问某个特定类的用户程序,能够声明并操作该类的对象;
- 信息隐藏 :在书写客户程序时只需知道C++类的每个方法功能的精确描述就可以使用该类,不必知道数据实际上是如何存储的以及方法实际上是如何编程实现的;
- 私有(private) :变量和函数是类私有的,客户程序不必知道它们是什么,它们是如何设计的或者对它们有任何访问;
- 公有(public):一个类的方法是公有的,客户程序仅需要类说明和定义的公用方法;
- 占位函数:为了正确地编译主程序,在用到的每个函数的位置上应该放上占位函数(简短的哑函数),在编写占位函数时至少必须约束它们的相关参数或返回类型。
Life主程序
Life游戏
的算法大纲可转化成下面的C++程序:
# include "utility.h"
# include "life.h"
int main()
//Program to play Conway's game of Life.
/*Pre: The user supplies an initial configuration of living cells.
Post: The program prints a sequence of pictures showing the changes in the configuration of living cells according to the rules for the game of Life.
Uses: The class Life and its methods initialize(), print(), and update().
The functions instructions(), user_says_yes(). */
{
Life configuration;
instructions();
configuration.initialize();
configuration.print();
cout<<"Continue viewing new generations?<<endl;
while(user_says_yes()) {
configuration.update();
configuration.print();
cout<<"Continue viewing new generations?"<<endl;
}
}
实用函数user_says_yes()
在实用程序包utility.h
中声明,对于Life程序,所需要的关于utility.h
的仅有的其他信息是它以如下的指令开始:
# include <iostream>
using namespace std;
类Life的定义
类Life的实现包含在两个文件 life.h
和life.c
中,将函数和变量定义放入后缀为.h
的文件的做法是一种标准的惯例。在例子中,文件life.h
将给出类Life的说明,文件life.c
包含类的方法和函数instruction()
的实现。特别地,应该将函数和变量定义放入文件utility.c
中,并将其他种类的应用程序指令放入utility.h
中,如包含标准C++库文件的指令即放在utility.h
中。
程序中需要类Life的占位定义,并为类Life的方法提供占位函数,其中这些方法定义必须使用C++的作用域解析运算符::
以指示它们属于类Life的作用域:
//类Life的占位定义
class Life{
public:
void initialize();
void print();
void update();
}
//为类Life的方法提供占位函数
void Life::initialize(){}
void Life::print(){}
void LIfe::update(){}
//放在.h文件中的类Life的定义
const int maxrow=20, maxcol=60; //grid dimensions
class Life {
public:
void initialeze();
void print();
void update();
private:
int grid[maxrow+2][maxcol+2]; //allows for two extra rows and columns
int neighbor_count(int row, int col);
}
类Life的方法
- 对邻居计数
int Life::neighbor_count(int row, int col)
/*Pre: The Life object contains a configuration, and the coordinates row and col define a cell inside its hedge.
Post: The number of living neighbors of the specified cell is returned. */
{
int i,j;
int count=0;
for (i=row-1; i<=row+1; i++)
for(j=col-1; j<=col+1; j++)
count+=grid[i][j]; //Increase the count if neighbor is alive.
count-=grid[row][col]; //Reduce count, since cell is not its own neighbor.
}
- 更新网格
void Life::update()
/*Pre: The Life object contains a configuration.
Post: The Life object contains the next generation of configuration. */
{
int row, col;
int new_grid[maxrow+2][maxcol+2];
for (row=1; row<=maxrow; row++)
for (col=1; col<=maxcol; col++)
switch(neighbor_count(row,col)) {
case 2:
new_grid[row][col] = grid[row][col]; //Status stays the same.
break;
case 3:
new_grid[row][col] = 1; //Cell is not alive.
default:
new_grid[row][col] = 0; //Cell is now dead.
}
for (row=1; row<=maxrow; row++)
for (col=1; col<=maxcol; col++)
grid[row][col] = new_grid[row][col];
}
- 输入和输出
在仅希望刷新输出缓冲区而不结束一行的地方,可以用flush
替代endl
void instructions()
/*Pre: None.
Psot: Instructions for using the Life program have been printed. */
{
cout<<"Welcome to Conway's game of Life."<<endl;
cout<<"This game uses a grid of size"<<maxrow<<"by"<<maxcol<<"in which"<<endl;
cout<<"each cell can either be occupied by an organism or not."<<endl;
cout<<"The occupied cells change from generation to generation"<<endl;
cout<<"according to the number of neighboring cells which are alive."<<endl;
}
//设置初始配置
void Life::initialize()
/*Pre: None.
Post The Life object contains a configuration specified by the user. */
{
int row, col;
for (row=0; row<=maxrow+1; row++)
for (col=0; col<=maxcol+1; col++)
grid[row][col]=0;
cout<<"List the coordinates for living cells."<<endl;
cout<<"Terminate the list with the special pair-1-1"<<endl;
cin>>row>>col;
while (row!=1 || col!=1){
if (row>=1 && row<=maxrow)
if (col>=1 && col<=maxcol)
grid[row][col]=1;
else
cout<<"Column"<<col<<"is out of range."<<endl;
else
cout<<"Row"<<row<<"is out of range."<<endl;
cin>>row>>col;
}
}
void Life::print()
/*Pre: The Life object contains a configuration.
Post: The configuration is written for the user. */
{
int row,col;
cout<<"\nThe current Life configuration is:"<<endl;
for (row=1; row<=maxrow; row++) {
for (col=1; col<=maxcol; col++)
if (grid[row][col]==1)cout<<'*';
else cout<<' ';
cout<<endl;
}
cout<<endl;
}
- 用户响应
bool user_says_yes()
{
int c;
bool initial_response=true;
do { //Loop until an appropriate input is received.
if (initial response)
cout<<"(y,n)?"<<flush;
else
cout<<"Respond with either y or n:"<<flush;
do { //Ignore white space.
c=cin.get();
} while (c=='\n' || c==' ' || c=='\t');
initial_response=false;
} while (c!='y' && c!='Y' && c!='n' && c!='N');
return (c=='y' || c=='Y');
}
- 调试驱动器
调试和测试单个函数的一种方法是写一个较短的辅助程序来为此函数提供必要的输入、调用此函数并评价函数执行的结果,这样的辅助函数称为函数的驱动器。通过使用驱动器可以将每个函数分离出来逐个研究,因此经常能很快地发现错误。
int main() //driver for neighbor count
/*Pre: None.
Post: Verifies that the method neighbor_count() returns the correct values.
Uses: The class Life and its method initialize(). */
{
Life configuration;
configuration.initialize();
for (row=1; row<=maxrow; row++){
for (col=1; col<=maxcol; col++)
cout<<configuration.neighbor count(row,col)<<" ";
cout<<endl;
}
除以上方法外,函数configuration.initialize();
与函数configuration.print();
也可以用来互相检查。
另:三种测试数据的通用思想——黑盒方法,玻璃盒方法与Ticking-Box方法。
const 修饰用法
const 是 constant 的缩写,本意是不变的,不易改变的意思。在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数。
修饰普通类型的变量
a 被定义为一个常量,并且可以将 a 赋值给 b,但是不能给 a 再次赋值。对一个常量赋值是违法的事情,因为 a 被编译器认为是一个常量,其值不允许修改。
const int a = 7;
int b = a; // 正确
a = 8; // 错误,不能改变
修饰指针变量
const 修饰指针变量有以下三种情况。
- const 修饰指针指向的内容
const int *p = 8;
则内容为不可变量- const 修饰指针
int* const p = &a;
则指针为不可变量- const 修饰指针和指针指向的内容
const int * const p = &a;
则指针和指针指向的内容都为不可变量
修饰函数
对于 const 修饰函数参数可以分为三种情况:
- 值传递的 const 修饰传递
void func(const int a)
此时函数中出现a++是错误的,因为a不能被改变(一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值)- 当 const 参数为指针时
void func(int *const a)
,可以防止指针被意外篡改。- 自定义类型的参数传递
void func(const Test& t)
,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此采取 const 外加引用传递的方法。并且对于一般的 int、double 等内置类型,我们不采用引用的传递方式。
对于 const 修饰返回值分三种情况:
- const 修饰内置类型的返回值
const int func()
,修饰与不修饰返回值作用一样- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改
- const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么
修饰类成员函数
const 修饰类成员函数
void func()const
,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const成员函数。
注意:const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this指针,即不能实例化,const 成员函数必须具体到某一实例。