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

合集下载

不能声明为虚函数的几种情况

不能声明为虚函数的几种情况

不能声明为虚函数的几种情况在面向对象的程序设计中,虚函数是一个相当重要的概念,它可以让派生类通过继承、重写的方式来实现多态性。

但是并不是所有的函数都能声明为虚函数。

下面我们就来详细地介绍一下哪些情况下不能声明为虚函数。

第一种情况:static函数static函数是一个非常常用的函数类型,它与类的实例无关,不受类的内存布局和实例化影响,因此无法通过实例来调用它。

由于这个函数没有被继承,因此也不能被重写,所以它不能声明为虚函数。

第二种情况:构造函数和析构函数构造函数和析构函数是C++面向对象开发中非常重要的两个函数,它们分别用于对象的初始化和销毁。

由于在子类对象构造或析构时,父类的构造函数或析构函数都会被自动调用。

但是,由于构造函数和析构函数的特殊性质,它们不能被声明为虚函数。

构造函数不能声明为虚函数,因为虚函数需要在对象完全构造成功后才能调用,而构造函数中的虚函数还没有完成构造对象过程。

同样的道理,析构函数也不能为虚函数,因为它需要在对象被完全析构前调用,如果是虚函数,那么在调用析构函数时可能导致类成员已经被销毁了,程序会崩溃。

第三种情况:友元函数友元函数是一个与类的访问权限无关的函数,能够访问类的私有成员。

虽然友元函数不是类的成员函数,但是在某些情况下,为了方便访问私有成员,我们会将其定义为类的友元函数。

但是友元函数不能被继承,也不能被重写,因此也不能被声明为虚函数。

第四种情况:全局函数全局函数是定义在类外部,且不属于任何类的函数,又称为独立函数。

由于它不属于任何类,因此也不能被继承和重写,所以也不能声明为虚函数。

在C++中,为了实现多态性,我们可以将需要多态的函数声明为虚函数。

但是无法将构造函数、析构函数、static函数、友元函数、全局函数声明为虚函数。

需要在实际编程中注意这些细节问题,才能更好地利用类的特性,提高程序的效率。

C++——类继承以及类初始化顺序

C++——类继承以及类初始化顺序

C++——类继承以及类初始化顺序对于类以及类继承, ⼏个主要的问题:1) 继承⽅式: public/protected/private继承.这是c++搞的, 实际上继承⽅式是⼀种允许⼦类控制的思想. ⼦类通过public继承, 可以把基类真实还原, ⽽private继承则完全把基类屏蔽掉.这种屏蔽是相对于对象层⽽⾔的, 就是说⼦类的对象完全看不到基类的⽅法, 如果继承⽅式是private的话, 即使⽅法在基类中为public的⽅法.但继承⽅式并不影响垂直⽅向的访问特性, 那就是⼦类的函数对基类的成员访问是不受继承⽅式的影响的.⽐较(java): java是简化的, 其实可认为是c++中的public继承. 实在没必要搞private/protected继承, 因为如果想控制,就直接在基类控制就好了.2) 对象初始化顺序: c++搞了个成员初始化列表, 并确明确区分初时化跟赋值的区别. c++对象的初始化顺序是:(a) 基类初始化(b) 对象成员初时化(c) 构造函数的赋值语句举例:假设 class C : public A, public B {D d;//}则初始化的顺序是A, B, D, C的构造函数.这⾥基类的初始化顺序是按照声明的顺序, 成员对象也是按照声明的顺序. 因此 c(int i, int j) : B(i), A(j) {} //这⾥成员初始化列表的顺序是不起作⽤的;析构函数的顺序则刚好是调过来, 构造/析构顺序可看作是⼀种栈的顺序;⽐较(java): java中初始化赋值是⼀回事. ⽽且对基类的构造函数调⽤必须显⽰声明, 按照你⾃⼰写的顺序.对成员对象, 也叫由你初始化.没有什么系统安排的顺序问题, 让你感觉很舒服;3) 多继承问题: c++⽀持多继承, 会导致"根"不唯⼀. ⽽java则没有该问题;此外c++没有统⼀的root object, java所有对象都存在Object类使得很多东西很⽅便. ⽐如公共的seriall, persistent等等.4) 继承中的重载: c++中, 派⽣类会继承所有基类的成员函数, 但构造函数, 析构函数除外.这意味着如果B 继承A, A(int i)是基类构造函数, 则⽆法B b(i)定义对象. 除⾮B也定义同样的构造函数.c++的理由是, 假如派⽣类定义了新成员, 则基类初始化函数⽆法初始化派⽣类的所有新增成员.⽐较(java): java中则不管, 就算有新增对象基类函数没有考虑到, ⼤不了就是null, 或者你⾃⼰有缺省值. 也是合理的.5) 继承中的同名覆盖和⼆义性: 同名覆盖的意思是说, 当派⽣类跟基类有完全⼀样的成员变量或者函数的时候, 派⽣类的会覆盖基类的.类似于同名的局部变量覆盖全局变量⼀样. 但被覆盖的基类成员还是可以访问的.如B继承A, A, B都有成员变量a,则B b, b.a为访问B的a, b.A::a 则为访问基类中的a. 这对于成员函数也成⽴.但需要注意的是, 同名函数必须要完全⼀样才能覆盖. int func(int j)跟int func(long j)其实是不⼀样的. 如果基类,派⽣类有这两个函数, 则不会同名覆盖.最重要的是, 两者也不构成重载函数. 因此假如A有函数int func(int j), B有函数int func(long j). 则B的对象b.func(int)调⽤为错误的. 因为B中的func跟它根本就不构成重载.同名覆盖导致的问题是⼆义性. 假如C->B=>A, 这⾥c继承B, B继承A. 假如A, B都有同样的成员fun, 则C的对象c.fun存在⼆义性. 它到底是指A 的还是B的fun呢?解决办法是⽤域限定符号c.A::fun来引⽤A的fun.另外⼀个导致⼆义性的是多重继承. 假设B1, B2都继承⾃B, D则继承B1, B2. 那么D有两个B⽽产⽣⼆义性.这种情况的解决办法是⽤虚基类. class B1 : virtual public B, class B2:virtual public B, D则为class D : public B1, public B2. 这样D中的成员只包含⼀份B的成员使得不会产⽣⼆义性.⽐较(java). java中是直接覆盖. 不给机会这么复杂, 还要保存基类同名的东西. 同名的就直接覆盖, 没有同名的就直接继承.虚基类的加⼊, 也影响到类的初始化顺序. 原则是每个派⽣类的成员化初始化列表都必须包含对虚基类的初始化.最终初始化的时候, 只有真正实例化对象的类的调⽤会起作⽤. 其它类的对虚基类的调⽤都是被忽略的. 这可以保证虚基类只会被初始化⼀次.c++没有显式接⼝的概念, 我觉得是c++语⾔的败点. 这也是导致c++要⽀持组件级的重⽤⾮常⿇烦. 虽然没有显式的接⼝, 但c++中的纯虚函数以及抽象类的⽀持, 事实上是等同于接⼝设施的. 当⼀个类中, 所有成员函数都是纯虚函数, 则该类其实就是接⼝.java c++接⼝类(所有成员函数都是纯虚函数)抽象类类(部分函数是虚函数)对象类对象类C++构造函数调⽤顺序1. 如果类⾥⾯有成员类,成员类的构造函数优先被调⽤;2. 创建派⽣类的对象,基类的构造函数优先被调⽤(也优先于派⽣类⾥的成员类);3. 基类构造函数如果有多个基类,则构造函数的调⽤顺序是某类在类派⽣表中出现的顺序⽽不是它们在成员初始化表中的顺序;4. 成员类对象构造函数如果有多个成员类对象,则构造函数的调⽤顺序是对象在类中被声明的顺序⽽不是它们出现在成员初始化表中的顺序;5. 派⽣类构造函数,作为⼀般规则派⽣类构造函数应该不能直接向⼀个基类数据成员赋值⽽是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。

C++第三章课后答案

C++第三章课后答案

C++第三章课后答案C++第三章习题及答案1、什么是结构化程序设计?它有什么优缺点?所谓结构化程序设计,是一种自顶而下、逐步求精的模块化程序设计方法。

2、什么是对象?什么是类?简述对象与类之间的关系!对象是系统中用来描述客观事物的一个实体,它是用于构成系统的一个基本单位,而系统可以看作是由一系列相互作用的对象组成。

类定义了同类对象的公共属性和行为,属性用数据结构表示,行为用函数表示!《类=数据结构+对数据进行操作的函数》。

对象和类的关系相当于元素和集合的关系、变量和变量的“数据类型”的关系。

从程序设计的角度来说,类是一种复杂的自定义数据类型,对象是属于这种数据类型的变量。

3、什么是面向对象程序设计?面向对象程序设计方法具有哪些基本特征?请比较面向对象程序设计和面向对象过程程序设计有何异同?4、何谓成员变量?何谓成员函数?C++将对象的属性抽象为数据成员,将对象的行为抽象为成员函数。

5、C++中结构和类之间有何异同?结构在默认情况下的成员是公共的,而类在默认情况下的成员是私有的。

在C++中,结构是特殊的类。

6、在C++中如何定义类?如何实现定义的类?如何利用类申明对象?7、类的成员的访问权限有哪几种?请说明它们分别有什么作用?三种,privte:类的私有成员,只能被本类的成员函数访问或调用。

Public:公有成员,可以被本类的成员或其他类的成员函数(通过对象)访问或调用。

Protected:保护成员,可以被本类的成员函数或派生类的成员函数访问或调用。

8、何谓构造函数?何谓析构函数?请说明它们分别有什么作用?构造函数:不需要用户程序调用,就能在创建对象时由系统自动调用,其作用是在对象被创建时利用初始值去构造对象,使得在声明对象时就能自动完成对象的初始化。

析构函数:在对象的生存周期即将结束时由系统自动调用的,其作用是用来在对象被删除前做一些清理工作和数据保存工作。

9、如何定义一个内联成员函数?内联函:内联函数必须是和函数体申明在一起,才有效。

构造函数可以是虚函数吗?构造函数和析构函数可以调用虚函数吗?虚表和虚表指针的概念

构造函数可以是虚函数吗?构造函数和析构函数可以调用虚函数吗?虚表和虚表指针的概念

构造函数可以是虚函数吗?构造函数和析构函数可以调⽤虚函数
吗?虚表和虚表指针的概念
构造函数不可以是虚函数。

因为类的虚函数表指针是在构造函数中初始化的,在虚表指针没有被正确初始化之前,我们不能调⽤虚函数。

构造函数和析构函数也不能调⽤虚函数,前者是因为虚表指针还没有被初始化,后者是因为虚表指针可能已经被析构了。

0i
存在虚函数的类都有⼀个⼀维的虚函数表,简称虚表。

类的每个对象都有⼀个指向虚表开始的虚表指针。

虚表是和类对应的,虚表指针是和对象对应的。

抽象类是指⾄少包含⼀个纯虚函数的类。

编译器在编译的时候发现类⾥有虚函数就会为该类创建⼀个虚表。

编译器另外还为每个类的对象提供⼀个虚表指针,这个指针指向对象所属的类的虚表。

在虚表指针没有被正确初始化之前,我们不能调⽤虚函数。

虚表的创建和虚表指针的初始化都是在构造函数中完成的。

派⽣类的虚函数表存放重写的虚函数,当基类的指针指向派⽣类的对象时,调⽤虚函数时都会根据虚表指针来选择虚函数。

⽽基类的虚函数在派⽣类中已经被重写了,因此只能调⽤派⽣类的虚函数版本了。

**这也揭⽰了虚析构函数的作⽤,当基类指针指向派⽣类对象时,调⽤虚析构函数会先调⽤派⽣类的析构函数。

如果析构函数不是虚函数,那么基类指针调⽤析构函数时只会调⽤基类的析构函数。

重载函数属于编译时多态,虚函数属于运⾏时多态。

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

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

一、构造函数不能为虚函数的理由: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,等.直到最后的构造函数结束。

C++基抽象类的构造析构(纯)虚函数

C++基抽象类的构造析构(纯)虚函数

C++基抽象类的构造析构(纯)虚函数⼀、析构函数可定义为⼀、析构函数可定义为纯虚函数纯虚函数,但也必须给出函数定义,但也必须给出函数定义 Effective C++ 条歀07: 为多态基类声明virtual 析构函数(Declare destructors virtual in polymorphic base classes ) 在某些类⾥声明纯虚析构函数很⽅便。

纯虚函数将产⽣抽象类——不能实例化的类(即不能创建此类型的对象)。

有些时候,你想使⼀个类成为抽象类,但刚好⼜没有任何纯虚函数。

怎么办?因为抽象类是准备被⽤做基类的,基类必须要有⼀个虚析构函数,纯虚函数会产⽣抽象类,所以⽅法很简单:在想要成为抽象类的类⾥声明⼀个纯虚析构函数。

1 //这⾥是⼀个例⼦:2 class awov {3 public :4 virtual ~awov() = 0; // 声明⼀个纯虚析构函数5 }; 这个类有⼀个纯虚函数,所以它是抽象的,⽽且它有⼀个虚析构函数,所以不会产⽣析构函数问题。

但这⾥还有⼀件事:必须提供纯虚析构函数的定义: awov::~awov() { ... } // 纯虚析构函数的定义 这个定义是必需的,因为虚析构函数⼯作的⽅式是:最底层的派⽣类的析构函数最先被调⽤,然后各个基类的析构函数被调⽤。

这就是说,即使是抽象类,编译器也要产⽣对~awov 的调⽤,所以要保证为它提供函数体。

如果不这么做,链接器就会检测出来,最后还是得回去把它添上。

⼆、? 关于C++为什么不⽀持虚拟构造函数,Bjarne 很早以前就在C++Style and Technique FAQ ⾥⾯做过回答 Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information.Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual. 含义⼤概是这样的:虚函数调⽤是在部分信息下完成⼯作的机制,允许我们只知道接⼝⽽不知道对象的确切类型。

C++中构造函数中调用虚函数的问题

C++中构造函数中调用虚函数的问题
cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << end码,输出就会变成:
<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
3) 如果对象有父类就会先调用父类的构造函数(并依次递归),如果有多个父类(多重继承)会依次对父类的构造函数进行调用,并会适当的调整this指针的位置。在调用完所有的父类的构造函数后,再执行自己的代码。
照上面的分析构造C190时也会调用C180的构造函数,这时在C180构造函数中的第一个foo调用为静态绑定,会调用到C180::foo()函数。第二个foo调用是通过指针调用的,这时多态行为会发生,应该调用的是C190::foo()函数。
17 0042700E mov dword ptr [eax],45C400h
18 00427014 mov eax,dword ptr [ebp-8]
19 00427017 pop edi
20 00427018 pop esi
21 00427019 pop ebx
22 0042701A add esp,0CCh
为此我查了一下C++标准规范。在12.7.3条中有明确的规定。这是一种特例,在这种情况下,即在构造子类时调用父类的构造函数,而父类的构造函数中又调用了虚成员函数,这个虚成员函数即使被子类重写,也不允许发生多态的行为。即,这时必须要调用父类的虚函数,而不子类重写后的虚函数。
我想这样做的原因是因为在调用父类的构造函数时,对象中属于子类部分的成员变量是肯定还没有初始化的,因为子类构造函数中的代码还没有被执行。如果这时允许多态的行为,即通过父类的构造函数调用到了子类的虚函数,而这个虚函数要访问属于子类的数据成员时就有可能出错。

C++函数中那些不可以被声明为虚函数的函数

C++函数中那些不可以被声明为虚函数的函数

常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

1、为什么C++不支持普通函数为虚函数?普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。

2、为什么C++不支持构造函数为虚函数?这个原因很简单,主要是从语义上考虑,所以不支持。

因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。

另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。

(这不就是典型的悖论)3、为什么C++不支持内联成员函数为虚函数?其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。

(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)4、为什么C++不支持静态成员函数为虚函数?这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。

5、为什么C++不支持友元函数为虚函数?因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

*********************************************************************1、顶层函数:多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层函数不属于成员函数,是不能被继承的。

2、构造函数:(1)构造函数不能被继承,因而不能声明为virtual函数。

(2)构造函数一般是用来初始化对象,只有在一个对象生成之后,才能发挥多态的作用,如果将构造函数声明为virtual函数,则表现为在对象还没有生成的情况下就使用了多态机制,因而是行不通的,如下例:#include <iostream>using namespace std;class B{public:B() {}virtual void show(){cout<<"***"<<endl;}};class D:public B{public:D() {}void show(){cout<<"==="<<endl;}};int main(void){B *pb;D d; //先生成对象pb=&d;pb->show(); //再体现多态pb=new D(); //先调用构造函数pb->show(); //再多态delete pb;return 0;}3、static函数:不能被继承,只属于该类。

构造函数的作用是在创建对象时

构造函数的作用是在创建对象时
都可以用于函数参数和返回值。
二,对象数组
1.定义格式:
2.对象数组元素:
3.区别:指向对象数组的指针和对象指针数组。
三,子对象和堆对象
子对象概念:一个对象作为另一个类的成员时,该对象称为类的子对象。子对象实际是某类的数据成员。
堆对象:在程序运行中,根据需要随时创建的对象称为堆对象。
C++中,内存被分为4种储存区域:
该运算符必须用于由new返回的指针。
对于一个指针只能使用一次运算符delete。
指针名前只能使用一对方括号,而不管所释放数组的为数,并且方括号内不写任何东西。
该运算符也适应于空指针。
四,类型转换和转换函数
类型转换包括隐含类型转换和强制类型转换。转换函数是一种强制类型转换。
单参数的构造函数,提供了一种功能,可以将其他数据类型的数值或变量转换为用户所定义的数据类型。这便是单参数构造函数所具有的类型转换功能。
动态联编:指在程序运行时进行的联编,又称晚期联编。
继承是动态联编的基础,虚函数是动态联编的关键。
三,动态联编的条件
公有继承
虚函数
引用虚函数的方法:对象引用和对象指针、成员函数。
虚函数的特性:
派生类中的虚函数与基类中的虚函数具有相同的参数个数、对应的参数类型,相同的返回值类型。
基类中说明的虚函数具有自动向下传给他的派生类的性质。即派生类的虚函数中的virtual说明可以省略。
基类构造函数。
子对象构造函数。
派生类构造函数。
派生类析构函数的调用顺序:
先调用派生类的析构函数。
在调用派生类中子对象的析构函数。
最后调用基类的析构函数。
在基类中定义有默认构造函数或者没有定义任何构造函数时,派生类构造函数中省略对基类构造函数的调用。

面试

面试

1.构造函数与析构函数可不可以为虚函数构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。

不建议在构造函数和析构函数里面调用虚函数。

构造函数不能声明为虚函数的原因是:1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。

而在构造一个对象时,由于对象还未构造成功。

编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。

无法确定。

2 虚函数的执行依赖于虚函数表。

而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。

而在构造对象期间,虚函数表还没有被初始化,将无法进行。

虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。

然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。

(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。

析构函数设为虚函数的作用: 解释:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

2.堆栈溢出如果堆栈的顶指针超出了定义的堆栈范围,将出现堆栈溢出,这是灾难性的。

您可以在写程序的时候定义一个顶指针的最大值,每次压栈都判断一次就可以了。

3内存泄露指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。

内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

堆内存泄漏对内存指的是程序运行中根据需要分配通过等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。

如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.系统资源泄露主要指程序使用系统分配的资源比如Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

虚函数继承,多态,虚继承(详细讲解)

虚函数继承,多态,虚继承(详细讲解)

虚函数的定义要遵循以下重要规则:1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。

即使虚函数在类的内部定义定义,但是在编译的时候系统仍然将它看做是非内联的。

5.构造函数不能是虚函数,因为构造的时候,对象还是一片位定型的空间,只有构造完成后,对象才是具体类的实例。

6.析构函数可以是虚函数,而且通常声名为虚函数。

在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(earlybinding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding)//例程3#include <iostream>usingnamespacestd;classVehicle{public:Vehicle(floatspeed,inttotal){Vehicle::speed=speed;Vehicle::total=total;}virtualvoidShowMember()//虚函数{cout<<speed<<"|"<<total<<endl;}protected:floatspeed;inttotal;};classCar:publicVehicle{public:Car(intaird,floatspeed,inttotal):Vehicle(speed,total){Car::aird=aird;}virtualvoidShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加{cout<<speed<<"|"<<total<<"|"<<aird<<endl; }public:intaird;};voidtest(Vehicle &temp){temp.ShowMember();}intmain(){Vehicle a(120,4);Car b(180,110,4);test(a);test(b);cin.get();}运行结果:120|4110|4|180//例程2#include <iostream>usingnamespacestd;classVehicle{public:Vehicle(floatspeed,inttotal){Vehicle::speed=speed;Vehicle::total=total;}voidShowMember(){cout<<speed<<"|"<<total<<endl;}protected:floatspeed;inttotal;};classCar:publicVehicle{public:Car(intaird,floatspeed,inttotal):Vehicle(speed,total)Car::aird=aird;}voidShowMember(){cout<<speed<<"|"<<total<<"|"<<aird<<endl; }protected:intaird;};voidtest(Vehicle &temp){temp.ShowMember();}voidmain(){Vehicle a(120,4);Car b(180,110,4);test(a);test(b);cin.get();运行结果:120|4110|4虚继承是为了在多继承的时候避免引发歧义,比如类A有个就是a,B继承了A,C也继承了A,当D多继承B,C时,就会有歧义产生了,所以要使用虚拟继承避免重复拷贝。

C++虚函数注意事项

C++虚函数注意事项

C++虚函数注意事项⽬录⼀、虚函数注意事项1.构造函数2.析构函数3.友元4.没有重新定义5.重新定义将隐藏⽅法⽂章转⾃公众号:Coder梁(ID:Coder_LT)⼀、虚函数注意事项在之前的当中,我们已经讨论了虚函数的使⽤⽅法,也对它的原理进⾏了简单的介绍。

这⾥简单做⼀个总结:在基类的⽅法声明中使⽤关键字virtual可以声明虚函数加上了virtual关键字的函数在基类以及派⽣类和派⽣类再派⽣出来的类中都是虚的在调⽤虚函数时,程序将会根据对象的类型执⾏对应的⽅法⽽⾮引⽤或指针的类型在定义基类时,需要将要在派⽣类中重新定义的类⽅法声明为虚,如析构函数除了这些之外,我们还有⼀些其他需要注意的事项。

1.构造函数构造函数不能是虚函数,创建派⽣类对象时将调⽤派⽣类的构造函数,⽽⾮基类的构造函数,毕竟构造函数是根据类名调⽤的。

⼀般我们会在派⽣类中调⽤基类的构造函数,这其实不是继承机制,所以将类构造函数声明为虚没有意义。

2.析构函数前⽂说过析构函数应该是虚函数,除⾮类不被继承。

因为派⽣类当中往往含有独有的成员变量,如果析构函数⾮虚,那么会导致在对象析构时仅调⽤基类的析构函数,从⽽导致独有的成员变量内存不被释放,引起内存泄漏。

所以通常我们会将析构函数设置成virtual,即使不⽤做基类也不会引起错误,⾄多只会影响⼀点效率。

但在⼤型合作开发的项⽬当中,许多组件和类都是共享的,我们往往⽆法保证我们开发的类是否会被其他开发者继承,因此设置虚析构函数也是⼀种常规做法。

3.友元友元函数不能是虚函数,因为友元不是类成员,只有成员函数才能是虚函数。

如果我们希望友元函数也能实现类似虚函数的功能,我们可以在友元函数当中使⽤虚函数来解决。

4.没有重新定义如果派⽣类当中没有重新定义虚函数,那么将使⽤该函数的基类版本。

如果派⽣类位于派⽣链中,如B继承了A,C继承了B这种情况,那么派⽣类将会使⽤最新的虚函数版本。

5.重新定义将隐藏⽅法我们来看⼀个例⼦:class Mammal {private:string name;public:Mammal(string n): name(n) {}virtual void speak() const {cout << "can't say anything" << endl;}};class Human : public Mammal{private:string job;public:Human(string n, string j): Mammal(n), job(j) {}virtual void speak(const string st) const {cout << "i'm human" << endl;}};我们在⽗类当中定义了⼀个⽆参虚函数speak,⽽在⼦类Human当中也定义了⼀个需要传⼊⼀个string类型的虚函数speak。

(完整)C++题目答案

(完整)C++题目答案

1、使用const语句定义一个标识符常量时,则必须对它同时进行初始化。

2、C++中可以有许多相同名称,但参数类型或个数不同的成员函数,称为函数重载。

3、在C++语言中,访问一个对象的成员所有的运算符是 . ,访问一个指针所指向的对象的成员所用的运算符是— > 。

4、在类的定义中,若没有显示规定其成员的访问属性。

则默认采用私有访问属性.5、当一个类对象被建立时将自动调用该类的构造函数 ,假定一个类对象数组为A[N],当离开它的作用域时,系统自动调用执行该类析构函数的次数为 N .6、若需要把一个函数“void F();”定义为一个类AB的友元函数,则应在类AB的定义中加入一条语句friend void F();。

7、非成员函数应声明为类的私有函数才能访问这个类的private成员。

8、重载的运算符保持其原有的优先级别和结合性。

9、在C++中,一个派生类可以从一个基类派生称为单继承;也可以从多个基类派生,称为多重继承。

无论哪种方式继承,基类的私有成员在派生类中是不可访问的.10、在C++中,有一种类不能用来定义对象,只能用来派生新的类,称之为抽象类 ,定义该类至少具有一个纯虚函数 .11、如果一个类中有一个或多个纯虚函数,则这个类称为抽象类。

1、以下正确的函数形式是( D)A double fun1(x,int y)B fun1(int x,y)C fun1(x,y)D double fun1(int x,int y)2、不能作为函数重载判断依据的是( D )A 参数个数B 参数类型C 函数名字D 返回类型3、下面有关重载函数的说法正确的是( C)A 重载函数必须具有不同的返回值类型B 重载函数形参个数必须不同C 重载函数必须有不同的形参列表D 重载函数名可以不同4、在C++语言中,对函数参数默认值描述争取的是( D )A 函数参数的默认值只能设定一个B 一个函数的参数若有多个,则参数默认值的设定可以不连续C 函数参数必须设定默认值D 在设定了参数的默认值后,该参数右边定义的所有参数都必须指定默认值5、关于new运算符的下列描述中,错误的是(D )A 它可以用来动态创建对象和对象数组B 使用它创建的对象或对象数组可以使用运算符delete删除C 使用它创建对象是要调用构造函数D 使用它创建对象数组时必须指定初始值6、有如下类声明:class Foo{int bar;};则Foo类的成员bar是( C )A 公有数据成员B 公有成员函数C 私有数据成员D 私有成员函数7、若类的简单成员函数是在类中定义的,则默认为( D )函数。

构造函数虚构函数是否可为虚函数

构造函数虚构函数是否可为虚函数

构造函数虚构函数是否可为虚函数构造函数:为对象分配存储空间,使一个对象初始化;析构函数:在该对象生命期完结时做相应的扫尾工作并释放由构造函数分配的内存;构造函数不能是虚函数的原因:自己的话:【只有基类指针指向子类对象时,虚函数才用意义。

当一个基类指针指向子类对象时,子类对象已经构造好了,已经没有动态绑定的必要了,所以虚函数不能是虚函数。

】从概念上来说,如前所述,虚函数机制只有在应用于地址时才有效,因为地址在编译阶段提供的类型信息不完全。

构造函数的功能是为一个对象在内存中分配空间,也就是说,此时该对象的类型已经确定了,编译系统确切的知道应该调用哪一个类的构造函数,不需要也不可能应用动态绑定。

从实现上来说,每个对象的VPTR是需要构造函数来初始化的(当然是由编译系统自动加进去的代码来实现),在构造函数没有调用之前,VPTR没有形成,根本就不可能实现动态绑定。

当构造函数内部有虚函数时,会出现什么情况呢?结果是,只有在该类中的虚函数版本被调用,也就是说,在构造函数中,虚函数机制不起作用了,调用虚函数如同调用一般的成员函数一样。

析构函数可以是虚函数:析构函数可以是虚函数,而且应该被声明为虚函数。

与一般成员函数相似,析构函数被调用时,对象的构造已经完成,VPTR和VTABLE也已被正确初始化,因此虚析构函数在实现上是可能的。

从设计角度来看,析构函数的任务是释放内存,因此它必须确切知道被释放的对象的类型,否则可能破坏有用的数据,产生不可预知的后果。

例如,我们用基类指针指向了派生类对象,那么释放内存时,必须是释放派生类对象的存储空间。

所以,析构函数经常被声明为虚函数。

由于效率上的原因,并不把析构函数缺省为虚函数。

但作为一条实践经验,可以给有虚函数的每个基类声明虚析构函数。

当析构函数内部有虚函数时,又如何工作呢?与构造函数相同,只有“局部”的版本被调用。

但是,行为相同,原因是不一样的。

构造函数只能调用“局部”版本,是因为调用时还没有派生类版本的信息。

C++ 虚函数[详讲]

C++ 虚函数[详讲]

什么是虚函数?简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。

为什么要引入虚函数?虚函数的作用是实现类的继承所体现的多态性,具体点是实现动态联编。

从程序的角度上来说,在定义了虚函数后,可以在派生类中对虚函数重新定义,以实现统一的接口,不同定义过程,在程序的运行阶段动态地选择合适的成员函数。

什么是多态性?简单点说,多态性是将接口与实现进行分离;C++实现运行时多态性的关键途径:在公有派生情况下,一个指向基类的指针可用来访问从基类继承的任何对象。

语法:普通函数的前面加上virtual[cpp]view plaincopyprint?1.virtual函数返回值类型虚函数名(形参表)2.{3.//函数体4.}虚函数的调用方式:只能通过指向基类的指针或基类对象的引用来调用虚函数调用语法:[cpp]view plaincopyprint?1.指向基类的指针变量名->虚函数名(实参表)2.基类对象的引用名. 虚函数名(实参表)注意:正常情况下,如果不把函数声明为虚函数,指向基类的指针的访问情况如下:1)基类指针指向基类对象:基类指针可以直接访问基类对象中的成员2)基类指针指向派生类对象:基类指针只能访问派生类中的从基类中继承的成员,派生类有同名的函数或成员,也只能调用基类的成员。

如果定义成虚函数时:定义一个基类指针,把不同的派生类对象付给它,会调用对应派生类的函数,而非基类函数。

举例:[cpp]view plaincopyprint?1.#include <iostream>ing namespace std;3.class A4.{5.public:6.virtual void show()7. {8. cout<<"A"<<endl;9. }10.};11.class B:public A12.{13.public:14.void show()15. {16. cout<<"B"<<endl;17. }18.};19.class C:public A20.{21.public:22.void show()23. {24. cout<<"C"<<endl;25. }26.};27.void main()28.{29. A*a;30. B b;31. C c;32. a=&b;33. a->show();34. a=&c;35. a->show();36. system("pause");37.}运行结果:B(换行)C(换行)--指向不同的派生类,调用不同的函数如果不加基类A中的Virtual,则输出结果:A(换行)A(换行)--基类指针,调用派生类中继承的基类成分定义虚函数,实现动态联编需要三个条件:1)必须把动态联编的行为定义为类的虚函数---定义虚函数2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来---类之间是公有继承3)基类指针指向派生类的对象,然后使用基类指针调用虚函数注意:1、使用时,虚函数可以在基类中声明,提供界面。

构造函数不能为虚重载函数总结

构造函数不能为虚重载函数总结

构造函数不能为虚重载函数总结作为⼀个类,他最基础的成员函数就要数构造函数了。

这⾥我们先探讨⼀下构造函数为什么不能是虚函数。

在解决这个问题之前,要先明⽩类中函数的调⽤⽅式。

⼀个类的函数共⽤⼀个函数空间,因此在实例化的对象中是不占空间的。

每当有函数调⽤的时候,会有⼀个指向该函数空间的指针,依次开始寻找被调⽤函数。

需要注意的是,虚函数的函数空间与普通的函数不同,因为需要类的对象将虚函数实例化,因此不同对象的虚函数实例化后不⼀定相同,所以虚函数的函数空间实际上在对象中。

知道了以上⼏点就可以明⽩,如果构造函数是虚函数,就会存在⼀个⽭盾。

类需要⽤构造函数⽣成对象,⽽构造函数在对象的虚函数空间中,但此时还不存在对象,不存在虚函数指针,⽆法调⽤构造函数。

因此构造函数不能作为虚函数。

----------------------------------------------------------------------接下来对函数重载进⾏总结我们学过运算符重载(说实话这⼀块我没学好,看了下书但是什么都没记住QAQ),有三种⽅式:普通函数,友元函数,类成员函数运算符重载是为了对⽤户⾃定义数据类型的数据的操作与内定义的数据类型的数据的操作形式⼀致。

不能重载的5个运算符:*成员指针访问运算符;::域运算符;sizeof长度运算符;?:条件运算符;.成员访问符双⽬运算符重载为成员函数时,仅有⼀个参数。

单⽬运算符重载为成员函数时,不能再显式说明参数,总是隐含了⼀个参数,该参数是this指针。

this指针是指向调⽤该成员函数对象的指针。

运算符重载函数还可以为友元函数。

当重载友元函数时,将没有隐含的参数this指针。

这样,对双⽬运算符,友元函数有2个参数,对单⽬运算符,友元函数有⼀个参数。

但是存在⼀些运算符不能被重载为友元函数:=,(),[ ],- >。

赋值运算符“=”只能重载为⾮静态成员函数。

不能重载为静态成员函数是因为静态成员函数是属于整个类的,不是属于某个对象的,静态成员函数只能操作静态数据成员。

在基类的构造函数中调用虚函数时为何调用基类的而不是派生类的

在基类的构造函数中调用虚函数时为何调用基类的而不是派生类的

在基类的构造函数中调用虚函数时为何调用基类的而不是派生类的展开全文虚函数指针在构造函数中初始化,派生类对象先构造基类对象,再构造派生类部分,当构造基类对象的时候,这个派生类对象还不是一个完整的对象,其虚函数指针还没初始化,所以不能够检索虚表去调用对应的虚函数,调用的函数也是这个构造函数(CBase)对应的类里定义的函数(或者其基类定义的函数,比如如果CBase没有定义func,但是CBase的基类有,假如CBase有基类),这里具体说就是基类Base的实现。

读Effective C++ 条歀09:绝不在构造和析构过程中调用虚函数(Never call virtual functions during construction or destruction)首先,我们用一句在程序员中比较流行的话作为本文的开篇:如果你在基类的构造函数中调用虚函数,那么在基类构造期间,虚函数的行为像一个“实”函数。

在派生类对象的基类部分构造期间,对象的类型是base class,而不是derived class。

这时,不只是虚函数会被编译器解析至基类型,就连RTTI中的typeid和dynamic_cast也会把对象视为基类型。

C++标准和编译器这样做的原因其实很简单:在派生类对象的基类部分构造期间,派生类的专属成分尚未被初始化,所以面对它们,最安全的做法就是视它们不存在。

派生类对象在自身的构造函数开始执行前,不会被作为一个派生类对象,而是被看作一个基类对象。

对于析构函数,也同样适用。

一旦派生类的析构函数执行完毕,派生类对象中的派生类特有的成员变量变呈现未定义状态,所以此时,C++编译器视它们如无物,即:进入到基类的析构函数中后,就把派生类对象视为一个基类对象,此时,任何虚函数、typeid和dynamic_cast等等也这么看待它。

C++ primer中说,如果在构造函数或析构函数中调用虚函数,则运行的是构造函数或析构函数自身类型所定义的版本。

Bjarne:可以在构造函数中调用虚拟函数吗

Bjarne:可以在构造函数中调用虚拟函数吗

Bjarne:可以在构造函数中调用虚拟函数吗可以,但是要小心。

它可能不象你期望的那样工作。

在构造函数中,虚拟调用机制不起作用,因为继承类的重载还没有发生。

对象先从基类被创建,"基类先于继承类(base before derived)"。

看看这个:#include#includeusing namespace std;class B {public:B(const string& ss) { cout << "B constructor\n"; f(ss); }virtual void f(const string&) { cout << "B::f\n";}};class D : public B {public:D(const string & ss) :B(ss) { cout << "D constructor\n";}void f(const string& ss) { cout << "D::f\n"; s = ss; }private:string s;};int main(){D d("Hello");}程序编译以后会输出:B constructorB::fD constructor注意不是D::f。

设想一下,如果出于不同的规则,B::B()可以调用D::f()的话,会产生什么样的后果:因为构造函数D::D()还没有运行,D::f()将会试图将一个还没有初始化的字符串s赋予它的参数。

结果很可能是导致立即崩溃。

析构函数在"继承类先于基类"的机制下运行,因此虚拟机制的行为和构造函数一样:只有本地定义(local definitions)被使用--不会调用虚拟函数,以免触及对象中的(现在已经被销毁的)继承类的部分。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

一、构造函数不能为虚函数的理由:
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的状态是由被最后调用的构造函数确定的。

这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。

但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置V P T R
指向它自己的V TA B L E。

如果函数调用使用虚机制,它将只产生通过它自己的V TA B L E的调用,而不是最后的V TA B L E(所有构造函数被调用后才会有最后的V TA B L E)。

相关文档
最新文档