在 C++ 中,左移运算符(<<) 最常用的场景是配合 cout 输出数据(如 cout << "hello")。对于自定义类型(如类、结构体),默认无法直接用 cout << 对象 输出,需通过重载 << 运算符实现,这也是 << 运算符重载最核心的应用场景。
一、左移运算符重载的核心特点
1、<< 是双目运算符:左操作数是 ostream 对象(如 cout),右操作数是自定义类型对象;
2、必须重载为全局函数 / 友元:若重载为成员函数,左操作数会被绑定为当前类对象(如 obj << cout),违反 cout << obj 的使用直觉,因此只能用全局函数,且需声明为类的友元(以访问私有成员);
3、返回值为 ostream&:支持链式输出(如 cout << obj1 << obj2 << endl);
4、不修改操作数:通常用 const 修饰自定义类型参数,保证不修改对象。
二、重载语法与步骤
- 核心语法
cpp
// 类内声明友元(需访问私有成员时)
friend ostream& operator<<(ostream& os, const 类名& obj);
// 全局定义重载函数
ostream& operator<<(ostream& os, const 类名& obj) {
// 自定义输出逻辑(如输出对象的成员)
os << 成员1 << " " << 成员2;
return os; // 返回ostream对象,支持链式输出
}
- 关键说明
ostream& 返回值:返回传入的 os 引用(而非临时对象),确保链式调用时 cout 是同一个对象;
const 类名& obj:用引用避免拷贝,const 保证不修改对象(即使输出常量对象也能生效);
友元声明:仅当需要访问类的私有 / 保护成员时才需要,若成员是 public,可省略友元,直接通过公共接口访问。
三、基础示例:自定义类输出
以复数类为例,实现 cout << 复数对象 输出实部和虚部:
cpp
#include <iostream>
using namespace std;
class Complex {
private:
double real; // 实部(私有)
double imag; // 虚部(私有)
public:
// 构造函数
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// 声明 << 运算符为友元(访问私有成员)
friend ostream& operator<<(ostream& os, const Complex& c);
};
// 全局定义左移运算符重载
ostream& operator<<(ostream& os, const Complex& c) {
// 自定义输出格式
os << c.real << " + " << c.imag << "i";
return os; // 返回ostream引用,支持链式输出
}
int main() {
Complex c1(1.5, 2.5), c2(3.5, 4.5);
// 基础输出
cout << "c1: " << c1 << endl; // 输出:c1: 1.5 + 2.5i
// 链式输出
cout << "c1 = " << c1 << ", c2 = " << c2 << endl; // 输出:c1 = 1.5 + 2.5i, c2 = 3.5 + 4.5i
return 0;
}
四、进阶示例:支持多格式输出 + 非友元重载
若不想用友元(避免破坏封装),可通过公共接口(getter 函数)访问成员,实现非友元重载:
cpp
#include <iostream>
using namespace std;
class Student {
private:
string name;
int age;
double score;
public:
Student(string n, int a, double s) : name(n), age(a), score(s) {}
// 公共getter接口
string getName() const { return name; }
int getAge() const { return age; }
double getScore() const { return score; }
};
// 非友元重载 <<:通过getter访问成员
ostream& operator<<(ostream& os, const Student& stu) {
os << "姓名:" << stu.getName()
<< ",年龄:" << stu.getAge()
<< ",成绩:" << stu.getScore();
return os;
}
int main() {
Student stu("张三", 18, 95.5);
cout << stu << endl; // 输出:姓名:张三,年龄:18,成绩:95.5
return 0;
}
五、支持自定义输出流(如文件流)
<< 重载不仅支持 cout(控制台输出),还支持所有 ostream 派生类(如 ofstream 文件输出),因为重载函数的参数是基类 ostream&:
cpp
#include <iostream>
#include <fstream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
friend ostream& operator<<(ostream& os, const Complex& c);
};
ostream& operator<<(ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
int main() {
Complex c(2.5, 3.5);
// 输出到控制台
cout << "控制台输出:" << c << endl;
// 输出到文件
ofstream fout("complex.txt");
if (fout.is_open()) {
fout << "文件输出:" << c << endl;
fout.close();
}
return 0;
}
运行后,complex.txt 文件中会写入:文件输出:2.5 + 3.5i。
六、常见误区与注意事项
1、错误地重载为成员函数:
cpp
// 错误示例:成员函数重载 <<,左操作数是Complex对象,使用时需写 c << cout,违反直觉
class Complex {
public:
ostream& operator<<(ostream& os) {
os << real << " + " << imag << "i";
return os;
}
};
// 调用:c1 << cout; // 极其反人类,绝对避免!
2、返回值错误:
若返回 void,无法支持链式输出(如 cout << c1 << c2 会报错);
若返回 ostream(值返回),会拷贝 cout(ostream 不支持拷贝),导致编译错误,必须返回 ostream&(引用)。
3、未用 const 修饰对象参数:
若参数是 Complex& c(无 const),则无法输出常量对象(如 const Complex c(1,2); cout << c; 报错),必须加 const。
4、友元的滥用:
仅当需要访问私有成员时才声明友元,优先通过 getter 接口实现非友元重载,减少封装破坏。
七、与右移运算符(>>)重载的配合
左移(<<)用于输出,右移(>>)用于输入,通常成对重载,示例如下:
cpp
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// 重载 <<(输出)
friend ostream& operator<<(ostream& os, const Complex& c);
// 重载 >>(输入)
friend istream& operator>>(istream& is, Complex& c);
};
ostream& operator<<(ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
istream& operator>>(istream& is, Complex& c) {
// 自定义输入格式:输入两个数,分别赋值给实部和虚部
cout << "请输入实部和虚部(空格分隔):";
is >> c.real >> c.imag;
return is;
}
int main() {
Complex c;
cin >> c; // 输入:3 4
cout << "你输入的复数是:" << c << endl; // 输出:3 + 4i
return 0;
}
总结
左移运算符(<<)重载的核心目的是让自定义类型支持 cout(或其他 ostream)输出;
必须重载为全局函数(友元按需声明),返回 ostream& 以支持链式输出;
参数需用 const 类名& 修饰,保证不修改对象且避免拷贝;
可兼容所有 ostream 派生类(控制台、文件等),通用性强;
通常与 >> 运算符重载配合,实现自定义类型的输入输出。