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

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

相关内容

热门资讯

江南三大名人,分别是那些人? 江南三大名人,分别是那些人?   唐伯虎妙写对联  从前,苏州街上有个南货店老板,求名士唐伯虎写副...
离退休人名参观养老院 离退休人名参观养老院正对离退休人员的养老有地方是以政府购买服务的方式让其入住本地好些的专业养老社区或...
电影财神客栈的拼音 电影财神客栈的拼音财神 cai 第二声 shen 第二声客栈 ke 第四声 zhan 第四声cais...
宠辱不惊 看庭前花开花落 ... 宠辱不惊 看庭前花开花落 去留无意 望天上云卷云舒的作者?年代?谢谢!《幽窗小记》中有这样一幅对...
怎么从任务管理器打开我的电脑? 怎么从任务管理器打开我的电脑?explorer.exe被破坏,无论是正常启动还是进安全模式,都提示e...
春望唐代诗人杜甫写的从题目上看... 春望唐代诗人杜甫写的从题目上看这首诗是诗人在什么季节的时候描写的?春望是唐代诗人杜甫在春天季节来写的...
丧偶式婚姻让人难过,是该继续坚... 丧偶式婚姻让人难过,是该继续坚持还是应该潇洒离开?潇洒离开。因为这样的婚姻名存实亡,没有任何感情,已...
把一个故事说好有哪些技巧? 把一个故事说好有哪些技巧?讲故事,最重要的是对何事的讲解,换句话说也就是重现场景,重现场景的一个技巧...
努力努力再努力繁体字 努力努力再努力繁体字努力努力再努力繁体字努力努力再努力繁体字还是(努力努力再努力)
我姓徐请帮忙给我女儿起个好听的... 我姓徐请帮忙给我女儿起个好听的名字谢谢大家徐清妍:美好。适用于女孩取名字。出自唐代韩愈《月池》诗:“...
沙之守鹤九尾到底什么关系?总共... 沙之守鹤九尾到底什么关系?总共有几个尾?沙之守鹤是一尾,他和九尾没啥关系,只是同为怪物吧...一尾守...
探索的意思是什么拜托了各位 谢... 探索的意思是什么拜托了各位 谢谢探索一般是指对自然未知部分的探索性发掘.是指人的一种行为、一种活动、...
供应商要账,老板不肯付钱我该怎... 供应商要账,老板不肯付钱我该怎么办就说财务章或法人章丢失正在办理,需要一段时间,让他们耐心等待!
女人想见你,才会发出哪些信号,... 女人想见你,才会发出哪些信号,你都知道吗?1、汪者主动联系你,问你“你有羡孝空吗?” 2、抱怨你和...
壁虎叫声是什么样的? 壁虎叫声是什么样的?壁虎的叫声有很多种,有的类似乌鸦的叫声,有的类似青蛙叫,还有的类似婴儿啼哭。声音...
传奇 攻城是什么意思 传奇 攻城是什么意思攻城 沙巴克是什么意思?怎么功??攻城就是拿沙或者沙战,想攻沙就带组玛头象和1个...
最后的莫西干人林子祥什么时候唱... 最后的莫西干人林子祥什么时候唱的的《最后的莫西干人》是由林子祥演唱的歌曲,发行于**1987年**。
梦中呓语是什么意思 梦中呓语是什么意思梦中呓语是什么意思梦中呓语就是“说梦话”,这样说文雅一点而已。
他自己打骂他自己的父母他不是傻... 他自己打骂他自己的父母他不是傻子呀?不是傻子连傻子都不是。就不是人是狗
大剑普莉希拉为什么那么强 大剑普莉希拉为什么那么强技能和能力强、特殊装备或武器。1、技能和能力强:大剑普莉希拉可能拥有强大的技...