类与对象(二)

继承

继承的基本语法

class 子类(派生类):继承方式 父类(基类)

示例代码:

#include<iostream>
#include<string>
using namespace std;

//继承语法:class 子类:继承方式 父类

//公共页面
class BasePage
{
public:
	void header()
	{
		cout << "首页、公开课、登录、注册" << endl;
	}
	void footer()
	{
		cout << "帮助中心、交流合作、站内地图" << endl;
	}
	void left()
	{
		cout << "JAVA、Python、C++" << endl;
	}
};
//JAVA页面
class Java :public BasePage
{
public :
	void content()
	{
		cout << "Java学科视频" << endl;
	}
};

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

void test01()
{
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();

	CPP cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

继承方式

继承方式和访问权限控制相同,分为三种:
1.公共继承(public)
2.保护继承(protected)
3.私有继承(private)
大致继承关系如下:
image
可以大致理解为继承方式为子类从父类手中拿过来的起步权限。
对于父类中的private变量,子类无法获取。
对于public和protected变量,如果以public方式继承,则维持原样,如果以protected方式继承,则在子类中都变为protected,如果以private方式继承,则都变为private。相当于继承方式是他们的上限,和他们自身在父类中的权限取min即可。
示例代码:

#include<iostream>
#include<string>
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;  //不可访问。
	}
};

class Son2 :protected Base
{
public:
	void func()
	{
		m_A = 100; //变为保护权限
		m_B = 100;
		//m_C = 100; 私有成员不可访问
	}
};

class Son3 :private Base
{
public:
	void func()
	{
		m_A = 100;
		m_B = 100;
		//m_C = 100; 私有成员不可访问
	}
};

void test01()
{
	Son1 s1;
	s1.m_A = 10;
	//s1.m_B = 10;//保护权限不可访问

	Son2 s2;
	//s2.m_A = 10;  //保护权限不可访问

	Son3 s3;
	//s3.m_A = 100;//私有权限不可访问
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

继承中的对象模型

继承的子类继承父类的所有属性,包括私有的属性,私有属性只不过被编译器隐藏,即编译器不允许你访问,但人家确实继承下来了。
示例代码:

#include<iostream>
#include<string>
using namespace std;

class Base 
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son :public Base
{
public:
	int m_D;
};

void test01()
{
	//ans = 16
	// 父类中所以非静态成员属性都会被子类继承下去
	// 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下了。
	cout << "size of Son = " << sizeof(Son) << endl;
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

之后采用开发人员命令提示符查看对象模型
在黑框中首先进入项目所在位置,之后输入以下代码:

c1 /d1 reportSingleClassLayouy类名 文件名

image

继承中的构造和析构顺序

当有继承时,顺序是先有父类构造,再子类构造,之后析构的话先有子类析构之后再有父类析构。
其实也挺好理解的,现有父亲再有儿子,然后按照栈式的方式取管理局部变量,所以先析构子类再析构父类。

#include<iostream>
#include<string>
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()
{
	Son son;
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

继承中同名成员的处理方式

image
补充一点:如果子类中出现和父类同名的成员函数,那么子类会隐藏父类中所有的同名函数(包括重载的)。如果想调用父类的函数,那么要么子类没有同名函数,直接调用,要么加作用域。

#include<iostream>
#include<string>
using namespace std;

class Base 
{
public:
	Base()
	{
		m_A = 100;
	}

	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 = 200;
	}

	void func()
	{
		cout << "这是Son的func()函数调用" << endl;
	}

	int m_A;
};


void test02()
{
	Son son;
	//直接调用为子类的函数
	son.func();
	//调用父类的同名函数需要加作用域
	son.Base::func();
	//如果子类中出现和父类同名的成员函数,那么子类会隐藏父类中所有的同名函数(包括重载的)
	//如果想调用父类的函数,那么要么子类没有同名函数,直接调用,要么加作用域。
	son.Base::func(100);
}

void test01()
{
	Son son;

	cout << "son.m_A = " << son.m_A << endl;
	//如果通过子类对象访问父类同名变量的话,需要加作用域
	cout << "Base.m_A = " << son.Base::m_A << endl;

	test02();
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

继承中同名的静态成员处理方法

同名静态成员处理方式和非静态成员处理方式完全相同。
无同名,子类直接访问父类的成员。
有同名,子类直接访问的是子类,像访问父类的加作用域。

多继承

image
实际开发中不推荐使用,所以点到为止。

多态

多态的基本概念

image
示例代码如下:

#include<iostream>
#include<string>
using namespace std;

//动态多态满足条件:
//1.有继承关系
//2.子类重写父类的虚函数

//动态多态使用
//父类的指针或者引用 来指向子类的对象。

class Animal
{
public:
	//函数加上virtual关键词,为虚函数
	//虚函数在编译阶段不确定函数地址,在执行阶段才确定函数地址
	virtual void speak()
	{
		cout << "动物再叫!!!" << endl;
	}
};

class Sheep: public Animal
{
public:
	//子类重写父类的虚函数
	void speak()
	{
		cout << "Sheep在叫!!" << endl;
	}
};

class Cat : public Animal
{
public:
	//子类重写父类的虚函数
	void speak()
	{
		cout << "Cat在叫!!" << endl;
	}
};

//这里采用父类的指针或者引用接受子类的对象,可以多态。
void Speak(Animal &animal)
{
	animal.speak();
}

void test01()
{
	Sheep sheep;
	Speak(sheep);

	Cat cat;
	Speak(cat);
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

关于运行时多态的内部原理如下:
image
具体的可以这样理解:
首先正常的成员函数是在代码区,每次类的对象调用时正常从代码区调用即可,所以是在编译时就确定了这个函数从哪调用,没办法更改。
而virtual关键字,使得函数称为虚函数,通过增加vfptr(virtual function pointer虚函数(表)指针)和vftable(virtual function table 虚函数表),使得多态称为可能。
当父类Animal中的speak增加了virtual函数指针后,Animal类中就多了一个vfptr的指针,指向了vftable,而vftable里存储的就是Animal的speak函数的地址。
之后子类Cat继承父类Animal,同样也继承了其中的vfptr和vftable,但当子类重写父类的speak方法时,他会在vftable中进行一个覆盖的操作,将原本继承过来的Animal::speak的地址替换成Cat::speak的地址,这样的话,每次运行从对象的虚函数表中取出函数地址进行执行,这样多态便运行了。

正常成员函数的对象模型:
image
虚函数的对象模型:
image
通过两者的对比,可以看出增加virtual关键词,对象模型中增加了vfptr的指针,和vftable。

子类不重写父类的虚函数时的对象模型:
image

子类重写父类的虚函数时的对象模型:
image
通过对比,发现子类重写父类虚函数时,将vftable中的地址替换成了自己函数的地址。

image

纯虚函数和抽象类

image
示例代码:

#include<iostream>
#include<string>
using namespace std;

class Base
{
public:
	
	//纯虚函数
	//只要有一个纯虚函数,该类就为抽象类
	//抽象类特点:
	//1.无法实例化对象
	//2.继承的子类要么重写纯虚函数,要么为抽象类。
	virtual void func() = 0;
};

class Son :public Base
{
public:
	//子类重写父类的纯虚函数
	void func()
	{
		cout << "Son的func函数调用" << endl;
	}
};

void test01()
{
	//Base b;  Error!抽象类无法实例化对象。
	//new Base;  同理
	//采用多态的形式调用,即父类的指针指向子类对象
	Base* base = new Son;
	base->func();

}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

虚析构和纯虚析构

image
多态在使用时,若子类的属性中有用到堆区的内存,使用多态时,采用的是子类的匿名对象给父类的的引用或者指针,这样的话最终会导致无法调用到子类的析构函数,从而导致子类的堆区内存无法释放。
示例代码如下:

#include<iostream>
#include<string>
using namespace std;

class Base
{
public:
	
	Base()
	{
		cout << "调用了Base的构造函数" << endl;
	}

	~Base()
	{
		cout << "调用了Base的析构函数" << endl;
	}

	//纯虚函数
	virtual void func() = 0;
};

class Son :public Base
{
public:
	//子类重写父类的纯虚函数
	void func()
	{
		cout << "Son的func函数调用" << endl;
	}

	Son(int A)
	{
		m_A = new int(A);
		cout << "调用了Son的构造函数,开辟了堆区空间" << endl;
	}

	~Son()
	{
		if (m_A != NULL)
		{
			delete m_A;
		}
		m_A = NULL;

		cout << "调用了Son的析构函数,释放了m_A的堆区空间" << endl;

	}

	int* m_A;
};

void test01()
{
	//用父类指针接受子类的匿名对象
	Base* base = new Son(10);
	base->func();
	delete base;
	//释放堆区空间
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

运行结果如下:
image
正常的继承的话,会先调用Base的构造,再调用Son的构造,之后调用Son的析构,最后调用Base的构造。但多态由于静态绑定的缘故,导致父类的指针无法调用子类的析构函数,所以就会导致子类的成员中开辟堆区内存泄露。
这时的解决办法就是将父类的析构函数也虚化即可,这就产生了虚析构和纯虚析构。
虚析构就是在析构函数的基础上加上了virtual,子类写上自己的析构函数,这样就会先调用子类的析构函数再调用父类的析构函数了。
结果如下:
image
代码改动如下:

class Base
{
public:

	Base()
	{
		cout << "调用了Base的构造函数" << endl;
	}

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

	//纯虚函数
	virtual void func() = 0;
};

这也产生了纯虚析构,类似纯虚函数(实际上析构函数也只是特殊的函数罢了),但当写上去就会有很大的问题,因为纯虚析构意味着在当前这个类中没有具体实现,但是析构函数必须实现!!!
所以这只能在类外实现,具体示例如下:

代码如下:

class Base
{
public:

	Base()
	{
		cout << "调用了Base的构造函数" << endl;
	}

	virtual ~Base() = 0;

	//纯虚函数
	virtual void func() = 0;
};

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

多态的实际应用

image
实际代码如下:

#include<iostream>
#include<string>
using namespace std;

class CPU
{

public:

	virtual void calculate() = 0;

};

class InterCpu: public CPU
{
public:
	void calculate()
	{
		cout << "因特尔的CPU正在工作" << endl;
	}
};

class LenovoCpu : public CPU
{
public:
	void calculate()
	{
		cout << "联想的CPU正在工作" << endl;
	}
};


class Memory
{
public:

	virtual void storage() = 0;
};

class InterMemoey : public Memory
{
public:
	void storage()
	{
		cout << "因特尔的内存正在工作" << endl;
	}
};

class LenovoMemoey : public Memory
{
public:
	void storage()
	{
		cout << "联想的内存正在工作" << endl;
	}
};

class VideoCard
{
public:

	virtual void display() = 0;
};

class InterVideoCard : public VideoCard
{
public:
	void display()
	{
		cout << "因特尔的显卡正在工作" << endl;
	}
};

class LenovoVideoCard : public VideoCard
{
public:
	void display()
	{
		cout << "联想的显卡正在工作" << endl;
	}
};

class Computer
{
public:

	void Work()
	{
		cpu->calculate();
		memory->storage();
		vdcard->display();
	}

	Computer(CPU* cpu, Memory* memory, VideoCard* vdcard)
	{
		this->cpu = cpu;
		this->memory = memory;
		this->vdcard = vdcard;
	}

	~Computer()
	{
		if(cpu != NULL)
		{
			delete cpu;
		}
		if (memory != NULL)
		{
			delete memory;
		}
		if (vdcard != NULL)
		{
			delete vdcard;
		}
		cpu = NULL;
		memory = NULL;
		vdcard = NULL;
	}
private:

	CPU* cpu;
	Memory* memory;
	VideoCard* vdcard;

};

void test01()
{
	Computer computer1(new InterCpu, new InterMemoey, new InterVideoCard);
	computer1.Work();

	cout << "-----------------------------------------" << endl;

	Computer computer2(new LenovoCpu, new LenovoMemoey, new LenovoVideoCard);
	computer2.Work();
}  

int main(){
	
	test01();

	system("pause");
	return 0;
}

文件操作

文件操作基本概念

image

写文件

image
image
注意: 文件打开方式可以配合使用,中间用|分隔。
代码实例:

	//1.包含头文件

	//2.创建流对象
	ofstream ofs;

	//3.打开文件,若文件不存在,则自动创建文件。
	ofs.open("test.txt",ios::out);

	//4.用输出流进行输出
	ofs << "姓名:张三 " << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;

	//5.关闭文件
	ofs.close();

读文件

示例代码如下:

//1.包含头文件

//2.创建流对象
ifstream ifs;
//3.打开文件
ifs.open("test.txt",ios::in);
//判断文件是否打开成功
if (!ifs.is_open())
{
	cout << "文件打开失败" << endl;
}
//4.读数据-四种方法

//第一种
char buf[1024] = { 0 };
while (ifs >> buf)
{
	cout << buf << endl;
}

//第二种
char buf[1024] = { 0 };
while (ifs.getline(buf, 1024))
{
	cout << buf << endl;
}

//第三种
string buf;
while (getline(ifs,buf))
{
	cout << buf << endl;
}

//第四种
char c;
while ( (c = ifs.get()) != EOF )
{
	cout << c;
}

//5.关闭文件
ifs.close();

二进制写文件

image
示例代码如下:

//1.包含头文件

//2.常见对象
ofstream ofs;  

//3.打开文件
ofs.open("Person.txt", ios::out | ios::binary);

//两步可以进行合并
//ofstream ofs("Person.txt", ios::out | ios::binary);

//4.写文件
Person p = { "张三", 18 };
ofs.write((const char * )&p, sizeof(Person));

//4.关闭文件
ofs.close();

二进制读文件

image
示例代码如下:

//1.包含头文件

//2.常见对象
ifstream ifs;  

//3.打开文件
ifs.open("Person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
	cout << "文件打开失败!" << endl;
	return ;
}

//4.写文件
Person p;
ifs.read((char*)&p, sizeof(Person));

cout << "姓名为:" << p.m_Name << ' ' << "年龄为" << p.m_Name << endl;

//4.关闭文件
ifs.close();

类与对象综合运用-职工管理系统

posted @ 2024-11-15 09:21  逆天峰  阅读(11)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//