面向对象语言的特点
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
多态性
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有两种方式,覆盖和重载。覆盖和重载的区别在于,覆盖在运行时决定,重载是在编译时决定。并且覆盖和重载的机制不同,例如在 Java 中,重载方法的签名必须不同于原先方法的,但对于覆盖签名必须相同。
抽象类:
不能用来创建。只能用来继承 子类必须 实现 父类抽象类的纯虚函数 这样使得抽象类的子类都强制具体化 抽象类 为子类提供一个接口文档的作用 抽象类还可以统一指针 借助虚函数实现程序的多态性 抽象类可以派生出抽象类,只要该类没有全部覆盖掉纯虚函数,那么子类就是抽象类
虚函数:
没加virtual 的函数为静态联编。。编译时函数地址就已经确定。 反之virtual 的函数为动态联编。。只有在运行时。。在能找到函数地址 虚函数 中没有采用指针或者引用。那就无法实现动态联编 虚函数靠指针实现动态联编。。多态靠虚函数实现多态
extern “C”
extern “C”是连接申明(linkage declaration),被extern “C”修饰的变量和函数是按照C语言方式编译和连接 && 实现C++与C及其它语言的混合编程。
因为C++为了实现函数重载改变了函数命的编译方法。是带有函数参数名, 直接引用C的函数是不可以的。 当C++ 调用C 时 要把头文件加上extern”C” {#include”cExample.h”} 。不去找带有函数参数类型的函数名。这样C++的编译器才能找到C的函数。 不管是C调用C++,还是C++调用C 都是用extern”C” 包含导入文件 已确定编译链接方式。 当c被调用时。C的头文件中要用extern int add( int x, int y ); 当c++被调用时。C++的头文件中要用extern “C” int add( int x, int y );
void foo( int x, int y ); 该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同 样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以”.”来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
类成员函数的重载、覆盖和隐藏区别?
- a.成员函数被重载的特征:
-
- 相同的范围(在同一个类中);
-
- 函数名字相同;
-
- 参数不同;
-
- virtual 关键字可有可无。
- 覆盖是指派生类函数覆盖基类函数,特征是:
-
- 不同的范围(分别位于派生类与基类);
-
- 函数名字相同;
-
- 参数相同;
-
- 基类函数必须有virtual 关键字。
- “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
-
- 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
-
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
只有当函数名,参数都相同且有virtual关键字叫覆盖,实现多态。其他的都是隐藏!!隐藏了。子类找不到函数
请说出static和const关键字尽可能多的作用
static关键字至少有下列n个作用:
- 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
- 在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
- 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
- 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
- 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
const关键字至少有下列n个作用:
- 欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
- 对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
- 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
- 对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
- 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator(const classA& a1,const classA& a2); operator的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错: classA a, b, c; (a * b) = c; // 对a*b的结果赋值 操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。
剖析: 惊讶吗?小小的static和const居然有这么多功能,我们能回答几个?如果只能回答1~2个,那还真得闭关再好好修炼修炼。
这个题可以考查面试者对程序设计知识的掌握程度是初级、中级还是比较深入,没有一定的知识广度和深度,不可能对这个问题给出全面的解答。大多数人只能回答出static和const关键字的部分功能。
C++的虚函数表如何实现函数调用的多态?
指针的类型保存了必要的操作信息
C++ 基类指针可以指向派生类的对象 并且调用派生各自的虚函数,是因为派生类的内存分布中,基类部分是相同的,并且从最开始的位置。他们的vtable虚函数表地理偏移位置一样。
指针的类型被转换 他的内容 就是他指向的地址 是不发生变化,但是编译器在编译的时候对他的理解发生了变化,在编译这两种 *int ++ *double ++
操作时 指针运算的偏移量是根据指针的类型运算的,比如 *int++
地址偏移了四个位置, *double ++
偏移了八个位置。
指针记录的信息 不仅仅是地址,还是编译对他的理解,也就是 这个类型的内存分布的大小.
一个class只要有一个虚函数,那么每一个class的实例就被安插上一个由编译器内部产生的指针,指向该类的虚函数表(virtual table)。
virtual table 的第一项是表示class的类型。
因为,基类指针的特殊性,它可以指向基类对象,也可以指向派生类对象。故:pFather->foo( ) ;
virtual table 的表格里是class中的每个虚函数地址。
一个class只会有一个virtual table。派生类的virtual table是在基类的virtual table上增加,修改的。
派生类中的虚函数会改写(overriding)与基类中同名且参数相同的虚函数,把virtual table表中相应的基类虚函数地址改写为相应派生类虚函数的地址。
以上工作都是由编译器完成的。执行期要做的就是在特定的virtual table表项中激活相应的虚函数,然后根据virtual table首项的类型信息,正确执行此虚函数。
例如:ptr->foo( ) ;
一般而言,我并不知道ptr所指对象的真正类型。然而我知道。经由ptr可以存取到该对象的virtual table。
虽然我不知道哪一个foo( )实体会被调用,但我知道每一个foo( )函数的地址都放在虚表的第二项。
故:根据以上信息,编译器可以将该调用转化为:
( *ptr->vptr[1] )( ptr ) ;
在运行阶段直接找这个指针偏移两个单位(4字节)地址的方法执行即可。他是父类找的就是父类的虚函数表的第二位,他是子类就找子类的虚函数表的第二位,子类如果覆盖了这个方法,就会替换掉父类的这个第二位的方法。运行时的实现了多态