当前位置: 首页 > news >正文

类与对象(二)——类的 6 个默认成员函数

目录

1. 类的 6 个默认成员函数

2. 构造函数

2.1 概念

2.2 基本特征

2.3 默认构造函数

2.3.1 默认构造函数分类

2.3.2 编译器自动生成的默认构造函数

2.3.3 默认构造函数与成员变量初始化

2.3.4 一个类中只能有一个默认构造函数

3. 析构函数

3.1 概念

3.2 基本特征

3.3 默认析构函数

4. 拷贝构造函数

4.1 概念

4.2 基本特征

4.3 默认拷贝构造函数

4.4 拷贝构造函数典型调用场景

5. const成员函数

5.1 定义与作用

5.2 调用规则

6. 

6.1 运算符重载

6.1.1

6.1.2.1 运算符重载(在类内)作类的成员函数

6.1.2.2 运算符重载(在类外)作全局函数

6.1.2 注意事项

6.2 赋值运算符重载

6.2.1 基本格式

6.2.2 注意事项

6.3 前/后置 ++ 重载

6.4 练习:日期类的实现

7. 取地址操作符重载和 const 取地址操作符重载

7.1 使用建议

7.2 取地址操作符重载

7.3 const 取地址操作符重载

💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!

👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!


1. 类的 6 个默认成员函数

如果一个类中什么成员都不写,编译器会自动生成 6 个默认成员函数。

默认成员函数:用户没有显式实现时,编译器会生成的成员函数。

名称作用典型应用场景
构造函数创建对象时自动调用,为对象的数据成员赋初值初始化对象,如创建日期对象时设置初始日期
析构函数对象销毁时自动调用,释放对象占用的资源释放动态分配的内存,如释放栈对象中动态分配的数组
拷贝构造函数使用已存在的对象创建新对象时自动调用,复制对象内容

(1)使用已存在对象创建新对象时

(2)函数的形参和实参类型都为类类型对象时

(3)函数返回值类型为类类型对象时

赋值运算符重载实现对象之间的赋值操作,将一个对象的内容复制给另一个对象对象之间的赋值,如 d2 = d1
取地址操作符重载返回对象的地址需要获取对象地址时,一般使用默认重载,特殊情况可显式定义返回指定内容
const 取地址操作符重载返回 const 对象的地址获取 const 对象的地址,一般使用默认重载,特殊情况可显式定义返回指定内容

2. 构造函数

2.1 概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,用于保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。

2.2 基本特征

  • 函数名与类名相同
  • 无返回值
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载

示例:

#include <iostream>
using namespace std;

class Date
{
public:
    // 1. 无参构造函数
    Date() 
    {
        _year = 2025;
        _month = 3;
        _day = 13;
    }

    // 2. 带参构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Print() const
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1; // 调用无参构造函数
    // 注意:如果通过无参构造函数创建对象时,对象后不跟括号,否则就成了函数声明
    // Date d2(); // 声明了d2函数,该函数无参,返回一个日期类型的对象

    Date d3(2025, 3, 13); // 调用带参的构造函数

    cout << "d1 date: ";
    d1.Print();
    cout << "d3 date: ";
    d3.Print();

    return 0;
}

2.3 默认构造函数

2.3.1 默认构造函数分类

默认构造函数是在创建对象时无需显式提供参数就能调用的构造函数。

C++ 里,默认构造函数可分为三类:

  • (1)编译器自动生成的默认构造函数

  • (2)无参构造函数

  • (3)全缺省构造函数

2.3.2 编译器自动生成的默认构造函数

  • 若类中没有显式定义任何构造函数,C++ 编译器会自动为该类生成一个无参的默认构造函数。一旦用户显式定义编译器将不再生成。

示例:

#include <iostream>
using namespace std;

class Date
{
public:

    /*Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }*/

    void Print() const
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    // 将Date类中构造函数屏蔽后,代码编译成功
    // 将Date类中构造函数放开,代码编译失败

    return 0;
}

2.3.3 默认构造函数与成员变量初始化

在 C++11 标准之前,编译器生成的默认构造函数,

  • 对于自定义类型成员变量,会调用其对应的默认构造函数来进行初始化;
  • 对于内置类型成员变量,不会进行初始化,这些成员变量的值是未定义的(随机值)。

C++ 类型分为基本类型(内置类型)和自定义类型。内置类型如 int、float、void 等。自定义类型是程序员根据需求使用class/struct/union等自己定义的类型。

#include <iostream>

class Time 
{
public:
    // 无参构造函数
    Time() 
    {
        std::cout << "Time()" << std::endl;
        _hour = 0;
        _minute = 0;
        _second = 0;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date 
{
private:
    // 内置类型
    int _year;
    int _month;
    int _day;

    // 自定义类型
    Time _t;
};

int main() 
{
    Date d;
    return 0;
}

在 Date 类中未显式定义构造函数,编译器会生成一个无参默认构造函数,而_t 是一个自定义的 Time 类的对象,所以在创建 Date 对象时,会自动调用 Time 类的构造函数来初始化 _t

C++11 标准针对默认构造函数不初始化内置类型成员变量的问题进行了改进,允许在类中声明内置类型成员变量时直接为其提供默认值,这样,即使没有显式定义构造函数,类的对象也能保证其内置类型成员变量有一个合理初始值。

class Date 
{
private:
    int _year = 2025;
    int _month = 3;
    int _day = 13;

    Time _t;
};

2.3.4 一个类中只能有一个默认构造函数

如果同时定义了无参构造函数和全缺省构造函数,当创建对象时不传递参数,编译器将无法确定调用哪个构造函数,从而产生编译错误。

例如以下代码将编译失败:

#include <iostream>

class Date 
{
public:
    Date() 
    {
        _year = 1900;
        _month = 1;
        _day = 1;
    }

    // 全缺省构造函数
    Date(int year = 1900, int month = 1, int day = 1) 
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() 
{
    Date d; 
    return 0;
}

3. 析构函数

3.1 概念

在对象销毁(生命周期结束)时会自动调用析构函数,完成对象中资源的清理工作,如释放动态分配的内存、关闭打开的文件等,但不负责对象本身的销毁,对象本身的销毁由编译器完成。

3.2 基本特征

  • 析构函数名 = ~类名
  • 无参数,无返回值类型
  • 在对象销毁(生命周期结束)时自动调用
  • 一个类只能有一个析构函数,析构函数不能重载
#include <iostream>
#include <cstdlib>

typedef int DataType;

class Stack 
{
public:
    // 构造函数,用于初始化栈
    Stack(size_t capacity = 3) 
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (_array == nullptr) 
        {
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = capacity;
        _size = 0;
    }

    void Push(DataType data) 
    {
        // 检查栈是否已满,若满了则扩容
        // ......
       
        _array[_size] = data;
        _size++;
    }

    // 出栈
    void Pop() 
    {
        // 检查栈是否为空
        if (_size > 0) 
        {
            _size--;
        }
    }

    // 获取栈顶元素
    DataType Top() 
    {
        if (_size > 0) 
        {
            return _array[_size - 1];
        }
        // 栈为空,这里简单返回 0 作为示例
        return 0;
    }

    bool Empty() 
    {
        // 如果栈的大小为 0,则栈为空
        return _size == 0;
    }

    size_t Size() 
    {
        return _size;
    }

    // 析构函数
    ~Stack() 
    {
        if (_array) 
        {
            free(_array);
            _array == nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array;
    int _capacity;
    int _size;
};

void TestStack() 
{
    // 创建一个栈对象
    Stack s;
    s.Push(1);
    s.Push(2);
    std::cout << "栈顶元素: " << s.Top() << std::endl;

    s.Pop();
    std::cout << "弹出元素后栈顶元素: " << s.Top() << std::endl;
    std::cout << "栈的大小: " << s.Size() << std::endl;
    
    std::cout << "栈是否为空: " << (s.Empty() ? "是" : "否") << std::endl;
}

int main() 
{
    TestStack();
    return 0;
}

3.3 默认析构函数

  • 若未显式定义,编译器会自动生成默认的析构函数。
  • 如果类中没有申请资源,可以不写析构函数,直接使用编译器生成的默认析构函数;有资源申请时,一定要写析构函数,否则会造成资源泄漏
  • 编译器生成的默认析构函数,对于自定义类型成员变量,会调用其对应的默认析构函数来进行资源清理。而对于内置类型成员变量,由于系统在对象销毁时会自动回收它们所占用的内存,不需额外的清理操作,所以对它们不作处理。
#include <iostream>

class Time 
{
public:
    // 析构函数,在对象销毁时输出信息
    ~Time() 
    {
        std::cout << "~Time()" << std::endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date 
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};

int main() 
{
    Date d;
    return 0;
}

4. 拷贝构造函数

4.1 概念

拷贝构造函数是一种特殊的构造函数,仅有单个形参,该形参为对本类类型对象的引用(常用 const 修饰),在利用已存在的类类型对象创建新对象时由编译器自动调用。

4.2 基本特征

  • 拷贝构造函数是构造函数的一个重载形式
  • 拷贝构造函数的名称和类名一致
  • 拷贝构造函数只有单个形参,该形参必须是对本类类型对象的引用。使用传值方式时编译器会报错,因为会引发无穷递归调用

定义拷贝构造函数的基本语法:

class ClassName 
{
public:
    // 拷贝构造函数
    ClassName(const ClassName& other) 
    {
        // 如:
        _year = other._year;
        _month = other._month;
        _day = other._day;
    }

    // 其他成员函数和成员变量
};

    4.3 默认拷贝构造函数

    • 未显式定义拷贝构造函数时,编译器会自动生成默认拷贝构造函数。
    • 默认拷贝构造函数将一个对象的内存内容逐字节地复制到另一个对象的内存空间中,这种方式属于浅拷贝(值拷贝)。

    浅拷贝(值拷贝):一种对象复制的方式。它只关注对象在内存中的二进制数据,将一个对象的内存内容逐字节地复制到另一个对象的内存空间中。对于基本数据类型(如 intfloatchar 等),浅拷贝会直接复制其值;对于对象类型的成员变量,浅拷贝只会复制对象的地址,而不会复制对象本身。

    • 类不涉及资源申请(动态分配内存/打开文件等)时,拷贝构造函数写或不写都行。类涉及资源申请时,必须显式定义拷贝构造函数,否则默认进行浅拷贝。浅拷贝对于申请来的资源不会进行妥善处理。

    4.4 拷贝构造函数典型调用场景

    • (1)使用已存在对象创建新对象时
    • (2)函数的形参和实参类型都为类类型对象时
    • (3)函数返回值类型为类类型对象时
    ​
    #include <iostream>
    
    class Date 
    {
    public:
        // 带参构造函数
        Date(int year, int month, int day) 
        {
            _year = year;
            _month = month;
            _day = day;
            std::cout << "Date(int, int, int): " << this << std::endl;
        }
    
        // 拷贝构造函数
        Date(const Date& d) 
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
            std::cout << "Date(const Date& d): " << this << std::endl;
        }
    
        // 析构函数
        ~Date() 
        {
            std::cout << "~Date(): " << this << std::endl;
        }
    
    private:
        int _year;
        int _month;
        int _day;
    };
    
    // 测试函数,接收一个 Date 类型的对象作为参数,并返回一个 Date 类型的对象
    Date Test(Date d) 
    {
        // 使用传入的对象 d 拷贝构造一个新对象 temp
        Date temp(d);
        return temp;
    }
    
    int main() 
    {
        Date d1(2022, 1, 13);
    
        // d1 传递给 Test 函数时,会调用拷贝构造函数创建形参 d
        Test(d1);
    
        return 0;
    }
    
    ​
    1. 使用已存在对象创建新对象:在 Test 函数内部,Date temp(d); 使用已存在的对象 d 拷贝构造新对象 temp
    2. 函数的形参和实参类型都为类类型对象:调用 Test(d1); 时,实参 d1 和形参 d 均为 Date 类类型的对象,会调用 Date 类的拷贝构造函数创建形参 d
    3. 函数返回值类型为类类型对象Test 函数返回 Date 类类型对象 temp 时,理论上可能会调用拷贝构造函数(没有编译器优化的情况下,会调用 Date 类的拷贝构造函数。然而,现代编译器通常会进行优化,编译器可能直接在调用 Test 函数的地方构造 temp 对象,从而避免额外的拷贝构造函数调用,提高程序执行效率)。

    5. const成员函数

    5.1 定义与作用

    const修饰的成员函数称为const成员函数。

    const修饰类成员函数时,实际修饰的是该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

    5.2 调用规则

    调用情况是否允许原因
    const对象调用非const成员函数不允许const对象的this指针是const类型,而非const成员函数的this指针是非const类型,类型不匹配。
    const对象调用const成员函数允许const对象的this指针可以隐式转换为const类型的this指针,const成员函数不会修改对象成员。
    const成员函数内调用非const成员函数不允许const成员函数的this指针是const类型,而非const成员函数的this指针是非const类型,不能保证成员不被修改。
    const成员函数内调用const成员函数允许const成员函数的this指针可以隐式转换为const类型的this指针,从而调用const成员函数。
    • const成员函数不能修改对象的成员变量,也不能调用非const成员函数。

    • const对象只能调用const成员函数,而非const对象可以调用const和非const成员函数。

    • 在函数重载时,const成员函数和非const成员函数可以作为两个不同的函数存在,编译器会根据对象是否为const来选择合适的函数。

    示例:

    ​
    #include <iostream>
    
    class Date 
    {
    public:
        Date(int year, int month, int day) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        // 非const成员函数
        void Print() 
        {
            std::cout << "Print()" << std::endl;
            std::cout << "year:" << _year << std::endl;
            std::cout << "month:" << _month << std::endl;
            std::cout << "day:" << _day << std::endl << std::endl;
        }
    
        // const成员函数
        void Print() const 
        {
            std::cout << "Print()const" << std::endl;
            std::cout << "year:" << _year << std::endl;
            std::cout << "month:" << _month << std::endl;
            std::cout << "day:" << _day << std::endl << std::endl;
        }
    
    private:
        int _year;  
        int _month;
        int _day;   
    };
    
    int main() 
    {
        Date d1(2022, 1, 13);
        d1.Print(); // 调用非const版本的Print()
    
        const Date d2(2022, 1, 13);
        d2.Print(); // 调用const版本的Print()
    
        return 0;
    }
    
    ​

    6. 

    6.1 运算符重载

    为了让代码更易读,C++ 引入运算符重载。

    运算符重载本质上是一个特殊函数,它有返回值类型、函数名和参数列表。

    通过运算符重载,自定义类型的对象也能像内置类型一样使用运算符进行操作。

    ​
    返回类型 operator运算符(参数列表) 
    {
        
    }
    
    ​// 函数名为 operator运算符
    // 参数写法:const 类名& 形参名

    6.1.1

    6.1.1.1 运算符重载(在类内)作类的成员函数

    形参数量 = 实际操作数数量 - 1

    因为成员函数有一个隐藏的 this 指针,该指针代表调用该函数的对象。this 指针隐式地作为运算符的一个操作数。

    示例:

    #include <iostream>
    
    class MyClass 
    {
    private:
        int value;
    public:
        // 构造函数
        MyClass(int v = 0) : value(v) {}
        // : 是成员初始化列表的起始符号
        // value(v) 表示把传入的参数 v 的值赋给 value
    
        // 重载 + 运算符
        MyClass operator+(const MyClass& other) const 
        // 参数other是一个常量引用,指向另一个MyClass对象,用于表示加法运算的右操作数
        {
            // 创建一个新的MyClass对象,其value成员变量的值 = 当前对象的value + other对象的value之和
            return MyClass(value + other.value);
        }
    
        void print() const 
        {
            std::cout << value << std::endl;
        }
    };
    
    int main() 
    {
        // 创建对象,初始化 value
        MyClass a(10);
        MyClass b(20);
    
        MyClass c = a + b;
        // a + b 会被编译器转换为 a.operator+(b)
    
        c.print();
    
        return 0;
    }
    6.1.1.2 运算符重载(在类外)作全局函数

    形参数量 = 实际操作数数量

    注意:当进行全局运算符重载时,要保证重载函数至少有一个参数是自定义类型。因为如果所有参数都是内置类型,就相当于对内置类型的运算符进行重载,这是不被允许的。

    示例:

    #include <iostream>
    
    class MyClass 
    {
    private:
        int value;
    public:
        MyClass(int v = 0) : value(v) {}
        int getValue() const 
        {
            return value;
        }
    };
    
    MyClass operator+(const MyClass& a, const MyClass& b) 
    {
        return MyClass(a.getValue() + b.getValue());
    }
    
    int main() 
    {
        MyClass a(10);
        MyClass b(20);
    
        MyClass c = a + b;
        // a + b 会被编译器转换为 operator+(a, b)
    
        std::cout << c.getValue() << std::endl;
    
        return 0;
    }

    6.1.2 注意事项

    • 不能把多个符号连起来创造新的运算符:像 operator@ 这种写法是不允许的。
    • 必须有类类型参数:重载运算符时,参数列表里至少要有一个类类型的参数。这样才能针对自定义的类对象进行运算符操作。
    • 内置运算符含义不变:内置类型(如 int)的运算符,其原本的含义不能被改变。
    • 不可重载的运算符:有 5 个运算符不能被重载,分别是  .* :: sizeof ?: 和  . (这一点在笔试选择题中常出现)。

    6.2 赋值运算符重载

    6.2.1 基本格式

    • (1)返回值类型T& 。以引用形式返回,避免了返回时对象的复制,从而提高返回效率。而且设置返回值,是为了能实现连续赋值操作,例如a = b = c 。
    • (2)参数类型const T& 。相比于传值,引用传递不需要额外复制一份对象,能节省时间和空间,提高传参效率。
    • (3)自我赋值检测:在函数内部,要检查是否是自己给自己赋值。如果不检测处理,可能会导致一些不必要的错误,比如重复释放资源等问题。
    • (4)返回 *this :函数最后返回 *this 。这样做是为了配合连续赋值。当执行a = b = c时,b = c执行完后会返回b(也就是*this),然后再将这个结果赋值给a ,从而实现连续赋值的功能 。

    示例:

    class Date 
    {
    public:
        Date(int year = 2025, int month = 3, int day = 19) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        Date(const Date& d) 
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    
        Date& operator=(const Date& d) 
        // (1)               (2)
        {
            if (this != &d)  // (3) 
            {
                _year = d._year;
                _month = d._month;
                _day = d._day;
            }
            return *this;  // (4)
        }
    
    private:
        int _year;
        int _month;
        int _day;
    };

    6.2.2 注意事项

    • 赋值运算符重载只能(在类内)作类的成员函数,不能(在类外)作全局函数
    • 未显式实现时,编译器自动生成一个默认的赋值运算符重载,以值的方式逐字节拷贝(浅拷贝)
    • 需要复制一个对象的状态到另一个对象时,可使用赋值运算符重载来实现。但编译器自动生成的默认赋值运算符重载进行浅拷贝,浅拷贝可能无法正确复制对象的状态。所以,需要自定义赋值运算符重载函数来实现深拷贝。
    • 内置类型成员变量直接赋值,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
      ​
      #include <iostream>
      
      class Time 
      {
      public:
          Time() 
          {
              _hour = 10;
              _minute = 10;
              _second = 10;
          }
          Time& operator=(const Time& t) 
          {
              if (this != &t) 
              {
                  _hour = t._hour;
                  _minute = t._minute;
                  _second = t._second;
              }
              return *this;
          }
          void printTime() const 
          {
              std::cout << _hour << ":" << _minute << ":" << _second << std::endl;
          }
      private:
          int _hour;
          int _minute;
          int _second;
      };
      
      class Date 
      {
      public:
          Date& operator=(const Date& d) 
          {
              if (this != &d) 
              {
                  // 内置类型成员变量直接赋值
                  _year = d._year;
                  _month = d._month;
                  _day = d._day;
      
                  // 自定义类型成员变量调用其对应的赋值运算符重载函数完成赋值
                  _t = d._t;
                  // _t属于Time类型的自定义对象
                  // _t = d._t;会调用 Time& operator=(const Time& t)
                  // 从而把d._t的值赋给当前对象的_t
              }
              return *this;
          }
      
          void printDate() const 
          {
              std::cout << _year << "-" << _month << "-" << _day << " ";
      
              _t.printTime();
              // _t为Date类的Time类型成员变量,printTime()是Time类的成员函数
              // _t.printTime();表示调用_t对象的printTime()函数
          }
      private:
          int _year = 2025;
          int _month = 3;
          int _day = 19;
      
          // 自定义类型
          Time _t;
      };
      
      int main() 
      {
          Date d1;
          Date d2;
      
          // 修改 d2 的时间、日期信息
          d2 = Date();
          d2 = Date();
      
          // 调用 Date 类的赋值运算符重载函数,将 d2 的值赋给 d1
          // 使得两者的日期和时间信息一致,保持在数据层面的一致,方便在程序里对对象进行操作和管理
          d1 = d2;
      
          std::cout << "d1 的日期和时间信息: ";
          d1.printDate();
      
          return 0;
      }
      
      ​

    • 如果类中未涉及资源管理(动态分配内存/打开文件等),赋值运算符重载是否实现都可;一旦涉及资源管理,则必须要实现

    示例:创建两个栈对象,将一个栈的内容赋给另一个

    #include <iostream>
    #include <cstdlib>
    
    typedef int DataType;
    
    class Stack 
    {
    public:
        // 构造函数
        Stack(size_t capacity = 10) 
        {
            _array = (DataType*)malloc(capacity * sizeof(DataType));
            if (_array == nullptr)
            {
                perror("malloc申请空间失败");
                return;
            }
            _size = 0;
            _capacity = capacity;
        }
    
        // 拷贝构造函数,用于使用一个已有的栈对象创建一个新的栈对象
        // other 是要拷贝的栈对象的引用
        Stack(const Stack& other) 
        {
            _size = other._size;   // 复制大小
            _capacity = other._capacity;   // 复制容量
            _array = (DataType*)malloc(_capacity * sizeof(DataType));
            if (_array == nullptr) 
            {
                perror("malloc申请空间失败");
                return;
            }
            // 复制元素
            for (size_t i = 0; i < _size; ++i) 
            {
                _array[i] = other._array[i];
            }
        }
    
        // 赋值运算符重载,用于将一个栈对象赋值给另一个栈对象
        // other 是要赋值的栈对象的引用
        Stack& operator=(const Stack& other) 
        {
            if (this != &other) 
            {
                free(_array);   // 释放当前栈对象的原有内存
    
                _size = other._size;
                _capacity = other._capacity;
                _array = (DataType*)malloc(_capacity * sizeof(DataType));
                if (_array == nullptr) 
                {
                    perror("malloc申请空间失败");
                    return *this;
                }
                for (size_t i = 0; i < _size; ++i) 
                {
                    _array[i] = other._array[i];
                }
            }
            return *this;
        }
    
        void Push(const DataType& data) 
        {
            _array[_size] = data;
            _size++;
        }
    
        // 析构函数
        ~Stack() 
        {
            if (_array) 
            {
                free(_array);
                _array = nullptr;
                _capacity = 0;
                _size = 0;
            }
        }
    
    private:
        DataType* _array;
        size_t _size;
        size_t _capacity;
    };
    
    int main() 
    {
        Stack s1;
        s1.Push(1);
        s1.Push(2);
        s1.Push(3);
        s1.Push(4);
    
        Stack s2;
        s2 = s1;
    
        return 0;
    }

    6.3 前/后置 ++ 重载

    对比项前置 ++ 重载后置 ++ 重载
    返回值类型T&(对象的引用)T(对象的值拷贝)
    函数签名T& operator++()T operator++(int)
    额外参数说明需要一个额外的 int 类型参数,是 C++ 编译器用来区分前置和后置 ++ 的约定,实际调用时无需传递该参数
    实现逻辑(1)直接对对象的数据成员进行加 1 操作。
    (2)返回当前对象的引用,以便支持连续赋值或操作。
    (1)先保存当前对象的一个拷贝。
    (2)对原对象进行加 1 操作。
    (3)返回保存的拷贝(即自增前的对象状态)。
    调用方式先自增,再使用先使用,再自增
    效率由于返回的是引用,避免了拷贝,效率较高。由于需要创建临时对象的拷贝,效率相对较低。
    #include <iostream>
    
    class Date 
    {
    public:
        Date(int year = 1900, int month = 1, int day = 1) 
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        // 前置++重载:返回+1之后的新值
        Date& operator++() 
        {
            _day += 1;
            return *this;
        }
    
        // 后置++重载:返回+1之前的旧值
        Date operator++(int) 
        {
            Date temp(*this);
            _day += 1;
            return temp;
        }
    
        void print() const 
        {
            std::cout << _year << "-" << _month << "-" << _day << std::endl;
        }
    
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main() 
    {
        Date d; // 默认构造,使用全缺省值
        Date d1(2025, 3, 19); // 指定构造
    
        d = d1++;   // 先将d1的值赋给d,然后d1自增
        std::cout << "后置++:" << std::endl;
        d.print();
        d1.print();
    
        d = ++d1;   // 先将d1自增,然后将自增后的值赋给d
        std::cout << "前置++:" << std::endl;
        d.print();
        d1.print();
    
        return 0;
    }
    

    6.4 练习:日期类的实现

    #include <iostream>
    
    class Date 
    {
    public:
        int GetMonthDay(int year, int month) 
        {
            static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
            int day = days[month];
    
            // 闰年
            if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) 
            {
                day += 1;
            }
    
            return day;
        }
    
        Date(int year = 1900, int month = 1, int day = 1) 
            : _year(year), _month(month), _day(day)  
        {
            // 检查日期是否合理
            if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) 
            {
                std::cerr << "Invalid date!" << std::endl;
                _year = 1900;
                _month = 1;
                _day = 1;
            }
        }
    
        // 拷贝构造函数,实现深拷贝,确保对象的独立性
        Date(const Date& d)
            : _year(d._year), _month(d._month), _day(d._day) {}
    
        Date& operator=(const Date& d) 
        {
            if (this != &d) 
            {
                _year = d._year;
                _month = d._month;
                _day = d._day;
            }
            return *this;
        }
    
        Date& operator+=(int day) 
        {
            _day += day;
            while (_day > GetMonthDay(_year, _month)) 
            {
                _day -= GetMonthDay(_year, _month);
                _month++;
                if (_month > 12) 
                {
                    _month = 1;
                    _year++;
                }
            }
            return *this;
        }
    
        Date operator+(int day) 
        {
            Date temp = *this;
            temp += day;
            return temp;
        }
    
        Date operator-(int day) 
        {
            Date temp = *this;
            temp -= day;
            return temp;
        }
    
        Date& operator-=(int day) 
        {
            _day -= day;
            while (_day <= 0) 
            {
                _month--;
                if (_month < 1) 
                {
                    _month = 12;
                    _year--;
                }
                _day += GetMonthDay(_year, _month);
            }
            return *this;
        }
    
        // 前置++重载
        Date& operator++() 
        {
            *this += 1;
            return *this;
        }
    
        // 后置++重载
        Date operator++(int) 
        {
            Date temp = *this;
            *this += 1;
            return temp;
        }
    
        // 后置--重载
        Date operator--(int) 
        {
            Date temp = *this;
            *this -= 1;
            return temp;
        }
    
        // 前置--重载
        Date& operator--() 
        {
            *this -= 1;
            return *this;
        }
    
        // >运算符重载
        bool operator>(const Date& d) 
        {
            if (_year > d._year) return true;
            if (_year < d._year) return false;
            if (_month > d._month) return true;
            if (_month < d._month) return false;
            return _day > d._day;
        }
    
        // ==运算符重载
        bool operator==(const Date& d) 
        {
            return _year == d._year && _month == d._month && _day == d._day;
        }
    
        // >=运算符重载
        bool operator >= (const Date& d) 
        {
            return *this > d || *this == d;
        }
    
        // <运算符重载
        bool operator < (const Date& d) 
        {
            return !(*this >= d);
        }
    
        // <=运算符重载
        bool operator <= (const Date& d) 
        {
            return !(*this > d);
        }
    
        // !=运算符重载
        bool operator != (const Date& d) 
        {
            return !(*this == d);
        }
    
        // 计算两个日期之间的天数差
        int operator-(const Date& d) 
        {
            Date min = *this < d ? *this : d;
            Date max = *this < d ? d : *this;
            int days = 0;
            while (min != max) 
            {
                min++;
                days++;
            }
            return days;
        }
    
        void Print() 
        {
            std::cout << _year << "-" << _month << "-" << _day << std::endl;
        }
    
    private:
        int _year;
        int _month;
        int _day;
    };
    
    int main() 
    {
        Date d1(2024, 3, 15);
        Date d2(2024, 3, 20);
    
        std::cout << "d1: ";
        d1.Print();
        std::cout << "d2: ";
        d2.Print();
    
        std::cout << "d1 + 5 days: ";
        (d1 + 5).Print();
    
        std::cout << "d2 - 3 days: ";
        (d2 - 3).Print();
    
        std::cout << "d1 > d2: " << (d1 > d2) << std::endl;
        std::cout << "d1 < d2: " << (d1 < d2) << std::endl;
        std::cout << "d1 - d2: " << (d1 - d2) << " days" << std::endl;
    
        return 0;
    }

    : _year(year), _month(month), _day(day) 是成员初始化列表,与在构造函数体中赋值不同,成员初始化列表在对象的内存分配后、构造函数体执行前完成成员变量的初始化。

    这里 _year(year) 表示将构造函数参数 year 的值赋给成员变量 _year,_month(month), _day(day) 同理。

    优点:对于一些类型(如常量成员、引用成员),必须使用成员初始化列表进行初始化;对于其他类型,使用成员初始化列表可以提高效率,因为它避免了先默认初始化再赋值的过程。

    • std::cout:主要用于输出程序的正常运行结果、提示信息等。数据先存入缓冲区,等缓冲区满、遇到换行符(\n)、调用std::endl或满足特定条件时,才将数据输出到终端,目的是提高输出效率。
    • std::cerr:专门用于输出程序运行过程中出现的错误信息。通常无缓冲,输出数据时立即发送到输出设备,让用户及时得知程序错误,避免因缓冲导致错误信息延迟显示。

    7. 取地址操作符重载和 const 取地址操作符重载

    取地址操作符重载和 const 取地址操作符重载是类的默认成员函数,一般情况下用户无需重新定义,编译器会自动生成。

    7.1 使用建议

    通常使用编译器默认生成的取地址操作符重载即可,仅在特殊场景,如需要让他人获取到指定内容时,才需要对这两个操作符进行重载。

    7.2 取地址操作符重载

    类名* operator&();
    • 返回指向当前类对象的指针
    • 无参数,因为它是类的成员函数,隐含的this指针指向调用该函数的对象

    7.3 const 取地址操作符重载

    const 类名* operator&() const;

    相关文章:

  • 模型空间、图纸空间、布局(Layout)之间联系——CAD c#二次开发
  • Java面试第十二山!《Redis缓存》
  • resnet与densenet的比较
  • 甘特图dhtmlx-gantt 一行多任务
  • MySQL-索引的使用
  • Docker Swarm集群搭建
  • 自然语言处理|深入解析 PEGASUS:从原理到实践
  • 电脑节电模式怎么退出 分享5种解决方法
  • ssh命令
  • 负载均衡的在线OJ项目
  • Redis核心机制(一)
  • 【智能体】| 知识库、RAG概念区分以及智能体是什么
  • 大数据学习(77)-Hive详解
  • 深度洞察:DeepSeek 驱动金融行业智能化转型变革
  • Nginx参数调优脚本
  • 基于springboot的免税商品优选购物商城(020)
  • Java面试黄金宝典5
  • MySql 存储引擎 InnoDB 与 MyISAM 有什么区别
  • 知识库项目开场白
  • 【Linux】线程同步与生产消费者模型
  • 宜昌全域高质量发展:机制创新与产业重构的双向突围
  • 中国科协发声:屡禁不止的奇葩论文再次敲响学风建设警钟
  • 时隔14个月北京怀柔区重启供地,北京建工以3.59亿元摘得
  • 保利42.41亿元竞得上海杨浦东外滩一地块,成交楼面单价超8万元
  • 上海:企业招用高校毕业生可享受1500元/人一次性扩岗补助
  • 欧盟委员会计划对950亿欧元美国进口产品采取反制措施