Day_08 类的继承和派生

本文详细介绍了面向对象编程中的继承概念,包括单继承、多继承以及虚继承。讨论了子类如何通过构造函数初始化继承自父类的属性,并解释了同名属性的访问规则。此外,还探讨了菱形继承和构造顺序问题,以及在实际应用中设计类的示例。
摘要由CSDN通过智能技术生成

        继承和派生其实都是一回事,只是说法不同罢了,如:子类继承了父类,父类派生了子类。

因此 父类也称作基类,子类也成为派生类。下文统一使用 继承、父类、子类 的说法。

        继承的实质就是:父类中的成员数据和成员函数,子类中也有一份。(要注意权限问题)

1.继承中子类的写法

        格式类似于 :class 子类名:继承方式 + 父类名,继承方式有public protected private 三种继承方式。三种继承方式的区别在于子类对父类属性的访问权限。继承方式只会增强父类属性在子类中的权限显示。

publicprotectedprivate
protected继承protectedprivate不可直接访问
public继承publicprotected不可直接访问
private继承privateprivate不可直接访问

        我们先写一个单继承的简单的类:

//父类
class parent{
public:
	void print(){
		cout << name << "  " << money << endl;
	}
	string& getWife(){
		return wife;
	}
protected:
	string name;
	int money;
private:
	string wife;
};
//子类
class son :public parent 
{
public:
	void printSon() 
	{
		print();
		cout << name << "\t" << money << endl;
		//cout << wife << endl;			父类中私有属性不能直接访问
		cout << getWide() << endl;		//间接通过父类的函数访问
	}
protected:
};

        上述son类通过public方式继承。不能直接访问父类中private下的属性。但是可以在父类中设置接口返回private下的属性。就可以在子类中间接访问父类下的保护属性。

2.子类中的构造函数

        父类中的属性通过父类的构造函数初始化,而子类中继承的父类属性,也需要通过初始化参数列表的写法来调用父类中的构造函数来初始化。(这一点类似于 组合类中,给其他类的对象初始化。不过还是有细微的差别)

class Parent 
{
public:
	Parent() { cout << "父类无参构造函数" << endl; }
	Parent(string FName, string SName):FName(FName), SName(SName){}
protected:
	string FName;
	string SName;
};
//单继承
class Son :public Parent
{
public:
	//这种写法,父类必须存在无参的构造函数,当然缺省也可以
	Son() { cout << "子类无参构造函数" << endl; }    
	Son(string FName, string SName, string sonSName) :Parent(FName,SName)
	{
		//自己的属性用什么办法初始化都行
		this->sonFName = FName;
		this->sonSName = sonSName;
	}
	void print() 
	{
		cout << "父:" << FName + SName << endl;
		cout << "子:" << sonFName + sonSName << endl;
	}
protected:
	string sonFName;
	string sonSName;
	//string FName;  相当于protected下有这两个父类属性
	//string SName;
};

        从上述代码可以看到初始化子类中的父类属性时,不同于初始化组合类的对象。前者是类名(...),后者是对象名(参数...)。

        还有一个注意点,子类如果想定义一个无参构造函数的话,父类中必须也要有一个无参构造函数。(因为子类中父类属性的初始化本质是调用父类的构造函数)。

3.多继承

        一个子类同时继承两个或两个以上的父类,称为多继承。

class 子类:继承方式 父类1,继承方式 父类2。这时,子类就有父类的所有数据。

class MM
{
public:
	//MM() = default;
	MM(string mmFName, string mmSName) 
	{
		this->mmFName = mmFName;
		this->mmSName = mmSName;
	}
protected:
	string mmFName;
	string mmSName;
};
class GG 
{
public:
	//GG() = default;
	GG(string ggFName, string ggSName)
	{
		this->ggFName = ggFName;
		this->ggSName = ggSName;
	}

protected:
	string ggFName;
	string ggSName;
};
class Girl :public GG,public MM
{
public:
	//子类想要这种构造函数,每个父类都要有一个无参的构造函数
	//Girl() {}
	Girl(string mmFName, string mmSName, string ggFName, string ggSName) 
		:MM(mmFName,mmSName),GG(ggFName,ggSName)
	{
		girlFName = ggFName + mmFName;
		girlSName = ggSName + mmSName;
	}
	void print() 
	{
		cout << "父:" << ggFName + ggSName << endl;
		cout << "母:" << mmFName + mmSName << endl;
		cout << "女:" << girlFName + girlSName << endl;
	}
protected:
	string girlFName;
	string girlSName;
};

        和单继承一样,子类中父类的属性,都需要通过初始化参数列表,然后调用父类的构造函数来初始化。多继承中,若是子类想定义一个无参构造函数,那么所有的父类中必须都有无参构造函数。

4.继承中同名问题

        在继承中,若是子类有属性名和父类中的属性名一样时,在不做特殊说明的情况下,编译器一般采取就近原则即优先调用子类中的属性,而不是继承下来的属性。若是想强行使用父类中的属性的话,我们需要使用类名限定符 ::  。

class MM
{
public:
	MM(string name, int age) :name(name), age(age) {}
	void print() 
	{
		cout << "MM:";
		cout << name << "\t" << age << endl;
	}
protected:
	string name;
	int age;
};
//子类
class Girl :public MM 
{
public:
	Girl(string name, int age) :MM("父类", 28), name(name), age(age) {}
	void print() 
	{
		//不做特别处理,就近原则
		cout << name<<"\t" << age << endl;
		//类名限定
		cout << MM::name << "\t" << MM::age << endl;
		//不做特别处理,就近原则
		MM::print();
	}
protected:
	string name;
	int age;
};

        子类的对象调用属性时也遵循就近原则原则。 优先调用子类中有的属性,再调用子类中继承下来的属性。 

	//不做特别处理,就近原则
	//正常对象调用
	Girl  girl("girl", 18);
	girl.print();
	MM mm("mm", 28);
	mm.print();

指针初始化对象(小知识点)

        允许子类对象初始化父类指针,没有virtual的情况下,看指针类型(父类指针,调用的都是父类属性)。有virtual的情况下看赋值对象

	MM* pMM = new Girl("newGirl", 49);
	pMM->print();		//父类的
	//在没有virtual情况下,看指针类型
	//在有virtual情况,看赋值对象

        不允许父类对象初始化子类指针。

    //父类对象初始化子类指针,不安全
    Girl* pGirl = new MM("newMM", 29); //错误

    MM* pM = new MM("newMM", 29);
	Girl* pGirl = (Girl*)pM;
	//pGirl->print();  引发问题

菱形继承

         继承的写法若使用不当的话,很容易会造成菱形继承。下面一张图表示了菱形继承      

         具体解释:假设类A中有一个变量a,变量a被类B和类C继承,接着类D继承类B和类C,那么就会出现问题,类D中会继承两个变量a。

        所以为了避免这种情况产生,我们在类B,类C继承类A的时候就要用到虚继承。而且类D中的构造函数还必须多调用他的祖父(类A)的构造函数,也就是类D中要调用A,B,C三个类的构造函数。且最后变量a的值,由祖父的构造函数决定)类B,C中的变量a也会随之改变。

//虚继承 --->菱形继承
class A
{
public:
	A(int a) :a(a) {}
protected:
	int a;
};
class B :virtual public A
{
public:
	B(int a, int b) :A(a), b(b) {}
protected:
	int b;
};
class C :virtual public A
{
public:
	C(int a, int c) :A(a),c(c) {}
	void printC() 
	{
		cout << a << endl;
	}
protected:
	int c;
};
class D :public C, public B 
{
public:
	//菱形继承,必须调用祖父的构造函数
	D() :B(1, 2), C(3, 4) ,A(999)
	{

	}
	void print() 
	{
		//只有一个a 只有一份
		cout << a << endl;
		cout << B::a << endl;
		cout << C::a << endl;
		printC();
	}
protected:

};

5.构造顺序问题

  • 单继承中的构造顺序问题

    • 先构造父类的在构造子类的,析构顺序相反

  • 多继承中构造顺序问题

    • 任何构造顺序问题都和初始化参数列表无关

    • 构造顺序和继承顺序一致

  • 多继承中构造顺序问题

  • #include <iostream>
    using namespace std;
    class A 
    {
    public:
    	A() { cout << "A"; }
    	~A() { cout << "A" ; }
    protected:
    };
    class B :public A 
    {
    public:
    	B() { cout << "B"; }
    	~B() { cout << "B" ; }
    };
    
    class C 
    {
    public:
    	C() { cout << "C"; }
    	~C() { cout << "C"; }
    };
    class D
    {
    public:
    	D() { cout << "D"; }
    	~D() {cout << "D";}
    };
    //构造顺序和继承顺序一致
    class F :public C, public A, public D
    {
    public:
    	//任何构造顺序问题,都和初始化参数列表无关
    	F() { cout << "F";}
    	~F() { cout << "F";}
    };
    //CADF FDAC
    int main() 
    {
    	{
    		B b;   //ABBA 
    	}
    	cout << endl;
    	F f;
    	return 0;
    }

 6.作业

        设计一个类父类 Shape类 设计多个子类:Rect类 Circle类 分别求出并打印相应形状的周长和面积。

#include<iostream>
#include<string>
using namespace std;
class Shape{
public:
	Shape(double widht = 0.0, double lenth = 0.0, double radius=0.0):width(widht),lenth(lenth),radius(radius){

	}
protected:
	double width;
	double lenth;
	double radius;

};
class Circle:public Shape{
public:
	Circle(double radius) :Shape(0.0, 0.0, radius){
		this->radius = Shape::radius;
	}
	void printPerimeter(){
		cout <<"周长:"<< 2 * 3.14*radius << endl;
	}
	void printArea(){
		cout << "面积:" << 3.14*radius*radius << endl;
	}
protected:
	double radius;
};

class Rectangle :public Shape{
public:
	Rectangle(double lenth, double width) :Shape(width, lenth, 0.0){
		this->lenth = Shape::lenth;
		this->width = Shape::width;
	}
	void printPerimeter(){
		cout << "周长:" << 2 * (lenth+width) << endl;
	}
	void printArea(){
		cout << "面积:" << lenth*width << endl;
	}
protected:
	double radius;
};

int main(){
	Circle cir(4);
	cir.printArea();
	cir.printPerimeter();

	Rectangle rec(10, 20);
	rec.printArea();
	rec.printPerimeter();

	return 0;
}

        设计类老师类,设计一个学生类,多继承产生一个研究生类,打印相关研究生的信息

#include<iostream>
#include<string>
using namespace std;
class Teacher{
public:
	Teacher(string teacherName, string teacherSubject) :teacherName(teacherName), teacherSubject(teacherSubject){
	
	}
	void printTeacherInformation(){
		cout << "老师姓名:" << this->teacherName << "    老师教的学科:" << this->teacherSubject << endl;
	}
protected:
	string teacherName;
	string teacherSubject;
};

class Student{
public:
	Student(string studentName, int studentAge) :studentName(studentName), studentAge(studentAge){
	
	}
	void printStudentInformation(){
		cout << "学生姓名:" << this->studentName << "    学生年龄:" << this->studentAge << endl;

	}
protected:
	string studentName;
	int studentAge;
};


class Graduate_student :public Student, public Teacher{
public:
	Graduate_student(string teacherName, string teacherSubject, string studentName, int studentAge) :Student(studentName, studentAge), Teacher(teacherName, teacherSubject){
		this->GraduateStudentName = studentName;
		this->GraduateStudentAge = studentAge;
	}
	void printInformation(){
		cout << "该研究生的教师信息:";
		printTeacherInformation();
		cout << "该研究生信息:";
		printStudentInformation();
	}
protected:
	string GraduateStudentName;
	int GraduateStudentAge;
};
int main(){
	Graduate_student std("张宏", "语文", "王小强", 19);
	std.printInformation();
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,根据您的要求,我为您设计了一个派生 Birthtime,继承 Time 和 Date,增加了一个数据成员 Childname,表示小孩的名字,同时还设计了一个函数 ShowBirthtime,用于输出小孩的出生时间和名字,代码如下: ```cpp #include <iostream> #include <string> using namespace std; class Time { public: Time(int h, int m, int s) : hour(h), minute(m), second(s) {} void ShowTime() const { cout << hour << ":" << minute << ":" << second << endl; } protected: int hour, minute, second; }; class Date { public: Date(int y, int m, int d) : year(y), month(m), day(d) {} void ShowDate() const { cout << year << "-" << month << "-" << day << endl; } protected: int year, month, day; }; class Birthtime : public Time, public Date { public: Birthtime(int y, int m, int d, int h, int mi, int s, const string& name) : Time(h, mi, s), Date(y, m, d), Childname(name) {} void ShowBirthtime() const { cout << "Childname: " << Childname << endl; cout << "Birthdate: "; ShowDate(); cout << "Birthtime: "; ShowTime(); } private: string Childname; }; int main() { Birthtime b(2021, 8, 1, 10, 30, 0, "Tom"); b.ShowBirthtime(); return 0; } ``` 在上面的代码中,我们定义了三个,分别是 Time、Date 和 Birthtime。Time 表示时间,Date 表示日期,这两个都是基。Birthtime 继承了 Time 和 Date,增加了一个数据成员 Childname,表示小孩的名字。同时,它还定义了一个函数 ShowBirthtime,用于输出小孩的出生时间和名字。 在 main 函数中,我们创建了一个 Birthtime 对象 b,表示小孩 Tom 的出生时间和名字。然后调用 ShowBirthtime 函数,输出小孩的相关信息。运行程序,输出结果如下: ``` Childname: Tom Birthdate: 2021-8-1 Birthtime: 10:30:0 ``` 可以看到,程序成功输出了小孩的出生时间和名字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值