目录
1、 构造函数析构函数可否抛出异常
1.C++只会析构已经完成的对象,对象只有在其构造函数执行完毕才算是完全构造妥当。在构造函数中发生异常,控制权转出构造函数之外。因此,在对象b的构造函数中发生异常,对象b的析构函数不会被调用。因此会造成内存泄漏。
2.用auto_ptr对象来取代指针类成员,便对构造函数做了强化,免除了抛出异常时发生资源泄漏的危机,不再需要在析构函数中手动释放资源;
3.如果控制权基于异常的因素离开析构函数,而此时正有另一个异常处于作用状态,C++会调用terminate函数让程序结束;
4.如果异常从析构函数抛出,而且没有在当地进行捕捉,那个析构函数便是执行不全的。如果析构函数执行不全,就是没有完成他应该执行的每一件事情。
2、 类如何实现只能静态分配和只能动态分配
1.前者是把new、delete运算符重载为private属性;后者是把构造、析构函数设为protected属性,再用子类来动态创建;
2.建立类的对象有两种方式:
1静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;
2动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象;
3.只有使用new运算符,对象才会被建立在堆上,因此只要限制new运算符就可以实现类对象只能建立在栈上。可以将new运算符设为私有。
3、 如果想将某个类用作基类,为什么该类必须定义而非声明?
派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类必须知道他们是什么。
4、 什么情况会自动生成默认构造函数?
1.带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象(另一个类的对象是这个类的成员),而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正被需要的时候才会发生;如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;
2.带有默认构造函数的基类,如果一个没有任何构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;
3.带有一个虚函数的类
4.带有一个虚基类的类
5.合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化。
5、 构造函数的扩展过程?
1.记录在成员初始化列表中的数据成员初始化操作会被放在构造函数的函数体内,并与成员的声明顺序为顺序;
2.如果一个成员并没有出现在成员初始化列表中,但它有一个默认构造函数,那么默认构造函数必须被调用;
3.如果class有虚表,那么它必须被设定初值;
4.所有上一层的基类构造函数必须被调用;
5.所有虚基类的构造函数必须被调用。
6、 程序员定义的析构函数被扩展的过程?
1.析构函数函数体被执行;
2.如果class拥有成员类对象,而后者拥有析构函数,那么它们会以其声明顺序的相反顺序被调用;
3.如果对象有一个vptr,现在被重新定义
4.如果有任何直接的上一层非虚基类拥有析构函数,则它们会以声明顺序被调用;
5.如果任何虚基类拥有析构函数
7、 构造函数的执行算法?
1.在派生类构造函数中,所有的虚基类及上一层基类的构造函数调用;
2.对象的vptr被初始化;
3.如果有成员初始化列表,将在构造函数体内扩展开来,这必须在vptr被设定之后才做;
4.执行程序员所提供的代码;
8、 哪些函数不能是虚函数
1.构造函数,构造函数初始化对象,派生类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每一个类有一个虚表,每一个对象有一个虚表指针,虚表指针在构造函数中初始化;
2.内联函数,内联函数表示在编译阶段进行函数体的替换操作,而虚函数意味着在运行期间进行类型确定,所以内联函数不能是虚函数;
3.静态函数,静态函数不属于对象属于类,静态成员函数没有this指针,因此静态函数设置为虚函数没有任何意义。
4.友元函数,友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
5.普通函数,普通函数不属于类的成员函数,不具有继承特性,因此普通函数没有虚函数。
9、 虚函数的调用机制
1.分为静态绑定(编译时绑定),动态绑定(运行时绑定);
当某个虚函数通过指针或是引用调用时就会发生动态绑定,具体调用的函数与对象的动态类型相匹配,当一个基类指针指向派生类时,调用的函数是派生类的函数。
2.由于继承导致对象的指针和引用有两种不同的状态,一个是静态类型,一个是动态类型。
静态类型:指针和引用声明时的类型
动态类型:指针和引用实际指向的类型。
10、 什么是类的继承?
1.类与类之间的关系
has-A包含关系,即成员对象。用以描述一个类由多个部件类构成,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类;
use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现;
is-A,继承关系,关系具有传递性;
2.继承的相关概念
所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类;
3.继承的特点
子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使用;
4.继承中的访问控制
public、protected、private
5.继承中的构造和析构函数
继承中的构造:
1子类对象在创建之时会首先调用父类的构造函数
2先执行父类的构造函数再执行子类的构造函数
3父类的构造函数可以被隐式调用或者显式调用
继承中的析构:
1执行自身的析构函数
2执行成员变量的析构函数
3执行父类的析构函数
6.继承中的兼容性原则
凡是基类能解决的问题,公有派生类都可以解决:
1子类对象可以当作父类对象使用
2子类对象可以直接赋值给父类对象
3子类对象可以直接初始化父类对象
4父类指针可以直接指向子类对象
5父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
11、 什么是多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
12、 什么是组合?
1.一个类里面的数据成员是另一个类的对象,即内嵌其他类的对象作为自己的成员;创建组合类的对象:首先创建各个内嵌对象,难点在于构造函数的设计。创建对象时既要对基本类型的成员进行初始化,又要对内嵌对象进行初始化。
2.创建组合类对象,构造函数的执行顺序:先调用内嵌对象的构造函数,然后按照内嵌对象成员在组合类中的定义顺序,与组合类构造函数的初始化列表顺序无关。然后执行组合类构造函数的函数体,析构函数调用顺序相反。
13、 抽象基类为什么不能创建对象?
抽象类是一种特殊的类,它是为了抽象和设计的目的建立的,它处于继承层次结构的较上层。
(1)抽象类的定义:称带有纯虚函数的类为抽象类。
(2)抽象类的作用:抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
(3)使用抽象类时注意:抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
抽象类是不能定义对象的。一个纯虚函数不需要(但是可以)被定义。
14、 纯虚函数定义
一、定义
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>{virtual <类型><函数名>(<参数表>)=0;…};
1.在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
2.纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。
二、纯虚函数引入原因
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。例如,绘画程序中,shape作为一个基类可以派生出圆形、矩形、正方形、梯形等,如果我要求面积总和的话,那么会可以使用一个 shape * 的数组,只要依次调用派生类的area()函数了。如果不用接口就没法定义成数组,因为既可以是circle ,也可以是square ,而且以后还可能加上rectangle,等等.
三、相似概念
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b.运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载。
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
15、 虚函数与纯虚函数的区别在于
1.纯虚函数只有定义没有实现,虚函数既有定义又有实现;
2.含有纯虚函数的类不能定义对象,含有虚函数的类能定义对象;
16、 类什么时候会析构?
1.对象生命周期结束,被销毁时;
2.delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3.对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
17、 为什么友元函数必须在类内部声明?
因为编译器必须能够读取这个结构的声明以理解这个数据类型的大小、行为等方面的所有规则。有一条规则在任何关系中都很重要,那就是谁可以访问我的私有部分。
18、 C++ ⾯向对象的三⼤特征是:封装、继承、多态。
1、所谓封装
就是把客观事物封装成抽象的类,并且类可以把⾃⼰的数据和⽅法只让信任的类或者对象操作,对不可信的进⾏信息隐藏。
⼀个类就是⼀个封装了数据以及操作这些数据的代码的逻辑实体。在⼀个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种⽅式,对象对内部数据提供了不同级别的保护,以防⽌程序中⽆关的部分意外的改变或错误的使⽤了对象的私有部分。
2、所谓继承
是指可以让某个类型的对象获得另⼀个类型的对象的属性的⽅法。它⽀持按级分类的概念。继承是指这样⼀种能⼒:它可以使⽤现有类的所有功能,并在⽆需重新编写原来的类的情况下对这些功能进⾏扩展。通过继承创建的新类称为“⼦类”或者“派⽣类”,被继承的类称为“基类”、“⽗类”或“超类”。继承的过程,就是从⼀般到特殊的过程。要实现继承,可以通过“继承”和“组合”来实现。
继承概念的实现⽅式有两类:
实现继承:实现继承是指直接使⽤基类的属性和⽅法⽽⽆需额外编码的能⼒。
接⼝继承:接⼝继承是指仅使⽤属性和⽅法的名称、但是⼦类必需提供实现的能⼒。
3、所谓多态
就是向不同的对象发送同⼀个消息,不同对象在接收时会产⽣不同的⾏为(即⽅法)。即⼀个接⼝,可以实现多种⽅法。
多态与⾮多态的实质区别就是函数地址是静态绑定还是动态绑定的。如果函数的调⽤,在编译器编译期间就可以确定函数的调⽤地址,并产⽣代码,则是静态绑定。⽽如果函数调⽤的地址不能在编译器期间确定,需要在运⾏时才确定,这就属于动态绑定。
19、 介绍一下C++里面的多态?
多态可以分为静态多态(重载,模板)和动态多态(覆盖,虚函数实现)。
lsy注:关于静态多态,可以看看C++primer这本书,大概在536页左右。
1、静态多态其实就是重载,因为静态多态是指在编译时期决定调⽤哪个函数,根据参数列表来决定。基本原理是,编译器为函数⽣成符号表时的不同规则,重载只是⼀种语⾔特性,与多态⽆关,与⾯向对象也⽆关,但这⼜是 C++中增加的新规则。
当编译器遇到一个模板定义时,它并不生成代码。只有当实例化出模板的一个特定版本时,编译器才会生成代码。
2、动态多态是指通过⼦类重写⽗类的虚函数来实现的,因为是在运⾏期间决定调⽤的函数,所以称为动态多态。函数的运行版本由实参决定,在运行时选择函数的版本,所以称为动态绑定。执行动态绑定,即运行基类指针指向派生类的对象,并调用派生类的函数。
⼀般情况下我们不区分这两个时所说的多态就是指动态多态。
3、动态多态的实现与虚函数表,虚函数指针相关。
虚函数实现原理:虚函数表(存在对象的栈上或堆上)->虚函数指针(常量区)->虚函数(代码区)。
纯虚函数: virtual int fun() = 0;
扩展:⼦类是否要重写⽗类的虚函数?⼦类继承⽗类时, ⽗类的纯虚函数必须重写,否则⼦类也是⼀个虚类,不可实例化。定义纯虚函数是为了实现⼀个接⼝,起到⼀个规范的作⽤,规范继承这个类的程序员必须实现这个函数。
20、 C++ 中重载和重写,重定义的区别
1、重载
翻译⾃overload,是指同⼀可访问区内被声明的⼏个具有不同参数列表的同名函数,依赖于C++函数名字的修饰会将参数加在后⾯,可以是参数类型,个数,顺序的不同。根据参数列表决定调⽤哪个函数,重载不关⼼函数的返回类型。
2、重写
重写翻译⾃override,也叫覆盖。子类重新定义父类中有相同名称和参数的虚函数(virtual)。在继承关系之间。C++利用虚函数实现多态。
重写的特点:
1被重写的函数不能是static的。必须是virtual的
2重写函数必须有相同的类型,名称和参数列表
3重写函数的访问修饰符可以不同。尽管父类的virtual方法是private的,派生类中重写改写为public,protected也是可以的。
3、重定义(隐藏)
派⽣类重新定义⽗类中相同名字的⾮ virtual 函数,参数列表和返回类型都可以不同,即⽗类中除了定义成 virtual 且完全相同的同名函数才不会被派⽣类中的同名函数所隐藏(重定义),否则就是重写了(重写与重定义的区别)。