Day 7:友元、运算符重载和函数对象
今天目标:掌握友元的作用,理解运算符重载的本质,并会重载常见运算符:+、==、<<、>>、[]、=、++、--、().
对应代码:
2-代码/day7-友元函数/33-友元示例1.cpp
2-代码/day7-友元函数/34-友元示例2.cpp
2-代码/day7-友元函数/35-友元示例3.cpp
2-代码/day7-友元函数/36-典型双目运算符重载示例.cpp
2-代码/day7-友元函数/37-输入输出运算符重载示例.cpp
2-代码/day7-友元函数/38-下标运算符示例.cpp
2-代码/day7-友元函数/39-赋值运算符重载示例.cpp
2-代码/day7-友元函数/40-赋值运算符重载示例2.cpp
2-代码/day7-友元函数/41-自增运算符示例.cpp
2-代码/day7-友元函数/42-函数调用运算符示例.cpp
2-代码/day7-友元函数/43-函数对象使用示例.cpp
1、友元 friend
友元用于授权某个函数或类访问本类的私有成员。
常见形式:
class Demo
{
friend void print_demo(Demo d); // 友元函数
friend class Other; // 友元类
};
注意:
1. 友元声明可以写在类中任意位置。
2. 友元不是成员函数,只是被允许访问私有成员。
3. 友元关系是单向的,不会自动反向。
4. 友元关系不能传递。
2、运算符重载
运算符重载就是给已有运算符添加适合类对象的新功能。
本质:
运算符重载本质上还是函数重载。
函数名格式:
operator运算符
例如:
operator+
operator==
operator<<
operator[]
简单理解:
d1 + d2;
可能等价于:
operator+(d1, d2); // 全局函数
d1.operator+(d2); // 成员函数
3、双目运算符
典型双目运算符:
+ - * /
== != > >= < <=
可以重载为全局函数,也可以重载为成员函数。
全局函数示例:
class Demo
{
public:
friend Demo operator+(Demo d1, Demo d2);
Demo(int x = 0) : _x(x) {}
private:
int _x;
};
Demo operator+(Demo d1, Demo d2)
{
return Demo(d1._x + d2._x);
}
成员函数示例:
bool operator==(Demo d2) const
{
return _x == d2._x;
}
建议:
不修改对象的运算符函数,尽量写 const。
参数较大时,优先用 const 引用传参。
4、输入输出运算符
<< 和 >> 一般重载为全局函数。
原因:
cout << d;
cin >> d;
左操作数是 cout 或 cin,不是当前类对象。
基本形式:
std::ostream& operator<<(std::ostream& out, const Demo& d);
std::istream& operator>>(std::istream& in, Demo& d);
示例:
class Demo
{
public:
friend std::ostream& operator<<(std::ostream& out, const Demo& d);
friend std::istream& operator>>(std::istream& in, Demo& d);
Demo(int x = 0) : _x(x) {}
private:
int _x;
};
std::ostream& operator<<(std::ostream& out, const Demo& d)
{
out << "Demo(" << d._x << ")";
return out;
}
std::istream& operator>>(std::istream& in, Demo& d)
{
in >> d._x;
return in;
}
返回引用是为了支持连续输入输出:
cout << d1 << d2 << endl;
cin >> d1 >> d2;
5、下标运算符 []
[] 必须重载为成员函数。
常见写法:
int& operator[](int index);
const int& operator[](int index) const;
示例:
class Demo
{
public:
int& operator[](int index)
{
return p[index];
}
const int& operator[](int index) const
{
return p[index];
}
private:
int* p;
};
说明:
非 const 版本:支持 d[0] = 100。
const 版本:支持 const 对象读取 d[0]。
返回引用:才能真正修改对象内部元素。
6、赋值运算符 =
拷贝构造和拷贝赋值不同:
Demo d2 = d1; // 拷贝构造,创建新对象
d2 = d1; // 拷贝赋值,已有对象之间赋值
赋值运算符只能重载为成员函数:
Demo& operator=(const Demo& rhs);
基本写法:
Demo& operator=(const Demo& rhs)
{
if (this != &rhs)
{
_x = rhs._x;
}
return *this;
}
如果类中管理堆内存,需要深拷贝:
1. 判断自赋值。
2. 释放旧资源。
3. 申请新资源。
4. 复制右侧对象的数据。
5. return *this。
7、自增自减运算符
前置和后置写法不同。
前置:
Demo& operator++(); // ++d
Demo& operator--(); // --d
后置:
Demo operator++(int); // d++
Demo operator--(int); // d--
后置版本中的 int 是占位参数,只用于区分前置和后置。
核心区别:
前置:先改变自己,再返回改变后的自己,通常返回引用。
后置:先保存旧值,再改变自己,返回旧值,通常返回对象。
示例:
Demo& operator++()
{
++x;
return *this;
}
Demo operator++(int)
{
Demo tmp = *this;
++x;
return tmp;
}
8、函数调用运算符 ()
()只能重载为成员函数。
class Demo
{
public:
void operator()()
{
cout << "hello operator()" << endl;
}
void operator()(int x)
{
cout << "x = " << x << endl;
}
};
使用:
Demo d;
d();
d(100);
等价于:
d.operator()();
d.operator()(100);
9、函数对象
重载了 operator() 的对象,称为函数对象,也叫仿函数。
函数对象像函数一样调用,但它比普通函数多一个优点:
函数对象可以保存状态。
示例:
class PrintInt
{
public:
PrintInt(char c = ' ') : c(c) {}
void operator()(int x)
{
cout << x << c;
}
private:
char c;
};
使用:
PrintInt p1('^');
p1(100); // 输出 100^
PrintInt('~')(5); // 匿名函数对象
函数对象常用于把“行为”传给函数:
void print_array(int a[], int n, PrintInt func)
{
for (int i = 0; i < n; i++)
func(a[i]);
}
10、Day7 总结
1. friend 用于授权函数或类访问私有成员。
2. 运算符重载本质是函数重载,函数名是 operator运算符。
3. 双目运算符可写成全局函数或成员函数。
4. << 和 >> 通常写成全局函数,并返回流对象引用。
5. [] 必须写成成员函数,常提供 const 和非 const 两个版本。
6. = 只能写成成员函数,管理资源时要防止浅拷贝问题。
7. 前置++/--返回改变后的自己,后置++/--返回改变前的旧值。
8. 后置++/--的 int 参数只是占位,用来区分前置和后置。
9. operator() 让对象可以像函数一样调用。
10. 函数对象比普通函数更灵活,因为它可以保存状态。
一句话记忆:
Day7 的重点是让类对象用起来更像内置类型:能加、能比、能输入输出、能下标访问,甚至能像函数一样调用。






Comments | NOTHING