目录
*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了
继承与组合