2010年3月29日
(1)、常用的繪畫成員函數:
SetPixel():用指定的顏色在指定的坐標畫一個點,返回值為RGB顏色值;
函數原型
COLORREF SetPixel( int x, int y, COLORREF crColor );
COLORREF SetPixel( POINT point, COLORREF crColor );
MoveTo():移動當前位置到指定的坐標,返回值為以前位置的坐標;
函數原型
CPoint MoveTo( int x, int y );
CPoint MoveTo( POINT point );
LineTo():從當前位置到指定位置畫一條直線,成功返回非0;
函數原型
BOOL LineTo( int x, int y );
BOOL LineTo( POINT point );
Polyline():從當前位置,繪畫多條曲線,成功返回非0;
函數原型
BOOL Polyline( LPPOINT lpPoints, int nCount/*數目*/ );
Rectangle():根據指定參數繪制一個矩形,成功返回非0;
函數原型
BOOL Rectangle( int x1, int y1, int x2, int y2 );
BOOL Rectangle( LPCRECT lpRect );
Ellipse(): 根據指定的矩形繪制一個內切橢圓,成功返回非0;
函數原型
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect ); DrawIcon():在指定位置畫一個圖標,成功返回非0;
函數原型
BOOL DrawIcon( int x, int y, HICON hIcon );
BOOL DrawIcon( POINT point, HICON hIcon );
(2)、有關文本處理的常用函數:
TextOut():在函數參數指定的位置顯示文本串。
函數原型
virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );
BOOL TextOut( int x,
int y,
const CString& str );
DrawText():在函數參數指定的矩形區域內顯示文本串。
函數原型
virtual int DrawText( LPCTSTR lpszString, int nCount, LPRECT lpRect, UINT nFormat /*類型*/
);
int DrawText( const CString& str, LPRECT lpRect, UINT nFormat );
SetTextColor():設置顯示文本的顏色,返回當前文本RGB顏色值;
函數原型
virtual COLORREF SetTextColor( COLORREF crColor );
GetTextColor():獲得當前文本顏色; 函數原型
COLORREF GetTextColor( ) const;
SetBkColor():設置顯示文本的背景顏色,返回當前文本背景RGB顏色值;
函數原型
virtual COLORREF SetBkColor( COLORREF crColor );
GetBkColor():獲得當前文本背景顏色; 函數原型
COLORREF GetBkColor( ) const;
SetBkMode():設置文本的背景模式,返回當前背景模式值;
函數原型
int SetBkMode( int nBkMode /*模式*/
); TRANSPARENT透明,
OPAQUE 不透明
GetBkMode():獲得當前文本背景模式; 函數原型
int GetBkMode( ) const;
SetTextAlign():設置顯示文本的對齊方式,成功返回非0;
函數原型
UINT SetTextAlign( UINT nFlags );
GetTextAlign():獲得文本的對齊方式,函數原型
UINT GetTextAlign( ) const;
2010年1月21日
概述:創建一個屬性表單,首先創建一個CPropertySheet對象;為每一個屬性表單創建一個CPropertyPage對象,在CPropertySheet類中;在CPropertySheet類的構造函數中添加AddPage函數添加每個屬性頁;最后在菜單函數中調用DoModal函數來顯示一個靜態屬性表單。屬性頁
是被添加屬性表單的,也就是說,屬性表單是屬性頁的父窗口。因此,可以通過GetParent()函數獲得屬性頁父窗口的指針,即屬性表單的
指針,但要經過類型轉換
步驟:
1、創建一個或多個屬性頁,基類為CPropertyPage。
class CPropSet1 : public CPropertyPage
{
// Dialog Data
//{{AFX_DATA(CPropSet1)
enum { IDD = IDD_PROP_SET1 };
int m_MAXVALUEX2;
int m_MINVALUEX2;
//}}AFX_DATA
}
2、建立CProp表單:基類為CPropertySheet。
CPropSheet::CPropSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
AddPage(&m_propSet1); //決定page順序
AddPage(&m_propSet2);
}
3、菜單函數:
void CDataView::OnPropsheet()
{
// TODO: Add your command handler code here
CPropSheet propSheet("參數設置"); //表單名稱,其他為缺省變量
propSheet.m_propSet1.m_MAXVALUEX2=m_XValueMax;
propSheet.m_propSet1.m_MINVALUEX2=m_XValueMin;
if( IDOK==propSheet.DoModal())
{
m_ChartCtrl1.EnableRefresh(false);
m_XValueMax=propSheet.m_propSet1.m_MAXVALUEX2;
m_XValueMin=propSheet.m_propSet1.m_MINVALUEX2;
m_ChartCtrl1.GetBottomAxis()->SetMinMax(m_XValueMin,m_XValueMax);
m_ChartCtrl1.EnableRefresh(true);
}
}
4、建立向導:
首先在調用屬性表單對象的DoModal函數之前,調用SetWizardMode函數。
propSheet.SetWizardMode();
然后通過SetWizardButtons函數設置向導對話框上的按鈕。
((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT);
((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_NEXT | PSWIZB_BACK);
((CPropSheet*)GetParent())->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);
注意點:需改文字種類和類型。
2009年12月31日
打印預覽實現流程:
首先調用CFormView::OnFilePrintPreview,再依次調用自己的所重寫的虛函數OnPreparePrinting(CPrintInfo* pInfo)、OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)、OnPrint(CDC* pDC, CPrintInfo* pInfo)、CFormView::OnPrint(pDC, pInfo);,當關閉打印預覽時調用OnEndPrinting(CDC* pDC, CPrintInfo* pInfo) ,結束打印。
部分代碼:
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)
BOOL CElectronValveView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
pInfo->SetMinPage(1); //設置打印文件起始頁
pInfo->SetMaxPage(1); //設置打印文件終止頁
return DoPreparePrinting(pInfo);
}
void CElectronValveView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
m_ChartCtrl.GetBottomAxis()->GetGrid()->SetColor(BlackColor);//設置柵格顏色為黑色,不然打印的時候太淡
m_ChartCtrl.GetLeftAxis()->GetGrid()->SetColor(BlackColor);
}
void CElectronValveView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
m_ChartCtrl.GetBottomAxis()->GetGrid()->SetColor(BlueColor);//恢復顏色,不然控件中也是這個顏色了。
m_ChartCtrl.GetLeftAxis()->GetGrid()->SetColor(BlueColor);
}
void CElectronValveView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: add customized printing code here
CMainFrame* pMain=(CMainFrame *)AfxGetApp()->m_pMainWnd
CDC *pCurrentDC = GetDC(); // will have dimensions of the client area
CSize PaperPixelsPerInch(pDC->GetDeviceCaps(LOGPIXELSX), pDC->GetDeviceCaps(LOGPIXELSY));
//獲得打印紙設備每英寸的像素數,應該是一樣的。
CSize ScreenPixelsPerInch(pCurrentDC->GetDeviceCaps(LOGPIXELSX), pCurrentDC->GetDeviceCaps(LOGPIXELSY));
//應該是獲得屏幕的每英寸的像素數,應該是一樣的
CSize m_PaperSize,m_LogicalPageSize;
m_PaperSize = CSize(pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES));
//把紙張所得的像素對應的屏幕大小得到。
m_LogicalPageSize.cx = ScreenPixelsPerInch.cx * m_PaperSize.cx / PaperPixelsPerInch.cx * 3 / 4;
m_LogicalPageSize.cy = ScreenPixelsPerInch.cy * m_PaperSize.cy / PaperPixelsPerInch.cy * 3 / 4;
//這里必須這樣設,是為了和ChartCtrl中的打印對應起來。
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(m_LogicalPageSize);
pDC->SetViewportExt(m_PaperSize);
pDC->SetWindowOrg(0, 0);
CRect rcClient,rcClient1;
GetClientRect(&rcClient1); //得到的仍然是打印預覽顯示客戶區的大小
rcClient=pInfo->m_rectDraw;//打印紙的客戶區
double ratioX,ratioY;
ratioX=(double)rcClient.right/(double)rcClient1.Width();
ratioY=(double)rcClient.bottom/(double)rcClient1.Height();
int nWid,left,right;
CFont *pOldFont;
CFont fnBig,fnBig1;
CPen Pen,*OldPen;
//畫主框
Pen.CreatePen(PS_SOLID,2,RGB(0,0,0));
OldPen=pDC->SelectObject(&Pen);
pDC->Rectangle(20,20,m_LogicalPageSize.cx-20,m_LogicalPageSize.cy-20);
pDC->SelectObject(OldPen);
//畫標題欄
pDC->MoveTo(20,60);
pDC->LineTo(m_LogicalPageSize.cx-20,60);
pDC->MoveTo(20,85);
pDC->LineTo(m_LogicalPageSize.cx-20,85);
pDC->SelectObject(&Pen);
pDC->MoveTo(20,110);
pDC->LineTo(m_LogicalPageSize.cx-20,110);
pDC->SelectObject(OldPen);
pDC->MoveTo(196,60);
pDC->LineTo(196,110);
pDC->MoveTo(390,60);
pDC->LineTo(390,110);
//畫結果欄
pDC->MoveTo(20,430);
pDC->LineTo(m_LogicalPageSize.cx-20,430);
pDC->MoveTo(85,430);
pDC->LineTo(85,470);//測量結果1分界線豎線
pDC->MoveTo((m_LogicalPageSize.cx-85-100)/4+85,430);//開啟響應時間分界線
pDC->LineTo((m_LogicalPageSize.cx-85-100)/4+85,470);
pDC->MoveTo((m_LogicalPageSize.cx-85-100)/4*2+85,430);
pDC->LineTo((m_LogicalPageSize.cx-85-100)/4*2+85,470);
pDC->MoveTo((m_LogicalPageSize.cx-85-100)/4*3+85,430);
pDC->LineTo((m_LogicalPageSize.cx-85-100)/4*3+85,470);
pDC->MoveTo((m_LogicalPageSize.cx-85-100)/4*4+85,430);
pDC->LineTo((m_LogicalPageSize.cx-85-100)/4*4+85,470);
pDC->MoveTo(20,450);
pDC->LineTo(m_LogicalPageSize.cx-20,450);
pDC->SelectObject(&Pen);
pDC->MoveTo(20,470);
pDC->LineTo(m_LogicalPageSize.cx-20,470);
pDC->SelectObject(OldPen);
//寫標題
fnBig.CreatePointFont(200,"黑體",pDC);
pOldFont=pDC->SelectObject(&fnBig);
nWid=rcClient1.Width();
left=rcClient1.left;
right=rcClient1.right;
pDC->SetTextAlign(TA_CENTER);
pDC->TextOut(m_LogicalPageSize.cx/2,30,"XXX性能測試報告");
fnBig.DeleteObject();
fnBig.CreatePointFont(110,"宋體",pDC);
pOldFont=pDC->SelectObject(pOldFont);
pOldFont=pDC->SelectObject(&fnBig);
pDC->SetTextAlign(TA_LEFT);
pDC->TextOut(25,67,"產品名稱:"+pMain->m_strProductName);
pDC->TextOut(25,92,"產品圖號:"+pMain->m_strProductPictureNo);
pDC->TextOut(201,67,"檢測時間:"+pMain->m_strMeasureTime0);
pDC->TextOut(201,92,"生產廠家:"+pMain->m_strProductFactory);
pDC->TextOut(395,67,"產品編號:"+pMain->m_strProductBianHao);
pDC->TextOut(395,92,"檢 驗 員:"+pMain->m_strMeasurerName);
CString aa;
aa.Format("%d(ms)",m_iPreviousTime1);
fnBig.DeleteObject();
fnBig.CreatePointFont(85,"宋體",pDC);
pDC->SelectObject(&fnBig);
pDC->TextOut(25,420,"預置電信號時間:"+aa);
aa.Format("%d",m_iCurveNumber1);
pDC->TextOut(150,420,"曲線數量:"+aa);
fnBig1.CreatePointFont(100,"宋體",pDC);
pOldFont=pDC->SelectObject(&fnBig1);
pDC->TextOut(25,455,"測試結果");
pDC->TextOut(90,435,"開啟響應時間(ms)");
pDC->TextOut(120,455,m_strOpenResponseTime);
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4+85+5,435,"開啟換向時間(ms)");
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4+85+5+30,455,m_strOpenInvertTime);
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4*2+85+5,435,"關閉響應時間(ms)");
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4*2+85+5+30,455,m_strCloseResponseTime);
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4*3+85+5,435,"關閉換向時間(ms)");
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4*3+85+5+30,455,m_strCloseInvertTime);
pDC->TextOut((m_LogicalPageSize.cx-85-100)/4*4+85+10,435,"檢測結論");
//最后一欄
fnBig.DeleteObject();
fnBig.CreatePointFont(130,"宋體",pDC);
pDC->SelectObject(&fnBig);
pDC->TextOut(m_LogicalPageSize.cx-180,m_LogicalPageSize.cy-20-20,"審核:");
pDC->SetTextAlign(TA_LEFT);
CPoint offset(16,110);
m_ChartCtrl.Print1(offset,pDC,pInfo,0);//調用chart類來畫表格和曲線
fnBig.DeleteObject();
CFormView::OnPrint(pDC, pInfo);
}
本程序利用ChartCtrl來實現繪制表格和曲線。
2009年12月30日
步驟1:添加新類CDIBStatic,類型為MFC Class,基類為CStatic。
步驟2:在工程中加入CPictureObj.h和CPictureObj.cpp,及CIstream.h和CIstream.cpp。
步驟3:在類CDIBStatic加入頭文件#include "PictureObj.h",添加變量CPictureObj* m_pPicObj;,用于讀取和顯示。CPictureObj中封裝了IPicture接口。
步驟4:新建一Dialog,加入控件picture,類型為MFC Class,基類為CfileDlg。
部分代碼:
CDIBStatic源代碼:
OOL CDIBStatic::LoadDib(LPCTSTR lpszFileName)//讀取
{
try//利用try語句當文件第一次打開時lpszFileName會出錯,在catch中捕獲將其設置為NULL
lpszFileName = lpszFileName;
// 確保文件存在并能打開
CFile file(lpszFileName, CFile::modeRead);
file.Close();
// 創建圖像顯示的對象并讀入圖像文件
m_pPicObj = new CPictureObj;
if(!m_pPicObj->Load(lpszFileName))
{
// 讀入文件失敗,清除對象
m_pPicObj = NULL;
delete m_pPicObj;
// 清除顯示的圖像并顯示錯誤提示
PaintDib(IsValidDib());
return FALSE;
}
PaintDib(IsValidDib());
return TRUE;
}
catch (CFileException* e)
{
m_lpszFileName = NULL;
PaintDib(IsValidDib());
e->Delete();
return FALSE;
}
}
void CDIBStatic::PaintDib(BOOL bDibValid)//顯示
{
ASSERT_VALID(this);
ClearDib(); // 清除以前的圖像
CRect PaintRect;
// 獲得顯示區域
GetClientRect(&PaintRect);
PaintRect.InflateRect(-1, -1);
CClientDC dc(this);
if (bDibValid && m_bPreview)
{
CSize size = m_pPicObj->GetSize(&dc);
int nDestX, nDestY, nDestWidth, nDestHeight;
if ((DWORD)size.cx < (DWORD)PaintRect.Width() && (DWORD)size.cy < (DWORD)PaintRect.Height())
{ // 圖像尺寸小于顯示區域將圖像顯示在中間
nDestX = PaintRect.left + (PaintRect.Width() - size.cx)/2;
nDestY = PaintRect.top + (PaintRect.Height() - size.cy)/2;
nDestWidth = size.cx;
nDestHeight = size.cy;
}
else
{ // 圖像尺寸大于顯示區域,進行比例縮放
if ((PaintRect.Width()/(float)size.cx) <= (PaintRect.Height()/(float)size.cy))
{ // 寬度限制
nDestWidth = PaintRect.Width();
nDestHeight = (nDestWidth*size.cy) / size.cx;
nDestX = PaintRect.left;
nDestY = PaintRect.top + (PaintRect.Height() - nDestHeight) /2;
}
else
{ // 高度限制
nDestHeight = PaintRect.Height();
nDestWidth = (nDestHeight*size.cx) / size.cy;
nDestX = PaintRect.left + (PaintRect.Width() - nDestWidth) /2;
nDestY = PaintRect.top;
}
}
// 獲得圖像的顯示位置和大小
CRect RectDest(nDestX, nDestY, nDestX+nDestWidth, nDestY+nDestHeight);
// 顯示圖像
m_pPicObj->Draw(&dc,&RectDest,&RectDest);
// 給圖像加一外框
CBrush* pOldBrush = (CBrush*)dc.SelectStockObject(NULL_BRUSH);
dc.Rectangle(RectDest);
if(NULL != pOldBrush) { dc.SelectObject(pOldBrush); }
}
else
{
// 顯示錯誤提示信息
CString strText = "不能識別的文件格式!";
if( m_lpszFileName == NULL || strlen(m_lpszFileName) <= 0 )
{
strText = "沒有選擇文件!";
}
if( !m_bPreview )
{
strText = "";
}
dc.DrawText(strText, strText.GetLength(), &PaintRect, DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_END_ELLIPSIS);
}
return;
}
HBRUSH CDIBSatic::CtlColor(CDC* pDC, UINT nCtlColor)//用于重繪
{
// TODO: Change any attributes of the DC here
PaintDib(IsValidDib());
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
BOOL IsValidDib() const { return (m_pPicObj && m_pPicObj->m_pPict); }
void CDIBSatic::ClearDib()//清除圖片
{
ASSERT_VALID(this);
CClientDC dc(this);
CRect rectPaint;
GetClientRect(&rectPaint);
rectPaint.InflateRect(-1,-1);
CBrush* pBrushWhite; //白畫刷
pBrushWhite = CBrush::FromHandle((HBRUSH)::GetStockObject(WHITE_BRUSH));
dc.FillRect(&rectPaint, pBrushWhite);
}
void RemoveDib() { m_lpszFileName = NULL; delete m_pPicObj; m_pPicObj = NULL; PaintDib(IsValidDib()); }
void SetPreview(BOOL bPreview) { m_bPreview = bPreview; PaintDib(IsValidDib()); }
CPreviewFileDlg源代碼:
BOOL CPreviewFileDlg::OnInitDialog()
{
CFileDialog::OnInitDialog();
m_DIBStaticCtrl.SubclassDlgItem(IDC_IMAGE, this);
CWnd* pParent = GetParent();
CRect rcParent;
pParent->GetClientRect(&rcParent);
CRect rcPrev;
GetDlgItem(IDC_PREVIEW)->GetClientRect(&rcPrev); //復選框
CRect rc;
m_DIBStaticCtrl.GetClientRect(&rc);
int height = rc.Height();
rc.top = rcPrev.bottom - 10;//圖像框設置
rc.bottom = rc.top + height ;
rc.left = 50;
rc.right = rcParent.Width() - rc.left;
m_DIBStaticCtrl.MoveWindow(&rc, true);
GetDlgItem(IDC_PREVIEW)->SendMessage(BM_SETCHECK, (m_bPreview) ? 1 : 0);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CPreviewFileDlg::OnFileNameChange()
{
CFileDialog::OnFileNameChange();
if (m_bPreview)
{
m_DIBStaticCtrl.SetPreview(m_bPreview);//顯示圖片
m_DIBStaticCtrl.LoadDib(GetPathName()); //加載
}
}//當點擊文件時調用
void CPreviewFileDlg::OnFolderChange()
{
CFileDialog::OnFolderChange();
m_DIBStaticCtrl.RemoveDib();//清除
}
菜單欄源代碼:
void CPreviewDlg::OnPreview()
{
// TODO: Add extra validation here
static char BASED_CODE szFilter[] = "Bitmap(*.bmp)|*.bmp|JPEG(*.jpg)|*.jpg|GIF(*.gif)|*.gif|WMF(*.wmf)|*.wmf|ICON(*.ico)|*.ico||";
CString strDefName;
char szPath[MAX_PATH];//最大目錄大小
CPreviewFileDlg FileDlg(TRUE,"*.*",NULL,
OFN_FILEMUSTEXIST|OFN_NONETWORKBUTTON|
OFN_PATHMUSTEXIST,szFilter);
FileDlg.m_ofn.lpstrInitialDir = szPath;
if( FileDlg.DoModal() != IDOK )
return;
// To get the selected file's path and name
CString strFileName;
strFileName = FileDlg.GetPathName();
if(strFileName.IsEmpty())
{
return;
}
}
2009年12月3日
一、malloc()和free()的基本概念以及基本用法:
1、函數原型及說明:
void *malloc(long NumBytes):
該函數分配了NumBytes個字節,并返回了指向這塊內存的指針。如果分配失敗,則返回一個空指針(NULL)。
關于分配失敗的原因,應該有多種,比如說空間不足就是一種。
void free(void *FirstByte):
該函數是將之前用malloc分配的空間還給程序或者是操作系統,也就是釋放了這塊內存,讓它重新得到自由。
2、函數的用法:
其實這兩個函數用起來倒不是很難,也就是malloc()之后覺得用夠了就甩了它把它給free()了,舉個簡單例子:
程序代碼:
// Code...
float *YValue;
YValue=(float *)malloc(DataNumberMax*sizeof(float)); //動態分配內存
if (NULL == YValue) exit (1);
gets(YValue);
// code...
free(YValue);
YValue= NULL;
// code...
就是這樣!當然,具體情況要具體分析以及具體解決。比如說,你定義了一個指針,在一個函數里申請了一塊內存然后通過函數返回傳遞給這個指針,那么也許釋放這塊內存這項工作就應該留給其他函數了。
3、關于函數使用需要注意的一些地方:
A、申請了內存空間后,必須檢查是否分配成功。
B、當不需要再使用申請的內存時,記得釋放;釋放后應該把指向這塊內存的指針指向NULL,防止程序后面不小心使用了它。
C、這兩個函數應該是配對。如果申請后不釋放就是內存泄露;如果無故釋放那就是什么也沒有做。釋放只能一次,如果釋放兩次及兩次以上會出現錯誤(釋放空指針例外,釋放空指針其實也等于啥也沒做,所以釋放空指針釋放多少次都沒有問題)。
D、雖然malloc()函數的類型是(void *),任何類型的指針都可以轉換成(void *),但是最好還是在前面進行強制類型轉換,因為這樣可以躲過一些編譯器的檢查。
二、malloc()到底從哪里得來了內存空間:
1、 malloc()到底從哪里得到了內存空間? 答案是從堆里面獲得空間。也就是說函數返回的指針是指向堆里面的一塊內存。操作系統中有一個記錄空閑內存地址的鏈表。當操作系統收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結點,然后就將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序。就是這樣!
2、什么是堆:堆是大家共有的空間,分全局堆和局部堆。全局堆就是所有沒有分配的空間,局部堆就是用戶分配的空間。堆在操作系統對進程 初始化的時候分配,運行過程中也可以向系統要額外的堆,但是記得用完了要還給操作系統,要不然就是內存泄漏。
什么是棧:棧是線程獨有的,保存其運行狀態和局部自動變量的。棧在線程開始的時候初始化,每個線程的?;ハ嗒毩ⅰC總€函數都有自己的棧,棧被用來在函數之間傳遞參數。操作系統在切換線程的時候會自動的切換棧,就是切換SS/ESP寄存器。??臻g不需要在高級語言里面顯式的分配和釋放。
通過上面對概念的描述,可以知道:
棧是由編譯器自動分配釋放,存放函數的參數值、局部變量的值等。操作方式類似于數據結構中的棧。
堆一般由程序員分配釋放,若不釋放,程序結束時可能由OS回收。注意這里說是可能,并非一定。所以我想再強調一次,記得要釋放!
所以,舉個例子,如果你在函數上面定義了一個指針變量,然后在這個函數里申請了一塊內存讓指針指向它。實際上,這個指針的地址是在棧上,但是它所指向的內容卻是在堆上面的!這一點要注意!所以,再想想,在一個函數里申請了空間后,比如說下面這個函數:
程序代碼:
// code...
void Function(void)
{
char *p = (char *)malloc(100 * sizeof(char));
}
就這個例子,千萬不要認為函數返回,函數所在的棧被銷毀指針也跟著銷毀,申請的內存也就一樣跟著銷毀了!這絕對是錯誤的!因為申請的內存在堆上,而函數所在的棧被銷毀跟堆完全沒有啥關系。所以,還是那句話:記得釋放!
3、free()到底釋放了什么
free()釋放的是指針指向的內存!注意!釋放的是內存,不是指針!這點非常非常重要!指針是一個變量,只有程序結束時才被銷毀。釋放了內存空間后,原來指向這塊空間的指針還是存在!只不過現在指針指向的內容的垃圾,是未定義的,所以說是垃圾。因此,前面我已經說過了,釋放內存后把指針指向NULL,防止指針在后面不小心又被解引用了。非常重要啊這一點!
三、malloc()以及free()的機制:
事實上,仔細看一下free()的函數原型,也許也會發現似乎很神奇,free()函數非常簡單,只有一個參數,只要把指向申請空間的指針傳遞
給free()中的參數就可以完成釋放工作!這里要追蹤到malloc()的申請問題了。申請的時候實際上占用的內存要比申請的大。因為超出的空間是用來記錄對這塊內存的管理信息。
malloc()申請的空間實際我覺得就是分了兩個不同性質的空間。一個就是用來記錄管理信息的空間,另外一個就是可用空間了。而用來記錄管理信息的實際上是一個結構體。在C語言中,用結構體來記錄同一個對象的不同信息是
下面看看這個結構體的原型:
程序代碼:
struct mem_control_block {
int is_available; //這是一個標記?
int size; //這是實際空間的大小
};
對于size,這個是實際空間大小。這里其實我有個疑問,is_available是否是一個標記?因為我看了free()的源代碼之后對這個變量感覺有點納悶(源代碼在下面分析)。這里還請大家指出!
所以,free()就是根據這個結構體的信息來釋放malloc()申請的空間!而結構體的兩個成員的大小我想應該是操作系統的事了。但是這里有一個問題,malloc()申請空間后返回一個指針應該是指向第二種空間,也就是可用空間!不然,如果指向管理信息空間的話,寫入的內容和結構體的類型有可能不一致,或者會把管理信息屏蔽掉,那就沒法釋放內存空間了,所以會發生錯誤?。ǜ杏X自己這里說的是廢話)
好了!下面看看free()的源代碼,我自己分析了一下,覺得比起malloc()的源代碼倒是容易簡單很多。只是有個疑問,下面指出!
程序代碼:
// code...
void free(void *ptr)
{
struct mem_control_block *free;
free = ptr - sizeof(struct mem_control_block);
free->is_available = 1;
return;
}
看一下函數第二句,這句非常重要和關鍵。其實這句就是把指向可用空間的指針倒回去,讓它指向管理信息的那塊空間,因為這里是在值上減去了一個結構體的大小!后面那一句free->is_available = 1;我有點納悶!我的想法是:這里is_available應該只是一個標記而已!因為從這個變量的名稱上來看,is_available 翻譯過來就是“是可以用”。不要說我土!我覺得變量名字可以反映一個變量的作用,特別是嚴謹的代碼。這是源代碼,所以我覺得絕對是嚴謹的!!這個變量的值是1,表明是可以用的空間!只是這里我想了想,如果把它改為0或者是其他值不知道會發生什么事?!但是有一點我可以肯定,就是釋放絕對不會那么順利進行!因為這是一個標記!
當然,這里可能還是有人會有疑問,為什么這樣就可以釋放呢??我剛才也有這個疑問。后來我想到,釋放是操作系統的事,那么就free()這個源代碼來看,什么也沒有釋放,對吧?但是它確實是確定了管理信息的那塊內存的內容。所以,free()只是記錄了一些信息,然后告訴操作系統那塊內存可以去釋放,具體怎么告訴操作系統的我不清楚,但我覺得這個已經超出了我這篇文章的討論范圍了。
那么,我之前有個錯誤的認識,就是認為指向那塊內存的指針不管移到那塊內存中的哪個位置都可以釋放那塊內存!但是,這是大錯特錯!釋放是不可以釋放一部分的!首先這點應該要明白。而且,從 free()的源代碼看,ptr只能指向可用空間的首地址,不然,減去結構體大小之后一定不是指向管理信息空間的首地址。所以,要確保指針指向可用空間的首地址!
程序代碼:
float *YValuePoint;
while ( !feof(file) && i<DataNumberMax) //讀數
{
fscanf(file,"%f ",&data);
YValue[i]=data;
sum+=YValue[i];
pLineSerie->AddPoint(XValue++,YValue[i]);
i++;
}
average=sum/DataNumberMax;
max=*YValue;
min=*(YValue+2);
YValuePoint=YValue; //保存首地址
for(int j=0;j<DataNumberMax;j++)
{
if(max <* YValue)
{
max =* YValue;
}
else
{
if(min > *YValue) min = *YValue;
}
YValue++; //YValue地址值+1
}
free(YValuePoint); //釋放內存,釋放從該內存空間的首地址開始
2009年11月28日
摘要: HHOOK SetWindowsHookEx( //裝載一個...
閱讀全文
2009年11月22日
指向另一指針的指針:轉自
風過無痕博客
一. 回顧指針概念:
早在本系列第二篇中我就對指針的實質進行了闡述。今天我們又要學習一個叫做指向另一指針地址的指針。讓我們先回顧一下指針的概念吧!
當我們程序如下申明變量:
short int i;
char a;
short int * pi;
程序會在內存某地址空間上為各變量開辟空間,如下圖所示。
內存地址→6 7 8 9 10 11 12 13 14 15
-------------------------------------------------------------------------------------
… | | | | | | | | | |
-------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
圖中所示中可看出:
i 變量在內存地址5的位置,占兩個字節。
a變量在內存地址7的位置,占一個字節。
pi變量在內存地址9的位置,占兩個字節。(注:pi 是指針,我這里指針的寬度只有兩個字節,32位系統是四個字節)
接下來如下賦值:
i=50;
pi=&i;
經過上在兩句的賦值,變量的內存映象如下:
內存地址→6 7 8 9 10 11 12 13 14 15
--------------------------------------------------------------------------------------
… | 50 | | | 6 | | | |
--------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
看到沒有:短整型指針變量pi的值為6,它就是I變量的內存起始地址。所以,這時當我們對*pi進行讀寫操作時,其實就是對i變量的讀寫操作。如:
*pi=5; //就是等價于I=5;
二. 指針的地址與指向另一指針地址的指針
在上一節中,我們看到,指針變量本身與其它變量一樣也是在某個內存地址中的,如pi的內存起始地址是10。同樣的,我們也可能讓某個指針指向這個地址。
看下面代碼:
short int * * ppi; //這是一個指向指針的指針,注意有兩個*號
ppi=pi
第一句:short int * * ppi;——申明了一個指針變量ppi,這個ppi是用來存儲(或稱指向)一個short int * 類型指針變量的地址。
第二句:&pi那就是取pi的地址,ppi=pi就是把pi的地址賦給了ppi。即將地址值10賦值給ppi。如下圖:
內存地址→6 7 8 9 10 11 12 13 14 15
------------------------------------------------------------------------------------
… | 50 | | | 6 | 10 | |
------------------------------------------------------------------------------------
|short int i|char a| |short int * pi|short int ** ppi|
從圖中看出,指針變量ppi的內容就是指針變量pi的起始地址。于是……
ppi的值是多少呢?——10。
*ppi的值是多少呢?——6,即pi的值。
**ppi的值是多少呢?——50,即I的值,也是*pi的值。
呵呵!不用我說太多了,我相信你應明白這種指針了吧!
三. 一個應用實例
1. 設計一個函數:void find1(char array[], char search, char * pi)
要求:這個函數參數中的數組array是以0值為結束的字符串,要求在字符串array中查找字符是參數search里的字符。如果找到,函數通過第三個參數(pa)返回值為array字符串中第一個找到的字符的地址。如果沒找到,則為pa為0。
設計:依題意,實現代碼如下。
void find1(char array[] , char search, char * pa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
}
你覺得這個函數能實現所要求的功能嗎?
調試:
我下面調用這個函數試試。
void main()
{
char str[]={“afsdfsdfdf\0”}; //待查找的字符串
char a=’d’; //設置要查找的字符
char * p=0; //如果查找到后指針p將指向字符串中查找到的第一個字符的地址。
find1(str,a,p); //調用函數以實現所要操作。
if (0==p )
{
printf (“沒找到!\n”);//1.如果沒找到則輸出此句
}
else
{
printf(“找到了,p=%d”,p); //如果找到則輸出此句
}
}
分析:
上面代碼,你認為會是輸出什么呢?
運行試試。
唉!怎么輸出的是:沒有找到!
而不是:找到了,……。
明明a值為’d’,而str字符串的第四個字符是’d’,應該找得到呀!
再看函數定義處:void find1(char array[] , char search, char * pa)
看調用處:find1(str,a,p);
依我在第五篇的分析方法,函數調用時會對每一個參數進行一個隱含的賦值操作。
整個調用如下:
array=str;
search=a;
pa=p; //請注意:以上三句是調用時隱含的動作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
哦!參數pa與參數search的傳遞并沒有什么不同,都是值傳遞嘛(小語:地址傳遞其實就是地址值傳遞嘛)!所以對形參變量pa值(當然值是一個地址值)的修改并不會改變實參變量p值,因此p的值并沒有改變(即p的指向并沒有被改變)。
(如果還有疑問,再看一看《函數參數的傳遞》了。)
修正:
void find2(char [] array, char search, char ** ppa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
}
主函數的調用處改如下:
find2(str,a,&p); //調用函數以實現所要操作。
再分析:
這樣調用函數時的整個操作變成如下:
array=str;
search=a;
ppa=&p; //請注意:以上三句是調用時隱含的動作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
看明白了嗎?
ppa指向指針p的地址。
對*ppa的修改就是對p值的修改。
下面看一下指向指針變量的指針變量怎樣正確引用。
用指向指針的指針變量訪問一維和二維數組。
#include<stdio.h>
#include<stdlib.h>
main()
{
int a[3],b[2][2],*p1,*p2,**p3,i,j;
printf("請輸入一維數組的值:\n");
for(i=0;i<3;i++)
scanf("%d",&a[i]);/*一維數組的輸入*/
printf("請輸入二維數組的值:\n");
for(i=0;i<2;i++)
for(j=0;j<2;j++)
scanf("%d",&b[i][j]);/*二維數組輸入*/
printf("用指針輸出一維數組:\n");
for(p1=a,i=0;i<3;i++) /* 用指針輸出一維數組*/
{
printf("%4d",*(p1+i));
}
printf("\n");
printf("用指向指針的指針變量輸出一維數組(1):\n");
for(p1=a,p3=&p1,i=0;i<3;i++)
printf("%4d",*(*p3+i));/*用指向指針的指針變量輸出一維數組*/
printf("\n");
printf("用指向指針的指針變量輸出一維數組(2):\n");
for(p1=a;p1-a<3;p1++)/*用指向指針的指針變量輸出一維數組*/
{
p3=&p1;
printf("%4d",**p3);
}
printf("\n");
printf("用指針輸出二維數組:\n");
for(i=0;i<2;i++) /*用指針輸出二維數組*/
{
p2=b[i] ;
for(int j=0;j<2;j++)
{
printf("%4d",*(p2+j)) ;
}
}
printf("\n");
printf("用指向指針的指針變量輸出二維數組(1):\n");
for(i=0;i<2;i++)/*用指向指針的指針變量輸出二維數組*/
{
p2=b[i];
p3=&p2;
for(j=0;j<2;j++)
printf("%4d",*(*p3+j));
利用指向指針的指針變量對二維字符數組的訪問。
#include<stdio.h>
#include<stdlib.h>
main()
{
int i;
char * ptr;
static char c[][16]={"clanguage","fox","computer","homepage"};
/*二維字符數組*/
static char *cp[]={c[0],c[1],c[2],c[3]};/*指針數組*/
static char **cpp;/*指向字符指針的指針變量*/
cpp=cp;/*將指針數組的首地址傳遞給指向字符指針的指針變量*/
for(i=0;i<4;i++)/*按行輸出字符串*/
printf("%s\n",*cpp++);
printf("-----------\n");
for(i=0;i<4;i++)/*按行輸出字符串*/
{
cpp=&cp[i];
printf("%s\n",*cpp);
}
printf("-----------\n");
for(i=0;i<4;i++)
{
ptr=c[i];
printf("%s",ptr);
printf("\n");
}
}
}
printf("\n");
printf("用指向指針的指針變量輸出二維數組(2):\n");
for(i=0;i<2;i++)/*用指向指針的指針變量輸出二維數組*/
{
p2=b[i];
for(p2=b[i];p2-b[i]<2;p2++)
{
p3=&p2;
printf("%4d",**p3);
}
printf("\n");
}
}
2009年11月21日
所謂接口繼承,就是派生類只繼承函數的接口,也就是聲明;而實現繼承,就是派生類同時繼承函數的接口和實現。
我們都很清楚C++中有幾個基本的概念,虛函數、純虛函數、非虛函數。
虛函數:
C++實現運行中的
多態性是通過虛函數實現的,而虛函數必須存在于繼承環境下。
因此,虛函數是指一個類中你希望進行重載的成員函數,當你用一個基類指針或引用指向一個繼承類對象的時候,你調用一個虛函數,實際調用的是繼承類的成員函數。虛函數用來表現基類和派生類的成員函數之間的一種關系。虛函數的定義在基類中進行,在需要定義為虛函數的成員函數的聲明前冠以關鍵字,如
virtual void func() 。 基類中的某個成員函數被聲明為虛函數后,此虛函數就可以在一個或多個派生類中被重新定義. 在派生類中重新定義時,其函數原型,包括返回類型、函數名、參數個數、參數類型及參數的先后順序,都必須與基類中的原型完全相同。
虛函數是
重載的一種表現形式,是一種動態的重載方式。
只有類的普通成員函數可以定義為虛函數,全局函數及靜態成員函數(類擁有)不能聲明為虛函數。
純虛函數: 純虛函數在基類中沒有定義且只能在基類中定義,但未給出具體的函數定義體(
實現),它們被初始化為0。任何用純虛函數派生的類,都要自己提供該函數的具體實現。
定義純虛函數:
virtual void func() = 0;
定義了純虛函數的類被稱之為
抽象類。抽象類定義一族派生類的共同
接口,而接口的完整
實現,即純虛函數的函數體,由派生類自己定義。
例://class Shape
public:
virtual void area()=0; // 純虛函數
//class Tringle : public Shape //公有繼承
public:
void area() {//} /接口與實現
抽象類可以有多個純虛函數,也可以定義其他虛函數。若派生類沒有重新定義純虛函數,那么該派生類也稱之為純虛函數。
純虛函不需要定義其實際操作,它的存在只是為了在派生類中被重新定義,只是提供一個
多態接口。
非虛函數:
一般成員函數,無virtual關鍵字修飾。
至于為什么要定義這些函數,我們可以將虛函數、純虛函數和非虛函數的功能與
接口繼承與
實現繼承聯系起來:
如前所述,聲明一個純虛函數(pure virtual)的目的是為了讓派生類只繼承函數接口,也就是上面說的接口繼承。
純虛函數一般是在不方便具體實現此函數的情況下使用。也就是說基類無法為繼承類規定一個統一的缺省操作,但繼承類又必須含有這個函數接口,并對其分別實現。但是,在C++中,我們是可以為純虛函數提供定義的,只不過這種定義對繼承類來說沒有特定的意義。因為繼承類仍然要根據各自需要實現函數。
通俗說,純虛函數就是要求其繼承類必須含有該函數接口,并對其進行實現。是對繼承類的一種接口實現要求,但并不提供缺省操作,各個繼承類必須分別實現自己的操作。
聲明非純虛函數(impure virtual)的目的是讓繼承類繼承該函數的接口和缺省實現。
與純虛函數唯一的不同就是其為繼承類提供了缺省操作,繼承類可以不實現自己的操作而采用基類提供的默認操作。
聲明非虛函數(non-virtual)的目的是為了令繼承類繼承函數接口及一份強制性實現。
相對于虛函數來說,非虛函數對繼承類要求的更為嚴格,繼承類不僅要繼承函數接口,而且也要繼承函數實現。也就是為繼承類定義了一種行為。
總結:
純虛函數:要求繼承類必須含有某個接口,并對接口函數實現。
虛函數:繼承類必須含有某個接口,可以自己實現,也可以不實現,而采用基類定義的缺省實現。
非虛函數:繼承類必須含有某個接口,必須使用基類的實現。
一個C++類有著兩個重要的方面:用于描述行為的公共接口,以及行為的私有實現。
大多數的繼承都是公有繼承:派生類繼承了基類的接口和實現。不過,我們也可以進行有選擇的繼承,即派生類可以只繼承接口或實現。私有基類,只繼承實實現,沒有接口;公有繼承基類,繼承接口,但繼承的實現可能是不完整的或不存在的(純虛函數)。
例:
我們可以用函數:
Triangle t;
t.area();
我們只是使用了其接口,但具體的實現可以不知道。void area() {......}
通過繼承機制,可以利用已有的數據類型來定義新的數據類型。所定義的新的數據類型不僅擁有新定義的成員,而且還同時擁有舊的成員。我們稱已存在的用來派生新類的類為基類,又稱為父類。由已存在的類派生出的新類稱為派生類,又稱為子類。
在C++語言中,一個派生類可以從一個基類派生,也可以從多個基類派生。從一個基類派生的繼承稱為單繼承;從多個基類派生的繼承稱為多繼承。
例:
#include <iostream.h>
class Metal
{
public:
unsigned atomicNumber;
float atomicWeight;
float pricePerounce;
public:
Metal( unsigned Number=0,
float Weight=0.000000,
float Perounce=0.000000)
{
atomicNumber=Number;
atomicWeight=Weight;
pricePerounce=Perounce;
}
~Metal() {}
unsigned GetNumber(void) {return atomicNumber;} //內聯函數
float GetWeight(void) {return atomicWeight;}
float Getprice(void) {return pricePerounce;}
virtual void output()
{
cout << "The atomic weight =" << atomicWeight << endl;
cout << "The atomic number =" << atomicNumber << endl;
cout << "Price per ounce =" << pricePerounce << endl;
}
};
class Pb : public Metal //公有繼承,單繼承
{
public:
Pb (unsigned Number=82,float Weight=207,float Perounce=0.01):Metal(Number,Weight,Perounce) {}
//子類構造首先調用基類構造函數
};
class Au : public Metal
{
public:
Au (unsigned Number=79,float Weight=196.9665,float Perounce=450.75):Metal(Number,Weight,Perounce) {}
Au (Pb& lemp) //拷貝函數
{
atomicNumber=lemp.GetNumber()-3;
atomicWeight=lemp.GetWeight()-10.2335;
pricePerounce=lemp.Getprice()+450.74;
}
};
void main ()
{
Pb m;
Au n=m; //拷貝
n.output();
}
注:
派生類的三種繼承方式:公有繼承(public)、私有繼承(private)、保護繼承(protected)是常用的三種繼承方式:
公有繼承時,水平訪問和垂直訪問對基類中的公有成員不受限制;
私有繼承時,水平訪問和垂直訪問對基類中的公有成員也不能訪問;
保護繼承時,對于垂直訪問同于公有繼承,對于水平訪問同于私有繼承。
對于基類中的私有成員,只能被基類中的成員函數和友元函數所訪問,不能被其他的函數訪問。
如果通過公有繼承來產生基類,那么這個派生類應該是其基類的特化。
如果派生類之間的區別在于屬性,則用數據成員來表示;如果在于行為,則用虛函數來表示。
任何一個類都可以派生出一個新類,派生類也可以再派生出新類。
2009年11月19日
函數介紹:CButtonST應用
DWORD CButtonST::SetMenu(UINT nMenu, HWND hParentWnd, BOOL bRepaint)
{
HINSTANCE hInstResource = NULL;
// Destroy any previous menu
if (m_hMenu)
{
::DestroyMenu(m_hMenu);
m_hMenu = NULL;
m_hParentWndMenu = NULL;
m_bMenuDisplayed = FALSE;
} // if
// Load menu
if (nMenu)
{
// Find correct resource handle
hInstResource = AfxFindResourceHandle(MAKEINTRESOURCE(nMenu), RT_MENU);
// Load menu resource
m_hMenu = ::LoadMenu(hInstResource, MAKEINTRESOURCE(nMenu));
m_hParentWndMenu = hParentWnd;
// If something wrong
if (m_hMenu == NULL) return BTNST_INVALIDRESOURCE;
} // if
// Repaint the button
if (bRepaint) Invalidate();
return BTNST_OK;
} // End of SetMenu
DWORD CButtonST::SetMenu(UINT nMenu, HWND hParentWnd, BOOL bWinXPStyle, UINT nToolbarID, CSize sizeToolbarIcon, COLORREF crToolbarBk, BOOL bRepaint) //除前兩個參數,其他參數都有初始值
{
BOOL bRetValue = FALSE;
// Destroy any previous menu
if (m_menuPopup.m_hMenu)
{
m_menuPopup.DestroyMenu();
m_hParentWndMenu = NULL;
m_bMenuDisplayed = FALSE;
} // if
// Load menu
if (nMenu)
{
m_menuPopup.SetMenuDrawMode(bWinXPStyle);
// Load menu
bRetValue = m_menuPopup.LoadMenu(nMenu);
// If something wrong
if (bRetValue == FALSE) return BTNST_INVALIDRESOURCE;
// Load toolbar
if (nToolbarID)
{
m_menuPopup.SetBitmapBackground(crToolbarBk);
m_menuPopup.SetIconSize(sizeToolbarIcon.cx, sizeToolbarIcon.cy);
bRetValue = m_menuPopup.LoadToolbar(nToolbarID);
// If something wrong
if (bRetValue == FALSE)
{
m_menuPopup.DestroyMenu();
return BTNST_INVALIDRESOURCE;
} // if
} // if
m_hParentWndMenu = hParentWnd;
} // if
// Repaint the button
if (bRepaint) Invalidate();
return BTNST_OK;
} // End of SetMenu
通過
#ifdef BTNST_USE_BCMENU 來判斷選擇哪個函數。
程序過程:
頭文件:
CButtonST m_btnHelp;
源文件:
1)在當前對話類的初始化函數中添加:
OnInitDialog() m_btnHelp.SetIcon(IDI_HELP, (int)BTNST_AUTO_GRAY); //設置圖標,未點擊時變灰
m_btnHelp.SetTooltipText(_T("Help")); //輸出文字
#ifdef BTNST_USE_BCMENU
m_btnHelp.SetMenu(IDR_MENU, m_hWnd); //點擊時彈出菜單欄
#else
m_btnHelp.SetMenu(IDR_MENU, m_hWnd);
#endif
2)設置控件交換信息:
DoDataExchange(CDataExchange* pDX)函數中
DDX_Control(pDX,IDC_BUTTON1,m_btnHelp); //輸出
3)新菜單欄響應函數:
新建一菜單欄:IDR_MENUNEW,設置為POP-UP;
設置子菜單:IDR_ITEM1.點擊ClassWizard,選擇當前文檔類點擊ON_COMMAND設置響應函數。