跳到主要内容

C++ 输入输出运算符重载

什么是输入输出运算符重载?

在C++编程中,我们经常需要对自定义类进行输入和输出操作。标准输入输出流(cin/cout)默认只能处理基本数据类型,但通过运算符重载,我们可以扩展这些运算符的功能,使其能够处理我们自定义的类。

  • 输出运算符(<<): 也称为插入运算符,用于将数据输出到流中
  • 输入运算符(>>): 也称为提取运算符,用于从流中读取数据
小贴士

重载输入输出运算符可以让你的类使用起来更加自然,就像使用基本数据类型一样!

输出运算符(<<)重载

语法和规范

输出运算符重载通常使用以下语法:

cpp
std::ostream& operator<<(std::ostream& out, const ClassName& obj);

重要规范:

  1. 必须是非成员函数(通常是友元)
  2. 返回类型是输出流的引用,实现链式调用
  3. 第一个参数是输出流的引用
  4. 第二个参数是要输出的对象(通常为常量引用)

示例代码

让我们创建一个简单的Person类,并为其重载输出运算符:

cpp
#include <iostream>
#include <string>

class Person {
private:
std::string name;
int age;

public:
// 构造函数
Person(const std::string& n = "", int a = 0) : name(n), age(a) {}

// 获取器
std::string getName() const { return name; }
int getAge() const { return age; }

// 友元声明
friend std::ostream& operator<<(std::ostream& out, const Person& person);
};

// 输出运算符重载实现
std::ostream& operator<<(std::ostream& out, const Person& person) {
out << "Person(name: " << person.name << ", age: " << person.age << ")";
return out; // 返回输出流以支持链式操作
}

int main() {
Person alice("Alice", 25);
Person bob("Bob", 30);

// 使用重载的输出运算符
std::cout << alice << std::endl;

// 链式调用
std::cout << "Two people: " << alice << ", " << bob << std::endl;

return 0;
}

输出:

Person(name: Alice, age: 25)
Two people: Person(name: Alice, age: 25), Person(name: Bob, age: 30)

输入运算符(>>)重载

语法和规范

输入运算符重载的语法如下:

cpp
std::istream& operator>>(std::istream& in, ClassName& obj);

重要规范:

  1. 必须是非成员函数(通常是友元)
  2. 返回输入流的引用,支持链式调用
  3. 第一个参数是输入流的引用
  4. 第二个参数是接收输入的对象(非常量引用)
  5. 应该处理输入错误和异常情况

示例代码

继续使用Person类,添加输入运算符重载:

cpp
#include <iostream>
#include <string>

class Person {
private:
std::string name;
int age;

public:
Person(const std::string& n = "", int a = 0) : name(n), age(a) {}

// 获取器
std::string getName() const { return name; }
int getAge() const { return age; }

// 友元声明
friend std::ostream& operator<<(std::ostream& out, const Person& person);
friend std::istream& operator>>(std::istream& in, Person& person);
};

// 输出运算符重载
std::ostream& operator<<(std::ostream& out, const Person& person) {
out << "Person(name: " << person.name << ", age: " << person.age << ")";
return out;
}

// 输入运算符重载
std::istream& operator>>(std::istream& in, Person& person) {
std::cout << "Enter name: ";
in >> person.name;

std::cout << "Enter age: ";
in >> person.age;

// 检查输入错误
if (in.fail()) {
in.clear(); // 清除错误标志
in.ignore(100, '\n'); // 忽略错误输入
person = Person(); // 设为默认值
}

return in;
}

int main() {
Person someone;

std::cout << "Please enter person details:\n";
std::cin >> someone;

std::cout << "\nYou entered: " << someone << std::endl;

// 链式输入示例
Person p1, p2;
std::cout << "\nEnter details for two people:\n";
std::cin >> p1 >> p2;

std::cout << "\nYou entered: " << p1 << " and " << p2 << std::endl;

return 0;
}

交互示例:

Please enter person details:
Enter name: John
Enter age: 28

You entered: Person(name: John, age: 28)

Enter details for two people:
Enter name: Emma
Enter age: 24
Enter name: Michael
Enter age: 32

You entered: Person(name: Emma, age: 24) and Person(name: Michael, age: 32)

重载输入输出运算符的最佳实践

  1. 异常处理: 在输入运算符中处理可能的输入错误
  2. 格式一致性: 输出格式应与输入格式一致,方便数据的保存和读取
  3. 私有数据访问: 通常需要将运算符重载函数声明为友元
  4. 返回流引用: 确保返回流的引用以支持链式操作
  5. 输入验证: 在输入运算符中添加输入数据的有效性检查

实际应用场景

1. 文件输入输出

cpp
#include <iostream>
#include <fstream>
#include <vector>

class Student {
private:
std::string id;
std::string name;
double gpa;

public:
Student(const std::string& i = "", const std::string& n = "", double g = 0.0)
: id(i), name(n), gpa(g) {}

friend std::ostream& operator<<(std::ostream& out, const Student& student);
friend std::istream& operator>>(std::istream& in, Student& student);
};

std::ostream& operator<<(std::ostream& out, const Student& student) {
out << student.id << " " << student.name << " " << student.gpa;
return out;
}

std::istream& operator>>(std::istream& in, Student& student) {
in >> student.id >> student.name >> student.gpa;
return in;
}

int main() {
// 写入文件
{
std::ofstream outFile("students.txt");
Student s1("S001", "Alice", 3.8);
Student s2("S002", "Bob", 3.5);
outFile << s1 << std::endl << s2 << std::endl;
}

// 读取文件
{
std::ifstream inFile("students.txt");
std::vector<Student> students;
Student temp;

while (inFile >> temp) {
students.push_back(temp);
}

std::cout << "Read " << students.size() << " students from file:" << std::endl;
for (const auto& s : students) {
std::cout << s << std::endl;
}
}

return 0;
}

2. 复数类的输入输出

cpp
#include <iostream>

class Complex {
private:
double real;
double imag;

public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

friend std::ostream& operator<<(std::ostream& out, const Complex& c);
friend std::istream& operator>>(std::istream& in, Complex& c);
};

std::ostream& operator<<(std::ostream& out, const Complex& c) {
out << c.real;
if (c.imag >= 0)
out << "+" << c.imag << "i";
else
out << c.imag << "i";
return out;
}

std::istream& operator>>(std::istream& in, Complex& c) {
std::cout << "Enter real part: ";
in >> c.real;
std::cout << "Enter imaginary part: ";
in >> c.imag;
return in;
}

int main() {
Complex c1(2.5, 3.7);
std::cout << "c1 = " << c1 << std::endl;

Complex c2;
std::cout << "Enter a complex number:" << std::endl;
std::cin >> c2;
std::cout << "You entered: " << c2 << std::endl;

return 0;
}

输入输出操作的高级技巧

1. 格式化输出

cpp
#include <iostream>
#include <iomanip>

class Date {
private:
int day, month, year;

public:
Date(int d = 1, int m = 1, int y = 2000) : day(d), month(m), year(y) {}

friend std::ostream& operator<<(std::ostream& out, const Date& date);
};

std::ostream& operator<<(std::ostream& out, const Date& date) {
// 保存当前格式设置
std::ios::fmtflags oldFlags = out.flags();
char oldFill = out.fill();

// 应用新的格式设置
out << std::setfill('0')
<< std::setw(2) << date.day << "/"
<< std::setw(2) << date.month << "/"
<< std::setw(4) << date.year;

// 恢复原来的格式设置
out.flags(oldFlags);
out.fill(oldFill);

return out;
}

int main() {
Date today(9, 5, 2023);
std::cout << "Today is: " << today << std::endl;

return 0;
}

2. 处理不同的输出格式

cpp
#include <iostream>
#include <sstream>

class Point {
private:
double x, y;

public:
Point(double xVal = 0.0, double yVal = 0.0) : x(xVal), y(yVal) {}

friend std::ostream& operator<<(std::ostream& out, const Point& p);
};

std::ostream& operator<<(std::ostream& out, const Point& p) {
// 检查输出流的类型和状态,提供不同的格式
if (dynamic_cast<std::ostringstream*>(&out)) {
// 用于字符串输出的简洁格式
out << p.x << "," << p.y;
} else {
// 用于控制台的友好格式
out << "Point(" << p.x << ", " << p.y << ")";
}
return out;
}

int main() {
Point p(3.5, 7.2);

// 控制台输出
std::cout << "Console output: " << p << std::endl;

// 字符串流输出
std::ostringstream oss;
oss << p;
std::string pointStr = oss.str();
std::cout << "String output: " << pointStr << std::endl;

return 0;
}

总结

输入输出运算符重载是C++中非常实用的特性,它可以让我们以自然的方式处理自定义类型的输入和输出操作。通过重载这些运算符,我们可以:

  • 使自定义类型的输入输出语法与内置类型保持一致
  • 提高代码的可读性和易用性
  • 实现链式输入输出操作
  • 支持文件和其他流的输入输出
  • 自定义数据的表示格式

重要的是要记住,输入输出运算符通常作为非成员函数实现,并且经常需要声明为友元以访问类的私有成员。另外,错误处理在输入运算符中尤为重要,以确保程序的健壮性。

练习

  1. 为一个表示时间的Time类(小时、分钟、秒)重载输入输出运算符。
  2. 实现一个Matrix类,重载输入输出运算符以便于矩阵的录入和显示。
  3. 为一个Address类(街道、城市、邮编)重载输入输出运算符,并实现文件输入输出功能。
  4. 创建一个购物车类(ShoppingCart),包含多个商品项,重载输出运算符以生成格式化的购物清单。
注意事项

在重载输入运算符时,一定要注意输入验证和错误处理,以避免无效数据导致程序崩溃。

参考资料

  • C++ 标准库中的 <iostream> 文档
  • Effective C++ 的相关章节
  • More Effective C++ 中关于输入输出的探讨
  • C++ Primer 中的运算符重载章节