您的位置:首页 > 其它

《程序猿佳能专访》学习记录7

2015-08-14 17:33 375 查看
印象笔记同步分享:《程序猿面试宝典》学习记录7



《程序猿面试宝典》学习记录7


第11章 继承与接口

整个C++程序设计全面环绕面向对象的方式进行。类的继承特性是C++的一个很重要的机制。

继承特性能够使一个新类获得其父类的操作和数据结构,程序猿仅仅需在新类中添加原有类没有的成分。

在面试过程中,各大企业会考量你对虚函数、纯虚函数、私有继承、多重继承等知识点的掌握程度


11.1 覆盖

1、下面代码的输出结果是什么?

#include<iostream>using namespace std;

class A
{
protected:
int m_data;
public:
A(int data = 0)
{
m_data = data;
}
int GetData()
{
return doGetData();
}
virtual int doGetData()
{
return m_data;
}
};

class B : public A
{
protected:
int m_data;
public:
B(int data = 1)
{
m_data = data;
}
int doGetData()
{
return m_data;
}

};

class C : public B
{
protected:
int m_data;
public:
C(int data = 2)
{
m_data = data;
}
};

int main ()
{
C c(10);

cout << c.GetData() <<endl;
C中没有定义。故调用B中的。可是B中也没有定义,故调用A中的GetData(),由于A中的doGetData()是虚函数,所以调用B类中的doGetData()。而B类的doGetData()返回B::m_data, 故输出 1。

cout << c.A::GetData() <<endl;
由于A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data。故输出 1。
cout << c.B::GetData() <<endl;
肯定是B类的返回值 1 了。

cout << c.C::GetData() <<endl;
C类中未重定义GetData(),故调用从B继承来的GetData()。可是B类也没有定义,所以调用A中的GetData(),由于A中的doGetData()是虚函数,所以调用B类的doGetData(),股输出为1

cout << c.doGetData() <<endl;
B类的返回值 1 了。
cout << c.A::doGetData() <<endl;
由于直接调用了A的doGetData() ,所以输出0。
cout << c.B::doGetData() <<endl;
调用了B的doGetData(),所以输出 1。
cout << c.C::doGetData() <<endl;
调用了B的doGetData(),所以输出 1。
return 0;
}


总结:这里要注意存在一个就近调用,假设父类存在相关接口则优先调用父类接口,假设父类也不存在相关接口则调用祖父辈接口。

考点2:虚函数覆盖虚函数

下面代码输出结果是什么?

#include<iostream>using namespace std;

class A
{
public:
void virtual f()
{
cout<<"A"<<endl;
}
};

class B : public A
{
public:
void virtual f()
{
cout<<"B"<<endl;
}
};

int main ()
{
A* pa=new A();
pa->f();          这个非常明显A
B* pb=(B*)pa;
pb->f();          这个强制将pa拷贝到pb,所以pb指向A

delete pa,pb;     删除pa,pb所指向的地址,可是pa、pb指针并没有删除,悬浮指针
pa=new B();
pa->f();          B
pb=(B*)pa;
pb->f();          B

return 0;
}


11.2 私有继承

考点1:公有继承和私有继承的差别

公有继承(public)

公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。不能被这个派生类的子类所訪问。

私有继承(private)

私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员。而且不能被这个派生类的子类所訪问。(私有继承使父类中的函数转化为私有

保护继承(protected)

保护继承的特点是基类的全部公有成员和保护成员都成为派生类的保护成员,而且仅仅能被它的派生类成员函数或友元訪问。基类的私有成员仍然是私有的。

public         protected       private
共同拥有继承     public         protected       不可见
私有继承     private        private         不可见
保护继承     protected      protected       不可见


考点2:保护继承和私有继承后,子类对象想訪问父类成员

公有继承:子类对象能够直接訪问父类的public的成员

保护继承:继承之后的类相对于父类是独立的,不能直接訪问父类成员,其类对象,在公共场合无法使用基类成员,也仅仅能通过自己的成员函数来訪问父类的protected和public成员。

私有继承:继承之后也不能直接訪问父类成员,仅仅能通过子类的成员函数来訪问父类的protected和public成员。

#include
class Animal
{
public:
Animal(){}
void eat(){cout << "eat\n";}
};
class Giraffe:protected Animal
{
Giraffe(){}
void StrechNeck(double)
{cout << "strechneck\n";}
void take()
{
eat(); //ok
}
};
void main()
{
Giraffe girl;
girl.eat();            错误 保护继承不能直接訪问父类成员
girl.take();           正确 保护继承仅仅能通过子类的成员函数来訪问父类成员
girl.StretchNeck();    正确 保护继承仅仅能通过子类的成员函数来訪问父类成员
}


考点3:派生类的三种继承深入了解

#include <iostream>#include <stdio.h>
class Parent
{
public:
Parent(int var = -1)
{
m_nPub = var;
m_nPtd = var;
m_nPrt = var;
}
public:
int m_nPub;
protected:
int m_nPtd;
private:
int m_nPrt;
};
class Child1:public Parent
{
public:
int GetPub(){return m_nPub;};
int GetPtd(){return m_nPtd;};
int GetPrt(){return m_nPrt;};    错误 父类私有变量不能被子类訪问
};
class Child2:protected Parent
{
public:
int GetPub(){return m_nPub;};
int GetPtd(){return m_nPtd;};
int GetPrt(){return m_nPrt;};   错误 父类私有变量不能被子类訪问
};
class Child3:private Parent
{
public:
int GetPub(){return m_nPub;};
int GetPtd(){return m_nPtd;};
int GetPrt(){return m_nPrt;};   错误 父类私有变量不能被子类訪问
};
int main()
{
Child1 cd1;
Child2 cd2;
Child3 cd3;

int nVar = 0;

//公有继承
cd1.m_nPud = nVar;          正确公有继承訪问并改变公有变量
cd1.m_nPtd = nVar;          错误公有继承m_nPtd能够被继承訪问可是不能被改动
nVar = cd1.GetPtd();        正确公有继承通过函数訪问父类的公有变量
//保护继承
cd2.m_nPtd = nVar;          错误 保护继承 保护继承不能直接訪问父类的成员
nVar = cd2.GetPtd();        正确 保护继承 通过函数来訪问父类成员
//私有继承
cd3.m_nPub = nVar;          错误 是有继承 不能直接改动父类的公有变量
nVar = cd3.GetPtd();        正确 能够通过函数訪问父类的保护变量
return 0;
}


11.3 虚函数继承和虚继承

考点1:理解虚方法(虚函数)

每个对象里有虚表指针,指向虚表。虚表里存放了虚函数的地址,虚函数表是顺序存放虚函数地址的,不须要用到链表。

所以类中的每个对象都有一个链表来存虚方法地址,那就是虚表。

虚函数的实现要求对象携带额外的信息。这些信息用于在执行时确定后该对象应该调用哪一个虚函数。典型的情况下。这个信息具有一种被称为vptr虚函数指针的指针形式,vptr指向一个被称为vtbl的虚函数表函数指针数组。每个虚函数都关联到vtbl。当一个对象调用了虚函数,实际的被调用函数通过以下步骤确定,找到对象的vptr指向的vtbl,之后在vtbl中寻找合适的函数指针。

虚拟函数使用的缺点

  长处讲了一大堆,如今谈一下缺点,虚函数最基本的缺点是运行效率较低,看一看虚拟函数引发的多态性的实现过程,你就能体会到当中的原因,另外就是因为要携带额外的信息(VPTR)。所以导致类多占的内存空间也会比較大,对象也是一样的

考点2:虚函数、虚函数表、虚函数指针的联系

每个具有虚函数的类都有一个虚函数表VTABLE,里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表VTABLE是这个类的全部对象所共同拥有的,也就是说不管用户声明了多少个类对象,可是这个VTABLE虚函数表仅仅有一个

在每一个具有虚函数的类的对象里面都有一个VPTR虚函数指针,这个指针指向VTABLE的首地址。每一个类的对象都有这么一种指针。

考点3:虚函数的继承

1)空类、单一继承的空类、多重继承的空类所占空间大小为:1(字节。下同);

2)一个类中,虚函数本身、成员函数(包含静态与非静态)和静态数据成员都是不占用类对象的存储空间的;

3)类对象的大小=各非静态数据成员(包含父类的非静态数据成员但都不包含全部的成员函数)的总和+ vfptr指针(多继承下可能不止一个)+vbptr指针(多继承下可能不止一个)+编译器额外添加的字节。

4)当类中声明了虚函数(无论是1个还是多个)。那么在实例化对象时,编译器会自己主动在对象里安插一个指针vPtr指向虚函数表VTable;

#include<iostream>#include<memory.h>#include<assert.h>

using namespace std;
class A
{
char k[3];                 所占的大小为3
public:
virtual void aa(){};       虚指针大小为4
};
class B : public virtual A
{
char j[3];
public:
virtual void bb(){};
};
class C : public virtual B
{
char i[3];
public:
virtual void cc(){};
};
int main(int argc, char *argv[])
{
cout << "sizeof(A): " << sizeof(A) << endl;   大小为4(char)+4(虚表)=8
cout << "sizeof(B): " << sizeof(B) << endl;   大小为8(A副本)+4(char)+4(虚表)=16
cout << "sizeof(C): " << sizeof(C) << endl;   大小为16(B副本)+4(char)+4(虚表)=24
return 0;
}


考点3:什么是虚继承?它和一般的继承有什么不同?有什么用

虚拟继承是多重继承中特有的概念。虚拟基类是为了解决多重继承而出现的,能够节省内存空间

请看下图:



在图 1中,类D接触自类B和类C,而类B和类C都继承自类A,因此出现了图 2所看到的的情况。

在图 2中。类D中会出现两次A。为了节省内存空间,能够将B、C对A的继承定义为虚拟继承。而A成了虚拟基类。最后形成了图 3。

代码例如以下:

class A;
class B : public virtual A;
class C : public virtual A;
class D : public B,public C;


考点4:区分虚函数继承和虚继承

虚拟继承是多重继承中特有的概念,是为解决多重继承的。用虚继承能够节省内存空间

虚函数是面向对象多态性的主要方式,通过继承基类中的虚函数在子类中重载实现不同操做。继承的虚函数在子类中不须要加virtual。默认就是虚函数。

能够被它的子类覆盖。

考点4:区分虚继承和直接继承

#include <stdio.h>

class A {
public:
int a;
};                               sizeof(A)=4

class B : virtual public A {
public:
  int b;                       sizeof(B)=4(虚表)+4(A副本)+4(自己变量)=12
};

class C : virtual public B {     sizeof(c)= 12(B副本)+4(虚表) = 16  假设这里改为直接继承,那么sizeof(c)=12
};

int main() {
printf("%d\n", sizeof(C));
return 0;
}


再举一个样例:

#include <stdio.h>

class A {
public:
int a;
};                                  sizeof(A) = 4

class B : virtual public A {
};                                  sizeof(B) =4+4=8

class C : virtual public A {        sizeof(C) =4+4=8
};

class D : public B, public C{       sizeof(D)=8+8-4=12 这里须要注意要减去4 由于B和C同一时候继承A,属于仅仅须要保存一个A的副本就好了 sizeof(D)=4(A的副本)+4(B的虚表)+4(C的虚表)=12
};

int main() {
printf("%d\n", sizeof(D));
return 0;
}


再举一个样例:含有普通继承

class A
{
};

class B
{
char ch;
virtual void func0()  {  }
};

class C
{
char ch1;
char ch2;
virtual void func()  {  }
virtual void func1()  {  }
};

class D: public A, public C
{
int d;
virtual void func()  {  }
virtual void func1()  {  }
};

class E: public B, public C
{
int e;
virtual void func0()  {  }
virtual void func1()  {  }
};

int main(void)
{
cout<<"A="<<sizeof(A)<<endl;     result=1  空类所占空间的大小为1
cout<<"B="<<sizeof(B)<<endl;     result=8  1+4   对其 8
cout<<"C="<<sizeof(C)<<endl;     result=8  1+1+4 对其 8
cout<<"D="<<sizeof(D)<<endl;     result=12 C的副本+D本身=12
cout<<"E="<<sizeof(E)<<endl;     result=20 B的副本+C的副本+E本身=20
return 0;
}


这里须要区分一下:①不没有继承的时候。存在虚函数则须要加上虚指针。假设有多个也仅仅须要加上一个。由于仅仅有一个虚指针。②对于普通继承。类D和类E中自己的虚函数,大小为0。由于他没有虚表③对于虚继承中,派生类中存在一个或多个虚函数的时候,它本身就有一个虚表,指向自己的虚表。所以要加4

再举一个样例:含有虚继承

class CommonBase
{
int co;                                     4
};

class Base1: virtual public CommonBase          4副本+4虚指针+4自身+4=16
{
public:
virtual void print1() {  }
virtual void print2() {  }
private:
int b1;
};

class Base2: virtual public CommonBase        同理16
{
public:
virtual void dump1() {  }
virtual void dump2() {  }
private:
int b2;
};

class Derived: public Base1, public Base2     16+16-4+4=32
{
public:
void print2() {  }
void dump2() {  }
private:
int d;
};


class Derived size(32):

+---
| +--- (base class Base1)
| | {vfptr}
| | {vbptr}
| | b1
| +---
| +--- (base class Base2)
| | {vfptr}
| | {vbptr}
| | b2
| +---
| d
+---
+--- (virtual base CommonBase)
| co
+---


再举一个样例:

class A
{
public:
virtual void aa() {  }
virtual void aa2() {  }
private:
char ch[3];
};

class B: virtual public A
{
public:
virtual void bb() {  }
virtual void bb2() {  }
};

int main(void)
{
cout<<"A's size is "<<sizeof(A)<<endl;        4+4=8
cout<<"B's size is "<<sizeof(B)<<endl;        A的副本+4+4=16
return 0;
}


11.4 多重继承

考点1:多重继承优缺点

长处:简单、清晰、更加有利于复用。对象能够调用多个基类中的接口

缺点:

1)二义性。比如类A派生了B和C,而B和C共同派生了D,麻烦就出现了,这样的中间大两头小的继承树有个形象的名字:叫做砖石型继承树(DOD)

2)使得父类指针指向子类对象变得非常麻烦。得用C++的dynamic_cast来执行强制转换,这个东西也非常麻烦。由于它是执行期间而非编译期间进行转换的。它要求编译器同意RTTI

3)多重继承还会使子类的vtable变得不同平常,由于子类的vtable中绝对不可能包括完整的有序的两个父类的vtable,因此每一个父类对象都加入了一个指针

诙谐版的说法:

长处:多种功能。加快任务实现。

缺点:多重性格,易得精神分裂。

考点2:声明一个类Jetplane。它是从Rocket和Airplane继承而来的

class JetPlane:public Rocket, public Airplane


考点3:在多继承的时候。假设一个类继承同一时候继承自class A和class B,而class A和B中有一个函数叫foo()。怎样明白地在子类中指出覆盖的是哪个父类的foo()

#include<iostream>#include<memory.h>#include<assert.h>

using namespace std;
class A
{
public:
void foo(){};
};
class B
{
public:
void foo(){};
};
class D:public A, public B
{
};
int main()
{
D d;
d.A::foo();
return 0;
}


考点4:基类和派生类的地址和布局的问题

#include <iostream>using namespace std;

class A
{
int m_a;

};

class B
{
int m_b;
};

class C: public A , public B
{
int m_c;
};

int main(int argc, char* argv[])
{
C *pc=new C;
B *pb=dynamic_cast<B*>(pc);
A *pa=dynamic_cast<A*>(pc);

if (pc==pb)
{
cout<<"equal"<<endl;
}
else
{
cout<<"unequal"<<endl;
}

if ((int)pc==(int)pb)
{
cout<<"equal"<<endl;
}
else
{
cout<<"unequal"<<endl;
}
delete pc;
return 0;
}
实验结果:第一个同样是由于父类指针指向子类对象的时候。採用多重继承之后用dynamic_cast。导致相等输出equal
第二指针PC和Pb值是不同的,所以转换为int型也是不同的。输出unequal


11.5 检測并改动不适合的继承

考点1:理解a part of、派生、a kind of、和has some kind of

题1:假设鸟是能够飞的,那么鸵鸟是鸟吗?鸵鸟怎样继承鸟类?

分析:要鸵鸟来继承鸟类,採用组合的方法,把鸟类中的能够被鸵鸟继承的函数挑选出来,这样鸵鸟就不是 a kind of鸟了。而是has some kind of鸟的属性

#include <iostream>#include <string>using namespace std;
class bird
{
public:
void eat();
void sleep();
void fly();
};

class ostrich
{
public:
bird eat(){cout << "ostrich eat";};
bird sleep(){cout << "ostrich sleep";};
};

int main()
{
ostrich xiaoq;
xiaoq.eat();
xiaoq.sleep();
return 0;
}


题2:若在逻辑上A是B的“一部分”(a part of)。则不同意B从A派生,而是要用A和其它东西组合出B。眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head应该由类Eye、Nose、Mouth、Ear组合而成,而不是派生而成。程序例如以下:

class Eye
{
public:
void Look(void);
};

class Nose
{
public:
void Smell(void);
};

class Mouth
{
public:
void Eat(void);
};

class Ear
{
public:
void Listen(void);
};

class Head
{
public:
void Look(void) { m_eye.Look(); }
void Smell(void) { m_nose.Smell(); }
void Eat(void) { m_mouth.Eat(); }
void Listen(void) { m_ear.Listen(); }

private:
Eye m_eye;
Nose m_nose;
Mouth m_mouth;
Ear m_ear;
};


Head由Eye、Nose、Mouth、Ear组合而成。假设同意Head从Eye、Nose、Mouth、Ear派生而成,那么Head将自己主动具有Look、Smell、Eat、Listen这些功能。

程序十分简短而且执行正确,可是以下这样的设计方法却是不正确的。

class Head : public Eye, public Nose, public Mouth, public Ear
{
};


考点2:类继承中私有继承是无法继承并使用父类函数中的公有变量的

找出以下程序的错误,并解释它为什么是错的。)[中国台湾某著名杀毒软件公司2005年面试题]

#include <iostream>
using namespace std;

class Base {
public:
int val;
Base() { val=1;};
};

class Derive: Base {
public:
int val;
Derive(int i) { val=Base::val+i; };
};

int main(int, char**, char**) {
Derive d(10);
cout<<d.Base::val<<endl<<d.val<<endl;
return 0;
}


答案:把
class Derive: Base
改成
class Derive:public Base


解析:这是个类继承问题。

假设不指定public,C++默认的是私有继承。私有继承是无法继承并使用父类函数中的公有变量的。

考点3:子类中设定初始化成员变量

(找出以下程序的错误。并解释它为什么是错的。)[德国某著名软件咨询企业2005年面试题]

class base{
private: int i;
public:   base(int x){i=x;}
};
class derived: public base{
private: int i;
public:   derived(int x, int y) {i=x;}
void printTotal() {int total = i+base::i;}
};


解析:要在子类中设定初始成员变量,把derived(int x, int y)改成derived(int x, int y) : base(x)。

答案:

代码例如以下:

class base
{
protected: //这里的訪问属性须要改变
int i;
public:
base(int x){i=x;}
};

class derived: public base
{
private:
int i;
public:
derived(int x, int y) : base(x) //曾经没有初始化基类的成员变量
{
i=y;
}
void printTotal()
{
int total = i+base::i;
}
};


11.6 纯虚函数

考点1:认识纯虚函数

1)虚函数与纯虚函数有什么差别?

虚函数,不代表函数为不被实现的函数。为了同意用基类的指针来调用子类的这个函数。同意被其子类又一次定义的成员函数。

纯虚函数,才代表函数没有被实现。为了实现一个接口。起到一个规范的作用,规范继承这个类的程序猿必须实现这个函数。

2)虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并非在编译时刻被确定的。而是在执行时刻被确定的。

因为编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。

3)纯虚函数的定义

纯虚函数是在基类中声明的虚函数,它在基类中未定义。但要求不论什么派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

virtual void funtion1()=0


4)定义纯虚函数的目的:使派生类只不过继承函数的接口。让全部的类对象(主要是派生类对象)都能够运行纯虚函数的动作。但类无法为纯虚函数提供一个合理的缺省实现。

所以类纯虚函数的声明就是在告诉子类的设计者。“你必须提供一个纯虚函数的实现。但我不知道你会如何实现它”。

个人任务纯虚函数的引入,是出于两个目的

①为了安全,由于避免不论什么须要明白可是由于不小心而导致的未知的结果。提醒子类去做应做的实现

②为了效率,不是程序运行的效率,而是为了编码的效率。

5)纯虚函数最显著的特征是:它们必须在继承类中又一次声明函数(不要后面的=0,否则该派生类也不能实例化)。并且它们在抽象类中往往未定义。

考点2:深入总结虚函数和纯虚函数

1)纯虚函数声明例如以下:
virtual void funtion1()=0;
纯虚函数一定未定义,纯虚函数用来规范派生类的行为,即接口。包括纯虚函数的类是抽象类。抽象类不能定义实例。但能够声明指向实现该抽象类的详细类的指针或引用。

2)虚函数声明例如以下:
virtual ReturnType FunctionName(Parameter);
虚函数必须实现,假设不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"


3)对于虚函数来说,父类和子类都有各自的版本号。

由多态方式调用的时候动态绑定。

4)实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类能够覆盖该虚函数。由多态方式调用的时候动态绑定。

5)虚函数是C++中用于实现多态(polymorphism)的机制。

核心理念就是通过基类訪问派生类定义的函数。

6)在有动态分配堆上内存的时候,析构函数必须是虚函数。但没有必要是纯虚的。

7)友元不是成员函数。仅仅有成员函数才干够是虚拟的。因此友元不能是虚拟函数。但能够通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。

8)析构函数应当是虚函数。将调用对应对象类型的析构函数,因此,假设指针指向的是子类对象,将调用子类的析构函数,然后自己主动调用基类的析构函数。

考点3:纯虚函数不能实例化一个对象

#include <iostream>using namespace std;
class Shape
{
public:
Shape(){}
~Shape(){}
virtual void Draw() = 0;
错误:由于Shape 不能实例化一个对象,所以要改成虚函数 virtual void Draw(){};
};
int main()
{
Shape s1;
}


考点4:什么是虚指针?

虚指针是一个虚函数的实现细节,带有虚函数的类中的每个对象都带有一个虚指针指向该类的虚函数表。

考点5:声明一个类Vehicle,使其称为抽象数据类型,写出类Car和Bus的声明,当中每一个类都从类Vehicle里派生。使Vehicle成为一个带有两个纯虚函数的ADT,使Car和Bus不是ADT

class Vehicle
{
public:
virtual void Move()=0;
virtual void Haul()=0;
};
class Car:public Vehicle
{
public:
virtual void Move();
virtual void Hual();
};
class Bus:public Vehicle
{
pulic:
virtual void Move();
virtual void Hual();
};


考点5:虚函数入口地址和普通函数有什么不同?

每一个虚函数都在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令,当一个包括虚函数的对象被创建的时候。它的头部附加一个指针。指向vtable中对应的位置,调用虚函数的时候。无论你是用什么指针调用的,它先依据vtable找到入口地址再运行。从而实现了动态联编。

可是普通函数简单跳转到一个固定的地址。

补充:C++仅仅有涉及到多态和虚拟函数就必需要使用动态联编,其它全是静态联编

考点6:C++怎样阻止一个类被实例化?


使用抽象类,或者构造函数被声明为
private


一般在什么时候构造函数被声明成private呢?

比方要阻止编译器生成默认的
copy constructor
的时候

什么时候编译器会生成默认的
copy constructor


仅仅要自己没写,而程序中须要,都会生成。

假设你写了一个构造函数。编译器还会生成
copy constructor
吗?

会生成


11.7 运算符重载和RTTI执行时类型识别

考点1:C++引入的额外开销主要体现

1)编译时候的开销;

2)执行时的开销

①虚基类

②虚函数

③RTTI

④异常

⑤对象的构造和析构

考点2:执行类型识别RTTI使用须要注意的问题

在分布式系统中,不适用RTTI的一个合理的解释是RTTI行为不可预期及缺乏扩展性

1)用
typeid()
返回一个typeinfo对象。也能够用于内部类型。当用用于非多态类型时没有虚函数。用typeid返回的将是基类地址

2)不能对void指针进行映射

3)假设p是指针,
typeid(*p)
返回p所指向的派生类类型,
typeid(p)
返回基类类型;假设r是引用,
typeid(r)
返回派生类类型,
typeid(&r)
返回基类类型

4)C++里面的
typeid
运算符返回值是
type_info常量对象的引用


考点3:认识运算符重载

所谓重载,就是又一次赋予新的含义,函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。

运算符的重载主要存在两种形式,一种是作为类的成员函数进行使用,还有一种则是作为类的友元函数进行使用。运算符的重载的形式为:

返回类型 operator 运算符符号(參数说明)
{
//函数体的内部实现
}


比如,是否能用“+”号进行两个复数的相加,在C++中不能在程序中直接用运算符“+”对复数进行相加运算。用户必须自己设法实现复数相加。

考点4:运算符重载运算符的运算规则

1)运算符重载函数也是函数,重载的运算符不会改变运算符的优先级、结合型和參数的个数。

2)重载运算符不能违反语言的语法规则。

3)赋值运算符除外,重载运算符可由派生类继承下去。

4)重载运算符不能使用默认參数。



5)运算符=、()、[]和->等操作符必须定义为类的成员函数,将这些操作符定义为友元函数将在编译时标记为错误。

6)C++中不能重载的运算符仅仅有5个:

. (成员訪问运算符)

.* (成员指针訪问运算符)

∷ (域运算符)

sizeof(长度运算符)

?

: (条件运算符)


由于前两个运算符不能重载是为了保证訪问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具重载的特征。

7)友元运算符的參数规则与类成员运算符的參数规则不同,一元运算符必须显示地声明一个參数,二元运算符必须显示地声明两个參数。类成员运算符重载时,參数中隐含了一个this指针。(另外。C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流输入“>>”和流输出“<<”运算符、类型转换运算符))。

重载为类的成员函数时。參数个数=原操作数个数-1(后置++、--除外),它能够通过this指针自由地訪问本类的数据成员。能够少写一个函数的參数,但必需要求运算表达式的第一个參数(即运算符左側的操作数)是一个类对象。

重载为类的友元函数时,參数个数=原操作数个数。且至少应该有一个自己定义类型的形參。

考点5:定义一个重载运算符函数參数表中參数的决定个数

取决于两个主要因素
operator@


1)运算符是一元的(一个參数)还是二元的(两个參数)

2)运算符被定义为全局函数:对于一元运算符,一个參数。对于二元运算符是两个參数

3)运算符是成员函数:对于一元运算符没有參数。对于二元元素符是一个參数

考点6:怎样重载增量运算符++和--

运算符
++
—-
有前置和后置两种形式,要使用
operator++( )
operator--( )
来重载前置运算符,使用
operator++(int)
operator--(int)
来重载后置运算符。调用时,參数int被传递给值0。

考点7:重载流输入运算符和流输出运算符

istream 类的对象cin;

ostream类的对象cout;

假设想直接用“<<”和“>>”输出和输入自己声明的类型的数据,必须对它们重载。对“<<”和“>>”重载的函数形式例如以下:

istream & operator >> (istream &,自己定义类 &);
ostream & operator << (ostream &,自己定义类 &);


重载运算符“>>”的函数的第一个參数和函数的类型都必须是istream&类型,第二个參数是要进行输入操作的类。

重载“<<”的函数的第一个參数和函数的类型都必须是ostream&类型,第二个參数是要进行输出操作的类。

仅仅能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: