C++学习(8)继承

继承

继承是C++面向对象编程的重要概念,它运行我们依据另一个类来定义一个类。(三大特性之一)

当创建一个类时,你不需要重新编写新的数据成员和成员函数,只需要指定新建的类继承一个已有类的成员即可。

这个已有的类 被称为 基类
新建的类 被称为 派生类

// 基类
class Animal {
    // eat() 函数
    // sleep() 函数
};

//派生类
class Dog : public Animal {
    // bark() 函数
};

由类派生列表来指定基类 如:

class derived-class: access-specifier base-class

(1)access-specifier 表示访问修饰符,即继承方式,包括:public、protected、private,
(2)base-class 表示已经定义好的基类
(3)derived-class派生类的名称
示例:

#include <iostream>
 
using namespace std;
 
// 基类
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
   protected:
      int width;
      int height;
};
 
// 派生类
class Rectangle: public Shape
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};
 
int main(void)
{
   Rectangle Rect;
 
   Rect.setWidth(5);
   Rect.setHeight(7);
 
   // 输出对象的面积
   cout << "Total area: " << Rect.getArea() << endl;
 
   return 0;
}

注意:这里需要说明的是之前学习是没有提到的 第三个访问控制:protected,特点如下:
(1)关键字protectedprivate相似,在类外只能用公有(public)类成员访问。

(2)关键字protectedprivate的区别在于,派生类的成员可以直接访问基类的保护(protected)成员,但是不能直接访问基类的私有(private)成员。

(3)在继承过程中,派生类其实也继承了基类的私有成员。基类的私有成员虽然不能直接被派生类访问,但是可以通过调用基类的公有和保护成员访问,

继承方式

  • 继承方式只是限定了继承的最高权限,权限高了就降,权限低了不变
    (4)继承方式的特点:public、protected、private,(三种继承的派生类都无法访问基类的私有成员)
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有(public)保护(protected)成员将成为派生类的保护成员(protected)
  • 私有继承(private):当一个类派生自私有基类时,基类的公有(public)保护(protected)成员将成为派生类的私有成员
  • 公共继承(public):当一个类派生自公共基类时,基类的公有成员(public) 将成为 派生类的公共成员(public)基类的保护成员(protected) 继续成为 派生类的 保护成员(protected)
  • 例:
#include <iostream>
using namespace std;

class Temp_base
{
public:
	int m_a =10;
private:
	int m_b =11;
protected:
	int m_c=12;
};
class Temp1 :public Temp_base
{
public :
	void fun()
	{
		m_aa = m_a + m_c;//m_a 是子类的 public ,m_c 是子类的 protected
	}
	int m_aa;
};
class Temp2 :protected Temp_base
{
public: 
	void fun()
	{

		m_c = 14;//使用 protected 方式继承的派生类,父类的 protected 和 public 都成为派生类的protected
		m_bb = m_c + m_a; // m_a 和 m_c 是子类的保护成员外部不能访问
	}
	int m_bb;

};

class Temp3 :private Temp_base
{
public :
	void fun()
	{
		m_a = 15;
		m_c = 17;
		m_CC = m_a + m_c; //m_a 和 m_c 是子类的私有成员外部不能访问
	}
	int m_CC;
};
int main()
{
	Temp1 p1;
	Temp2 p2;
	Temp3 p3;
	cout << p1.m_a << endl;// public 方式继承的子类,父类的public权限成员也成为子类的public,protected权限成员也成为子类的protected成员
	//cout << p2.m_a << endl;//,外部不能访问 protected 方式继承的子类,父类的protected权限成员和public权限成员都成为子类的protected成员 
	p2.fun();//使用函数,将 protected 的成员变量赋值给 子类的 public 成员
	cout << p2.m_bb << endl;//

	p3.fun();
	cout << p3.m_CC << endl;
}
  • (5)继承中构造与析构的顺序
    比如: 父类 A ,子类 B,B 继承 A,当创建一个 B 类对象时,
    系统内部会先创建 父类A的构造——》子类B的构造———》子类B的析构——》父类A的析构

改变访问权限

  • 使用 using ,可改变基类成员在派生类中的访问权限,例如public 变 private, protected 变 public
class Temp_base
{
public:
	int m_a =10;
private:
	int m_b =11;
protected:
	int m_c;
};
class Temp1 :public Temp_base
{
public :
	using Temp_base::m_c; // 将 protected 改为public
	int m_aa;
};

处理继承中出现的同名成员

比如:父类 A 中有一个 名为fun 的成员,子类 B 中也有一个 名为fun 的成员,

  • 访问子类同名成员,直接访问即可:B p;p.fun;
  • 访问父类同名成员,需要加上作用域:B p;p.A::fun;
    例:
class Temp_base
{
public:
	int m_a =10;

	void fun1()
	{
		cout << "父类" << endl;
	}

	int m_aa = 1;

private:
	int m_b =11;
protected:
	int m_c=12;

};
class Temp1 :public Temp_base
{
public :
	void fun()
	{
		m_aa = m_a + m_c;//m_a 是子类的 public ,m_c 是子类的 protected
	}
	int m_aa;
	void fun1()
	{
		cout << "子类成员函数" << endl;
	}
};

void test()
{
	Temp1 p;
	p.fun1();
	//父类的成员函数,需要注意的是,父类与子类之间的不存在重载的特性,因为子类函数会屏蔽掉所有的父类同名函数,所以需要使用作用域
	p.Temp_base::fun1();

	cout << p.m_aa << endl;
	//父类的成员变量
	cout << p.Temp_base::m_aa << endl;

	}

注意:

  • 调用父类的成员函数,需要注意的是,父类与子类之间的不存在重载的特性,因为子类函数会屏蔽掉所有的父类同名函数,所以需要使用作用域

父类子类的静态成员变量

类内声明,类外初始化;

(1)同名静态成员属性:

  • 注意:静态成员变量,类内声明,类外初始化
  • 访问方式上:1、 从通过定义类变量访问,2、直接访问(采用"::")
class Base
	{
	public:
		static int m_a;//类内声明
		static void fun()
		{
			cout << endl;
		}
	};
	int Base::m_a = 100;//类外初始化

	class Son:public Base
	{
	public:
		static int m_a;//与父类同名的静态
	    static void fun()
		{
			cout << endl;
		}
	};
	int Son::m_a = 200;
	
	void test()//继承出现,同名的属性
	{
		//访问方式:从通过定义类变量访问
		Son s;
		cout << s.m_a << endl;
		//直接访问
		cout << Son::m_a << endl;//直接访问Son的静态
		cout << Base::m_a << endl;//直接访问base 的静态
		cout << Son::Base::m_a << endl;//通过Son 访问 Base的静态 第一个:: 表示通过类名访问,第二个:: 表示访问父类作用域下
	}

(2)同名静态函数:(接上例)

  • 注意:子类出现了与父类同名的静态函数,它会将父类的函数隐藏掉,,所以Son::fun();是访问的子类的,而采用Son::Base::fun()可访问父类的
void test01()//继承出现,同名的函数 
	{
		//静态非静态都能这样嗲用
		Son s;
		s.fun();
		s.Base::fun();

		//这样写的话,函数必须是静态函数
		Son::fun();
		Son::Base::fun();
		//子类出现了与父类同名的静态函数,它会将父类的函数隐藏掉
	}

基类和派生类的构造与析构函数

  • 在设计派生类时, 对继承过来的成员变量的初始化工作的也要有派生类的构造函数完成, 但是大部分基类都有 private 属性的成员变量, 它们在派生类中无法访问, 更不能使用派生类的构造函数进行初始化。
  • 解决此类问题的方法,即是在派生类的构造函数中的调用基类的构造函数
class Temp_base
{
public:
	Temp_base(int num1, int num2);
	int m_a ;
private:
	int m_b;
};
Temp_base::Temp_base(int num1, int num2):m_b(num1), m_a(num2){
}

class Temp1 :public Temp_base
{
public :
	Temp1(int num0);
	int m_aa;
};

Temp1::Temp1(int num1, int num2, int num0):Temp_base(num1, num2), m_aa(num0){}
或者:
Temp1::Temp1(int num1, int num2, int num0):Temp_base(12, 11), m_aa(num0){
	cout << num1 << num2 << endl;
}
  • 注意不能将基类的构造函数写在派生类的构造函数里面,因为基类构造函数不是派生类的成员函数,故只能写在初始化列表上(构造函数头上),
  • 同时基类的初始化不一定需要从派生类的构造中获取形参,可以自己设定。

多继承

即 一个子类可以有多个父类,且同时继承这些父类的特性。

结构:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,{
<派生类类体>
};

注意:多继承中会出现同名的属性,这上面同名处理一样,使用作用域的方式进行操作。

s.Base1::m_A ;
s.Base2::m_A;

在C++的开发中,不建议使用多继承

菱形继承

两个派生类继承同一个基类,又有某个类同时继承着两个派生类,形成一个菱形。


//菱形顶部
class Base
{
public:
	int m_a;
};
//菱形左边
class left :public Base
{

};
//菱形右边
class right :public Base
{

};
//菱形底部
class bottom :public left, public right
{

};

void test()
{
	bottom b;
	//菱形继承时,两个父类用于相同的数据,需要使用作用域区分,
	b.left::m_a = 10; 
	b.right::m_a = 11;
	//b.m_a = 1;//错误,不明确
	//
	//但是这两个父类有事继承于同一个父类,由于都是源于一个父类,这个份数据只有一份即可,而菱形继承会导致数据继承两分,造成资源浪费
}
  • 但是这两个父类有事继承于同一个父类,由于都是源于一个父类,这个份数据只有一份即可,而菱形继承会导致数据继承两分,造成资源浪费
  • 如何解决菱形继承问题——虚继承 在菱形的两边继承时,使用virtual
//菱形顶部
class Base
{
public:
	int m_a;
};
//菱形左边
class left :virtual public Base
{

};
//菱形右边
class right :virtual public Base
{

};
//菱形底部
class bottom :public left, public right
{

};

void test()
{
	bottom b;
	//菱形继承时,两个父类用于相同的数据,需要使用作用域区分,
	b.left::m_a = 10; 
	b.right::m_a = 11;

	//但是这两个父类有事继承于同一个父类,由于都是源于一个父类,这个份数据只有一份即可,而菱形继承会导致数据继承两分,造成资源浪费
	//如何解决菱形继承问题——虚继承 在菱形的两边继承时,使用virtual
	b.m_a = 1;//没问题了

}

此时,left 和 right 中的将存储一个 vbptr 指针(虚拟基类指针),虚基类指针会指向相应虚拟基类表,由存储的偏移量来进行区分。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LionelMartin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值