第四章 类和对象(三)

4.6继承

继承是面向对象三大特征之一

有些类与类之间存在特殊的关系

我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。 

4.6.1基本语法

代码

#include<iostream>
using namespace std;

class BasePage 
{
public:
	void header() 
	{
		cout << "首页、公开课、登录....(公共头部)" << endl;
	}
	void footer() 
	{
		cout << "帮助中心、合作交流、站内地图....(公共底部)" << endl;
	}
	void left() 
	{
		cout << "Java、Python、C++....(公共分类列表)" << endl;
	}
};

//Java页面
class Jave :public BasePage 
{
public:
	void content() 
	{
		cout << "Java学科视频" << endl;
	}
};
//Python页面
class Python :public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
//C++页面
class CPP :public BasePage
{
public:
	void content()
	{
		cout << "CPP学科视频" << endl;
	}
};

void test01() 
{
	cout << "Java下载视频页面如下:" << endl;
	Jave ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();

	cout << "----------------------" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();

	cout << "----------------------" << endl;
	CPP cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
}
int main()
{

	test01();
	system("pause");

	return 0;
}

总结:

1、继承的好处:减少重复代码

2、基本语法:class 子类:继承方式  父类

3、子类 也称为 派生类;父类 也称为 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过来的表现其共性,而新增的成员体现了其个性

4.6.2继承方式

继承方式的种类

  • 公共继承
  • 保护继承
  • 私有继承

 代码

#include<iostream>
using namespace std;
class Base 
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class son1 :public Base 
{
public:
	void func()
	{
		m_A = 10;//父类中的公共成员,到子类中仍是公共成员
		m_B = 10;//父类中的保护成员,到子类中仍是保护成员
		//m_C = 10; //父类中的私有成员,子类中访问不到
	}
};
void test01() 
{
	son1 s1;
	s1.m_A = 100;
	//s1.m_B = 100;//到son1中,m_B是保护成员,类外访问不到
	//s1.m_C = 100;//到son1中,m_C是私有成员,类外访问不到
}

class son2 :protected Base 
{
public:
	void func() 
	{
		m_A = 10;//父类中的公共成员,到子类中为保护成员
		m_B = 10;//父类中的保护成员,到子类中为保护成员
		//m_C = 10;//父类中的私有成员,子类中仍是私有成员,访问不到
	}
};

void test02() 
{
	son2 s2;
	//s2.m_A = 100;  在son2中,m_A为保护成员,类外访问不到
	//s2.m_B = 100;  在son2中,m_B为保护成员,类外访问不到

}

class son3 :private Base 
{
public:
	void func() 
	{
		m_A = 10;//父类中的公共成员,子类中为私有成员
		m_B = 10;//父类中的保护成员,子类中为私有成员
		//m_C = 10;父类中私有成员,子类访问不到
	}
};

void test03() 
{
	son3 s3;
	//s3.m_A = 100;  子类中为私有成员,类外访问不到
	//s3.m_B = 100;  子类中为私有成员,类外访问不到
}
int main()
{
	system("pause");

	return 0;
}

4.6.3 继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

代码

#include<iostream>
using namespace std;
//1、函数重载需要函数都在同一个作用域下
void func() 
{
	cout << "func的调用" << endl;
}
 
void func(int a)
{
	cout << "func(int a)的调用" << endl;
}
 
void func(double a)
{
	cout << "func(double a)的调用" << endl;
}
 
void func(int a , double b)
{
	cout << "func(int a,double b)的调用" << endl;
}
 
void func(double a,int b)
{
	cout << "func(double a,int b)的调用" << endl;
}
//函数的返回值不可以作为函数重载条件
//int func(double a, int b) 
//{
//	cout << "func(double a,int b)的调用" << endl;
//}
 
int main()
{
	func();
	func(10);
	func(3.14);
	func(3.14, 10);
 
 
	system("pause");
 
	return 0;
}

4.6.4继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

代码

using namespace std;

class Base 
{
public:
	Base() 
	{
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
		cout << "Base析构函数!" << endl;
	}
};

class Son :public Base 
{
public:
	Son()
	{
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
		cout << "Son析构函数!" << endl;
	}
};
void test01() 
{
	//Base b1;
	Son s1;
}

int main()
{
	test01();

	system("pause");

	return 0;
}

总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。

4.6.5 继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?

  • 访问子类同名成员  直接访问即可
  • 访问父类同名成员  需要加作用域

代码

#include<iostream>
using namespace std;

class Base 
{
public:
	Base() 
	{
		m_A = 10;
	}
	void func() 
	{
		cout << "Base-func()调用" << endl;
	}
	void func(int a)
	{
		cout << "Base-func(int a)调用" << endl;
	}
	int m_A;
};
class Son:public Base
{
public:
	Son() 
	{
		m_A = 20;
	}
	void func()
	{
		cout << "Son-func()调用" << endl;
	}
	int m_A;
};
//同名成员属性处理
void test01()
{
	Son s1;
	cout << "Son中m_A = " << s1.m_A << endl;

	cout << "Base中m_A=" << s1.Base::m_A << endl;
}
//同名成员函数处理
void test02() 
{
	Son s2;
	s2.func();
	s2.Base::func();
	s2.Base::func(12);
}

int main()
{
	//test01();
	test02();
	system("pause");
	
	return 0;
}

总结:

1、子类对象可以直接访问到子类中同名成员

2、子类对象加作用域可以访问到父类同名成员

3、当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

4.6.6 继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

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

  • 访问子类同名成员  直接访问即可
  • 访问父类同名成员  需要加作用域

代码

#include<iostream>
using namespace std;

class Base 
{
public:
	static int m_A;
	static void func() 
	{
		cout << "Base-static void func()调用" << endl;
	}
};
int Base::m_A = 100;

class Son:public Base
{
public:
	static int m_A;
	static void func()
	{
		cout << "Son-static void func()调用" << endl;
	}
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
	//1、通过对象访问
	cout << "通过对象访问 " << endl;
	Son s1;
	cout << "Son 下 m_A=" << s1.m_A << endl;
	cout << "Base 下 m_A=" << s1.Base::m_A << endl;
	//2、通过类名访问
	cout << "通过类名访问" << endl;
	cout << "Son 下 m_A=" << Son::m_A << endl;
	cout << "Base 下 m_A=" << Son::Base::m_A << endl;
}
//同名成员函数
void test02()
{
	//1、通过对象访问
	cout << "通过对象访问 " << endl;
	Son s1;
	s1.func();
	s1.Base::func();
	//2、通过类名访问
	cout << "通过类名访问" << endl;
	Son::func();
	Son::Base::func();
	//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作用域访问
}
int main()
{
	//test01();
	test02();
	system("pause");

	return 0;
}

总结:

同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(对象和类名)。

4.6.7 多继承语法

c++允许一个类继承多个类

语法:class 子类 :继承方式  父类1 ,继承方式  父类2......

多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议用多继承

代码

#include<iostream>
using namespace std;

class Base1 
{
public:
	Base1() 
	{
		m_A = 10;
	}
	int m_A;
};
class Base2
{
public:
	Base2()
	{
		m_B = 20;
	}
	int m_B;
};

class Son :public Base1, public Base2 
{
public:
	Son() 
	{
		m_C = 30;
		m_D = 40;
	}
	int m_C;
	int m_D;
};

void test01()
{
	Son s1;
	cout << "sizeof Son=" << sizeof(s1) << endl;
}
int main()
{
	test01();

	system("pause");

	return 0;
}

4.6.8 菱形继承

概念:

两个派生类继承同一个基类

又有某个类同时继承两个派生类

这种继承被称为菱形继承,或者钻石继承

菱形继承经典案例:

 问题:

1、羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性

2、草泥马继承动物的数据继承了两份,但实际上我们只需要一份就可以

代码

#include<iostream>
using namespace std;

class Animal 
{
public:
	int m_Age;
};
//利用虚继承  解决菱形继承问题
//继承之前  加上关键字virtual  变为虚继承  ,Animal类称为  虚基类
class Sheep :virtual public Animal {};
class Tuo :virtual public Animal {};
class SheepTuo :public Sheep, public Tuo {};

void test01() 
{
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;
	//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
	cout << "st.Sheep::m_Age=" << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age=" << st.Tuo::m_Age << endl;
	//
	cout << "st.m_Age=" << st.m_Age << endl;

}
int main()
{
	test01();

	system("pause");

	return 0;
}

总结:

1、菱形继承带来的问题是子类继承两份相同的数据,导致资源浪费以及毫无意义

2、利用虚继承可以解决菱形继承问题

4.7多态

4.7.1多态的基本概念

多态是C++面向对象三大特性之一

分类

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

区别

  • 静态多态的函数地址早绑定——编译阶段确定函数地址
  • 动态多态的函数地址晚绑定——运行阶段确定函数地址

代码

#include<iostream>
using namespace std;

class Animal 
{
public:
	//虚函数
	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();
}
//动态多态满足条件:
//有继承关系、子类重写父类中的虚函数
//重写;函数返回值类型  函数名  参数列表  完全相同

//动态多态使用:
//父类指针或引用指向子类对象

void test01() 
{
	Cat cat;
	DoSpeak(cat);

	Dog dog;
	DoSpeak(dog);
}

int main()
{
	test01();

	system("pause");

	return 0;
}

总结:

多态满足条件

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

多态使用条件

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

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

4.7.2多态案例一——计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

代码

#include<iostream>
using namespace std;
#include<string>
//普通写法
class Calculator 
{
public:
	int getResult(string oper) 
	{
		if (oper == "+") 
		{
			return m_Num1 + m_Num2;
		}
		else if (oper == "-") 
		{
			return m_Num1 - m_Num2;
		}
		else if (oper == "*") 
		{
			return m_Num1 * m_Num2;
		}
	
	}
	int m_Num1;
	int m_Num2;
};
void test01() 
{
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;

	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}

//利用多态来实现计算器

//实现计算器抽象类
class AbstractCaculator 
{
public:
	virtual int getResult() 
	{
		return 0;
	}
	
	int m_Num1;
	int m_Num2;

};
//加法计算器
class AddCalculator :public AbstractCaculator 
{
public:
	virtual int getResult()
	{
		return m_Num1 + m_Num2;
	}
};
//减法计算器
class SubCalculator :public AbstractCaculator
{
public:
	virtual int getResult()
	{
		return m_Num1 - m_Num2;
	}
};
//乘法计算器
class MulCalculator :public AbstractCaculator
{
public:
	virtual int getResult()
	{
		return m_Num1 * m_Num2;
	}
};

void test02()
{
	//加法计算器
	AbstractCaculator *abs = new AddCalculator;
	abs->m_Num1 = 100;
	abs->m_Num2 = 100;
	cout << abs->m_Num1 << " + " << abs->m_Num2 << " = " << abs->getResult() << endl;
	delete abs;
	//减法计算器
	abs = new SubCalculator;
	abs->m_Num1 = 100;
	abs->m_Num2 = 100;
	cout << abs->m_Num1 << " - " << abs->m_Num2 << " = " << abs->getResult() << endl;
	delete abs;
	//乘法计算器
	abs = new MulCalculator;
	abs->m_Num1 = 100;
	abs->m_Num2 = 100;
	cout << abs->m_Num1 << " * " << abs->m_Num2 << " = " << abs->getResult() << endl;
	delete abs;
}

int main()
{
	//test01();
	test02();

	system("pause");

	return 0;
}

4.7.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数的语法: virtual  返回值类型  函数名  (参数列表) =  0;

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

抽象类的特点

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

代码

#include<iostream>
using namespace std;

class Base 
{
public:
	virtual void func() = 0;
};

class Son:public Base
{
	virtual void func() 
	{
		cout << "func函数调用" << endl;
	}
};

void test01()
{
	//Base b;  抽象类是无法实例化对象
	//new Base;
	Base * base = new Son;
	base->func();

}

int main()
{
	test01();

	system("pause");

	return 0;
}

4.7.4 多态案例二——制作饮品

制作饮品的大致流程:煮水——冲泡——倒入杯中——加入辅料

利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶

代码

#include<iostream>
using namespace std;

class AbstractDrinking 
{
public:
	//煮水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;

	//制作饮品
	void makeDrink() 
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};
//制作咖啡
class Coffee :public AbstractDrinking 
{
public:
	//煮水
	virtual void Boil() 
	{
		cout << "煮农夫山泉" << endl;
	}
	//冲泡
	virtual void Brew() 
	{
		cout << "冲泡咖啡" << endl;
	}
	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething() 
	{
		cout << "加入牛奶和糖" << endl;
	}
};

//制作茶叶
class Tea :public AbstractDrinking
{
public:
	//煮水
	virtual void Boil()
	{
		cout << "煮开水" << endl;
	}
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入枸杞" << endl;
	}
};
//制作函数
void doWork(AbstractDrinking * abs) 
{
	abs->makeDrink();
	delete abs;//释放
}

void test01() 
{
	doWork(new Coffee);

	cout << "-----------" << endl;
	
	doWork(new Tea);
}
int main()
{
	test01();

	system("pause");

	return 0;
}

4.7.5虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

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

区别

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

语法

虚析构语法:virtual ~类名(){}

纯虚析构语法:virtual ~类名()=0;

类名::~类名(){}

代码

#include<iostream>
using namespace std;
#include<string>
class Animal 
{
public:
	Animal()
	{
		cout << "Animal的构造函数调用" << endl;
	}
	//利用虚析构可以解决  父类指针释放子类对象时不干净的问题
	//virtual ~Animal()
	//{
	//	cout << "Animal的虚析构函数调用" << endl;
	//}
	//纯虚析构——需要声明也需要实现
	//有了纯虚析构之后, 这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;

	virtual void speak() = 0;
};
Animal::~Animal() 
{
	cout << "Animal的纯虚析构函数调用" << endl;
}

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

void test01() 
{
	
	Animal *animal = new Cat("Tom");
	animal->speak();
	//父类指针在析构时候 不会调用子类中析构函数  导致子类如果有堆区属性,出现内存泄露
	delete animal;

}

int main()
{
	test01();

	system("pause");

	return 0;
}

总结:

1、虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2、如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3、拥有纯虚析构函数的类也属于抽象类

4.7.6 多态案例三——电脑组装

电脑主要组成部件CPU、显卡、内存条

将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件

创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

代码

#include<iostream>
using namespace std;
//抽象不同零件类
class CPU 
{
public:
	virtual void calculate() = 0;
};

class VideoCrad
{
public:
	virtual void display() = 0;
};

class Memory
{
public:
	virtual void storage() = 0;
};
//电脑类
class Computer 
{
public:
	Computer(CPU * cpu, VideoCrad * vc, Memory * mem) 
	{
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}
	//提供工作函数
	void work() 
	{
		//让零件工作起来,调用接口
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}
	~Computer() 
	{
		//释放CPU零件
		if (m_cpu != NULL) 
		{
			delete m_cpu;
			m_cpu = NULL;
		}
		//释放VideoCard零件
		if (m_vc != NULL)
		{
			delete m_vc;
			m_vc = NULL;
		}
		//释放Memory零件
		if (m_mem != NULL)
		{
			delete m_mem;
			m_mem = NULL;
		}
	}

private:
	CPU * m_cpu;
	VideoCrad * m_vc;
	Memory * m_mem;
};
//具体厂商
//Intel厂商
class IntelCPU :public CPU 
{
public:
	virtual void calculate() 
	{
		cout << "Intel的CPU开始计算!" << endl;
	}
};

class IntelVideoCard :public VideoCrad
{
public:
	virtual void display()
	{
		cout << "Intel的显卡开始显示!" << endl;
	}
};

class IntelMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始存储!" << endl;
	}
};
//Lenovo厂商
class LenovoCPU :public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo的CPU开始计算!" << endl;
	}
};

class LenovoVideoCard :public VideoCrad
{
public:
	virtual void display()
	{
		cout << "Lenovo的显卡开始显示!" << endl;
	}
};

class LenovoMemory :public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo的内存条开始存储!" << endl;
	}
};
 
void test01() 
{
	cout << "第一台电脑组装:" << endl;
	//第一台电脑零件
	CPU * intelCPU = new IntelCPU;
	VideoCrad * intelVideoCard = new IntelVideoCard;
	Memory * intelMem = new IntelMemory;

	//创建第一台电脑
	Computer * computer1 = new Computer(intelCPU, intelVideoCard, intelMem);
	computer1->work();
	delete computer1;  //电脑释放了,但电脑的零件没有释放——computer的析构函数

	cout << "----------------" << endl;
	cout << "第二台电脑组装:" << endl;
	//第二台电脑组装
	Computer * computer2 = new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << "----------------" << endl;
	cout << "第三台电脑组装:" << endl;
	//第三台电脑组装
	Computer * computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new LenovoMemory);
	computer3->work();
	delete computer3;
}

int main()
{
	test01();

	system("pause");

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Memorises1999

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

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

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

打赏作者

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

抵扣说明:

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

余额充值