目录
[四、为什么 cout 不能直接输出 Date 对象?](#四、为什么 cout 不能直接输出 Date 对象?)
[五、operator<< 为什么通常写成全局函数?](#五、operator<< 为什么通常写成全局函数?)
[六、operator<< 为什么要声明为友元?](#六、operator<< 为什么要声明为友元?)
[七、operator<< 为什么返回 ostream&?](#七、operator<< 为什么返回 ostream&?)
[八、operator>> 运算符重载](#八、operator>> 运算符重载)
[九、cout 为什么会识别不同类型?](#九、cout 为什么会识别不同类型?)
一、为什么需要友元函数?
在C++中,类的私有成员只能在类内部访问。
例如:
cpp
class Date {
private:
int _year;
int _month;
int _day;
};
如果在另外一个类写普通函数:
cpp
void PrintDate(const Date& d) {
cout << d._year << endl; // 错误
}
这段代码会报错,因为 _year 是私有成员,类外不能直接访问。
如果确实需要让某个类外函数访问私有成员,可以使用 友元函数。
二、友元函数的写法
友元函数使用 friend 关键字声明。
cpp
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
}
friend void PrintDate(const Date& d);
private:
int _year;
int _month;
int _day;
};
类外实现:
cpp
void PrintDate(const Date& d) {
cout << d._year << "-" << d._month << "-" << d._day << endl;
}
这样 PrintDate 就可以直接访问 Date 类中的私有成员了。
需要注意:
友元函数不是类的成员函数,只是被允许访问这个类的私有成员。
所以调用仍然是普通调用:
cpp
Date d(2024, 5, 1);
PrintDate(d);
而不是:
cpp
d.PrintDate(); //错误
三、友元函数的特点
友元函数有几个特点:
cpp
1. 友元函数不只是类中的函数;
2. 友元函数可以直接访问类的私有成员;
3. friend声明可以写在 public、private、protected 任意位置;
4. 友元函数只对当前类有效;
5. 友元函数会突破封装,不能滥用。
一般情况下能写成成员函数,就优先写成成员函数。
只有实在不适合写成成员函数时,再考虑友元。
四、为什么 cout 不能直接输出 Date 对象?
对于内置类型, cout 可以直接输出:
cpp
int a = 10;
double d = 3.14;
cout << a << d << endl;
但是对于自定义类型:
cpp
Date d(2024, 5, 1);
cout << d << endl;
编译器不知道一个 Date 对象应该怎么输出。
也不知道输出格式是:
cpp
2024-5-1
还是:
cpp
2024/5/1
所以我们要重载自己的输出运算符 operator<< 。
五、operator<< 为什么通常写成全局函数?
如果把 operator<< 写成成员函数,左操作数默认就是 this。
例如成员函数时形式会变成:
cpp
d.operator<<(cout);
这样调用时可能要写成:
cpp
d << cout;
这显然不符合我们的使用习惯。
我们想要的是:
cpp
cout << d;
左边是 cout ,右边是 Date 对象。
所以 operator<< 通常写成全局函数:
cpp
ostream& operator<<(ostream& out, const Date& d);
其中:
cpp
out 表示输出流对象,例如 cout
d 表示要输出的 Date 对象
六、operator<< 为什么要声明为友元?
operator<< 写成全局函数后,他就不是 Date 类的成员函数了。
但是它它需要访问 Date 的私有成员:
cpp
d._year
d._month
d._day
所以需要在类中把他声明为友元函数:
cpp
friend ostream& operator(ostream& out, const Date& d);
完整代码:
cpp
#include <iostream>
using namespace std;
class Date {
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day << endl;
}
int main() {
Date d1(2024, 5, 1);
cout << d1;
return 0;
}
运行结果:
cpp
2024-5-1
七、operator<< 为什么返回 ostream&?
如果只想输入一次,似乎可以不返回。
但我们平常这样写比较多:
cpp
cout << d << endl;
这个表达形式本质上是连续调用:
cpp
operator<<(cout, d) << endl;
如果 operator<< 不返回 osteram&,后面就不能继续接 << endl。
所以输出运算符重载要写成:
cpp
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
返回 out 的引用,才能支持连续输出。
八、operator>> 运算符重载
输入运算符重载和输出运算符重载类似,但是有差别。
首先是在返回值类型上,输入运算符重载是:
cpp
istream&
还要注意的是第二个参数是:
cpp
Date& d
因为输入时要修改 d 的成员变量。
完整代码:
cpp
#include <iostream>
using namespace std;
class Date {
friend istream& operator>>(istream& in, Date& d);
friend ostream& operator<<(ostream& out, const Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day << endl;
}
int main() {
Date d1(2024, 5, 1);
cin >> d1;
cout << d1;
return 0;
}
输入:
cpp
2024 5 1
输出:
cpp
2024-5-1
九、cout 为什么会识别不同类型?
cout 能输出 int、double、char等不同类型,本质上也是函数重载。
例如可以简单理解标准库中已经写好的很多版本:
cpp
ostream& operator<<(ostream& out, int x);
ostream& operator<<(ostream& out, double x);
ostream& operator<<(ostream& out, char ch);
ostream& operator<<(ostream& out, const char* str);
当我们写:
cpp
cout << 10;
cout << 3.14;
cout << 'A';
编译器会根据右操作数的类型,自动匹配对应的 operator<< 版本。
但是标准库不知道我们的 Date 类型应该怎么输出,所以自定义类型需要自己重载。
十、小结
本篇主要学习了友元函数和输入输出运算符重载。
需要记住:
- 友元函数用 friend 声明;
- 友元函数不是类的成员函数;
- 友元函数可以访问类的私有成员;
- 友元会破坏类的封装,不能滥用;
operator<<通常写成全局函数;operator<<第一个参数是ostream&;operator<<第二个参数一般是const Date&;operator<<返回ostream&,用于支持连续输出;operator>>第一个参数是istream&;operator>>第二个参数是Date&,因为输入会修改对象;cout的类型自动识别,本质上依赖函数重载。
有云函数最经典的应用场景之一,就是配合输入和输出运算符重载,让自定义类型也能像内置类型一样使用 cin 和 cout。