C++ Primer Plus 第11章:使用类

11.1 运算符重载

11.1.1 什么是运算符重载?

运算符重载允许为自定义类型定义运算符的行为,使代码更直观自然。

复制代码
没有运算符重载:              有运算符重载:
Time t3 = t1.add(t2);   →   Time t3 = t1 + t2;
t1.print();              →   cout << t1;

运算符重载的本质 :运算符重载是特殊的函数,函数名为 operator运算符

cpp 复制代码
// 例如:重载 + 运算符
Time operator+(const Time& t) const;
// 调用方式:t1 + t2 等价于 t1.operator+(t2)

11.1.2 可重载与不可重载的运算符

可以重载的运算符(部分):

类别 运算符
算术 + - * / %
比较 == != < > <= >=
赋值 = += -= *= /=
位运算 & `
逻辑 ! && `
其他 [] () -> ++ -- << >>

不能重载的运算符:

  • :: 作用域解析
  • .* 成员指针访问
  • . 成员访问
  • ?: 条件运算符
  • sizeof

11.1.3 基本运算符重载示例

cpp 复制代码
// time_class.cpp -- 时间类运算符重载
#include <iostream>

class Time
{
private:
    int hours;
    int minutes;

public:
    Time() : hours(0), minutes(0) {}
    Time(int h, int m) : hours(h), minutes(m) {}

    void show() const
    {
        std::cout << hours << "小时 " << minutes << "分钟" << std::endl;
    }

    // 重载 + 运算符(成员函数)
    Time operator+(const Time& t) const
    {
        Time sum;
        sum.minutes = minutes + t.minutes;
        sum.hours   = hours + t.hours + sum.minutes / 60;
        sum.minutes %= 60;
        return sum;
    }

    // 重载 - 运算符
    Time operator-(const Time& t) const
    {
        Time diff;
        int total1 = hours * 60 + minutes;
        int total2 = t.hours * 60 + t.minutes;
        int diff_min = total1 - total2;
        if (diff_min < 0) diff_min = -diff_min;
        diff.hours   = diff_min / 60;
        diff.minutes = diff_min % 60;
        return diff;
    }

    // 重载 * 运算符(乘以倍数)
    Time operator*(double mult) const
    {
        Time result;
        long total_min = (long)(hours * 60 + minutes) * mult;
        result.hours   = total_min / 60;
        result.minutes = total_min % 60;
        return result;
    }

    // 重载 += 运算符
    Time& operator+=(const Time& t)
    {
        minutes += t.minutes;
        hours   += t.hours + minutes / 60;
        minutes %= 60;
        return *this;
    }

    // 重载 == 运算符
    bool operator==(const Time& t) const
    {
        return hours == t.hours && minutes == t.minutes;
    }

    // 重载 < 运算符
    bool operator<(const Time& t) const
    {
        if (hours != t.hours)
            return hours < t.hours;
        return minutes < t.minutes;
    }
};

int main()
{
    using namespace std;

    Time t1(2, 35);   // 2小时35分
    Time t2(1, 40);   // 1小时40分

    cout << "t1 = "; t1.show();
    cout << "t2 = "; t2.show();

    Time t3 = t1 + t2;   // 调用 operator+
    cout << "t1 + t2 = "; t3.show();   // 4小时15分

    Time t4 = t1 - t2;
    cout << "t1 - t2 = "; t4.show();   // 0小时55分

    Time t5 = t1 * 2.5;
    cout << "t1 * 2.5 = "; t5.show();  // 6小时27分

    t1 += t2;
    cout << "t1 += t2 后:"; t1.show();

    cout << boolalpha;
    cout << "t1 == t2 : " << (t1 == t2) << endl;
    cout << "t2 < t3  : " << (t2 < t3)  << endl;

    return 0;
}

11.2 友元函数

11.2.1 为什么需要友元?

cpp 复制代码
// 问题:成员函数 operator* 只能处理 Time * double
// 但 double * Time 怎么办?

Time t1(2, 30);
Time t2 = t1 * 2.0;    // ✅ t1.operator*(2.0)
// Time t3 = 2.0 * t1; // ❌ 2.0没有operator*成员函数!

解决方案 :使用友元函数(非成员函数,但可以访问私有成员)。


11.2.2 友元函数的定义与使用

cpp 复制代码
// friend_demo.cpp -- 友元函数示例
#include <iostream>

class Time
{
private:
    int hours;
    int minutes;

public:
    Time(int h = 0, int m = 0) : hours(h), minutes(m) {}

    void show() const
    {
        std::cout << hours << "h " << minutes << "m" << std::endl;
    }

    // 成员函数版本:Time * double
    Time operator*(double mult) const
    {
        long total = (long)(hours * 60 + minutes) * mult;
        return Time(total / 60, total % 60);
    }

    // 声明友元函数:double * Time
    // friend关键字只在类声明中出现
    friend Time operator*(double mult, const Time& t);

    // 友元:重载 << 运算符(输出流)
    friend std::ostream& operator<<(std::ostream& os, const Time& t);

    // 友元:重载 >> 运算符(输入流)
    friend std::istream& operator>>(std::istream& is, Time& t);
};

// 友元函数定义(不加 friend 关键字,不加 Time::)
Time operator*(double mult, const Time& t)
{
    // 可以访问 t 的私有成员
    long total = (long)(t.hours * 60 + t.minutes) * mult;
    return Time(total / 60, total % 60);
}

// 重载 << 运算符(必须是友元,因为左操作数是ostream)
std::ostream& operator<<(std::ostream& os, const Time& t)
{
    os << t.hours << "小时" << t.minutes << "分钟";
    return os;   // 返回os支持链式输出
}

// 重载 >> 运算符
std::istream& operator>>(std::istream& is, Time& t)
{
    is >> t.hours >> t.minutes;
    return is;
}

int main()
{
    using namespace std;

    Time t1(2, 30);

    // 成员函数版本
    Time t2 = t1 * 2.0;
    cout << "t1 * 2.0 = " << t2 << endl;   // 使用重载的<<

    // 友元函数版本(double在左边)
    Time t3 = 3.0 * t1;
    cout << "3.0 * t1 = " << t3 << endl;

    // 链式输出
    cout << "t1=" << t1 << " t2=" << t2 << " t3=" << t3 << endl;

    // 输入
    Time t4;
    cout << "请输入时间(小时 分钟):";
    cin >> t4;
    cout << "你输入的时间:" << t4 << endl;

    return 0;
}

11.2.3 重载 << 和 >> 运算符(详解)

cpp 复制代码
// io_overload.cpp -- 输入输出运算符重载
#include <iostream>
#include <string>

class Student
{
private:
    std::string name;
    int         age;
    double      score;

public:
    Student(const std::string& n = "", int a = 0, double s = 0.0)
        : name(n), age(a), score(s) {}

    // 重载 << :必须是友元(ostream在左边)
    friend std::ostream& operator<<(std::ostream& os,
                                    const Student& s)
    {
        os << "姓名:" << s.name
           << " 年龄:" << s.age
           << " 成绩:" << s.score;
        return os;   // 必须返回os,支持链式:cout << s1 << s2
    }

    // 重载 >> :必须是友元(istream在左边)
    friend std::istream& operator>>(std::istream& is, Student& s)
    {
        is >> s.name >> s.age >> s.score;
        return is;   // 必须返回is,支持链式:cin >> s1 >> s2
    }
};

int main()
{
    using namespace std;

    Student s1("张三", 20, 92.5);
    Student s2("李四", 21, 88.0);

    // 链式输出
    cout << s1 << endl;
    cout << s2 << endl;
    cout << "学生1:" << s1 << "\n学生2:" << s2 << endl;

    // 输入
    Student s3;
    cout << "请输入学生信息(姓名 年龄 成绩):";
    cin >> s3;
    cout << "输入的学生:" << s3 << endl;

    return 0;
}

💡 重载 <<>> 的关键点

  • 必须是友元函数 (因为左操作数是 ostream/istream,不是自定义类)
  • 必须返回流的引用ostream& / istream&),以支持链式操作
  • 参数:operator<<(ostream& os, const 类名& obj)

11.3 重载运算符:成员函数 vs 友元函数

cpp 复制代码
// member_vs_friend.cpp -- 成员函数与友元函数的选择
#include <iostream>

class Vector2D
{
public:
    double x, y;

    Vector2D(double x = 0, double y = 0) : x(x), y(y) {}

    // ✅ 适合成员函数:左操作数是本类对象
    Vector2D operator+(const Vector2D& v) const
    {
        return Vector2D(x + v.x, y + v.y);
    }

    Vector2D operator-() const   // 一元负号
    {
        return Vector2D(-x, -y);
    }

    Vector2D& operator+=(const Vector2D& v)
    {
        x += v.x; y += v.y;
        return *this;
    }

    // ✅ 适合友元函数:左操作数不是本类(如double * Vector)
    friend Vector2D operator*(double scalar, const Vector2D& v)
    {
        return Vector2D(scalar * v.x, scalar * v.y);
    }

    // ✅ 必须是友元:左操作数是ostream
    friend std::ostream& operator<<(std::ostream& os,
                                    const Vector2D& v)
    {
        os << "(" << v.x << ", " << v.y << ")";
        return os;
    }

    // 点积(成员函数)
    double dot(const Vector2D& v) const
    {
        return x * v.x + y * v.y;
    }

    // 模长
    double length() const
    {
        return sqrt(x * x + y * y);
    }
};

int main()
{
    using namespace std;

    Vector2D v1(3.0, 4.0);
    Vector2D v2(1.0, 2.0);

    cout << "v1 = " << v1 << endl;
    cout << "v2 = " << v2 << endl;
    cout << "v1 + v2 = " << (v1 + v2) << endl;
    cout << "-v1 = " << (-v1) << endl;
    cout << "2.0 * v1 = " << (2.0 * v1) << endl;
    cout << "v1 · v2 = " << v1.dot(v2) << endl;
    cout << "|v1| = " << v1.length() << endl;

    v1 += v2;
    cout << "v1 += v2 后:" << v1 << endl;

    return 0;
}

选择原则:

情况 推荐方式
左操作数是本类对象 成员函数
左操作数不是本类(如 double * 类 友元函数
<<>> 运算符 必须是友元函数
= [] () -> 运算符 必须是成员函数

11.4 重载赋值运算符

cpp 复制代码
// assignment_overload.cpp -- 赋值运算符重载
#include <iostream>
#include <cstring>

class MyString
{
private:
    char* data;
    int   length;

public:
    // 构造函数
    MyString(const char* str = "")
    {
        length = strlen(str);
        data   = new char[length + 1];
        strcpy(data, str);
    }

    // 拷贝构造函数(深拷贝)
    MyString(const MyString& other)
    {
        length = other.length;
        data   = new char[length + 1];
        strcpy(data, other.data);
        std::cout << "拷贝构造:\"" << data << "\"" << std::endl;
    }

    // 赋值运算符重载(深拷贝)
    MyString& operator=(const MyString& other)
    {
        std::cout << "赋值运算符:\"" << other.data << "\"" << std::endl;

        // 1. 防止自赋值(s = s)
        if (this == &other)
            return *this;

        // 2. 释放旧内存
        delete[] data;

        // 3. 分配新内存并复制
        length = other.length;
        data   = new char[length + 1];
        strcpy(data, other.data);

        // 4. 返回 *this 支持链式赋值
        return *this;
    }

    // 析构函数
    ~MyString()
    {
        delete[] data;
    }

    void show() const
    {
        std::cout << "\"" << data << "\" (长度:" << length << ")" << std::endl;
    }

    friend std::ostream& operator<<(std::ostream& os,
                                    const MyString& s)
    {
        os << s.data;
        return os;
    }
};

int main()
{
    using namespace std;

    MyString s1("Hello");
    MyString s2("World");
    MyString s3;

    cout << "s1 = "; s1.show();
    cout << "s2 = "; s2.show();

    // 赋值(调用赋值运算符)
    s3 = s1;
    cout << "s3 = s1 后:"; s3.show();

    // 链式赋值
    MyString s4, s5;
    s4 = s5 = s2;   // s5=s2,然后s4=s5
    cout << "s4 = "; s4.show();
    cout << "s5 = "; s5.show();

    // 自赋值(不应崩溃)
    s1 = s1;
    cout << "自赋值后 s1 = "; s1.show();

    return 0;
}

⚠️ 赋值运算符的四个要点

  1. 防止自赋值if (this == &other) return *this;
  2. 释放旧资源 :先 delete[] 旧内存
  3. 深拷贝:分配新内存并复制内容
  4. 返回 *this :支持链式赋值 a = b = c

11.5 重载下标运算符 \[\]

cpp 复制代码
// subscript_overload.cpp -- 下标运算符重载
#include <iostream>
#include <stdexcept>

class SafeArray
{
private:
    int*  data;
    int   size;

public:
    SafeArray(int sz) : size(sz)
    {
        data = new int[size]();   // 初始化为0
    }

    ~SafeArray() { delete[] data; }

    // 非const版本:可读可写
    int& operator[](int index)
    {
        if (index < 0 || index >= size)
            throw std::out_of_range("索引越界:" + std::to_string(index));
        return data[index];
    }

    // const版本:只读
    const int& operator[](int index) const
    {
        if (index < 0 || index >= size)
            throw std::out_of_range("索引越界:" + std::to_string(index));
        return data[index];
    }

    int getSize() const { return size; }
};

int main()
{
    using namespace std;

    SafeArray arr(5);

    // 写入
    for (int i = 0; i < 5; i++)
        arr[i] = (i + 1) * 10;

    // 读取
    cout << "数组内容:";
    for (int i = 0; i < 5; i++)
        cout << arr[i] << " ";
    cout << endl;

    // 越界检测
    try
    {
        arr[10] = 999;   // 抛出异常
    }
    catch (const out_of_range& e)
    {
        cout << "捕获异常:" << e.what() << endl;
    }

    return 0;
}

11.6 重载递增/递减运算符

cpp 复制代码
// increment_overload.cpp -- 递增递减运算符重载
#include <iostream>

class Counter
{
private:
    int value;

public:
    Counter(int v = 0) : value(v) {}

    // 前缀 ++(先加后用):返回引用
    Counter& operator++()
    {
        ++value;
        return *this;
    }

    // 后缀 ++(先用后加):返回值(用int参数区分)
    Counter operator++(int)
    {
        Counter temp = *this;   // 保存当前值
        ++value;                // 递增
        return temp;            // 返回递增前的值
    }

    // 前缀 --
    Counter& operator--()
    {
        --value;
        return *this;
    }

    // 后缀 --
    Counter operator--(int)
    {
        Counter temp = *this;
        --value;
        return temp;
    }

    int getValue() const { return value; }

    friend std::ostream& operator<<(std::ostream& os,
                                    const Counter& c)
    {
        os << c.value;
        return os;
    }
};

int main()
{
    using namespace std;

    Counter c(5);
    cout << "初始值:" << c << endl;

    cout << "前缀++:" << ++c << endl;   // 先加:6
    cout << "当前值:" << c  << endl;   // 6

    cout << "后缀++:" << c++ << endl;  // 先用:6
    cout << "当前值:" << c  << endl;   // 7

    cout << "前缀--:" << --c << endl;  // 先减:6
    cout << "后缀--:" << c-- << endl;  // 先用:6
    cout << "当前值:" << c  << endl;   // 5

    return 0;
}

💡 前缀 vs 后缀的区分

  • 前缀 ++objoperator++(),无参数,返回引用
  • 后缀 obj++operator++(int),有一个 int 哑参数,返回值(不是引用)

11.7 类型转换运算符

cpp 复制代码
// conversion_operator.cpp -- 类型转换运算符
#include <iostream>

class Fraction
{
private:
    int numerator;   // 分子
    int denominator; // 分母

public:
    Fraction(int n = 0, int d = 1) : numerator(n), denominator(d) {}

    // 转换为 double
    operator double() const
    {
        return (double)numerator / denominator;
    }

    // 转换为 bool(判断是否为零)
    explicit operator bool() const
    {
        return numerator != 0;
    }

    friend std::ostream& operator<<(std::ostream& os,
                                    const Fraction& f)
    {
        os << f.numerator << "/" << f.denominator;
        return os;
    }
};

int main()
{
    using namespace std;

    Fraction f1(3, 4);   // 3/4
    Fraction f2(0, 1);   // 0/1

    cout << "f1 = " << f1 << endl;

    // 隐式转换为double
    double d = f1;
    cout << "f1 转 double = " << d << endl;   // 0.75

    // 在数学运算中自动转换
    cout << "f1 + 1.0 = " << f1 + 1.0 << endl;   // 1.75

    // explicit转换:必须显式
    if (static_cast<bool>(f1))
        cout << "f1 非零" << endl;
    if (!static_cast<bool>(f2))
        cout << "f2 为零" << endl;

    return 0;
}

11.8 综合示例:复数类

cpp 复制代码
// complex_class.cpp -- 综合示例:完整的复数类
#include <iostream>
#include <cmath>
#include <string>

class Complex
{
private:
    double real;   // 实部
    double imag;   // 虚部

public:
    // 构造函数
    Complex(double r = 0.0, double i = 0.0)
        : real(r), imag(i) {}

    // 访问器
    double getReal() const { return real; }
    double getImag() const { return imag; }

    // 模(绝对值)
    double magnitude() const
    {
        return sqrt(real * real + imag * imag);
    }

    // 辐角
    double argument() const
    {
        return atan2(imag, real);
    }

    // 共轭复数
    Complex conjugate() const
    {
        return Complex(real, -imag);
    }

    // ===== 算术运算符(成员函数)=====
    Complex operator+(const Complex& c) const
    {
        return Complex(real + c.real, imag + c.imag);
    }

    Complex operator-(const Complex& c) const
    {
        return Complex(real - c.real, imag - c.imag);
    }

    Complex operator*(const Complex& c) const
    {
        return Complex(real * c.real - imag * c.imag,
                       real * c.imag + imag * c.real);
    }

    Complex operator/(const Complex& c) const
    {
        double denom = c.real * c.real + c.imag * c.imag;
        return Complex((real * c.real + imag * c.imag) / denom,
                       (imag * c.real - real * c.imag) / denom);
    }

    Complex operator-() const   // 一元负号
    {
        return Complex(-real, -imag);
    }

    // ===== 复合赋值运算符 =====
    Complex& operator+=(const Complex& c)
    {
        real += c.real; imag += c.imag;
        return *this;
    }

    Complex& operator-=(const Complex& c)
    {
        real -= c.real; imag -= c.imag;
        return *this;
    }

    Complex& operator*=(const Complex& c)
    {
        *this = *this * c;
        return *this;
    }

    // ===== 比较运算符 =====
    bool operator==(const Complex& c) const
    {
        return real == c.real && imag == c.imag;
    }

    bool operator!=(const Complex& c) const
    {
        return !(*this == c);
    }

    // ===== 类型转换 =====
    explicit operator double() const { return magnitude(); }

    // ===== 友元函数 =====
    // double * Complex
    friend Complex operator*(double scalar, const Complex& c)
    {
        return Complex(scalar * c.real, scalar * c.imag);
    }

    // 输出运算符
    friend std::ostream& operator<<(std::ostream& os,
                                    const Complex& c)
    {
        os << c.real;
        if (c.imag >= 0)
            os << "+" << c.imag << "i";
        else
            os << c.imag << "i";
        return os;
    }

    // 输入运算符
    friend std::istream& operator>>(std::istream& is, Complex& c)
    {
        is >> c.real >> c.imag;
        return is;
    }
};

int main()
{
    using namespace std;

    Complex c1(3.0, 4.0);    // 3+4i
    Complex c2(1.0, -2.0);   // 1-2i

    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;

    cout << "\n===== 算术运算 =====" << endl;
    cout << "c1 + c2 = " << (c1 + c2) << endl;   // 4+2i
    cout << "c1 - c2 = " << (c1 - c2) << endl;   // 2+6i
    cout << "c1 * c2 = " << (c1 * c2) << endl;   // 11-2i
    cout << "c1 / c2 = " << (c1 / c2) << endl;
    cout << "-c1     = " << (-c1)     << endl;   // -3-4i

    cout << "\n===== 属性 =====" << endl;
    cout << "|c1| = " << c1.magnitude() << endl;   // 5
    cout << "c1的共轭 = " << c1.conjugate() << endl;   // 3-4i

    cout << "\n===== 标量乘法 =====" << endl;
    cout << "2.0 * c1 = " << (2.0 * c1) << endl;   // 6+8i

    cout << "\n===== 复合赋值 =====" << endl;
    Complex c3 = c1;
    c3 += c2;
    cout << "c1 += c2 后:" << c3 << endl;

    cout << "\n===== 比较 =====" << endl;
    cout << boolalpha;
    cout << "c1 == c2 : " << (c1 == c2) << endl;   // false
    cout << "c1 != c2 : " << (c1 != c2) << endl;   // true

    cout << "\n===== 类型转换 =====" << endl;
    cout << "double(c1) = " << static_cast<double>(c1) << endl;   // 5

    return 0;
}

输出:

复制代码
c1 = 3+4i
c2 = 1-2i

===== 算术运算 =====
c1 + c2 = 4+2i
c1 - c2 = 2+6i
c1 * c2 = 11-2i
c1 / c2 = -1+2i
-c1     = -3-4i

===== 属性 =====
|c1| = 5
c1的共轭 = 3-4i

===== 标量乘法 =====
2.0 * c1 = 6+8i

===== 复合赋值 =====
c1 += c2 后:4+2i

===== 比较 =====
c1 == c2 : false
c1 != c2 : true

===== 类型转换 =====
double(c1) = 5

📝 第11章知识点总结

知识点 核心要点
运算符重载 operator运算符 函数,使自定义类型支持运算符操作
成员函数重载 左操作数是本类对象时使用,this 是左操作数
友元函数重载 左操作数不是本类时使用,可访问私有成员
重载 <</>> 必须是友元函数,必须返回流引用(支持链式操作)
赋值运算符 防自赋值 → 释放旧资源 → 深拷贝 → 返回 *this
下标运算符 [] 提供 const 和非 const 两个版本,可加边界检查
前缀/后缀 ++ 前缀无参返回引用,后缀有 int 哑参返回值
类型转换运算符 operator 目标类型() const,加 explicit 防止隐式转换
不可重载的运算符 :: . .* ?: sizeof
必须是成员函数 = [] () -> 这四个运算符必须重载为成员函数
相关推荐
yujunl2 小时前
NetCore常用的中间件说明
开发语言
comedate3 小时前
FMT_UNICODE 与 CUDA 编码配置专栏技术文档
c++·utf-8·nvcc
玖玥拾3 小时前
C/C++ 基础笔记(二)
c语言·c++
Hanniel3 小时前
Python 元类(下):进阶与实战建议
开发语言·python
会编程的土豆3 小时前
Go interface 底层的 itab 到底是什么
开发语言·后端·golang
千纸鹤の脉搏3 小时前
多线程的初步了解---进程与线程
java·开发语言·学习·线程
秋田君3 小时前
Qt 5.12.8 下载与安装教程(附网盘资源)
开发语言·qt
故事和你913 小时前
洛谷-【动态规划2】线性状态动态规划4
开发语言·数据结构·c++·算法·动态规划·图论
不吃土豆的马铃薯4 小时前
Socket 网络编程实战教程
linux·服务器·开发语言·网络·c++·算法