C++之继承

目录

一、继承的概念

1、概念

2、继承的定义

 3、继承关系和访问限定符

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

三、继承下的子类的成员函数

父类

 子类

1、构造函数

2、拷贝构造函数

 3、赋值运算符

4、析构函数

四、继承与友元

五、继承与静态成员

六、菱形继承问题

1、单继承

2、多继承

3、菱形继承

七、继承与组合


一、继承的概念

1、概念

继承 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,
承是类设计层次的复用。
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 _stuid; // 学号
};

class Teacher : public Person
{
protected:
    int _jobid; // 工号
};

 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。

2、继承的定义

下面我们看到Person父类,也称作基类Student子类,也称作派生类

 3、继承关系和访问限定符

我们知道访问限定符有 public、protected 和 private三种,而继承方式也是有三种。他们之间重要的关系如下:

 注:不可见表示子类无法访问父类的私有成员。因此如果父类里有不想让子类访问的成员,就可以设为私有权限。

基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。


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

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 。这里有个形象的说法叫切片
或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。
class Person
{
protected :
    string _name; // 姓名
    string _sex;  // 性别
    int _age; // 年龄
};

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

int main()
{
    Student sobj ;
 // 子类对象可以赋值给父类对象/指针/引用
    Person pobj = sobj ;
    Person* pp = &sobj;
    Person& rp = sobj;
}


三、继承下的子类的成员函数

父类

class Person
{
public:
	Person(const char* name = "ZDL")
		: _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);
    Student(const Student& s);
    Student& operator=(const Student& s);
	~Student();
protected:
	int _num;
};

1、构造函数

子类自己的成员,跟类和对象一样。从父类继承过来的成员需要去调用父类的构造函数。

Student(const char* name,int num)
    :Person(name)  //_name是由父类继承而来,需要去调用父类的构造函数
	,_num(num)   //子类自己的成员自己初始化
{}

2、拷贝构造函数

子类自己的成员,跟类和对象一样(内置类型完成值拷贝,自定义类型调用它自己的拷贝构造)。从父类继承过来的成员需要去调用父类的拷贝构造函数。

Student(const Student& s)
    :Person::Person(s)//调用父类的拷贝构造
	,_num(s._num)
{}

 3、赋值运算符

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

4、析构函数

在继承的情况中,不需要显示调用父类的析构函数,编译器自动调用,先析构子类,再析构父类。只需要处理子类中的成员。

//子类的析构函数跟父类的析构函数构成隐藏
~Student()
{
	//处理子类自己的
}


四、继承与友元

友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员。


五、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。

六、菱形继承问题

1、单继承

一个子类只有一个直接父类时称这个继承关系为单继承。

2、多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承。

3、菱形继承

菱形继承是多继承的一种特殊情况。 如下图:

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,加上关键字 virtual 即可解决问题。
为了方便观察,我们使用下面的例子来看看菱形继承的问题:
class A
{
public:
	int _a;
};

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

// class C : virtual public A
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;
}

 从上面的图中我们就可以看出在D中A的成员_a有两份,有数据冗余的问题。接下来我们了看看虚拟继承下的D中的情况。

这下我们发现 A 的 _a 成员只有一份了(红色框起来的),而 B 类和 C类中也分别多出了一个指针。那么这指针是个什么的呢?下面我们来讲一讲。  

从内存的结构看去,我们发现指针指向的内容为它到取到 _a 的位置的偏移量,这样我们就可以找到共用的_a。这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A(虚基类) 。


七、继承与组合

1、public 继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
2、组合是一种 has-a 的关系。假设 B 组合了 A ,每个 B 对象中都有一个 A 对象。
3、 优先使用对象组合,而不是类继承。
4、 实际尽量多去用组合。组合的耦合度低,代码维护性好。要实现多态,也必须要继承。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值