C++ 继承

一.继承

1.1 继承的概念

        继承本质上就是一种复用的思想,假设我们定义老师和学生类,本质上学生和老师都有一些相同的属性,比如说都有姓名,年龄这些共同的属性,那我们把这些相同属性单独拿出来封装成一个类,再定义其他类去复用这个类,这种思想被称为继承。

#include<iostream>
using namespace std;
class person {
public:
	string name;
	size_t age;
	string address;
};
class student :public person {
public:
	int _stid;
	string _major;
};
class teacher :public person {
public:
	int _workid;
};

        在这里学生和老师类都复用了person这个类,但是学生和老师类都有出了person类以外的其他属性,例如id号。

1.2 继承的定义

        被继承的类被称为父类(基类),继承他人类的类被称为子类(派生类)

1.3 继承关系和访问限定符 

C++中的继承关系是有些冗余的,

其中继承方式有

(1)public 继承                 (2)protected 继承             (3)private 继承

访问限定符有

(1)public 访问                 (2)protected 访问             (3)private 访问

继承方式可以和有访问限定符有9种不同的组合

 1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能
访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式==Min(成员在基类的访问限定符继承方式),public >protected>private。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
5.在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用
protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

总结:一般都用 public 继承

二. 基类和派生类对象赋值转换

2.1 赋值

对于内置或者自定义类型来说,将两种不同类型的数据相互赋值时,会产生一个具有常性的临时对象。

double型的数据d在把数据赋值给int类型的变量时会临时生成一个const int 类型的常量。

但是当一个充当父类的对象可以直接接受子类对象的值,这种现象被称为切片。因为子类本身是一种特殊父类的存在(is a 关系),父类有的子类都有,子类有的父类不一定有,因此子类对象赋值给父类对象的本质就是,将从父类继承来的成员再还给一个父类对象。

2.2 特性 

(1)子类对象可以将值赋给父类对象(父类不能赋值给子类)

(2)父类指针和引用可以修改子类对象中父类成员的值

三.隐藏

        子类和父类中允许存在同名的成员变量或者是同名的成员函数,子类在这时会对父类的成员变量或者成员函数进行隐藏(重定义),编译器允许这种操作存在的原因是,子类成员和父类成员是处在不同的命名空间中的,在子类对象中使用变量或者函数时会优先使用子类域中进行查找,想要访问父类中被隐藏的成员需要指定父类域。

#include<iostream>
using namespace std;
class person {
public:
	string name;
	size_t age=18;
	string address;
};
class student :public person {
public:
    size_t age;
	int _stid;
	string _major;
void print()
{
    cout<<person::age<<endl;
}
};

四.派生类的默认成员函数

4.1 构造函数

        当一个子类想要去在构造函数中初始化父类中的成员时候,我们需要调用父类的构造函数,而不能直接写父类中的成员。

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(const char* name = "张三", int num = 0)
		: person(name)
		, _num(num) {
		cout << "Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s;
	return 0;
}

4.2 析构函数

        在调用析构函数时会先调用子类的析构函数,再调用父类的析构函数。因为如果父类对象先不存在,那么子类对象这时依然有修改父类对象的权利,但是此时他已经不存在了,那么就会导致不安全的情况存在。所以在C++中,编译器会首先调用子类的析构函数,再调用父类的析构函数。

4.3 赋值重载

        对于子类中的父类对象,直接显示调用赋值重载,和构造函数一样,会将子类对象中父类的值给切割出来。因为父类和子类的赋值重载函数名相同,构成隐藏,在以下的函数重载中需要指明使用了父类的赋值重载,不然会应发无线递归调用。

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

五. 继承与友元

友元关系时不可以被继承的,子类不能访问父类的友元函数。

//声明Student类
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;
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
}

六. 继承和静态友元函数

        父类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。静态成员属于父类,被子类继承后也属于子类了,但是它不存储在任何一个父子类对象中,只是每个对象都可以访问它。

class Person{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

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

int main()
{
	Person p;
	Student s;
	Student s;
	//3
	cout << p._count << endl;
}

七.菱形继承

7.1 单继承和多继承

(1)单继承:一个子类只有一个直接父类时成这个继承关系为单继承。

(2) 多继承:一个类继承了两个及其以上的直接父类时,这种关系被称为多继承。

菱形继承就是多继承中一种复杂的情况,大致的关系如下图所示

助理类(assistant)继承了 学生(student)和老师(teacher)两种类,而学生和老师类继承了person类。这就是一种菱形关系。

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; // 主修课程
};

int main()
{
	Assistant as;
}

这样的设计还会引发,数据冗余和歧义的问题

(1)歧义:student和teacher类中有都有_name这个名字的属性,编译器不知道给哪一个赋值。

(2)数据冗余:student 和 teacher 类中除了名字肯能不一样其余的属性都是一样的,把相同的属性存两遍构成了数据的冗余。

7.2 解决歧义和数据冗余

        C++的解决方案就是在继承了初始父类的类中加virtual关键字,如下代码所示,就是在student和teacher类中加virtual关键字。我们可以认为person这个对象只构造了一次,并且student 和 teacher 类的对象都可以引用这个person对象。这样可以大大的节省空间资源。

class Person {
public:
	void print() {
		cout << "person" << endl;
	}
	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; // 主修课程
};

        C++中通常采用 vritual 关键字来将子类变为虚拟继承的方式来解决以上提到的两个问题,其作用就是将虚拟继承的子类中的公共数据部分进行引用,从而解决数据冗余的问题。那下图代码来举例。

class A {
public:
	int _a;
};
class B : public A {
public:
	int _b;
};
class C : 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 数据的部分现在存放的是一个地址(dc 7b 3b 00)这个地址中存放了一个偏移量 ,用这个偏移量加上现在自己的地址就是_a的位置。

地址(dc 7b 3b 00)所存放的数据为 14 00 00 00,用这个16进制的数字加上我们现在的地址(0  x0093f8a4)就是_a 所在的位置(0X0093f8b8)

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值