日志21
类的继承与派生、构造与析构、多继承及名字隐藏
一、类的继承与派生
类的继承是面向对象编程中的一个核心概念,它允许新的类(派生类)从已有的类(基类或父类)那里继承已有的特性。这一过程也可以看作是从已有类产生新类的过程,即类的派生。
继承方式
公有继承(public):基类的公有(public)和保护(protected)成员在派生类中保持不变,但基类的私有(private)成员不可被派生类直接访问。通过派生类对象只能访问到基类的公有成员。
私有继承(private):基类的公有和保护成员都以私有身份出现在派生类中,基类的私有成员同样不能被直接访问。通过派生类的对象不能直接访问基类中的任何成员。
保护继承(protected):基类的公有和保护成员以保护的身份出现在派生类中,基类的私有成员不能被直接访问。通过派生类的对象也不能直接访问基类中的任何成员。
派生类的声明
派生类的声明通过指定继承方式和基类名来完成。例如:
class 派生类名 : 继承方式 基类名 { // 派生类的主体 };
基类与派生类的对应关系
单继承:派生类只从一个基类派生。
多继承:派生类从多个基类派生。
多重派生:由一个基类派生出多个不同的派生类。
多层派生:派生类又作为基类,继续派生出新的类。
二、构造与析构
构造函数
构造函数是一个特殊的成员函数,它在对象创建时自动调用,用于初始化对象的成员变量。构造函数的名称必须与类名相同,且不能有返回值类型。
特性:
1.自动调用:在对象实例化时自动调用。
2.初始化成员变量:主要作用是初始化对象的成员变量。
3.重载:一个类可以有多个构造函数,只要它们的参数列表不同。
4.初始化列表:可以使用初始化列表来初始化成员变量,特别是当成员变量是常量或引用类型时。
继承中的调用顺序:
在继承中,基类的构造函数会在派生类的构造函数之前被调用。可以通过初始化列表显式调用基类的构造函数。
析构函数
析构函数是在对象消亡之前进行一些必要的清理工作的函数。它没有类型,也没有参数。析构函数的执行顺序与构造函数相反。
特性:
不被继承:派生类需要自行声明析构函数。
自动调用:对象销毁时,系统会自动隐式调用析构函数。
执行顺序:析构函数的调用次序与构造函数的调用次序相反。
三、多继承
多继承是面向对象编程中的一个特性,它允许一个类从多个类继承属性和方法。这在某些情况下可以提高代码的复用性和灵活性。
多继承的声明
多继承的声明方式与单继承类似,只是在继承方式后列出了多个基类名。例如:
class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, ..., 继承方式n 基类名n { // 派生类的主体 };
多继承下的构造函数
在多继承的情况下,派生类的构造函数需要负责所有基类构造函数的调用。派生类构造函数的参数个数必须包含完成所有基类初始化所需的参数个数。构造函数的执行顺序是先执行所继承基类的构造函数,再执行派生类本身的构造函数。
二义性问题
多继承可能会带来二义性问题。当派生类从多个基类派生,而这些基类又从同一个基类派生时,访问此共同基类中的成员将产生二义性。为了解决这个问题,可以使用虚基类。
四、名字隐藏
名字隐藏是C++中的一个重要概念,它发生在继承关系中。当派生类重新定义了基类的非virtual函数时,编译器会采用名字隐藏机制。
名字隐藏机制
1.编译器会从当前域(如派生类对象调用时,会在派生类的定义内查找)开始查找需要的名字
2.如果在当前域没有找到,编译器会在外围作用域继续查找,先是基类的定义内,然后是全局名字空间。
3.一旦在某个作用域内找到需要的名字,编译器就会停下来,并就该作用域内的名字进行决议。这意味着往外层的作用域就不予考虑了,从而将外层作用域的同名函数隐藏。
4.如果在多个作用域内找到了同名的函数,编译器会尝试选出最优的函数。如果选不出最优的,就会产生二义性错误。
名字隐藏的例子
class Base { public: void func() { // 基类的实现 } }; class Derived : public Base { public: void func() { // 派生类的实现 } }; int main() { Derived d; d.func(); // 调用的是派生类的func函数,基类的func函数被隐藏 return 0; }
在这个例子中,派生类Derived重新定义了基类Base的func函数。因此,当通过派生类对象d调用func函数时,调用的是派生类的实现,基类的实现被隐藏。