[c++] 继承和多态整理一

1 private 和 protected 继承,子类指针不能赋值给父类指针

如下代码,有一个基类 Base,Derived1,Derived2,Derived3 3 个子类继承了基类 Base,分别是 private 继承,protected 继承,public 继承。在 main 函数里,分别使用 new 来创建 3 个子类,将 3 个子类指针赋值给 Base 指针,private 和 protected 继承的时候,子类指针无法赋值给父类指针。

private 继承或者 protected 继承下,父类的属性和函数在子类中的权限发生了变动。所以使用父类指针指向子类,可能会导致不安全的行为。

#include <iostream>
#include <string>

class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }

  ~Base() {
    std::cout << "~Base()" << std::endl;
  }

  virtual void Do() {
    std::cout << "Base() Do()" << std::endl;
  }
};

class Derived1 : private Base {
public:
  Derived1() {
    std::cout << "Derived1()" << std::endl;
  }

  ~Derived1() {
    std::cout << "~Derived1()" << std::endl;
  }
};

class Derived2 : protected Base {
public:
  Derived2() {
    std::cout << "Derived2()" << std::endl;
  }

  ~Derived2() {
    std::cout << "~Derived2()" << std::endl;
  }
};

class Derived3 : public Base {
public:
  Derived3() {
    std::cout << "Derived3()" << std::endl;
  }

  ~Derived3() {
    std::cout << "~Derived3()" << std::endl;
  }
};

int main() {
  Base *b1 = new Derived1();
  Base *b2 = new Derived2();
  Base *b3 = new Derived3();

  b1->Do();
  b2->Do();
  b3->Do();
  return 0;
}

编译结果如下:

2 子类和父类中的同名函数

在类继承中,一般子类可以覆盖父类中的虚函数,这是我们使用继承和多态常用的方式。如果子类中的函数和父类中的函数同名,并且这个函数不是虚函数呢,父类指针调用函数的时候调用的是父类中的函数,还是子类中的函数 ?在实际开发中,一般不会这么使用,这样的代码是毫无意义的,是自相矛盾的,因为继承了父类,那么就是要复用父类中的方法与属性,即使要覆盖也应该覆盖虚函数,如果不是虚函数,那么显得不伦不类。这里只是对这种情况做一个探讨。

#include <iostream>
#include <string>

class Base {
public:
  Base() {
    std::cout << "Base()" << std::endl;
  }

  ~Base() {
    std::cout << "~Base()" << std::endl;
  }

  void Do() {
    std::cout << "Base() Do()" << std::endl;
  }

  virtual void VDo() {
    std::cout << "Base() VDo()" << std::endl;
  }
};

class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived()" << std::endl;
  }

  ~Derived() {
    std::cout << "~Derived()" << std::endl;
  }

  void Do() {
    std::cout << "Derived() Do()" << std::endl;
  }

  void VDo() {
    std::cout << "Derived() VDo()" << std::endl;
  }
};

int main() {
  std::cout << "----------------" << std::endl;
  Base *b = new Derived();
  b->Do();
  b->VDo();

  std::cout << "----------------" << std::endl;
  Base b1 = *(new Derived);
  b1.Do();
  b1.VDo();

  std::cout << "----------------" << std::endl;
  Base &b2 = *(new Derived);
  b2.Do();
  b2.VDo();
  return 0;
}

Derived 和 Base 之间的赋值,不管是指针赋值,引用赋值还是值赋值,Base 指针,引用,值调用的都是 Base 中的 Do() 函数;Base 指针和引用调用的是 Derived 中的 VDo(),Base 值调用的是 Base 中的 VDo。

3 编译器默认生成的函数

面向对象的语言中 =、& 这么基础的运算符也是需要函数来实现的,这就是 c++ 的特点,一切皆对象。这个在 c 语言中很难理解的。

如果声明了一个对象,这个对象中什么也没有声明,那么为了使用这些基础的运算,编译器会默认生成一些函数。

构造函数类的构造
拷贝构造函数拷贝构造
析构函数析构
赋值运算符赋值
取地址运算符取值
取值运算符取地址
#include <iostream>
#include <string>

class Base {
};

int main() {
  Base b1;
  Base b2;
  b2 = b1;
  Base b3 = b2;

  std::cout << "&b1 = " << &b1 << std::endl;
  std::cout << "&b2 = " << &b2 << std::endl;
  return 0;
}

c++ 默认生成的函数,比如构造函数,如果我们在定义类的时候定义了自己的构造函数,那么编译器就不会生成默认的了。

如下代码中,Base 是一个基类,Derived 继承了 Base 类。在 Base 类中声明了构造函数 Base(int i),这样就不会有默认构造函数 Base() 了。在 Derived 类构造时,没有显式调用 Base 的构造函数,那么就会调用默认构造函数 Base(),但是默认构造函数又不存在,所以在编译的时候就会报错。需要在 Derived 构造列表中对 Base 进行构造,比如 Base(10)。

#include <iostream>
#include <string>

class Base {
public:
  Base(int i) : i_(i) {
    std::cout << "Base(), i = " << i_ << std::endl;
  }

private:
  int i_;
};

class Derived : public Base {
public:
  Derived() {
    std::cout << "Derived(), i " << Base::i_ << std::endl;
  }
};

int main() {
  Derived d;
  return 0;
}

4 私有继承,继承和组合

在讨论设计模式的时候,有一个原则经常被提到: 多用组合,少用继承,能用组合就用组合,万不得已的时候才使用继承。

私有继承的两个特点:

(1)私有继承的派生类指针不能赋值给基类指针

(2)私有继承之后,基类中的 public 和 protected 成员,在子类中都变成了 private 属性,不能通过对象来访问

比如对于汽车这个对象,包括汽车发动机,悬架,转向 3 个主要的系统,我们在描述汽车的时候,如果使用私有继承的方式继承了发动机,悬架,转向 3 个类,是可以实现的,但是从语义上是不通的;使用组合的方式,在语义上是相通的,因为汽车是由这 3 个对象组合而成的。

如下是一个私有继承和组合的例子,Steering 表示转向系统,BydCar 私有继承了 Steering 类,TeslaCar 使用的组合。

#include <iostream>
#include <string>

class Steering {
public:
    Steering() {
      std::cout << "Steering()" << std::endl;
    }

    ~Steering() {
      std::cout << "~Steering()" << std::endl;
    }

    void Turn() {
      std::cout << "Steering() Turn()" << std::endl;
    }
};

class BydCar : private Steering {
public:
    void Turn() {
        std::cout << "BydCar() Turn()" << std::endl;
        Steering::Turn();
    }
};

class TeslaCar {
private:
  Steering steer;

public:
  void Turn() {
    std::cout << "TeslaCar() Turn()" << std::endl;
    steer.Turn();
  }
};

int main() {
    BydCar byd;
    TeslaCar tesla;

    byd.Turn();
    tesla.Turn();
    return 0;
}

使用组合的时候,一般组合的各个子元素都是已经实现的最终的结果,组合的元素是一个基本的元素,组合之后不能对这些基本元素进行修改,并且不能访问元素中的私有成员;使用继承的时候,基类可以是一个抽象类,派生类中也可以重写基类的函数。

设计原则中有一个是对修改关闭,对扩展开开放。这两个方面和继承与组合也有一定的对应关系,组合相当于扩展,继承可以修改,对组合开放相当于优先选用组合,对修改关闭相当于少用继承。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值