欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Effective C++:面向对象与继承

程序员文章站 2022-11-18 11:57:19
在看《Effective C++》这本书的过程中,我无数次的发出感叹,写得太好了,句句一针见血,直接说到点上。下面序号代表书的章节号(原书更新时间不明确,实际请以原书为准) 1:子类不要覆写父类的非虚函数。 为了解释方便,先看一个简单的例子。 运行结果截图: 例子中指针a是指向对象b的,但是他们调用 ......

在看《effective c++》这本书的过程中,我无数次的发出感叹,写得太好了,句句一针见血,直接说到点上。下面序号代表书的章节号(原书更新时间不明确,实际请以原书为准)

Effective C++:面向对象与继承
 

1:子类不要覆写父类的非虚函数。

为了解释方便,先看一个简单的例子。

class a

{

    public:

        a(int d):data(d){  }

 

        void print()

        {

            cout<<"a print..."<<data<<endl;

        }

 

        virtual void test(int i=2)

        {

            cout<<"a test..."<<i<<endl;

        }

    private:

        int data;

};

 

class b:public a

{

    public :

 

        b(int d):a(d){  }

        void print()

        {

            cout<<"b print..."<<endl;

        }

        virtual void test(int i=4)

        {

            cout<<"b test..."<<i<<endl;

        }

};

 

//测试代码

int main() {

    {

        b b(5);

        b.print();

        a *a=&b;

        a->print();

        cout<<endl;

        b.test();

        a->test();

        cout<<endl;

        a a1=b;

        a1.test();

    }

 

     getchar();

     return 0;

}

运行结果截图:

Effective C++:面向对象与继承
 

例子中指针a是指向对象b的,但是他们调用的print方法却不是同一个。这里涉及到静态绑定和动态绑定的问题。a的静态类型是a,a的动态的类型却是b,b的静态类型和动态类型都是b,因为静态类型就是申明时的类型,动态类型是其真正指向的类型。还有一点就是非虚方法是静态绑定,虚拟方法是动态绑定。print是非虚方法,它是静态绑定,调用的是自己的对象申明类型的方法,所以a调用的是a的print,b调用的是b的print方法。我想我们更想知道c++是怎么实现动态绑定。我们都知道含有虚方法的类都有一个虚拟方法表,每个对象的实例都有一个指针指向这个虚拟方法表,子类会继承父类的virtual方法,也可以覆写父类的虚拟方法,如果子类覆写父类的虚拟方法,那么在虚拟表中对应的指针就指向子类覆写父类的方法,如果子类不覆写父类的虚拟方法,则还是指向父类的方法,这样就形成了动态绑定。不同的子类按照自己的方式覆写父类的虚拟方法,表现出不同的行为这就是多态。在多重继承中,每个对象可能有多个虚拟表,那么它的实例就会有多个指向虚拟表的指针,如果多个父类有一个相同的方法,那么你就不能直接用这个实例调用这个方法,因为编译器根本不知道它该调用哪个方法,你要指定是那个父类的方法,当你指明了哪个父类,编译就可以通过对应的指针调用对应的虚拟表中对应的方法。那么实例调用虚拟方法的过程是怎么样的呢,你有没有想过?其实上面也提到一点,大致三步:

1:根据对象的vptr指针找到其虚拟方法表vtbl;

2:找到被调用方法在vtbl中对应的指针;

3:调用2中指针指向的方法。

 

2:子类不要覆写从父类继承过来的默认参数

这一条其实还是涉及到静态绑定和动态绑定的问题,关于这个问题我想上面已经说得比较清楚了,默认值也是静态绑定,这是毫无疑问的,因为它在编译期就已经确定了,而虚拟方法确实动态绑定,你把静态绑定的东西和动态绑定的东西搅在一起没有问题,但是你还有得寸进尺的在子类中覆写静态的东西就会出问题,对不起,父类不管子类中静态的东西,它只管自己静态的东西,所以当子类不要覆写从父类继承过来的默认参数时,子类就可能出现精神分裂的行为,上面那个列子就是证明。

上面更多提到的都是关于虚拟方法的,那么非虚拟方法呢,对象实例时怎么调用非虚拟方法的呢?非虚拟方法是怎么实现的呢?非虚拟方法就像一般的c函数那样被实现的,所以他们的调用不需要像虚拟方法一样先要找到一个指针,然后在通过这个指针调用对应的方法。

3:子类与父类之间的赋值问题

首先将父类转换成子类的事最好不要做,因为子类的很多特性父类根本没有,当你把一个从父类转换过来的子类,当做子类来用的话,很可能出问题。接下来我们重点讨论将子类转换成父类。还是通过上面例子来说明问题。

b b(2);

a a=b;//调用copy constructor

a=b;//调用 operator=

上面两行代码,第一行先实例化了一个对象b,第二行将b赋给a,那么是怎么将b赋给a的呢,这里其实调用的不是operator=,而是copy constructor,因为构造一个对象必须调用constructor,或是copy constructor,那么这里肯定是调用copy constructor,operator=只是一个赋值动作,一个对象还没有构造出来怎么给他赋值呢,在operator=可不是用来帮你构造对象的哦,在第三行的时候a已经被构造出来了,那么这里真的就是赋值了调用的就是operator=。总之一句话,一个对象作为左值时,第一次肯定调用的是copy constructor,被初始化后(分配了内存),之后的操作才是赋值。一个对象作为by value形式的参数,那么每次调用的都是copy constructor,而不是operator=,我们一般都会说将实参赋给形参,其实是用实参构造一个形参。

将b赋给a,就是将b的a部分赋给a,a就是一个完全的a了,它对b一无所知,更不会表现出b的任何行为,所以by value是很暴力并且很耗性能的,也不会出现多态的行为。所以要避免使用by value,尽量用by reference。

博主是一个有着7年工作经验的架构师,对于c++,自己有做资料的整合,一个完整学习c语言c++的路线,学习资料和工具。可以进我的q群7418,18652领取,免费送给大家。希望你也能凭自己的努力,成为下一个优秀的程序员!另外博主的微信公众号是:c语言编程学习基地,欢迎关注!