一、运算符重载
运算符重载:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
1、加号运算符重载
作用:实现两个自定义数据类型相加的运算
方法1、成员函数重载
class Person {
public:
	Person operator+(Person& p) {
		Person temp;
		temp.m_a = this->m_a + p.m_a;
		temp.m_b = this->m_b + p.m_b;
		return temp;
	}
	int m_a;
	int m_b;
};
void test() {
	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	Person p2;
	p2.m_a = 10;
	p2.m_b = 20;
	Person p3 = p1 + p2;	//等价于p1.operator+(p2)
	cout << "p3.m_a = " << p3.m_a << endl;
	cout << "p3.m_b = " << p3.m_b << endl;
}
int main() {
	test();
	system("pause");
	return 0;
}
方法2、全局函数重载
//定义类
class Person {
public:
	int m_a;
	int m_b;
};
//全局函数重载
Person operator+(Person& p1,Person& p2) {
	Person temp;
	temp.m_a = p1.m_a + p2.m_a;
	temp.m_b = p1.m_b + p2.m_b;
	return temp;
}
void test() {
	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	Person p2;
	p2.m_a = 10;
	p2.m_b = 20;
	Person p3 = p1 + p2;	//等价于p1.operator+(p2)
	cout << "p3.m_a = " << p3.m_a << endl;
	cout << "p3.m_b = " << p3.m_b << endl;
}
int main() {
	test();
	system("pause");
	return 0;
}
总结:
1、成员函数重载本质调用:Person p3 = p1.operator+(p2)
2、全局函数重载本质调用:Person p3 = operator(p1,p2)
3、运算符重载,也可以发生函数重载
2、左移运算符重载
作用:可以输出自定义数据类型

所以, 只能利用全局函数重载左移运算符
//左移重载
class Person {
public:
	int m_a;
	int m_b;
};
//全局函数重载
ostream& operator<<(ostream& cout, Person& p) {
	cout <<"p.m_a = "<<p.m_a <<"p.m_a = "<<p.m_b << endl;;
	return cout;
}
//测试
void test() {
	Person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	
	cout << p1 << endl;
}
int main() {
	test();
	system("pause");
	return 0;
}
扩展:如果我们需要打印的成员属性是私有的呢?全局函数无法访问,怎么重载呢?
答:对全局函数使用友元
//左移重载
class Person {
public:
	//声明友元
	friend ostream& operator<<(ostream& cout, Person& p);
	//构造函数赋初值
	Person(int a,int b) {
		m_a = a;
		m_b = b;
	}
private:
	int m_a;
	int m_b;
};
//全局函数重载
ostream& operator<<(ostream& cout, Person& p) {
	cout <<"p.m_a = "<<p.m_a <<" p.m_a = "<<p.m_b << endl;;
	return cout;
}
//测试
void test() {
	Person p1(10,20);//调用有参构造函数
	cout << p1 << endl;
}
int main() {
	test();
	system("pause");
	return 0;
}
3、递增运算符重载
作用:通过重载,实现自己的数据类型递增
前置++:
//++运算符重载
class Person {
public:
	//前置++
	Person& operator++() {
		++m_a;
		return *this;
	}
	int m_a = 0;
};
ostream& operator<<(ostream& cout,Person p) {
	cout << p.m_a << endl;
	return cout;
}
//测试
void test01() {
	Person p;
	p.m_a = 100;
	cout << ++p << endl; //前置++
}
int main() {
	test01();
	system("pause");
	return 0;
}
后置++:
重载后置递增,需要用int代表占位参数,用于区分前置和后置递增
//++运算符重载
class Person {
public:
	//后置++
	Person operator++(int) {
		Person temp = *this;	//记录操作前的结果
		m_a++;
		return temp;
	}
	int m_a = 0;
};
ostream& operator<<(ostream& cout,Person p) {
	cout << p.m_a << endl;
	return cout;
}
//测试
void test02() {
	Person p1;
	p1.m_a = 100;
	cout << "后置++:" << p1++ << endl;//后置++ 
	cout << "后置++:" << p1 << endl;
}
int main() {
	test02();
	system("pause");
	return 0;
}
总结:
1、前置++,返回对象自身(*this),且是用引用的方式返回
2、后置++,需要记录操作前的结果,返回记录的结果,并且是用值的方式返回(因为用于记录操作前的结果是一个临时变量,返回引用会出错)
3、后置++,在参数上需要用int 作为占位参数
4、赋值运算符重载
c++编译器至少给一个类添加4个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行值拷贝
4、赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
示例:
class Person {
public:
	Person(int age) {
		m_a = new int(age);
	}
	~Person() {
		if (m_a != NULL) {
			delete m_a;
			m_a = NULL;
		}
	}
	Person& operator=(Person& p) {
		//系统默认浅拷贝
		//this->m_a = p.m_a;	
		//深拷贝
		if (this->m_a = NULL) {
			delete m_a;
			m_a = NULL;
		}
		this->m_a = new int(*p.m_a);
		//返回自身
		return *this;
	}
	int* m_a = NULL;
};
//测试
void test01() {
	Person p(10);
	Person p2(20);
	Person p3(30);
	cout << "赋值前:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl;
	p = p2 = p3;
	cout << "赋值后:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

5、关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行比较
示例:
//关系运算符重载	
//1、==  2、 !=
class Person {
public:
	Person(string name,int age) {
		m_Name = name;
		m_Age = age;
	}
	//重载 ==
	int operator==(Person& p){
		if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {
			return 1;
		}
		else
			return 0;
	}
	//重载 !=
	int operator!=(Person& p){
		if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) {
			return 1;
		}
		else
			return 0;
	}
	string m_Name;
	int m_Age;
};
//测试
void test01() {
	Person p("Tom",18);
	Person p2("Tom", 18);
	Person p3("Tom", 18);
	Person p4("Jiu", 18);
	
	if (p == p2) {
		cout << "p 和 p2 相等!" << endl;
	}
	else
		cout << "p 和 p2 不相等!" << endl;
	if (p3 != p4) {
		cout << "p3 和 p4  不相等!" << endl;
	}
	else
		cout << "p3 和 p4 相等!" << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

6、函数调用运算符重载
函数调用运算符 () 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
示例:
//函数调用运算符重载
//打印类
class myPrint {
public:
	void operator()(string name) {
		m_Name = name;
		cout << m_Name << endl;
	}
	string m_Name;
};
void test() {
	myPrint mp;
	mp("Hello World");
}
int main() {
	test();
	system("pause");
	return 0;
}
二、继承
继承是面向对象的三大特征之一
在定义一些类的时候,如果下级别的成员除了拥有上一级的共性,还有自己的特性,这时候我们就可以选择继承,来减少重复代码
1、继承的基本语法
 class 子类 :继承类型 父类
例如:
class son : public father{
	//成员
};注意事项:
1、子类又称:派生类,父类又称:基类
2、子类的成员包括从父类继承来的,以及自己增加的成员
3、继承过来的表现共性,增加的表现个性
2、继承方式
继承方式分三种:
1、公有继承(public):当一个子类公有继承父类时,父类的公有成员也是子类的公有成员,父类的保护成员也是子类的保护成员,父类的私有成员不能直接被子类访问,但是可以通过调用父类的公有和保护成员来访问。
2、保护继承(protected): 当一个子类保护继承父类时,父类的公有和保护成员将成为子类的保护成员。
3、私有继承(private):当一个子类私有继承父类时,父类的公有和保护成员将成为子类的私有成员。

3、继承中的对象模型
问题:从父类继承过来的成员,那些属于子类对象?
答:在父类中的非静态成员属性都会被子类继承,私有属性也会继承,只是无法访问。会被编译器隐藏。

扩展:利用开发人员命令提示工具查看对象模型
1、跳转盘符:E:(存放文件的盘符)
2、跳转文件路径: cd 具体路径下
3、查看命令:cl /d1 reportSinglcleClassLayout类名 "文件名"

4、继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序谁先谁后?
答:
1、先构造父类,在构造子类
2、析构与构造相反

可以看见,子类父类的顺序,同,一个类中的类成员顺序是一致的
5、继承同名成员处理方式
问题:当子类和父类出现同名的成员,如何通过子类或父类中同名的数据?
答:访问子类同名成员,直接访问;访问父类同名成员,需要加作用域。

注意事项:
1、如果子类中出现了和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,即,不可直接调用重载


解决方法:调用时,增加作用域即可

6、继承同名静态成员处理
静态成员和非静态成员出现同名时,处理方法一致
1、访问子类同名成员,直接访问
2、访问父类同名成员,需要加作用域

7、多继承语法
c++中允许一个类继承多个类
语法:class 子类 : 继承方式 父类1,继承方式 父类2...
多继承可能回引发父类中同名成员出现,需要加作用域区分,实际开发中,不建议使用
//多父类继承
class Base1 {	//父类1
public:
	Base1() {
		m_b1 = 100;
	}
	int m_b1;
};
class Base2 {	//父类2
public:
	Base2() {
		m_b2 = 200;
	}
	int m_b2;
};
class Base3 {	//父类3
public:
	Base3() {
		m_b3 = 300;
	}
	int m_b3;
};
//子类多继承
class Son : public Base1,public Base2, public Base3{	
public:
	int m_s1 = 400;
};
void test() {
	Son s;
	cout << "多继承子类字节数:" << sizeof(s) << endl;
	cout << "Base1 = " << s.Base1::m_b1 << endl;;
	cout << "Base2 = " << s.Base2::m_b2 << endl;;
	cout << "Base3 = " << s.Base3::m_b3 << endl;;
	cout << "Son = " << s.m_s1 << endl;;
}
int main() {
	test();
	system("pause");
	return 0;
}
8、菱形继承
概念:
1、两个子类继承同一个父类
2、又有某个类同时继承两个子类
3、这种继承被称为菱形继承,或砖石继承

菱形继承的问题:
1、羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。
答:加以作用域区分!
2、羊驼继承自动物的数据继承了两份,导致资源浪费
答:利用虚继承(在继承之前加上关键字virtual,变为虚继承),可以解决!
//菱形继承
class BASE {	//父父类
public:
	int m_a;
};
//父类1
class Base1 :virtual public BASE {};
//父类2
class Base2 :virtual public BASE {};
//子类
class Son : public Base1, public Base2{};
void test() {
	Son s;
	s.Base1::m_a = 100;
	s.Base2::m_a = 200;
	cout << "父类1,base1 :" << s.Base1::m_a << endl;
	cout << "父类2,base3 :" << s.Base2::m_a << endl;
	cout << "子类:Son :" << s.m_a << endl;
}
int main() {
	test();
	system("pause");
	return 0;
}
虚继承原理:子类继承父类的两个指针,指针通过偏移量,找到同一份数据。

三、多态
1、多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类:
1、静态多态: 函数重载 和 运算符重载 属于静态多态,复用函数名
2、动态多态: 子类和虚函数实现运行时多态
区别:
1、静态多态的函数地址早绑定 - 编译阶段确定函数地址
2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址
示例:
//多态
//动物类
class Animal {	
public:
	//虚函数,用virtual关键字修饰的函数
	virtual void speek() {	
		cout << "动物说话..." << endl;
	}
};
//猫类
class Cat : public Animal {
	void speek() {
		cout << "猫在说话..." << endl;
	}
};
//狗类
class Dog : public Animal {
	void speek() {
		cout << "狗在说话..." << endl;
	}
};
void doSpeek(Animal& ani) {	//父类和子类类型可以相互转换
	ani.speek();
}
void test() {
	Cat c;
	Dog d;
	//调用执行说话函数
	doSpeek(c);
	doSpeek(d);
	
}
int main() {
	test();
	system("pause");
	return 0;
}
总结:
动态多态的满足条件:
1、有继承关系
2、子类重写父类的虚函数
区分重写和重载:
重写:函数返回值类型,函数名,参数列表完全相同
重载:函数的参数的数量,类型或位置不同
动态多态的使用:
父类的指针或引用指向子类对象

2、深入剖析多态原理:
Animal类内部结构:




Cat类内部结构:
1、未重写父函数



2、重写父函数后
当子类重写父类虚函数后,子类的虚函数表内部会替换成子类的虚函数地址,父类不发生改变。



3、纯虚函数和抽象类
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
但类中有了纯虚函数,这个类就被称为抽象类
抽象类特点:
1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
示例:

4、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}


示例:
//虚析构和纯虚析构
class Base {
public:
	Base() {
		cout << "Base构造函数调用" << endl;
	}
	virtual ~Base() = 0;
	virtual void func() = 0;
};
Base:: ~Base(){
	cout << "Base析构函数调用" << endl;
}
class Son :public Base {
public:
	
	Son(string name) {
		m_name = new string(name);
		cout << "son构造函数调用" << endl;
	}
	~Son() {
		
		cout << "son析构函数调用" << endl;
		if (m_name != NULL) {
			delete m_name;
			m_name = NULL;
		}
	}
	void func() {
		cout << *m_name << endl;
	}
	string* m_name;
};
void test01() {
	
	Base* b = new Son("TOM");
	b->func();
	delete b;
}
int main() {
	test01();
	system("pause");
	return 0;
}使用虚析构或纯虚析构前:

使用虚析构或纯虚析构后:

四、文件操作
文件操作头文件:<fstream>
操作文件的三大流:
1、ofstream:写操作
2、ifstream:读操作
3、fstream:读写操作
1、文本文件
写文件
步骤:
1、包含头文件:#include<fstream>
2、创建流对象:ofstream ofs;
3、打开文件:ofs.open("文件路径",打开方式);
4、写数据:ofs<<"写入的数据"
5、关闭文件:ofs.close();
文件打开方式:
| 打开方式 | 解释 | 
| ios::in | 为读文件而打开文件 | 
| ios::out | 为写文件而打开文件 | 
| ios::ate | 初始位置:文件尾 | 
| ios::app | 追加方式写文件 | 
| ios::trunc | 如果文件存在先删除,再创建 | 
| ios::binary | 二进制方式 | 
注意:文件的打开方式可以配合使用,用 | (或)操作符
例如:用二进制方式写文件
ios::binary | ios::out
示例:
#include<fstream>	//1、包含头文件
void wfile() {
	
	//2、流对象
	ofstream ofs;
	//3、打开文件
	ofs.open("test.txt",ios::out);
	//4、写文件
	ofs << "大鹏一日同风起" << endl;
	ofs << "扶摇直上九万里" << endl;
	ofs << "有约不来过夜半" << endl;
	ofs << "闲敲棋子落灯花" << endl;
	//关闭文件
	ofs.close();
}
int main() {
	wfile();
	system("pause");
	return 0;
}
读文件:
步骤:
1、包含头文件:#include<fstream>
2、创建流对象:ifstream ifs;
3、打开文件并判断是否成功打开:ifs.open("文件路径",打开方式);
4、读数据:四种方式读取
5、关闭文件:ifs.close();
四种读取方式:




不推荐使用第四种,其余的可根据个人喜好使用,个人推荐第三种~
示例:
#include<fstream>	//1、包含头文件
#include <string>
void rfile() {
	//2、流对象
	ifstream ifs;
	//3、打开文件,判断是否成功
	ifs.open("test.txt", ios::in);
	//判断
	if (! ifs.is_open()) {
		cout << "文件打开失败" << endl;
		return;
	}
	//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;
	}
	////第四种
	//char c;
	//while ((c = ifs.get()) != EOF) {
	//	cout << c;
	//}
	//关闭文件
	ifs.close();
}
int main() {
    
	rfile();
	system("pause");
	return 0;
}
2、二进制文件
打开方式指定为:ios::binary
写文件:
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include<fstream>	//1、包含头文件
#include <string>
//二进制文件
class Person {
public:
	char Name[10];	//姓名
	int age;//年龄
};
//写二进制文件
void binWfile() {
	Person p = { "张三",18};
	//1、定义头文件
	//2、流对象
	ofstream ofs("Person.txt", ios::out | ios::binary);
	//3、打开文件
	//ofs.open("Person.txt", ios::in | ios::binary);
	//4、写文件
	ofs.write((const char*) & p,sizeof(Person));
	//5、关闭文件
	ofs.close();
}
int main() {
	binWfile();
}
读文件:
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include<fstream>	//1、包含头文件
//二进制文件
class Person {
public:
	char Name[10];	//姓名
	int age;//年龄
};
//读二进制文件
void binRfile() {
	Person p;
	//1、定义头文件
	//2、流对象
	ifstream ifs("Person.txt", ios::in | ios::binary);
	if (!ifs.is_open()) {
		cout << "二进制文件打开失败" << endl;
		return;
	}
	//3、打开文件
	//ofs.open("Person.txt", ios::in | ios::binary);
	//4、读文件
	ifs.read((char*)&p, sizeof(Person));
	//ofs.write((const char*) & name, sizeof(Person));
	cout << "姓名:" << p.Name << " 年龄:" << p.age << endl;
	//5、关闭文件
	ifs.close();
}
int main() {
	binRfile();
}










