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

印象笔记同步分享:《程序员面试宝典》学习记录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, 故输出 1cout << c.A::GetData() <<endl;
    因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出 1cout << 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() ,所以输出0cout << c.B::doGetData() <<endl;
    调用了B的doGetData(),所以输出 1cout << c.C::doGetData() <<endl;
    调用了B的doGetData(),所以输出 1return 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&类型,第二个参数是要进行输出操作的类。
只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值