(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;
概述:创建一个属性表单,首先创建一个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);
注意点:需改文字种类和类型。
打印预览实现流程:
首先调用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来实现绘制表格和曲线。
步骤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;
}
}
一、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、什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
通过上面对概念的描述,可以知道:
栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。
堆一般由程序员分配释放,若不释放,程序结束时可能由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()申请空间后返回一个指针应该是指向第二种空间,也就是可用空间!不然,如果指向管理信息空间的话,写入的内容和结构体的类型有可能不一致,或者会把管理信息屏蔽掉,那就没法释放内存空间了,所以会发生错误!(感觉自己这里说的是废话)
好了!下面看看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); //释放内存,释放从该内存空间的首地址开始
摘要: HHOOK SetWindowsHookEx( //装载一个...
阅读全文
指向另一指针的指针:转自
风过无痕博客
一. 回顾指针概念:
早在本系列第二篇中我就对指针的实质进行了阐述。今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧!
当我们程序如下申明变量:
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");
}
}
所谓接口继承,就是派生类只继承函数的接口,也就是声明;而实现继承,就是派生类同时继承函数的接口和实现。
我们都很清楚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)是常用的三种继承方式:
公有继承时,水平访问和垂直访问对基类中的公有成员不受限制;
私有继承时,水平访问和垂直访问对基类中的公有成员也不能访问;
保护继承时,对于垂直访问同于公有继承,对于水平访问同于私有继承。
对于基类中的私有成员,只能被基类中的成员函数和友元函数所访问,不能被其他的函数访问。
如果通过公有继承来产生基类,那么这个派生类应该是其基类的特化。
如果派生类之间的区别在于属性,则用数据成员来表示;如果在于行为,则用虚函数来表示。
任何一个类都可以派生出一个新类,派生类也可以再派生出新类。
函数介绍: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设置响应函数。