C++学习 day7

发布于 22 天前  22 次阅读


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;

左操作数是 coutcin,不是当前类对象。

基本形式:

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 的重点是让类对象用起来更像内置类型:能加、能比、能输入输出、能下标访问,甚至能像函数一样调用。

"When faced with uncertainty, ask the spring breeze."