类之间可以建立联系,这就使得类可以有某种关系
类之间的关系
has-A
:包含关系,一个类使用另一个已经定义好的类的数据
uses-A
:使用关系,友元或者对象参数传递
is-A
:是的关系,这就是继承,具有传递性不具有对称性
继承是类之间定义的一种重要关系,一个B类继承A类,或称从类A派生类B,类A称为基类(父类),类B称为派生类(子类)
基类和派生类
类继承关系的语法形式
class 派生类名:基类名表
{
数据成员和成员函数声明
};
基类名表构成:访问控制 基类名1, 访问控制 基类名2,···, 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
- public 公有继承
- private 私有继承
- protected 保护继承
访问控制
派生类对基类成员的使用,与继承访问控制和基类中成员性质有关
公有继承:基类的公有成员 -> 派生类的公有成员,基类的保护成员 -> 派生类的保护成员
私有继承:基类的公有成员和保护成员 -> 派生类的私有成员
保护继承:基类的公有成员和保护成员 -> 派生类的保护成员
不论种方式继承基类,派生类都不能直接使用基类的私有成员
重名成员
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名::成员
派生类中的静态成员
基类定义的静态成员,将被所有派生类共享
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
派生类中访问静态成员,用以下形式显式说明:
通过类:类名::成员
通过对象:对象名.成员
基类的初始化
建立一个类层次后,通常创建某个派生类的对象,包括使用基类的数据和函数
C++提供一种机制,在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
派生类构造函数声明为
派生类构造函数(变元表):基类(变元表),对象成员1(变元表)···对象成员n(变元表);
构造函数执行顺序:基类 -> 对象成员 -> 派生类
继承的应用实例
#include <iostream>
#include <iomanip>
using namespace std;
class Point
{
friend ostream &operator<< (ostream &, const Point &);
public:
Point(int = 0, int = 0);//带默认参数的构造函数
void setPoint(int, int);//对点坐标数据赋值
int getX() const
{
return x;
}
int getY() const
{
return y;
}
protected:
int x, y;//Point类的数据成员
};
class Circle :public Point
{
friend ostream &operator<<(ostream &, const Circle &);//友员函数
public:
Circle(double r = 0.0, int x = 0, int y = 0);//构造函数
void setRadius(double);/*置半径*/
double getRadius() const;/*返回半径*/
double area() const;//返回面积
protected:
double radius;//数据成员,半径
};
class Cylinder :public Circle
{
friend ostream & operator<<(ostream &, const Cylinder &);//友员函数
public:
Cylinder(double h = 0.0, double r = 0.0, int x = 0, int y = 0);//构造函数
void setHeight(double);/*置高度值*/
double getHeight() const;/*返回高度值*/
double area() const;/*返回面积*/
double volume() const;/*返回体积*/
protected:
double height;//数据成员,高度
};
//Point 类的成员函数
//构造函数,调用成员函数对x,y作初始化
Point::Point(int a, int b)
{
setPoint(a, b);
}
//对数据成员置值
void Point::setPoint(int a, int b)
{
x = a;
y = b;
}
//重载插入算符,输出对象数据
ostream &operator<<(ostream &output, const Point &p)
{
output << '[' << p.x << "," << p.y << "]";
return output;
}
//Circle 类的成员函数
//带初始化式构造函数,首先调用基类构造函数
Circle::Circle(double r, int a, int b):Point(a, b)
{
setRadius(r);
}
//对半径置值
void Circle::setRadius(double r)
{
radius = (r >= 0 ? r : 0);
}
// 返回半径值
double Circle::getRadius() const
{
return radius;
}
// 计算并返回面积值
double Circle::area() const
{
return 3.14159 * radius * radius;
}
// 输出圆心坐标和半径值
ostream & operator<< (ostream &output, const Circle &c)
{
output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = "
<< setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << c.radius;
return output;
}
//Cylinder 类的成员函数
//带初始化式构造函数,首先调用基类构造函数
Cylinder::Cylinder(double h, double r, int x, int y):Circle(r, x, y)
{
setHeight(h);
}
// 对高度置值
void Cylinder::setHeight(double h)
{
height = (h >= 0 ? h : 0);
}
// 返回高度值
double Cylinder::getHeight() const
{
return height;
}
// 计算并返回圆柱体的表面积
double Cylinder::area() const
{
return 2 * Circle::area() + 2 * 3.14159*radius*height;
}
// 计算并返回圆柱体的体积
double Cylinder::volume() const
{
return Circle::area()*height;
}
// 输出数据成员圆心坐标、半径和高度值
ostream &operator<< (ostream &output, const Cylinder &cy)
{
output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = "
<< setiosflags(ios::fixed | ios::showpoint) << setprecision(2) << cy.radius
<< "; Height = " << cy.height << endl;
return output;
}
int main()
{
Point p(72, 115);//定义点对象并初始化
cout << "The initial location of p is " << p << endl;
p.setPoint(10, 10);//置点的新数据值
cout << "
The new location of p is " << p << endl;//输出数据
Circle c(2.5, 37, 43);//定义圆对象并初始化
cout << "
The initial location and radius of c are
" << c << "
Area = " << c.area() << "
";
//置圆的新数据值
c.setRadius(4.25);
c.setPoint(2, 2);
//输出圆心坐标和圆面积
cout << "
The new location and radius of c are
" << c << "
Area = " << c.area() << "
";
Cylinder cyl(5.7, 2.5, 12, 23);//定义圆柱体对象并初始化
//输出圆柱体各数据和表面积,体积
cout << "
The initial location, radius ang height of cyl are
" << cyl
<< "Area = " << cyl.area() << "
Volume = " << cyl.volume() << '
';
//置圆柱体的新数据值
cyl.setHeight(10);
cyl.setRadius(4.25);
cyl.setPoint(2, 2);
cout << "
The new location, radius ang height of cyl are
" << cyl
<< "Area = " << cyl.area() << "
Volume = " << cyl.volume() << "
";
system("pause");
return 0;
}
多继承
一个类有多个直接基类的继承关系称为多继承
多继承声明语法
class 派生类名:访问控制 基类名1, 访问控制 基类名2,···,访问控制 基类名n
{
数据成员和成员函数声明
};
类C可以根据访问控制同时继承类A和类B的成员,并添加自己的成员
class C:public A, public B
多继承的派生类构造和访问
- 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
- 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序
- 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别
e.g.
class Base1
{
public:
Base1(int x)
{
value = x;
}
int getData() const
{
return value;
}
protected:
int value;
};
class Base2
{
public:
Base2(char c)
{
letter = c;
}
char getData() const
{
return letter;
}
protected:
char letter;
};
class Derived : public Base1, public Base2
{
friend ostream &operator<< (ostream &, const Derived &);
public:
Derived(int, char, double);
double getReal() const;
private:
double real;
};
虚基类
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
class B
{
public:
int b;
};
class B1:public B
{
private:
int b1;
};
class B2:public B
{
private:
int b2;
};
class C:public B1, public B2
{
public:
int f();
private:
int d;
};
C c;
c.B;// error
c.B::b;// error,从哪里继承的?
c.B1::b;// ok,从B1继承的
c.B2::b;// ok ,从B2继承的
建立 C 类的对象时,B 的构造函数将被调用两次:一次由B1调用,另一次由 B2 调用,以初始化 C 类的对象中所包含的两个 B 类的子对象
- 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
- 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
- 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类
- 虚继承声明使用关键字:
virtual
class B
{
public:
int b;
};
class B1:virtual public B
{
private:
int b1;
};
class B2:virtual public B
{
private:
int b2;
};
class C:public B1, public B2
{
private:
float d;
};
C cc;
cc.b;// ok
由于类 C 的对象中只有一个 B 类子对象,名字 b 被约束到该子对象上,所以,当以不同路径使用名字 b 访问 B 类的子对象时,所访问的都是那个唯一的基类子对象。即cc.B1::b
和cc.B2::b
引用是同一个基类 B 的子对象
面向对象中的继承指类之间的父子关系
- 子类拥有父类的所有成员变量和成员函数
- 子类就是一种特殊的父类
- 子类对象可以当作父类对象使用
- 子类可以拥有父类没有的方法和属性
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
- public继承:父类成员在子类中保持原有访问级别
- private继承:父类成员在子类中变为private成员
- protected继承:父类中public成员会变成protected,父类中protected成员仍然为protected,父类中private成员仍然为private
注意:private成员在子类中依然存在,但是却无法访问到。
继承总结
- 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
- 单继承的派生类只有一个基类。多继承的派生类有多个基类。
- 派生类对基类成员的访问由继承方式和成员性质决定。
- 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
- C++提供虚继承机制,防止类继承关系中成员访问的二义性。
- 多继承提供了软件重用的强大功能,也增加了程序的复杂性