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;
}
使用组合的时候,一般组合的各个子元素都是已经实现的最终的结果,组合的元素是一个基本的元素,组合之后不能对这些基本元素进行修改,并且不能访问元素中的私有成员;使用继承的时候,基类可以是一个抽象类,派生类中也可以重写基类的函数。
设计原则中有一个是对修改关闭,对扩展开开放。这两个方面和继承与组合也有一定的对应关系,组合相当于扩展,继承可以修改,对组合开放相当于优先选用组合,对修改关闭相当于少用继承。