C++ 继承(补充)

目录

继承关系

赋值兼容规则

继承中的作用域

派生类的默认成员函数

构造函数

 拷贝构造函数

派生类的 operator=

派生类的析构函数

 思考题:设计出一个类A,让这个类不能被继承

继承与友元

复杂的菱形继承及菱形虚拟继承

*C++编译器如何通过虚继承解决数据冗余和二义性问题?(通过内存调试观察)

继承与组合


继承关系

 以学生类和老师类为例

#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter";//姓名
	int _age = 18; //年龄
};
class Student : public Person
{
protected:
	int _studi;//学号
};
class Teacher : public Person
{
protected:
	int _jobid;//工号
};

int main()
{
	Student s;
	Teacher t;

	s.Print();
	t.Print();
	return 0;
}

 基类的其他成员在子类的访问方式取小  public>proteced>private,基类的私有成员在派生类中不可见。

class Student : private Person
{
protected:
	int _studi;//学号
};

 私有成员和私有继承

 出现错误

 在类外收到访问限定符的限制,但是在类里面就不收到该限制。若在子类中设置一个函数,

该函数无法被调用

 

 换成公有继承,也错误

 

 综上,父类的私有成员在子类不可见。不可见的意思是:内存上子类对象有这个成员,但是语法规定了我们不能去访问

赋值兼容规则

父子类对象之间能否互相赋值?

class Person
{
protected:
	string _name; //姓名
	string _sex;  //性别
	int    _age;  //年龄
};
class Student : public Person
{
public:
	int _No;//
};
int main()
{
	Person p;
	Student s;

	p = s;
	s = p;

	return 0;
}

 

 使用强制类型转换

 综上子类可以给父类,反之不行。派生类对象赋值给基类的对象/基类的指针/基类的引用(将子类给父类的过程)称为切割/切片。切割的三种情况如下

继承中的作用域

class Person
{
protected:
	string _name = "x";//姓名
	int _num = 111;    //身份证号
};

class Student : public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "身份证号:" << Person::_num << endl;
		cout << "学号:" << _num << endl;
	}
protected:
	int _num = 999;//学号
}; 
int main
{
	Student s
	return 0;
}

 出现两个num,一般访问时访问的是子类的num,若想访问父类的num,加上限定符即可

隐藏(重定义): 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问

下面代码为例不能编译通过

class A
{
public:
	void fun()
	{
		cout << "fun()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b;
	b.fun(10);
}

  B中的fun和A中的fun不是构成重载,因为不是在同一作用域,B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。想调用函数应该指定作用域

派生类的默认成员函数

构造函数


class Person
{
public:
	Person(const char* name = "peter")
		:_name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person&)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;//姓名
};
 
class Student : public Person
{
public:
	Student(const char* name, int id)
		:_name(name)
		,id(id)
	{

	}

private:
	int _id;
};
int main()
{
	Student s("张三",1);

	return 0;
}

 程序编译出错

 

 不初始化name反而可以编译通过

 调试过程中打印了一个person,说明调用了person函数,但派生类中我们没有调用person。则在初始化列表阶段会自动调用

 

 改正如下。子类对象中先调用父类再调用子类。调用父类构造函数初始化继承父类部分,再初始化自己的成员

 

 拷贝构造函数

Student(const Student& s)
		:Person(s)  //-> s传递给Person& s是一个切片行为
		,_id(s._id)
	{
		//类似构造函数
		cout << "Student(const Student& s)" << endl;
	}

派生类的 operator=

Person& operator(const Person& p)
	{
		cout << "Person opertor=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;
		    
		return *this;
	}
Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			operator=(s);
				_id = s._id;
		}
		return *this;
	}

运行后报了一个栈溢出的错误:子类的operator赋值和父类的operator赋值构成隐藏关系(同名成员)

 改正:加上作用域即可

Student& operator=(const Student& s)
	{
		if (this != &s)
		{
		    Person::operator=(s);
				_id = s._id;
		}
		return *this;
	}

派生类的析构函数

//父类的析构函数
~Person()
	{
		cout << "~Person()" << endl;
	}
~Student()
	{
		~Person();
		//清理自己的
	}

无法显式地调用父类的: 编译器认为子类的析构函数和父类的析构函数构成隐藏。因为后面多态的一些原因,任何类析构函数名都会被统一处理成destructor()

 改正如下:指定父类即可

~Student()
	{
		Person::~Person();
	}

 调用的时候父类被析构了两次?

 按理说构造函数先调用父类再调用子类,析构函数先调用子类再调用父类,中间有一次析构是多余的

 为了保证析构时,保持先子后父的后进先出的顺序析构,子类析构函数完成后,会自动去调用父类的析构函数。调用了两次没有崩溃的原因:析构函数是完成清理工作,子类中没有需要清理的东西

 思考题:设计出一个类A,让这个类不能被继承

父类A的构造函数私有化后,B就无法构造对象


class A
{
private:
	A()
	{}
};

class B :public A
{

};


int main()
{

	B b;
	return 0;
}

继承与友元

成员可以继承,但友元关系不能被继承

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string_name;//姓名
};

class Student : public Person
{
protected:
	int _stuNum; //学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}

void main()
{
	Person p;
	Student s;
	Display(p, s);
}

 如果想访问,再增加一个友元即可

复杂的菱形继承及菱形虚拟继承

 class Person
{
public:
	string_name;//姓名
};

class Student :   public Person
{
protected:
	int _num; //学号
};

class Teacher :   public Person
{
protected:
	int _id; //职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; //主修课程
};


void Test()
{
	//这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "小张";
}

报错如下:

指定作用域即可解决 

void Test()
{
	Assistant a;
	a.Student::_name = "小张";
	a.Teacher::_name = "张老师" 
}

 

加入virtual后name变为同一个

class Person
{
public:
	string_name;//姓名
};

class Student : virtual public Person
{
protected:
	int _num; //学号
};

class Teacher : virtual public Person
{
protected:
	int _id; //职工编号
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; //主修课程
};

 
void Test()
{
	Assistant a;
	a.Student::_name = "小张";
	a.Teacher::_name = "张老师" 
}

 综上,虚继承可以解决数据冗余和二义性

*C++编译器如何通过虚继承解决数据冗余和二义性问题?(通过内存调试观察)

1.通过监视窗口已经看不到真实的存在,因为监视窗口被编译器处理过

2.建议使用内存窗口来查看

下面展示非虚继承

#include<iostream>
using namespace std;

class A
{
public:
	//int _a[10000];
	int _a;
};

class B : public A
//class B : virtual public A
{
public:
	int _b;
};

class C : public A
//class C : virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

 打断点后进入内存窗口调试 

 

 不是虚继承的时候,B继承A,那么内存中是先A后B。虚继承以后,类似切片b = d,这个时候在内存中就不是先A后B了

 

继承与组合

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值