天天看点

多重继承_C++ 继承和封装

这一篇介绍一下 C++面向对象另外两个特性继承和封装

如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢

继承

  • 继承(是 is-a 的关系)的作用是提高代码的重用性,子类继承了父类,子类可以调用父类的成员,跟调用自己的成员一样,父类被继承后没有影响,还是可以正常定义对象
继承的限定词
  • private继承,父类中 public 和 protected 成员,在子类中为 private,父类中 private 成员在子类中不变
  • protected继承,父类中 public 成员,在子类中为 protected,父类中 protected 和 private 成员在子类中不变
  • public,父类中如何子类中如何
  • 继承限定词是将父类中访问权限大于等于限定词降为和限定词权限相同,需要说明一点,子类 private 或 protected 继承父类,子类中的父类成员权限发生变化,父类中的没有变化,还是可以正常定义对象
  • 如果不写继承限定词,默认是 private 继承的
成员的继承
  • 友元函数不能被继承,静态成员只有一份,子类继承父类,父类有静态成员(静态函数/静态成员),所有的子类和父类共享一份静态成员
  • 无参构造函数,从上向下调用,先调用父类再调用子类,如果父类还有父类,那就先调用父类的父类的构造函数,以此类推,或者说从辈分高到辈分低
class TestA {
public:
    TestA() {
        cout << "TestA ctor" << endl;
    }

    TestA(int i) {
        cout << "TestA param ctor" << endl;
    }
};

class Test : public TestA {
public:
    Test() { // 此处默认是 Test() : TestA()
        cout << "Test ctor" << endl;
    }
};

Test();
// 运行结果:
// TestA ctor
// Test ctor
           
  • 有参构造函数,如果父类的构造函数有参数子类的构造函数必须显式调用父类的构造函数,子类只关心自己的父类,父类的父类有几个参数对子类没有影响,父类的父类的构造函数需要在父类的构造函数的初始化列表中显式调用并传递参数,父类的构造函数可以重载,编译器会根据子类的初始化列表调用相应的父类的构造函数,默认的情况下,子类构造函数的参数列表是调用无参的父类构造函数
class TestA {
public:
    TestA() {
        cout << "TestA ctor" << endl;
    }

    TestA(int i) {
        cout << "TestA param ctor" << endl;
    }
};

class Test : public TestA {
public:
    Test() : TestA(10){  // Test(int n) : TestA(n) 创建对象时就要传参
        cout << "Test ctor" << endl;
    }
};

Test();
// 运行结果:
// TestA param ctor
// Test ctor
           
  • 析构函数,析构函数的调用顺序与构造函数的调用顺序相反,先调用子类的析构函数再调用父类的析构函数,即从辈分低到辈分高
class TestA {
public:
    ~TestA() {
        cout << "TestA dtor" << endl;
    }
};

class Test : public TestA {
public:
    ~Test() {
        cout << "Test dtor" << endl;
    }
};

Test();
// 运行结果:
// Test dtor
// TestA dtor
           
覆盖
  • 父类和子类中出现同名的成员(成员变量和成员函数)时,C++采用的一种处理方式叫覆盖
  • 数据成员,在类内,子类覆盖父类,默认是调用子类的成员,要调用父类的成员需要用类名作用域,在类外也是一样的,默认是调用子类的成员,要调用父类的成员需要用类名作用域
class TestA {
public:
    int n1;
    TestA() : n1(1), n2(2) { }

private:
    int n2;
};

class Test : public TestA {
public:
    int n1;
    Test() : n1(3), n2(4) { }
    void func() {
        cout << n1 << " " << n2 << " " << TestA::n1 << endl;
    }
private:
    int n2;
};

Test().func();
cout << Test().n1 << " " << Test().TestA::n1 << endl;

// 运行结果:
// 3 4 1
// 3 1
           
  • 函数成员,函数成员的覆盖通常情况下与数据成员一样,成员函数的覆盖只会看函数名是否是一样的,不看返回值类型和参数列表,所以父类和子类同名函数参数列表不同,不存在重载关系
class TestA {
public:
    void func() {
        cout << "TestA func" << endl;
    }
};

class Test : public TestA {
public:
    void func() {
        cout << "Test func" << endl;
    }
};

Test().func();
Test().TestA::func();

// 运行结果:
// Test func
// TestA func
           
虚继承
  • 虚继承是多重继承中特有的概念,虚基类是为解决多重继承中访问不明确的问题而出现的
// ====== 这是一个菱形继承的测试用例 ======
class A {
public:
    int n;
};

class B : virtual public A { };

class C : virtual public A { };

class Test : public B, C { 
public:
    void func() {
        cout << n << endl;
    }
};
           
  • 上面的代码中,若没有 virtual 修饰B类和C类的继承关系B类和C类就会都复制一份A类中的n,Test的对象调用n,就会发生访问不明确的现象,这是因为Test继承的B类和C类中,都有变量 n,加上 virtual 修饰后,B类和C类不会在类中复制一份n,只是继承了A类中n的使用权,所以还是只有一份变量n,另外还需要注意Test定义的不同对象中的n之间是相互独立的

封装

  • 封装是 C++ 面向对象特征之一
  • 封装是将客观事物抽象得到的属性(数据)和方法(函数)相结合,形成一个整体,即类,其中数据和函数都是类的成员
  • 封装可以隐藏细节,让代码模块化,只留下接口给外界调用
  • 实现代码重用
如果未特殊说明,以上测试均是在win10 vs2017 64bit编译器下进行的