跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 类和对象(中):默认成员函数与运算符重载

讲解 C++ 类和对象的核心概念,包括默认成员函数、构造函数、析构函数、拷贝构造函数及赋值运算符重载。重点阐述了构造函数的初始化作用、析构函数的资源清理机制以及浅拷贝与深拷贝的区别。同时介绍了运算符重载的实现方式,特别是流插入输出和比较运算符的重载规则。此外还讲解了 const 成员函数对 this 指针的影响以及取地址运算符重载的特殊场景。通过 Stack 和 Date 类的示例代码,展示了内存管理与对象生命周期的最佳实践。

清心发布于 2026/3/23更新于 2026/8/1715K 浏览
C++ 类和对象(中):默认成员函数与运算符重载

一、类和对象

1.1 类的默认成员函数

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。我们要从两个方面去学习:

  • 第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
  • 第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?

文章配图

1.2 构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象 (我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前 Stack 和 Date 类中写的 Init 函数的功能,构造函数自动调用的特点就完美的替代了 Init。

构造函数的特点:

  • 函数名与类名相同。
  • 无返回值 (返回值啥都不需要给,也不需要写 void,不要纠结,C++ 规定如此)
  • 对象实例化时系统会自动调用对应的构造函数。
  • 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  • 默认构造函数是指 不需要传参数就能调用的构造函数,它包括三种情况:无参构造函数(没有参数)、全缺省构造函数(所有参数都有默认值),以及当你 没写任何构造函数时编译器自动生成的那个构造函数。这三者都属于 默认构造函数,但一个类中只能存在其中一个——不能同时定义无参和全缺省构造函数,否则调用时会产生歧义。需要注意的是,很多人误以为只有编译器生成的那个才叫默认构造函数,其实只要能无参调用的构造函数,都算默认构造函数。
  • 当类中未定义构造函数时,编译器自动生成的默认构造函数不会初始化内置类型成员(值不确定),但会调用自定义类型成员的默认构造函数;若该自定义类型没有默认构造函数,则会导致编译错误,此时必须通过初始化列表显式初始化。

常见的错误:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data {
private:
    int _year;
    int _month;
    int _day;
public:
    // 1.无参构造函数
    //Data()
    //{
    // _year = 1;
    // _month = 1;
    // _day = 1;
    //}
    // 2.带参构造函数
    Data(int year, int month, int day) { 
        _year = year; 
        _month = month; 
        _day = day; 
    }
    /*3.全缺省构造函数*/
    //Data(int year = 1, int month = 1, int day = 1)
    //{
    // _year = year;
    // _month = month;
    // _day = day;
    //}
    void Print() { cout << _year << "/" << _month << "/" << _day << endl; }
};
int main() {
    Data d1; //这里想要调用"默认构造函数",但是我注释了 1 和 3,写了带参的构造函数,所以编译器不再生成默认构造函数
    d1.Print();
    return 0;
}

文章配图

原因:你写了带参构造函数 → 编译器不再自动生成默认构造函数 → Date d1; 没有匹配的构造函数可用!

(2)

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data {
private:
    int _year;
    int _month;
    int _day;
public:
    //1.无参构造函数
    //Data()
    //{
    // _year = 1;
    // _month = 1;
    // _day = 1;
    //}
    //2.带参构造函数
    //Data(int year, int month, int day)
    //{
    // _year = year;
    // _month = month;
    // _day = day;
    //}
    /*3.全缺省构造函数*/
    Data(int year = 1, int month = 1, int day = 1) { 
        _year = year; 
        _month = month; 
        _day = day; 
    }
    void Print() { cout << _year << "/" << _month << "/" << _day << endl; }
};
int main() {
    //Data d1; //这里想要调用"默认构造函数",但是我注释了 1 和 3,写了带参的构造函数,所以编译器不再生成默认构造函数
    //d1.Print();
    Data d1;
    d1.Print();
    Data d2();
    return 0;
}

文章配图

Date d2(); 在 C++ 中不是创建对象!

它的意思是:声明一个名为 d2 的函数,该函数返回类型为 Date。

所以不要这样写:Date d2(),要么就别加括号,加括号就传参数。

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 申请空间失败");
        }
        _capacity = n;
        _top = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};
// 两个 Stack 实现队列
class MyQueue {
public:
    //编译器默认生成 MyQueue 的构造函数调用了 Stack 的构造,完成了两个成员的初始化
private:
    Stack pushst;
    Stack popst;
};
int main() {
    MyQueue mq;
    return 0;
}

文章配图

1.3 析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++ 规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没有 Destroy,其实就是没有资源需要释放,所以严格说 Date 是不需要析构函数的。析构函数的特点:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。(这里跟构造类似,也不需要加 void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数。
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如 Date;如果默认生成的析构就可以用,也就不需要显示写析构,如 MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如 Stack。
  7. 一个局部域的多个对象,C++ 规定后定义的对象先析构。
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 申请空间失败");
        }
        _capacity = n;
        _top = 0;
    }
    ~Stack() {
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};
// 两个 Stack 实现队列
class MyQueue {
public: // 显示写析构,也会自动调用 Stack 的析构
    /*~MyQueue() {}*/
private:
    Stack pushst;
    Stack popst;
};
int main() {
    Stack st1;
    Stack st2;
    return 0;
}

对比一下用 C++ 和 C 实现的 Stack 解决之前括号匹配问题 isValid,我们发现有了构造函数和析构函数确实方便了很多,不会再忘记调用 Init 和 Destroy 函数了,也方便了不少。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
    }
    ~Stack() {
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};
// 用最新加了构造和析构的 C++ 版本 Stack 实现
bool isValid(const char* s) {
    Stack st; //之前这边需要初始化
    while (*s) {
        if (*s == '[' || *s == '(' || *s == '{') {
            st.Push(*s);
        } else {
            // 右括号比左括号多,数量匹配问题
            if (st.Empty()) { //这边需要销毁
                return false;
            }
            // 栈里面取左括号
            char top = st.Top();
            st.Pop();// 顺序不匹配
            if ((*s == ']' && top != '[') || (*s == '}' && top != '{') || (*s == ')' && top != '(')) {
                //这边也需要销毁
                return false;
            }
        }
        ++s;
    }
    // 栈为空,返回真,说明数量都匹配 左括号多,右括号少匹配问题
    //这边也需要销毁
    return st.Empty();
}

1.4 拷贝构造函数

作用:就是用一个已经存在的、同类型的对象(即已初始化的对象)来初始化一个正在被创建的新对象(即尚未初始化的对象)

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

  • 拷贝构造函数是构造函数的—个重载。
  • 拷贝构造函数的第一个参数必须是 类类型对象的引用,使用 传值方式编译器直接报错, 也就是拷贝构造的参数如果不是引用会导致无穷递归(因为传值传参会调用拷贝构造)。拷贝构造函数也可以有多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  • C++ 规定 自定义类型对象进行拷贝行为必须调用 拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  • 若 未显式定义拷贝构造,编译器会 自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成 值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用它的拷贝构造。
  • 像 Date 这样的类成员变量全是 内置类型且 没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝 不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧:如果一个类显式实现了析构并释放资源,那么它就需要显式写拷贝构造,否则就不需要。
  • 传值返回会产生一个临时对象,调用 拷贝构造; 传引用返回,返回的是返回对象的别名(引用), 没有产生拷贝。但是如果返回对象是一个当前 函数局部域的局部对象,函数结束就销毁了,那么使用 引用返回是有问题的,这时的引用相当于一个野引用,类似于一个野指针。传引用返回可以减少拷贝,但一定要确保返回对象在当前函数结束后仍然存在,才能使用引用返回。所以在正确性和性能选择上,优先选择正确性。其次才是性能。
#include<iostream>
using namespace std;
class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) { 
        _year = year; 
        _month = month; 
        _day = day; 
    }
    // 编译报错:error C2652: 'Date': 非法的复制构造函数:第一个参数不应是'Date'
    // Date(Date d)
    Date(const Date& d) { 
        _year = d._year; 
        _month = d._month; 
        _day = d._day; 
    }
    Date(Date* d) { 
        _year = d->_year; 
        _month = d->_month; 
        _day = d->_day; 
    }
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
private:
    int _year;
    int _month;
    int _day;
};
void Func1(Date d) {
    cout << &d << endl;
    d.Print();
}
// Date Func2()
Date& Func2() {
    Date tmp(2024, 7, 5);
    tmp.Print();
    return tmp; // 危险!返回局部变量的引用
}
int main() {
    Date d1(2024, 7, 5);
    // C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,
    // 所以这里的 d1 传值传参给 d 要调用拷贝构造完成拷贝,
    // 传引用传参可以减少这里的拷贝。
    Func1(d1);
    cout << &d1 << endl;
    // 这里可以完成拷贝,但不是拷贝构造,只是一个普通的构造(接受指针)
    Date d2(&d1);
    d1.Print();
    d2.Print();
    // 这样写才是拷贝构造:通过同类型的对象初始化
    Date d3(d1);
    d3.Print();
    // 也可以这样写,这里也是拷贝构造
    Date d4 = d1;
    d4.Print();
    // Func2 返回了一个局部对象 tmp 的引用作为返回值
    // Func2 函数结束,tmp 对象就销毁了,相当于一个野引用
    Date ret = Func2();
    ret.Print();
    return 0;
}

两个栈来实现一个队列

#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
    }
    Stack(const Stack& st) { 
        // 需要对_a 指向资源创建同样大的资源再拷贝值
        _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
        if (nullptr == _a) {
            perror("malloc 申请空间失败!!!");
            return;
        }
        memcpy(_a, st._a, sizeof(STDataType) * st._top);
        _top = st._top;
        _capacity = st._capacity;
    }
    void Push(STDataType x) {
        if (_top == _capacity) {
            int newcapacity = _capacity * 2;
            STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType));
            if (tmp == NULL) {
                perror("realloc fail");
                return;
            }
            _a = tmp;
            _capacity = newcapacity;
        }
        _a[_top++] = x;
    }
    ~Stack() {
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};
// 两个 Stack 实现队列
class MyQueue {
public:
private:
    Stack pushst;
    Stack popst;
};
int main() {
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    // Stack 不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝
    // 会导致 st1 和 st2 里面的_a 指针指向同一块资源,析构时会析构两次,程序崩溃
    Stack st2 = st1;
    MyQueue mq1;
    // MyQueue 自动生成的拷贝构造,会自动调用 Stack 拷贝构造完成 pushst/popst
    // 的拷贝,只要 Stack 拷贝构造自己实现了深拷贝,他就没问题
    MyQueue mq2 = mq1;
    return 0;
}

二、赋值运算符重载

2.1 运算符重载

  • 当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调用对应的运算符重载;若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,它的名字是由 **operator 和后面要定义的运算符共同构成。**和其他函数一样,它也具有其返回类型、参数列表以及函数体。
  • 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数;二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
  • 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
  • 不能通过连接语法中没有的符号来创建新的操作符:比如 operator@。
  • 操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)。
  • 一个类需要重载哪些运算符,是看哪些运算符重载后有意义。比如 Date 类重载 **operator-**就有意义(可以计算两个日期之间的天数差),但是重载 operator+ 就没有意义(两个日期相加没有合理的语义)。
  • 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好地区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,与前置 ++ 构成函数重载,方便区分。
  • 重载 << 和 >> 时,需要重载为全局函数,因为如果重载为成员函数,this 指针会默认抢占第一个形参位置,而第一个形参位置对应的是左侧运算对象,调用时就会变成 对象 << cout,不符合使用习惯和可读性。重载为全局函数时,可以把 ostream/istream 放在第一个形参位置,第二个形参位置放类类型对象,从而实现如 cout << 对象 的自然语法。

Date.h

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
class Date {
    //友元函数声明,使得类外面的函数可以使用类里面的 private
    friend ostream& operator<<(ostream& out,const Date& d);
    friend istream& operator>>(istream& in, Date& d);
public:
    bool CheckDate();
    //默认构造——全缺省默认构造
    Date(int year = 1900, int month = 1, int day = 1);
    //不改变成员变量的都可以把 const 加上
    void Print()const;
    // const 修饰的是*this
    int GetMonthDay(int year, int month) { 
        assert(_month > 0 && _month < 13);
        static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
        if (month == 2 && ((year % 4 == 0) && year % 100 != 0) || (year % 400) == 0) {
            return 29;
        }
        return monthDayArray[month];
    }
    bool operator<(const Date& d);
    bool operator<=(const Date& d);
    bool operator>(const Date& d);
    bool operator>=(const Date& d);
    bool operator==(const Date& d);
    bool operator!=(const Date& d);
    // d1 += 天数
    Date& operator+=(int day);
    Date operator+(int day);
    // d1 -= 天数
    Date& operator-=(int day);
    Date operator-(int day);
    //前置++和后置++
    //d1++ = d1.operator++(0)
    Date operator++(int);
    //++d1 = d1.operator++(),后置++是正常的
    Date operator++();
    // d1 - d1
    int operator-(const Date& d);
    ////流插入的运算符重载
    //void operator<<(ostream& out);
private:
    int _year;
    int _month;
    int _day;
};
//流插入的运算符重载
ostream& operator<<(ostream& out,const Date& d);
//流提取的运算符重载
istream& operator>>(istream& in, Date& d);

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
bool Date::CheckDate() {
    if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) {
        return false;
    } else {
        return true;
    }
}
//默认构造函数,用来初始化
Date::Date(int year, int month, int day) {
    _year = year;
    _month = month;
    _day = day;
    if (!CheckDate()) {
        cout << "非法日期:";
        Print();
    }
}
//打印日期
//void Date::Print(const Date* this)和下面那个等价
void Date::Print()const {
    cout << _year << "-" << _month << "-" << _day << endl;
}
//+=
Date& Date::operator+=(int day) {
    //注意处理负数情况
    if (day < 0) {
        return *this -= (-day);
    }
    _day += day;
    while (_day > GetMonthDay(_year, _month)) {
        _day -= GetMonthDay(_year, _month);
        _month++;
        if (_month == 13) {
            _year++;
            _month = 1;
        }
    }
    return *this;
}
//+
Date Date::operator+(int day) {
    Date tmp = *this;
    tmp += day;
    return tmp;
}
// d1 -= 天数
Date& Date::operator-=(int day) {
    //这边和+=那边一样,所以测试用例覆盖很重要
    if (day < 0) {
        return *this += (-day);
    }
    _day -= day;
    while (_day <= 0) {
        _month--;
        if (_month == 0) {
            _year--;
            _month = 12;
        }
        _day += GetMonthDay(_year, _month);
    }
    return *this;
}
// -
Date Date::operator-(int day) {
    Date tmp = *this;
    tmp -= day;
    return tmp;
}
//<
bool Date::operator<(const Date& d) {
    if (_year < d._year) {
        return true;
    } else if (_year == d._year) {
        if (_month < d._month) {
            return true;
        } else {
            if (_month == d._month) {
                return _day < d._day;
            }
        }
    }
    return false;
}
bool Date::operator==(const Date& d) {
    return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator<=(const Date& d) {
    return *this < d || *this == d;
}
bool Date::operator>(const Date& d) {
    return !(*this <= d);
}
bool Date::operator>=(const Date& d) {
    return !(*this < d);
}
bool Date::operator!=(const Date& d) {
    return !(*this == d);
}
//前置++和后置++
//d1++ = d1.operator++(0)
Date Date::operator++(int) {
    //前置返回++之前的值,所以需要拷贝一份出来
    Date tmp = *this;
    *this += 1;
    return tmp;
}
//++d1 = d1.operator++(),后置++是正常的
Date Date::operator++() {
    *this += 1;
    return *this;
}
// d1 - d1
int Date::operator-(const Date& d) {
    int flag = 1;
    Date max = *this;
    Date min = d;
    if (max < min) {
        max = d;
        min = *this;
        flag = -1;
    }
    //到了这,就不用管谁正谁负了
    int n = 0;
    while (max != min) {
        ++min;
        ++n;
    }
    return flag * n;
}
//流提取和流插入的运算符重载
//void Date::operator<<(ostream& out)
//{
// out << _year << "年" << _month << "月" << _day << endl;
//}
ostream& operator<<(ostream& out,const Date& d) {
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}
istream& operator>>(istream& in, Date& d) {
    while (1) {
        cout << "请依次输入年月日" << endl;
        in >> d._year >> d._month >> d._day;
        if (!d.CheckDate()) {
            cout << "输入错误!!" << endl;
            d.Print();
            cout << "请重新输入!" << endl;
        } else {
            break;
        }
    }
    return in;
}

2.2 赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象之间的拷贝赋值。这里要注意跟拷贝构造区分:拷贝构造用于用一个已存在的对象去初始化另一个正在创建的对象。

赋值运算符重载的特点:

  • 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算符重载的参数建议写成 const 当前类类型引用,否则会因传值传参而产生不必要的拷贝。
  • 赋值运算符重载应有返回值,且建议返回当前类类型的引用。引用返回可以提高效率,而提供返回值的目的是为了支持连续赋值场景(如 a = b = c)。
  • 没有显式实现时,编译器会自动生成一个默认赋值运算符重载。默认赋值运算符重载的行为跟默认拷贝构造函数类似:对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用其赋值运算符重载函数。
  • 像 Date 这样的类,成员变量全是内置类型且没有指向任何资源,编译器自动生成的赋值运算符重载就可以完成所需的拷贝,因此不需要我们显式实现赋值运算符重载。
    像 Stack 这样的类,虽然成员也都是内置类型,但 _a 指向了动态分配的资源,编译器自动生成的赋值运算符重载执行的是值拷贝/浅拷贝,会导致多个对象共享同一块内存,析构时可能重复释放,因此不符合需求,需要我们自己实现深拷贝(即对指向的资源也进行拷贝)。
    像 MyQueue 这样的类,内部主要包含自定义类型 Stack 成员,编译器自动生成的赋值运算符重载会调用 Stack 的赋值运算符重载,只要 Stack 的赋值行为正确,MyQueue 就无需显式实现自己的赋值运算符重载。
  • 这里还有一个小技巧:如果一个类显式实现了析构函数并释放资源,那么它通常也需要显式实现赋值运算符重载(以及拷贝构造函数);否则,可以依赖编译器生成的默认版本。
#include<iostream>
using namespace std;
class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) { 
        _year = year; 
        _month = month; 
        _day = day; 
    }
    Date(const Date& d) {
        cout << " Date(const Date& d)" << endl;
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    // 传引用返回减少拷贝
    // d1 = d2;
    Date& operator=(const Date& d) {
        // 避免自赋值检查
        if (this != &d) {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        // d1 = d2 表达式的返回对象应该为 d1,也就是*this
        return *this;
    }
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
private:
    int _year;
    int _month;
    int _day;
};
int main() {
    Date d1(2024, 7, 5);
    // 需要注意这里是拷贝构造,不是赋值重载
    Date d2(d1);
    Date d3(2024, 7, 6);
    d1 = d3;
    // 请牢牢记住赋值重载完成两个已经存在的对象直接的拷贝赋值
    // 拷贝构造用于一个对象拷贝初始化给另一个要创建的对象
    Date d4 = d1;
    return 0;
}

三、取地址运算符重载

3.1 const 成员函数

  • 将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数时应放在成员函数参数列表的后面。
  • const 实际修饰的是该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。当 const 修饰 Date 类的 Print 成员函数时,其隐含的 this 指针类型由 Date* const this 变为 const Date* const this。
#include<iostream>
using namespace std;
class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) { 
        _year = year; 
        _month = month; 
        _day = day; 
    }
    // void Print(const Date* const this) const
    void Print() const {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main() {
    // 这里非 const 对象也可以调用 const 成员函数是一种权限的缩小
    Date d1(2024, 7, 5);
    d1.Print();
    const Date d2(2024, 8, 5);
    d2.Print();
    return 0;
}

3.2 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载。一般情况下,这两个函数编译器自动生成的版本已经足够使用,不需要显式实现。除非在一些特殊场景下,例如我们不希望别人获取当前类对象的真实地址,就可以自行实现这两个运算符重载,并返回一个无效或伪装的地址。

class Date {
public:
    Date* operator&() { 
        return this; // return nullptr; 
    }
    const Date* operator&() const { 
        return this; // return nullptr; 
    }
private:
    int _year; // 年
    int _month; // 月
    int _day; // 日
};

目录

  1. 一、类和对象
  2. 1.1 类的默认成员函数
  3. 1.2 构造函数
  4. 1.3 析构函数
  5. 1.4 拷贝构造函数
  6. 二、赋值运算符重载
  7. 2.1 运算符重载
  8. 2.2 赋值运算符重载
  9. 三、取地址运算符重载
  10. 3.1 const 成员函数
  11. 3.2 取地址运算符重载
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 利用 KAT-Coder-Pro V1 零代码开发小红书卡片 MCP 工具
  • CentOS 部署 Teemii 搭建私人漫画库
  • macOS 12.7.6 部署 Ollama 与 Dify 避坑指南
  • PaddleOCR-VL-WEB 核心优势与本地部署推理教程
  • 策略模式详解:将 if-else 转化为可切换算法
  • C++ 与 Linux 基础:深入理解虚拟文件系统 VFS
  • Java ArrayList 核心解析:底层结构、使用与扩容机制
  • 探索云开发Copilot,AI如何重塑开发流程?
  • C++ 多态详解:概念、实现与底层原理
  • 系统开发成本高企?低代码的工程化降本路径
  • Trae IDE 安装与使用指南:字节跳动 AI 原生开发环境
  • 飞算 JavaAI 生成 SpringBoot 项目流程解析
  • WeBASE 一键部署配置与下载问题解决方案
  • Ubuntu 安装硬盘分区方案与实践
  • Java 多线程三大特性详解:原子性、可见性、有序性
  • 使用 CrewAI 和 Gemini 1.5 构建多智能体系统实战指南
  • Spring Cloud OpenFeign 远程调用最佳实践与参数传递
  • pyEIT 电阻抗断层成像算法完整解析
  • Spring Cloud Gateway 过滤器工厂详解
  • Flutter serial 库在鸿蒙系统的适配指南与串口通信实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online