1 继承和派生
在 C++ 中 可重用性是通过继承这一机制实现的。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定 新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承与派生,是同一种意义两种说法。继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物。
如:B 类继承 A 类,可称从类 A 派生类 B。类 A 称为基类(父类),类 B 称为派生类(子类)。
2 派生类的组成
派生类的成员,包含两大部分,一类是从基类继承过来的,一类是自己新增的成员。从基类继承过来的成员表现其共性,而新增的成员体现了其个性,派生类有了自己的个性,使派生类有了意义。
注意:
一个派生类继承了所有的基类方法,但下列情况除外:
-
基类的构造函数、析构函数和拷贝构造函数。
-
基类的重载运算符。
-
基类的友元函数。
3 继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
-
公有继承(public):当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。
-
保护继承(protected): 保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。
-
私有继承(private):当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
注意:
1. 无论何种方式继承基类,派生类都不能直接使用基类的私有成员。
2. 继承时,如果未使用访问修饰符,则继承类型默认为 private。
4 单继承
一个类可以派生自多个类,如果只派生一个,即为单继承。语法如下:
class 派生类名:继承类型 基类名
{
派生类类体;
};
继承类型即是访问修饰符,是 public、protected 或 private 其中的一个。如果未使用访问修饰符,则默认为 private。
假设有一个基类 Shape,Rectangle 是它的派生类,如下所示:
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Total area: 35
5 多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。语法如下:
class 派生类名:继承类型1 基类名1,继承类型2 基类名2,...
{
派生类类体;
};
继承类型即是访问修饰符,是 public、protected 或 private 其中的一个。如果未使用访问修饰符,则默认为 private。
各个基类之间用逗号分隔。
假设有一个派生类 Rectangle 继承于 基类 Shape 与 基类 PaintCost,如下所示:
#include <iostream>
using namespace std;
// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;
// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
上面的代码被编译和执行时,它会产生下列结果:
Total area: 35
Total paint cost: $2450
6 虚继承
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
6.1 多继承中二义性问题
6.1.1 多重派生类 C 的对象的存储结构示意
6.2 虚继承 virtual
-
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
-
如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象。
要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类称为虚基类。
虚继承声明使用关键字 virtual
语法如下:
class 类名: virtual 继承类型 父类名
继承类型即是访问修饰符,是 public、protected 或 private 其中的一个。如果未使用访问修饰符,则默认为 private。
6.2.1 带有虚基类的多重派生类 C 的对象的存储结构示意
7 继承中的构造和析构
7.1 类型兼容原则
类型兼容规则是指 在需要基类的任何地方,都可以使用公有(pulic)派生类的对象来替代。
类型兼容规则中所指的替代包括以下情况:
-
子类对象可以当做父类对象使用
-
子类对象可以直接赋值给父类对象
-
子类对象可以直接初始化父类对象
-
父类指针可以直接指向子类对象
-
父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
示例代码如下所示:
// inheritTest1.cpp,
// 用于验证类型兼容规则
/*
类型兼容规则中所指的替代包括以下情况:
1. 子类对象可以当做父类对象使用
2. 子类对象可以直接赋值给父类对象
3. 子类对象可以直接初始化父类对象
4. 父类指针可以直接指向子类对象
5. 父类引用可以直接引用子类对象
*/
#include <iostream>
using namespace std;
class Parent
{
public:
void printParent()
{
cout << "this is ParentClass" << endl;
}
int parent_age;
};
class Child : public Parent
{
public:
void printChild()
{
cout << "this is ChildClass" << endl;
}
};
void printByPoint(Parent *parent)
{
parent->printParent();
}
void printByReference(Parent& parent)
{
parent.printParent();
}
int main()
{
Child childTest_1;
childTest_1.printParent(); // 1. 子类对象可以当做父类对象使用
cout << "验证 子类对象可以当做父类对象使用 成功" << endl;
Child childTest_2;
childTest_2.parent_age = 18; // 2. 子类对象可以直接赋值给父类对象
cout << "验证 子类对象可以直接赋值给父类对象 成功" << endl;
Child childTest_3;
childTest_3.parent_age = 1; // 注意:子类对象初始化父类对象,必须对父类对象的公有成员也初始化,否则去掉这句会报错
Parent parentTest_3 = childTest_3; // 3. 子类对象可以直接初始化父类对象
cout << "验证 子类对象可以直接初始化父类对象 成功" << endl;
Child childTest_4;
Parent *parentTest_4 = NULL;
parentTest_4 = &childTest_4; // 4. 父类指针可以直接指向子类对象
printByPoint(parentTest_4);
cout << "验证 父类指针可以直接指向子类对象 成功" << endl;
Child childTest_5;
Parent &parentTest_5 =childTest_5; // 5. 父类引用可以直接引用子类对象
printByReference(parentTest_5);
cout << "验证 父类引用可以直接引用子类对象 成功" << endl;
return 0;
}
运行结果:
7.2 继承中构造析构调用原则
- 子类对象在创建时会首先调用父类的构造函数,父类构造函数执行结束后,执行子类的构造函数
2.当父类的构造函数有参数时,需要在子类的初始化列表中显式调用
3.析构函数调用的先后顺序与构造函数相反
示例代码如下所示:
// inheritTest2.cpp,继承中构造析构调用原则
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(const char* temp_s)
{
this->s = temp_s;
cout << "父类有参构造函数运行"<< endl;
}
~Parent()
{
cout << "父类析构函数运行" << endl;
}
private:
const char* s;
};
class Child : public Parent
{
public:
Child(int temp_age):Parent("我是父类")
{
this->age = temp_age;
cout << "子类有参构造函数运行"<< endl;
}
Child(int temp_age,const char *temp_s):Parent(temp_s)
{
this->age = temp_age;
cout << "子类有参构造函数运行"<< endl;
}
~Child()
{
cout << "子类析构函数运行" << endl;
}
private:
int age;
};
int main()
{
Child child_1(18);
//Child child_2(18,"我是父类"); //这条语句也可实现
return 0;
}
运行结果: