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

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

相关内容

热门资讯

东方航空×泡泡玛特:DIMOO... 文旅观察近日,中国东方航空与泡泡玛特携手推出的DIMOO中泰建交50周年限定主题彩绘专机正式首航,执...
多个“第一”!重大突破! 今天(7月9日)上午国新办举行首场“高质量完成‘十四五’规划”系列主题新闻发布会一起看 ↓国家发展改...
中广核新能源:6月发电量140... 格隆汇7月9日|中广核新能源(1811.HK)公告,6月本集团按合并报表口径完成发电量1,403.5...
1.52亿千瓦!江苏电网最高用... 转自:上观新闻7月7日,江苏电网最高用电负荷,年内第三次刷新历史新高,达1.52亿千瓦。截至目前,南...
ST华铭因信披违规被罚,收购聚... 近日,ST华铭(维权)(300462)发布公告,近日收到中国证券监督管理委员会上海监管局下发的《行政...
山西泽辰医药乌帕替尼缓释片启动... 药物临床试验登记与信息公示平台数据显示,山西泽辰医药科技有限公司的乌帕替尼缓释片在健康受试者中随机、...
西海都市报公布地址、订阅及零售... 本报地址:西宁市长江路5号 广告经营许可证: 广告部电话:6125601 排版:西海都市报社编辑部 ...
台风丹娜丝“绘就”陆家嘴诗意画... 转自:上观新闻连日来,受台风“丹娜丝”外围环流持续影响,上海晴雨相间,同时带来降温、大风,云雾光影间...
国家发改委:中国做的这件事,全... 转自:北京日报客户端在国新办今天(9日)举行的首场“高质量完成‘十四五’规划”系列主题新闻发布会上,...
或受欧盟法规影响,苹果将在欧盟...   炒股就看金麒麟分析师研报,权威,专业,及时,全面,助您挖掘潜力主题机会! IT之家 7 月 9...