运算符重载和文件操作
一、运算符重载
- 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
- 运算符重载的位置:可以在全局函数中实现或者在成员函数中实现。
- 运算符重载可以发生函数重载。
- 对于内置的数据类型的表达式的运算符是不可能改变的。
- 不要滥用运算符重载。
1. 加号运算符重载
加号运算符重载用于实现两个自定义数据类型相加的运算。
class Person {
public:
Person() {};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式本质上相当于Person p3 = p2.operator+(p1)
//全局函数方式本质上相当于Person p3 = operator+(p1,p2)
Person p3 = p2 + p1;
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main() {
test();
system("pause");
return 0;
}
2. 左移运算符重载
- 左移运算符重载可以输出自定义数据类型。重载左移运算符配合友元可以实现输出自定义数据类型。
- 左移运算符重载只能用全局函数实现,无法用成员函数实现,因为成员函数实现的重载简化写法为
p << cout
而不是cout << p
。 - 注意:
ostream
对象只能有一个。 - 个人理解:运算符重载的意思其实就是该运算符在碰到【对此运算符进行重载的类的对象】以后,能够以预先定义的方式继续运行。
class Person {
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}
private:
int m_A;
int m_B;
};
//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //链式编程
}
int main() {
test();
system("pause");
return 0;
}
3. 递增运算符重载
- 递增运算符重载实现自己的整型数据。
- 前置递增返回引用,后置递增返回值。
- 后++需要在参数列表里面加上一个
int
,int
代表一个占位参数。
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
4. 赋值运算符重载
C++编译器默认会给类添加四个函数,除了构造函数、析构函数和拷贝构造函数外,还会添加赋值运算符operator=
,用来对属性进行值拷贝。此时,如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
如果重载operator=
函数,保险起见,最好在深拷贝之前先进行释放。
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person &p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
test01();
//int a = 10;
//int b = 20;
//int c = 30;
//c = b = a;
//cout << "a = " << a << endl;
//cout << "b = " << b << endl;
//cout << "c = " << c << endl;
system("pause");
return 0;
}
5. 关系运算符重载
关系运算符重载可以让两个自定义类型对象进行对比操作。
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
6. 函数调用运算符重载
- 函数调用运算符
()
也可以重载。 - 由于重载后使用的方式非常像函数的调用,因此称为仿函数。
- 仿函数没有固定写法,非常灵活。
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
//匿名对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
二、文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。C++中对文件操作需要包含头文件 fstream
。
文件类型分为两种:
- 文本文件:文件以文本的ASCII码形式存储在计算机中。
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。
文件的打开方式如下(后续会说明如何使用):
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意:文件打开方式可以配合使用,利用|操作符。比如:用二进制方式写文件ios::binary | ios:: out
。
1. 文本文件
1.1 写文件
- 包含头文件:
#include <fstream>
- 创建流对象:
ofstream ofs
; - 打开文件:
ofs.open("文件路径",打开方式);
- 写数据:
ofs << "写入的数据";
- 关闭文件:
ofs.close();
注意:如果文件路径没有说明具体路径,则会放在当前源文件的同级目录下。
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
1.2 读文件
读文件步骤如下:
- 包含头文件:
#include <fstream\>
- 创建流对象:
ifstream ifs;
- 打开文件(需要额外判断文件是否打开成功):
ifs.open("文件路径",打开方式);
- 读数据四种方式读取
- 关闭文件:
ifs.close();
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//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() {
test01();
system("pause");
return 0;
}
2. 二进制文件
如果要以二进制的方式对文件进行读写操作,那么打开方式要指定为ios::binary
。
2.1 写文件
- 二进制方式写文件主要利用流对象调用成员函数
write
。 - 函数原型 :
ostream& write(const char * buffer,int len);
。其中,字符指针buffer指向内存中一段存储空间,len是读写的字节数。 - 步骤和写文本文件的步骤大致相同。
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二进制文件 写文件
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
2.2 读文件
- 二进制方式读文件主要利用流对象调用成员函数
read
。 - 函数原型:
istream& read(char *buffer,int len);
。其中,字符指针buffer指向内存中一段存储空间,len是读写的字节数。 - 步骤和读文本文件的步骤大致相同。
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}