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

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

相关内容

热门资讯

“皖美智链 金赋江淮” 中国... 转自:新华财经为贯彻落实国家“人工智能+”行动部署,扎实推进科技创新与产业创新深度融合,5月17日,...
浙昆为青年演员量身打造大戏   由浙江京昆艺术中心出品,浙江京昆艺术中心(昆剧团)演出的昆剧《盛世红妆》,5月17日至18日在杭...
摩托车技巧挑战赛倡导文明骑行     比赛中,两名车手同场竞技。    本报记者林显威 摄  本报讯(记者林显威)南宁周末赛场,上...
市委常委会召开扩大会议   本报讯(记者吴军)5月17日上午,自治区党委常委、市委书记农生文主持召开市委常委会扩大会议,通报...
离婚已3年男子喊话前妻求放过 ... 转自:今晚报 【#离婚已3年男子喊话前妻求放过# 冯先生...
泰国精品购物节 超百种商品让你...     各种特色美食“泰美味”。    本报记者黄敬敏 摄    市民购买泰国榴莲。  本报讯(记者...
新冠阳性率明显上升,紧急提醒三... 近期全国多地新冠阳性率呈现明显上升趋势5月16日#新冠感染又抬头#的话题登社交媒体热搜榜引发社会各界...
中信证券:管理软件或是AI A... 中信证券发布研报称,2025年有望成为AI应用落地的元年,AI Agent应用场景有望迎来快速爆发。...
2025“文化中国·水立方杯”... 中新网伦敦5月18日电 (记者 欧阳开宇)5月17日,2025“文化中国·水立方杯”中文歌曲大赛英国...
湖南龙山第十届舍巴日开幕 展湘... 5月17日,湖南龙山县第十届舍巴日在湖南湘西龙山县惹巴拉古寨开幕。“舍巴日”是土家族歌舞祭祖、庆丰祈...
私家车跑“顺风”出事故,意外险... 来源:法治日报 顺风车是与他人合意,一同顺路搭乘前往目的地的共享出行方式,因其方便且实惠,受到不少人...
吉林市家政服务协会“三送”服务... 转自:中工网中工网讯 近日,吉林市家政服务协会工会联合会与解放军第九六五医院在吉林市大世界广场举办“...
什么是博物馆? 这堂公益研学课...     小朋友们在展厅里学习文物知识。    本报记者宋延康 摄  本报讯(记者陈蕾)博物馆到底是什...
尼日利亚发生武装袭击事件 至少... 中新网北京5月18日电 据多家外媒17日报道,5月15日在尼日利亚东北部博尔诺州发生的针对平民的袭击...
信息消费呈现两大发展态势 上海... 2025上海信息消费节于5月17日拉开帷幕。证券时报记者从开幕式上获悉,上海信息消费呈现两大发展态势...
俄罗斯食品文化节(俄罗斯制造)... 本报17日讯(记者孙铭阳)17日,由俄罗斯出口中心主办的俄罗斯食品文化节(俄罗斯制造)在哈尔滨正式开...
新华社权威快报 | 我国卫星导... 转自:新华社5月18日在京发布的《2025中国卫星导航与位置服务产业发展白皮书》显示,2024年我国...
“莎头组合”出战 多哈世乒赛第... 转自:央视新闻客户端今天,多哈世乒赛迎来第二个比赛日中国军团将在混双、女双、男女单打等多条战线全面出...
墨西哥海军一帆船撞上纽约布鲁克... 转自:财联社【墨西哥海军一帆船撞上纽约布鲁克林大桥 多人落水】财联社5月18日电,一艘载有200人的...
江苏队实现“六连冠” 广西队获... 广西队兰星宇在比赛中。 本报记者宋延康 摄江苏队尤浩在比赛中。本报记者宋延康 摄  昨日,20...