(C++基础随笔) 04 C++多态

多态

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

分为静态多态和动态多态两种

静态多态:函数重载和运算符重载都属于静态多态,复用函数名

动态多态:派生类和虚函数实现

区别

  • 静态多态的函数地址早绑定:编译阶段确定函数地址
  • 动态多态的函数地址晚绑定:运行阶段确定函数地址
class Animal {
public:
	//加上virtual就是虚函数了,地址晚绑定
	virtual void speak() {
		cout << "speaking" << endl;
	}
};
//加上virtual之后的继承叫虚继承,此时称Animal为虚基类
class Cat:public Animal {
public:
	void speak() {
		cout << "miao" << endl;
	}
};

//地址早绑定,所以这里的cat传进去都会调用animal的speak方法
void dospeak(Animal &animal) {
	animal.speak();
}


void test01() {
	Cat cat;
	dospeak(cat);//Animal &animal=cat,c++允许父类和子类的类型转换(不用强转)
}

动态多态的满足条件

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

动态多态的使用

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

如:

void dospeak(Animal &animal) {
	animal.speak();
}

Cat cat;
dospeak(cat);//Animal &animal=cat

多态原理刨析

virtual修饰的成员会在类内存放一个vfptr(虚函数指针),4字节大小

当子类继承父类时,同时也会继承父类的虚函数指针(也就同时继承了虚函数表vftable)

当子类重写父类虚函数时,vftable中的内容会被替换成子类的虚函数地址

计算器项目

普通方法

class Calculator {
public:
	Calculator(int a,int b) {
		m_a = a;
		m_b = b;
	}

	int getResult(string op) {
		if (op == "+") {
			return m_a + m_b;
		}
		if (op == "-") {
			return m_a - m_b;
		}
		if (op == "*") {
			return m_a * m_b;
		}
		if (op == "/") {
			return m_a / m_b;
		}
	}
	int m_a;
	int m_b;
};


void test01() {
	Calculator cal(10, 20);
	string op = "/";
	int result=cal.getResult(op);
	cout << "result:  " << result << endl;

}

普通的方法,如果想扩展新功能,需要修改源码

多态方法

//实现计算器的基类
class Calculator {
public:
	virtual int getResult() {
		return 0;
	}
	int m_a;
	int m_b;
};

//加法计算器
class addCal:public Calculator {
public:
	//重写虚函数
	int getResult() {
		return m_a + m_b;
	}
};

//减法计算器
class downCal :public Calculator {
public:
	//重写虚函数
	int getResult() {
		return m_a - m_b;
	}
};

//乘法计算器
class mulCal :public Calculator {
public:
	//重写虚函数
	int getResult() {
		return m_a * m_b;
	}
};

//除法计算器
class divCal :public Calculator {
public:
	//重写虚函数
	int getResult() {
		return m_a / m_b;
	}
};

void test01() {
	//创建父类指针指向子类对象
	//Calculator* cal = new addCal;
	Calculator* cal = new divCal;
	cal->m_a = 10;
	cal->m_b = 10;
	int result = cal->getResult();
	cout << "result:  " << result << endl;

	delete cal;//记得销毁堆区数据
}

多态的好处

  • 组织结构清晰
  • 可读性强
  • 拓展性可维护性强

纯虚函数和抽象类

父类中的虚函数从来没想要调用,可以改成纯虚函数

//不需要定义函数体,直接=0,就是纯虚函数了
virtual int getResult() = 0;

只要包含了纯虚函数的类,就叫抽象类

抽象类不可实例化!!!

子类必须重写父类的纯虚函数,否则也视为抽象类,不可实例化!!!

多态实现模拟做饮品

//饮品基类
class makeDrink {
public:
	//煮水
	void boil() {
		cout << "煮水ing..." << endl;
	}
	//冲泡
	virtual void make() = 0;
	//倒杯
	void pool() {
		cout << "倒杯ing..." << endl;
	}
	//辅料
	virtual void addition() = 0;

	void proceed() {
		boil();
		make();
		pool();
		addition();

	}

};

class makeCoffe:public makeDrink {
public:
	//冲泡
	virtual void make() {
		cout << "冲泡咖啡ing" << endl;
	}

	//辅料
	virtual void addition() {
		cout << "加入方糖、牛奶ing" << endl;
	}
};

class makeTea:public makeDrink {
public:
	//冲泡
	virtual void make() {
		cout << "冲泡茶叶ing" << endl;
	}

	//辅料
	virtual void addition() {
		cout << "加入柠檬ing" << endl;
	}
};

void test01() {
	makeDrink* maked=new makeCoffe;
	maked->proceed();

	makeDrink* maked1 = new makeTea;
	maked1->proceed();


	delete maked;
	delete maked1;
}

虚析构和纯虚析构

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

解决方法

将父类中的析构函数改为虚析构或纯虚析构

虚析构和纯虚析构共性:

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

虚析构和纯虚析构的区别:

纯虚析构的是抽象类,不可实例化

class Animal {
public:
	Animal() {
		cout << "父类构造" << endl;
	}
	virtual void speak() = 0;

	利用虚析构可以解决父类指针释放子类对象时不干净的问题
	//virtual ~Animal() {
	//	cout << "父类析构" << endl;
	//}
	

	//纯虚析构
	virtual ~Animal() = 0;//注意,纯虚析构需要具体实现!否则会报错
};

Animal::~Animal() {

}

class Cat:public Animal {
public:
	Cat(string name) {
		cout << "子类构造" << endl;
		m_name = name;
	}
	~Cat() {
		cout << "子类析构" << endl;
	}
	void speak() {
		cout << m_name << " miao" << endl;
	}
	string m_name;
};

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

注意

不管是虚析构还是纯虚析构都需要代码实现,否则会报错

虚拟组装电脑的小Demo

class CPU {
public:
	virtual void calcu() = 0;
};

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

class Memo {
public:
	virtual void storage() = 0;
};

class IntelCPU :public CPU{
public:
	IntelCPU() {
		cout << "using  IntelCPU" << endl;

	}
	void calcu() {
		cout << "IntelCPU正在计算" << endl;
	}
};
class IntelVideoCard :public VideoCard {
public:
	IntelVideoCard() {
		cout << "using  IntelVideoCard" << endl;

	}
	void display() {
		cout << "IntelVideoCard正在显示" << endl;
	}
};

class NvidiaVideoCard :public VideoCard {
public:
	NvidiaVideoCard() {
		cout << "using  NvidiaVideoCard" << endl;

	}
	void display() {
		cout << "NvidiaVideoCard正在显示" << endl;
	}
};

class SamsungMemo:public Memo {
public:
	SamsungMemo() {
		cout << "using  SamsungMemo" << endl;

	}
	void storage() {
		cout << "SamsungMemo正在储存" << endl;
	}
};


class Computer {
public:
	Computer(CPU* cpu,VideoCard* videocard,Memo* memo) {
		m_cpu = cpu;
		m_videocard = videocard;
		m_memo = memo;
	}

	void processing() {
		m_cpu->calcu();
		m_videocard->display();
		m_memo->storage();
	}

	~Computer() {
		if (m_cpu != NULL) {
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_videocard != NULL) {
			delete m_videocard;
			m_videocard = NULL;
		}
		if (m_memo != NULL) {
			delete m_memo;
			m_memo = NULL;
		}
	}
private:
	CPU* m_cpu;
	VideoCard* m_videocard;
	Memo* m_memo;
};


void test01() {
	CPU* intelcpu = new IntelCPU;
	VideoCard* intelvc = new IntelVideoCard;
	Memo* sammemo = new SamsungMemo;
	Computer pc1(intelcpu,intelvc,sammemo);//这里需要传入指向子类对象的父类指针
	pc1.processing();

	delete pc1;
}

int main() {
	test01();

	system("pause");
	return 0;
}

特别注意这个写法!

class Computer {
public:
	Computer(CPU* cpu,VideoCard* videocard,Memo* memo) {//如果构造函数里传入的是指针,就可以随时改变该指针指向的对象
		m_cpu = cpu;
		m_videocard = videocard;
		m_memo = memo;
	}

	void processing() {
		m_cpu->calcu();
		m_videocard->display();
		m_memo->storage();
	}
private:
	CPU* m_cpu;
	VideoCard* m_videocard;
	Memo* m_memo;
};



Computer pc1(intelcpu,intelvc,sammemo);//这里需要传入指向子类对象的父类指针

文件操作

头文件

三大类

  1. ofstream 写
  2. ifstream 读
  3. fstream 读写

写文件

  1. 包含头文件
  2. 创建流对象 ofstream ofs
  3. 打开文件 ofs.open(“路径”,打开方式)
  4. 写数据 ofs<<“数据”
  5. 关闭文件 ofs.close()

打开方式

ios::in 为读文件打开文件
ios::out 为写文件打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

打开方式可以**用|**配合使用

读文件

if(!ifs.is_open()){
	//文件未打开
    return;
}


int main() {

	//1.引入头文件
	//2.创建ifstream对象
	ifstream ifs;
	//3.打开文件
	ifs.open("test.txt", ios::in);
	if (!ifs.is_open()) {
		cout << "打开失败" << endl;
		return -1;
	}
	//4.读文件
	第一种读文件
	//char buf[1024] = { 0 };
	//while (ifs >> buf) {
	//	cout << buf << endl;
	//}

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

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


	//5.关闭文件
	ifs.close();
	system("pause");
	return 0;
}

二进制读写

class Person {
public:
	char m_name[64];
	int m_age;

};

int main() {
	二进制写文件
	//ofstream ofs;
	//ofs.open("person.txt", ios::out|ios::binary);
	//Person p1 = { "zhangsan",20 };
	//ofs.write((const char* )&p1,sizeof(Person));
	//ofs.close();

	//二进制读文件
	ifstream ifs;
	ifs.open("person.txt", ios::in | ios::binary);
	if (!ifs.is_open()) {
		return -1;
	}
	Person p2;
	ifs.read((char*)&p2,sizeof(Person));
	cout << p2.m_name << "   " << p2.m_age << endl;

	system("pause");
	return 0;
}

注意

  • 在读文件时,需要强转成const char*
  • 在写文件时需要强转成char*

你可能感兴趣的:(Coding,c++)