05.程序组织与开发方法
库与接口
库与程序文件
程序文件:源文件(*.cpp)、头文件(*.h、*.hpp、*),必须包含main()作为程序的入口
库:源文件与头文件,因为库文件不会单独执行,所以无需包含main()。源文件提供库的具体实现,头文件提供库的接口。
接口
通过接口使用库:包括指定库的头文件与源文件
优势:不需了解库的实现细节,只需了解库的使用方法
随机数库
随机数的生成
编写程序,调用rand()函数生成五个随机数
第一版
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int i;
cout<<"On this computer, the RAND_MAX is "<< RAND_MAX <<".\n";
cout<<"Five numbers the rand function generates as follows:\n";
for(i = 0;i < 5; i++)
cout<<rand()<<";";
cout<<endl;
return 0;
}
程序运行结果:
PS E:\C++\Random> .\Random //第一次执行
On this computer, the RAND_MAX is 32767.
Five numbers the rand function generates as follows:
41;18467;6334;26500;19169;
PS E:\C++\Random> .\Random //第二次执行
On this computer, the RAND_MAX is 32767.
Five numbers the rand function generates as follows:
41;18467;6334;26500;19169;
PS E:\C++\Random> .\Random //第三次执行
On this computer, the RAND_MAX is 32767.
Five numbers the rand function generates as follows:
41;18467;6334;26500;19169;
每次产生的随机数相同。C++ 库有一个名为 rand() 的函数,每次调用该函数都将返回一个非负整数。要使用 rand() 函数,必须在程序中包含 <cstdlib> 头文件。但是,该函数返回的数字其实是伪随机数。这意味着它们具有随机数的表现和属性,但实际上并不是随机的,它们实际上是用算法生成的。该算法需要一个起始值,称为种子,以生成数字。如果没有给出一个种子,那么它将在每次运行时产生相同的数字流。
要在每次运行程序时获得不同额随机数字流,则必须为随机数生成器提供一个种子以开始。在C++中,这是通过srand()完成的。在rand()被调用之前,srand()要先被调用,并且srand()在整个程序中仅被调用一次。如下随机数生成第二版所示:
#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;
int main()
{
int i;
unsigned int seed;
cout<<"On this computer, the RAND_MAX is "<< RAND_MAX <<".\n";
cout<<"Input the seed: ";
cin>>seed;
cout<<"Five numbers the rand function generates as follows:\n";
srand(seed);
for(i = 0;i < 5; i++)
cout<<rand()<<";";
cout<<endl;
return 0;
}
程序运行结果:
PS E:\C++\Random> ./Randomv2 //第一次运行
On this computer, the RAND_MAX is 32767.
Input the seed: 3
Five numbers the rand function generates as follows:
48;7196;9294;9091;7031;
PS E:\C++\Random> ./Randomv2 //第二次运行
On this computer, the RAND_MAX is 32767.
Input the seed: 2
Five numbers the rand function generates as follows:
45;29216;24198;17795;29484;
PS E:\C++\Random> ./Randomv2 //第三次运行
On this computer, the RAND_MAX is 32767.
Input the seed: 3
Five numbers the rand function generates as follows:
48;7196;9294;9091;7031;
srand(seed),seed只接受非负整数。当种子seed相同时,产生的随机数相同。
获取种子值的常见做法是调用time(),它是C++标准库的函数。
time()返回从1970年1月1日午夜到现在逝去的秒数,因此每次运行程序,它都将提供不同的种子值。如下所示:
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
int i;
cout<<"On this computer, the RAND_MAX is "<< RAND_MAX <<".\n";
cout<<"Five numbers the rand function generates as follows:\n";
srand((int)time(0));
for(i = 0;i < 5; i++)
cout<<rand()<<";";
cout<<endl;
return 0;
}
限制随机数的范围
要将随机数的范围限制在1和某个最大值max之间的整数,则可使用如下公式:
number = rand() % max + 1
;
例如生成1~9的随机数:
number = rand() % 9 + 1
;
余数范围为08,加1即为19的随机数
可以扩展到任意范围内的随机数,通用公式如下:
number = (rand() % (maxValue - minValue + 1)) + minValue;
库的设计原则
习惯把自定义的库的头文件保存为*.h,源文件保存为*.cpp。
例如:要使用自定义的随机数库产生符合要求的随机数,需定义三个文件,CustomRandom.h,CustomRandom.cpp,main.cpp
随机数库接口
接口设计原则
用途一致:接口中所有函数都属于同一类问题
操作简单:函数调用方便,最大限度隐藏操作细节
功能充足:满足不同潜在用户的需要
性能稳定:经过严格测试,不存在程序缺陷
实例
CustomRandom.h包含自定随机数库的接口
void Randomize();
int GenerateRandomNumber(int low, int high);
double GenerateRandomReal(double low, double high);
随机数库实现
实例
CustomRandom.cpp包含自定随机数库的具体实现
#include <iostream>
#include <cstdlib> //包含srand()
#include <ctime> //包含time()
using namespace std;
void Randomize()
{
srand((int) time(0));
}
int GenerateRandomNumber(int low, int high)
{
double _d;
if(low > high)
{
cout<<"GenerateRandomNumber:make sure low <= high. \n ";
exit(1);
}
_d = (double)rand()/((double)RAND_MAX + 1.0);
return low + (int)(_d * (high - low + 1));
}
double GenerateRandomReal(double low, double high)
{
double _d;
if(low > high)
{
cout<<"GenerateRandomReal:make sure low <= high. \n ";
exit(2);
}
_d = (double)rand()/((double)RAND_MAX);
return low + _d * (high - low);
}
随机数库测试
单独测试库的所有函数:合法参数时返回结果是否正确;非法参数时返回结果是否正确,即容错功能是否正常。
联合测试:多次运行程序,查看生成的数据是否随机;测试整数与浮点数随机数是否均能正确工作。
实例
main.cpp来调用自定随机数库
#include <iostream>
#include "CustomRandom.h" //标准库用<>,自定的库需要用""
using namespace std;
int main()
{
int i;
Randomize();
for(i = 0; i < 8; i++)
{
int t = GenerateRandomNumber(10,99);
cout<<t<<";";
}
cout<<endl;
for(i = 0; i < 8; i++)
{
int t = GenerateRandomReal(10.0,99.0);
cout<<t<<";";
}
cout<<endl;
}
作用域与生存期
量的作用域与可见性
作用域与可见性
作用域:标识符的有效范围
可见性:程序中某个位置是否可以使用某个标识符
标识符仅在其作用域内可见,位于作用域内的标识符不一定可见
局部数据对象
定义于函数或复合语句块内部的数据对象(包括变量、常量于函数形式参数等)
局部数据对象具有块作用域,仅在定义它的块内有效
有效性从定义出开始知道该块结束
多个函数定义同名的数据对象是允许的
全局数据对象
定义于函数或复合语句块之外的数据对象
全局数据对象具有文件(全局)作用域,有效性从定义处开始直到本文件结束,其后函数都可直接使用
若包含全局数据对象定义的文件被其他文件包含,则其作用域扩展到宿主文件中,这可能会导致问题,所以一般不要在头文件中定义全局数据对象
函数原型作用域
定义在函数原型中的参数具有函数原型作用域,其有效性仅延续到此函数原型结束
函数原型中参数名称可以与函数实现中的不同,也可以省略
量的存储类与生存期
生存期:量在程序中存在的时间范围
C/C++使用存储类表示生存期
作用域表达量的空间特性,存储类表达量的时间特性
静态(全局)生存期
全局数据对象具有静态(全局)生存期
生死仅与程序是否执行有关
自动(局部)生存期
局部数据对象具有自动(局部)生存期
生死仅与程序流程是否位于该块中有关
程序每次进入该块时就为该对象分配内存,退出该块时释放内存
两次进入该块时使用的不是同一个数据对象
static关键字
修饰局部变量:静态局部变量
使局部变量具有静态生存期
程序退出该块时局部变量仍存在,并且下次进入该块时使用上一次的数据值
静态局部变量必须进行初始化
不改变量的作用域,仍具有块作用域,即只能在该块中访问,其他代码段不可见
修饰全局变量
使其作用域仅限定于本文件内部,其他文件不可见
函数的作用域与生存期
所有函数都具有文件作用域与静态生存期
在程序每次执行时都存在,并且可以在函数原型或函数定义之后的任意位置调用
内部函数与外部函数
外部函数:可以被其他文件中的函数所调用
内部函数:不可以被其他文件中的函数所调用
函数缺省时均为外部函数
内部函数定义:使用static关键字
内部函数示例:static int Transform(int x);
内部函数示例:static int Transform(int x){...}
声明与定义
声明不是定义
定义是在程序产生一个新实体
声明仅仅在程序中引入一个实体
函数的生命与定义
声明是给出函数的原型,定义是给出函数实现代码
类型的声明与定义
产生新类型就是定义
类型定义示例:typedef enum _BOOL{FALSE,TRUE} BOOL;
不产生新类型就不是定义,而仅仅是声明
类型声明示例:enum _BOOL;
典型软件开发流程
软件工程的思想