1.构造函数

(1)作用

对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。

(2)特点

  1. 函数名与类名相同。
  2. 无返回值(不需要写 void,这是 C++ 的语法规定)。
  3. 对象实例化时,系统会自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,C++ 编译器会自动生成一个无参的默认构造函数;一旦用户显式定义了任何构造函数,编译器就不再生成默认构造函数。
  6. 无参构造函数、全缺省构造函数以及我们不写构造函数时编译器默认生成的构造函数,都称为默认构造函数。但请注意,这三个函数有且只能有一个存在,不能同时出现。无参构造函数和全缺省构造函数虽然构成函数重载,但调用时会产生歧义。很多同学误以为只有编译器默认生成的那个才叫默认构造函数,实际上无参构造函数和全缺省构造函数也属于默认构造函数。简单来说,不传递实参就能调用的构造函数就是默认构造函数
  7. 如果我们不写构造函数,编译器默认生成的构造函数对内置类型成员变量的初始化没有要求(即是否初始化取决于编译器)。对于自定义类型成员变量,编译器会调用该成员变量的默认构造函数进行初始化。如果该成员变量没有默认构造函数,则会报错;此时我们需要使用初始化列表来初始化该成员(初始化列表将在后续章节详细讲解)。

说明:C++ 将类型分为内置类型(基本类型)和自定义类型。内置类型是语言提供的基本数据类型,如 intchardouble、指针等;自定义类型则是我们使用 classstruct 等关键字自己定义的类型。

(3)代码

class Date
{
public:
    // 1.⽆参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
    // 2.带参构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // 3.全缺省构造函数
    /*Date(int year = 1, int month = 1, int day = 1)
    {
    _year = year;
    _month = month;
    _day = day;
    }*/
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
    int _day;
};
int main()
{
    // 如果留下三个构造中的第⼆个带参构造,第⼀个和第三个注释掉

    Date d1;             // 调⽤默认构造函数
    Date d2(2025, 1, 1); // 调⽤带参的构造函数
    // 注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法
    // 区分这⾥是函数声明还是实例化对象

    Date d3();
    d1.Print();
    d2.Print();
    return 0;
}

析构函数

(1)作用

C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。只有类里面写的都是普通变量,或者是智能指针,才不需要写析构函数

(2)特点

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值(与构造函数类似,也不需要写 void)。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 与构造函数类似,如果我们不写析构函数,编译器自动生成的析构函数对内置类型成员不做处理,对自定义类型成员则会调用其自身的析构函数。
  6. 需要注意的是,即使我们显式写了析构函数,对于自定义类型成员,也会自动调用其析构函数。也就是说,自定义类型成员在任何情况下都会自动调用析构函数。
  7. 如果类中没有申请资源(如 Date 类),析构函数可以不写,直接使用编译器生成的默认析构函数即可;如果默认生成的析构函数就够用(如 MyQueue 类),也不需要显式写析构函数。但是,如果类中申请了资源(如 Stack 类),则必须自己编写析构函数来释放资源,否则会造成资源泄漏。
  8. 在同一局部作用域中,多个对象的析构顺序是后定义的对象先析构。

(3)代码

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;
};
// 两个Stack实现队列
class MyQueue
{
public:
// 显⽰写析构,也会⾃动调⽤Stack的析构
/*~MyQueue()
{}*/
private:
    Stack pushst;
    Stack popst;
};
int main()
{
    Stack st;
    MyQueue mq;
    return 0;
}

3.拷贝构造函数

(1)作用

如果一个构造函数的第一个参数是自身类类型的引用,并且任何额外的参数都有默认值,那么这个构造函数就称为拷贝构造函数。也就是说,拷贝构造函数是构造函数的一种特殊形式。

(2)特点

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用。如果使用传值方式,编译器会直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数可以有多个参数,但第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  3. C++ 规定,自定义类型对象进行拷贝行为时必须调用拷贝构造函数。因此,自定义类型传值传参和传值返回都会调用拷贝构造函数来完成拷贝。
  4. 若未显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。自动生成的拷贝构造函数对内置类型成员变量会进行值拷贝/浅拷贝(按字节逐个拷贝),对自定义类型成员变量则会调用其自身的拷贝构造函数。
  5. 对于像 Date 这样的类,其成员变量全是内置类型且不指向任何资源,编译器自动生成的拷贝构造函数就能满足需求,因此我们不需要显式实现拷贝构造函数。而对于像 Stack 这样的类,虽然成员变量也都是内置类型,但 _a 指针指向了动态分配的资源,编译器自动生成的浅拷贝不符合我们的需求,因此需要我们自己实现深拷贝(对指向的资源也进行拷贝)。对于像 MyQueue 这样的类型,其内部成员主要是自定义类型 Stack,编译器自动生成的拷贝构造函数会自动调用 Stack 的拷贝构造函数,因此我们也不需要显式实现 MyQueue 的拷贝构造函数。这里有一个小技巧:如果一个类显式实现了析构函数来释放资源,那么它通常也需要显式实现拷贝构造函数,否则就不需要
  6. 传值返回会产生一个临时对象,这会调用拷贝构造函数。而传引用返回时,返回的是返回对象的别名(引用),不会产生拷贝。但是,如果返回对象是当前函数局部作用域中的局部对象,函数结束后该对象就会被销毁,此时使用引用返回是有问题的,这时的引用相当于一个“野引用”,类似于野指针。传引用返回可以减少拷贝,但必须确保返回的对象在函数结束后仍然存在,才能使用引用返回。

(3)代码1

#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);
    d2.Print();

    // 也可以这样写,这⾥也是拷⻉构造
    Date d4 = d1;
    d2.Print();

    // Func2返回了⼀个局部对象tmp的引⽤作为返回值
    // Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤
    Date ret = Func2();
    ret.Print();

    return 0;
}

(3)代码2

#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;
}

用到的场景

  1. 用一个已有对象初始化新对象
  2. 对象作为函数参数,进行“传值传参”
  3. 函数“传值返回”对象

4.赋值运算符重载

运算符重载

(1)特点

当运算符作用于类类型的对象时,C++ 允许我们通过运算符重载来赋予其新的含义。C++ 规定,类类型对象使用运算符时,必须转换为调用对应的运算符重载函数;如果没有对应的运算符重载,编译器会报错。

  • 运算符重载函数:是一种具有特殊名称的函数,其名称由 operator 关键字和要重载的运算符共同组成。与其他函数一样,它也有返回类型、参数列表和函数体。
  • 参数数量:重载运算符函数的参数数量与该运算符作用的运算对象数量相同。一元运算符有一个参数,二元运算符有两个参数。对于二元运算符,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。
  • 成员函数重载:如果运算符重载函数是类的成员函数,那么它的第一个运算对象会隐式地传递给 this 指针。因此,作为成员函数的运算符重载,其参数数量比运算对象少一个。
  • 优先级与结合性:运算符重载后,其优先级和结合性与对应的内置类型运算符保持一致。
  • 不能创建新运算符:不能通过连接语法中不存在的符号来创建新的运算符,例如 operator@
  • 不能重载的运算符:以下 5 个运算符不能重载:.*::sizeof?:.(选择题中常考,需要牢记)。
  • 至少一个类类型参数:重载的运算符必须至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。例如,int operator+(int x, int y) 是不允许的。
  • 重载的意义:一个类需要重载哪些运算符,取决于哪些运算符在重载后具有实际意义。例如,Date 类重载 operator- 有意义,但重载 operator+ 可能就没有意义。
  • 区分前置与后置 ++:重载 ++ 运算符时,有前置 ++ 和后置 ++ 两种形式,它们的函数名都是 operator++,无法直接区分。C++ 规定,后置 ++ 重载时,增加一个 int 类型的形参,从而与前置 ++ 构成函数重载,方便区分。
  • 流插入与流提取运算符:重载 <<>> 时,通常需要将它们重载为全局函数。如果重载为成员函数,this 指针会默认占据第一个形参位置,导致调用形式变为 对象 << cout,这不符合使用习惯和可读性。重载为全局函数时,可以将 ostream/istream 对象放在第一个形参位置,第二个形参位置放置类类型对象。

运算符重载==

成员放公有(暴力破解法)
#include <iostream>
using namespace std;

class Date {
public:
    // 方法一核心:直接把成员变量设为 public
    int _year;
    int _month;
    int _day;

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

// 全局重载函数,因为成员是公有的,所以可以直接访问
bool operator==(const Date& d1, const Date& d2) {
    return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

int main() {
    Date d1(2024, 7, 5), d2(2024, 7, 5);
    cout << "方法一结果: " << (d1 == d2 ? "true" : "false") << endl;
    return 0;
}
提供 getxxx 函数(曲线救国法)
#include <iostream>
using namespace std;

class Date {
private:
    int _year, _month, _day;
public:
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
    }
    // 方法二核心:提供公有的 get 接口
    int getYear() const { return _year; }
    int getMonth() const { return _month; }
    int getDay() const { return _day; }
};

// 全局重载函数,通过调用 get 函数来访问私有成员
bool operator==(const Date& d1, const Date& d2) {
    return d1.getYear() == d2.getYear() && 
           d1.getMonth() == d2.getMonth() && 
           d1.getDay() == d2.getDay();
}

int main() {
    Date d1(2024, 7, 5), d2(2024, 7, 5);
    cout << "方法二结果: " << (d1 == d2 ? "true" : "false") << endl;
    return 0;
}
友元函数(特权授权法)
#include <iostream>
using namespace std;

class Date {
private:
    int _year, _month, _day;
public:
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
    }
    // 方法三核心:声明友元,给外部全局函数授权
    friend bool operator==(const Date& d1, const Date& d2);
};

// 全局重载函数,因为有 friend 授权,可以直接访问 d1._year 等私有成员
bool operator==(const Date& d1, const Date& d2) {
    return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

int main() {
    Date d1(2024, 7, 5), d2(2024, 7, 5);
    cout << "方法三结果: " << (d1 == d2 ? "true" : "false") << endl;
    return 0;
}
重载为成员函数(自己动手法)
#include <iostream>
using namespace std;

class Date {
private:
    int _year, _month, _day;
public:
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
    }
    // 方法四核心:运算符重载直接作为类的成员函数
    // 只需要传一个参数(右边的对象),左边的对象是隐含的 this 指针
    bool operator==(const Date& d) const {
        return _year == d._year && 
               _month == d._month && 
               _day == d._day;
    }
};

// 注意:这里没有任何全局的 operator== 函数!

int main() {
    Date d1(2024, 7, 5), d2(2024, 7, 5);
    // 调用时,d1 会作为 this 指针隐式传入,d2 作为参数 d 传入
    cout << "方法四结果: " << (d1 == d2 ? "true" : "false") << endl;
    return 0;
}

运算符重载++(前缀++和后缀++)

#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()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

    bool operator==(const Date& d)
    {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }

    // 前置++ 重载
    Date& operator++()
    {
        cout << "前置++" << endl;
        // ... 实际的自增逻辑
        return *this;
    }

    // 后置++ 重载
    Date operator++(int)
    {
        Date tmp;
        cout << "后置++" << endl;
        // ... 实际的自增逻辑
        return tmp;
    }

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

int main()
{
    Date d1(2024, 7, 5);
    Date d2(2024, 7, 6);

    // 运算符重载函数可以显式调用
    d1.operator==(d2);
    // 编译器遇到 d1 == d2 会自动转换成 d1.operator==(d2);
    d1 == d2;

    // 编译器遇到 ++d1 会自动转换成 d1.operator++();
    ++d1;
    // 编译器遇到 d1++ 会自动转换成 d1.operator++(0);
    d1++;

    return 0;
}

重载运算符<<和>>

C++ 中,如果你把二元运算符重载为类的成员函数,编译器会默认把调用该函数的对象(也就是 this 指针指向的对象)作为运算符的左侧操作数。

重载成成员函数代码(不符合常规,但正确)
#include <iostream>
using namespace std;

class Date {
public:
   Date(int year = 2024, int month = 7, int day = 5) 
       : _year(year), _month(month), _day(day) {}

   // 强行把 << 重载为 Date 类的成员函数
   // 此时,this 指针(即 d1 对象)会抢占左侧位置
   void operator<<(ostream& out) {
       out << _year << "-" << _month << "-" << _day;
   }

private:
   int _year, _month, _day;
};

int main() {
   Date d1(2024, 7, 5);
   
   // 如果写成 cout << d1,编译器会报错,因为它找不到 ostream 类里的这个成员函数
   // cout << d1; 

   // 成员函数重载,只能被迫写成这种“反人类”的顺序:
   d1 << cout;  // 相当于 d1.operator<<(cout);
   
   return 0;
}
重载为全局函数(平时看到的写法)
#include <iostream>
using namespace std;

class Date {
public:
    Date(int year = 2024, int month = 7, int day = 5) 
        : _year(year), _month(month), _day(day) {}

    // 声明友元,允许全局函数访问私有成员
    friend ostream& operator<<(ostream& out, const Date& d);

private:
    int _year, _month, _day;
};

// 重载为全局函数
// 第一个参数 out 对应左侧的 cout,第二个参数 d 对应右侧的 d1
ostream& operator<<(ostream& out, const Date& d) {
    out << d._year << "-" << d._month << "-" << d._day;
    return out; // 返回 out 引用,支持 cout << d1 << endl; 连续输出
}

int main() {
    Date d1(2024, 7, 5);
    
    // 完美符合日常使用习惯!
    // 编译器遇到 cout << d1,会自动匹配到全局的 operator<<(cout, d1);
    cout << d1 << endl; 
    
    return 0;
}

赋值运算符重载,即operator=

1.作用

赋值运算符重载(operator=)是 C++ 中的一个默认成员函数,用于完成两个已经存在的对象之间的拷贝赋值。这里需要特别注意与拷贝构造函数进行区分:拷贝构造函数用于将一个对象拷贝初始化给另一个正在创建的对象。

2.特点

赋值运算符重载是一个运算符重载,C++ 规定它必须重载为类的成员函数。

  1. 参数:建议写成 const 当前类类型& 的形式。如果使用传值方式,会导致额外的拷贝开销。
  2. 返回值:建议返回当前类类型的引用(如 Date&)。引用返回可以提高效率,并且支持连续赋值(如 d1 = d2 = d3;)。
  3. 默认生成:如果没有显式实现赋值运算符重载,编译器会自动生成一个默认的赋值运算符重载。其行为与默认拷贝构造函数类似:
    • 对内置类型成员变量进行值拷贝/浅拷贝(按字节逐个拷贝)。
    • 对自定义类型成员变量,会调用该成员自身的赋值运算符重载函数。
  4. 何时需要显式实现
    • 对于像 Date 这样的类,其成员变量全是内置类型且不指向任何资源,编译器自动生成的赋值运算符重载就能满足需求,因此不需要我们显式实现。
    • 对于像 Stack 这样的类,虽然成员变量也都是内置类型,但 _a 指针指向了动态分配的资源。编译器自动生成的浅拷贝不符合需求,需要我们自己实现深拷贝(对指向的资源也进行拷贝)。
    • 对于像 MyQueue 这样的类型,其内部成员主要是自定义类型 Stack,编译器自动生成的赋值运算符重载会自动调用 Stack 的赋值运算符重载,因此也不需要我们显式实现 MyQueue 的赋值运算符重载。
    • 一个小技巧:如果一个类显式实现了析构函数来释放资源,那么它通常也需要显式实现赋值运算符重载(以及拷贝构造函数),否则就不需要。
3.代码
#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;
    }

    // 赋值运算符重载
    // 传引用返回减少拷贝,并支持连续赋值
    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 都是已经存在的对象)
    d1 = d3;
    
    // 重点:这里依然是调用拷贝构造函数,不是赋值重载!
    // 牢牢记住:赋值重载完成两个已经存在的对象直接的拷贝赋值
    // 而拷贝构造用于一个对象拷贝初始化给另一个要创建的对象
    Date d4 = d1;
    
    return 0;
}
4. 赋值运算符重载与拷贝构造函数的区别

核心区别

  • 拷贝构造函数:用于通过一个已有对象来初始化一个新创建的对象
  • 赋值运算符重载函数:用于在两个已经存在的对象之间进行赋值操作,通过赋值的方式修改等号左侧的对象。

以 Date 类为例

  • 拷贝构造函数声明:Date(const Date& d);
  • 赋值运算符重载函数声明:Date& operator=(const Date& d);

调用方式对比

  • 拷贝构造函数的调用:Date d1(d);Date d1 = d;(注意:这里的 = 是初始化,不是赋值!)
  • 赋值运算符重载函数的调用:d1 = d;(两个对象都已存在)

关键要点

  1. 时机不同:拷贝构造函数在对象创建时被调用,用于初始化新对象;赋值运算符重载在对象已存在时被调用,用于修改已有对象。
  2. 语法形式不同:拷贝构造函数是特殊的构造函数,没有返回值;赋值运算符重载是运算符函数,通常返回引用以支持连续赋值。
  3. 默认行为相似但用途不同:两者默认都进行浅拷贝,但拷贝构造函数用于"诞生",赋值运算符重载用于"修改"。

5. 取地址运算符重载

const 成员函数

在 C++ 中,使用 const 修饰成员函数的主要作用是改变该函数中隐含的 this 指针的类型。具体来说,是将 Date* const this 变为 const Date* const this

这意味着:

  • 原本的 this 指针本身是常量(不能改变指向),但指向的对象内容可以修改。
  • 加上 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;
    }

    // const 成员函数
    // 编译器实际处理为:void Print(const Date* const this)
    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 对象调用 const 成员函数:权限匹配,允许
    const Date d2(2024, 8, 5);
    d2.Print();

    return 0;
}

取地址运算符重载

作用

取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载。一般情况下,编译器自动生成的这两个函数就足够我们使用了,不需要显式实现。

只有在一些特殊场景下才需要手动实现,例如:

  • 不想让别人获取到当前类对象的真实地址
  • 需要返回一个特定的地址(如返回空指针)

代码示例

class Date {
public:
    // 普通对象的取地址运算符重载
    Date* operator&() {
        return this;
        // return nullptr; // 特殊场景:如果想隐藏对象真实地址,可以返回空指针
    }

    // const 对象的取地址运算符重载
    const Date* operator&() const {
        return this;
        // return nullptr;
    }

private:
    int _year;   // 年
    int _month;  // 月
    int _day;    // 日
};

更多推荐