近日,有搞数控与CAD的朋友遇到此问题,与本人谈及,遂有兴趣完成此程序。
程序的目标,是从矩形一角出发,沿边界前进,到一个距离后,斜向拐弯,然后遇到边界,沿边界前进一个距离,再拐弯,依次前进,直到最后一个矩形边界。
效果图如下:
该程序看上去很简单,但具体实现起来,需要考虑的细节不少,本人的方法,是直线矩形裁剪算法+绘图起点两侧的vector存储交点,同时交点结构体中有一个标志与下一点是否连线的变量。
上代码了 。。。。。
首先,依然是那个坑爹的stdafx.h
#pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN //从Windows头中排除极少使用的资料 //C++标准库头文件 #include<vector> using namespace std; //Windows 头文件: #include <windows.h> //C运行时头文件 #include<stdlib.h> #include<malloc.h> #include<memory.h> #include<tchar.h> #include<math.h> //TODO:在此处引用程序需要的其他头文件 #include"myconst.h" #include"mygraph.h"
紧跟着就是stdafx.cpp
// stdafx.cpp : 只包括标准包含文件的源文件 // sdkRectfill.pch 将作为预编译头 // stdafx.obj 将包含预编译类型信息 #include "stdafx.h" // TODO: 在 STDAFX.H 中 // 引用任何所需的附加头文件,而不是在此文件中引用
然后是一直使用但似乎没见到太大作用的targetver.h
#pragma once // 以下宏定义要求的最低平台。要求的最低平台 // 是具有运行应用程序所需功能的 Windows、Internet Explorer 等产品的 // 最早版本。通过在指定版本及更低版本的平台上启用所有可用的功能,宏可以 // 正常工作。 // 如果必须要针对低于以下指定版本的平台,请修改下列定义。 // 有关不同平台对应值的最新信息,请参考 MSDN。 #ifndef WINVER // 指定要求的最低平台是 Windows Vista。 #define WINVER 0x0600 // 将此值更改为相应的值,以适用于 Windows 的其他版本。 #endif #ifndef _WIN32_WINNT // 指定要求的最低平台是 Windows Vista。 #define _WIN32_WINNT 0x0600 // 将此值更改为相应的值,以适用于 Windows 的其他版本。 #endif #ifndef _WIN32_WINDOWS // 指定要求的最低平台是 Windows 98。 #define _WIN32_WINDOWS 0x0410 // 将此值更改为适当的值,以适用于 Windows Me 或更高版本。 #endif #ifndef _WIN32_IE // 指定要求的最低平台是 Internet Explorer 7.0。 #define _WIN32_IE 0x0700 // 将此值更改为相应的值,以适用于 IE 的其他版本。 #endif
一行语句,包含了资源文件声明的sdkRectfill.h
#pragma once #include "resource.h"
资源声明的h文件(这个忽略了,因为在VC中新建Win32Application后,自然就有这个了,不必理会)
**来正题了**
定义了一堆常量的myconst.h,这里大部分是在GUI时由用户输入的。
#ifndef _myconst_h_ #define _myconst_h_ //函数执行是否成功的标识 const int INTOK=1; const int INTERR=0; const float PI=3.14159265f; //设置初始边界变量 const int LEFT=0x0001; const int RIGHT=0x0010; const int BOTTOM=0x0100; const int TOP=0x1000; //本部分数值实际工程中可由用户输入 /******Begin******/ //测试用初始线段的两端点 const int LINE_START_X=20; const int LINE_START_Y=10; const int LINE_END_X=800; const int LINE_END_Y=400; //初始矩形的两顶点 const int REC_X_LEFT=40; const int REC_Y_BOTTOM=50; const int REC_X_RIGHT=600; const int REC_Y_TOP=500; //起始直线倾角 //倾角与绘制起始点也有范围关系,本次暂不做校验 const int ARG_ANGLE=60; const int ARG_NULL=90; //在矩形上可选的绘制起点(暂定为自左上角启动) const int REC_LEFTBOTTOM=1; const int REC_LEFTTOP=2; const int REC_RIGHTBOTTOM=3; const int REC_RIGHTTOP=4; //直线间距 const int LINE_DIST=30; //边界填充的初始坐标选择 const int EDGE_X=1; const int EDGE_Y=2; /******End******/ #endif
绘图处理函数的声明mygraph.h
#ifndef _mygraph_h_ #define _mygraph_h_ struct newPoint { int x; int y; int intNextForward; }myPointStruct; typedef struct newPoint myPoint; //初始化函数 extern int intInitSet(int *,int); //依据倾角计算斜率的函数声明 extern float flaKCalc(int); //线段的端点赋值函数声明 extern int intLineset(int *,int *,int *,int *,int,int,int,int); //判别XY轴增长方向的函数 //暂未考虑倾角的合理性校验 extern int intXYFoward(int *,int *,int); //根据起始点标志变量计算矩形起始点实际坐标函数声明 extern int intXYStart(int *,int *,int); //PI/2-倾角的正负校验 extern int intYAngleStepCalc(int); //对角点坐标计算函数声明 extern int intXYDig(int *,int *,int); //对角点到起始直线的距离计算函数声明 extern int intPt2LineStartCalc(int,int,int,int,float); //边界绘制时初始XY两轴交点是否连接的处理函数声明 extern int intXYEdge(int *,int *,int); //Cohen-Sutherland编码函数声明 extern int cohenCode(int,int); //Cohen-Sutherland处理函数声明 extern int cohenProcess(int *,int *,int *,int *,int,int,int,int); #endif
绘图处理函数的实现mygraph.cpp
#include<stdafx.h> //初始化函数 int intInitSet(int *iDst,int iSrc) { *iDst=iSrc; return INTOK; } //依据倾角计算斜率 float flaKCalc(int intArg) { if((intArg+ARG_NULL)%ARG_NULL) { return tan(intArg*PI/180); } else return (float)INTERR; } //线段两端点值的赋值函数 int intLineset(int *pintXNewStart,int *pintYNewStart,int *pintXNewEnd,int *pintYNewEnd,int intXStart,int intYStart,int intXEnd,int intYEnd) { *pintXNewStart=intXStart; *pintYNewStart=intYStart; *pintXNewEnd=intXEnd; *pintYNewEnd=intYEnd; return INTOK; } //判别批量绘制直线时XY轴增长方向的函数 int intXYFoward(int *iXStep,int *iYStep,int intRecStartSet) { switch(intRecStartSet) { case REC_LEFTBOTTOM: *iXStep=1; *iYStep=1; return INTOK; case REC_LEFTTOP: *iXStep=1; *iYStep=-1; return INTOK; case REC_RIGHTBOTTOM: *iXStep=-1; *iYStep=1; return INTOK; case REC_RIGHTTOP: *iXStep=-1; *iYStep=-1; return INTOK; default: *iXStep=0; *iYStep=0; return INTERR; } } //根据起始点标志变量计算矩形起始点实际坐标函数 int intXYStart(int *iXRecStart,int *iYRecStart,int intPtRecStart) { switch(intPtRecStart) { case REC_LEFTBOTTOM: *iXRecStart=REC_X_LEFT; *iYRecStart=REC_Y_BOTTOM; return INTOK; case REC_LEFTTOP: *iXRecStart=REC_X_LEFT; *iYRecStart=REC_Y_TOP; return INTOK; case REC_RIGHTBOTTOM: *iXRecStart=REC_X_RIGHT; *iYRecStart=REC_Y_BOTTOM; return INTOK; case REC_RIGHTTOP: *iXRecStart=REC_X_RIGHT; *iYRecStart=REC_Y_TOP; return INTOK; default: *iXRecStart=0; *iYRecStart=0; return INTERR; } } //PI/2-倾角的正负校验 int intYAngleStepCalc(int intRecStartSet) { switch(intRecStartSet) { case REC_LEFTBOTTOM: return -1; case REC_LEFTTOP: return 1; case REC_RIGHTBOTTOM: return 1; case REC_RIGHTTOP: return -1; default: return INTERR; } } //对角点坐标计算函数 int intXYDig(int *iXDig,int *iYDig,int intPtRecStart) { switch(intPtRecStart) { case REC_LEFTBOTTOM: *iXDig=REC_X_RIGHT; *iYDig=REC_Y_TOP; return INTOK; case REC_LEFTTOP: *iXDig=REC_X_RIGHT; *iYDig=REC_Y_BOTTOM; return INTOK; case REC_RIGHTBOTTOM: *iXDig=REC_X_LEFT; *iYDig=REC_Y_TOP; return INTOK; case REC_RIGHTTOP: *iXDig=REC_X_LEFT; *iYDig=REC_Y_BOTTOM; return INTOK; default: *iXDig=0; *iYDig=0; return INTERR; } } //对角点到起始直线的距离计算 //intXPt:对角点X坐标 //intYPt:对角点Y坐标 //intXInLine:点斜式中所用在直线上的点X坐标 //intYInLine:点斜式中所用在直线上的点Y坐标 //k:直线斜率 int intPt2LineStartCalc(int intXPt,int intYPt,int intXInLine,int intYInLine,float k) { if(k) { return (int)(fabs(k*intXPt+(-1)*intYPt+intYInLine-k*intXInLine)/sqrt(k*k+1)); } else return INTERR; } //边界绘制时初始XY两轴交点是否连接的处理函数声明 //iXEdge对应mpAdd一侧的初始状态 //iYEdge对应mpSub一侧的初始状态 int intXYEdge(int *iXEdge,int *iYEdge,int iEdgeForward) { switch(iEdgeForward) { case EDGE_X: *iXEdge=-1; *iYEdge=1; return INTOK; case EDGE_Y: *iXEdge=1; *iYEdge=-1; return INTOK; default: return INTERR; } } //Cohen编码函数定义 int cohenCode(int x,int y) { int c=0; if(x<REC_X_LEFT) c|=LEFT; if(x>REC_X_RIGHT) c|=RIGHT; if(y<REC_Y_BOTTOM) c|=BOTTOM; if(y>REC_Y_TOP) c|=TOP; return c; } //Cohen裁剪算法处理函数 int cohenProcess(int *pintXNewStart,int *pintYNewStart,int *pintXNewEnd,int *pintYNewEnd,int xs,int ys,int xe,int ye) { int intCode1,intCode2,intCode; int x,y; intCode1=cohenCode(xs,ys); intCode2=cohenCode(xe,ye); while(intCode1!=0||intCode2!=0) { if((intCode1 & intCode2)!=0) return INTERR; intCode=intCode1; if(intCode1==0) { intCode=intCode2; } if((LEFT & intCode)!=0) { x=REC_X_LEFT; y=ys+(ye-ys)*(REC_X_LEFT-xs)/(xe-xs); } else if((RIGHT & intCode)!=0) { x=REC_X_RIGHT; y=ys+(ye-ys)*(REC_X_RIGHT-xs)/(xe-xs); } else if((BOTTOM & intCode)!=0) { y=REC_Y_BOTTOM; x=xs+(xe-xs)*(REC_Y_BOTTOM-ys)/(ye-ys); } else if((TOP & intCode)!=0) { y=REC_Y_TOP; x=xs+(xe-xs)*(REC_Y_TOP-ys)/(ye-ys); } if(intCode==intCode1) { xs=x; ys=y; intCode1=cohenCode(x,y); } else { xe=x; ye=y; intCode2=cohenCode(x,y); } } intLineset(pintXNewStart,pintYNewStart,pintXNewEnd,pintYNewEnd,xs,ys,xe,ye); //用处理完毕的xs,ys,xe,ye对线段两端点进行赋值 return INTOK; }
最终,主文件(sdkRectfill.cpp)出场了,因为有不少是WindowsSDK自动生成的,比较长,默认折叠吧,需要看的朋友请展开。
主要功能,在CREATE和PAINT消息当中完成。
View Code
//sdkRectfill.cpp:定义应用程序的入口点。 // #include "stdafx.h" #include "sdkRectfill.h" /******以下各全局变量最后将检查并进行局部化******/ //Cohen算法处理结果是否成功的标识 int intCohenPro=0; //线段两个端点变量 int intXLineStart,intYLineStart,intXLineEnd,intYLineEnd; //交点坐标 int intXAddCross,intYAddCross,intXSubCross,intYSubCross; //矩形两个定义点变量 int intXRecLeft,intYRecBottom,intXRecRight,intYRecTop; //绘制直线的倾角与斜率(同时完成初始化) int intArgAngle=0; float flaK=0.0f; //批量绘制工作在矩形上的启动点与方向标识变量 int intPtRecStart=0; //矩形起始点坐标 int intXRecStart=0; int intYRecStart=0; //矩形对角线长度变量 int intRecDiag=0; //矩形绘制起始点对角点坐标 int intXDig=0; int intYDig=0; //绘制过程中直线的间距 int intLineDist=0; //对角点到起始直线的距离 int intPt2LineStart=0; //需要绘制的直线个数变量 int intLineCount=0; //绘制时xy两轴的坐标变化方向变量(将取值为1或-1) int intXStep=0; int intYStep=0; //PI/2-倾角的正负标志变量 int intYAngleStep=0; //存储批量绘制直线的交点坐标 vector<myPoint>vecAddCross; vector<myPoint>vecSubCross; //最终从启动点开始沿XY某坐标轴方向的选择标志变量 int intEdgeForward=0; int intXEdge=0; int intYEdge=0; //**如下部分为Windows程序自有声明及定义** #define MAX_LOADSTRING 100 HINSTANCE hInst; // 当前实例 TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本 TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名 //若干基本的函数声明 ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE,int); LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); INT_PTR CALLBACK About(HWND,UINT,WPARAM,LPARAM); int APIENTRY _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); MSG msg; HACCEL hAccelTable; //初始化全局字符串 LoadString(hInstance,IDS_APP_TITLE,szTitle,MAX_LOADSTRING); LoadString(hInstance,IDC_SDKRECTFILL,szWindowClass,MAX_LOADSTRING); MyRegisterClass(hInstance); //执行应用程序初始化: if (!InitInstance (hInstance,nCmdShow)) { return FALSE; } hAccelTable=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_SDKRECTFILL)); //主消息循环: while(GetMessage(&msg,NULL,0,0)) { if (!TranslateAccelerator(msg.hwnd,hAccelTable,&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } //函数MyRegisterClass():注册窗口类。 ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize=sizeof(WNDCLASSEX); wcex.style=CS_HREDRAW|CS_VREDRAW; wcex.lpfnWndProc=WndProc; wcex.cbClsExtra=0; wcex.cbWndExtra=0; wcex.hInstance=hInstance; wcex.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(IDI_SDKRECTFILL)); wcex.hCursor=LoadCursor(NULL,IDC_ARROW); wcex.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName=MAKEINTRESOURCE(IDC_SDKRECTFILL); wcex.lpszClassName=szWindowClass; wcex.hIconSm=LoadIcon(wcex.hInstance,MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); } //函数InitInstance(HINSTANCE, int)保存实例句柄并创建主窗口 BOOL InitInstance(HINSTANCE hInstance,int nCmdShow) { HWND hWnd; hInst=hInstance; // 将实例句柄存储在全局变量中 hWnd=CreateWindow(szWindowClass,szTitle,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL); if(!hWnd) { return FALSE; } ShowWindow(hWnd,nCmdShow); UpdateWindow(hWnd); return TRUE; } //函数WndProc: 处理主窗口的消息 LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { //int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; switch (message) { case WM_CREATE: /******初始化工作Begin******/ //矩形坐标初始化 intInitSet(&intXRecLeft,REC_X_LEFT); intInitSet(&intYRecBottom,REC_Y_BOTTOM); intInitSet(&intXRecRight,REC_X_RIGHT); intInitSet(&intYRecTop,REC_Y_TOP); //完成绘制直线倾角与启动点及画线间距 intInitSet(&intArgAngle,ARG_ANGLE); intInitSet(&intLineDist,LINE_DIST); //初始化批量绘制直线的起始点 intInitSet(&intPtRecStart,REC_LEFTTOP); //初始化矩形边界的绘制方向及连接变量 intInitSet(&intEdgeForward,EDGE_X); //intInitSet(&intEdgeForward,EDGE_Y); /******End初始化工作******/ //斜率计算 flaK=flaKCalc(intArgAngle); //矩形对角线长度 intRecDiag=(int)sqrt((float)(intXRecRight-intXRecLeft)*(intXRecRight-intXRecLeft)+(float)(intYRecTop-intYRecBottom)*(intYRecTop-intYRecBottom)); //完成xy两个坐标轴增长方向的判断 intXYFoward(&intXStep,&intYStep,intPtRecStart); //根据绘图的起始点计算出起始点与对角点坐标 intXYStart(&intXRecStart,&intYRecStart,intPtRecStart); intXYDig(&intXDig,&intYDig,intPtRecStart); //矩形边界绘制初始变量的判断 intXYEdge(&intXEdge,&intYEdge,intEdgeForward); //计算对角点到起始直线的距离 intPt2LineStart=intPt2LineStartCalc(intXDig,intYDig,intXRecStart,intYRecStart,flaK); //结合间距计算直线绘制数量 intLineCount=intPt2LineStart/intLineDist; //准备一批待绘制直线的两个端点 //****步骤***** //a-算出从矩形起始点向绘制方向延伸点的坐标 //b-算出从延伸点向绘制的垂直方向两侧各延伸出对角线长的点坐标 //c-计算结果存入vector vecAddCross.clear(); vecSubCross.clear(); //垂线中点坐标的初始化 int intXCenter,intYCenter; intXCenter=intXRecStart; intYCenter=intYRecStart; //垂线两侧点的初始化 myPoint mpAdd; myPoint mpSub; mpAdd.x=intXCenter; mpAdd.y=intYCenter; mpSub.x=intXCenter; mpSub.y=intYCenter; //交点坐标结构体变量的声明 myPoint mpAddCross; myPoint mpSubCross; for(int i=0;i<intLineCount;i++) { //先计算在绘制直线的垂线上的中点 //不同的启动点决定了PI/2-intArgAngle角度正负差异,故使用intYStepAngle校正sin(cos因偶函数性质不需校正) intYAngleStep=intYAngleStepCalc(intPtRecStart); //垂线上各点计算 intXCenter=(int)(intXRecStart+intXStep*(i+1)*intLineDist*cos((float)(PI/2-intArgAngle*PI/180))); intYCenter=(int)(intYRecStart+intYAngleStep*intYStep*(i+1)*intLineDist*sin((float)(PI/2-intArgAngle*PI/180))); //计算两侧坐标 //Add方向更靠近X轴一侧 //Y增长方向一定与垂线的Y增长方向相反,故*(-1) mpAdd.x=(int)(intXCenter+intXStep*intRecDiag*cos(intArgAngle*PI/180)); mpAdd.y=(int)(intYCenter+(-1)*intYStep*intRecDiag*sin(intArgAngle*PI/180)); mpSub.x=(int)(intXCenter-intXStep*intRecDiag*cos(intArgAngle*PI/180)); mpSub.y=(int)(intYCenter-(-1)*intYStep*intRecDiag*sin(intArgAngle*PI/180)); //根据两侧坐标计算裁剪后与矩形的交点 intXAddCross=mpAdd.x; intYAddCross=mpAdd.y; intXSubCross=mpSub.x; intYSubCross=mpSub.y; intCohenPro=cohenProcess(&intXAddCross,&intYAddCross,&intXSubCross,&intYSubCross,intXAddCross,intYAddCross,intXSubCross,intYSubCross); //Add与Sub获得两个交点坐标 mpAddCross.x=intXAddCross; mpAddCross.y=intYAddCross; mpSubCross.x=intXSubCross; mpSubCross.y=intYSubCross; //分别设定mpAdd与mpSub每一点与下一点是否连接的标志变量 //直线绘制的起始点为intPtRecStart,故Vector中的第一点实为绘制时的第二点 //因最后循环时mpSub与mpAdd反方向,故mpAdd与mpSub为配对相等关系 mpAddCross.intNextForward=intXEdge; mpSubCross.intNextForward=intYEdge; //将两侧裁剪所得交点存入相应的Vector vecAddCross.push_back(mpAddCross); vecSubCross.push_back(mpSubCross); //下次是否连接必然与此次相反 intXEdge=-intXEdge; intYEdge=-intYEdge; } break; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); //绘制矩形 Rectangle(hdc,intXRecLeft,intYRecBottom,intXRecRight,intYRecTop); //设定初始及裁剪后线段绘图画笔 HGDIOBJ hgBlackPen,hgRedPen; hgBlackPen=(HGDIOBJ)GetStockObject(BLACK_PEN); hgRedPen=CreatePen(PS_SOLID,2,RGB(255,0,0)); //主工作流程 //(1)-批量绘制直线 //绘制裁剪结果之前的画笔准备 SelectObject(hdc,hgRedPen); //沿矩形外周循环一圈完成边界交错连接绘制 //首先需要完成的是启动点和指定坐标轴上第一点的连接 MoveToEx(hdc,intXRecStart,intYRecStart,NULL); if(EDGE_X==intEdgeForward) { LineTo(hdc,vecAddCross[0].x,vecAddCross[0].y); } else if(EDGE_Y==intEdgeForward) { LineTo(hdc,vecSubCross[0].x,vecSubCross[0].y); } else { //错误提醒处理 } //(2)-分别沿XY两坐标轴方向完成Add与Sub两个方向的交错绘制 //矩形边角上的直角拐弯处理 //Add一侧一定是从X相等变到Y相等 //Sub一侧一定是从Y相等变到X相等 for(int i=0;i<intLineCount-1;i++) { if(1==vecAddCross[i].intNextForward) { if(vecAddCross[i].y==vecAddCross[i+1].y) { MoveToEx(hdc,vecAddCross[i].x,vecAddCross[i].y,NULL); LineTo(hdc,vecAddCross[i+1].x,vecAddCross[i+1].y); } else { MoveToEx(hdc,vecAddCross[i].x,vecAddCross[i].y,NULL); LineTo(hdc,vecAddCross[i+1].x,vecAddCross[i].y); MoveToEx(hdc,vecAddCross[i+1].x,vecAddCross[i].y,NULL); LineTo(hdc,vecAddCross[i+1].x,vecAddCross[i+1].y); } } if(1==vecSubCross[i].intNextForward) { if(vecSubCross[i].x==vecSubCross[i+1].x) { MoveToEx(hdc,vecSubCross[i].x,vecSubCross[i].y,NULL); LineTo(hdc,vecSubCross[i+1].x,vecSubCross[i+1].y); } else { MoveToEx(hdc,vecSubCross[i].x,vecSubCross[i].y,NULL); LineTo(hdc,vecSubCross[i].x,vecSubCross[i+1].y); MoveToEx(hdc,vecSubCross[i].x,vecSubCross[i+1].y,NULL); LineTo(hdc,vecSubCross[i+1].x,vecSubCross[i+1].y); } } } //(3)-批量矩形内部对直线的裁剪结果绘制 for(int i=0;i<intLineCount;i++) { //绘制裁剪后的结果 MoveToEx(hdc,vecAddCross[i].x,vecAddCross[i].y,NULL); LineTo(hdc,vecSubCross[i].x,vecSubCross[i].y); } //清除各种GDI资源 DeleteObject(hgBlackPen); DeleteObject(hgRedPen); //绘图代码结束 EndPaint(hWnd, &ps); break; //return 0; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd,message,wParam,lParam); } return 0; }
附:上述程序,在Windows7x64,VS2008(关闭增量编译)环境下编译调试通过。