C++——继承
创始人
2024-06-01 19:14:26
0

概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特

性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,

体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

定义

class Person
{
public:Person(const char* name="mwb"){}void print(){cout << "name: " << _name << endl;cout<<"age: " << _age << endl;}
private:string _name = "peter";int _age = 18;
};class Student : public Person
{
public:Student(const char* name, int num):Person(name), _num(num){}
protected:int  _stuid = 0;int _num = 111;
};class Teacher : public Person
{
protected:int  _jobid = 0;int _num = 999;
};

基类和派生类进行赋值转换

赋值兼容规则:

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。赋值兼容规则中所指的替代包括以下的情况:

  1. 派生类的对象可以赋值给基类对象。

  1. 派生类的对象可以初始化基类的引用。

  1. 派生类对象的地址可以赋给指向基类的指针。

在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

何为切片

注意:子类赋值给父类的时候会发生切片。

class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};

若执行以下代码,就会发生如图所示的切片:

Student S;
Person& rp=S;

如图所示:

解析:顾名思义就是将子类对象中的成员直接给给父类对象,虽然类型不同但是也没有产生临时变量。

与非切片类型转换的区别:

int a=1;
double b=2.2;
double& ra=a;

与之相似的以上代码就是错误的,由于ra的类型与a不同,当类型转换的时候,a需要生成一个临时变量,然而临时变量是常量,引用不能引用一个常量,所以应该在double前面加一个const修饰一下。

即为:

int a=1;
double b=2.2;
const double& ra=a;

作用域

隐藏

概念:

1、子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定

义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

2、 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

成员变量的隐藏

代码如下:

class Person
{
public:int _num = 1;
};
class Student : public Person
{
public:int _num = 2;
};
int main()
{Student s;cout << s._num << endl;return 0;
}

由于构成隐藏,所以s对象会屏蔽基类对同名成员的直接访问。


如果想访问父类中的成员变量可以在成员变量前加一个作用域限定一下。

class Person
{
public:int _num = 1;
};
class Student : public Person
{
public:int _num = 2;
};
int main()
{Student s;cout << s.Person::_num << endl;return 0;
}

成员函数的隐藏


class Person
{
public:void fun(int _num = 999){cout << _num << endl;}int _num = 1;
};
class Student : public Person
{
public:void fun(const char* name = "mwb", int _num = 666){cout <

由于构成隐藏,所以s对象会屏蔽基类对同名成员的直接访问。


如果想访问父类中的成员变量可以在成员函数前加一个作用域限定一下。


class Person
{
public:void fun(int _num = 999){cout << _num << endl;}int _num = 1;
};
class Student : public Person
{
public:void fun(const char* name = "mwb", int _num = 666){cout <

派生类的默认成员函数

重中之重:

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函

数,则必须在派生类构造函数的初始化列表阶段显示调用。

2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

3. 派生类的operator=必须要调用基类的operator=完成基类的复制。

4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类

对象先清理派生类成员再清理基类成员的顺序。

5. 派生类对象初始化先调用基类构造再调派生类构造。

6. 派生类对象析构清理先调用派生类析构再调基类的析构。

构造、拷贝构造、赋值重载

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;
};//派生类中
//1、构造函数,父类成员调用父类的构造函数完成初始化
class Student : public Person
{
public://构造Student(const char* name="mawenbo", int num=3):Person(name), _num(num){}Student(const Student& s):Person(s)//必须调用基类的拷贝构造进行初始化,_num(s._num){}Student& operator=(const Student& s){if (this != &s){Person::operator=(s);//这里如果不调用基类的将一直循环//operator=(s);_num = s._num;}return *this;}
protected:int _num;
};int main()
{return 0;
}

析构


class Person
{
public:Person(){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
};
class Student : public Person
{
public:Student(){cout << "Student()" << endl;}~Student(){cout << "~Student()" << endl;}
};
int main()
{Student s;return 0;
}

由运行结果我们可以得出以下几点:

  1. 派生类初始化先调用基类构造函数再调用派生类构造函数。

  1. 清理的时候先调用派生类的析构再调用基类的析构。

继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员,好比你爸爸的朋友不一定就是你的朋友。

继承与静态成员


class Person
{
public:Person(){_count++;}void Print(){cout << _count << endl;}
public:static int _count;
};int Person::_count = 0;
class Student : public Person
{
public:Student(){Person::_count++;}void Print(){cout << Person::_count++ << endl;}
};int main()
{Person p;p.Print();Student s;s.Print();cout << Person::_count << endl;Person::_count = 3;cout << Person::_count << endl;Person::_count = 99;cout << Person::_count << endl;}

解析:静态变量不属于任何对象,属于全局。当无论在哪里改变出了作用域都会改变。

菱形继承

概述


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;
};

顾名思义,继承的关系像一个菱形一样,如上图所示。

菱形继承的二义性与改进方法(菱形虚拟继承)

二义性

先看代码:

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._a;return 0;
}

由于B和C里面都有A继承下来的_a,此时我们使用d去访问_a时就会产生二义性,编译器会不知道到底应该访问哪个_a。

菱形虚拟继承

代码如下:

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

我们只需要在继承方式的前面,也就是菱形的腰部位置加两个virtual,就构成了菱形虚拟继承。

此时我们再用d去访问_a就不会报错了,因为此时菱形虚拟继承已经吧B和C中的_a当成一个变量来处理了。

原理

代码如下:

虚拟继承之前

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和C中的_a是两个值,因而导致二义性。

虚拟继承后

代码如下:

class A
{
public:int _a;
};
class B : virtual public A
{
public:int _b;
};
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和C中的_a已经成为一个值了,那么在内存中是怎样实现的呢,并且我们发现在内存中_a被放在了最下边独立于其他成员变量。

我们发现在_b和_c的存储位置上方有一个虚基表指针,我们在内存中去使用他们的指针所指向的地址可以找到他们对应的值,叫做偏移量,当我们用虚基表指针自身的地址与偏移量相加可获得_a的地址,因此内存就是以这种方式来寻找_a存放的位置的。

注意:

很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有

菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在

复杂度及性能上都有问题。多继承可以认为是C++的缺陷之一,很多后来的OO语言(Object Oriented,OO,也就是面向对象的语言)都没有多继承,如Java。

组合


//继承 is-a关系
class Person
{
protected:string _name;
};
class Student : public Person
{
protected:int _id;
};//组合 has-a关系
class Hair
{
protected:string _colour;
};
class Animal
{
protected:int _age;Hair h;
};

组合的耦合度低,继承的耦合度高,能使用组合就不使用继承,因为继承一定程度的破坏了代码的封装,派生类和基类的依赖关系很强。

相关内容

热门资讯

热搜第一!南京VS苏州,诞生两... 在60396名现场观众的注视下,“苏超”诞生了首场0比0平局的比赛。在60396名现场观众的注视下,...
辟谣2025年7月5日陨石撞击... (转自:上林下夕)关于“2025年7月5日陨石撞击太平洋”的传闻,目前科学界和权威机构已明确辟谣。以...
雷蛇战锤狂鲨 V3 有线入耳式...   炒股就看金麒麟分析师研报,权威,专业,及时,全面,助您挖掘潜力主题机会! IT之家 7 月 5...
暴风城的勇士 暴风城的勇士15个冠军徽记,,首先你要先去接日常,做3天日常,吧自己阵营勇士刷出来,然后用15个冠军...
巴西总统卢拉:金砖国家对全球经... 当地时间7月5日,金砖国家工商论坛在巴西里约热内卢开幕,巴西总统卢拉、副总统杰拉尔多·阿尔克明出席论...
在那边的英文句子怎么写 在那边的英文句子怎么写在那边的英文句子怎么写在那边英语:It is over there.
保障重点工程建设,浦东王港派出... 转自:上观新闻7月2日正午,金谷智能终端制造基地工地宿舍内,工地专管民警曹德生在走访中检查了工友们常...
只为回应人间烟火的重托 转自:千龙网他们的故事如同滚烫的星河,从大连的山海间奔涌而来,没有雷霆万钧的口号,只有肩头的百姓冷暖...
香港市民向山东舰“回归”舰员赠... 来源:中国新闻网 中新社香港7月5日电 (记者 戴梦岚)“回归,有位香港老人给你准备了生日礼物,快来...
个人创业贷款怎么办理,大学生创... 很多创业者在创业初期,苦于资金不足,无法支撑你的创业梦想。如果你是刚毕业的大学生,建议你先工作,积累...