- ^ _ ^摘自阿秀
- 31、形参和实参的区别?
- 32、值传递、指针传递、引用传递的区别和效率
- 33、静态变量什么时候初始化?
- 34、类的继承是什么?
- 类和类之间的关系
- 继承的相关概念
- 继承的特点
- 继承中访问控制
- 35、面向对象的特性
- ①封装
- ②继承
- ③多态
- 静态多态
- 动态多态
- 36、虚函数
- 37、构造函数为什么不能是虚函数
- 38、析构函数为为什么要定义为虚函数
- 39、虚函数和纯虚函数的区别
- 区别
- 40、为什么要有虚函数,还要有纯虚函数?
- 41、new和malloc的区别
- 42、类成员初始化方式?
- 43、
- 形参只有在函数内部有效,在被调用时分配内存,在调用结束后,释放分配的内存单元。
- 实参在传参时必须要有确定的值。
- 实参和形参应在数量上、类型上、顺序上应严格一致,否则会发生类型不匹配。
- 函数调用发生的数据传送时单向的,只能把实参的值传送给形参。
- 当形参和实参不是指针的时候,他们是不同的变量,在内存中位于不同的位置。
大佬博客讲得好
值传递就是最普通的传递方式,比如函数定义为fun(int a),在调用的地方有int x=6, 使用fun(x)就可以了。这种方式在fun(int a)函数内部的对a的修改 不能导致外部x的变化。
指针传递其实也就是地址传递,函数定义为fun(int *a),形参为指针,这就要求调用的时候传递进去一个参数的地址,例如int x=6; fun(&x)。 这种方式在fun(int a)函数内部的对a的修改能导致外部x的变化。
引用传递只有C++支持,相比前两种方式用的比较少,但也非常有用。引用传递函数定义为fun(int &a),这里&符号是引用而不是取地址的意思,调用方式和值传递类似,例如int x=6; fun(x)。 但是这种方式在fun(int a)函数内部的对a的修改能导致外部x的变化。
效率方面:指针传递和引用传递效率比值传递效率高,一般主张用引用传递,代码逻辑上更加紧凑。(Tip:要去看C++1~30中的指针和引用的区别)
33、静态变量什么时候初始化?静态变量的初始化时间
静态变量存储在虚拟地址空间的数据段和bss段。
- C语言中其在代码执行之前初始化,属于编译期初始化。
- C++中,由于对象的引入,对象的生成必须要调用构造函数,因此,C++规定全局或者局部静态对象当且仅当对象首次用到时进行构造。
has-A包含关系,用以描述一个类由多个部件类,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类;
use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现。
is-A,继承关系,关系具有传递性。
继承的相关概念所谓继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类。
继承的特点子类拥有父类的所有属性和方法,子类可以拥有父类没有属性和方法,子类对象可以当作父类对象使用。
继承中访问控制 35、面向对象的特性 ①封装- 封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。
- 访问限定符
(1)public(共有)
(2)protected(保护)
(3)private(私有)
继承机制是面向对象程序设计使代码可以复用的最重要的手段
继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
静态多态,也叫早绑定(编译期多态)
一个类有多个重名函数,但是参数和返回值不同,这是函数的重载。实例化这个类的对象时,通过对象调用这两个函数,计算机在编译时,就会自动调用对应的函数。
即程序运行之前,在编译阶段就已经确定下来到底要使用哪个函数,
可见:很早就已经将函数编译进去了,称这种情况为早绑定或静态
动态多态,也叫晚绑定(运行期多态)
动态多态是有前提的,它必须以封装和继承为基础。在封装中,将所有数据封装到类中,在继承中,又将封装着的各个类使其形成继承关系
如果想要实现动态多态,就必须使用 虚函数
运行期多态发生的三个条件:继承关系、虚函数覆盖、父类指针或引用指向子类对象。
在父类中,把想要实现多态的成员函数前加上virtual 关键字,使其成为虚函数
使用父类指针指向子类对象,调用函数时,调用的就是对应子类的函数
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”。
- 通过父类型的指针访问子类自己的虚函数
- 当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的实际类型决定。
- 虚函数的实现过程:通过对象内存中的vptr找到虚函数表vtbl,接着通过vtbl找到对应虚函数的实现区域并进行调用。
- 虚函数表中只存有一个虚函数的指针地址,不存放普通函数或是构造函数的指针地址。只要有虚函数,C++类都会存在这样的一张虚函数表,不管是普通虚函数亦或是纯虚函数,亦或是派生类中隐式声明的这些虚函数都会生成这张虚函数表。
- 虚函数表创建的时间:在一个类构造的时候,创建这张虚函数表,而这个虚函数表是供整个类所共有的。虚函数表存储在对象最开始的位置。虚函数表其实就是函数指针的地址。函数调用的时候,通过函数指针所指向的函数来调用函数。
当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有vtbl的。因此,构造函数不能是虚函数。
38、析构函数为为什么要定义为虚函数在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。
解释:
基本上只有涉及到 virtual 函数时,才会发生动态绑定,如果基类的析构函数不是虚函数,那么当基类的指针指向派生类时,delete的时候,只会调用基类的析构函数,而不会调用派生类的析构函数。
C++ 中虚函数和纯虚函数都可以在子类中被重写
区别1、纯虚函数只有定义,没有实现;而虚函数既有定义,也有实现的代码。
纯虚函数一般没有代码实现部分,如 virtual void print() = 0; 2)而一般虚函数必须要有代码的实现部分,否则会出现函数未定义的错误。
2、包含纯虚函数的类不能定义其对象,而包含虚函数的则可以。包含纯虚函数的类是抽象类
40、为什么要有虚函数,还要有纯虚函数?- 1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
- 2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
41、new和malloc的区别- new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
- new申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显示地指出所需内存的大小。
- new分配内存成功会返回与对象类型严格匹配的指针,故new时符合类型安全性的操作符。而malloc内存分配成功则返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型。
- new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。
- new会先调用operator new函数,申请足够内存(底层用malloc实现)。然后调用类型构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(底层用free实现)。
- malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
- 赋值初始化,通过在函数体内进行赋值初始化;
- 列表初始化,在冒号后使用初始化列表进行初始化。
区别:
对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
初始化列表是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式,那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体未执行。
43、


