C++进阶——继承

1.继承的概念及介绍

继承是C++中一个重要的知识点,继承是一种对类进行复用的很好方式。继承用于对类的拓展,在保持原有类特性的基础上进行扩展,增加功能,进而产生新的类,这种继承产生的类称派生类,其继承的类称为基类,我们也习惯称派生类为子类,基类为父类。下面设计到的派生类和基类将用子类和父类来替代。

2.继承的定义使用

子类通过继承可以获得父类的全部属性,而子类获得的属性具有的权限是由父类中的访问限定符和其继承方式共同决定的,类中的访问限定符有三个,分别是:public、private、protected其访问的权限大小依次是:public > protected > prvate,同样的类的继承方式也是这三种类型,但实际上绝大多数场景都是使用public继承方式,这也是封装复用的思想。

2.1继承使用

继承的使用形式是:

  1. 子类:继承方式 + 父类名
  2. 子类:父类名

下面是一个简单的继承使用例子:

class A
{
private:
	int _n = 0;
protected: 
    int _a = 1;
};

class B : public A //类B通过public方式继承父类A
{
private:
	int _b = 10;
};

class C :  A //类C通过class类默认private方式继承父类A
{
private:
	int _c;
};

对于类继承的两种方式,第二种使用了类的默认继承方式,也就是struct类默认继承方式为public而class类默认为private,一般我们都应该使用第一种方式显示的去继承使用 ,这样代码的可读性会更好。此外还有一些我们需要注意的

  • 对于父类的private成员,其虽然会被子类继承下来,但是在子类中一般是不可见被隐藏的,这与子类的继承方式无关,无论什么继承方式,父类中的private成员子类在类中或类外都不可以进行访问。
  • 对于父类的protected成员,子类可以在内中进行访问,在类外不可访问。这反映出,对于非继承类中的protected成员与private成员的权限大小是相同的。

如定义一个B类对象b,我们就能发现其成员中含有父类的private成员_n以及protected成员_a,只是private成员被隐藏无法被子类访问。

 

2.2子类与父类间的赋值转换

在继承中,支持着子类对象赋值给父类对象的转换,但是父类对象不能赋值给子类对象。这是因为,父类中的成员子类中都存在,而子类拥有父类所不存在的成员,而对于将子类对象赋值给父类对象转换这一过程,我们可以理解为,将子类中父类所没有的成员切割掉后,不使用这部分, 再将剩余父类中需要的成员一一赋值,得到父类对象,。对于子类对象赋值给父类对象,单纯的对象赋值,指针赋值、引用赋值均可。需要注意的是切割不是真正意义上的切割掉,只是不用这部分,但是实际上这部分仍然存在,否则引用或指针的赋值是解释不通的。

class Person
{
protected:
	string _name;
	string _sex;
	int _age;
public:
	Person(string name = "",string sex = "",int age = 0)
		:_name(name)
		,_sex(sex)
		,_age(age)
	{}
	void Print()
	{
		cout << _name << "-" << _sex << "-" << _age << endl;
	}

};
class Student : public Person
{
private:
	int _N;
public:
	Student(int N = 10)
		:_N(N)
	{}
};

 2.3继承的作用域

在继承中,子类与父类都有不同的独立作用域,这就意味着,如果子类中出现与父类中同名的成员是不会排斥的,但是有着优先级使用的关系,类似于全局变量与局部变量同名的关系。

  • 如果子类和父类中有同名成员,子类成员将屏蔽父类中的同名成员优先访问子类自己的成员,这种情况叫隐藏,也叫重定义。如果想访问父类中的同名成员,在子类成员函数中,可以使用 基类::基类成员 显示访问
  • 对于函数成员的同名,只要函数的名称相同算构成隐藏,在调用函数时会优先调用子类中的成员函数。
class A
{
protected:
	int _a;
	int _n;
public:
	A(int a = 0,int n =0)
		:_a(a)
		,_n(n)
	{}
};
class B :public A
{
private:
	int _b = 1;
	int _n = 10;
public:
	void Print()
	{
		cout << _a << "-" << _n << "-" << _b << endl;
		cout << _a << "-" << A::_n << "-" << _b << endl;
	}
	
};

2.4子类中的默认成员函数

 子类中的六个默认成员函数我们这里主要会讨论其中最为重要的四个,也就是构造函数,析构函数,拷贝构造函数,赋值重载函数,在继承中,默认成员函数的使用与普通类有所不同。除了析构函数以外,其余默认函数的子类都需要手动调用父类的默认函数。

class A
{
protected:
	int _a;
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "A(const A & a)" << endl;
	}
	A& operator=(const A& a)
	{
		_a = a._a;
		cout << "A operator = " << endl;
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};

class B :public A
{
private:
	int _b;
public:
	B(int a = 0, int b = 0)
		:A(a)//调用父类的构造函数
		,_b(b)
	{
		cout << "B()" << endl;
	}
	B(const B& b)
		:A(b)//调用父类的拷贝构造
		,_b(b._b)
	{
		cout << "B(const B& b)" << endl;
	}
	B& operator=(const B& b)
	{
		if (this != &b)
		{
			A::operator=(b);//调用父类的赋值重载
			_b = b._b;
			cout << "operator= (const B& b)" << endl;
		}
		return *this;
	}
	~B()
	{
		cout << "~B()" << endl;//父类的析构函数会在子类调用后自动调用,不需要手动调用
	}
};

2.5继承与友元 

继承的友元关系是不能够继承的,子类与父类中的友元是分开的,子类并不会去继承父类中的友元,子类中的友元也与父类无关,两者并不能通过对方的友元来进行互相访问。

2.6继承与静态成员

继承关系中的静态成员函数是共有有的,这与普通类是相似的,无论定义多少继承关系,父类中的静态成员永远都且只有一个,父类中的静态成员通过继承关系与域作用访问限定符可供所有的子类成员共享。

class A
{
protected:
	int _a = 0;
public:
	static int _n;
};
int A::_n = 0;

class B : public A 
{
private:
	int _b = 0;
};

class C : public A
{
private:
	int _c = 0;
};

2.7菱形继承与虚继承 

实际上继承分为两种情况,一个是单继承,另一个是多继承。而菱形继承作为C++中的一个缺陷点,有着一些较为复杂的运用场景,菱形继承是由多继承所导致的复杂继承,是多继承中的一个特殊情况,菱形继承具有数据的冗余性以及二义性的缺点,而虚继承是用于解决菱形继承这种场景的出现。

2.71单继承

单继承实际上就是只有一个父类对象的继承方式,子类只继承一个父类,这种是我们大多数所使用的继承方式

2.72多继承

多继承中,子类继承了多个父类,有多个父类。在多继承中

子类通过:继承方式+父类,继承方式+ 父类...的形式继承多个父类。

class A
{
protected:
	int _a;
};

class B
{
protected:
	int _b;
};

class C : public A ,public B
{
private:
	int _c;
};

2.73菱形继承

菱形继承作为一个多继承的特殊方式,有着数据冗余二义性的问题,让我们看一个简单的菱形继承的例子。

class N
{
protected:
	int _n;
};

class A : public N
{
protected:
	int _a;
};

class B : public N
{
protected:
	int _b;
};

class C : public A ,public B
{
protected:
	int _c;
};

 

我们知道,类A与类B都继承了类N,类A、B有着共同的父类N,而类C又继承了类A与类B两个父类,这样就形成了一个封闭的菱形继承,仔细观察,我们会知道,A、B类中有着类N相同的成员,而C的继承再次继承了A与B中共同的成员,导致了C类中同时有多个同名的成员,从而导致数据冗余与二义性的问题。

2.74虚继承(virtual)

虚拟继承是为了解决萎形继承的二义性和数据兄余的问题而出现的。

虚拟继承由关键词 :virtual + 继承方式 + 父类名 的语法构成的,但需要注意的是,虚拟继承不要在其他地方去使用。

class A : virtual public N
{
protected:
	int _a;
};
class B : virtual public N
{
protected:
	int _b;
};

虚继承的本质是通过将A,B中相同的成员合并为一个成员,从而解决菱形继承数据冗余与二义性的问题。另外虚定位会额外开空间用于记录被合并同名成员的地址偏移量,以后要使用这些同名成员,通过地址偏移量找到,这个作为了解即可。

3.继承和组合

了解继承之后,会发现继承与类的组合很相似,类的组合实际上就是一个类作为另一个类成员的使用,这种我们使用的情况也较多,比较两者,都是在实现对于类的复用。对于继承和组合,两者有着不同的更合适的使用场景。

  • 类的继承可以看做是一种is-a的关系。也就是说每个派生(子)类对象都是一个基(父)类对象,二者类似于属于的关系。
  • 组合可以看做是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象,二者类似于包含的关系。
class A
{
protected:
	int _a;
};

class B//类组合
{
	A _aa;
};

class C :public A//类继承
{
private:
	int _c;
};

3.1继承和组合的使用

对于类的继承和组合的选择,实际上如果两者都可使用,我们应尽量使用类的组合,这是因为相比类的继承,子类与父类具有较强的关联性,也就是我们常说的高耦合性,子类很容易修改父类的中的一些成员变量,这会导致父类的封装性变差。但对于适合用继承的场景我们仍然需要使用继承,其存在即合理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值