2007年9月10日星期一

二分法查找&二分法排序

二分查找1、二分查找(Binary Search)
二分查找又称折半查找,它是一种效率较高的查找方法。
二分查找要求:线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。不妨设有序表是递增有序的。
2、二分查找的基本思想
二分查找的基本思想是:(设R[low..high]是当前的查找区间)
(1)首先确定该区间的中点位置:
(2)然后将待查的K值与R[mid].key比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找,具体方法如下:
①若R[mid].key>K,则由表的有序性可知R[mid..n].keys均大于K,因此若表中存在关键字等于K的结点,
则该结点必定是在位置mid左边的子表R[1..mid-1]中,故新的查找区间是左子表R[1..mid-1]。
②类似地,若R[mid].key<K,则要查找的K必在mid的右子表R[mid+1..n]中,即新的查找区间是右子表R[mid+1..n]。下一次查找是针对新的查找区间进行的。
因此,从初始的查找区间R[1..n]开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,
不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为K的结点,或者直至当前的查找区间为空(即查找失败)时为止。

3、二分查找算法
  int BinSearch(SeqList R,KeyType K)
{ //在有序表R[1..n]中进行二分查找,成功时返回结点的位置,失败时返回零
int low=1,high=n,mid; //置当前查找区间上、下界的初值
while(low<=high){ //当前查找区间R[low..high]非空
mid=(low+high)/2;
if(R[mid].key==K) return mid; //查找成功返回
if(R[mid].kdy>K)
high=mid-1; //继续在R[low..mid-1]中查找
else
low=mid+1; //继续在R[mid+1..high]中查找
}
return 0; //当low>high时表示查找区间为空,查找失败
} //BinSeareh

二分查找算法亦很容易给出其递归程序【参见练习
6、二分查找的优点和缺点
虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。
二分查找只适用顺序存储结构。为保持表的有序性,在顺序结构里插入和删除都必须移动大量的结点。因此,二分查找特别适用于那种一经建立就很少改动、而又经常需要查找的线性表。
对那些查找少而又经常需要改动的线性表,可采用链表作存储结构,进行顺序查找。链表上无法实现二分查找。

二分法排序

#include <stdlib.h>
#include <stdio.h>
void TwoInsertSort(int array[],int n)
{
int left,right,num;
int middle,j,i;
for(i = 1;i < n;i++)
{
left = 0;// 准备
right = i-1;
num = array[i];
while( right >= left)// 二分法查找插入位置
{
middle = ( left + right ) / 2; // 指向已排序好的中间位置
if( num < array[middle] )// 即将插入的元素应当在在左区间
right = middle-1;
else // 即将插入的元素应当在右区间
left = middle+1;
}
for( j = i-1;j >= left;j-- )// 后移排序码大于R[i]的记录
array[j+1] = array[j];
array[left] = num;// 插入
}
}

int rcmp( const int *a, const int *b)
{
return (*a-*b);
}
void main()
{
int array[50];
int i;
printf("The original array is :\n");
for( i=0; i<50; i++ )//数组初始化并显示
{
array[i] = 50-i;
printf("array[%d]:%d\n", i, array[i]);
}
TwoInsertSort(array,sizeof(array)/sizeof(int));//二分法排序
printf("\nAfter sorted :\n");
for( i=0; i<50; i++ )
printf("array[%d]:%d\n", i, array[i]);

//库函数bsearch用二分法查找一个有序数组中的一个特定数,并返回该数的地址

a = (int *)bsearch(&b, numarray, sizeof(numarray)/sizeof(numarray[0]), sizeof(int),rcmp);

}


七种场景下的软件工作量估算步骤

七种场景下的软件工作量估算步骤

场景一:合同前的工作量估算
场景描述
(1) 没有实施过CMMI2
(2) 合同未签,需要给客户报价
(3) 有客户的概要需求,有类似的项目数据可供参考
(4) 需要估计整个项目的总工作量,以便于估算总成本,给客户报价
估算步骤
1)寻找类似的历史项目,进行项目的类比分析,根据历史项目的工作量凭经验估计本项目的总工作量;
2)进行WBS分解,力所能及地将整个项目的任务进行分解;
3)参考类似项目的数据,采用经验法估计WBS中每类活动的工作量;
4)汇总得到项目的总工作量;
5)与第(1)步的结果进行印证分析,根据分析结果,确定估计结果。
场景二:基于详细需求的经验估计
场景描述
(1) 只有详细需求,没有历史数据
估算步骤:
1WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
2)采用经验法估计每个活动的工作量;
3)汇总得到:每个阶段的工作量、项目的总工作量。
其他说明:
在该场景下,只使用了经验法,无法对结果进行印证,难以判断结果的合理性。
场景三:由编码估算整体
场景描述
1)有类似项目的历史数据
2)有编码活动的生产率数据
3)有详细需求
4)实施了CMMI2级,但是没有积累历史项目的工作量分布数据
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
3)建立WBS分解中的活动与产品元素的映射关系,识别出WBS中哪些活动可以采用模型法估算;
4)估计产品元素的规模,可以采用代码行法或功能点法,并估计每个产品元素的复杂度、复用率等;
5)根据历史的编码阶段的生产率数据和产品元素的规模估计、复杂度、复用率等采用模型法计算每个产品元素的编码工作量;
6)根据历史的类似项目的数据及估算人的经验估计其他活动的工作量,可以采用经验法。
7)汇总得到:每个阶段的工作量、项目的总工作量。
其他说明:
在该场景下,混合使用了经验法与模型法,这2种方法互相补充,而不是互相印证。
场景四:由总体印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的全生命周期的生产率数据(含管理工作量)
3)有详细需求
4)实施了CMMI2级,但是没有积累历史项目的工作量分布数据
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法;
3)累计出整个产品的总规模,并估计产品总体的复杂度、复用率等;
4)根据类似项目的全生命周期的生产率数据和产品的总规模、复杂度、复用率等采用模型法计算总的开发工作量;
5WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
6)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
7)汇总得到:每个阶段的工作量、项目的总工作量。
8)与第(4)步得出的工作量进行比较印证,如果偏差不大,则以第(7)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,这些结果可以互相印证,以发现估算过程中的不合理之处,是估计更加合理。
场景五:三维印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的全生命周期的生产率数据(含管理工作量)
3)有详细需求
4)实施了CMMI3级,有历史项目的工作量分布数据(阶段分布、工种分布)
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法;
3)累计出整个产品的总规模,并估计产品总体的复杂度、复用率等;
4)根据类似项目的全生命周期的生产率数据和产品的总规模、复杂度、复用率等采用模型法计算总的开发工作量;
5)根据历史项目的工作量分布数据及第(4)步估算的项目总工作量,计算:
Ø 每个阶段的工作量
Ø 每个工种的工作量
6WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
7)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
8)汇总得到:每个阶段的工作量、每个工种的工作量、项目的总工作量。
9)与第(4)、(5)步得出的工作量进行比较印证,如果偏差不大,则以第(7)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,并且采用2种方法都得到了每个阶段、每个工种的工作量、项目的总工作量,可以从上述的3个维度对这些结果进行互相印证,以发现估算过程中的不合理之处,是估计更加合理。
场景六:四维印证基于WBS的估计
场景描述
1)有类似项目的历史数据
(2) 有类似项目的编码活动的生产率数据(不含管理工作量)
3)有详细需求
4)实施了CMMI3级,有历史项目的工作量分布数据(阶段分布、工种分布、阶段工种分布)
5)项目采用了瀑布模型
估算步骤
1)产品分解,将系统分为子系统,子系统分解为模块;
2)估计产品元素的规模,可以采用代码行法或功能点法,并估计每个产品元素的复杂度、复用率等;
3)根据类似项目的编码活动的生产率数据和产品元素的规模、复杂度、复用率等采用模型法计算每个产品元素的编码工作量;
4)根据历史项目的按工种的工作量分布数据及第(3)步的估算的编码工作量依次计算:
i)根据历史项目的编码的工作量占编码阶段的工作量的百分比与第(3)部计算出的编码工作量计算编码阶段的总工作量;
ii)根据历史项目的编码阶段各工种的工作量分布百分比计算编码阶段每个工种的工作量;
iii)根据历史项目的其他阶段的工作量与编码阶段的工作量比例计算其他阶段的总工作量;
iv)根据历史项目的其他阶段的每个工种的工作量分布百分比及第iii)步的结果计算其他阶段的每个工种的工作量;
5WBS分解,将任务分解到一个人或者一个小团队可以执行的颗粒度;WBS分解时要识别出所有的交付物、项目管理活动、工程活动等。
6)根据历史的类似项目的数据及估算人的经验估计所有活动的工作量,可以采用经验法。
7)汇总得到:每个阶段每个工种的工作量、每个阶段的工作量、每个工种的工作量、项目的总工作量。
8)与第(4)步得出的工作量进行比较印证,如果偏差不大,则以第(6)步的结果为准,如果偏差比较大,要仔细分析原因,可能的原因举例如下:
Ø 类似项目的生产率数据不适合本项目;
Ø WBS分解的颗粒度不够详细;
Ø 估算专家的经验不适合本项目;
Ø 具体任务的估计不合理;
针对原因,对估算的结果进行调整,使其趋向合理。
其他说明
在该场景下,对于项目的总工作量有2个结果或者多个结果,并且采用2种方法都得到了每个阶段的工作量、每个工种的工作量、每个阶段每个工种的工作量、项目的总工作量,可以从上述的4个维度对这些结果进行互相印证,以发现估算过程中的不合理之处,是估计更加合理。

场景七:需求变更的工作量估计
场景描述
(1) 有变更的需求描述
(2) 项目进行到了编码阶段
(3) 有本项目的编码的生产率
估算步骤:
1)进行需求变更的波及范围分析
2)进行本次变更的的WBS分解
3)对于变更引起的代码变化进行规模、复杂度等其他属性的估计
4)根据本项目的编码的生产率及估计的规模采用模型法估计工作量
5)对于WBS分解中其他活动进行经验估计
6)汇总所有的工作量得到本次变更的工作量估计

线程的使用

线程的使用
1.创建简单的线程;
在视图类的鼠标的消息映射函数中加入:
    AfxBeginThread((AFX_THREADPROC)TestThread,this);
然后在视图类中加入线程的入口函数:
UINT TestThread(LPVOID pParam)
{
    AfxMessageBox("这是一个测试工作线程的对话框");
    return 0;
}
函数说明:
CWinThread*
AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int
nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD
dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
CWinThread*
AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority =
THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
PfnThreadProc:
    指向工作者线程的控制函数,不能是NULL,函数的定义必须如下:
UINT MyControllingFunction( LPVOID pParam );
PThreadClass:CwinThread的RUNTIME_CLASS对象.
PParam:传递给控制函数的参数.
Npriority:线程的优先级,可以用函数
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);来设定
nStackSize:堆栈的大小,如果为0,就和创建的主线程一样的大小.
DwCreateFlags:创建线程时的附加标志,可以为CREATE_SUSPENDED,代表挂起线程,直到调用DWORD ResumeThread( );函数时才执行,为0,表示立即执行.
LpSecurityAttrs:指向一个SECURITY_ATTRIBUTES描述线程的安全性,如果与主线程一样的安全属性,可以为NULL.
另外可以用CwinThread类的成员函数
BOOL
CreateThread( DWORD dwCreateFlags = 0, UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );来创建一个线程,具体的参数和上面一样.
还可以用API来创建线程:
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
  DWORD dwStackSize,                         // initial thread stack size
  LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
  LPVOID lpParameter,                        // argument for new thread
  DWORD dwCreationFlags,                     // creation flags
  LPDWORD lpThreadId                         // pointer to receive thread ID
);
DWORD WINAPI YourThreadFunc(LPVOIDlpvThreadParm);
lpThreadId:32位标示符,用来接收线程的标示
函数返回的都是创建线程的句柄.
2.如何结束一个线程:
void AfxEndThread( UINT nExitCode );//结束当前正在运行的线程.
NExitCode:为线程退出代码.
BOOL TerminateThread(
  HANDLE hThread,    // handle to the thread
  DWORD dwExitCode   // exit code for the thread
);
如果线程调用了VOID ExitThread(UINTfuExitCode);也可以终止

3.如何取得线程的退出代码;
使用函数
BOOL GetExitCodeThread(
  HANDLE hThread,      // handle to the thread
  LPDWORD lpExitCode   // address to receive termination status
);
4.设置线程的优先级;
BOOL SetThreadPriority(
  HANDLE hThread, // handle to the thread
  int nPriority   // thread priority level
);
nPriority;
线程的优先级,有:THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_TIME_CRITICAL具体的函数参见MSDN。
在上面创建的工作者线程中我们可以这样设置优先级:
    SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);//设置比通常高两个点
5.如何创建多线程多任务。
可以调用利用上面的线程创建函数,传递给同一个线程函数不同的参数,也可以开始另外一个线程。
6.挂起和恢复线程:

前我提到过可以创建挂起状态的线程(通过传递CREATE_SUSPENDED标志给函数CreateThread来实现)。当你这样做时,系统创建指定
线程的核心对象,创建线程的栈,在CONTEXT结构中初始化线程CPU注册成员。然而,线程对象被分配了一个初始挂起计数值1,这表明了系统将不再分配
CPU去执行线程。要开始执行一个线程,一个线程必须调用ResumeThread并传递给它调用CreateThread时返回的线程句柄。
DWORD ResumeThread(HANDLE hThread);
一个线程可以被挂起多次。如果一个线程被挂起3次,则该线程在它被分配CPU之前必须被恢复3次。
除了在创建线程时使用CREATE_SUSPENDED标志,你还可以用SuspendThread函数挂起线程。
DWORDSuspendThread(HANDLE hThread);
7.线程同步对象的使用;

步对象有:Critical_section(临界区),Event(事件),Mutex(互斥对象),Semaphores(信号量)。都有相应的
API创建函数和MFC类,同步对象可以处于两种状态:信号状态(signal
state)或非信号状态,当一个线程与某个对象相关联时,若该对象处于非信号状态,则要等到其变成信号状态线程才能继续执行。
WIN32 API提供了等待命令WaitForSingleObject和WaitForMutipleObjects:
  DWORD WaitForSingleObject(
                HANDLE hObject,                         // 要等待的对象句柄
                DWORD dwMilliseconds);  // 最大等待时间(毫秒)
        DWORD WaitforMultipleObjects(
                DWORD dwNumObjects,             // 要等待的对象数
                LPHANDLE lpHandles,             // 对象句柄数组
                BOOL bWaitAll,               // 是否等待所有对象都有信号才返回
                DWORD dwMilliseconds);  // 最大等待时间

果在指定时间内对象达到 信号状态则返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT,出错返回
WAIT_FAILED。对于互斥量、信号量和自动重置(auto-reset)事件对象,等待成功
时将它们改成非信号状态(信号量计数器减1),以实现对象的互斥访问。
件:jianuangzhiganhue
8.事件:
"事件"――一个允许一个事件发生时线程通知另一个线程的同步对象。在一个线程需要了解何时执行任务时,事件是十分有用的。
CEvent对象有两种类型:自动和手工。一个手工CEvent对象存在于由ResetEvent 或SetEvent设置的状态中,直到另一个函数被调用。
一个自动CEvent对象在至少一个线程被释放后自动返回一个无标记(无用的)状态。
CEvent(
BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName
= NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn;如果为TRUE, CMultilock or CsingleLock对象就可以使用,为FALSE,线程就必须等到直到所需的资源被释放.
BManualReset:为TRUE,设置为手动复位事件对象,为FALSE,设置为自动复位事件对象.
LpszName:事件对象的名称,不能指定一个已经存在的对象
LpsaAttribute:事件对象的安全属性
与同步对象相关的多个同步访问对象CMultilock 和单个同步访问对象 CsingleLock对象.
CSingleLock( CSyncObject* pObject, BOOL bInitialLock = FALSE );
PObject:指向一个要处理的同步对象
BinitialLock:指定是否初始的时候就尝试访问对象。
CMultiLock( CSyncObject* ppObjects[ ], DWORD dwCount, BOOL bInitialLock = FALSE );
DwCount:指定同步对象的个数,必须大于0。
下面简单介绍他们的应用:

用此成员函数构造一个已命名或未命名的CEvent对象。要访问或释放一个CEvent对象,可建立一个CSingleLock或CMultiLock对
象并调用其Lock和Unlock成员函数。要将CEvent对象的状态改为已标记(无须等待的线程),可调用SetEvent或PulseEvent。
要设置一个CEvent对象为无标记(必须等待的线程),可调用ResetEvent。
可以在你的线程开始设置CEvent::SetEvent,其他线程使用CSingleLock::Lock来等待你线程处理结束。当你的线程处理完毕,
使用CEvent::ResetEvent清除事件,其他程序就可以继续执行了。
API函数:
    HANDLE CreateEvent(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针,Win98忽略
                BOOL bManualRest,              // 是否手动重置(manual-reset)
                BOOL bInitialState,            // 初始状态是否为信号状态
                LPCTSTR lpName);              // 对象名字符串指针

动重置(auto-reset)事件和手动重置(manual-reset)事件,这由CreateEvent()的第二个参数指定。对于自动重置事件,
WaitForSingleObject()和
WaitForMultipleObjects()会等待事件到信号状态,随后又自动将其重置为非信号状
态,这样保证了等待此事件的线程中只有一个会被唤醒。而手动重置事件需要用户调用ResetEvent()才会重置事件。可能有若干个线程在等待同一事
件,这样当事件变 为信号状态时,所有等待线程都可以运行了。
SetEvent()函数用来把事件对象设置成信号状态,ResetEvent()把事件对象重置成非信号状态,两者均需事件对象句柄作参数。
9.临界区

一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。在运行性能比较重要而且资源不会跨进程使用
时,建议采用临界区代替信号灯。有关在MFC中使用信号灯的详细信息,请参阅CMutex。使用CCriticalSection对象之前,需要构造它。
在构造函数返回后,就可以使用临界区了。在使用完之后要调用UnLock函数。存取由CCriticalSection控制的资源时,要在资源的存取函数
中定义一个CSingleLock型的变量。然后调用加锁对象的Lock成员函数(如CSingleLock::Lock)。此时,调用的线程要么获得对
资源的存取权,要么等待他人释放资源等待加锁,或者等待他人释放资源,但又因为超时而加锁失败。这样就保证了一次只有一个线程在存取临界资源。释放资源只
需调用成员函数UnLock(例如CSingleLock:Unlock),或让锁对象在作用范围之外。
此外,可以单独地建立一个CCriticalSection对象,并在存取临界资源之前显式地存取它。这种方式有助于保持代码的清晰,
但是更容易出错,因为要记住在存取临界资源前加锁,存取之后开锁。
构造函数:CCriticalSection ( );
成员函数:BOOL Lock( ); BOOL Lock( DWORD dwTimeout ); 本函数用于取得对临界区对象的存取权。Lock是一个成块性的操作,直到取得临界区对象的存取权才返回。
virtual
BOOL UnLock( );
如果临界区是被本线程占用的并且开锁成功,则返回非零值。否则为0。本函数释放线程占用的CCriticalSection对象。如果
CCriticalSection是单独使用的,在独占使用完临界资源以后,应尽快调用UnLock开锁。如果使用了CSingleLock对象,
CCriticalSection::Unlock将会在它的成员函数Unlock中调用。
API函数:
CRITICAL_SECTION cs; 然后初始化,调用下面函数(参数为空对象的指针):
     VOID InitializeCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID EnterCriticalSection(LPCRITICAL_SECTION lpcs); 进入和离开临界区分别调用函数:
     VOID LeaveCriticalSection(LPCRITICAL_SECTION lpcs);
     VOID DeleteCriticalSection(LPCRITICAL_SECTION lpcs); 删除临界区对象用:
10. Mutex(互斥对象)
它为一个同步对象,允许某线程共同访问同一资源。在仅仅一个线程被允许用于修改数据或其它被控制的资源时。

种使用CMutex
对象的方法就是一个CMutex类型的变量,将其作为你希望控制类的数据成员。在被控制对象的构造过程中,若互斥对象最初拥有了互斥对象的名称或期待的安
全属性,那么就调用CMutex数据成员指定的构造函数,以这种方式访问由CMutex
对象控制的资源,首先要在资源访问的成员函数中创建CSingleLock类型或CMultiLock类型的变量。然后调用封锁对象的Lock成员函数
(例如,
CSingleLock::Lock)。这样,你的线程要么就获得资源的访问权,以等待将要释放的资源,并获取访问权,要么就等待将要释放的资源,当超时
后,返回失败。在任何一种情况下,都可以在线程安全的模式下访问资源。若要释放这些资源,使用封锁对象的Unlock成员函数(例如,
CSingleLock::Unlock),或允许封锁对象越界。
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL,LPSECUR-ITY_ATTRIBUTES lpsaAttribute = NULL );
BInitiallyOwn:指定是否创建了CMutex 对象的线程最初拥有由互斥对象控制资源的访问权。
lpszName:
CMutex
对象的名称。若其它的互斥对象由同样的名称,那么必须提供lpszName,以便对象能够通过进程边界。若该值为NULL,那么互斥对象将没有命名。若该
名称匹配已经存在的互斥对象,那么构造函数将创建一个参考互斥对象名称的新CMutex
对象。若该名称匹配已存在的不是互斥对象的同步对象,那么构造过程将失败。
LpsaAttribute:对象的安全性
API函数:

HANDLE CreateMutex(
                LPSECURITY_ATTRIBUTES lpsa,     // 安全属性指针
                BOOL bInitialOwner,             // TR UE表示线程将拥有该信号量
                LPCTSTR lpName);                // 对象名

别的进程中可以用相同的互斥量名调用CreateMutex()和
OpenMutex()来打开该对象,这样互斥量就可用于多个进程中,线程通过WaitForSingleObject()和
WaitForMultipleObject()来等待一个互斥量,用ReleaseMutex()来释放一个互斥量。
11.Semaphores(信号量)
    一个CSemaphore类对象代表一个"信号"一个同步对象,它允许有限数目的线程在一个或多个进程中访问同一个资源。
一个Semaphore对象保持了对当前访问某一指定资源的线程的计数。
对于一个只能支持有限数目用户的共享资源来说,CSemaphore是很有用的。
CSemaphore
对象的当前计数是还可以允许的其它用户的数目。当这个计数达到零的时候,所有对这个由CSemaphore对象控制的资源的访问尝试都将被插入到一个系统
队列中等待,直到它们的时间用完或计数值不再为零。这个被控制的资源可以同时接受访问的最大用户数目是在CSemaphore对象的构造期间被指定的。
使
用CSemaphore对象的另一种方法是,将一个CSemaphore类型的变量添加到你想要控制的类中作为一个数据成员。在被控制对象的构造期间,调
用CSemaphore数据成员的构造函数来指定访问计数的初始值,访问计数的最大值,信号的名字(如果它要在整个进程中使用),以及需要的安全标志。要
访问由CSemaphore对象用这种方式控制的资源,首先要在你的资源的访问成员函数中创建一个CSingleLock类型或CMultiLock类型
的变量。然后调用加锁对象的Lock成员函数(例如,CSingleLock::Lock)。这时,你的线程将达到对资源的访问,等待资源被释放并访问
它,或者是在等待资源被释放的过程中超过了时间,对资源的访问失败。不管是哪一种情况,你的资源都是以一种线程安全(thread-safe)方式被访问
的。要释放资源,可以使用加锁对象的Unlock成员函数(例如,CSingleLock::Unlock),或者是让加锁对象超越范围。
CSemaphore(
LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName=
NULL,       LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
LInitialCount:信号的初始使用计数。必须是大于或等于0,并且小于或等于lMaxCount。
LMaxCount:信号的使用计数的最大值。必须大于0。
PstrName:
信号的名字。如果此信号将在整个进程中被访问,则必须提供这个名字。如果是NULL,则对象将是没有名字的。如果这个名字与一个已经存在的信号的名字一
样,则构造函数创建一个新的CSemaphore对象,此对象引用具有这个名字的对象。如果这个名字与一个已经存在的但不是一个信号的同步对象的名字一
样,则构造函数会失败。
API使用:
HANDLE CreateSemaphore(
                LPSECURITY_ATTRIBUTES lpSecAttr,        // 安全属性指针
                LONG InitialCoutn,                       // 初始信号量数目
                LONG lMaxCount,                        // 允许的最大信号量数目
                LPCTSTR lpszSemName);                 // 信号量对象名指针

信号量数目大于0时,就处于信号状态。若lpszSemName参数为NULL时,信号量局限于一个进程的线程中;若给予一个字符串对象名,则其它进程也
可以使
用该信号量。别的进程中可以在调用CreateSemaphore()和OpenSemaphore()时把这个字符串名作参数,以打开该信号量,这样可
以实现多个进程间的同步。WaitForSingleObject()和WaitForMultipleObjects()来等待一个信号量。当完成同步
任务时,线程应用ReleaseSemaphore()来释放信号量:
BOOL ReleaseSemaphore(HANDLE hSema, LONG lReleaseCount, LPLONG  lpPrevious);
12.用户界面线程的创建:
创建用户界面线程时,必须首先从 CWinThread 派生类。必须使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREAT宏声明并实现此类。
用户界面线程通常用于处理用户输入和响应用户事件。
13.进程的创建和终止;
调用CreateProcess函数创建新的进程,运行指定的程序。CreateProcess的原型如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
其中:
lpApplicationName指向包含了要运行模块名字的字符串。
lpCommandLine指向命令行字符串。
lpProcessAttributes描述进程的安全性属性,NT下有用。
lpThreadAttributes描述进程初始线程(主线程)的安全性属性,NT下有用。
bInHeritHandles
表示子进程(被创建的进程)是否可以继承父进程的句柄。可以继承的句柄有线程句柄、有名或无名管道、互斥对象、事件、信号量、映像文件、普通文件和通讯端
口等;还有一些句柄不能被继承,如内存句柄、DLL实例句柄、GDI句柄、URER句柄等等。
子进程继承的句柄由父进程通过命令行方式或者进程间通讯(IPC)方式由父进程传递给它。
dwCreationFlags表示创建进程的优先级类别和进程的类型。创建进程的类型分控制台进程、调试进程等;
优先级类别用来控制进程的优先级别,分Idle、Normal、High、Real_time四个类别。
lpEnviroment指向环境变量块,环境变量可以被子进程继承。
lpCurrentDirectory指向表示当前目录的字符串,当前目录可以继承。
lpStartupInfo指向StartupInfo结构,控制进程的主窗口的出现方式。
lpProcessInformation指向PROCESS_INFORMATION结构,用来存储返回的进程信息。
从其参数可以看出创建一个新的进程需要指定什么信息。
从上面的解释可以看出,一个进程包含了很多信息。若进程创建成功的话,返回一个进程信息结构类型的指针。进程信息结构如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
进程信息结构包括进程句柄,主线程句柄,进程ID,主线程ID。
进程的终止
进程在以下情况下终止:
调用ExitProcess结束进程;
进程的主线程返回,隐含地调用ExitProcess导致进程结束;
进程的最后一个线程终止;
调用TerminateProcess终止进程。
当要结束一个进程时,发送WM_QUIT消息给主窗口,当然也可以从它的任一线程调用ExitProcess。

9.边界 测试
在测试用例部分已经说过,设计测试用例时要考虑边界输入和非法输入,这里统称为特殊输入,程序员在编写代码时也要考虑特殊输入,要为特殊输入编写处理代
码。在实际工作中,程序员没有考虑到某些特殊输入是很常见的,这也是程序错误的一个重要来源。不幸的是,如果编写代码和建立测试用例时都没有考虑这些输
入,那么白盒覆盖也不能自动发现,因为白盒覆盖是以代码为基础的,如果相应的代码根本不存在,白盒覆盖当然不会告诉用户"某某代码未覆盖"。

  特殊输入通常与数据类型有关,例如,如果一个参数是指针,空指针就是一个特殊输入,对于一个整数类型,最大值、最小值、0、1、-1都可以算是特殊输
入;另一方面,当输入特殊数据时,如果程序未作合适的处理,运行结果常常是产生异常。根据这两个条件,如果预先为各个数据类型定义特殊值,然后由测试工具
自动生成测试用例,使用这些特殊值或其组合作为输入数据进行测试,通常可以发现"未处理特殊输入"而产生的程序错误,这就是边界测试。