第五、六讲
第五讲 程序组织与软件开发方法
一 库与接口
1 库与程序文件
程序文件:源文件(.cpp)、头文件(.h、 *.hpp、 *)
库:源文件与头文件
2 接 口
通过接口使用库:包括指定库的头文件与源文件
优势:不需了解库的实现细节,只需了解库的使用方法
3 标准库
C标准库 标准输入输出库、工具与辅助函数库、字符串库
C++标准库 输入输出流库、字符串库、标准模板库
4 数学库
数学库 头文件:
math.h/cmath
库文件:
libm
链接方式:g++ -lm main.cpp (linux系统下)
5 标准辅助函数库
工具与辅助函数 头文件:
stdlib.h/cstdlib
常用函数
void exit( int status );
void free( void * p );
void * malloc( size_t size );
int rand();
void srand( unsigned int seed );
二 随机数库
1 随机数的生成
#include <iostream>
#include <cstdlib>
#include <ctime>
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 << "\n";
return 0;
}
2 库的设计原则
用途一致 接口中所有函数都属于同一类问题
操作简单 函数调用方便,最大限度隐藏操作细节
功能充足 满足不同潜在用户的需要
性能稳定 经过严格测试,不存在程序缺陷
3 随机数库接口
void Randomize();
int GenerateRandomNumber( int low, int high );
double GenerateRandomReal( double low, double high );
4 随机数库实现
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "random.h"
using namespace std;
void Randomize()
{
srand( (int)time(NULL) );
}
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));
}
5 随机数库测试
单独测试库的所有函数 合法参数时返回结果是否正确 非法参数时返回结果是否正确,即容错功能是否正常
联合测试 多次运行程序,查看生成的数据是否随机 测试整数与浮点数随机数是否均能正确工作
三 作用域与生存期
1 量的作用域与可见性
作用域与可见性 作用域:标识符的有效范围 可见性:程序中某个位置是否可以使用某个标识符 标识符仅在其作用域内可见 位于作用域内的标识符不一定可见
局部数据对象 定义于函数或复合语句块内部的数据对象(包括变量、常量与函数形式参数等) 局部数据对象具有块作用域,仅在定义它的块内有效 有效性从定义处开始直到该块结束 多个函数定义同名的数据对象是允许的
全局数据对象 定义于函数或复合语句块之外的数据对象 全局数据对象具有文件(全局)作用域,有效性从定义处开始直到 本文件结束,其后函数都可直接使用 若包含全局数据对象定义的文件被其他文件包含,则其作用域扩展 到宿主文件中, 这可能会导致问题 不要在头文件中定义全局数据对象
函数原型作用域 定义在函数原型中的参数具有函数原型作用域,其有效性仅延续到 此函数原型结束 函数原型中参数名称可以与函数实现中的不同,也可以省略
2 量的存储类与生存期
生存期:量在程序中存在的时间范围 C/C++ 使用存储类表示生存期 作用域表达量的空间特性,存储类表达量的时间特性
静态(全局)生存期 全局数据对象具有静态(全局)生存期 生死仅与程序是否执行有关
自动(局部)生存期 局部数据对象具有自动(局部)生存期 生死仅与程序流程是否位于该块中有关 程序每次进入该块时就为该对象分配内存,退出该块时释放内存; 两次进入该块时使用的不是同一个数据
static 关键字
修饰局部变量:静态局部变量 使局部变量具有静态生存期 程序退出该块时局部变量仍存在,并且下次进入该块时使用上一次 的数据值 静态局部变量必须进行初始化 不改变量的作用域,仍具有块作用域,即只能在该块中访问, 其他 代码段不可见 修饰全局变量 使其作用域仅限定于本文件内部,其他文件不可见
3 函数的作用域与生存期
所有函数具有文件作用域与静态生存期 在程序每次执行时都存在,并且可以在函数原型或函数定义之后的 任意位置调用
内部函数与外部函数 外部函数:可以被其他文件中的函数所调用 内部函数:不可以被其他文件中的函数所调用 函数缺省时均为外部函数 内部函数定义:使用 static 关键字 内部函数示例: static int Transform( int x ); 内部函数示例: static int Transform( int x ){ … }
4 声明与定义
声明不是定义 定义在程序产生一个新实体 声明仅仅在程序中引入一个实体
函数的声明与定义 声明是给出函数原型,定义是给出函数实现代码
类型的声明与定义 产生新类型就是定义 类型定义示例:
typedef enum __BOOL { FALSE, TRUE } BOOL;
不产生新类型就不是定义,而仅仅是声明 类型声明示例:enum BOOL;
5 全局变量的作用域扩展
全局变量的定义不能出现在头文件中,只有其声明才可以出现 在头文件中 声明格式:使用 extern 关键字
/* 库的头文件 */
/* 此处仅引入变量 a,其定义位于对应源文件中 */
extern int a; /* 变量 a 可导出,其他文件可用 */
/* 库的源文件 */
/* 定义变量 a */
int a;
四 典型软件开发流程
略
第六讲 复合数据类型
一 字 符
1 字符类型、字符文字与量
定义格式:
char ch; const char cch = 'C';
字符文字使用单引号对
实际存储时字符类型量存储字符的对应 ASCII 值
可使用 signed 与 unsigned 修饰字符类型
2 字符表示的等价性
char a = 'A'; char a = 65; char a = 0101; char a = 0x41;
3 ASCII 码
回车与换行
Windows: \n\r
Linux: \n
Mac: \r

二 数 组
1 数组的意义与性质
数组的定义 定义格式: 元素类型 数组名称[常数表达式]; 示例:
int a[8]; /* 定义包含 8 个整数元素的数组 */
特别说明 常数表达式必须是常数和常量,不允许为变量 错误示例: int count = 8; int c[count]; 数组元素编号从 0 开始计数,元素访问格式为 a[0]、 a[1]、 …… 不允许对数组进行整体赋值操作,只能使用循环逐一复制元素 错误示例三: int a[8], b[8]; a = b;
意义与性质 将相同性质的数据元素组织成整体,构成单一维度上的数据序列
2 数组的存储表示
数组的地址 数组的基地址:数组开始存储的物理位置 数组首元素的基地址:数组首个元素开始存储的物理地址, 数值上总是与数组基地址相同 “&” 操作符:&a 获得数组的基地址;&a[0] 获得数组首元素的基地址
3 数组元素的访问
4 数组与函数
数组元素作为函数实际参数 int Add( int x, int y ){ return( x + y ); } int a[2] = { 1, 2 }, sum; sum = Add( a[0], a[1] );
数组整体作为函数形式参数 基本格式: 返回值类型 函数名称( 元素类型 数组名称[], 元素 个数类型 元素个数 ) 示例:
void GenerateIntegers( int a[], unsigned int n );
特别说明:作为函数形式参数时,数组名称后的中括号内不需 列写元素个数,必须使用单独的参数传递元素个数信息
5 多维数组
多维数组的定义 定义格式: 元素类型 数组名称[常数表达式1] [常数表达式2]…; 示例一:
int a[2][2]; /* 2×2 个整数元素的二维数组 */
示例二:int b[2][3][4]; /* 2×3×4 个整数元素数组 */
特别说明:同单维数组多维数组的初始化 与一维数组类似:
int a[2][3] = {1, 2, 3, 4, 5, 6};
单独初始化每一维:int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
(建议用这种,调整格式写出矩阵形式)
三 结构体
1 结构体的意义与性质
结构体的意义 与数组的最大差别:不同类型数据对象构成的集合 当然也可以为相同类型的但具体意义或解释不同的数据对象集合
结构体类型的定义
`struct 结构体名称
{
成员类型 1 成员名称 1;
成员类型 2 成员名称 2;
……
成员类型 n 成员名称 n;
};`
2 结构体的存储表示
3 结构体数据对象的访问
结构体类型的变量与常量:按普通量格式定义 示例一: DATE date; 示例二: STUDENT zhang_san; 示例三: STUDENT students[8];
结构体量的初始化 示例四: DATE date = { 2008, 8, 8 };
结构体量的赋值 与数组不同,结构体量可直接赋值,拷贝过程为逐成员一一复制 示例五: DATE new_date; new_date = date;
结构体量成员的访问 使用点号操作符“.” 解析结构体量的某个特定成员 示例一:
DATE date; date.year = 2008; date.month = 8; date.day = 8;
嵌套结构体成员的访问 可以连续使用点号逐层解析 示例二:
struct FRIEND{ int id; STRING name; DATE birthday; };
FRIEND friend;
friend.birthday.year = 1988;
复杂结构体成员的访问 严格按照语法规范进行
4 结构体与函数
例:编写一函数,使用结构体类型存储日期,并返回该日在该年的第几天信息,具体天数从 1 开始计数,例如 2016 年 1 月 20 日返回 20,2 月 1 日返回 32
unsigned int GetDateCount( DATE date )
{
static unsigned int days_of_months[13] =
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
unsigned int i, date_id = 0;
for( i = 1; i < date.month; i++ )
date_id += days_of_months[i];
date_id += date.day;
if( date.month > 2 && IsLeap(date.year) )
date_id++;
return date_id;
}