今天看啥  ›  专栏  ›  starmier

C++ 的 面向对象特点

starmier  · 简书  ·  · 2019-05-21 23:57

1.1 封装

类展现了C++的封装特性,即将具体的实现过程隐藏,只向外暴露公有接口,即数据抽象,通过数据抽象,我们可以将类的接口与实现分离,(即设计类)。

1.2 继承

与C相比,类可以通过相互间的继承和派生形成层次结构,派生类继承了基类的数据结构方法 [编译时]。
使用继承,可以定义相似的类型并对其相似关系建模
继承体现了OOP中事物的普遍性特殊性,还解决了软件重用的问题。
类的模型是所有对象的数据成员单独存储,子类和父类都是单独存储的。

继承的优点:

类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。

继承的缺点:

首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。
更糟的是,父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

子类析构时要调用父类的析构函数吗?

析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数JAVA无析构函数深拷贝和浅拷贝。

继承可以完成的一些操作:
  • 可以再已有类的基础上添加功能。
  • 可以给类添加数据。比如说基类马,可以派生一个白马类,在内加入颜色属性:白色。
  • 可以修改类方法的行为。

当然,以上都可以通过复制修改源代码来完成,但继承机制只需要提供新特性,甚至只看对外接口不用看源码就可以派生出类,添加新特性。

class Base
{
public:
    Base(int a = 0,int b = 0,int c = 0)
        :_pub(a)
        , _pro(b)
        , _pri(c)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    int _pub;
protected:
    int _pro;
private:
    int _pri;
};
class Derive :public Base
{
public:
    Derive()
    {}
    ~Derive()
    {}
    void DoSomething()
    {
        //_pri为父类的是私有的,相当于在类外访问私有成员
        cout << "_pri" << _pri << endl;//会报错

        //_pro,_pub均为protected属性,可以类内访问
        cout << "_pro" << _pro << endl;
        cout << "_pub" << _pub << endl;

        //派生类声明时,新增加的成员 可以访问
        cout << "d_pri" << d_pri << endl;
        cout << "d_pro" << d_pri << endl;
        cout << "d_pub" << d_pri << endl;
    }
private:
    int d_pri;
protected:
    int d_pro;
public:
    int d_a;
};
int main()
{
    Derive a;
    a.DoSomething();//会报错,编译不通过

    a._pro = 0;  //编译报错,_pro 为protected属性的成员,不能在类外访问 
    a._pub = 0; //如果定义时,为class Derive :protected Base ,则这里也会编译报错,_pub为protected属性的成员,不能在类外访问 

}

特别说明
  1. 公有继承时,基类的公用成员和保护成员在派生类中保持原有的访问属性,其私有成员仍为基类私有,即在派生类中不能访问,在类外也不能访问。
  私有成员体现了数据的封装性,如果基类的私有成员可以被派生类所访问,即破坏了基类的封装性,这就会失去C++的一个重要特性。
  并且派生类和基类是不同的类域,在不同类中访问相当于在类外访问私有成员。
  我们在派生类的成员函数中访问了父类的私有成员,相当于在类外访问私有成员,是错误的。

  2. 保护继承时,基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
  对派生类而言,保护成员类似于公有成员,但对于外部而言,保护成员与私有成员类似。
  保护继承——原public,protected属性的成员继承后为protected属性,原private成员仍为private属性

  3.私有继承时,所有基类成员均变成派生类的私有成员,基类的私有成员仍然不能在派生类中访问
所以,我们可以得出以下结论:
  4.因基类与派生类的静态成员函数与静态成员是共用一段空间的,即静态成员和静态成员函数是可以继承的。父类的static变量和函数在派生类中依然可用,但是受访问性控制(比如,父类的private域中的就不可访问)。而且对static变量来说,派生类和父类中的static变量是共用空间的,这点在利用static变量进行引用计数的时候要特别注意。
  5.派生类的friend函数可以访问派生类本身的一切变量,包括从父类继承下来的protected域中的变量。但是对父类来说,他并不是friend的。
  6. 友元只是能访问指定类的私有和保护成员的自定义函数,不是被指定类的成员,自然不能继承。
  • 友元可以访问类的私有成员。
  • 友元只能出现在类定义内部,友元声明可以在类中的任何地方,一般放在类定义的开始或结尾。
  • 友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。
  • 类必须将重载函数集中每一个希望设为友元的函数都声明为友元。
  • 友元关系不能继承,基类的友元对派生类的成员没有特殊的访问权限。如果基类被授予友元关系,则只有基类具有特殊的访问权限。该基类的派生类不能访问授予友元关系的类。

1.3 多态

在编译期就确定调用具体方法从而执行特定的函数代码称为“静态绑定”,在运行期才确定下调用哪个方法称为“动态绑定”。实现“静态绑定”的机制有:函数重载、模板。而实现“动态绑定”的机制是虚函数。

当方法没有实现为virtual时,在编译期就可以根据对象的类型、指针的类型来确定调用哪个方法,当实现为virtual时,就得在运行时通过对象中的虚函数指针,找到相应的虚函数表,得到方法地址,才能确定调用的是哪个方法,这时通过指针调用就和指针的类型无关。

Base类中实现了虚函数,Base指向Derive对象时,Base的类型变为Derive,这里就涉及到虚表中的内容——RTTI(RTTI又称为“运行时多态”,当基类中实现了虚函数时,基类指针指向派生类对象时,打印 *指针 类型得到的会是派生类类型。得到的 *指针 类型其实是 派生类的虚函数表中的RTTI内容)。




原文地址:访问原文地址
快照地址: 访问文章快照