C++包含与继承

类的包含

  • 包含表示一个类含有一个基本数据元素或对象,用来实现“has-a”关系
  • 对象成员的访问:
    • 对象成员的访问方法有两种,一种是将对象放在public中,直接访问,另一种是将对象放在private中,在public中设置函数访问。如点类和线类:
class Point {
	double x,y;
public:
	Point(double x = 0,double y = 0)
	{ this -> x = x; this -> y =y;}
	double getx() { return x;}
	double gety() { return y;}
	void setx(double x) {this -> x = x;}
	void sety(double y) {this -> y = y;}
};
class Line {
public:
	Point p1,p2;
	Line(Point a,Point b): p1(a),p2(b) {}
};
int main()
{
	Point a,b;
	Line L(a,b);
	L.p1.setx(3); L.p1.sety(4); //直接访问成员类
	return 0;
}
class Point {
	double x,y;
public:
	Point(double x = 0,double y = 0)
	{ this -> x = x; this -> y =y;}
	double getx() { return x;}
	double gety() { return y;}
	void setx(double x) {this -> x = x;}
	void sety(double y) {this -> y = y;}
};
class Line {
	Point p1,p2;
public:
	Line(Point a,Point b): p1(a),p2(b) {}
	double getDistance() {
		double detax,detay;
		detax = p1.getx() - p2.getx();
		detay = p1.gety() - p2.gety();
		return sqrt(detax * detax + detay * detay);
	}
	void setp1(double x,double y) 
	{ p1.setx(x); p1.sety(y); }
};
int main()
{
	Point a,b;
	Line L(a,b);
	L.setp1(3,4); //通过Line的public函数访问
	return 0;
}
  • 对向成员初始化
    • 上述例子中,Line的创建必须要两个Point才能成功创建
    • 成员对象初始化也可以设置默认值,成员初始化的顺序和成员声明的次序相同,下面重写Line类构造函数:
class Line {
	Point p1,p2;
public:
	Line(Point a,Point b): p1(a),p2(b) {}
	Line(double ax = 0,double ay = 0,double bx = 0,double by = 0)
		: p2(bx,by),p1(ax,ay) { } //虽然p2在前,但是根据声明顺序,先构造p1
}; 
int main()
{
	Line L1(3,4), L2, L3(3,4,5,6); 
	return 0;
}

类的继承

  • 被继承的已有类称为基类,继承得到的新类称为派生类,派生类可以再被继承,这样构成的层次结构称为继承层次,继承用来实现“is-a”关系。
  • 继承的语法形式:
    class \quad 派生类名字 \quad \quad [访问限定符] \quad 基类名字
    { 成员声明; };
    • 访问限定符可以是public、private 或 protected
  • 基类成员在派生类中的可见性
    • public 公有继承
      基类的 public 和 protected 分别被继承为 public 和 protected
      基类的 private 不可见
    • private 私有继承
      基类的 public 和 protected 都被继承为 private
      基类的 private 不可见
    • protected 继承
      基类的 public 和 protected 都被继承为 protected
      基类的 private 不可见
  • 访问声明
    • 在派生类中调整个别基类成员的访问限制,但是不能改变基类private 成员的访问限制。
    • 声明语法:
      public ( private 或 protected ) :
      \quad Base :: 成员名
      // 将继承的成员调整为public(private或protected)
  • 通过继承基类,派生类就拥有访问基类protected 成员特权,如果担心封装性被破坏,可以声明为private,在protected中提供访问器函数。
  • 通常私有继承可以被包含所代替,protected继承也不经常使用。
  • 实例
class Shape {
public:
    void draw() const { ... }
    double area() const { ... }
    void move(int) { ... }
};
class Line : public Shape {
private:
    Shape::area; //Line没有area,将接口隐藏
};

派生类对象的创建和撤销

  • 派生类由其基类子对象及派生类自己的非静态数据成员构成。
  • 在派生类中,首先存储基类子对象,接下来存放派生类自己的数据成员。
  • 在派生类的构造函数表中,需要为基类子对象提供初始值,初始化列表中用基类的名字来调用构造函数。与成员对象初始化方式相似,只不过在初始列表中使用的是基类的名字,包含时使用的是成员对象的名字。
  • 如果派生类中包含其他类的对象成员,先构造基类,再构造成员类,最后构造派生类。
  • 如果派生类的构造函数没有显式调用基类构造函数,会自动调用基类的默认构造函数。
  • 实例:
#include <iostream>
using namespace std;
class Base {
   int bm;
public:
   Base(int v = 0): bm(v) {cout<<"Base构造函数"<<endl;}
   ~Base() {cout<<"Base析构函数"<<endl; }
};
class Member {
   int mm;
public:
   Member(int v = 0):mm(v) {cout<<"Member构造函数"<<endl;}
   ~Member() {cout<<"Member析构函数"<<endl;}
};
class Derived : public Base {
   Member dm;
public:
   Derived(int num = 0): dm(num) {cout<<"Derived构造函数"<<endl;}
   ~Derived() {cout<<"Derived析构函数"<<endl;}
};
int main()
{
   Derived d;
   return 0;
}

在这里插入图片描述

派生类对基类的修改

  • 派生类中修改基类的方式有两种:
    • 覆盖或隐藏基类的操作:
      重新定义基类接口中已经存在的函数,会覆盖基类中同名的函数(包括以不同参数列表重载的)使派生类对象在接收到同样的消息时其行为不同于基类对象。
    • 扩充接口:
      向派生类的接口中添加新的操作,使派生类对象能够接受更多的信息。
  • 如果扩充了派生类的接口,那么派生类对象可以接收的消息就有别于基类对象了,能发送给基类对象的消息扔然能发送给派生类,反之则不能。这时,派生类与基类之间可以描述为“is-like-a”关系。
  • 不能自动继承的成员
    • 构造函数
    • 析构函数
    • operator=
  • 实例
class Point2d {
protected:
   double x,y;
public:
   Point2d(double xx = 0,double yy = 0): x(xx),y(yy) {}
   double getx() {return x;}
   double gety() {return y;}
   void setx(double xx) {x = xx;}
   void sety(double yy) {y = yy;}
   void moveto(double xx,double yy) {x = xx; y = yy;}
   void print()
   { cout<<'('<<x<<','<<y<<')'<<endl; }
};
class Point3d : public Point2d {
protected:
   double z;
public:
   Point3d(double xx = 0, double yy = 0,double zz = 0)
   	: Point2d(xx,yy),z(zz) {}
   double getz() {return z;}
   void setz(double zz) {z = zz;}
   void moveto(double xx,double yy,double zz)  //覆盖了Point2d::moveto
   { x = xx;y = yy;z = zz;}
   void print()
   { cout<<'('<<x<<','<<y<<','<<z<<')'<<endl; }
};

替代原则——向上类型转换

  • 代替原则也叫赋值兼容规则:在任何需要基类对象(或地址)的地方,都可以由其公有派生类的对象(或地址)代替。
  • 公有派生类的对象可以自动转换为基类类型,基类的指针和引用可以指向派生类的对象。
  • 向上类型转换可以发生在:赋值、函数调用或函数返回时。
  • 对象的向上类型转换:派生类对象被“切片”,只剩下适合的基类子对象,会损失派生类对象的部分内容,因此不常使用。
  • 指针和引用的向上类型转换,只是简单改变地址类型,用不同方式解读同一地址内容,不会真正切除派生类对象的多余部分。
  • 实例:在上述Point2d和Point3d定义的基础上
int main()
{
	Point2d p2(1,2), *pt2 = &p2;
	Point3d p3(4,5,6), *pt3 = &p3;
	pt2 -> print(); //Point2d::print()
	pt3 -> print(); //Point3d::print()
	p2 = p3;  //对象切片
	p2.print();
	Point2d& r2 = p3; //引用
	r2.print();
	pt2 = &p3; //指针
	pt2 -> print();
}

在这里插入图片描述

多重继承

  • 如果一个派生类只有唯一的基类,这种继承关系称为单继承,一个派生类直接继承多个基类,这称为多重继承
  • 多重继承语法:
    class \quad 派生类名 \quad : \quad 访问限定符 \quad 基类1,
    \quad\quad\quad\quad\quad\quad\quad\quad 访问限定符 \quad 基类2,…
    { 成员声明; }
  • 派生类直接继承多个基类必须是不同的类型,每个基类都有自己的访问限定符,默认为private
  • 多重继承时,依据基类声明的次序来调用构造函数初始化这些子对象。
  • 多重继承的二义性
    • 如果两个基类中有同名的成员,在派生类中使用这个成员时会产生二义性。
    • 如果二义性是由上述原因造成,可以通过显式指定类名来消除。
class B1 {
public:
    void f(); void g();
};
class B2 {
public:
    void f(); void h();
};
class D : public B1, public B2 {...}
int main ()
{
    D obj;
    obj.f(); //错误,有二义性
    obj.B1::f(); //正确,调用B1::f()
    obj.B2::f(); //正确,调用B2::f()
};
  • 虚基类
    • C++不允许一个基类被同一个派生类多次直接继承,但是可以被多次间接继承,例如B继承A,C继承A,D继承B、C。这时D对象中间接包含两个A类子对象,A类成员的引用也会产生二义性。
    • 在继承时,可以将基类声明为虚基类,这种继承方式也称为虚继承。虚基类在继承层次中无论出现多少次,最终的派生类对象中也只会存在一个共享的基类子对象。这样,同一基类被多次间接继承而引起的二义性问题就得以解决。
    • 定义派生类时,在基类名字前加上关键字virtual 就可以将其声明为虚基类。语法如下:
      class \quad 派生类名 \quad : \quad virtual \quad 访问限定符 \quad 基类名
      或者:
      class \quad 派生类名 \quad : \quad 访问限定符 \quad virtual \quad 基类名

猜你喜欢

转载自blog.csdn.net/qq_43575267/article/details/86770163