C++笔记之类的继承和派生

一. 继承和派生介绍

1.概念

继承: 保持已有类的特性而构造新类的过程。
派生: 在已有类的基础上新增自己的特性而产生新类的过程。
基类(或父类):被继承的已有类。
派生类(或子类) :派生出的新类。

2.继承派生目的

继承的目的:实现代码重用
派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。

3.派生类定义

class  派生类名:继承方式  基类名1, 继承方式 基类名2...继承方式  基类n
{
	派生类成员声明;
};

注意:
(1)一个派生类可以同时有多个基类,即多继承;一个派生类可以只有一个基类,即单继承
(2)直接派生出某类的基类即直接基类,基类的基类甚至更高的基类为间接基类
(3)派生类成员是指除了从基类继承来的所有成员之外,新增加的数据和函数
举例:

class  TimeDate: public Time, private Date
{
public:
	TimeDate(int y,int m,int d,int h,int n,int s);
	~TimeDate();
};

4.派生类生成过程

(1)吸收基类成员
派生类继承了基类中除构造函数和析构函数之外的所有成员。
(2)改造基类成员
基类成员的访问控制问题;基类数据或函数成员的隐藏。
(3)添加新的成员
派生类成员的加入是继承和派生机制的核心,给派生类添加数据和函数成员实现新增功能。

二.继承方式

不同继承方式的影响主要体现在:

  • 派生类成员对基类成员的访问权限
  • 通过派生类对象对基类成员的访问权限

1.公有继承(Public)

基类的public和protected成员的访问属性在派生中保持不变,但基类的private成员不可直接访问

  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员
  • 派生类的对象只能访问基类的public成员

成员理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:public A
{
public:
	int b1;    //包含 int a1
private:
	int b2;		//包含int a2
protected:
	int b3;		//包含int a3
}

举例说明:

#include<iostream>
#include<cmath>
using namecpace std;

class Point	//基类Point类的声明
{
public:	//公有函数成员
	void initP(float xx=0, float yy=0)
    {x=xx;y=yy;}
	void move(float xOff, float yOff)
    {x+=xOff;x+=yOff;}
	float getX() {return x;}
	float getY() {return y;}
private:	//私有数据成员
	float x,y;
};

class Square: public Point  //派生类声明,公有继承方式
{
public:	
	void InitR(float x, float y, float l)  //新增公有函数成员
	{InitP(x,y); length =l;} //派生类成员函数调用基类公有成员函数
	float getLength() {return length;}
private:	
	float length;//新增私有数据成员,另外还有基类数据成员 float x, float y
};

int main()
{   Square square;
	square.InitR(1,1,10);    //通过派生类对象访问基类公有成员函数
	square.move(3,3);  
    cout<<"The data of square(X,Y,Length):"<<endl;
	cout<< square.getX()<<','<< square.getY()<<',' //通过派生类对象访问基类公有成员函数
		<< square.getLength()<<endl;
	return 0;
}

2.私有继承(Private)

基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问

  • 派生类中的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员
  • 派生类对象不能直接访问基类的任何成员

成员理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:private A
{
public:
	int b1;    
private:
	int b2;		//包含int a1,int a2, int a3
protected:
	int b3;		 
};

举例说明:

//基类和公有继承的Point类一样,此处略去
class Square: private Point	//派生类声明
{
public:	//新增外部接口
	void InitR(float x, float y, float l){InitP(x,y);length=l;}	//派生类成员函数访问基类公有成员函数
	void move(float xOff, float yOff) {Point::move(xOff,yOff) ;}
	float getX() {return Point::getX();} //派生类成员函数访问基类的getX函数
	float getY() {return Point::getY();}
	float getLength() {return length;}
private:	
	//包含Point类的public成员如move函数、getX函数、getY函数、initP函数,private成员float x,float y
	float length; 
};

int main()
{  //通过派生类对象只能访问本类成员
    Square square;
	square.InitR(1,1,10);  //派生类对象不能访问基类的任何成员,现在这些函数都是派生类自己的函数
	square.move(3,3);
    cout << "The data of square(X, Y, Lenght) :" ;
	cout << square.getX( ) << "," << square.getY( ) << ",";
	cout << square.getLength( ) << endl ;
	return 0;
}

3.保护继承(Protected)

基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问.

  • 派生类成员函数可以直接访问基类的public和protected成员,但不能直接访问基类的private成员
  • 派生类对象不能直接访问基类中的任何成员

protected成员的特点与作用:

  • 其所在类的对象来说,它与 private 成员的性质相同。
  • 对于其派生类来说,它与 public 成员的性质相同。
  • 既实现了数据隐藏,又方便继承,实现代码重用.

成员理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:protected A
{
public:
	int b1;    
private:
	int b2;		 //包含 int a2
protected:
	int b3;	//包含int a1, int a3	 
};

举例说明:

class A
{
protected:
	int x;
};
class B:public A
{
public:
	void Function() { x=5;}  //正确,protected成员对象其派生类来说是public
};
int main()
{   A a;
	a.x=5;  //报错,protected成员对其对象来说是私有,不能直接访问
}

Alt

三.类型兼容规则

一个公有派生类的对象在使用上可以被当做基类对象,反之则禁止。(大材小用可以, 小材大用不行

  1. 派生类的对象可以被赋值给基类对象
  2. 派生类对象可以初始化基类的引用
  3. 指向基类的指针也可以指向派生类

另外注意,通过基类对象名、指针,只能使用从基类继承的成员。

帮助理解:

class B
{ ...};
class C:public B
{ ... };
B b1,*pb1;
C c1;

b1 = c1; //派生类的对象可以被赋值给基类
B &bb = c1; //派生类的对象可以初始化基类的引用
pb1 = &c1; //指向基类的指针也可以指向派生类

基类指针指向派生类对象,只能使用继承下来的基类成员

#include <iostream>
using namespace std;
class B0	//基类B0声明
{ 
public:
	void display() {cout<<"B0::display()"<<endl;}//公有成员函数
};

class B1: public B0	
{
 public:
	void display(){cout<<"B1::display()"<<endl;}	
};

void fun(B0 *ptr)	
{	ptr->display();	//"对象指针->成员名"  
}
int main()	//主函数
{	B0 b0;	//声明B0类对象
	B1 b1;	//声明B1类对象
	B0 *p;	//声明B0类指针
	p=&b0;	//B0类指针指向B0类对象
	fun(p); //运行结果 B0::display()
	p=&b1;	//B0类指针指向B1类对象
	fun(p); //运行结果 B0::display()

四.派生类的构造函数和析构函数

1.继承时的构造函数

(1)**基类的构造函数不被继承,派生类需要声明自己的构造函数
(2) 声明构造函数时
只对本类新增成员进行初始化**,对继承而来的基类成员的初始化自动调用基类构造函数完成。
(3) 当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数。
(4) 当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类构造函数。

派生类名::派生类名(基类名1所需形参,基类名2所需形参,本类成员所需形参):基类名1(参数表),基类名2(参数表)
{
	本类成员初始化;
}

2.构造函数的调用次序

(1)调用基类构造函数,按照基类被继承时的声明次序(从左到右)
(2) 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序
(3) 派生类中的构造函数体中的内容。
构造函数的执行顺序和派生类构造函数中列出的名称顺序无关

3.复制构造函数

  • 若建立派生类对象时调用缺省拷贝构造函数,则编译器将自动调用基类的缺省拷贝构造函数
  • 若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数
C::C(C &c1):B(c1)
{}

4.继承时的析构函数

(1) 析构函数也不被继承,派生类自行声明
(2) 声明方法与一般(无继承关系时)类的析构函数相同。
(3) 不需要显式地调用基类的析构函数,系统会自动隐式调用。
(4)析构函数的调用次序与构造函数相反

举例说明:

#include<iostream>
#include<string>
using namespace std;
class Person {
public:
	Person(string n, char s, int a);
	Person(Person &p);
	void input();
	void display();
private:
	string name;
	char sex;
	int age;
};
Person::Person(string n, char s, int a) {
	name = n;
	sex = s;
	age = a;
}
Person::Person(Person &p) {
	name = p.name;
	sex = p.sex;
	age = p.age;
}
void Person::input() {
	cout << "请输入姓名:";
	cin >> name;
	cout << "年龄:";
	cin >> age;
	cout << "性别:";
	cin >> sex;
}
void Person::display() {
	cout << "姓名:" << name << ", 年龄:" << age << ", 性别:" << sex;
}
class Student :public Person {
public:
	Student(string n, char s, int a, int num, int cn, int math, int eng, int chin);
	Student(Student &st);
	void input();
	void display();
private:
	int number, classnum, Math, English, Chinese;
};
Student::Student(string n, char s, int a, int num, int cn, int math, int eng, int chin) :Person(n, s, a) {
	Math = math;
	English = eng;
	Chinese = chin;
	number = num;
	classnum = cn;
}
Student::Student(Student &stu):Person(stu) {
	number = stu.number;
	classnum = stu.classnum;
	Math = stu.Math;
	English = stu.English;
	Chinese = stu.Chinese;
}
void Student::input() {
	Person::input();
	cout << "学号:";
	cin >> number;
	cout << "班级:";
	cin >> classnum;
	cout << "数学、英语、语文:" << endl;
	cin >> Math >> English >> Chinese;
}
void Student::display() {
	Person::display();
	cout << ", 学号:" << number << ", 班级:" << classnum << ", 数学:" << Math << ", 英语:" << English << ", 语文:" << Chinese << endl;
}
int main() {
	Student stu1("小王",'m',20,20200509,1,90,89,99);
	stu1.display();

	Student stu2(stu1);
	stu2.display();

	stu1.input();
	stu1.display();
	return 0;
}

运行结果:
Alt

五.同名隐藏规则和二义性问题

1.同名隐藏规则

当派生类与基类中有相同成员时:

  • 若未强行指名,则通过派生类对象使用的是派生
    类中的同名成员
  • 如要通过派生类对象访问基类中被覆盖的同名成
    员,应使用基类名限定。
#include <iostream>
using namecpace std;
class B1	//声明基类B1
{ 
public:	
	int number;
	void fun()  {cout<<"Member of B1"<<endl;}
};
class D1: public B1	 
{ 
public:
	int number;	//同名数据成员
	void fun() {cout<<"Member of D1"<<endl;}	//同名函数成员
};
int main()
{	
    D1 d1;
	d1.number=1;  //对象名.成员名标识, 访问D1类成员
	d1.fun();	         
	d1.B1::number=2;  //作用域分辨符标识, 访问基类B1成员
	d1.B1::fun();	
}

2.二义性问题

多继承时基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数同名隐藏规则来解决。

派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。

举例一:

class A
{
public:
	void f();
};
class B
{
public:
	void f();
	void g();
}
class C:public A, public B
{
public:
	void g();
};
int main()
{   C  c1;
	c1.f(); //二义性, 基类之间同名函数
	c1.g(); //无二义性,同名隐藏
	
	//解决方法1:用类名限定
	c1.A::f()  或  c1.B::f()
	//解决方法2:同名覆盖
	在C中声明一个同名成员函数f()
	在其中调用A::f()  或  B::f()
}

举例二:

class Employee
{         
public:
     int salary;
}; 
class salesMan : public Employee
{
private:
     int a;
};
class manager : public Employee
{
private:
     int b;
};
class salesManager: public salesMan,public manager
{
public:
     int f();
private:
     int c;
};
int main()
{   salesManager s1;
	s1.salary;    //报错,有二义性
	s1.Employee::salary;   //报错,有二义性
	s1.salesMan::salary; //无二义性
	s1.manager::salary; //无二义性
}

六.虚基类

1.虚基类介绍

(1) 用于有共同基类的场合。 在第一级继承时就要将共同基类设计为虚基类
(2) 关键字 virtual
如: class B1: virtual public B
(3) 作用:

  • 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.
  • 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝

举例说明:

class B{ public: int b; void fun(){} };
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class D1 : public B1, public B2{ private: int b3;}

//下面的访问是正确的:
D1 d;
d.b; //使用最远基类成员
d.fun();//使用最远基类成员

Alt

B1
B
B2
D1

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

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

举例说明:
定义Person(人员类),包含姓名、年龄、性别、地址、电话等数据成员。再分别定义派生类Teacher(教师)类Cadre(干部)类,然后采用多重继承方式由这两个类派生出新类Teacher_ Cadre(教师兼干部)类
要求:
① 在Teacher类中还包含数据成员title(职称),在Cadre类中还包含数据成员post(职务),在Teacher Cadre类中还包含数据成员wages(工资)。
② 在类体中声明成员函数,在类外定义成员函数。
③输出Teachers_Cadre类对象的姓名、年龄、性别、职称、地址、电话、职务与工资。
④使用对象数组保存输入的对象

#include<iostream>   
#include<string>   
using namespace std;
class Person 
{
public:
	Person(string n, int a, char s, string add, int te);
	void show();
private:
	string name;
	int age;
	char sex;
	string address;
    int tel;
};
Person::Person(string n, int a, char s, string add, int te) 
{
	name = n;
	age = a;
	sex = s;
	address = add;
	tel = te;
}
void Person::show() 
{
	cout << "姓名:" << name << " 年龄:" << age << " 性别:" << sex << " 地址:" << address << " 电话:" << tel;
}

class Teacher:virtual public Person
{
public:
	Teacher(string n, int a, char s, string add, int te, string ti);
	void show();
	void getTitle();
private:
	string title;  
};
Teacher::Teacher(string n, int a, char s, string add, int te, string ti):Person(n,a,s,add,te)
{
	title = ti;
}
void Teacher::show() 
{
	Person::show();
	cout << " 职称:" << title;
}
void Teacher::getTitle() {
	cout << " 职称:" << title;
}
class Cadre:virtual Person
{
public:
	Cadre(string n, int a, char s, string add, int t, string p);
	void show();
	void getPost();
private:
	string post;  
};
Cadre::Cadre(string n, int a, char s, string add, int te, string p):Person(n, a, s, add, te)
{
	post = p;
}
void Cadre::show() 
{
	Person::show();
	cout << " 职务:" << post;
}
void Cadre::getPost() 
{
	cout << " 职务:" << post;
}
class Teacher_Cadre :public Teacher, public Cadre  
{  // 声明多重继承的Teacher_Cadre类
public:
	Teacher_Cadre(string n, int a, char s, string add, int te, string ti, string p, double w);
	void show();
private:
	double wages;
};
Teacher_Cadre::Teacher_Cadre(string n, int a, char s, string add, int te, string ti, string p, double w):Person(n, a, s, add, te),Teacher(n, a, s, add, te,ti),Cadre(n, a, s, add, te,p)
{
	wages = w;
}
void Teacher_Cadre::show()  
{
	Person::show();
	Teacher::getTitle();
	Cadre::getPost();
	cout << " wages: " << wages << endl;

}
int main()
{
	Teacher_Cadre *tc = new Teacher_Cadre[3]{
		Teacher_Cadre("zhangsan", 30, 'm', "广C213", 85290111,"tutor(助教)","普通职工",6000),
		Teacher_Cadre("lisi", 40, 'm', "广C504", 85290222,"associate professor(副教授)","副院长",8000),
		Teacher_Cadre("wangwu", 32, 'f', "广C502", 85290333,"lectuer(讲师)","教研室主任",7000)
	};
	for (int i = 0;i < 3;i++) {
		tc[i].show();
	}
	delete[]tc;
	return 0;
}

运行结果
在这里插入图片描述

欢迎关注微信公众号:学编程的金融客,作者:小笨聪

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章