Windows的消息处理机制是用如下代码进行消息处理的:
MSG message;
While(::GetMessage(&message,NULL,0,0)){
::TranslateMessage(&message);
::DispatchMessage(&message);
}
当消息到达时,由TranslateMessage进行必要的转换,例如:将WM_KEYDOWN消息转换为包含有ASCII字符的WM_CHAR消息,
然后由DispatchMessage进行发送,当处理完成后,DispatchMessage返回.
CFileDialog mp3(TRUE,NULL,NULL,OFN_HIDEREADONLY,"MP3 Files (*.mp3)|*.mp3|");
if(mp3.DoModal() == IDOK)
{
m_Path = mp3.GetPathName();
UpdateData(FALSE);
}
VC++6.0中的部分类的新特性
( 作者: 武汉 鄢小征 )
VC6.0比起VC5.0来,界面与其各自提供的MFC类都有所不同。例如,VC6.0中没有了
5.0版本中Workspace的InfoView,其相关内容另外提供在MSDN 中;再如,用MFC AppWizard(exe)
生成Document 的过程中,你可以选择你的项目是标准型(MFC Standard)还是浏览器型(Windows
Explorer)等等。这些都是较为明显且很容易看到并能运用的。其实我觉得其中较为隐蔽也最重要的
是其MFC 类版本的提高所提供的更多的类、更多的函数、更多的结构。能否运用这些“更多”来开发
手中的产品,是你的产品是否跟得上比尔他老人家Windows的关键。
例如,CPropertyPage与CPropertySheet大家一定很熟悉吧,但若要你在向导页上加背景图像、标题
图像可不一定容易。用VC6.0中提供的CPropertyPageEx与CPropertySheetEx可以很容易地办到。下面一
起来做一个看看。
准备尺寸为320×240,320×80的两张24位(确信你的系统支持) .BMP;
运行VC6.0,File>New>Projectx>MFC AppWizard(exe),在Project Name中填写:
NewWizard; 单击OK,选Dialog based,Next,不选About box,Finish。在生成的项目中删除ID为
IDD_NEWWIZARD_DIALOG的Dialog以及NewWizardDlg.h、NewWizardDlg.cpp两个文件。
然后是Insert>New Class...,Name填CNWPropertySheet,Base class选CPropertySheet
(不会自动提供CPropertySheetEx与CPropertyPageEx,怎么办?等一下自己
改);
打开NewWizard.cpp文件,
把 #include “NewWizardDlg.h"改为#include“NWPropertySheet.h";
InitInstance 函数改为:
BOOL CNewWizardApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL Enable3dControls();
#else Enable3dControlsStatic();
#endif
CBitmap hbmWatermark; CBitmap hbmHeader;
hbmWatermark.LoadBitmap(IDB_WATERMARK);
hbmHeader.LoadBitmap(IDB_HEADER);
CNWPropertySheet dlg(_T("New Wizard"),
NULL,
0,
hbmWatermark,
NULL,
hbmHeader);
m_pMainWnd = &dlg; // 不用dlg.SetWizardMode();
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{ }
else if (nResponse == IDCANCEL) { }
return FALSE;
}
记得把两个.BMP文件加入(Insert>Import...)。ID值分别为
IDB_WATERMARK、
IDB_HEADER (在VC6.0中不可以打开24位.BMP,但可以点右键改ID)。
接下来新建两个Dialog,去掉OK与CANCEL两按钮。
利用ClassWizard分别生成基于它们的NewClass:CPage1,CPage2;Base class都为CPropertyPage。
利用ClassWizard为它们加入OnSetActive();打开Page1.cpp文件,在OnSetActive()中加入:
CPropertySheet* pSheet = (CPropertySheet*)GetParent();
ASSERT_KINDOF(CPropertySheet,pSheet);
pSheet->SetWizardButtons(PSWIZB_NEXT);
为了在首页Page1呈现水印(watermark),即第一幅.BMP,
还需在解析函数中加入m_psp.dwFlags |= PSP_HIDEHEADER;
同样,在Page2.cpp 文件的OnSetActive() 中加入
CPropertySheet* pSheet = (CPropertySheet*)GetParent();
ASSERT_KINDOF(CPropertySheet, pSheet);
pSheet->SetWizardButtons( PSWIZB_BACK | PSWIZB_NEXT | PSWIZB_FINISH);
打开NWPropertySheet.h , 在其开头处加入
#include “Page1.h"
#include “Page2.h"
CNWPropertySheet 类中说明:
public: CPage1 pPage1; CPage2 pPage2;
接着打开 NWPropertySheet.cpp , 需在解析函数(两个)中加入:
AddPage(&pPage1);
AddPage(&pPage2);
m_psh.dwFlags |= PSH_WIZARD97; // 新特性
好了,接下来要做的就是手工把CPropertySheet与CPropertyPage改为CPropertySheetEx
与CPropertyPageEx。
可以利用Edit>Replace,一个一个文件改。但是,它们的结构都不同(CPropertySheetEx
在CPropertySheet上扩展),
例如,CNWPropertySheet类中应为:
public:
CNWPropertySheet(UINT nIDCaption,
CWnd*pParentWnd=NULL,
UINT iSelectPage=0,
HBITMAP hbmWatermark=NULL,
VC++5.0定制窗口的方法
作者 刘杰
VC++5.0是Microsoft新近推出的可视化C++集成开发环境。它在继承以前VC++的基础上增加了许多新的功能,用于支持Win32平台应用程序、服务程序和控件的开发。VC++5.0提供了强大、快捷的编程工具,其中最基本的是三个导航: AppWizard用于程序框架的生成,AppStudio用于资源的编辑, ClassWizard用于类的编辑和管理。其中,窗口、菜单等无需用户编写程序,而由系统自动生成。但在许多情况下,用户要设置自己希望的窗口(即定制窗口)。
一、如何在多文档界面下去掉开始的子窗口
在多文档界面下,自动生成一个新的子窗口,而一个实际的应用系统往往是由用户操作后再生成新的窗口。为了去掉开始的子窗口,可在应用程序文件分析命令行的语句
CcommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);后加入:
cmdInfo.m_nShellCommand=CcommandLineInfo::FileNothing;
去掉子窗口后,就只剩下主框架窗口了。因为在多文档界面中,系统生成两个菜单:一个是用户的菜单,另一个是系统主框架菜单。通常用户工作在用户菜单。为了保证菜单界面不变,可修改主框架菜单资源,使其与用户菜单保持一致。
二、修改窗口标题栏
在缺省情况下,窗口标题栏中显示的文档名为文件名。若要在标题栏显示一个长字符串,而又不修改文件名,则可将项目工作区转换到 Resource View面版,选择串表( StringTable)资源,在StringTable中双击 IDR-MAIN-FRAME项,caption中显示一字符串xx/n/yy......,将第一个参数修改为用户自己希望见到的主窗口标题即可。
三、修改主框架窗口、子窗口及其显示性质
可通过覆盖CWnd的成员函数PreCreateWindow来修改主窗口和子窗口。PreCreateWindow函数在即将创建窗口前被调用,函数原型为:
Virtual BOOL PreCreateWindow(CREATESTRUCT cs)
如果要覆盖 PreCreateWindow函数,则在创建窗口前可以修改 CREATESTRUCT结构以替换缺省参数。CREATESTRUCT结构存放窗口特征,如窗口坐标、风格等,还可以定义新窗口风格,
若想修改主框架窗口,则可以在MainFrm.cpp的下列成员函数中加入待修改的内容。例如:
BOOL CmainFrame::PreCreateWindow(CREATESTRUCT&cs)
{
//通过修改CREATESTRUCT结构来修改窗口类或风格
//定义新窗口的高度、宽度
cs.cx=450;
cs.cy=300;
//定义新窗口风格为去掉主窗口名及最大化等按钮
cs.style=ws-POPWINDO;
return CframeWnd::PreCreateWindow(cs);
}
定制子窗口的操作与上述主窗口相同,可在 ChildFrm.cpp中加入以下内容:
BOOL CmainFrame::PreCreateWindow(CREATESTRUCT&cs)
{
//通过修改CREATESTRUCT结构来修改窗口类或风格
return C mdichildWnd::PreCreateWindow(cs);
}
要修改视图窗口的显示性质,则可在视图文件 xxView.cpp的下述成员函数中加入以下语句:
BOOL xxView::PreCreateWindow(CREATESTRUCT&cs)
{
//增加的语句
cs.lpszClass=AfxRegisterWndClass(cs-HREDRAW|CS-VREDRAW,0,(HBRUSH)::GetStockObject(WHITE-BRUSH),0);
return CscrollView::PreCreateWindow(cs);
}
其中, cs的参数pszClass用于存放Windows窗口类名称。要想注册Windows 窗口类,则必须调用全局函数AfxRegisterWndClass。该函数原型为:
LPCTSTR AFXAPI AfxRegisterWndClass(UINTnClassStyle,HCURSOR hCursor=0,HBRUSH hbrBackground=0,HICON hIcon=0)
上述各参数用于定义风格,其含义分别为光标资源句柄、背景资源句柄、图标资源句柄。上述增加的语句的作用是:改变窗口大小时重画窗口、不显示光标图标、设置白色背景。
四、窗口的滚动
使用CscrollView代替Cview类即可实现滚动窗口。此时,系统生成OnInitialUpdate()成员函数:
void CmyscrollView::OnInitialUpdat()
{
CscrollView::OnIntialUpdate();
Csize sizeTotal;
SizeTotal.cs=sizeToal.cy=100;
SetScrollSizes(MM-TEXT,sizeTotal);
}
其中,cs和cy分别为滚动窗口的水平、垂直分量,表明窗口的水平、垂直方向尺寸小于 100像素单位时将出现水平方向滚动条和垂直方向滚动条。通过修改滚动尺寸,可改变出 现滚动条的最小窗口。例如,若“sizeTotal.cx=600;sizeTotal.cy=800; ”,则当窗口尺寸小于600*800时,就会出现滚动条。
五、窗口分割
该功能可将窗口分割成多个可滚动的面板,面板之间的边界称为分割条,可用分割条来调整每个面板的相对大小。要想增加窗口分割功能, 则必须修改主窗口类。首先,在主窗口类的头文件 MainFrm.h中添加以下代码:
CsplitterWnd m-SWnd;
Virtual BOOL OnCreateClient (LPCREATESTRUCTcs,CcreateContext *pContext);
再在 MainFrm.cpp中添加成员函数 OnCreateClient的定义:
BOOL CmainFrame::OnCreateCline(LPCREATESTRUCTcs,CcreateContext *p Context)
{
return m-SWnd.Creat(this,2,2,Csize(20,20),pContext);
}
新的CsplitterWnd类对象m-SWnd用于创建和管理分割窗口,该窗口中可以包含一个或多个面板。
VC6中两个对话框的同时显示
信息产业部电子第三十四所
黄基前
---- 对于VC++初学者,可能会遇到这样一个问题:一个基于Dialog的MFC AppWizard应用程序,再Insert一个对话框,如何同时显示这两个对话框呢?
---- 其实这个问题很简单,想要在屏幕上同时显示两个对话框,并且这两个对话框都可以被激活,则至少第二个对话框应该为非模态对话框。启动非模态对话框的方法与启动一个普通窗口的步骤是一样的,即先调用窗口类的Create()函数创建一个窗口对象,再用ShowWindow()使之显示出来即可(值得注意的是:第二个对话框的对象不能为局部变量,否则在退出OnInitDialog时,该对象会被自动关闭,从而导致第二窗口也会关闭)。例如,下列的代码演示了如何在主对话框的OnInitDialog()中启动另一个非模态对话框。
CDialog2 Dlg2; //注意Dlg2不能为OnInitDialog的局部变量。
BOOL CDialog1::OnInitDialog()
{
CDialog::OnInitDialog();
Dlg2.Create(IDD_DIALOG2,this );
Dlg2.ShowWindow(SW_SHOW);
}
---- 编译并运行,怎么样,结果是不是OK了?
VC5.0编制AD/DA卡
控制程序中科院光电技术研究所(610209)
潘栋梁 熊胜明(pan-
[email protected])
本文以一实例介绍了如何利用VC运行时间库里提供的端口输入函数_inp、端口输出函数
_outp以面向对象的方式实现AD/DA转换控制的编程方法。我们以所使用的PS-2104A12位多功能
AD/DA卡为例,先对该卡的工作方式、端口地址等作个介绍,再对VC5.0用于访问端口的两个函
数作个介绍,然后详细说明控制程序的设计和使用方法。
1.AD/DA卡简介我们使用的PS-2104AAD/DA转换卡具有32路12位A/D转换通道和2路
12位D/A转换通道。该卡口地址可以在0~03FFH间设置。A/D操作为:
N+0地址写操作选择A/D通道;
N+1地址读操作查询A/D转换状态,值为"0"表示转换结束;
N+1地址写操作启动A/D转换,写"0"开始A/D转换;
N+2地址读操作获取转换结果高8位数据;
N+3地址读操作获取转换结果低4位数据。D/A操作为:
N+1地址写操作启动D/A转换;
写"1"启动D/A通道1;
写"2"启动D/A通道2;
N+2地址写操作送D/A高8位数据;
N+3地址写操作送D/A低4位数据。尽管本文的程序例子是针对这种卡编写的,但大多
A/D、D/A卡的接口方式是类似的,只是在操作口地址上稍有差别,因此只要针对你所
使用的卡的具体情况对本文的例子稍作修改便可以同样采用了。
2._inp、_outp函数介绍VC5.0提供了对控制台(console)和端口(port)进行读写
访问的例程,_inp和_outp是其中的两个。它们的原型在头文件中定义,要在程序中
使用它们必须包含该头文件。
_inp原型为:
int _inp(unsigned short port);
port 参数为指定的输入端口号。调用后,它从port参数指定的端口读入并返回一个
字节,输入值可以是在0-255范围内的任意无符号整数值。
_outp原型为:
int _outp(unsigned short port, int databyte );
port 参数为指定的输出端口号,
databyte 参数为输出的值。调用后,它将databyte参数指定的值输出到port参指定
的端口并返回该值。databyte可以是0-255范围内的任何整数值。这两个函数都没有
错误值返回。我们将利用这两个函数来对AD/DA卡进行读入和输出操作。
3.A/D控制类的设计在VC5中用MFC AppWizard[exe]方式建立新工程,把它命名为
AdDaControllApp, 在第一步中选择Dialog based,采用中文[中国]字库,按下
Finish 按钮,选择OK,建立起应用程序的框架。建立A/D 类,选择Insert菜单的
New Class选项,在New Class对话框中选择Class type为Generic Class,并将类命
名为CAdControll, 按OK。在FileView窗口中双击Source Files, 双击
AdControll.cpp, 在该文件的第一行加入#include语句。 选择在ClassView窗口中
选择CAdControll类,为它加入下列成员变量:
protected:
unsigned short m_unBaseAddress;
//存放A/D转换卡基地址
BYTE m_byChannelAddress;
// 存 放A/D 通 道 基 本 偏 移 地 址
BYTE m_byHighEightBits; // 存 放 高8 位 数 据
BYTE m_byLowFourBits; // 存 放 低4 位 数 据
BYTE m_byADState; // 存 放A/D 转 换 器 状 态
BYTE m_byStart; // 存 放 开 始A/D 转 换 的 信 号
float m_fResult; // 存 放 最 终 结 果
在缺省构造函数CAdControll:: CAdControll()中添入它们的缺省初始化语句:
m_unBaseAddress=0x0100; //我们的AD/DA卡基地址
m_byChannelAddress=0x20; //通道基本偏移地址
m_byStart=0x00; // 开 始A/D 转 换 的 信 号
加 入 带 参 数 的 构 造 函 数:
CAdControll:: CadControll
(unsigned short unBaseAddress, BYTE
byChannelAddress)
{
m_unBaseAddress= unBaseAddress;
m_byChannelAddress= byChannelAddress;
m_byStart=0x00; // 开 始A/D 转 换 的 信 号
}
添 加 设 置A/D 卡 基 地 址 的 方 法:
void CAdControll::SetBaseAddress
(unsigned short unBaseAddress)
{
m_unBaseAddress= unBaseAddress;
}
添 加 设 置 转 换 通 道 基 本 偏 移 地 址 的 方 法:
void CAdControll::SetChannelBaseAddress
(BYTE byChannelAddress)
{
m_byChannelAddress= byChannelAddress;
}
添 加 读A/D 转 换 结 果 的 方 法:
float CAdControll::ReadAD(BYTE byChannel=0)
// 缺 省 为 第0 通 道
{
// 向A/D 转 换 器 送 通 道 号
_outp(m_unBaseAddress, m_byChannelAddress +byChannel);
// 发 出 启 动 转 换 信 号
_outp(m_unBaseAddress +1,m_byStart);
// 循 环 查 询 直 到 转 换 结 束
do{
m_byADState=_inp(m_unBaseAddress +1);
// 读 转 换 器 状 态
m_byADState=m_byADState &0x01;
}while(m_byADState);
// 正 在 转 换 则 继 续 查 询,
转 换 结 束 则 退 出 循 环
m_byHighEightBits= -_inp(m_unBaseAddress +2);
// 读 取 转 换 结 果 的 高8 位
m_byLowFourBits=_inp(m_unBaseAddress +3);
// 读 取 转 换 结 果 的 低4 位
// 计 算A/D
TCP/IP网络重复型服务器通信软件的设计
庄 文 祥
(中国工商银行惠安县支行, 惠安 362100)
摘要:本文介绍一种新型的基于消息队列的重复型服务器通信软件的设计方法,不同于并
发型服务器和一般的重复型服务器通信软件,这种新的软件具有生成的子进程数少的优
点,并且容易对客户机与服务器的连接进行管理,适用于客户机数量较多和随机数据通
信的情况,能够有效地提高服务器的运行效率。
关键词:TCP/IP网络 重复型服务器通信软件 套接字 连接 共享内存 消息队列
1 并发服务器与重复服务器的区别
一般TCP/IP服务器通信软件都是并发型的,即是由一个守护进程负责监听客户机的
连接请求,然后再由守护进程生成一个或多个子进程与客户机具体建立连接以完成通信,
其缺点是随着连接的客户机数量的增多,生成的通信子进程数量会越来越多,在客户机
数量较多的应用场合势必影响服务器的运行效率。一般的重复服务器指的是服务器在接
收客户机的连接请求后即与之建立连接,然后要在处理完与客户机的通信任务后才能再
去接收另一客户机的请求连接,其优点是不必生成通信子进程,缺点是客户机在每次通
信之前都要与服务器建立连接,开销过大,不能用于随机的数据通信和繁忙的业务处理。
本文提出的新型的重复型服务器不同于一般的重复服务器,它摒弃了上述两类服务器的
缺点综合其优点,该服务器通信软件具有一般重复服务器的特征但又能处理客户机的随
机访问,在客户机数量多且业务繁忙的应用场合将发挥其优势。重复型服务器通信软件
只用三个进程就可完成与所有客户机建立连接,并始终保持这些连接。
2 重复型服务器通信软件与客户机建立连接的方法
2.1 基本思路
当第一台客户机向服务器请求连接时,服务器的守护进程与之建立初始连接(L0),客户
机利用L0向服务器发送两个端口号,守护进程将客户机的IP地址和端口号登记在共享内
存的记录中,然后关闭L0。由守护进程生成的两个通信子进程从共享内存中获得客户机
IP地址及端口号后,分别向客户机请求连接,建立一个从客户机读的连接(L1)和一个往
客户机写的连接(L2),并将两个连接的套接字的句柄记录在共享内存中。当另一台客户
机请求连接时,守护进程不再生成通信子进程,只是将客户机IP地址和端口号同样登记
在共享内存中。通信子进程在一个大循环中先查询共享内存中是否有新的记录,如果有
则与这一台客户机建立连接,然后轮询所有已建立的连接的读套接字,查看是否有数据
可读,有则读取数据,同时标明该数据是从共享内存中的哪条记录上的读套接字中获得
的,再由另一个通信子进程根据这个记录的编号从共享内存中获得对应的写套接字,最
后将结果数据往该套接字写往客户机。
2.2 建立连接
⑴ 服务器通信软件的初始进程首先建立公用端口上的套接字,并在该套接字上建立监听
队列,同时生成一个守护进程(Daemon)tcp_s,然后初始进程就退出运行。守护进程在函
数accept处堵塞住直到有客户机的连接请求,一有连接请求即调用server函数处理,然
后继续循环等待另一台客户机的请求。因为TCP/IP在连接被拆除后为了避免出现重复连
接的现象,一般是将连接放在过时连接表中,连接在拆除后若要避免处于TIME_WAIT状
态(过时连接),可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置
为0。服务器在/etc/services文件中要登记一个全局公认的公用端口号:
tcp_server 2000/tcp。
struct servent *sp;
struct sockaddr_in peeraddr_in,myaddr_in;
linkf=0;
sp=getservbyname("tcp_server","tcp");
ls=socket(AF_INET,SOCK_STREAM,0); /* 创建监听套接字 */
myaddr_in.sin_addr.s_addr=INADDR_ANY;
myaddr_in.sin_port=sp->s_port; /* 公用端口号 */
bind(ls,&myaddr_in,sizeof(struct sockaddr_in));
listen(ls,5);
qid3=msgget(MSGKEY3,0x1ff); /* 获得消息队列的标志号 */
qid4=msgget(MSGKEY4,0x1ff);
signal(SIGCLD,SIG_IGN); /* 避免子进程在退出后变为僵死进程 */
addrlen=sizeof(struct sockaddr_in);
lingerlen=sizeof(struct linger);
linger.l_onoff=1;
linger.l_linger=0;
setpgrp();
switch(fork()){ /* 生成Daemon */
case -1:exit(1);
case 0: /* Daemon */
for(;;){
s=accept(ls,&peeraddr_in,&addrlen);
setsockopt(s,SOL_SOCKET,SO_LINGER,&linger,lingerlen);
server();
close(s);
}
default:
fprintf(stderr,"初始进程退出,由守护进程监听客户机的连接请求./n");
}
⑵ 客户机以这样的形式运行通信程序tcp_c:tcp_c rhostname,rhostname为客户机所要
连接的服务器主机名。客户机上的/etc/services文件中也要登记:
tcp_server 2000/tcp,公用端口号2000要与服务器一样。
int qid1,qid2,s_c1,s_c2,cport1,cport2;
struct servent *sp;
struct hostent *hp;
memset((char *)&myaddr_in,0,sizeof(struct sockaddr_in));
memset((char *)&peeraddr_in,0,sizeof(struct sockaddr_in));
addrlen=sizeof(struct sockaddr_in);
sp=ge
void CMp3PlayerDlg::OnPlay()
{
m_Audio = NULL;
if(m_Audio == NULL)
{
m_Audio = MCIWndCreate(this->GetSafeHwnd(),
AfxGetInstanceHandle(),
WS_CHILD | WS_VISIBLE|MCIWNDF_NOMENU,m_Path);
m_Status.SetWindowText(_T("Playing..."));
}
else
{
MCIWndHome(m_Audio);
}
MCIWndPlay(m_Audio);
Pause = FALSE;
m_Play.EnableWindow(TRUE);
}
C++代码优化方法总结
作者: cppbug
[email protected]
发表日期:2004年8月15日
转自:
摘要
优化是一个非常大的主题,本文并不是去深入探讨性能分析理论,算法的效率,况且我也没有这个能力。我只是想把一些可以简单的应用到你的C++代码中的优化技术总结在这里,这样,当你遇到几种不同的编程策略的时候,就可以对每种策略的性能进行一个大概的估计。这也是本文的目的之所在。
关键字
代码优化
文章正文
一. 优化之前
在进行优化之前,我们首先应该做的是发现我们代码的瓶颈(bottleneck)在哪里。然而当你做这件事情的时候切忌从一个debug-version进行推断,因为debug-version中包含了许多额外的代码。一个debug-version可执行体要比release-version大出40%。那些额外的代码都是用来支持调试的,比如说符号的查找。大多数实现都为debug-version和release-version提供了不同的operator new以及库函数。而且,一个release-version的执行体可能已经通过多种途径进行了优化,包括不必要的临时对象的消除,循环展开,把对象移入寄存器,内联等等。
另外,我们要把调试和优化区分开来,它们是在完成不同的任务。 debug-version 是用来追捕bugs以及检查程序是否有逻辑上的问题。release-version则是用来做一些性能上的调整以及进行优化。
下面就让我们来看看有哪些代码优化技术吧:
二. 声明的放置
程序中变量和对象的声明放在什么位置将会对性能产生显著影响。同样,对postfix和prefix运算符的选择也会影响性能。这一部分我们集中讨论四个问题:初始化v.s 赋值,在程序确实要使用的地方放置声明,构造函数的初始化列表,prefix v.s postfix运算符。
(1) 请使用初始化而不是赋值
在C语言中只允许在一个函数体的开头进行变量的声明,然而在C++中声明可以出现在程序的任何位置。这样做的目的是希望把对象的声明拖延到确实要使用它的时候再进行。这样做可以有两个好处:1. 确保了对象在它被使用前不会被程序的其他部分恶意修改。如果对象在开头就被声明然而却在20行以后才被使用的话,就不能做这样的保证。2. 使我们有机会通过用初始化取代赋值来达到性能的提升,从前声明只能放在开头,然而往往开始的时候我们还没有获得我们想要的值,因此初始化所带来的好处就无法被应用。但是现在我们可以在我们获得了想要的值的时候直接进行初始化,从而省去了一步。注意,或许对于基本类型来说,初始化和赋值之间可能不会有什么差异,但是对于用户定义的类型来说,二者就会带来显著的不同,因为赋值会多进行一次函数调用----operator =。因此当我们在赋值和初始化之间进行选择的话,初始化应该是我们的首选。
(2) 把声明放在合适的位置上
在一些场合,通过移动声明到合适的位置所带来的性能提升应该引起我们足够的重视。例如:
bool is_C_Needed();
void use()
{
C c1;
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
//use c1 here
return;
}
上面这段代码中对象c1即使在有可能不使用它的情况下也会被创建,这样我们就会为它付出不必要的花费,有可能你会说一个对象c1能浪费多少时间,但是如果是这种情况呢:C c1[1000];我想就不是说浪费就浪费了。但是我们可以通过移动声明c1的位置来改变这种情况:
void use()
{
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
C c1; //moved from the block’s beginning
//use c1 here
return;
}
怎么样,程序的性能是不是已经得到很大的改善了呢?因此请仔细分析你的代码,把声明放在合适的位置上,它所带来的好处是你难以想象的。
(3) 初始化列表
我们都知道,初始化列表一般是用来初始化const或者reference数据成员。但是由于他自身的性质,我们可以通过使用初始化列表来实现性能的提升。我们先来看一段程序:
class Person
{
private:
C c_1;
C c_2;
public:
Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {}
};
当然构造函数我们也可以这样写:
Person::Person(const C& c1, const C& c2)
{
c_1 = c1;
c_2 = c2;
}
那么究竟二者会带来什么样的性能差异呢,要想搞清楚这个问题,我们首先要搞清楚二者是如何执行的,先来看初始化列表:数据成员的声明操作都是在构造函数执行之前就完成了,在构造函数中往往完成的只是赋值操作,然而初始化列表直接是在数据成员声明的时候就进行了初始化,因此它只执行了一次copy constructor。再来看在构造函数中赋值的情况:首先,在构造函数执行前会通过default constructor创建数据成员,然后在构造函数中通过operator =进行赋值。因此它就比初始化列表多进行了一次函数调用。性能差异就出来了。但是请注意,如果你的数据成员都是基本类型的话,那么为了程序的可读性就不要使用初始化列表了,因为编译器对两者产生的汇编代码是相同的。
(4) postfix VS prefix 运算符
prefix运算符++和—比它的postfix版本效率更高,因为当postfix运算符被使用的时候,会需要一个临时
[转贴]托盘编程全接触
作者:acute
2004-08-10
浏览次数 818次
托盘编程全接触
作者:acute
托盘编程
一、托盘简介
所谓的“托盘”,在Windows系统界面中,指的就是下面任务条右侧,有系统时间等等的标志的那一部分。在程序最小化或挂起时,但有不希望占据任务栏的时候,就可以把程序放到托盘区。其实,托盘区的编程很简单,下面简要阐述一下子喽
二、托盘编程相关函数
其实呢,把程序放到托盘上的本质就是先在托盘区绘制一个图标,然后把程序隐藏不见,再对托盘的图标进行消息处理,就可以了。
绘制图标以及确定图标所传送消息的函数只有一个,那就是——————
WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
DWORD dwMessage,
PNOTIFYICONDATA pnid
);
这个函数呢,负责向系统传递消息,以添加、修改或删除托盘区的图标。她的返回值呢,是个布尔类型的。就是说,如果返回0,那就是成仁啦,非0才成功。
参数dwMessage 是表示这个函数的应用功能是哪一方面,是添加、删除,还是修改图标。如果是添加,则它的值为NIM_ADD;删除则是NIM_DELETE;而修改是NIM_MODIFY。参数pnid就是具体的和程序在托盘区的图标有关系的结构了。它的定义如下:
typedef struct _NOTIFYICONDATA {
DWORD cbSize;
HWND hWnd;
UINT uID;
UINT uFlags;
UINT uCallbackMessage;
HICON hIcon;
char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;
下面就对该结构各个参数进行刨析:
cbSize : 结构的长度,用“位”来做单位。一般在程序中,我们用(DWORD)sizeof(NOTIFYICONDATA) 给它赋值。
HWnd : 一个句柄,如果对托盘中的图标进行操作,相应的消息就传给这个句柄所代表的窗口。自然了,大多数情况下是this->m_hWnd喽。
uID : 在工程中定义的图标ID
uFlags : 这个成员标志着其他哪些成员的数据是有效的,分别为NIF_ICON, NIF_MESSAGE, NIF_TIP,分别代表着数据有效的成员是hIcon, uCallbackMessage, szTip。当然,三个值可以用“|”联系到一起。下面分别对涉及到的成员进行阐述
hIcon : 要增加,删除或修改的图标句柄。如果只知道个uID, 一般可能会用函数LoadIcon来得到句柄。例如LoadIcon ( AfxGetInstanceHandle() ,MAKEINTRESOURCE (IDR_MAINFRAME) )。
uCallbackMessage : 这在对托盘区的操作中,是比较重要的数据成员。这是个消息标志,当用鼠标对托盘区相应图标进行操作的时候,就会传递消息给Hwnd所代表的窗口。所以说,在uFlags中,一般都得标志它有效。这里一般都是自定义的消息。
szTip : 鼠标移动到托盘图标上时的提示文字。
三、托盘编程例子
有关托盘编程的基础知识呢,也就上面这些了。下面呢,我们就进入具体的实战演练阶段,举几个托盘编程的例子瞧瞧,加深理解。
1、将程序最小化到系统托盘区的函数toTray()。
void CTimeWakeDlg::toTray()
{
NOTIFYICONDATA nid;
nid.cbSize=(DWORD)sizeof(NOTIFYICONDATA);
nid.hWnd=this->m_hWnd;
nid.uID=IDR_MAINFRAME;
nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP ;
nid.uCallbackMessage=WM_SHOWTASK;//自定义的消息名称
nid.hIcon=LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDR_MAINFRAME));
strcpy(nid.szTip,"计划任务提醒");//信息提示条为“计划任务提醒”
Shell_NotifyIcon(NIM_ADD,&nid);//在托盘区添加图标
ShowWindow(SW_HIDE);//隐藏主窗口
}
这是个很简单的函数,里面首先给NOTIFYICONDATA赋值,然后调用shell_NotifyIcon, 头一个参数是NIM_ADD,表示添加。然后用函数ShowWindow 隐藏主窗口,这样,就实现了将程序最小化到系统托盘区的任务了。
2、程序已经最小化到托盘区了,但是呢,对托盘图标的操作如何进行呢?这就体现了结构NOTIFYICONDATA的成员uCallbackMessage 的作用了。它所提供的作用就是,当用户用鼠标点击托盘区的图标的时候(无论是左键还是右键),会向hWnd所代表的窗口传送消息,如果是上例,消息的名称就是WM_SHOWTASK。根据VC的消息机制,对自定义消息增加消息响应函数。
在头文件的//{{AFX_MSG和//}}AFX_MSG之间声明消息响应函数:
afx_msg LRESULT onShowTask(WPARAM wParam,LPARAM lParam);
然后在CPP文件中添加消息映射。在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP 之间加入:
ON_MESSAGE(WM_SHOWTASK,onShowTask)将消息和消息响应函数映射起来。
然后就是在CPP文件中加入函数onShowTask的实现了:
LRESULT CTimeWakeDlg:nShowTask(WPARAM wParam,LPARAM lParam)
//wParam接收的是图标的ID,而lParam接收的是鼠标的行为
{
if(wParam!=IDR_MAINFRAME)
return 1;
switch(lParam)
{
case WM_RBUTTONUP://右键起来时弹出快捷菜单,这里只有一个“关闭”
{
LPPOINT lpoint=new tagPOINT;
::GetCursorPos(lpoint);//得到鼠标位置
CMenu menu;
menu.CreatePopupMenu();//声明一个弹出式菜单
//增加菜单项“关闭”,点击则发
void MediaPlayerDlg::Play(CString psing)
{
extname=getExt(psing);
if(extname=="jpg"|extname=="bmp"|extname=="gif")
{
pApp->OpenDocumentFile(psing);
playing=TRUE;
}
else
{
m_pParent->ShowWindow(SW_HIDE);
mciopenparms.lpstrElementName=psing;
mciopenparms.lpstrDeviceType=NULL;
mciSendCommand(0,MCI_OPEN,MCI_DEVTYPE_WAVEFORM_AUDIO,(DWORD)(LPVOID)&mciopenparms);
m_count=mciopenparms.wDeviceID;
mciplayparms.dwCallback=(DWORD)GetSafeHwnd();
cdlen=getinfo(MCI_STATUS_LENGTH);
if(playing==TRUE)cdfrom=MCI_MAKE_HMS(0,0,0);
cdto=MCI_MAKE_HMS(MCI_HMS_HOUR(cdlen),MCI_HMS_MINUTE(cdlen),MCI_HMS_SECOND(cdlen));
mciplayparms.dwFrom=cdfrom;
mciplayparms.dwTo=cdto;
mciSendCommand(m_count,MCI_PLAY,MCI_TO|MCI_FROM,(DWORD)(LPVOID)& mciplayparms);
SetTimer(0,1000,NULL);
m_slider.SetRange(0,cdlen);
playing=TRUE;
//计算歌曲播放时间
second=cdlen/1000;
minite=second/60;
second=second%60;
singtime.Format("%02d:%02d",minite,second);
}
}
DWORD MediaPlayerDlg::getinfo(DWORD item)
{
mcistatusparms.dwCallback=(DWORD)GetSafeHwnd();
mcistatusparms.dwItem=item;
mcistatusparms.dwReturn=0;
mciSendCommand(m_count,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)&mcistatusparms);
return mcistatusparms.dwReturn;
}
void MediaPlayerDlg::OnButtonpause()
{
// TODO: Add your control notification handler code here
KillTimer(0);
DWORD dwsf=getinfo(MCI_STATUS_POSITION);
cdfrom=MCI_MAKE_MSF(MCI_MSF_MINUTE(dwsf),MCI_MSF_SECOND(dwsf),MCI_MSF_FRAME(dwsf));
mciSendCommand(m_count,MCI_CLOSE,0,NULL);
m_count=0;
playing=FALSE;
}
创建适用于多种容器的控件
作 者 : Raymond Cirincione著 阿良译
即使是一个符合OLE标准的控件,在不同的ActiveX容器里其行为也会偶尔不同。不能成功地适应容器之间的差别将严重影响控件在某些容器内的应用,甚至导致控件完全无法在个别容器使用。
本文讨论使用Visual C++创建控件时如何适应容器相关的需求,特别是为大范围内使用而开发ActiveX控件时必须执行的策略。例如,如何解决诸如许可、线程、内容检验、键盘事件响应等问题。
一、关于ActiveX控件
在具体讨论容器之间的差别前(这种差别使得为多种容器开发ActiveX控件复杂化),有必要回顾一下何谓ActiveX控件以及它的创建过程。
ActiveX控件可以看成是实现了标准OLE接口的COM对象。所有的控件都必须最终定位于某种容器,如Visual Basic、Visual C++、IE浏览器。容器使用标准的OLE接口和控件协商。例如,容器可以创建、定制、存储控件以便以后使用。容器和ActiveX控件之间的所有交互都通过标准的OLE接口进行,由此,ActiveX控件追随了“黑盒”这一思想。控件的用户除了需要了解它的外部接口外,并不需要知道它的内部工作过程。只要开发工具(容器)以及编程语言理解并使用标准的OLE接口,就可以在多种容器中使用ActiveX控件。当然,这仅仅是理论;在实践中,没有两种容器是相同的,开发者必须把握它们之间的不同之处。
创建ActiveX控件开始于选择开发工具。可供选择的工具很多,从VB到Delphi到VJ++。本文由VC++为出发点讨论控件创建。使用VC++可以获得更快的执行速度和对创建过程更多的控制,以及最大范围的平台SDK和API支持。VC++提供了MFC ActiveX控件向导来简化ActiveX控件的创建。这个向导引导您通过创建控件外壳的每一步。向导提出的第一个问题是是否需要许可。
二、许可控件
控件操作有两个不同的环境:运行时和设计时。一个需要许可证的控件包含几个接口用于设计时限制某些访问。缺乏适当许可的用户只能在运行环境下使用该控件,而不能在设计环境下使用它。如果打算在企业内部、Internet、本地Intranet上使用控件,一般会避免使用许可证。然而,如果是出售商业产品或打算限制设计时访问控件的能力,就应该利用许可所带来的优点。
如果选择许可某个控件,控件向导就自动加入了必要的接口并创建可定制的许可文件(LIC)。剩下必须做的工作只是修改主文档(如myprojectCTL.CPP)中几个变量。请修改许可文件的内容使之符合许可证键:
static const TCHAR BASED_CODE
_szLicFileName[] = _T("control.lic");
static const WCHAR BASED_CODE
_szLicString[] ="My Unique Validation String";
在许可文件可用之后,开发工具经常在工程内缓冲控件的许可证键。如果许可文件本身不再可用,应用程序就使用缓冲的许可证键验证控件。在桌面环境下这是可行的,但在Internet(和Intranet)环境下并没有内建的机制以通过HTML安全地缓冲这个许可信息。
有两种方法解决这个问题。第一,可以使用Microsoft的一个叫LPK_Tool.exe的工具,它是Microsoft Internet Client SDK的一部份。LPK_Tool.exe能够将许可文件转换为可在HTML文档内引用的加密文件。IE能够在实例化一个需要许可证的控件时从LPK文件提取许可信息:
第二个办法需要定制控件的许可验证例程。例如,它可以询问容器自己正处于设计模式还是运行模式。控件所继承的类(COleControl)包含成员函数AmbientUserMode,此函数在控件处于设计模式时返回TRUE。
然而,并非所有容器响应此查询(包括IE浏览器)。此时AmbientUserMode总是返回TRUE;换句话说,它总是假定控件是在设计模式下。如果容器错误地响应查询,可以写一个函数强制控件认为自己处于运行模式,这样就可以避免这个限制了:
BOOL CCtrl::OptimisticAmbientUserMode(){
BOOL bUserMode;
if (!GetAmbientProperty(
DISPID_AMBIENT_USERMODE,
VT_BOOL, &bUserMode))
bUserMode = TRUE;
//如果容器没有回答则假定为运行模式
return bUserMode;}
三、线程模型和资源共享
Microsoft的两种线程模型,单线程和单元模型,同样使得在多种容器内使用控件复杂化。单线程控件在单一线程内执行所有对象;单元线程控件可在任何时候任何线程内执行一个对象。
某些情况下可能需要将特定资源全局化以便控件的所有实例访问。例如,如果控件的多个实例执行许多数据库操作,此时需要为所有实例创建单一的、共享的数据库连接,而不是为每个实例单独创建连接(其它的情况还包括只有一个可用资源的情形,例如设备上下文或端口)。
在单元模型线程环境下共享资源时有一个重大问题需要解决。例如,两个线程能够同时尝试使用同一个资源。这可能导致数据错误或其它非预期的结果。那么,容器如何才能知道控件是单元模型线程安全的?在类工厂(类对象)调用UpdateRegistry期间控件写入数据
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
HDC hdc = CreateDC( "DISPLAY", NULL, NULL, NULL );
cs.cx = GetDeviceCaps( hdc, HORZRES );//GetSystemMetrice( SM_CXSCREEN );
cs.cy = GetDeviceCaps( hdc, VERTRES ) - 27;//GetSystemMetrice( SM_CYSCREEN );
cs.y = 0;
cs.x = 0;
cs.style &= ~FWS_ADDTOTITLE;
cs.lpszName = "全国交通咨询系统";
return TRUE;
}
计时器是不依赖CPU的时钟速度的. 注意的是因为Windows并不是实时的操作系统,所以,如果你指定的周期小于100毫秒的话,
计时器事件之间的周期可能不精确.有了计时器,有时可以替代多线程情况, 例如下面的代码就允许在循环内仍然接收处理消息. 这是一个进度条,
在OnTimer里面改动进度条的显示, 同时可以自定义CANCEL消息, 在OnCancel中将程序终止.
Void CDlg::OnStart()
{
MSG message;
SetTimer(0,100,NULL);
GetDlgItem(IDC_START)->EnableWindow(FALSE); // 使按钮无效
Volatile int nTemp; //使变更不保存在寄存器中, 因为变量如果保存在寄存器中, 在线程的切换过程中可能会出现值的错误.
For (m_nCount=0;m_nCount<nMaxCount;m_nCount++){
For (nTemp=0;nTemp<10000;nTemp++){
………
}
if (::PeekMessage(&message,NULL,0,0,PM_REMOVE)){
::TranslateMessage(&message);anslateMessage(&message);
::DispatchMessage(&message);
}
}
CDlg::OnOK(); // 线程结束后关闭对话框
}
令Win32应用程序跳入系统零层
东南大学卢威(
[email protected])
周昊理(
[email protected])
--- 众 所 周 知, 在Windows95/98 的Win32 on Intel x86 体 系 中 利 用 了 处 理 器 的 三 环 保 护 模 型 中 的 零 环(Ring0, 最 高 权 限 级 别) 和 三 环(Ring3, 最 低 权 限 级 别)。 一 般 应 用 程 序 都 运 行 在Ring3 下, 受 到 严 格 的" 保 护", 只 能 规 矩 地 使 用Win32API。 如 果 我 们 想 进 行 一 些 系 统 级 的 操 作, 例 如 在 嵌 入 汇 编 中 使 用 诸 如"Mov EAX,CR0", 或 像 在DOS 下 那 样 调 用 一 些 必 不 可 少 的 系 统 服 务( 如BIOS,DPMI 服 务) 而 用"Int xx", 都 会 导 致" 非 法 操 作"。 但 这 种 能 力 有 时 是 必 不 可 少 的, 一 到 这 种 时 候Microsoft 就 " 建 议 编 写 一 个VxD"。VxD 大 家 早 有 所 闻 了, 在VxD 里, 不 但 可 以 执 行CPU 的 所 有 指 令, 而 且 可 以 调 用VMM( 虚 拟 机 管 理 器) 和 其 他VxD 提 供 的 上 千 个 系 统 级 服 务。 获 得 这 一 能 力 的 最 本 质 原 因 在 于 它 运 行 在Ring0, 与 系 统 内 核 同 一 级 别。 但 是 它 体 系 的 复 杂 性、 开 发 工 具 的 不 易 获 得、 帮 助 文 档 的 不 完 备, 使Microsoft 排 除 了 一 大 批 程 序 员 和 竞 争 对 手。 而 将 在Windows2000(Windows98 也 开 始 支 持) 中 取 代VxD 的WDM 对Win95 程 序 员 也 是 个 噩 梦, 它 需 要 了 解Windows NT 核 心 驱 动 模 型。
----有 没 有 简 单 一 些 的 办 法 呢 ? 我 们 可 以 令 一 个 普 通Win32 应 用 程 序 运 行 在Ring0 下, 从 而 获 得VxD 的 能 力 吗 ? 答 案 是 肯 定 的。 下 面 我 们 就 简 述 一 下 这 一 技 巧, 有 关Intel x86 保 护 模 式 的 基 础 知 识 请 大 家 看 有 关 书 籍。
----首 先 此 技 巧 基 于 以 下 理 论 根 据:
----一、SIDT 指 令( 将 中 断 描 述 符 表 寄 存 器 IDTR - -64 位 宽,16 ~47Bit 存 有 中 断 描 述 符 表IDT 基 地 址 - - 的 内 容 存 入 指 定 地 址 单 元) 不 是 特 权 指 令, 就 是 说 我 们 可 以 在Ring3 下 执 行 该 指 令, 获 得IDT 的 基 地 址, 从 而 修 改IDT, 增 加 一 个 中 断 门 安 置 我 们 的 中 断 服 务, 一 旦Ring3 程 序 中 产 生 此 中 断,VMM 就 会 调 用 此 中 断 服 务 程 序, 而 此 中 断 服 务 程 序 就 运 行 在Ring0 下 了。 这 一 点 与 在DOS 下 非 常 相 似。
----二、Windows95 Win32 应 用 程 序 运 行 一 个 映 射 到 全 部4G 内 存 的 段 中, 选 择 子 为0137h,Ring0 中 的VxD 运 行 在 另 一 个 映 射 到 全 部4G 内 存 的 段 中, 选 择 子028h, 这 两 个 段 除 了 选 择 子 决 定 的 访 问 权 限 不 同 外, 没 什 么 不 同, 各 自 段 中 相 同 的 偏 移 量 对 应 了 相 同 的 线 性 地 址。 所 以 我 们 放 在Win32 应 用 程 序 中 的 中 断 服 务 程 序 可 以 以Ring3 的 段 偏 移 量 被Ring0 中 的VMM 寻 址。
----下 面 我 们 以 具 体 例 子 进 一 步 说 明, 程 序 中 有 详 细 注 释。
----这 是 一 个Win32 Console Program( 控 制 台 应 用 程 序), 虽 然 运 行 中 看 起 来 很 像DOS 筐 中 运 行 的 实 模 式DOS 程 序, 但 它 是 货 真 价 实 的 运 行 在Ring3 下 的Win32 程 序。 用Visual C + + 5.0 AppWizard 创 建 一 个Win32 Console Program 项 目, 添 加 以 下.CPP 文 件, 编 译 即 可。
#include
#include
#include
#include
// 若 无DDK 带 下 划 线 的 可 略 去,
这 些 语 句 演 示 了 调 用VMM/VXD 服 务
DWORDLONG IDTR,SavedGate;
WORD OurGate[4]={0,0x0028,0xee00,0x0000};
// 中 断 门 描 述 符 格 式 如 下:
DWORD _eax,_ecx,_cr0;
WORD vmmver;
HVM sysvm;
void nothing()
{
//Used to test call in Ring0
sysvm=Get_Sys_VM_Handle();
}
void __declspec( naked ) Ring0Proc(void)
// 中 断 例 程, 运 行 在Ring0
{
_asm{
mov _eax,eax //
mov _ecx,ecx //
mov eax, CR0
// 测 试Ring3 中 不 能 执 行 的 特 权 指 令
mov _cr0,eax //
}
VMMCall(Get_VMM_Version);
// 调 用VMM 服 务
_asm{
mov vmmver,ax
}
nothing();
// 测 试 在 运 行 于Ring0 的
中 断 例 程 中 调 用 子
_asm iretd
// 中 断 返 回, 与 在 实 模 式
编 程 无 本 质 区 别
}
void main() // 主 程 序
{
_asm{
mov eax, offset Ring0Proc
mov [OurGate], ax // 将 中 断 函 数 的 地 址
shr eax, 16 // 填 入 新 造 的 中 断 门
mov [OurGate +6], ax // 描 述 符
sidt fword ptr IDTR
// 将 中 断 描 述 符 表 寄 存 器
内存分配函数
malloc
在内存中分配一块指定大小的空间, 返回的类型为void *型, 可以强制转换为其他类型, 如果内存空间已经不足, 则会返回NULL. 注意: 实际分配的空间可能大于指定的大小, 因为内存块还需要保存队列或其他相关的信息.
free
释放由malloc,calloc,realloc所分配的内存空间, 如果释放不是由这些函数所分配的内存空间会发生错误.
new
为变量初始化内存空间, 例如:
double *pdoub = new double(20.4);
delete
与new对应,释放由new分配的变量所拥有的内存空间.
HeapAlloc, HeapFree
GlobalAlloc
此函数从堆里面分配一个指定大小的内存空间, 此函数仅仅是为了与16位版本兼容而设.
uFlags: 分配的内存属性, 它可以有以下几种属性值:
如果此参数为0,缺省为GMEM_FIXED. 它分配一个固定的内存空间, 返回的值为一个指针
GMEM_MOVEABLE分配一个活动的内存, 在WIN32里,内存块在物理内存里是从不移动的,但它们可以在缺省的堆里面移动. 此参数不能与GMEM_FIXED联合. 它的返回值是一个内存对象句柄,如果要把它转变为一个指针,需使用GlobalLock函数.
GPTR与GMEM_FIXED和GMEM_ZEROINIT联合
GHND与GMEM_MOVEABLE和GMEM_ZEROINIT联合
GMEM_DDESHARE, GMEM_SHARE, 这两个属性首先是为了与16位兼容的,然后使用它可以提高应用程序执行DDE操作的效率,所以如果内存被用于DDE, 可以指定它.
GMEM_DISCARDABLE, 被忽略,仅是为了与16位兼容.在WIN32里,你必须使用GlobalDiscard函数释放掉一个内存块.它不能与GMEM_FIXED联合.
GMEM_LOWER,GMEM_NOCOMPACT,GMEM_NODISCARD,GMEM_NOT_BANKED,GMEM_NOTIFY 被忽略,仅为了与16位兼容
GMEM_ZEROINIT, 将内存块初始化为0.
dwBytes: 内存块大小
如果置为0, 且uFlags置为GMEM_MOVEABLE的话, 函数将返回一个内存对话句柄, 此内存对象被标记为已丢弃.
如果函数调用失败, 将返回NULL, 得到错误信息,调用GetLastError
释放内存使用GlobalFree函数.
下面的代码示例使用一块内存:
HGLOBAL hMem;
char *pStartBuffer=NULL;
#define MAX_BUFFER_SIZE 102400
if ( (hMem = GlobalAlloc(GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, MAX_BUFFER_SIZE))==NULL )
return FALSE;
pStartBuffer = (char *)GlobalLock(hMem);
GlobalUnlock(hMem);
GlobalFree(hMem);
[提出问题]
最近我正在开发一个程序,任务是从CD-ROM上读取文件,如视频和音频文件。因为每一台机器上的情况都不一样。如何知道CD-ROM驱动器呢?
[解答]
首先,一台机器可能有不止一个CD-ROM驱动器。现在CD-ROM已经是PC机的标准配置,此外再装个可擦写的光盘驱动,甚至是DVD都是很常见的事情。不管一台机器上装多少个不同的驱动器,如何找到它们呢?
有关驱动器的函数有GetLogicalDrives,GetLogicalDriveStrings和GetDriveType。前两个用来获取逻辑驱动器盘符,GetLogicalDriveStrings返回路径名字符串,如:
"A:/<null>C:/<null>F:/<null><null>"
这里每一个路径名都由NULL(空或者零)字符分隔,最后结尾是两个空字符--这是标准的C风格处理方法。对于喜欢操作位和字节的汇编语言使用者来说,GetLogicalDrives是个很好用的API函数。它以位掩码的形式返回逻辑驱动器。即在一个DWORD类型的返回值中,位0(最小的一位)表示驱动器A,位1表示驱动器B,以此类推。每一个位的状态如果是on,则表示对应的逻辑驱动器存在;否则状态为off,表示对应的逻辑驱动器不存在。大家知道DWORD是一个32位的值,足以包括所有的英文字母,也就是说最多可有26个盘符。
为了确定某个逻辑驱动器的类型,必须调用GetDriveType函数。它以路径名作为参数(如C:/),返回DRIVE_FIXED,DRIVE_REMOVABLE,或DRIVE_UNKNOWN。下面列出了所有可能返回的值:这些值在winbase.h定义
#define DRIVE_UNKNOWN 0 // 无效路径名
#define DRIVE_NO_ROOT_DIR 1 // 无效路经,如无法找到的卷标
#define DRIVE_REMOVABLE 2 // 可移动驱动器(如磁盘驱动器,光驱等)
#define DRIVE_FIXED 3 // 固定的驱动器 (如 通常的硬盘)
#define DRIVE_REMOTE 4 // 网络驱动器
#define DRIVE_CDROM 5 // CD-ROM
#define DRIVE_RAMDISK 6 // 随机存取(RAM) 磁盘
为了更容易说明问题,我写了一个小程序--ListDrives,它可以列出某台机器上所有的逻辑驱动器。其实现代码如下: ListDrives.cpp
#include "stdafx.h"
#include "resource.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
using namespace std; // for string class
// 下面是一个GetDriveType返回码与人可读字符串的迷你对照表
//
struct {
UINT type; // GetDriveType返回码类型
LPCSTR name; // ascii 名称
} DriveTypeFlags [] = {
{ DRIVE_UNKNOWN, "未知" },
{ DRIVE_NO_ROOT_DIR, "无效路经" },
{ DRIVE_REMOVABLE, "可移动" },
{ DRIVE_FIXED, "固定" },
{ DRIVE_REMOTE, "网络驱动器" },
{ DRIVE_CDROM, "CD-ROM" },
{ DRIVE_RAMDISK, "随机存取磁盘" },
{ 0, NULL},
};
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) {
cerr << _T("Fatal Error: MFC initialization failed") << endl;
return -1;
}
// 获取逻辑驱动器字符串- a:/b:/c:/... 等.
// 还可以用GetLogicalDrives 以位图形式代替字符串形式获取信息
TCHAR buf[100];
DWORD len = GetLogicalDriveStrings(sizeof(buf)/sizeof(TCHAR),buf);
// 显示每个驱动器的信息
//
string msg = "Logical Drives:/n"; // STL string
for (TCHAR* s=buf; *s; s+=_tcslen(s)+1) {
LPCTSTR sDrivePath = s;
msg += sDrivePath;
msg += " ";
// GetDriveType 获取枚举值,如DRIVE_UNKNOWN等.
//
UINT uDriveType = GetDriveType(sDrivePath);
// 查找驱动器类型。在此我用了表(结构数组)来进行查找处理,过于繁琐了一些,
// 但既然uDriveType 的值是连续的。
// 我可以用DriveTypeFlags[uDriveType]来代替线性查找。在实际的编程中通常可以这么做:
// if (uDriveType & DEVICE_CDROM) {
……
// }
//
for (int i=0; DriveTypeFlags[i].name; i++) {
if (uDriveType == DriveTypeFlags[i].type) {
msg += DriveTypeFlags[i].name;
break;
}
}
msg += ’’’’’’’’’’’’’’’’/n’’’’’’’’’’’’’’’’;
}
cout << msg.c_str();
return 0;
}
程序代码很简单,它是一个MFC程序。用GetLogicalDriveStrings获取所有逻辑驱动器的根路径名,然后调用GetDriveType来确定每个驱动器的类型。如果你要找CD-ROM,则检查uDriveType = DRIVE_CDROM就可以了。
在多文档界面下,自动生成一个新的子窗口,而一个实际的应用系统往往是由用户操作后再生成新的窗口。为了去掉开始的子窗口,可在应用程序文件分析命令行的语句
CcommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);后加入:
cmdInfo.m_nShellCommand=CcommandLineInfo::FileNothing;
去掉子窗口后,就只剩下主框架窗口了。因为在多文档界面中,系统生成两个菜单:一个是用户的菜单,另一个是系统主框架菜单。通常用户工作在用户菜单。为了保证菜单界面不变,可修改主框架菜单资源,使其与用户菜单保持一致。
DISPID dispid;
BSTR bstr=::SysAllocString(OLESTR("Sum"));
hr=pDisp->GetIDsOfNames(IID_NULL,&bstr,1,LOCALE_USER_DEFAULT,&dispid);
::SysFreeString(bstr);
这样就可以获得函数Sum的dispitch ID
void CControlDlg::OnRestartcomputer()
{
// TODO: Add your control notification handler code here
OSVERSIONINFO OsVersionInfo; //包含操作系统版本信息的数据结构
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&OsVersionInfo); //获取操作系统版本信息
if(OsVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
//Windows98,调用ExitWindowsEx()函数重新启动计算机
DWORD dwReserved;
ExitWindowsEx(EWX_REBOOT,dwReserved); //可以改变第一个参数,实现注销用户、关机、关闭电源等操作
// 退出前的一些处理程序
}
}
void CControlDlg::OnShutdowncomputer()
{
// TODO: Add your control notification handler code here
typedef int (CALLBACK *SHUTDOWNDLG)(int); //显示关机对话框函数的指针
HINSTANCE hInst = LoadLibrary("shell32.dll"); //装入shell32.dll
SHUTDOWNDLG ShutDownDialog; //指向shell32.dll库中显示关机对话框函数的指针
if(hInst != NULL)
{
//获得函数的地址并调用之
ShutDownDialog = (SHUTDOWNDLG)GetProcAddress(hInst,(LPSTR)60);
(*ShutDownDialog)(0);
}
}
const的思考
1、什么是const?
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)
2、为什么引入const?
const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
3、cons有什么主要的作用?
(1)可以定义const常量,具有不可变性。
例如:
const int Max=100;
int Array[Max];
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如:
void f(const int i) { .........}
编译器就会知道i是一个常量,不允许修改;
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
还是上面的例子,如果在函数体内修改了i,编译器就会报错;
例如:
void f(const int i) { i=10;//error! }
(5) 为函数重载提供了一个参考。
class A
{
......
void f(int i) {......} file://一/个函数
void f(int i) const {......} file://上/一个函数的重载
......
};
(6) 可以节省空间,避免不必要的内存分配。
例如:
#define PI 3.14159 file://常/量宏
const doulbe Pi=3.14159; file://此/时并未将Pi放入ROM中
......
double i=Pi; file://此/时为Pi分配内存,以后不再分配!
double I=PI; file://编/译期间进行宏替换,分配内存
double j=Pi; file://没/有内存分配
double J=PI; file://再/进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
(7) 提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
3、如何使用const?
(1)修饰一般常量
一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。
例如:
int const x=2; 或 const int x=2;
(2)修饰常数组
定义或说明一个常数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};
const int a[5]={1, 2, 3, 4, 5};
(3)修饰常对象
常对象是指对象常量,定义格式如下:
class A;
const A a;
A const a;
定义常对象时,同样要进行初始化,并且该对象不能再被更新,修饰符const可以放在类名后面,也可以放在类名前面。
(4)修饰常指针
const int *A; file://const/修饰指向的对象,A可变,A指向的对象不可变
int const *A; file://const/修饰指向的对象,A可变,A指向的对象不可变
int *const A; file://const/修饰指针A, A不可变,A指向的对象可变
const int *const A; file://指/针A和A指向的对象都不可变
(5)修饰常引用
使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:
const double & v;
(6)修饰函数的常参数
const修饰符也可以修饰函数的传递参数,格式如下:
void Fun(const int Var);
告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无意的或错误的修改。
(7)修饰函数的返回值:
const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:
const int Fun1();
const MyClass Fun2();
(8)修饰类的成员函数:
const修饰符也可以修饰类的成员函数,格式如下:
class ClassName
{
public:
int Fun() const;
.....
};
这样,在调用函数Fun时就不能修改类里面的数据
(9)在另一连接文件中引用const常量
extern const int i; file://正/确的引用
extern const int j=10; file://错/误!常量不可以被再次赋值
另外,还要注意,常量必须初始化!
例如:
const int i=5;
4、几点值得讨论的地方:
(1)const究竟意味着什么?
说了这么多,你认为const意味着什么?一种修饰符?接口抽象?一种新类型?
也许都是,在Stroustup最初引入这个关键字时,只是为对象放入ROM做出了一种可能,对于const对象,C++既允许对其进行静态初始化,也允许对他进行动态初始化。理想的const对象应该在其构造函数完成之前都是可写的,在析够函数执行开始后也都是可写的,换句话说,const对象具有从构造函数完成到析够函数执行之前的不变性,如果违反了这条规则,结果都是未定义的!虽然我们把const放入ROM中,但这并不能够保证const的任何形式的堕落,我们后面会给出具体的办法。无论const对象被放入ROM中,还是通过存储保护机制加以保护,都只能保证,对于用户而言这个对象没有改变。换句话说,废料收集器(我们以后会详细讨论,这就一笔带过)
我在VC5中想实现窗口的全屏显视,方法是把窗口的客户区扩
大到整个屏幕,则整个窗口扩大到超过屏幕大小,菜单,工具
条,边框等等显视在屏幕外面。但实际上行不通,普通窗口在
Win98下最大只能拉到屏幕的分辨率大小,显然窗口的Client区
域就不能扩展到整个屏幕。
请哪位指点一下解决方法。(要求可以在全屏和正常之间切换)
多谢。
--
※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 202.114.21.36]
--
※ 转载:.碧海青天 bbs.dlut.edu.cn.[FROM: 202.118.70.52]
发信人: oldcat (老猫), 信区: C
标 题: Re: [转载] 请教 VC 中的窗口问题
发信站: 碧海青天 (Tue Oct 12 19:04:09 1999), 转信
【 在 smoke (若智) 的大作中提到: 】
: 【 以下文字转载自 TSH_VisualC 讨论区 】
: 【 原文由 cloudflow 所发表 】
: 我在VC5中想实现窗口的全屏显视,方法是把窗口的客户区扩
: 大到整个屏幕,则整个窗口扩大到超过屏幕大小,菜单,工具
: 条,边框等等显视在屏幕外面。但实际上行不通,普通窗口在
: Win98下最大只能拉到屏幕的分辨率大小,显然窗口的Client区
: 域就不能扩展到整个屏幕。
: 请哪位指点一下解决方法。(要求可以在全屏和正常之间切换)
: 多谢。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
全屏过程如下:
void CMainFrame::OnMax()
{
// TODO: Add your command handler code here
CRect rectclient,rectwindow;
GetClientRect(rectclient);
GetWindowRect(rectwindow);
dx=::GetSystemMetrics(SM_CXFULLSCREEN);
dy=dx*3/4;
m_oldclient.CopyRect(rectwindow);
ScreenToClient(&rectwindow);
rect.top=rectwindow.top-rectclient.top;
rect.left=rectwindow.left-rectclient.left ;
rect.bottom=dy+rectwindow.bottom-rectclient.bottom;
rect.right=dx+rectwindow.right-rectclient.right;
int width,height;
width=rect.right-rect.left;
height=rect.bottom-rect.top;
m_full=TRUE;
SetWindowPos(NULL,rect.left,rect.top,width,height,SWP_NOZORDER|SWP_FRAMECHANGED
);
}
void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
// TODO: Add your message handler code here and/or call default
if(m_full)
{
lpMMI->ptMaxPosition.x=rect.right-rect.left ;
lpMMI->ptMaxPosition.y=rect.bottom-rect.top ;
lpMMI->ptMaxTrackSize.x=rect.right-rect.left ;
lpMMI->ptMaxTrackSize.y=rect.bottom-rect.top ;
}
CFrameWnd::OnGetMinMaxInfo(lpMMI);
}
恢复时可用:
m_full=FALSE;
SetWindowPos(NULL,m_oldclient.left,m_oldclient.top,m_oldcilent.right,
m_oldclient.bottom,SWP_NOZORDER|SWP_RAMECHANGE);
注意:
应在构造函数中;
m_full=FALSE;
--
※ 来源:.碧海青天 bbs.dlut.edu.cn.[FROM: 202.118.69.226]
发信人: smoke (若智), 信区: C
标 题: Re: [转载] 请教 VC 中的窗口问题
发信站: 碧海青天 (Tue Oct 12 19:58:28 1999), 转信
你的全屏还保留工具条,我的更彻底。
void CMainFrame::OnMax()
{
GetWindowPlacement(&m_OldWndPlacement);
CRect WindowRect;
GetWindowRect(&WindowRect);
CRect ClientRect;
RepositionBars(0,0xffff,AFX_IDW_PANE_FIRST,reposQuery,&ClientRect);
ClientToScreen(&ClientRect);
int nFullWidth=GetSystemMetrics(SM_CXSCREEN);
int nFullHeight=GetSystemMetrics(SM_CYSCREEN);
m_FullScreenRect.left=WindowRect.left-ClientRect.left;
m_FullScreenRect.top=WindowRect.top-ClientRect.top;
m_FullScreenRect.right=WindowRect.right-Client
25.2定义一个样板双向链表类
尽管在上一节创建的链表类是十分有效的,但由于dblinkob定义的数据类型的限制,它
只能用来管理字符串链表。如果要存储其它类型的数据,就必须改变info的类型标识符,修
改几个函数,才能适应新的数据类型。当然,对每一个新数据类型作这些改变既容易出错又
单调乏味。一个更好的解决方法是使用模板建立样板链表类,这样可以自动处理任何数据类
型,这也恰恰是本节要做的事情。
创建一个样板双向链表类的优点是使实现机制(即链表维护算法)和实际存储在表中的
数据分离。因此,这个机制可以被创建一次并多次重复使用。
注:创建和使用样板类的基本知识在第二十章已进行了讨论。如果用户对关键字tem-
plate或样板类的用法不熟悉的话,还需在了解创建样板链表类之前阅读第二十章。
25.2.1样扳链表类
将dblinkob和dllist转换为样板类的第一步是将它们变成模板。当该过程完成后,在这
些类的对象被创建时,它们操作的数据类型作为参数传递。dblinkob和dllist样板类如下所
示:
template<class Datat> class dblinkob{
public:
DataT info;// information
dblinkob <DataT>*next;// pointer to next object
428页
dblinkob<DataT> *prior;//pointer to previous object
dblinkob() {
info=0;
next=NULL;
prior=NULL;
}
dblinkob(DataT c) {
info=c;
next=NULL;
prior=NULL;
}
dblinkob<DataT> *getnext() {return next;}
dblinkob<DataT> * getprior() { return prior;}
void getinfo(DataT &c){ c=info; }
void change(DataT c) {info=c; } // change an element
//Overload<<for object of type dblinkob.
friend ostream &operator<<(ostream&stream,dblinkob<DataT> o )
stream << o.info <<"/n";
return stream;
}
//overload<<for pointer to object of type dblinkob.
friend ostream&operator<<(ostream&stream,dblinkob <DataT> *o)
{
stream<<o->info<<"/n";
return stream;
}
//Overload>>for dblinkob references.
friend istream &Operator>>(istream&stream,dblinkob <DataT>&o)
{
cout<<"Enter information:";
stream>>o.info;
return stream;
}
};
template<class DataT> class dllist : public dblinkob<DataT> {
dblinkob<DataT> * start, * end;
public:
dllist() {start = end = NULL;}
void store(DataT c);
void remove(dblinkob <DataT> * ob);// delete entry
void frwdlist();// display the list from beginning
void bkwdlist();// display the list from the end
dblinkob <DataT>*find(DataT c);//return pointer to matching element
dblinkob <DataT>*getstart(){return start;}
dblinkob <DataT> * getend() {return end;}
};
429页
可以看到,样板数据类型称为DataT。它用作存储在dblinkob中数据所有引用的类型说
明符。当一个对象被创建时,这个类型由实际说明的类型替代。
例如,创建一个称为mylist的链表,它存储了unsigned long类型值,可以看到这样的说
明:
dllist <unsigned long>mylist;
这是dllist的一个具体实例,它能够存储无符号长整数。在dblinkob和dllist说明中,需特别
注意当dblinkob被dllist继承时样板类型DataT的处理方法。特别是,用于实例化dllist的数
据类型被传给了dblinkob。因此,在前面的说明中,数据类型unsigned long被传给dllist,当然
也被传给dblinkob。这就是说,在这种情况中,存储在dblinkob对象中的数据类型也将是un-
signed long。
为了创建其它的表类型,只需简单地改变一下数据类型说明。例如,这里创建了一个存
储字符指针的表:
dllist<char *>CharPtrlist;
25.2.2完整的样板双向链表类
下面示出了完整样板双问链表类和函数main()的示例。注意样板数据类型贯穿整个函
数定义的使用方法。可以看到,在所有情况中,表所操作的数据用样板DataT类型说明。直
至在main()中实际的表实例化之后,数据的具体属性才确定。
// A generic doubly linked list class.
#include<iostream. h>
#include <string. h>
#include<stdlib.h>
template <class DataT> class dblinkob{
public:
DataT info;//information
本书的最后一章讨论有关用类实现双向链表的问题。样板类是C++的最重要特性之
一,尤其是在专业程序设计环境中更是如此。尽管双向链表只是存储信息的方法之一,但在
用类管理双向链表时出现的问题和解决的办法,对其它的存储方法来说是有代表性的。
记住:样板类是用关键字template构成的。它所操作的数据类型在该类的每个对象实例
化时作为参数指定。
创建样板链表类需要用到C++中一些最先进和最抽象的特性。因此,本章将从实现一
个非样板双向链表类开始。首先是为每种具体类型的数据创建一个双向链表,即把表所含的
数据硬编码到管理链表的类里;链表的特定版本可以用来开发、说明、解释基本链表类的实
现机制。其次,将双向链表类修改为样板链表类,这样就可适用于任何数据类型。
25.1 简单双向链表类
双向链表是动态数据结构,在程序执行过程中可以改变长度。事实上,动态数据结构的
优点是在编译时大小无需固定,在运行时根据需要可自由扩展或缩小。在链中,每个对象都
包含指向前一个和下一个对象的链。在链表中插入或删除对象要对链重新进行维护。由于双
向链表是动态数据结构,所以链表中的每个对象通常都是动态分配的。本章讨论双向链表的
动态特性。
存储在双向链表中的每一项都包含三个部分:一个指向表中下一个元素的指针、一个指
向表中前一个元素的指针和存储在表中的数据信息。图25-1示出了一个双向链表。双向链表
可以存储任何数据类型,包括字符、整数结构、类和联合等。这里讨论的双向链表类只存储字
符串(为了便于描述),任何其它的数据类型同样可以存储。
双向链表用简单的类层次实现。称为dblinkob的类定义了存储在链表中的对象的属性。
这个类由另一个类dllist继承,实际实现了双向链表的管理。
图25-1 一个双向链表
info info info info
0 0 .
dblinkob类定义了链表每个元素的瞩性:
//This class defines each element in the list.
class dblinkob{
public:
char info;// information
dblinkob *next;// pointer to next Object
418页
dblinkob*prior;// pointer to previous objeCt
dblinkob(){
info=0;
next=NULL;
prior=NULL;
};
dblinkob(char c){
info=c;
neXt =NULL;
prior=NULL;
};
dblinkob *getnext() {return next;}
dblinkob *getprior() {return prior;}
void getinfo(char&c){c=info;};
void change(char c) { info=c;}// change an element
//Overload<<for object of type dblinkob.
friend ostream &operator<<(ostream&stream, dblinkob o)
{
stream<<o.info <<"\n";
return stream;
}
//Overload<<for pointer to object of type dblinkob.
friend ostream&operator<<(ostream&stream, dblinkob *o)
{
stream<<o->info<<"/n";
return stream;
}
//Overload>>for dblinkob references.
friend istream &Operator>>(istream &stream, dblinkob&o)
{
cout<<"Enter information:";
stream >> o.info;
return stream;
}
};
由此看出,dblinkob有三个数据成员。info成员容纳着存在表中的信息。记住,现在数据
作为一个字符的硬性编码,所以,链表只能容纳字符。next指向表中的下一个元素,prior指
向表中的前一个元素。注意,dblinkob的数据成员是公有的,这是为了说明问题并使链表的
所有方面便于阐述。当然,用户也可以在自己的程序中将它们说明为私有的或是受保护的。
在dblinkob中还定义了一组作用于dblinkob对象的操作。特别是,与对象有关的信息可
以被检索或修改,可以得到指向下一个或前一个元素的指针。同时,dblinkob类型的对象可
用重载运算符<<和>>进行输入或输出。记住,在dblinkob中定义的操作是独立于链表维
护机制本身的。dblinkob仅定义存在表中数据的特性。
数据成员info容纳表存储的字符串。如前所述,这个类是为管理字符串链表而专门设
计的。无论管理什么数据类型的链表,它们使用的机制都是相同的。指针next指向表中的下
一个元素,而指针prior指向表中的上一个元素。
419页
当每个对象构造好后,把指针prior和next域初始化为NULL,并保持空直到该对象放
入表中。如果包含了初始值,就把它拷贝到info中;否则,info初始化为0。
函数getnext()返回一个指向表中下一个元素的指针。如果已到达表的尾部,它的值为
空(NULL)。如果存在的话,则函数getprior()返回表中的上一个元素,否则返回NULL。由
于next和prior是公有的,故这些函数从技术上来说不是必要的。但当在自己的程序中将
next和prior定
403页
*/
int wintype::wingetche()
{
union inkey{
char ch[2];
int i;
}c;
union REGS r;
if(!active) return 0;// window not active
winxy(curx, cury);
r.h.ah=0;// read a key
c.i=int86(0x16,&r,&r);
if(c. ch[0]){
switch(c.ch[0]){
case’/r’:// the ENTER key is pressed
break;
case’/b’:// back space
break;
default:
if(curx+leftx<rightx-1){
write_char(leftx+curx + 1,
upy+cury + 1,c. ch[0], color);
curx++;
}
}
if(cury < 0) cury=0;
if(cury+upy > downy-2)
Cury--;
winxy(curx,cury);
}
return c.i;
}
这个函数调用BIOS中断16的功能0,它等待按键并返回一个16位的键盘码。键盘码分
为两部分:字符值和位置码。如果按下的键是字符键,字符值在低8位返回;如果按下的键是
没有字符代码的特殊键,如箭头键等,则低字节为0,高字节是位置码。例如上箭头和下箭头
的位置码分别为72和80。虽然没有窗口函数利用位置码,我们还是提供出来,或许用户的窗
口应用程序要存取字符和位置码。
通过测试这个函数可以发现,不会有按键回显到窗口的边界之外。函数用给窗口定义的
颜色回显按键,而且窗口必须是活动的。
函数wingets()从键盘读一个字符串,如下所示:
// Read a string from a window.
void wintype :: wingets(char * s)
{
char ch, * temp;
temp=s;
404页
for(;;){
ch=wingetche();
switch(ch){
case’/r’:// the ENTER key to pressed
*s=’/0’;
return;
case’/b’:// backspace
if(s>temp){
s--;
curx--;
if(curx<0) curx=0;
winxy(curx, cury);
write_char(leftx + curx + 1, upy+cury+1,’ ’, color);
}
break;
default:
*s=ch;
s++;
}
}
}
该函数调用wingetche()读入每个字符。由于wingetche()能防止按键回显到窗口以外,
所以wingets()也不会将其输入显示到窗口之外。
下面是一个用winputs()向窗口输出一个串的例子。
/* Write a string at the current cursor position
in the specified window.
Return 0 if window not active;
1 otherwise.
*/
int wintype::winputs(char * s)
{
register Int x, y;
char far*v;
// make sure window is active
if(!active) return 0;
x=curx+leftx+1;
y=cury+upy+1;
v=vid_mem;
v+=(y*160)+x*2;// compute starting address
for(; * s;s++){
if(y >= downy){
return 1;
}
if(x >= rightx){
return 1;
}
if(*s==’/n’){
405页
y++;
x=leftx+1;
v=vid_mem;
· v++(y*160)+x*2;// compute the address
cury++;// increment Y
curx=0;// reset x
}
else{
curx++;
x++;
*v++ = * s;// write the character
*v++=color;//color
}
winxy(curx, cury);
}
return 1;
}
该函数从当前光标位置(由curx和cury定义)开始用当前颜色输出指定的字符串,并
且不会向窗口之外的任何地方输出任何东西。
前面曾讲过,除了I/O成员函数以外,还可以用<< 输出一个串,用>> 输入一个串。如
下所示,这些重载运算符的操作是简单易懂的。
// Output to a window.
wintype &operator<<(wintype&o, char * s)
{
o. winputs(s);
return o;
}
//Input from a window.
wintype &operator>>(wintype&o, char * s)
{
o. wingets(s);
return o;
}
如果要输入输出其它类型的数据,只要再重载<< 和>> 即可。
三个附加的窗口输出函数是:wincls()——清除窗口,wincleol()——清除当前光标位置
到行尾,winxy()——把光标置到相对窗口x,y的位置。这些函数如下所示:
// Clear a w
弹出式窗口已成为大多数现代计算的最重要的特征之一。现在要编写一个商业应用程
序不用窗口是很难想象的,窗口概念已深入各个层面。弹出式窗口也提供了一个使用C++
进行面向对象程序设计的典型例子。因此,本章创建了一个基于文本的窗口类,并提供了支
持简单有效的窗口系统。
因为MicrosOft的Windows已成为主流PC操作系统,读者可能要问,再开发其它窗口
系统是否有具有实际价值?答案是“有”。原因有三:第一,本章中建立的系统是基于文本的
(不象Windows中的基于图象的窗口),专门设计运行在DOS下或是由Windows提供的仿
DOS环境中。这个系统很小但非常有效,使得基于文本的窗口化操作非常简单。第二,用户
能够完全控制自己创建的系统。随着硬件的不断升级,用户可以增强自己的窗口系统以利用
新的显示方式或设备,可以让它运行在新的操作环境和系统中。最后,本章的窗口系统直接
与PC的视频硬件接口。由此,本章阐明了C++既适合低级设备接口又支持高级程序设计。
用于与视频硬件的接口方法同样可以用来与其它类型的设备接口。
注:窗口系统在很大程度上依赖于设备。本章假设环境为PC系统,而且有多个函数直
接和ROMBIOS(基本I/O系统)接口。然而,只需修改几个函数,这个窗口系统就可以在任
何环境下工作。
首先,重要的是定义窗口系统要做些什么。
24.1 弹出式窗口
弹出式窗口是屏幕的一部分,它用于某个特殊的目的。当生成窗口时,系统保存屏幕上
的内容并显示这个窗口。当应用使用完这个窗口时,则清除这个窗口并恢复屏幕原来的内
容。同一时刻屏幕上可以有几个窗口。
窗口系统的一个重要特点是,不允许应用写到窗口的边界之外。由于不必让应用程序知
道窗口的尺寸,所以防止越界写是窗口程序自己的事,而不是应用程序的事。这样,所有c+
+的通用控制台I/O函数(如printf()和gets()等)以及cout和cin流都不能使用了,取而代
之的是可供选择的窗口专用I/O函数。事实上,窗口专用的I/O函数构成了任何窗口系统的
主要部分。
为便于理解窗口是如何被有效使用的,不妨假设我们编写了一个包括一些附加功能的
文本编辑器。附加功能之一是一个计算器。由于计算器功能不是文本编辑的一部分,所以不
管什么时侯激活计算器都要用弹出式窗口。因此,当使用计算器的时侯,文本编辑就被挂起
而不是完全被分离。一旦计算器使用完毕,系统就移去计算器窗口并继续文本编辑。
394页
24.2 创建一些视频支持函数
在建立窗口系统之前,必需开发一些支持函数。由于创建弹出式窗口时要求直接密切地
控制屏幕,所以必须执行屏幕I/O的专用函数。前面曾指出,不能使用C++的普通输出函数
和运算符,而那些专用函数将绕过DOS和BIOS直接写入视频硬件。这是实现快速屏幕修
改的唯一方法。
首先,我们简短回顾一下PC的显示系统。
注:本章对PC显示系统的讨论对读者了解窗口系统的工作原理已经足够了,如果想进
一步学习显示接口,可参阅其它有关的书籍。
24.2.1 PC视频系统
所有PC都有某种类型的向显示器输出图象的视频适配器。四种最常见的适配器是:单
色适配器、彩色/图形适配器(CGA)、增强的图形适配器(EGA)和视频图形阵列(VGA)。(还
有一些Super VGA适配卡可支持扩展的视频方式。在本章中,VGA和Super VGA是等同
的。)CGA、EGA和VGA有多种操作方式。本章的窗口系统要求显示系统为80列文本方式,
它通常是通用应用程序的缺省方式。单色适配器用方式7作80列文本方式,而CGA/EGA/
VGA适配器用方式2或方式3。
显示在屏幕上的字符放在给视频适配器留用的RAM中。单色适配器使用的视频RAM
地址为B000:0000H,CGA/EGA/VGA为B800:0000H。虽然CGA、EGA和VGA的函数在
某些方式下有差异,但在方式2和方式3下是完全相同的。显示在屏幕上的每个字符占显示内
存的两个字节,第一个字节容纳实际的字符,第二个字节容纳字符的屏幕属性。对于彩色适
配器,属性字节和含义如表24-1所示。几种主要的颜色可以复合产生更多的颜色。如果是
CGA、EGA或VGA,显示字符的缺省属性字节值是7,该值打开三个前景位,从而产生白色。
如果要产生反视效果,则关闭前景位,打开三个背景位,即值70H。
表24-1 显示属性字节
位 二进制值 置位时的含义
0 1 蓝色前景
1 2 绿色前景
2 3 红色前景
3 8 低强度
4 16 蓝色背景
5 32 蓝色背景
6 64 蓝色背景
7 128 闪烁字符
单色适配器能够识别闪烁位和强度位。它也把属性7解释为普通属性,70H产生反视效
果,1产生带下划线的字符。
每种适配
383页
int operator>(Char * s){return strcmp(p,s)>0;}
int operator<=(Char * s) {return strcmp(p,s)<=0;}
int operator>=(char * s){return strcmp(p,s)>=0;}
关系运算符直接易懂,理解它的实现原理是不会有什么问题的。但要记住,StrType类
是在两个StrType对象之间,或左操作数是一个StrType对象而右操作数是一个用引号括
起的字符串之间进行比较的。如果要把用引号括起的字符串作为左操作数而把StrType对
象作为右操作数,就必需另外增加一个关系函数。
如果给出了由StrType定义的重载关系运算符函数,则下面这些类型的字符串比较是
允许的。
StrType x("one"), y("two"),z("three");
if(x < y) cout <<" x less than y";
if(z=="three")cout <<" z equals three";
y=’o’;
z=’ne’;
if(x==(y+z))cout<<"x equals y+z";
23.9各种字符串函数
StrType类定义了三个函数,以便StrType对象和普通类C++字符串更彻底地结合起
来。它们是strsize()、makestr()和转换函数operator char*()。这些函数在StrType的说明
中定义,如下所示:
int strsize(){ return strlen(p); } // return size of string
void makestr(char * s){strcpy(s,p);}// make quoted string
operator char * () { return p;} //convertion to char *
前两个函数很容易理解。可以看出,函数strsize()返回p所指的字符串的长度,函数makestr
()把p所指的字符串拷贝到一个字符数组。当希望得到被赋予StrType对象的空终止字符
串时,这个函数非常有用。
转换函数operator char*()返回对象中指向字符串的指针p。此函数允许一个StrType
对象用在可以使用类C字符串的任何地方。例如,下面的代码是有效的:
StrType x("Hello");
/ / output the string using a standard C++ function
puts(x);// automatic conversion to char *
当定义了转换的类用在表达式里时,转换函数就自动执行。在这种情况下,由于函数
puts()的原型告诉编辑程序其变元的类型是char*,所以自动执行从StrType到char*的
转换,并返回一个指向包含在x里的字符串的指针。有了转换函数,用户就可以把一个Str-
Type对象用在普通用引号括起的字符串的地方,作为一个变元传给带类型char*变元的
函数。
注:转换为char* 会破坏封装性。因为函数一旦有了指向对象的字符串的指针,它就有
384页
可能直接修改这个字符串,从而绕过StrType的成员函数,也就没有了对象的概念。因此,使
用对char* 的变换时必须十分小心(如不需要这个转换,只需将它从类的说明中删去)。封
装性损失了,实用性却增强了,并提高了和现存库函数的集成度。但这种交换并不总是值得
的。
23.10完整的字符串类
下面是一个完整的StrType类,函数main()演示了它的特点。
#include<iostream.h>
#include<string. h>
#include<stdlib.h>
#include<conio.h>
#include<stdio.h>
class strType{
char * p;
Int size;
public:
strType(char * str);
StrType();
StrType(const StrType&o);// copy constructor
~StrType() {delete[]p;}
friend ostream &operator<<(ostream &stream, StrType &o);
friend istream &operator>>(istream &stream, StrType &o);
StrType operator=(strType &o); // assign a StrType object
strType operator=(char *s);// assign a qouted string
strType operator+(strType &o);// concatenate a StrType object
StrType operator+(char *s); // concatenate a qouted string
friend StrType operator+(char *s, StrType&o);/* concatenate a
qouted string with a StrType object */
StrType operator-(StrType &o); // subtract a subtring
strType operator-(char *s);// subtract a qouted subtring
// relational operations between StrType objects
int operator==(StrType&o){return !strcmp(p,o.p);}
int operator!=(StrType &o){return strcmp(p,o. p);}
int operator<(StrType &o){return strcmp(p,o.p)<0;}
int operator>(StrType &o) {return strcmp(p,o.p)>0;}
int operator<=(StrType &o) {return strcmp(p,o.p)<=0;}
int operator>=(StrType &o){return strcmp(p,o.p) >=0;}
// operations between StrType objects and quoted strings
int operator==(StrType *s){return !strcmp(p,s);}
int operator!=(StrType *s) { return strcmp(p,s);}
int operator
本书第三部分提供了用C++编写的样板应用程序。它的目的有两个。第一,实例有助于
阐明面向对象程序设计的好处,其中包括多态性、封装性和继承性及类库创建;第二,实例还
说明了C++可用来解决不同类型的程序设计问题——面向对象的和非面向对象的程序设
计问题。记住C++语言是C语言的增强和扩充版本,它给予程序员独立于面向对象方法的
更大的能力。因此,不管是用C++语言追求OOP时尚,还是为正常的程序设计任务规定边
界都是无关紧要的。
第二十三章字符串类
在C++里,字符串是作为以空值结束的字符数组而不是作为一种特殊的数据类型。实现
的。这种方法使C++的字符串功能强、精美别致且效率高。同时,数组和指针的这种紧密关
系使用户可以写出许多简洁出色的串操作。然而,有很多应用字符串的情况并不需要有很高
的效率或很强的能力。在这种情况下,用C++的字符串就变得乏味了。幸运的是,用C++可
以创建一个字符串类型,它用牺牲一些效率换来使用的极大便利。
本章将介绍一个字符串类,它使字符串的创建、使用和变换都非常容易。
注:在写本书时,ANSIC++标准化委员会正在定义一个标准的字符串类(但准确形式
仍未定夺)。本章的目的不是开发出这个类的替代品,而只是让读者了解向C++环境中增加
和集成任何新数据类型是容易的。字符串类的创建是这个过程典型的例子。虽然在本章创
建的字符串类比C++标准所提出的要容易得多,但它有一个优点,即可以完全控制字符串
的操作和实现方法。读者将会发现它在各种情况下的作用。
23.1 定义字符串类型
首先,重要的是定义一个字符串类型的含义和对它可以进行哪些操作。其它语言已经定
义了字符串类型,这里可以把它们作为参考的模型。BASIC是一个字符串类型的非常好的
模型。虽然不少C++程序员对把BASIC语言作为一种程序设计语言没有多少热情,但这种
语言对字符串的处理方法既直观又简便。还有一个原因是所有的程序员都了解它。
本章介绍的字符串类并不是BASIC的简单分支,但它借用了下面讨论的BASIC的非
常重要的特点。
在BASIC里,给字符串赋值只需用BASIC的赋值运算符=把带引号的字符串赋给它
即可。下面是一有效的BAsIC字符串赋值语句:
A$="This is a string"
(BASIC里的所有字符串变量都必须以$符号结尾,但本章介绍的字符串类没有这个
374页
限制。)
这个语句把字符串"This is a string"赋给A$。
也可以把一字符串变量赋给另一字符串变量。例如,下面将A$包含的字符串拷贝到B
$中:
B$=A$
由此可见,BASIC和C++在把字符串赋给字符串变量时的主要区别在于BASIC使用
运算符,而C++调用函数strcpy()(尽管C++在初始化时也允许字符数组使用运算符=)。
运算符+在BASIC里用于把两个字符串连接在一起。例如,下列语句使C$含有值"Hi
there":
A$="Hi”
b$="there”
C$=A$+B$
实际上,上述序列可以简化如下:
A$=“Hi”
C$=A$+"there"
其中,字符串变量和一个用引号括起的字符串连接起来。因此,BASIC允许字符串变量和字
符串变量或用引号括起的字符串连接起来。
BASIC的字符串连接和C的标准函数strcat()的一个不同点是,函数strcat()修改了调
用时所带的两个字符串中的一个,并使它包含了结果,而在BASIC里,把两个串相加会产生
一个包含相加结果的临时字符串,但不改变原始值。
BASIC的字符串是直接易懂的,这是因为它在比较字符串时使用关系运算符和在比较
其它数据类型时使用的关系运算符相同。所有的字符串比较都按字典顺序进行。例如,下面
的语句判断A$是否比B$大:
IF A$>B$ THEN PRINt"A$ is greater than B$"
从这个例子可以看出,BASIC处理字符串的方法的优点是,它允许所有主要的字符串
操作使用和其它数据类型使用的相同的运算符。可以认为,BASIC重载了其赋值运算符、加
运算符和关系运算符,使它们可以处理字符串。这就是本章的字符串类将要贯彻的基本概
念。这里介绍的字符串类型将用重载运算符来代替对库函数的调用。
以BASIC对字符串的处理方法为背景,我们现在给C++开发一个字符串类。
23.2 字符串类
这里定义的字符串类要满足下列要求:
字符串可以通过赋值运算符进行赋值。
可以把一个字符串对象赋给另一个字符串对象,也可以把用引号括起的字符串赋给
字符串对象。
两个字符串对象的连接通过运算符+实现。
字符串之间的比较通过关系运算符实现。
375页
字符串对象的初始化可以通过另一字符串对象或用引号括起的字符串实现。
一字符串必须可以是任意的和不定长的。这意味着,每个字符串的存储空间要动态分
配。
-(减号)运算符可以从串中截取一个子串。
要提供一种把字符串对象转换成类C字符串的方法。
下面管理字符串的类叫StrType。其说明如下:
class StrType {
char *p;
int size;
public:
strType
355页
第二十二章 杂项问题和高级论题
本章将讨论本书其它地方未论述的一些有关C++的论题,其中包括转换函数、拷贝构
造函数、缺省函数变元、由建议的ANSI C++标准增加的一些新特性、连接说明以及C和C
++之间的差异。
22.1缺省函数变元
C++在调用函数时,如果没有指定和参数相对应的变元,则允许赋给参数一个缺省值。
缺省值是按从语法上和变量初始化相同的方法指定的。例如,下面说明了一个函数myfunc
(),它带有一个缺省值为0.0的double变元。
void myfunc(double d=0.0)
{
}
下面的例子说明调用函数myfunc()的两种方法:
myfunc(198.234); // pass an explicit value
myfunc();// let function use default
第一个调用把值198.234传给d,第二个调用自动把缺省值0赋给d。
缺省变元包含在C++里的原因是,缺省变元给程序员提供管理更大复合性的另外一种
方法。为了处理更广泛的情况,往往是一个函数包含比通常情况下所需要的参数更多的参
数。所以,当提供缺省值时,只需说明对最通常情况而不是最一般情况有意义的变元。例如,
前面几章中介绍的C++的许多I/o函数使用缺省变元就是这个原因。
下面程序中的函数clrscr()简要地介绍了一个缺省函数变元的用途。函数clrscr()输出
一系列换行符来清除屏幕(它不是最好的方法,但对本例已足够了)。由于最常用的显示方式
是25行文本,所以缺省值为25。某些终端能够显示多于25行或少于25行(通常按使用的显示
方式而定),这时可以通过显式地指定变元的值以取代缺省值。
#include<iostream.h>
void clrscr(int size=25);
main()
{
register Int i;
for(i=0;i<30;i++)cout<<i<<endl;
cin.get();
356页
clrscr();//clears 25 lines
for(i=0;i<30; i++)cout<<i<<endl;
cin.get();
clrscr(10);// clear 10 lines
return 0;
}
void clrscr(int size)
{
for(; size; size--)cout << endl;
}
这个程序说明,在调用clrscr()时,如果缺省值合适,就不必指定变元了。也可以赋给size-
个不同的值取代缺省值。
缺省变元也可以用作标志以通知函数重用以前的变元。为了说明这种缺省变元的用法,
这里编写了一个简单的函数iputs (),它自动地按指定的数缩进一个串。下面是不使用缺省
变元的函数:
void iputs(char *str, int indent)
{
if(indent<0) indent=0;
for(; indent; indent--)cout<<" ";
cout<<str<<"/n";
}
调用这种形式的iputs()要带两个变元:输出的串和缩进的数量。尽管这样写没有任何
错误,但我们还可以改进它,即给参数indent提供一个缺省值以通知加以)缩进到和上一
行对齐。常常是按每行一样的缩进显示一个文本块。在这种情况下,不要再三赋给变元in-
dent相同的值,而是赋给andent一个缺省值以通知inputs() 按以前指定的数量缩进。下面的
程序介绍了这种方法:
#include <iostream.h>
/* Default indent to-1. This value tells the function
to reuse the previous value. */
void iputs(char *str, int indent==1);
main()
{
iputs("Hello there", 10);
iputs(“This will be indented 10 spaces by default");
iputs("This will be indented 5 spaces",5);
iputs("This is not indented",0);
return 0;
}
void iputs(char *str,int indent)
{
357页
static i=0;//holds previous indet value
if(indent>=0)
i=indent;
else// reuse old indent value
indent=i;
for(; indent; indent--)cout<<" ";
cout<<str<<"/n";
}
该程序显示如下:
Hello there
This will be indented 10 spaces by default
This will be indented 5 spaces
This is not indented
在创建带缺省变元的函数时,要记住只能说明一次缺省值,而且必须是文件中的首次说
明。在前面的程序中,缺省变元在iputs()的原型里指定。如果试图在iputs()的定义中指定新
的缺省值(甚至是同样的值),编译程序就会提示一个错误并停止编译。尽管在同一程序中不
能重定义缺省值,但可以给重载函数的不同形式指定不同的缺省值。
带缺省值的参数必须出现在不带缺省值的参数的右边。例如,下面这样定义iputs()是
错误的:
// wrong!
void iputs(int indent=-1, char *str);
一旦开始了定义带缺省值的参数,就不能再说明不带缺省值的参数了。所以,下面的说
明是错误的,不能编译。
int myfunc(floatf, char *str, int i = 10, int j);
由于i赋给了缺省值,所以j也必须赋给缺省值。
同样,在对象的构造函数中也可