C Pulse Pulse 继承与多态

 3.6继承

3.6.1继承的基本语法
class A:pubilc B;
//A称为 子类 或 派生类
//B称为 父类 或 基类
3.6.2继承方式

无论是那种继承方式,都不能访问父类中访问权限为private的成员,公共权限和保护权限均可访问;继承后,子类:

  • 公共继承:访问权限保持不变
  • 保护继承:访问权限均变为 pretected
  • 私有继承:访问权限均变为 private
3.6.3继承中的对象模型

继承过程中所有成员都会继承下去,但是私有成员会被编译器隐藏

class Base
{
	int m_A;
	int m_B;
	int m_C;
};
class Son :public Base
{
	int m_D;
};
int main()
{
	Son s1;
	cout <<"sizeof(s1)=" << sizeof(s1) << endl;
}

class默认的成员属性为private,根据sizeof 的计算结果也能推测出子类会继承父类的所有成员,但是private权限的成员会被编译器隐藏

 

3.6.4继承中构造和析构的顺序

 在子类继承父类后,当创建子类对象时,会先创建一个父类的对象,也会调用父类的构造函数,销毁时也会调用父类的析构函数

构造:先构造父类,再构造子类

析构:先析构子类,再析构父类

class Base
{
public:
	Base()
	{
		cout << "Base的构造函数" << endl;
	}
	~Base()
	{
		cout << "Son的析构函数" << endl;
	}
};
class Son :public Base
{
public:
	Son()
	{
		cout << "Son的构造函数" << endl;
	}
	~Son()
	{
		cout << "Son的析构函数" << endl;
	}
};
int main()
{
	Son s1;
	return 0;
}

3.6.5继承中同名成员的处理

当子类成员和父类成员出现同名的情况时,直接调用 调用的是子类的成员,如果要调用父类的成员,需要加作用域

如果成员函数同名,则子类的同名成员会隐藏掉父类中的所有同名成员函数,即使成员函数的参数不同,直接调用都只会调用子类的成员函数

class Base
{
public:
	int m_A;
};
class Son :public Base
{
public:
	int m_A;
	Son(int a,int b)
	{
		m_A = a;
		Base::m_A = b;
	}
};
int main()
{
	Son s1(13, 14);
	cout << s1.m_A << s1.Base::m_A << endl;
	s1.m_A = 5;
	s1.Base::m_A = 20;
	cout << s1.m_A << s1.Base::m_A << endl;
}

拓展:继承中同名静态成员的处理方式

静态成员和非静态成员出现同名,处理方式一致

  • 通过对象访问

访问子类同名成员直接访问即可

访问父类同名成员需要加作用域

  • 通过类名访问
class Base
{
public:
	static int m_A;
};
int Base::m_A = 0;
class Son : public Base
{
public:
	static int m_A;
};
int Son::m_A = 1;
int main()
{
	Son s1;
	cout << "1.通过对象访问" << endl;
	cout << "Base下的 m_A=" << s1.Base::m_A << endl;
	cout << "Son下的 m_A=" << s1.m_A << endl;

	cout << "2.通过类名访问" << endl;
	cout << "Base下的 m_A=" << Base::m_A << endl;
	cout << "Son下的 m_A=" << Son::m_A << endl;
	//第一个::代表以类名形式访问,第二个::代表访问父类作用域下
	cout << "Base下的 m_A=" << Son::Base::m_A << endl;
}

3.6.6多继承语法

C++允许—个类继承多个类
语法:

class 子类︰继承方式 父类1,继承方式 父类2..

多继承可能会引发父类中有同名成员出现,需要加作用域区分,实际开发不建议使用多继承
 

3.6.7 菱形继承

概念:两个派生类继承同一个基类,又有某个类(简称孙类)同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承

这个孙类继承了父类的数据两份,但这份数据只需要一份就行;

解决这个问题可以利用 虚继承 解决:在继承之前加上 关键字 virtual 变为 虚继承, 

class Animal
{
public:
	int m_Age;
};
class Sheep:virtual public Animal{};
class Tuo:virtual  public Animal{};
//Animal类称为 虚基类
class SheepTuo :public Sheep, public Tuo{};
//当Sheep和Tuo虚继承后,SheepTuo继承的并不是两份数据而是两个指针
//这两个指针通过偏移量找到唯一的数据
int main()
{
	//可以通过在作用域的方式解决SheepTuo中m_Age的二义性;
	SheepTuo s1;
	s1.Sheep::m_Age = 18;
	s1.Tuo::m_Age = 28;
	cout << "s1.Sheep::m_Age=" << s1.Sheep::m_Age << endl;
	cout << "s1.Tuo::m_Age=" << s1.Tuo::m_Age << endl;
	cout << "s1.m_Age=" << s1.m_Age << endl;
}

3.7多态

3.7.1多态的基本语法

基本概念:多态是C++面向对象三大特性之一

分类:

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

区别:

  • 静态多态的函数地址早绑定–编译阶段确定函数地址
  • 动态多态的函数地址晚绑定–运行阶段确定函数地址
class Animal
{
public:
	//函数前面加上virtual关键字,变成虚函数
//编译器在编译时就不能确定函数调用了
	virtual void speak()//虚函数
	{
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};
//执行说话的函数
void doSpeak(Animal& animal)
{
	animal.speak();//地址早绑定,在编译阶段确定函数地址
//如果要让输出根据输入的不同而变化,需要地址晚绑定
	//动态多态满足条件
//1.有继承关系 2.子类重写父类的虚函数 
	//重写:函数返回类型 函数名 参数列表 完全相同
}
int main()
{
	Cat cat;
	doSpeak(cat);//C++中允许父类子类之间的类型转换
	//Animal &animal=cat;
	Dog dog;
	doSpeak (dog);
}

总结:

多态满足条件:

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件:

  • 父类指针或引用指向子类对象

重写:函数返回值类型  函数名  参数列表  完全一致称为重写
 

3.7.2纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:

virtual  返回值类型  函数名(参数列表)=0 ;

当类中有了纯虚函数,这个类也称为抽象类,其特点为:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

3.7.3虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构


虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象:无法通过类名直接创建对象,但可创建该类的指针

代码事例:

class Animal
{
public:
	Animal()
	{
		cout << "Animal 构造函数调用" << endl;
	}
	~Animal()
	{
		cout << "Animal 析构函数调用" << endl;
	}
	virtual void speak() = 0;//纯虚函数
};
class Cat :public Animal
{
public:
	string* m_Name;
	Cat(string name)
	{
		cout << "Cat 构造函数调用" << endl;
		m_Name = new string (name);
	}
	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat 析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}
	void speak()
	{
		cout << *m_Name << "小猫在说话" << endl;
	}
};

int main()
{
	Animal* animal = new Cat("Tom");
	animal->speak();
//父类指针在析构时 不会调用 子类中的析构函数,导致子类中如果有堆区数据,会发生内存泄漏
	delete animal;
}

父类指针在析构时 不会调用 子类中的析构函数,导致子类中如果有堆区数据,会发生内存泄漏

解决办法是在 父类的析构函数前加上 virtual,称为虚析构

virtual ~Animal()
{
	cout << "Animal 析构函数调用" << endl;
}

纯虚析构:类内声明,类外实现。

class Animal
{
public:
	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal 析构函数调用" << endl;
}

总结:

  • 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 拥有纯虚析构函数的类也属于抽象类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值