暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

第十七天:C++类的继承

瓶子的跋涉 2023-02-16
742

C++ 类的继承

类的继承与派生

  • 继承与派生是同一过程从不同的角度看 _ 保持已有类的特征而构造新类的过程称为继承 _ 在已有类的基础上新添自己的特征而产生新类的过程称为派生
  • 被继承的已有类称为基类
    /(或父类)
  • 派生出的新类称为派生类
    /(或子类)
  • 直接参与派生出某类的基类称为直接基类
  • 基类的基类甚至更高层的基类称为间接基类
  • 继承的目的:实现设计与代码的重用
  • 派生的目的:当新问题出现,原有程序无法解决(或不能完全解决)需要对原有程序进行改造。

派生类的定义

单继承时:
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...
{
成员声明
}
//每一个"继承方式",只用于限制对紧随其后之基类的继承
class Derived:public Base1,private Base2
{
public:
Derived();
~Derived();
}

派生类的构成

  • 吸收基类成员 * 吸收基类成员之后,派生类实际上包含了它的全部积累中除构造和析构函数之外的全部成员
  • 改造基类成员 * 如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或覆盖了外层同名成员
  • 添加新的成员 * 派生类添加新成员使派生类在功能上有所发展

不同继承方式及类成员的访问控制

不同继承方式的影响主要体现在:(继承过来,在哪里能用,可以怎么用)
  • 派生类成员对基类成员的访问权限
  • 通过派生类对象对基类成员的访问权限
三种继承方式:
  • 共有继承(初学精通) _ 基类的 public 和 protected 成员的访问属性
    在派生类中保持不变
    ,但基类的 private 成员不可直接访问
    。 _ 派生类中的成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员 * 通过派生类的对象访问从基类继承的成员,只能访问 public 成员。
  • 私有继承
  • 保护继承
公有继承举例
Point.h
#pragma once
#ifndef _POINT_H
#define _POINT_H
class Point 基类Point类的定义
{

public:
void initPoint(float x = 0, float y = 0)
{
this->x = x;
this->y = y;
}
void move(float offx, float offy)
{
x += offx;
y += offy;
}
float getX() const
{
return x;
}
float getY() const
{
return y;
}
private:
float x, y;
};




#endif // !_POINT_H


Rectangle.h
#pragma once
#ifndef _RECTANGLE_H
#define _RECTANGLE_H
#include "Point.h"
class Rectangle :public Point //派生类的定义
{
public: //新添共有函数成员
void initRectangle(float x, float y, float w, float h)
{
initPoint(x, y); //调用基类共有成员函数
this->w = w; //this此Rectangle指针,可以调用本类私有成员以及成成的派生类的共有函数
this->h = h;
}
float getH() const
{
return h;
}
float getW() const
{
return w;
}
private: //新添私有函数成员
float w, h;

};


#endif // !_RECTANGLE_H


main.h
/*
project :矩形位置,类的继承
*/

#include <iostream>
#include "Rectangle.h"

using namespace std;

int main(void)
{
Rectangle rect;//定义Rectangle类的对象
//设置举行数据
rect.initRectangle(2, 3, 20, 10);//设置矩形数据
rect.move(3, 2);//移动矩形位置
cout << "The data of rect(x,y,w,h):" << endl;
//输出矩形的特征参数
cout << rect.getX() << "," << rect.getY() << "," << rect.getW() <<"," << rect.getH() << endl;
return 0;
}
/*
The data of rect(x,y,w,h):
5,5,20,10
*/


protected
成员的特点与作用

  • 对建立其所在类
    对象的模块来说,它与private
    成员的性质相同,类外不能直接访问
  • 对于其派生类
    来说,它与public
    成员性质相同
  • 即实现了数据隐藏,又方便继承,实现代码重用

私有继承(private
)

  • 基类的 public 和 protected 成员都以 private 身份出现在派生类中,但基类的private成员不可直接访问
    ,派生类内使用,不对外继承,在类外需要调用,需要在派生类中重新定义对外服务接口
  • 派生类中的成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员
  • 通过派生类的对象不能直接访问从基类继承的任何成员

保护继承(protected)

  • 基类的public
    protected
    成员都以protected
    身份出现在派生类中,但基类的private
    成员是不可直接访问
  • 派生类中的成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员
  • 通过派生类的对象不能直接访问从基类继承的任何成员

向上转型

  • 一个共有派生类的对象在使用上可以被当作基类的对象,反之则不可,具体表现在: _ 派生类的对象可以隐含转换为基类对象 _ 派生类的对象可以初始化基类的引用 * 派生类的指针可以隐含转换为基类的指针
  • 通过基类对象名,指针只能使用从基类继承的成员 注意:

继承时的构造函数

  • 默认情况下基类的构造函数不被继承,派生类需要定义自己的构造函数
  • 定义构造函数时,只需要对本类中新添成员进行初始化,对继承来的基类成员的初始化,是自动调用基类构造函数完成的
  • 派生类的构造函数需要给基类的构造函数传递参数
  • C++11 规定可以用 using 语句继承基类构造函数,使之成为派生类的构造函数,但是只能初始化从基类继承的成员 语法形式:
	using Base::Base;

单一继承时构造函数的定义

派生类名::派生类名(基类所需的形参,本类成员所需的形参):
基类名(形参表),本类成员初始化列表
{
//其他初始化;
}

多继承时构造函数的定义

派生类名::派生类名(参数表):
基类名1(基类1初始化参数表),基类名2(基类2初始化参数表),...,基类名n(基类n初始化参数表),本类成员初始化列表
{
//其他初始化;
}

派生类与基类的构造函数

  • 当基类中声明有默认构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数,也可以不声明构造函数。构造派生类的对象时,基类的默认构造函数将被调用
  • 当需要执行基类中带形参的构造函数来初始化基类数据时,派生类构造函数应在初始化列表中为基类构造函数提供参数

多继承且有对象成员时的构造函数

即有继承又有组合的情况

派生类名::派生类名(参数表):
基类名1(参数),基类名2(参数),...,基类名n(参数),对象成员初始化列表,基本类型成员初始化列表
{
//其他初始化;
}

构造函数的执行顺序

1,调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右) 2,对初始化列表中的对象成员和基本类型成员进行初始化,初始化顺序按照它们在类中声明的顺序。对象成员初始化是自动调用对象所属类的构造函数完成的。 3,执行派生类的构造函数体中的内容。

#include <iostream>
using namespace std;
class Base1
{

public:
Base1(int i)
{
cout << "Constructing Base1 " << i << endl;
}
};
class Base2
{

public:
Base2(int j)
{
cout << "Constructing Base2 " << j << endl;
}
};
class Base3
{

public:
Base3()
{
cout << "Constructing Base3 * " << endl;
}
};
class Derived :public Base2, public Base1, public Base3 //首先按照此定义出书
{
public:
Derived(int a, int b, int c, int d) :
Base1(a),
member2(d),
member1(c),
Base2(b)
//此处的次序与构造函数的执行次序无关
{

}

private: //之后根据此调用
Base1 member1;
Base2 member2;
Base3 member3;
};
int main()
{
Derived obj(1, 2, 3, 4);
return 0;
}
/*
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
*/


复制构造函数

  • 若建立派生类对象时没有编写复制构造函数,编译器会生成一个隐含的复制构造函数,该函数先调用基类的复制构造函数,再为派生类新添的成员对象执行复制。
  • 若编写派生类的复制构造函数,一般都要为基类的复制构造函数传递参数
  • 派生类的复制构造函数只能接受一个参数,此参数不仅用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。
  • 基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用
C::C(const C &c1):B(c1)
{
...
}

析构函数

  • 析构函数不被继承,派生类如果需要,要自行声明析构函数
  • 声明方法与一般(无继承关系时)类的析构函数相同
  • 不需要显式地调用基类的析构函数,系统会自动隐式调用
  • 析构函数的调用次序与析构函数相反
/* 析构函数与构造函数的执行次序相反*/
#include <iostream>
using namespace std;
class Base1
{

public:
Base1(int i)
{
cout << "Constructing Base1 " << i << endl;
}
~Base1()
{
cout << "Destructing Base1" << endl;
}
};
class Base2
{

public:
Base2(int j)
{
cout << "Constructing Base2 " << j << endl;
}
~Base2()
{
cout << "Destructing Base2" << endl;
}
};
class Base3
{

public:
Base3()
{
cout << "Constructing Base3 * " << endl;
}
~Base3()
{
cout << "Destructing Base3" << endl;
}
};
class Derived :public Base2, public Base1, public Base3 //首先按照此定义出书
{
public:
Derived(int a, int b, int c, int d) :
Base1(a),
member2(d),
member1(c),
Base2(b)
//此处的次序与构造函数的执行次序无关
{

}

private: //之后根据此调用
Base1 member1;
Base2 member2;
Base3 member3;
};
int main()
{
Derived obj(1, 2, 3, 4);
return 0;
}
/*
Constructing Base2 2
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3
Constructing Base2 4
Constructing Base3 *
Destructing Base3
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2
*/


虚继承

作用域限定
  • 当派生类与基类中有相同成员时 _ 若未特别限定,则通过派生类对象使用的是派生类中的同名成员 _ 若要通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符(::)来限定
#include <iostream>
using namespace std;
class Base1
{

public:
int var;
void fun()
{
cout << "member of bases1" <<var<< endl;
}
};
class Base2
{

public:
int var;
void fun()
{
cout << "member of bases2" << var << endl;
}
};
class Derived:public Base1,public Base2
{
public:
int var;
void fun()
{
cout << "member of Derived" << var << endl;
}
};
int main(void)
{
Derived d;
Derived* p = &d;
//访问Derived类成员
d.var = 1;
d.fun();
//访问Base1基类成员
d.Base1::var = 2;
d.Base1::fun();
//访问Base2基类成员
p->Base2::var = 3;
p->Base2::fun();
return 0;
}
/*
member of Derived1
member of bases12
member of bases23
*/


虚基类语法与用途

  • 需要解决的问题 * 当派生类从对个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性
  • 虚基类声明 * 以virtual
    说明基类继承方式
class B1:virtual public B

  • 作用: _ 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题 _ 为最远的派生类提供唯一的基类成员,而不重复产生多次复制
  • 注意: * 在第一级继承时就要将共同基类设计为虚基类
#include <iostream>
using namespace std;
class Base0
{

public:
int var0;
void fun0()
{
cout << "member of bases1" << endl;
}
};
class Base1:virtual public Base0
{
public:
int var1;

};
class Base2 :virtual public Base0
{
public:
int var2;

};
class Derived:public Base1,public Base2
{
public:
int var;
void fun()
{
cout << "member of Derived" << endl;
}
};
int main(void)
{
Derived d;
d.var0 = 2; //直接访问虚基类的数据成员
d.fun0(); //直接访问虚基类的函数成员
cout << &d.Base1::var0 << ", " << &d.Base2::var0 << endl; //空间地址相同
cout << &d.var0;

return 0;
}
/*输出:
member of bases1
00000066B05BF6E0, 00000066B05BF6E0
00000066B05BF6E0
*/


有虚基类时的构造函数

#include <iostream>
using namespace std;
class Base0
{

public:
Base0(int var) :var0(var)
{

}
int var0;
void fun0()
{
cout << "member of bases0" << endl;
}
};
class Base1 :virtual public Base0
{
public:
Base1(int var) :Base0(var)
{

}
int var1;

};
class Base2 :virtual public Base0
{
public:
Base2(int var) :Base0(var)
{

}
int var2;

};
class Derived :public Base1, public Base2
{
public:
Derived(int var) :Base0(var), Base1(var), Base2(var) //
{

}
int var;
void fun()
{
cout << "member of Derived" << endl;
}
};
int main(void)
{
Derived d(1); //最远派生类对象
d.var0 = 2; //直接访问虚基类的数据成员
d.fun0(); //直接访问虚基类的函数成员

return 0;
}
/*输出:
member of bases0
*/


虚基类及其派生类构造函数

  • 建立对象时所指定的类称为最远派生类
  • 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的
  • 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数
  • 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。


文章转载自瓶子的跋涉,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论