C++面向对象

面向对象

定义基类和派生类

成员函数和继承

  • C++中,基类需要将其两种成员函数区分开
    • 基类希望其派生类进行覆盖的函数
    • 基类希望派生类直接继承而不改变的函数
  • 对于上述前者,基类通常将其定义为虚函数,用virtual关键字进行区分,当调用虚函数时,会使用动态绑定进行调用。
  • 任何构造函数之外的非静态函数都可以是虚函数
  • 成员函数如果没有被声明为虚函数,则其解析会发生在编译期间而非运行时
  • 尽管派生类含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员,需要使用基类的初始化去初始化其基类部分。
  • 在声明派生类时,不需要包含它的派生类列表,但是声明中不需要包含它的派生列表。
  • 如果希望某个类作为基类,则它必须已经被定义而非仅仅声明。
  • 如果希望一个类防止被继承,则可以通过关键字final来实现这个功能
  • code

    class A
    {
    public:
        static void printInfo()
        {
            cout << "I am a static member func.\n";
        }
        A(int val) : val_a(val) {  }
        int val_a;
    };
    
    // 类派生列表,表明自己的基类
    class B final:public A
    {
    public:
        // 派生类构造函数
        B(int a, int b) : A(a), val_b(b) { }
    
        int val_b;
    };
    
    // 错误,B被声明为final的类了,无法被继承
    //class C : public B
    //{};
    
    void test()
    {
        A a(1);
        B b(1, 2);
        // 下面4种访问方式相同
        a.printInfo();
        b.printInfo();
        A::printInfo();
        B::printInfo();
    }
    

类型转换与继承

  • 不存在从基类向派生类的隐式类型转换,因为派生类中可能含有一些成员是基类没有的(自己尝试,用static_cast进行转换也不可以)
  • code

    class A
    {
    public:
        A(int val) : val_a(val) {  }
        int val_a;
    
        virtual void f1(int a) const { cout << __FUNCTION__ << ", current num : " << a << endl; };
        virtual void f2(){ cout << __FUNCTION__ << endl; };
        //void f3();
    };
    
    // 类派生列表,表明自己的基类
    class B final:public A
    {
    public:
        // 派生类构造函数
        B(int a, int b) : A(a), val_b(b) { }
        int val_b;
    
        void f1(int a) const override { cout << __FUNCTION__ << ", current num : " << a << endl; }
        // void f2(int) override; // 没有这样的虚函数
        // void f3() override;  // f3不是虚函数
        void f2() override { cout << __FUNCTION__ << endl; }
    
    };
    
    // 错误,B被声明为final的类了,无法被继承
    //class C : public B
    //{};
    
    void test()
    {
        A a1(1);
        a1.f1(1);
        a1.f2();
        B b1(1, 2);
        b1.f1(2);
        b1.f2();
    
        A a2 = b1;
        a2.f1(3);
        a2.f2();
    
        // B b2 = a1; // 无法实现隐式转换
        // 要想使得从派生类到基类转换成功,需要使用引用或者指针的方式
        B* b3 = static_cast<B*>(&a1);
        if (b3 != NULL)
        {
            b3->f1(4);
            b3->f2();
        }
    }
    

访问控制和继承

protected

  • protected成员对于类的用户来说是不可访问的。
  • 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的
  • 派生类的成员或者友元只能通过派生类对象来访问基类的protected成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。
  • code

    class A
    {
    public :
        A(int a = 0) :prot_mem(a){}
    protected:
        int prot_mem;
    };
    
    class B : public A
    {
        friend void clobber(A&);
        friend void clobber(B&);
    };
    
    // 无法访问基类的受保护成员
    void clobber(A& a)
    {
        //cout << __FUNCTION__ << " : " << a.prot_mem << endl; // 报错
        cout << __FUNCTION__ << endl;
    }
    
    // 可以通过派生类去访问
    void clobber(B& b)
    {
        cout << __FUNCTION__ << " : " << b.prot_mem << endl;
    }
    
    void test()
    {
        A a;
        clobber( a );
        clobber( *(static_cast<B*>(&a)) );
    }
    

继承中的类作用域

  • 派生类也可以重定义在基类中的名字,此时基类中的名字会被隐藏
  • 可以通过作用域运算来访问基类中被隐藏的成员
  • 名字查找会优先于类型检查,即使基类和派生类的成员参数列表也不相同,派生类中的成员函数也会隐藏基类中的同名成员函数
  • 基类和派生类的虚函数必须有相同的形参列表,如果形参不同,则无法通过基类的指针调用派生类的成员函数。
  • 如果调用的函数是非虚函数,则不会进行动态绑定,即实际调用的函数由指针或者引用的静态类型决定。
  • code

    class A
    {
    public:
        A(int a = 0) : mem(a) {}
    
        virtual void print() { cout << mem << endl; }
        void func() { cout << __FUNCTION__ << endl; }
    
        virtual void printNum(int a) { cout << __FUNCTION__ << " : " << a << endl; }
    protected:
        int mem;
    };
    
    class B : public A
    {
    public:
        B(int a) : mem(a) {  }
        void print() { cout << mem << endl; }
        void func() { cout << __FUNCTION__ << endl; } // 不是虚函数,覆盖了基类的成员函数,而且不会动态绑定
    
        void printBase() { cout << A::mem << endl; } // 可以通过作用域运算来访问基类中被隐藏的成员
        void printNum() { cout << __FUNCTION__ << endl; }
    protected:
        // 会将基类中的mem屏蔽掉
        int mem;
    };
    
    
    void test()
    {
        B b(2);
        b.print();
        b.printBase();
    
        A &a2 = b;
        a2.print(); // 执行的是b中的print,动态绑定
        // a2.printNum(  ); // 形参不同,无法通过基类的指针调用派生类的成员函数
    
        b.printNum();
        // b.printNum( 1 ); // 基类中的成员函数会被隐藏,这句话会报错
        b.A::printNum( 2 ); // 按照这种方式调用基类中的同名成员函数
    
        A &a3 = b;
        a3.func(); // 由于不是虚函数,因此在调用时不会执行动态绑定,调用的函数由指针或者引用的静态类型决定
        b.func();
    }
    

构造函数和拷贝控制

  • 如果一个类没有定义拷贝控制操作,则编译器会为其合成一个版本,但是我们可以设置这个拷贝控制函数是delete的状态(即该类对象不可以被拷贝。)
  • 如果要删除一个动态分配类型的对象,则我们需要在积累中将析构函数也设置为虚函数,这样才能确保执行正确的析构函数版本。
  • 对于派生类的构造函数,基类的构造函数会首先执行,然后再执行派生类的构造函数;对于析构过程正好相反,派生类的析构函数首先执行,再执行基类的析构函数。
  • code

    class A
    {
    public:
        A() {}
        virtual void func() {}
        virtual ~A()
        {
            cout << __FUNCTION__ << " is being deleted." << endl;
        }
    };
    
    class B : public A
    {
    public:
        B() {}
        void func() {}
        ~B()
        {
            cout << __FUNCTION__ << " is being deleted." << endl;
        }
    };
    
    void test()
    {
        A *a1 = new A();
        delete a1;
        cout << endl;
        a1 = new B(); // 动态绑定,会析构B,这里由于是派生类,所以之后也会析构A
        delete a1;
    
        //B *b1 = new A(); // 无法将基类转换为派生类
        // delete b1;
    }
    

容器与对象

  • 如果要在容器中放入继承体系中的对象,通常必须采取间接存储的方式,因为不允许在容器中放入不同的对象,因此如果只是放置类对象的话,无法放入一个继承体系中的各种类。
  • 可以在容器中放入(智能)指针而非类对象,这些指针的动态类型都是基类类型,从而实现多个继承类对象指针存储在一个容器中。
  • code

    class A
    {
    public:
        A() {}
        virtual void func() { cout << __FUNCTION__ << endl; }
    };
    
    class B : public A
    {
    public:
        B() {}
        void func() { cout << __FUNCTION__ << endl; }
    };
    
    void test()
    {
        cout << " class object with container. " << endl;
        vector<A> vec;
        vec.push_back( A() );
        vec.push_back( B() ); // 可以将派生类对象转化为基类,但是无法通过对象的方式将基类转化为派生类
        vec[0].func();
        vec[1].func(); // 由于容器中是对象,因此B被向上转化为A
    
        cout << " share_ptr with container. " << endl;
        vector<shared_ptr<A>> vec2;
        vec2.push_back( make_shared<A>() );
        vec2.push_back(make_shared<B>());
        vec2[0]->func();
        vec2[1]->func(); //采用指针的方式,这里可以调用
    }
    

猜你喜欢

转载自blog.csdn.net/u012526003/article/details/80236441