C++面试知识点汇总:
一、多态性:实现了“一个接口,多种方法”。程序在运行时才决定调用的函数。
从实现的角度来讲,多态可以分为两类:编译时的多态性和运行时的多态性。前者是通过静态联编来实现的,比如C++中通过函数的重载和运算符的重载。后者则是通过动态联编来实现的,在C++中运行时的多态性主要是通过虚函数来实现的。
1)虚函数:(1)因为虚函数使用的基础是赋值兼容,而赋值兼容成立的条件是派生类从基类公有派生而来。所以使用虚函数,派生类必须是基类公有派生的;(2)定义虚函数,不一定要在最高层的类中,而是看在需要动态多态性的几个层次中的最高层类中声明虚函数;(3)虽然在上述示例代码中main()主函数实现部分,我们也可以使用相应图形对象和点运算符的方式来访问虚函数,如:rectangcle.showArea(),但是这种调用在编译时进行静态联编,它没有充分利用虚函数的特性。只有通过基类对象的指针或者引用来访问虚函数,才能获得动态联编的特性;(4)一个虚函数无论配公有继承了多少次,它仍然是虚函数;(5)虚函数必须是所在类的成员函数,而不能是友元函数,也不能是静态成员函数。因为虚函数调用要靠特定的对象类决定该激活哪一个函数;(6)内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的即使虚函数在类内部定义,编译时将其看作非内联;(7)构造函数不能是虚函数,但析构函数可以是虚函数;
纯虚函数:类内的虚函数声明语句的分号前加“=0”即可,必须在类的外部定义函数体。
至少含有一个纯虚函数的类,为抽象类。
抽象类只能作为其他类的基类来使用,不能建立抽象类对象;(2)不允许从具体类中派生出抽象类(不包含纯虚函数的普通类);(3)抽象类不能用作函数的参数类型、返回类型和显示转化类型;(4)如果派生类中没有定义纯虚函数的实现,而只是继承成了基类的纯虚函数。那么该派生类仍然为抽象类。一旦给出了对基类中虚函数的实现,那么派生类就不是抽象类了,而是可以建立对象的具体类;
2)运算符重载:在实际的运算符重载函数声明当中,要不定义其为要操作类的成员函数或类的友元函数。
3)函数重载:是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
多态有两个好处:
1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用
二、动态绑定怎么实现?
C++的函数调用默认不使用动态绑定。要触发动态绑定,必须满足两个条件:
1)只有指定为虚函数的成员函数才能进行动态绑定
2)必须通过引用或指针进行函数调用
因为每个派生类对象中都拥有基类部分,所以可以使用基类类型的指针或引用来引用派生类对象。
三、指针和引用的区别:
1)首先,引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。而声明指针是可以不指向任何对象,也正是因为这个原因,使用指针之前必须做判空操作,而引用就不必。
2)其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。
3)再次,引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节。
4)最后,引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)。
5)作为参数传递时,两者不同。指针传递参数的本质是值传递,被调函数对形参的任何操作作为局部变量进行的,不会影响到主调函数的实参。如果想通过指针参数传递来改变主调函数中的相关变量,就得使用指针的指针,或者指针引用。引用传递参数的本质是传递实参的地址,被调函数对形参的任何操作都影响了主调函数的实参。
总而言之,言而总之——它们的这些差别都可以归结为"指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。"
四、四种强制类型转换:(网易两次面试均出现)
C风格的类型转换——强制类型转换(type) expression.
C++中类型转换四种分别为:static_cast ,const_cast,dynamic_cast,reinterpret_cast.
1)static_cast. 最常用的类型转换符,在正常状况下的类型转换,如把int转换为float,如:int i;float f; f=(float)i;或者f=static_cast<float>(i);
还可以用于将编译器无法自动执行的类型转换。
例如,void *p=&d; double *dp=static_cast(double *)(p) //将void类型指针转换为double类型。
2)const_cast. 用于取出底层const属性,把const类型的指针变为非const类型的指针.如:const int *fun(int x,int y){}int *ptr=const_cast<int *>(fun(2,3)).
3)dynamic_cast.用于将基类的指针或引用类型安全地转换成派生类的指针或引用,在运行时检查该转换是否类型安全,但只在多态类型时合法,即该类至少具有一个虚函数。
例如:基类Base(至少有一个虚函数),派生类Derived,
Base *bp; Derived *dp=dynamic_cast<Base*>(bp)。
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置的基本数据类型的强制转换。
(3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话前者返回0,后者抛出bad_cast异常。
(4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
B中需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,
(5)只有定义了虚函数的类才有虚函数表。dynamic_cast与static_cast具有相同的基本语法,dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。如:
class C
{
//…C没有虚拟函数
};
class T{
//…
}
int main()
{
dynamic_cast<T*> (new C);//错误
}
此时如改为以下则是合法的:
class C
{
public:
virtual void m() {};// C现在是 多态
}
4)interpret_cast
interpret是解释的意思,reinterpret即为重新解释,此标识符的意思即为数据的二进制形式重新解释,但是不改变其值。如:int i; char *ptr="hello freind!"; i=reinterpret_cast<int>(ptr);这个转换方式很少使用。
五、操作符重载
operator是C++的关键字,它和运算符一起使用,表示一个运算符函数。
理解时应将operator=整体上视为一个函数名。
操作符重载的实现方式有两种,即通过“友元函数”或者“类成员函数”。
利用友元函数重载二元操作符”-“时,形式参数是两个,而利用类成员函数时,形式参数却只有一个。这时因为类成员函数中存在this指针,这相当于一个参数,所以类成员实现操作符重载需要的形式参数比原来少一个,这比如:利用类成员函数实现一元操作符”-“,就不需要参数了。也正是因为这个原因,友元函数实现的操作符重载是有限制的,比如:[],(),->和=不能利用友元函数实现运算符的重载。
运算符重载也是要遵循一些原则的:
1.C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符。
2.C++中绝大部分的运算符可重载,除了成员访问运算符.,作用域运算符::,长度运算符sizeof以及条件运算符?:。
3.运算符重载后不能改变运算符的操作对象(操作数)的个数。如:"+"是实现两个操作数的运算符,重载后仍然为双目运算符。
4.重载不能改变运算符原有的优先级和原有的结合性。
6.运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质。
为了区分前后,增量运算符中,放上一个整数形参,就是后增量运行符,它是值返回,对于前增量没有形参,而且是引用返回。即用++()表示前自增,用++(int)后自增。
六、内存对齐原则
显示规定采用几个字节对齐
#pragma pack(n) c编译器将按照n个字节对齐。
•原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
•原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
•原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
七、模板怎么实现的?
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对数据成员和成员函数类型不同的类。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
1)函数模板通式
1、函数模板的格式:
template <class 形参名,class形参名,......>返回类型 函数名(参数列表)
{
函数体
}
其中template和class是关键字,class可以用typename关键字代替,在这里typename和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一旦声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称为实例化了函数模板的一个实例。比如swap的模板函数形式为
template <class T> void swap(T& a, T& b){},
2、注意:对于函数模板而言不存在 h(int,int)这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3) 这样的调用,或者int a, b; h(a,b)。
2)类模板通式
1、类模板的格式为:
template<class 形参名,class形参名,…> class类名
{ ... };
类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一旦声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如
template<class T> class A{public: T a; T b; T hy(T c, T &d);};
在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A<int> m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int所代替。当类模板有两个模板形参时创建对象的方法为A<int, double> m;类型之间用逗号隔开。
3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: 'a' uses undefined class 'A<int>'),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int型传递给模板形参。要把类模板形参调置为int型必须这样指定A<int> m。
4、在类模板外部定义成员函数的方法为:
template<模板形参列表>函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:template<class T1,class T2> void A<T1,T2>::h(){}。
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
5、再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
有三种类型的模板形参:类型形参,非类型形参和模板形参。
八、指针和const的用法(四种情况说了一下)
const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性
指向常量的指针:
const int *pa;
int const *pa;
两者等价。因为指向常量的指针有时候会指向常量,所以它具有这个性质:“不能靠解引用改变它指向的对象的值”,以此保护它所指向的常量的常量性:
*pa =d; // 不可行(d是已经声明过的整型)
但指针本身的值是可变的:
pa=& d; // 可行(d是已经声明过的整型)
而且指向常量的指针有时候也会指向变量,如下:
int t,u;
const int *pa;
pa =&t; //可行,指向变量t
pa =&u; //也可行,指向变量u
我们可以把它理解成:“为了指向常量而发明的指针”,这样比较贴切。
常量指针:
int *const pa =&n; // n是之前已经声明过的整型变量,注意必须是变量,理由见下
“常量指针”即指针本身的值是常量,但“能靠解引用改变它指向的对象的值”,如下:
pa=&d; // 不可行(d是已经声明过的整型)
*pa =d; // 可行(d是已经声明过的整型)
因为常量指针也是一种const常量,所以它同样必须在第一次声明时就初始化,不过它的初始值缩小为只能是变量(的地址),因为只有变量才能确保以后能靠解引用而改变它指向的对象的值。这使得常量指针不象一般的const常量,用变量或常量初始化都可以。
也就是说,常量指针反而总是指向变量的。
九、虚函数、纯虚函数、虚函数与析构函数?(纯虚函数如何定义,为什么析构函数要定义成虚函数)
见程序员面试宝典4,P117。
十、内联函数(讲了一下内联函数的优点以及和宏定义的区别)
1)什么是内联函数?
答:定义在类声明之中的成员函数,在函数返回类型前加上inline关键字,将自动地成为内联函数。将函数定义为内联函数,一般就是将在程序中每个调用点上“内联地”展开。
2)内联函数适用情况:
1.一个函数被重复调用;
2.函数只有几行,且不包含for,while,switch语句。
内联函数应该放在头文件中定义,这一点不同于其他函数。
内联函数可能在程序中定义不止一次,只要内联函数的定义在某个源文件中只出现一次,而且在所有的源文件中,其定义必须是相同的。如果inline函数的定义和声明是分开的,而在另外一个文件中需要调用这些inline函数得时候,内联是无法在这些调用函数内展开的。这样内联函数在全局范围内就失去了作用。解决的办法就是把内联函数得定义放在头文件中,当其它文件要调用这些内联函数的时候,只要包含这个头文件就可以了。把内联函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并保证在调用点该函数的定义对调用点可见。
内联函数具有一般函数的特性,它与一般函数所不同之处只在于函数调用的处理:
一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。
内联机制被引入C++作为对宏(Macro)机制的改进和补充(不是取代)。内联函数的参数传递机制与普通函数相同。但是编译器会在每处调用内联函数的地方将内联函数的内容展开。这样既避免了函数调用的开销又没有宏机制的前三个缺陷。
但是程序代码中的关键字"inline"只是对编译器的建议:被"inline"修饰的函数不一定被内联(但是无"inline"修饰的函数一定不是)。
内联使用不恰当是会有副作用的:会带来代码膨胀,还有可能引入难以发现的程序臭虫。
a.从inline的原理,我们可以看出,inline的原理,是用空间换取时间的做法,是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。所以,如果函数体代码过长或者函数体重有循环语句,if语句或switch语句或递归时,不宜用内联。
b.关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。内联函数调用前必须声明。
内联函数:
(1)内联函数定义和作用:
将一个函数声明为inline,那么函数就成为内联函数。内联函数通常就是它在程序中每个调用点上“内联地”展开。从定义上看,内联函数跟一般函数不一样,一般函数调用的时候是需要调用开销的(比如出栈入栈等操作),内联函数从定义上看更像是宏,但是跟宏不一样。
内联函数的作用主要就是使用在一些短小而使用非常频繁的函数中,为了减少函数调用的开销,为了避免使用宏(在c++中,宏是不建议使用的)。比如内联函数inline int func(int x){return x*x;}在调用的时候cout<<func(x)<<endl,在编译时将被展开为:
cout<<(x*x)<<endl;
(3)如何使用内联函数和禁止内联:
要让一个函数称为内联函数,有两种方法:一种是把函数加上inline关键字;一种是在类的说明部分定义的函数,默认就是内联的。
要禁止编译器进行内联,可以使用#pragma auto_inline编译指令或者改变编译参数。
内联函数注意事项:
(1)内联函数一定会内联展开吗?答案是否定的。对于内联函数,程序只是提供了一个“内联建议”,即建议编译器把函数用内联展开,但是真正是否内联,是由编译器决定的,对于函数体过大的函数,编译器一般不会内联,即使制定为内联函数。
(2)在内联函数内部,不允许用循环语句和开关语句(if或switch)。内联函数内部有循环和开关,也不会出错,但是编译器会把它当做非内联函数的。
(3)关键字inline必须与函数定义体放在一起才能使函数真正内联,仅把inline放在函数声明的前面不起任何作用。因为inline是一种用于实现的关键字,不是一种用于声明的关键字。内联函数的声明是不需要加inline关键字的,内联函数的定义是必须加inline的(除了类的定义部分的默认内联函数),尽管很多书声明定义都加了,要注意理解声明和定义的区别。
(4) 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件*享。
(5) 内联函数的定义必须在第一次调用之前。注意,这里是定义之前,不仅仅是声明之前。对于普通函数,可以在调用之前声明,调用代码之后具体定义(实现函数),但是内联函数要实现内联,必须先定义再调用,否则编译器会把在定义之前调用的内联函数当做普通函数进行调用。
(6) 说明:上面这些inline的注意事项,在编程时要自己注意,因为上面的注意事项不遵守很多并不会引起编译错误,只是会导致写了inline的函数不是内联函数,从而与预期的目的不一样。所以很多没法用程序实例说明到底编译器是按照inline还是非inline调用的,或许分析汇编代码能看出,但是水平有限,就不多分析了。
使用内联函数至少有如下两个优点。
(1)减少因为函数调用引起开销,主要是参数压栈、栈帧开辟与回收,以及寄存器保存与恢复等。从而提高函数的执行效率。
(2)内联后,编译器在处理调用内联函数的函数(如上例中的foo()函数)时,因为可供分析的代码更多,因此它能做的优化更深入彻底。前一条优点对于开发人员来说往往更显而易见一些,但往往这条优点对最终代码的优化可能贡献更大。
内联函数与宏定义的区别:
宏是在预处理时进行的代码替换,内联是在编译时进行的;
内联函数是真正的函数,只是在调用时,没有调用开销,在编译阶段插入代码,像宏一样进行展开;
内联函数会进行参数匹配检查,宏定义没有参数匹配检查。
十一、const和typedef(主要讲了const的用处,有那些优点)
1)#define:
宏不仅可以用来代替常数值,还可以用来代替表达式,甚至是代码段。(宏的功能很强大,但也容易出错,所以其利弊大小颇有争议。)
宏的语法为:
#define 宏名称 宏值
注意,宏定义不是C或C++严格意义上的语句,所以其行末不用加分号结束。
作为一种建议和一种广大程序员共同的习惯,宏名称经常使用全部大写的字母。
利用宏的优点:
1)让代码更简洁明了
当然,这有赖于你为宏取一个适当的名字。一般来说,宏的名字更要注重有明确直观的意义,有时宁可让它长点。
2)方便代码维护
对宏的处理,在编译过程中称为“预处理”。也就是说在正式编译前,编译器必须先将代码出现的宏,用其相应的宏值替换。所以在代码中使用宏表达常数,归根结底还是使用了立即数,并没有明确指定这个量的类型。
2)typedefine:
typedef与#define的区别
typedef则常用来定义关键字、冗长的类型的别名,是在编译时处理的。
宏定义只是简单的字符串代换(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能。
typedef (int*) pINT;
以及下面这行:
#define pINT2 int*
效果相同?实则不同!实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b;
表示定义了一个整型指针变量a和整型变量b。
注意:两者还有一个行尾;号的区别哦!
3)const:
const部分
常量定义的格式为:
const 数据类型 常量名 =常量值;
而const定义的常量具有数据类型,定义数据类型的常量便于编译器进行数据检查,使程序可能出现错误进行排查。常量必须一开始就指定一个值,然后,在以后的代码中,我们不允许改变此常量的值。
区别:
1.内存空间的分配上。define进行宏定义的时候,不会分配内存空间,编译时会在main函数里进行替换,只是单纯的替换,不会进行任何检查,比如类型,语句结构等,即宏定义常量只是纯粹的置放关系,如#define null 0;编译器在遇到null时总是用0代替null它没有数据类型.而const定义的常量具有数据类型,定义数据类型的常量便于编译器进行数据检查,使程序可能出现错误进行排查,所以const与define之间的区别在于const定义常量排除了程序之间的不安全性.
2.const常量存在于程序的数据段,#define常量存在于程序的代码段。
3. 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
十二、排序算法有哪些?快速排序怎么实现的?最好时间复杂度,平均时间复杂度
十三、链接指示:extern “C”(作用)
C++程序有时需要调用其他语言编写的函数,最常见的是调用C语言编写的函数。像所有其他名字一样,其他语言中的函数名字也必须在C++中进行声明,并且该声明必须指定返回类型和形参列表。对于其他语言编写的函数来说,编译器检查其调用方式与处理普通C++函数的方式相同,但生成的代码有所区别。C++使用链接指示(linkage directive)指出任意非C++函数所用的语言。
声明一个非C++函数:
链接指示可以有两种形式:单个或复合。链接指示不能出现在类定义或函数定义的内部。
例如:
单语句:extern "C" size_t strlen(const char *);
复合语句:extern "C" {
int strcmp(const char*, const char*);
char *strcat(char*, const char*);
}
链接指示与头文件:
复合语句:
extern "C" {
#include <string.h>
}
指向extern "C"函数的指针:
编写函数所用的语言是函数类型的一部分。(指向其他语言编写的函数的指针必须与函数本身使用相同的链接指示)
extern "C" void (*pf)(int);
当我们使用pf调用函数时,编译器认定当前调用的是一个C函数。
指向C函数的指针与指向C++函数的指针是不一样的类型。
链接指示对整个声明都有效:
当我们使用链接指示时,他不仅对函数有效,而且对作为返回类型或形参类型的函数指针也有效。
//f1是一个C函数,它的形参是一个指向C函数的指针
extern "C" void f1( void(*)(int) );
因为链接指示同时作用于声明语句中的所有函数,所以如果我们希望给C++函数传入一个指向C函数的指针,则必须使用类型别名。
//FC是一个指向C函数的指针
extern "C" typedef void FC( int );
//f2是一个C++函数,该函数的形参是指向C函数的指针
void f2(FC *);
十四、c语言和c++有什么区别?(大体讲了一下,继承、多态、封装、异常处理等)
封装:是面向对象的特征之一,是对象和类概念的主要特性。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信任的类或者对象操作,对不可信的进行信息隐藏。
继承:使用现有类的所有功能,并在无需重新编写原来类的情况下,对这些功能进行扩展。
继承过程,是从一般到特殊的过程。
继承的实现方式有三类:实现继承、接口继承和可视继承。
1) 实现继承:使用基类的属性和方法而无需额外编码的能力。
2) 接口继承:仅使用属性和方法的名称,但是子类必须提供实现的能力。
3) 可视继承:子窗体实现基窗体的外观和实现代码的能力。
多态:允许将子类类型的指针赋给基类类型的指针。允许将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
实现多态的两种方式:覆盖和重载。
覆盖:子类重新定义基类虚函数的做法。
重载:允许存在多个同名函数,而这些函数的参数列表不同(或许参数个数不同,或者参数类型不同,或者两者都不同)。
异常处理:主要用于针对程序在运行时刻出现错误而提供的语言层面的保护机制。它允许开发者最大限度地发挥,针对异常处理进行相应的修改调整。
C++语言除了提供异常的关键字语法支持以外,其标准库中支持异常处理而封装异常类也很好的为应用程序中异常处理判断使用提供直接的帮助。
C++的异常类分别定义在头文件中,如最通用的异常类exception,它只报告异常的发生,不提供任何额外信息。
1)C++语言中针对异常处理提供了三个关键字,分别为try、throw与catch。
异常检测是采用throw表达式实现的。通常throw关键字后会跟随着一个操作数,该操作数可以是一个表达式、一个C++内置类型数据或者为类类型的对象等。
try体中可以直接抛出异常,或者在try体中调用的函数体中间接的抛出。try块中代码体作为应用程序遵循正常流程执行。一旦该代码体中出现异常操作,会根据操作的判断抛出对应的异常类型。随后逐步的遍历catch代码块,此步骤与switch控制结构有点相像。当遍历到对应类型catch块时,代码会跳转到对应的异常处理中执行。如果try块中代码没有抛出异常,则程序继续执行下去。
catch关键字的代码块则主要负责异常的捕获处理,catch块可以拥有多个,并且紧跟着try块之后。每个catch块部分主要由圆括号中参数类型,紧跟其后的大括号中异常代码处理部分构成。catch块中随后圆括号中参数类型,该类型可以拥有变量名称或者对象名称,也可以直接是对应的参数类型。如果是对应的类型对象,则在catch块中可以引用该对象处理。而如果是只有数据类型没有相应参数名称的参数,则直接匹配对应的参数类型即可,程序会从try块中转移至catch块中执行。
前面小节在讲述异常抛出处理时已经对其使用的原理作过解释,即当在可能抛出异常的try代码块中通过throw抛出了异常。随后开始匹配catch块中的参数类型是否符合异常抛出时的类型,如果符合则执行对应catch块中的代码处理。