栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

c++继承

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++继承

继承

文章目录
      • 一、继承的概念
      • 二、继承的相关内容介绍
        • 继承定义的格式
        • 继承方式的介绍
        • 继承基类成员访问方式的变化
        • 切片的介绍(子类对象赋值给基类对象的指针或引用时发生)
        • 继承中的作用域
        • 派生类中的六大默认成员函数和基类的联系
        • 友元与继承的关系
        • 静态成员和继承的关系
      • 三、继承的拓展
        • 单继承
        • 多继承
        • 菱形继承
        • 菱形虚拟继承
        • 菱形虚拟继承的原理

一、继承的概念
  • 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特
    性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,
    体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

例如大街上有很多车,它们都有各自的品牌,颜色,价格,拥有者,所属省等等共有的属性,但是每辆车都有它们各自不同的功能和作用,假如用代码把这些车归类,使用继承就可以大大提高效率,让所有车都继承一个同一个父类,它们各自不同的地方,再在父类的基础上增加,共同的属性全部从父类继承下来。

二、继承的相关内容介绍 继承定义的格式

继承方式的介绍

继承关系有三类,分别为公有继承(public),保护继承(protected),私有继承(private),但是最最常用的只有公有继承,另外两种继承方式都很少用。

class 派生类名 : 继承方式 基类
其中访问方式为public 就是公有继承 protected 就是保护继承 private就是私有继承 

继承基类成员访问方式的变化

这里的访问方式的意思是继承下来的成员变量的新的访问权限。

总结:

  • 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是
    被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

  • 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能
    访问,就定义为protected。可以看出保护成员限定符是因继承才出现的

  • 基类的其他成员在子类的访问方式=min(成员再基类的访问限定符,继承方式)

  • public > protected > private

切片的介绍(子类对象赋值给基类对象的指针或引用时发生)

子类中会继承父类中的所有成员(私有成员也会继承下来,只是语法限制了其在子类中不可见),子类还有自己额外的成员,那么子类就具有父类中的所有成员,相反父类不具有子类中的所有成员。那么子类可以赋值给父类,但是父类不可以赋值给子类。

  • 子类对象可以赋值给父类对象、父类指针(用子类地址赋值)、父类引用。 这个过程中就会产生切片或者叫切割,就是把子类中属于父类的那部分切出来赋值给父类。

  • 基类的指针是可以通过强制类型转换为派生类指针的,但是当访问该指针指向对象的数据的时候,只有该基类指针指向的是派生类对象的时候才是安全的否则就会有越界(基类中不具有派生类中所有的成员)。

相关代码:

class Person
{
protected :
string _name; 
string _sex; 
int _age; 
};
class Student : public Person
{
public :
int _score ; 
};
void Test ()
{
Student sobj ;
// 子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//基类对象不能赋值给派生类对象
sobj = pobj;
//基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换可以
ps1->_score = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_score = 10;
}
继承中的作用域

在继承中,基类和子类都有自己独立的作用域。

  • 例:下面两个类构成继承关系,子类中有一个函数与父类中的函数同名,这两个函数是重载关系吗?
class Person
{
    public:
    void func()//父类中的func函数
    {
        cout<<"Person::func()"<
    public:
    void func()//子类中的func函数
    {
        cout<<"Student::func()"< 

解答:上面两个父子类中有同名函数,但是他们不是构成的重载,而是叫隐藏或者重定义。因为重载必须是同一作用域内的两个同名函数,但是彼此的参数列表不同(参数个数,顺序,类型),但是基类和派生类是有着自己独立的作用域的,因此他们不是同一作用域的同名函数,不构成重载关系!!!

注意:

  • 1.成员函数的隐藏,只需要函数名相同即可,不需要参数列表也一模一样

  • 在子类成员函数中,可以通过使用指定访问限定符的方式显式的访问父类中的成员变量或函数

派生类中的六大默认成员函数和基类的联系
  • 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函
    数,则必须在派生类构造函数的初始化列表阶段显示调用

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

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

  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序

  • 派生类对象初始化先调用基类构造再调派生类构造

  • 派生类对象析构清理先调用派生类析构再调基类的析构

例:

//普通函数的继承是继承实现,虚函数的继承是继承接口,实现需要子类重写。
class Person
{
public:
	Person()
	{
		cout << "Person()" << endl;
	}
	virtual ~Person()
	{
		cout << "~Person()" << endl;
		delete[] p;
	}

protected:
	char* p = new char[10]{ 's','d' };
};

class Teacher :public Person
{
public:
	//如果父类的析构函数是虚函数,此时只要子类的析构函数定义 无论是否加virtual都会与父类的析构函数构成重写,虽然两者的函数名
	//不相同,不符合重写规则,但是实际上编译器对二者的析构函数函数名做了统一处理,将其全都命名为distructor 从而构成重写
	Teacher()
	{
		cout << "Teacher" << endl;
	}
	virtual ~Teacher()
	{
        //不用在里面在调用基类的析构函数,因为派生类析构函调用完成后会自动地调用基类的析构函数
		cout << "~Teacher()" << endl;
		delete[] s;
	}
private:
	char* s = new char[10]{ 'f','g' };
};
void test()
{
	
	//Person p;
	Teacher t;

}

友元与继承的关系

友元关系是不能被继承的。

  • 例如基类的友元函数可以访问该基类的私有和保护成员变量及函数但是不可以访问该基类的子类的私有和保护成员变量和函数,因为友元关系是不会被子类继承下来的!!!
class Student;//提前声明
class Person
{
public:
	//友元关系是不可以继承的!!!
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};

class Student : public Person
{
    //下面这行注释掉,调用Display会报错, 解开注释正常运行!!!
	//friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum; // 学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
静态成员和继承的关系

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

class A
{
public:
	A()
	{
		++_count;
	}
	void func()
	{
		cout << _count << endl;
	}
protected:
	static int _count;
};
int A::_count = 0;

class B :public A
{};
class C :public A
{};
void test()
{
A a;
a.func();
B b;
b.func();
B b1;
b.func();
C c;
c.func();
}

三、继承的拓展 单继承

一个子类只有一个直接父类的继承就是单继承。

格式
class A{}
class B:public A{}
class C:public B{}
class D:public C{}
多继承

一个子类有两个或两个以上的直接父类的继承叫做多继承。

格式:
class D:public A,public B,public C
{

}
  • 多继承子类会继承每个父类的成员
菱形继承

下面的结构就是菱形继承

菱形继承中B C 继承自A,那么B C 中的成员有继承自A的_a 和自己的成员变量,D又是继承自B C ,那么其内部就会有B C 中的成员(继承下来的和本身的)和自己的成员_d

A B C D 中的类的成员图解:

根据上图可见,D中会有两个_a变量,分别来自B继承A的 _a 和 C继承A的 _a ,那么就会存在数据冗余和二义性的问题。

  • 数据冗余:在一个对象中会有两份相同的成员。

  • 二义性:以上图为例,D中有两个_a ,继承自B和C,那么如果想访问D中的 _a,直接访问的话那么就会使得编译器分不清到底是访问的是继承自B中的 _a 还是继承自 C 中的 _a ,这就是由数据冗余导致的二义性问题。需要指定类域去显式访问。

错误访问方式:

相关代码:

//菱形继承问题
namespace zh6
{
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;
};
void test()
{
	//菱形继承 就会导致最底下的一层里会有两个最顶层里的成员变量或函数,会有二义性和数据冗余
	//二义性就是A里有成员变量_a ,D继承了B C ,B C 继承自A 那么会导致D中会有两个_a ,分别来自B和C
	//如果D中想要访问_a 那么就会出现分不清是访问继承自B中的_a,还是继承自C中的_a,出现二义性。
	// 数据冗余就是相同的数据会继承两份。  如果换成虚继承就不会有这些问题了
	
	D d;
	//d._a = 9;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
}
}

对象成员模型

菱形继承会继承B 和 C 中的成员_a ,那么d对象中就会有两块空间,分别是B C对象的 _a

菱形虚拟继承

上面的菱形继承会有数据冗余和二义性的问题,但是如果换成菱形虚拟继承就不会有上面的问题。因为公共的数据只会有一份。

//格式
class A
{...}
class B:virtual public A
{...}
class C:virtual public A
{...}
class D:public B,public C
{...}
菱形虚拟继承的原理
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;
	};
	void test()
	{
		//菱形继承 就会导致最底下的一层里会有两个最顶层里的成员变量或函数,会有二义性和数据冗余
		//二义性就是A里有成员变量_a ,D继承了B C ,B C 继承自A 那么会导致D中会有两个_a ,分别来自B和C
		//如果D中想要访问_a 那么就会出现分不清是访问继承自B中的_a,还是继承自C中的_a,出现二义性。
		// 数据冗余就是相同的数据会继承两份。  如果换成虚继承就不会有这些问题了
		
		D d;
		//d._a = 9;
		d.B::_a = 1;
		d.C::_a = 2;
		d._b = 3;
		d._c = 4;
		d._d = 5;
	}

对象成员模型:

下图的内存2是对象d的成员模型,可以观察到其里面有数字2 3 4 5,和两个地址,这两个地址分别是对象B 和C的虚基表指针,分别指向一张虚基表,虚基基表中存的是两个偏移量,第一个偏移量是该虚基表指针和虚函数表指针的偏移量,第二个偏移量是该对象和公共对象的偏移量。

  • 利用菱形虚拟继承就会使得d对象中的_a只有一个,在该模型的最下面的一块独立内存 与B C对象分离,但其是B和C对象公共的,因为上面的代码对d中的 _a 修改了两次,对B对象和C对象中的 _a 的修改是一样的,都是修改的d对象中的 _a,所以 _a的结果以最后一次修改为主,为2;

菱形虚拟继承解决了数据冗余问题,将两个对象中公共的数据从两个对象中剥离出来,另外再两个对象外用一块独立的内存存放该公共数据,省去了两个对象中都存着各自的公共数据,造成数据冗余和二义性。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1033068.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号