波奇学C++:继承

继承是为了复用代码,成员的变量或者成员函数

class Person
{
public:
	
protected:
	string _name="li";
	int _age=1;
};
class Student :public Person
{
public:
	void print()
	{
		cout << _age;
	}
protected:
	int _stuid=2;
};

子类student公有继承基类Person,子类,继承Person所有成员变量,相当于copy一份,成员函数在公有,但子类可以访问。

子类的访问受到继承方式和父类访问限定符的影响

父类的public父类的protected父类的private
public子类public子类的protected子类private
protect子类protected子类protected子类private
private不可见不可见不可见

不可见指的是类外都不能访问,private相比,至少类中可以访问。

实际上子类的访问=min(父类的访问限定符,继承方式)

protected 用于存放可以私有且子类可以访问。

不加继承方式,class默认私有继承,struct默认公有继承。

父类不能直接赋值子类(向下转换),子类可以给父类(向上转换),这是一种赋值兼容,把子类中父类的部分切割赋值给父类,而不是强制类型转换

student=person;//报错 
person=student;

子类重定义/隐藏父类成员

子类和父类同名成员变量,成员函数,编辑器优先选择子类,通过父类域可以选择父类的变量,函数,编译器优先顺序:作用域>类域>全局域。

class Student:public Person
........
void Print()
{
cout<< num;
cout<< Person::num;
}

*注意不是重载关系,因为重载要在同一个作用域,而子类和父类分别为两个类域。

派生类的构造,拷贝构造,赋值重载

//基类
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person operator=(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()
		:_id(0)
	{

	}
protected:
	int _id;
};

子类Student继承了父类Person,继承相当于copy了一份父类的成员变量,因此我们理解子类中有_name和_id两个变量。但不能在初始化列表中初始化_name,但可以在函数内部中。

初始化列表相当于在定义时初始化,可以理解为编译器在基类的定义找不到父类中的_name声明。

实际上继承的父类的成员变量,在初始化列表,默认调用父类构造函数来初始化。

// 子类构造函数
Student()
		:_id(0)
		,Person("xiaoli")
	{
		_name="xixiaong"
	}

注意Person的初始化优先于,_id的初始化

理解成父类成员变量的声明先于子类成员变量。

派生类的拷贝构造

Student(const Student& s)
		:Person(s)
		,_id(s._id)
{

}

用父类的拷贝构造,子类作为参数,拷贝构造父类的成员变量,我们此前所说的向上转换,子类给父类是指出的,再拷贝构造基类自身成员变量。

如果不显示调用拷贝构造,会调用默认构造。

派生类的赋值重载,除了子类向上构造,还有注意operator=()重定义的问题。

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

析构函数不用显式调用父类析构函数

析构函数名被特殊处理destructor,在子类析构调用后,会自动调用父类析构函数。来保证先子后父

~Student()
	{

	}

先子后父的原因:初始化列表时,先调用父类构造函数,所以结束时后调用父类析构函数。子类可能用到父类的函数,成员变量,如果父类被析构了,那么就会报错。

友元关系不能继承。

静态成员变量属于子父类公有,子父类的静态成员变量是共有的,而不是复制的关系

class Person
......
protected:
	string _name;
	static int _count;
};
int Person::_count = 0;

多继承和菱形继承

Assiatant类继承了Student和Teacher两个类

class Assistant :public Student, public Teacher
{
protected:
	string _majorCourse;
};

 菱形继承存在的问题,student可能和teacher同时从Person那里继承了age,使得assistant类中有两个年龄造成数据冗余和二义性

 虚继承解决冗余,二义性问题

// Student和Teacheer类虚继承了Person
class Student :virtual public Person
{
protected:
	int  _num;
};
class Teacher :virtual public Person
{
protected:
	int _id;
};

二义性的证明

class A
{
public:
	int _a;
};
class B : public A
{
public: int _b;
};
class C : public A
{
public: int _c;
};
class D : public C, public B
{
public: int _d;
};

由图可得_a有两个分别在B,C中。并且类A,B,C的变量依次排列

虚继承virtual

 _a 的位置存放着一串地址

对应地址的下个位置的数值为20,12,恰好就是B,C到A的偏移量。

本质上保存的是_a的相对位置,那么为什么不直接保存A的地址,或者偏移量而是要保存指向一块空间的地址?如果存在多个D对象,因为相对位置是固定的,所以可以直接复用。d2 和d1的绝对地址是不一样的,但是相对位置是一样的。

右边的表又叫为虚基表,专门保存偏移量。

总结:把它单独划分出来便于复用。

虚继承后B,C,D 都报错相似的结构,有虚基表

 偏移量的运用

当ptr无论指向B 还是C 都可以通过偏移量,找到A

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值