VC++调用UpdateLayeredWindow实现半透明窗体

VC++调用UpdateLayeredWindow实现半透明窗体
VC++调用UpdateLayeredWindow实现半透明窗体

半年没碰MFC了,感觉是那么的陌生,忘记了绝大部分函数了....你说我现在都有点学这忘哪,以后能跟的上技术的发展嘛?

注意:由于代码有点长,直接放在这里显得冗长,所以,如果需要源码的朋友请发邮件或留言,代码只是互相学习之用,由于本人水平有限,如果您觉得不好,望理解,且不要有攻击性的言行,谢谢!)

首先看看我做的效果,有点像Vista的效果,而且解决了控件不能显示的问题,注意上层是一个子窗体,用来承载窗体控件:

最近看了桌面天气秀这款小软件,其界面精致漂亮,而且可以透明显示,还有个特别有趣的功能就是能让窗体固定在桌面上并且鼠标可以“穿透”而窗体不受任何影响,这就是带着些许神秘的“有影无形”的功能。

事实上要实现这种“有影无形”的功能,很简单,我相信聪明的您一定很快会想到怎么做。几句代码就可以搞定,给你点提示,只有在2000以上的Win操作系统才支持(当然如果你有足够的时间可以完全可以在98上实现)。那么今天我们主要讨论一下窗体的半透明显示,其实,如果仅仅要实现窗体的半透明显示,很简单,UpdateLayeredWindow调用就可以了,网上去搜一下几乎都是贴的这个函数怎么怎么实现窗体的半透明,在往下就没了,比如此函数有个缺陷,就是经过此函数处理过的窗体,其上面的控件都透明了。诸如此类的问题至少到现在我在网上没找到一个让人感觉满意的解决方案。

记得在一个所谓专业的C#网站上看到一篇题为“XP下透明窗体的完美实现”,当时看了还真高兴一阵子,可是把代码下下来一看,又是被忽悠了,里面就调用了UpdateLayeredWindow函数实现透明然后加个旋转显示,这样子就算完美了,我晕!这个函数查查资料谁不会。看来还是自己动手,不知道有心的朋友注意到没有,在桌面天气秀里,打开设置,然后快速拖动,会看到上面的窗体明显脱离,这就是多层窗口留下的尾巴。下面的透明窗体是背景,上面的窗体承载控件。在拖动下层窗体的时候让上层窗体跟着一起动。对于小软件来说,效果还可以。事实上,今天我实现的也和这个思路差不多,不过我这个上下两层窗体是父子关系,它们各施其职,但是又没有拖动时留下的尾巴(窗口移动的处理),我把这些功能都封装在一起了,只需要简单地把你要在透明窗体上显示的窗体传过去以及透明处理的图片就OK了。限于篇幅下面给绘制函数以及调用示例:

bool DrawAlpha(Image*pBgImg,HWND hWnd)

{

if (!::IsWindow(hWnd)) return false;

CWndDecorate wndDecor(hWnd);

//wndDecor.SetAlpha((255 * 20) / 100);

//wndDecor.ShowOnDesk();

RECT rcWnd;

::GetWindowRect(hWnd,&rcWnd);

int nWid=rcWnd.right-rcWnd.left;

int nHei=rcWnd.bottom-rcWnd.top;

HDC hdcTemp=::GetWindowDC(hWnd);

HDC hdcMemory=::CreateCompatibleDC(hdcTemp);

HBITMAP hBitMap=CreateCompatibleBitmap(hdcTemp,nWid,nHei);

SelectObject(hdcMemory,hBitMap);

HDC hdcScreen=::GetWindowDC(hWnd);

POINT ptWinPos={rcWnd.left,rcWnd.top};

Graphics graph(hdcMemory);

Point points[]={Point(0,0),Point(nWid,0),Point(0,nHei)};

graph.DrawImage(pBgImg,points,3);

SIZE sizeWindow={nWid,nHei};

POINT ptSrc={0,0};

DWORD dwExStyle=::GetWindowLong(hWnd,GWL_EXSTYLE);

if ((dwExStyle&0x80000) !=0x80000)

{

::SetWindowLong(hWnd,GWL_EXSTYLE,dwExStyle^0x80000);

}

CString strTitle;

m_alWnd.GetWindowText(strTitle);

Gdiplus::Font font(L"宋体",12,FontStyleBold,UnitPixel);

StringFormat strFormat;

SolidBrush brush(Color(0xff,0,0,0));

graph.DrawString(strTitle,-1,&font,PointF(32,30),&strFormat,&brush);

::UpdateLayeredWindow(hWnd,hdcScreen,&ptWinPos,&sizeWindow,hdcMemory,&p tSrc,0,&m_alBlend,ULW_ALPHA);

graph.ReleaseHDC(hdcMemory);

::ReleaseDC(hWnd,hdcScreen);

hdcScreen=NULL;

::ReleaseDC(hWnd,hdcTemp);

hdcTemp=NULL;

DeleteObject(hBitMap);

DeleteDC(hdcMemory);

hdcMemory=NULL;

return true;

}

调用示例:

CWndAlphawndAl;

wndAl.InitAlpha(L"C:\\bg.png",NULL,rcMargin,true);

m_pMainWnd=wndAl.GetMainWnd();

wndAl.DoModal();

很简单吧,就这么几行代码就可以实现背景透明而且又可以随意编辑你自己的子控件了。下面是我生成的示例图,大家欣赏一下了,欢迎留言讨论,如果有需要的请留言!谢谢!

C++中虚析构函数的作用

C++中虚析构函数的作用 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明: 有下面的两个类: class ClxBase { public: ClxBase() {}; virtual ~ClxBase() {cout<<”aaa”<DoSomething(); delete pTest; 的输出结果是: Do something in class ClxDerived! Output from the destructor of class ClxDerived! aaa 这个很简单,非常好理解。 但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了: Do something in class ClxDerived! aaa 也就是说,类ClxDerived的析构函数根本没有被调用!(注:肯定不会被调用,因为动态联

C++试题及答案 (五)

C++程序设计模拟试卷(五) 一、单项选择题(本大题共20小题,每小题1分,共20分)在每小题列出的四个备选项中 只有一个是符合题目要求的,请将其代码填写在题后的括号内。错选、多选或未选均无 分。 1. 静态成员函数没有() A. 返回值 B. this指针 C. 指针参数 D. 返回类型 答案:B 解析:静态成员函数是普通的函数前加入static,它具有函数的所有的特征:返回类型、 形参,所以使用静态成员函数,指针可以作为形参,也具有返回值。静态成员是类具有的 属性,不是对象的特征,而this表示的是隐藏的对象的指针,因此静态成员函数没有this 指针。静态成员函数当在类外定义时,要注意不能使用static关键字作为前缀。由于静态成员函数在类中只有一个拷贝(副本),因此它访问对象的成员时要受到一些限制:静态成员函数可以直接访问类中说明的静态成员,但不能直接访问类中说明的非静态成员;若要访问非静态成员时,必须通过参数传递的方式得到相应的对象,再通过对象来访问。 2. 在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管 理操作的函数是() A. 友元函数 B. 虚函数 C. 构造函数 D. 析构函数 答案:C 解析:定义构造函数作用就是初始化对象,而析构函数释放对象空间。虚函数用于完成多 态性,友元增加访问方便性。 3. 所有在函数中定义的变量,都是() A. 全局变量 B. 局部变量 C. 静态变量 D. 寄存器变量 答案:B 解析:变量存储类可分为两类:全局变量和局部变量。 (1)全局变量:在函数外部定义的变量称为全局变量,其作用域为:从定义变量的位置开始 到源程序结束。全局变量增加了函数之间数据联系的渠道,全局变量作用域内的函数,均可使用、修改该全局变量的值,但是使用全局变量降低了程序的可理解性,软件工程学提倡尽量避免使用全局变量。 (2)局部变量:在函数内部定义的变量称为局部变量,其作用域为:从定义变量的位置开始 到函数结束。局部变量包含自动变量(auto)静态变量(static)以及函数参数。 auto变量意味着变量的存储空间的分配与释放是自动进行的。说明符auto可以省略。函数中 的局部变量存放在栈空间。在函数开始运行时,局部变量被分配内存单元,函数结束时,局部变量释放内存单元。因此,任两个函数中的局部变量可以同名,因其占有不同的内存单元而不影响使用。这有利于实现软件开发的模块化。 static变量是定义在函数体内的变量,存放在静态存储区,不用栈空间存储,其值并不随存 储空间的释放而消失。 4. 假定AB为一个类,则执行“AB a(2), b[3],*p[4];”语句时调用该类构造函数的次数 为() A. 3 B. 4 C. 5 D. 9 答案:B 解析: a(2)调用1次带参数的构造函数,b[3]调用3次无参数的构造函数,指针没有给它 分配空间,没有调用构造函数。所以共调用构造函数的次数为4。 5. 如果表达式++a中的“++”是作为成员函数重载的运算符,若采用运算符函数调用格式,则可表示为() A. a.operator++(1) B. operator++(a) C. operator++(a,1) D. a.operator++() 答案:D 解析:运算符的重载,前缀先让变量变化。调用++a,等价为a.operator++(),注意无参 的形式。后缀的话a++,等价于a.operator(0),带形参,形参名可省。 6. 已知f1和f2是同一类的两个成员函数,但f1不能直接调用f2,这说明() A. f1和f2都是静态函数 B. f1不是静态函数,f2是静态函数 C. f1是静态函数,f2不是静态函数

C++箴言:多态基类中将析构函数声明为虚拟

C++箴言:多态基类中将析构函数声明为虚拟 有很多方法可以跟踪时间的轨迹,所以有必要建立一个 TimeKeeper 基类,并为不同的计时方法建立派生类: class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); ... }; class AtomicClock: public TimeKeeper { ... }; class WaterClock: public TimeKeeper { ... }; class WristWatch: public TimeKeeper { ... }; 很多客户只是想简单地取得时间而不关心如何计算的细节,所以一个 factory 函数--返回一个指向新建派生类对象的基类指针的函数--被用来返回一个指向计时对象的指针:TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic- // ally allocated object of a class // derived from TimeKeeper 按照 factory 函数的惯例,getTimeKeeper 返回的对象是建立在堆上的,所以为了避免泄漏内存和其他资源,最重要的就是要让每一个返回的对象都可以被完全删除。 TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object // from TimeKeeper hierarchy ... // use it delete ptk; // release it to avoid resource leak 现在我们精力集中于上面的代码中一个更基本的缺陷:即使客户做对了每一件事,也无法预知程序将如何运转。 问题在于 getTimeKeeper 返回一个指向派生类对象的指针(比如 AtomicClock),那个对象通过一个基类指针(也就是一个 TimeKeeper* 指针)被删除,而且这个基类

C++复习题

一、单项选择题(本大题共20小题,每小题1分,共20分) 1. 静态成员函数没有() A. 返回值 B. this指针 C. 指针参数 D. 返回类型 2. 在类的定义中,用于为对象分配内存空间,对类的数据成员进行初始化并执行其他内部管理操作的函数是() A. 友元函数 B. 虚函数 C. 构造函数 D. 析构函数 3. 所有在函数中定义的变量,都是() A. 全局变量 B. 局部变量 C. 静态变量 D. 寄存器变量 4. 假定AB为一个类,则执行“AB a(2), b[3],*p[4];”语句时调用该类构造函数的次数为() A. 3 B. 4 C. 5 D. 9 5. 如果表达式++a中的“++”是作为成员函数重载的运算符,若采用运算符函数调用格式,则可表示为() A. a.operator++(1) B. operator++(a) C. operator++(a,1) D. a.operator++() 6. 已知f1和f2是同一类的两个成员函数,但f1不能直接调用f2,这说明()

A. f1和f2都是静态函数 B. f1不是静态函数,f2是静态函数 C. f1是静态函数,f2不是静态函数 D. f1和f2都不是静态函数 7. 一个函数功能不太复杂,但要求被频繁调用,则应把它定义为() A. 内联函数 B. 重载函数 C. 递归函数 D. 嵌套函数 8. 解决定义二义性问题的方法有() A. 只能使用作用域分辨运算符 B. 使用作用域分辨运算符或成员名限定 C. 使用作用域分辨运算符或虚基类 D. 使用成员名限定或赋值兼容规则 9. 在main函数中可以用p.a的形式访问派生类对象p的基类成员a,偶中a是() A. 私有继承的公有成员 B. 公有继承的私有成员 C. 公有继承皀保护成员 D. 公有廧承的公有成员 10. 在C++中不返回任何????数应该说明为() A. int B. char C. void D. double 11. 若Sample类中的一个成员函数说明如下: void set(Sample& a),则Sample& a的含义是() A. 指向类Sample的名为a的指针

C++复习题

C++作业题(8) 一.选择填空 (1) 定义重载函数的下列条件中,(C )是错误的。 A. 要求参数个数不同 B. 要求参数类型不同 C. 要求函数返回值类型不同 D. 要求在参数个数相同时,参数类型的顺序不同 (2) 关于下列虚函数的描述中,( C)是正确的。 A. 虚函数是一个static存储类的成员函数 B. 虚函数是一个非成员函数 C. 基类中说明了虚函数后,派生类中可不必将对应的函数说明为虚函数 D. 派生类的虚函数与基类的虚函数应具有不同的类型或个数 (3) 关于纯虚函数和抽象类的描述中,(C )是错误的。 A. 纯虚数是一种特殊的虚函数,它没有具体实现 B. 抽象类中一定具有一个或多个纯虚函数 C. 抽象类的派生类中一定不会再有纯虚函数 D. 抽象类一般作为基类使用,使纯虚函数的实现由其派生类给出 (4) 以下一种类中,( A)不能建立对象。 A. 抽象类 B. 派生类 C. 虚基类 D. 基类 (5)下列函数中不能重载的是( C )。 A)成员函数 B)非成员函数 C)析构函数 D)构造函数 (6)下列描述中,抽象类的特征有( D )。 A)可以说明虚函数 B)可以构造函数重载 C)可以定义友员函数 D)不能说明其对象(7)下列不属于动态联编实现的条件有( D )。 A)要有说明的虚函数。 B)调用虚函数的操作是指向对象的指针或者对象引用:或者是由成员函数调用虚函数。C)子类型关系的确立。 D)在构造函数中调用虚函数。 (8)派生类中对基类的虚函数进行替换时,派生类中说明的虚函数与基类中的被替换的虚

函数之间不要求满足的是( C )。 A)与基类的虚函数具有相同的参数个数。 B)其参数的类型与基类的虚函数的对应参数类型相同。 C)基类必须定义纯虚函数。 D)其返回值或者与基类的虚函数相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类的子类型。(9)下列关于抽象类说法正确的是:( B ) A)抽象类处于继承类层次结构的较下层。 B)抽象类刻画了一组子类的操作通用接口。C)抽象类可以作为类直接使用。 D)抽象类可以直接定义对象。 (10)下列关于虚析构函数说法不正确的是( B )。 A)在析构函数前加上关键字virtual,就说明了虚析构函数。 B)如果一个基类的析构函数说明为虚析构函数,则它的派生类中的析构函数须用virtual 关键字说明后才是虚析构函数。 C)说明虚析构函数的目的在于使用delete删除一个对象时,能保证析构函数被正确地执行。D)设置虚函数后,可以采用动态联编的方式选择析构函数。 (11)编译时多态性通过使用( B )获得。 A)继承 B)虚函数 C)重载函数 D)析构函数 (12)可以使用( A )来阻止基类的成员函数调用派生类中的虚函数。 A)成员名限定 B)指针 C)引用 D)关键字virtual (13)抽象类应该含有( D )。 A)至多一个虚函数 B)至多一个虚函数是纯虚函数 C)至少一个虚函数 D)至少一个虚函数是纯虚函数 (14)一个抽象类可以说明为( A )。 A)指向抽象类对象的指针 B)类成员数据 C)抽象类的对象 D)数组元素(15)对于抽象类的使用需要注意的地方,下列不正确的说法是:( C ) A)抽象类只能用作其它类的基类,不能建立抽象类对象。 B)抽象类不能用作参数类型,函数返回类型或显式转换的类型。

C++程序设计第八章

第八章 多态性和虚函数 8.1多态性 静态联编所支持的多态性称为编译时的多态性。动态联编所支持的多态性称为运行时的多态性, 8.1.1静态联编中的赋值兼容性及名字支配规律 8.1.2 动态联编的多态性 如果让编译器动态联编,也就是在编译“point*P=&c;”语句时,只根据兼容性规则检查它的合理性,也就是检查是否符合“派生类对象的地址可以赋给基类的指针”的条件。至于“p->area()” 调用哪个函数,等程序运行到这里时再决定。 就要使类point的指针p指向派生类函数area的地址。假设使用关键字virtual声明point的类area 函数,将这种函数称为虚函数。 当编译系统编译含有虚函数的类时,将为它建立以个虚函数表,表中的每个元素都指向一个虚函数地址。编译器为类增加一个数据成员,这个数据成员是一个指向该虚函数表的指针,通常称为vptr。 虚函数的地址翻译取决于对象的内存地址。编译器为含有虚函数的类的对象建立一个入口地址,这个地址用来存放指向虚函数表的指针vptr,然后按照类中的虚函数声明次序,一一填入函数指针。 派生类能继承基类的虚函数表,而且只要是和类同名(参数也相同)的成员函数,无论是否使用virtual 声明,它们都自动成为虚函数。如果派生类没有改写基类的虚函数,则函数指针调用基类的虚函数。如果派生改写了基类的函数,编译器将重新为派生类的虚函数建立地址,函数指针会调用改写过的虚函数。 虚函数的调用规则是:根据当前对象,优先调用对象本身的虚成员函数。 8.2 虚函数 一旦基类定义了虚函数该基类的派生类中的同名函数也自动成为虚函数。 8.2.1 虚函数 函数只能是类中的一个成员函数,但不能使静态成员, 8.2.2 虚函数实现多态的条件 关键字virtual指示C++编译器对调用虚函数进行动态联编。这种多态性是程序运行到需要的语句处才动态确定的,所以称为运行时的多态。 (1)类之间的继承关系满足赋值兼容规则。 (2)改写同名虚函数 (3)根据赋值兼容性规则使用指针 满足钱联条件不一定产生动态编译,必须同时满足第3条才能保证实现动态编译。第3友分为两种情况:第1中是已经演示过的按赋值兼容性规则使用基类指针(或引用)访问虚函数;第2种是把指针(或引用)作为函数参数,即这个函数不一定是类的成员函数,可以是普通函数,而且可以重载。 8.2.3构造函数和析构函数调用虚函数 在构造函数和析构函数中调用虚函数采用静态联编, 在建立类C的对象c时,它所包含的基类对象在派生类中定义的成员建立之前被建立。在对象撤销时,该对象所包含的在派生类中定义的成员要先于基类对象之前撤销。 目前推存的C++标准不支持虚构函数。有于析构函数不允许有参数,因此一个类只能有一个虚析构函数。虚析构函数使用vitual说明。 Delete运算符合析构函数一起工作(new和构造函数一起工作),当使用delete删除一个对象时,delete 隐含着对析构函数的一次调用,如果析构函数为虚函数,则这个调用采用动态联编。 8.2.4纯虚函数与抽象 Virtual 函数类型函数名(参数列表)=0;

C实验十一虚函数.docx

实验十一虚函数 一、实验目的 1)掌握虚函数的定义和使用 2)掌握抽彖类的定义和使用 3)掌握纯虚函数的定义 4)掌握虚析构函数、纯虚函数和抽象类的作用 二、实验原理 1.利用虚函数的作用:当编译器编译虚函数吋,编译系统将用动态连接的方式进行编译。即在编译时不确定该虚函数的版本,而是利用一种机制在运动过程屮根据其所指向的实例决定使用哪一个函数版本。 2.利用虚析构函数的原则:当将基类指针或引用new运算符指向派生类对象时, 为了在释放派牛类对象时能调用派牛类的析构函数,必须将基类的析构函数定义为虚函数。 3.抽彖类的作用:为它的所有派生类提供一个公共接口,纯虚函数是定义抽彖类的一种间接手段。 三、实验设备 实验室里调备的计算机、window xp,visual c++6.0 四、实验内容 4.1分析下面各题程序,按各题的要求进行实验 1)分析下面的程序,指出程序运行的结果: # include class CBase {public: virtual void fl() 〃将成员函数fl()声明为虚函数 {cout?n调用函数CBase::fl()!H?endl;} virtual void f2() 〃将成员函数f2()声明为虚函数 {cout?"调用函数CBase::f2()!"?endl;} void f3() //一般成员函数 {cout?n调用函数CBase::f3()!H?endl;} }; class CDerived:public CBase 〃公有继承CBase {void fl() {cout?n调用函数CDerived::fl()!M?endl;} void f2() {cout?n调用函数CDerived::f2()!M?endl;} void f3() {cout?"调用函数CDerived::f3()! "?endl;} }; void main() {CBase objl,*p; 〃定义CBase的对象objl和指针对象p CDerived obj2; 〃定义CDerived 的对彖obj 1

C++虚函数相关练习及答案分析

1、A,B,C,D四个类,分别是B继承于A,C继承于B,其中B类中内嵌一D对象, 在main函数中构造一C对象,观测构造函数和析构函数的调用顺序。 代码: #include using namespace std; class A { public: A(){cout<<"调用A的构造函数"<

结果分析: 构造类C的对象时,因为C继承于B,B继承于A,且在B类中内嵌了D的对象,所以创建C的对象时,先调用A的构造函数,然后再调用内嵌对象d的构造函数,然后调用B的构造函数,然后调用C的构造函数,然后依次反向调用析构函数。当类之间存在继承关系时,创建对象时总是从最远(最里层)的类开始创建。 2、给上题A类增加一虚函数func,并要求B/C派生类也提供该函数实现,并实验分别以指针、引用 和普通对象方式访问该虚函数时的输出,并分析原因。 代码: #include using namespace std; class A { public: A(){cout<<"调用A的构造函数"<

构造函数不能为虚函数的理由

一、构造函数不能为虚函数的理由: 1,从存储空间角度 虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。 2,从使用角度 虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。 虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个 成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。 3、构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它 但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。 4、从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数 从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数 5、当一个构造函数被调用时,它做的首要的事情之一是初始化它的V P T R。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码- -既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。 所以它使用的V P T R必须是对于这个类的V TA B L E。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,V P T R将保持被初始化为指向这个V TA B L E, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置V P T R指向它的V TA B L E,等.直到最后的构造函数结束。V P T R的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。

C++中的虚函数(virtual function)

C++中的虚函数(virtual function) 一.简介 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次: class A { public: virtual void foo() { cout << "A::foo() is called" << endl;} }; class B: public A { public: virtual void foo() { cout << "B::foo() is called" << endl;} }; 那么,在使用的时候,我们可以: A * a = new B(); a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B 的! 这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。 虚函数只能借助于指针或者引用来达到多态的效果,如果是下面这样的代码,则虽然是虚函数,但它不是多态的: class A { public: virtual void foo(); }; class B: public A {

virtual void foo(); }; void bar() { A a; a.foo(); // A::foo()被调用 } 1.1 多态 在了解了虚函数的意思之后,再考虑什么是多态就很容易了。仍然针对上面的类层次,但是使用的方法变的复杂了一些: void bar(A * a) { a->foo(); // 被调用的是A::foo() 还是B::foo()? } 因为foo()是个虚函数,所以在bar这个函数中,只根据这段代码,无从确定这里被调用的是A::foo()还是B::foo(),但是可以肯定的说:如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类的实例,则B::foo()被调用。 这种同一代码可以产生不同效果的特点,被称为“多态”。 1.2 多态有什么用? 多态这么神奇,但是能用来做什么呢?这个命题我难以用一两句话概括,一般的C++教程(或者其它面向对象语言的教程)都用一个画图的例子来展示多态的用途,我就不再重复这个例子了,如果你不知道这个例子,随便找本书应该都有介绍。我试图从一个抽象的角度描述一下,回头再结合那个画图的例子,也许你就更容易理解。 在面向对象的编程中,首先会针对数据进行抽象(确定基类)和继承(确定派生类),构成类层次。这个类层次的使用者在使用它们的时候,如果仍然在需要基类的时候写针对基类的代码,在需要派生类的时候写针对派生类的代码,就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变(增加了新类),都需要使用者“知道”(针对新类写代码)。这样就增加了类层次与其使用者之间的耦合,有人把这种情况列为程序中的“bad smell”之一。 多态可以使程序员脱离这种窘境。再回头看看1.1中的例子,bar()作为A-B 这个类层次的使用者,它并不知道这个类层次中有多少个类,每个类都叫什么,但是一样可以很好的工作,当有一个C类从A类派生出来后,bar()也不需要“知

C++语言程序设计作业4

作业4 一、选择题 1.下列关于动态联编的描述中,错误的是_________。 A)动态联编是以虚函数为基础的 B)动态联编是在运行时确定所调用的函数代码的 C)动态联编调用函数操作是指向对象的指针或对象引用 D)动态联编是在编译时确定操作函数的 注:先期联编也称静态联编,迟后联编也称动态联编。 2 关于虚函数的描述中,正确的是________。 A)虚函数是一个静态成员函数 B)虚函数是一个非成员函数 C)虚函数既可以在函数说明时定义,也可以在函数实现时定义 D)派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型 3 在下面四个选项中,________是用来声明虚函数的。 A)virtual B)public C)using D)false 4 对虚函数的调用________。 A)一定使用动态联编 B)必须使用动态联编 C)一定使用静态联编 D)不一定使用动态联编 5 实现运行时的多态性要使用___________。 A)重载函数 B)构造函数 C)析构函数 D)虚函数 6 要实现动态联编,必须通过____调用虚函数。 A)对象指针 B)成员名限定 C)对象名 D)派生类名 7 在派生类中重新定义虚函数时,除了_____方面,其他方面都必须与基类中相应的 虚函数保持一致。 A)参数个数 B)参数类型 C)函数名称 D)函数体 8 下面关于构造函数和析构函数的描述,错误的是__。 A)析构函数中调用虚函数采用静态联编 B)对虚析构函数的调用可以采用动本联编 C)当基类的析构函数是虚函数时,其派生类的析构函数也一定是虚函数 D)构造函数可以声明为虚函数 9 关于纯虚函数和抽象类的描述中,错误的是__。 A)纯虚函数是一种特殊的虚函数,它没有具体的实现 B)抽象类是指具有纯虚函数的类 C)一个基类中说明有纯虚函数,该基类的派生类一定不再是抽象类 D)抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出 10 下列描述中,____是抽象类的特性。 A)可以说明虚函数 B)可以进行构造函数重载 C)可以定义友元函数 D)不能说明其对象 11 _______是一个在基类中说明的虚函数,它在该基类中没有定义,但要求任何派生 类都必须定义自己的版本。 A)虚析构函数 B)虚构造函数 C)纯虚函数 D)静态成员函数 12 如果一个类至少有一个纯虚函数,那么就称该类为__。 A)抽象类 B)虚基类 C)派生类 D)以上都不对 13 以下___成员函数表示纯虚函数。

(完整版)C++模拟试题1

模拟试题一 一、一、选择题 1、1、C++对C语言作了很多改进,下列描述 中()使得C语言发生了质变,从面向 过程变成了面向对象。 A、增加了一些新的运算符; B、允许函数重载,并允许设置缺省参数; C、规定函数说明必须用原型; D、引进了类和对象的概念; 2、2、下列描述中,()是错误的。 A、内联函数主要解决程序的运行效率问 题; B、内联函数的定义必须出现在内联函数第 一次被调用之前; C、内联函数中可以包括各种语句; D、对内联函数不可以进行异常接口声明; 3、3、在C++中,关于下列设置缺省参数值的 描述中,()是正确的。 A、不允许设置缺省参数值; B、在指定了缺省值的参数右边,不能出现 没有指定缺省值的参数; C、只能在函数的定义性声明中指定参数的 缺省值; D、设置缺省参数值时,必须全部都设置; 4、4、()不是构造函数的特征。 A、构造函数的函数名与类名相同; B、构造函数可以重载; C、构造函数可以设置缺省参数; D、构造函数必须指定类型说明。 5、()是析构函数的特征。 A、析构函数可以有一个或多个参数; B、析构函数名与类名不同; C、析构函数的定义只能在类体内; D、一个类中只能定义一个析构函数; 6、关于成员函数特征的下列描述中,()是错误的。 A、成员函数一定是内联函数; B、成员函数可以重载; C、成员函数可以设置缺省参数值; D、成员函数可以是静态的; 7、下列静态数据成员的特性中,()是错误的。 A、说明静态数据成员时前边要加修饰符static; B、静态数据成员要在类体外进行初始化; C、静态数据成员不是所有对象所共用的; D、引用静态数据成员时,要在其名称前加<类名>和作用域运算符; 8、友元的作用是()。 A、提高程序的运用效率; B、加强类的封装性; C、实现数据的隐藏性; D、增加成员函数的种类; 9、关于new运算符的下列描述中,()是错误的。 A、它可以用来动态创建对象和对象数组; B、使用它创建的对象或对象数组可以使用运算符delete删除; C、使用它创建对象时要调用构造函数; D、使用它创建对象数组时必须指定初始值; 10、关于delete运算符的下列描述中,()是错误的。 A、它必须用于new返回的指针; B、使用它删除对象时要调用析构函数; C、对一个指针可以使用多次该运算符; D、指针名前只有一对方括号符号,不管所删除数组的维数。 11、const int *p说明不能修改()。 A、p指针; B、p指针指向的变量; C、p指针指向的数据类型; D、上述A、B、C三者; 12、已知:print()函数是一个类的常成员函数,它无 返回值,下列表示中,()是正确的; A、void print() const; B、const void print(); C、void const print();

c 选择题题集讲解

C++选择题集 1.下列的各类函数中,不是类的成员函数。( C ) A)构造函数 B)析构函数 C)友元函数 D)拷贝初始化构造函数 2.作用域运算符“::”的功能是:( B ) A)标识作用域的级别的 B)指出作用域的范围的 C)给定作用域的大小的 D)标识成员是属于哪个类的 3.下列说明中const char *ptr;其中ptr应该是:( C ) A)指向字符常量的指针 B)指向字符的常量指针 C)指向字符串常量的指针 D)指向字符串的常量指针 4.已知:print()函数是一个类的常成员函数,它无返回值,下列表示中,是正确的。( A ) A)void print()const; B)const void print(); C)void const print(): D)void print(const); 5.在类定义的外部,可以被访问的成员有( C ) A)所有类成员 B)private的类成员 C)public的类成员 D)public或private的类成员 6.要求打开文件"d:\file.dat",可写入数据,正确的语句是:( B ) ifstream infile("d:\file.dat", ios::in); ifstream infile("d:\\file.dat", ios::in); ofstream infile("d:\file.dat", ios::out); fstream infile("d:\\file.dat", ios::in|ios::out); 7.关于类和对象不正确的说法是:( C ) A)类是一种类型,它封装了数据和操作 B)对象是类的实例 C)一个类的对象只有一个 D)一个对象必属于某个类 8.在C++中,类与类之间的继承关系具有( C ) A)自反性 B)对称性 C)传递性 D)反对称性 9.结构化程序设计所规定的三种基本控制结构是:( C ) A)输入、处理、输出 B)树形、网形、环形 C)顺序、选择、循环 D)主程序、子程序、函数

虚函数与虚析构函数

一.简介 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次: class A { public: virtual void foo() { cout << "A::foo() is called" << endl;} }; class B: public A { public: virtual void foo() { cout << "B::foo() is called" << endl;} }; 那么,在使用的时候,我们可以: A * a = new B(); a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B 的! 这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。 虚函数只能借助于指针或者引用来达到多态的效果,如果是下面这样的代码,则虽然是虚函数,但它不是多态的: class A { public: virtual void foo(); }; class B: public A { virtual void foo(); }; void bar() { A a; a.foo(); // A::foo()被调用 }

1.1 多态 在了解了虚函数的意思之后,再考虑什么是多态就很容易了。仍然针对上面的类层次,但是使用的方法变的复杂了一些: void bar(A * a) { a->foo(); // 被调用的是A::foo() 还是B::foo()? } 因为foo()是个虚函数,所以在bar这个函数中,只根据这段代码,无从确定这里被调用的是A::foo()还是B::foo(),但是可以肯定的说:如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类的实例,则B::foo()被调用。 这种同一代码可以产生不同效果的特点,被称为“多态”。 1.2 多态有什么用? 多态这么神奇,但是能用来做什么呢?这个命题我难以用一两句话概括,一般的C++教程(或者其它面向对象语言的教程)都用一个画图的例子来展示多态的用途,我就不再重复这个例子了,如果你不知道这个例子,随便找本书应该都有介绍。我试图从一个抽象的角度描述一下,回头再结合那个画图的例子,也许你就更容易理解。 在面向对象的编程中,首先会针对数据进行抽象(确定基类)和继承(确定派生类),构成类层次。这个类层次的使用者在使用它们的时候,如果仍然在需要基类的时候写针对基类的代码,在需要派生类的时候写针对派生类的代码,就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变(增加了新类),都需要使用者“知道”(针对新类写代码)。这样就增加了类层次与其使用者之间的耦合,有人把这种情况列为程序中的“bad smell”之一。 多态可以使程序员脱离这种窘境。再回头看看1.1中的例子,bar()作为A-B这个类层次的使用者,它并不知道这个类层次中有多少个类,每个类都叫什么,但是一样可以很好的工作,当有一个C类从A类派生出来后,bar()也不需要“知道”(修改)。这完全归功于多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。 1.3 如何“动态联编” 编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢?也就是说,虚函数实际上是如何被编译器处理的呢?Lippman在深度探索C++对象模型[1]中的不同章节讲到了几种方式,这里把“标准的”方式简单介绍一下。

C++试题

《试题一》 一、单项选择(每题1分,共10分) 1.下面列出的基类中的哪部分能被派生类自动继承: A. 基类中的构造函数 B. 基类中的虚析构函数 C. 基类中重载的赋值操作 D. 基类中的私有成员 2.对于全局函数int f(void) ,与其等价的函数原型为: A.int& f( ); B. int f( ) const; C. int* f( ); D. const int f( ); 3.类A中有唯一的一个成员函数f,且f是公有的静态或非静态成员函数,对于 类A的一个对象a,执行语句a.f(100);成功,那么f 的函数原型不可以是: A.A& f( int, int=50 ); B. void f(int& ) ; C. const A * f(const int ); D. A f( const int&); 4.关于异常和C++提供的异常处理机制不正确的说法是: A.能够改变程序的执行顺序 B. 异常可以是对象 C. 用户不能自定义异常类型 D. 异常可以用catch进行捕捉处理 5.在不考虑强制类型转换的情况下,关于类中常量成员函数的下列说法不正确的是: A.常量成员函数中不能修改本类中的非静态数据成员。 B.常量成员函数中可以调用本类中的任何静态成员函数。 C.常量成员函数的返回值只能是void。 D.若常量成员函数中调用虚函数f,那么函数f在本类中也一定是一个常量成员 函数。 6.任意一个类,析构函数的个数最多是: A.不限个数B.1 C.2 D.3 7.在C++程序中,对象之间的相互通信可以通过: A.继承实现B.调用成员函数实现 C.封装实现D.函数重载实现 8.下面模板定义中不正确的是: A.template Q F(Q x) { return Q + x; } B.template Q F(Q x) { return x + x; } C.template T F(T x) { return x * x; } D.template T F(T x) { return x > 1; } 9.对类型转换函数说明正确的是: A.转换函数不能被派生类继承 B.一个类中只能有一个类型转换函数,以免错误调用 C.类型转换函数不能带参数,但可以指定返回类型 D.转换函数能对所属类中对象进行类型转换 10.下面关于类的成员函数描述不正确的是: A.静态成员函数内可以直接访问类的非静态成员数据 B.静态成员函数内可以直接访问类的静态成员数据 C.非静态成员函数可以直接访问类的非静态成员数据 D.非静态成员函数可以直接访问类的静态成员数据

相关文档
最新文档