03--C++ 类和对象中篇

【本节目标】

  1. 类的 6 个默认成员函数
  2. 构造函数
  3. 析构函数
  4. 拷贝构造函数
  5. 赋值运算符重载
  6. const 成员函数
  7. 取地址及 const 取地址操作符重载

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

空类(无显式成员)并非真的为空,编译器会自动生成 6 个默认成员函数 ------ 用户未显式实现时,编译器自动补充的成员函数。

默认成员函数 核心作用
构造函数 对象创建时初始化(保证成员有合适初始值)
析构函数 对象销毁时清理资源(如堆内存、文件句柄)
拷贝构造函数 用已存在对象初始化新对象(同类对象拷贝创建)
赋值运算符重载 将一个对象的值赋给另一个已存在的同类对象
取地址运算符重载 获取普通对象的地址(默认生成,无需手动实现)
const 取地址运算符重载 获取 const 对象的地址(默认生成,无需手动实现)

!note\] 关键特性若用户显式实现了某类默认成员函数,编译器将不再生成该函数(部分例外如析构函数与拷贝构造的组合,后续详解)。

2. 构造函数

2.1 概念

创建对象时,编译器自动调用的特殊成员函数,核心任务是初始化对象 (而非开空间创建对象),避免每次创建对象后手动调用初始化方法(如Init)。

入门示例(问题场景)
cpp 复制代码
class Date
{
public:
    // 手动初始化方法,每次创建对象需调用
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    d1.Init(2022, 7, 5); // 手动调用初始化,繁琐
    d1.Print();
    return 0;
}
构造函数解决示例
cpp 复制代码
class Date
{
public:
    // 构造函数:名字与类名相同,无返回值
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2022, 7, 5); // 创建对象时自动调用构造函数,无需手动初始化
    d1.Print(); // 输出:2022-7-5
    return 0;
}

2.2 核心特性

  1. 函数名与类名完全相同 ,无返回值(无需写void,直接定义);
  2. 对象实例化时编译器自动调用,且整个对象生命周期仅调用一次;
  3. 支持重载(可定义多个参数不同的构造函数);
cpp 复制代码
   class Date
   {
   public:
       // 无参构造函数
       Date()
       {
           _year = 1900;
           _month = 1;
           _day = 1;
       }
       // 带参构造函数
       Date(int year, int month, int day)
       {
           _year = year;
           _month = month;
           _day = day;
       }
       // 全缺省构造函数
       Date(int year = 1900, int month = 1, int day = 1)
       {
           _year = year;
           _month = month;
           _day = day;
       }
   private:
       int _year;
       int _month;
       int _day;
   };
   ```
   
4. **无参构造函数的调用陷阱**:创建无参对象时,对象后不能加括号(否则视为函数声明);

 ```cpp
   void TestDate()
   {
       Date d1; // 正确:调用无参构造函数
       // Date d3(); // 错误:声明一个返回Date类型的无参函数,非对象创建
   }
   ```
   
5. **默认构造函数的唯一性**:无参构造、全缺省构造、编译器默认生成的构造函数,统称 "默认构造函数",一个类只能有一个(否则编译冲突);
 ```cpp
   class Date
   {
   public:
       Date() {} // 无参构造
       Date(int year = 1900, int month = 1, int day = 1) {} // 全缺省构造
   };
   // Date d1; // 编译报错:默认构造函数不唯一
   ```
6. **编译器自动生成规则**:
   
   - 若用户未显式定义任何构造函数,编译器生成无参默认构造函数;
   - 若用户显式定义了任意构造函数(无论带参 / 无参),编译器不再生成默认构造函数;
 ```cpp
   class Date
   {
   public:
       // 显式定义带参构造函数,编译器不再生成无参构造
       Date(int year, int month, int day)
       {
           _year = year;
           _month = month;
           _day = day;
       }
   private:
       int _year;
       int _month;
       int _day;
   };
   // Date d1; // 编译报错:无合适的默认构造函数可用
   ```
7. **默认构造函数的初始化行为**:
   
   - 对**内置类型**(int、char 等):不初始化,成员值为随机值;
<font color="#ff0000">    - 对<font color="#00b050">自定义类型</font>(class/struct 定义的类型):调用其默认构造函数初始化;  </font>
 ```cpp
   class Time // 自定义类型
   {
   public:
       Time() // Time的默认构造函数
       {
           cout << "Time()" << endl;
           _hour = 0;
           _minute = 0;
           _second = 0;
       }
   private:
       int _hour;
       int _minute;
       int _second;
   };
   
   class Date
   {
   private:
       // 内置类型:默认构造不初始化,值为随机
       int _year;
       int _month;
       int _day;
       // 自定义类型:默认构造调用Time的默认构造
       Time _t;
   };
   
   int main()
   {
       Date d; // 输出:Time()(_t被初始化,_year/_month/_day为随机值)
       return 0;
   }
   ```

5. **C++11 补丁**:内置类型成员可在类中声明时指定默认值(编译器生成默认构造时会使用该值初始化);
 ```cpp
   class Date
   {
   private:
       // 内置类型声明时指定默认值
       int _year = 1970;
       int _month = 1;
       int _day = 1;
       Time _t; // 自定义类型仍调用其默认构造
   };
   
   int main()
   {
       Date d; // _year =1970, _month=1, _day=1,_t初始化正常
       return 0;
   }
   ```


> [!tip] 实践建议尽量显式定义构造函数(无参 / 全缺省),避免依赖编译器生成的默认构造(内置类型初始化不可控)。

## 3. 析构函数

### 3.1 概念

与构造函数功能相反,对象生命周期结束时(如局部对象出作用域)编译器自动调用,核心任务是**清理对象申请的资源**(如堆内存、文件流),而非销毁对象本身(对象销毁由编译器完成)。

#### 入门示例(资源泄漏场景)

```cpp
typedef int DataType;
class Stack
{
public:
   Stack(size_t capacity = 3)
   {
       // 申请堆内存资源
       _array = (DataType*)malloc(sizeof(DataType) * capacity);
       if (NULL == _array)
       {
           perror("malloc申请空间失败!!!");
           return;
       }
       _capacity = capacity;
       _size = 0;
   }
   void Push(DataType data) { _array[_size++] = data; }
   // 无析构函数:对象销毁时堆内存未释放,导致资源泄漏
private:
   DataType* _array; // 堆内存资源
   int _capacity;
   int _size;
};

void TestStack()
{
   Stack s;
   s.Push(1);
   s.Push(2);
   // s销毁时,_array指向的堆内存未释放,资源泄漏
}
析构函数解决示例
cpp 复制代码
typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 3)
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity);
        if (NULL == _array)
        {
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = capacity;
        _size = 0;
    }
    void Push(DataType data) { _array[_size++] = data; }
    // 析构函数:清理堆内存资源
    ~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);
    // s销毁时自动调用~Stack(),堆内存释放,无资源泄漏
}

3.2 核心特性

  1. 函数名 :类名前加~(如~Date()),无参数、无返回值;

  2. 不可重载:一个类只能有一个析构函数(显式定义或编译器生成);

  3. 自动调用时机:对象生命周期结束时触发(局部对象出作用域、全局对象程序结束时);

  4. 编译器自动生成规则

    • 若用户未显式定义析构函数,编译器生成默认析构函数;
    • 默认析构函数对内置类型:不做任何清理(如堆内存不会释放);
    • 默认析构函数对自定义类型:调用其析构函数清理;
cpp 复制代码
  class Time
  {
  public:
      ~Time() // Time的析构函数
      {
          cout << "~Time()" << endl;
      }
  private:
      int _hour;
      int _minute;
      int _second;
  };
  
  class Date
  {
  private:
      int _year = 1970; // 内置类型:默认析构不清理
      Time _t;          // 自定义类型:默认析构调用~Time()
  };
  
  int main()
  {
      Date d; // 程序结束时输出:~Time()(_t被清理,_year无操作)
      return 0;
  }
  ```
  
5. **显式定义的必要性**:
  - 类中**无资源申请**(如 Date 类,仅内置类型成员):无需显式定义,用编译器默认析构即可;
  - 类中**有资源申请**(如 Stack 类,堆内存、文件句柄):必须显式定义析构函数,否则导致资源泄漏。

> [!warning] 常见错误忘记在析构函数中释放堆内存,或释放后未将指针置为`nullptr`,可能导致野指针访问。

## 4. 拷贝构造函数

### 4.1 概念

用**已存在的同类对象**创建新对象时,编译器自动调用的特殊成员函数(本质是构造函数的重载形式),核心任务是复制已有对象的成员值。

#### 生活类比

双胞胎:用 "已存在的人"(哥哥)复制出 "新的人"(弟弟),二者初始状态完全一致。

#### 入门示例

```cpp
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
      _year = year;
      _month = month;
      _day = day;
  }
  // 拷贝构造函数:用已有对象d初始化新对象
  Date(const 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;
};

int main()
{
  Date d1(2022, 7, 5); // 调用普通构造函数
  Date d2(d1);          // 调用拷贝构造函数,d2复制d1的状态
  d2.Print(); // 输出:2022-7-5(与d1一致)
  return 0;
}

4.2 核心特性

  1. 函数原型 :参数必须是 "本类类型的 const 引用"(const 类名&);

    • 若用传值方式(Date d),会引发无穷递归(传值时需调用拷贝构造,拷贝构造又需传值,循环往复);
cpp 复制代码
   class Date
   {
   public:
       Date(int year = 1900, int month = 1, int day = 1) {}
       // 正确:const引用参数
       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; }
   private:
       int _year;
       int _month;
       int _day;
   };
   ```

1. **编译器自动生成规则**:
   																																																				     
   - 若用户未显式定义拷贝构造,编译器生成默认拷贝构造;
   - 默认拷贝构造的行为:**浅拷贝(值拷贝)** ------ 按内存字节序逐字节复制;
       - 内置类型:直接复制字节;
       - 自定义类型:调用其拷贝构造函数复制;
 ```cpp
   class Time
   {
   public:
       Time(int hour = 1, int minute = 1, int second = 1) {}
       // Time的拷贝构造
       Time(const Time& t)
       {
           _hour = t._hour;
           _minute = t._minute;
           _second = t._second;
           cout << "Time::Time(const Time&)" << endl;
       }
   private:
       int _hour;
       int _minute;
       int _second;
   };
   
   class Date
   {
   private:
       int _year = 1970; // 内置类型:浅拷贝直接复制
       Time _t;          // 自定义类型:调用Time的拷贝构造
   };
   
   int main()
   {
       Date d1;
       Date d2(d1); // 输出:Time::Time(const Time&)(默认拷贝构造触发)
       return 0;
   }
   ```

2. **浅拷贝的问题与显式定义的必要性**:
   
   - 无资源申请的类(如 Date):浅拷贝足够,无需显式定义;
   - 有资源申请的类(如 Stack):浅拷贝会导致**双重释放**(两个对象指向同一块堆内存,销毁时两次调用`free`);
 ```cpp
   typedef int DataType;
   class Stack
   {
   public:
       Stack(size_t capacity = 10)
       {
           _array = (DataType*)malloc(capacity * sizeof(DataType));
           if (nullptr == _array) { perror("malloc失败"); return; }
           _size = 0;
           _capacity = capacity;
       }
       ~Stack()
       {
           if (_array) { free(_array); _array = nullptr; } // 释放堆内存
       }
       // 未显式定义拷贝构造,使用编译器默认浅拷贝
   private:
       DataType* _array; // 堆内存资源
       size_t _size;
       size_t _capacity;
   };
   
   int main()
   {
       Stack s1;
       s1.Push(1);
       Stack s2(s1); // 浅拷贝:s2._array = s1._array(指向同一块堆内存)
       return 0;
       // s1和s2销毁时,均调用~Stack(),双重free导致程序崩溃
   }
   ```
   
   > [!solution] 解决方案涉及资源申请的类,必须显式定义拷贝构造函数,实现**深拷贝**(为新对象重新申请资源,复制内容而非地址)。
   
3. **典型调用场景**:
   
   - 用已存在对象创建新对象(`Date d2(d1)`);
   - 函数参数为类类型对象(传值时触发拷贝构造);
   - 函数返回值为类类型对象(返回时触发拷贝构造,部分编译器可能优化);

 ```cpp
   class Date
   {
   public:
       Date(int year = 1900, int month = 1, int day = 1)
       { cout << "Date(int,int,int):" << this << endl; }
       Date(const Date& d)
       { cout << "Date(const Date& d):" << this << endl; }
       ~Date() { cout << "~Date():" << this << endl; }
   private:
       int _year;
       int _month;
       int _day;
   };
   
   Date Test(Date d) // 传参:调用拷贝构造生成d
   {
       Date temp(d); // 用d创建temp:调用拷贝构造
       return temp;  // 返回temp:调用拷贝构造生成临时对象
   }
   
   int main()
   {
       Date d1(2022, 1, 13); // 调用普通构造
       Test(d1);              // 触发多次拷贝构造(视编译器优化而定)
       return 0;
   }
   ```

> [!tip] 性能优化建议函数参数尽量用**const 引用**(避免拷贝构造开销),返回值优先用引用(无拷贝开销),仅当返回局部对象时用值返回。

> [!tip] 浅拷贝问题:浅拷贝会让多个对象共享同一份资源,而析构时却都以为自己拥有它,从而产生析构两次的问题。

> [!tip] 需要动态开辟空间的需要深拷贝
## 5. 赋值运算符重载

### 5.1 运算符重载基础

C++ 允许为类重载运算符(如`==`、`+`、`=`),本质是 "具有特殊函数名的函数",目的是增强代码可读性(用运算符表达对象操作,更直观)。

#### 核心规则

- 函数名:`operator + 运算符`(如`operator==`、`operator=`);
- 必须有**类类型参数**(否则与内置类型运算符冲突);
- 内置类型运算符含义不能改变(如不能重载`int`的`+`为减法);
- 5 个运算符不能重载:`.*`、`::`、`sizeof`、`?:`、`.`;
- 作为类成员函数重载时,第一个参数为隐含的`this`指针(指向左操作数),形参个数比运算符操作数少 1;

#### 示例:重载`==`运算符

```cpp
class Date
{
public:
   Date(int year = 1900, int month = 1, int day = 1)
   {
   														        _year = year;
       _month = month;
       _day = day;
   }
   // 成员函数重载==:左操作数为this,右操作数为d2
   bool operator==(const Date& d2)
   {
       return _year == d2._year
           && _month == d2._month
           && _day == d2._day;
   }
private:
   int _year;
   int _month;
   int _day;
};

int main()
{
   Date d1(2022, 7, 5), d2(2022, 7, 6);
   cout << (d1 == d2) << endl; // 等价于d1.operator==(d2),输出0
   return 0;
}

5.2 赋值运算符重载(重点)

赋值运算符(=)是特殊的运算符重载,核心用于 "将一个已存在对象的值赋给另一个已存在对象"(与拷贝构造的 "用已有对象创建新对象" 区分)。

1. 正确格式(必须遵守)
cpp 复制代码
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // 赋值运算符重载格式
    Date& operator=(const Date& d)
    {
        // 1. 检测自赋值(避免自己给自己赋值时的不必要操作)
        if (this != &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        // 2. 返回*this(支持连续赋值,如d1 = d2 = d3)
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};
格式说明
  • 参数类型const Date&------ 传引用避免拷贝开销,const防止修改右操作数;
  • 返回值类型Date&------ 返回引用避免拷贝开销,且支持连续赋值;
  • 自赋值检测this != &d------ 若d1 = d1(自赋值),直接返回,避免无效操作(尤其涉及资源时,防止双重释放);
  • 返回 * this :使赋值表达式返回左操作数,支持d1 = d2 = d3(等价于d1.operator=(d2.operator=(d3)))。
2. 必须重载为类成员函数

赋值运算符不能重载为全局函数,否则与编译器生成的默认赋值运算符冲突:

cpp 复制代码
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1) {}
    int _year;
    int _month;
    int _day;
};

// 错误:全局赋值运算符重载与默认生成的冲突
Date& operator=(Date& left, const Date& right)
{
    if (&left != &right)
    {
        left._year = right._year;
        left._month = right._month;
        left._day = right._day;
    }
    return left;
}
// 编译报错:error C2801: "operator ="必须是非静态成员
3. 编译器自动生成规则
  • 若用户未显式定义,编译器生成默认赋值运算符重载;
  • 默认行为:浅拷贝(值拷贝) ------ 内置类型直接赋值,自定义类型调用其赋值运算符重载;
  • 显式定义的必要性:与拷贝构造一致,涉及资源申请的类必须显式实现(深拷贝),否则导致双重释放;
cpp 复制代码
typedef int DataType;
class Stack
{
public:
    Stack(size_t capacity = 10)
    {
        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array) { perror("malloc失败"); return; }
        _size = 0;
        _capacity = capacity;
    }
    ~Stack()
    {
        if (_array) { free(_array); _array = nullptr; }
    }
    // 未显式定义赋值运算符,使用默认浅拷贝
private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};

int main()
{
    Stack s1, s2;
    s1.Push(1);
    s2 = s1; // 浅拷贝:s2._array = s1._array(同一块堆内存)
    return 0;
    // s1和s2销毁时双重free,程序崩溃
}

5.3 前置 ++ 与后置 ++ 重载(扩展)

++运算符有前置(++d)和后置(d++)两种形式,需通过重载区分:

  • 前置 ++:先自增,返回自增后的对象(引用返回,无拷贝);
  • 后置 ++:先返回原对象,再自增(值返回,需拷贝临时对象);
  • 区分技巧:后置 ++ 重载时多增加一个int类型的占位参数(编译器自动传递,用户无需显式传参)。
cpp 复制代码
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // 前置++:返回引用(自增后的对象)
    Date& operator++()
    {
        _day += 1;
        return *this;
    }
    // 后置++:int为占位参数,返回值(自增前的临时对象)
    Date operator++(int)
    {
        Date temp(*this); // 保存原状态
        _day += 1;        // 自增
        return temp;      // 返回原状态
    }
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d(2022, 1, 13);
    Date d1 = ++d; // 前置++:d先自增为2022-1-14,d1复制该状态
    d1.Print(); // 输出:2022-1-14
    Date d2 = d++; // 后置++:d先复制给d2(2022-1-14),再自增为2022-1-15
    d2.Print(); // 输出:2022-1-14 
    d.Print();  // 输出:2022-1-15
    return 0;
}

6. 日期类完整实现(综合示例)

cpp 复制代码
class Date
{
public:
    // 获取某年某月的天数(静态成员函数:无this指针,可通过类名调用)
    static 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];
        // 闰年2月29天
        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)
    {
        // 合法性检查
        if (year >= 1 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year, month))
        {
            _year = year;
            _month = month;
            _day = day;
        }
        else
        {
            cout << "非法日期:" << year << "-" << month << "-" << day << endl;
        }
    }

    // 拷贝构造函数
    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() {}

    // 日期+=天数(修改原对象,引用返回)
    Date& operator+=(int day)
    {
        if (day < 0)
        {
            return *this -= (-day); // 复用-=逻辑
        }
        _day += day;
        while (_day > GetMonthDay(_year, _month))
        {
            _day -= GetMonthDay(_year, _month);
            _month++;
            if (_month > 12)
            {
                _year++;
                _month = 1;
            }
        }
        return *this;
    }

    // 日期+天数(不修改原对象,值返回)
    Date operator+(int day)
    {
        Date temp(*this); // 拷贝原对象
        temp += day;      // 复用+=逻辑
        return temp;
    }

    // 日期-=天数(修改原对象,引用返回)
    Date& operator-=(int day)
    {
        if (day < 0)
        {
            return *this += (-day); // 复用+=逻辑
        }
        _day -= day;
        while (_day < 1)
        {
            _month--;
            if (_month < 1)
            {
                _year--;
                _month = 12;
            }
            _day += GetMonthDay(_year, _month);
        }
        return *this;
    }

    // 日期-天数(不修改原对象,值返回)
    Date operator-(int day)
    {
        Date temp(*this);
        temp -= day;
        return temp;
    }

    // 前置++
    Date& operator++() { return *this += 1; }

    // 后置++
    Date operator++(int)
    {
        Date temp(*this);
        *this += 1;
        return temp;
    }

    // 前置--
    Date& operator--() { return *this -= 1; }

    // 后置--
    Date operator--(int)
    {
        Date temp(*this);
        *this -= 1;
        return temp;
    }

    // 关系运算符重载
    bool operator==(const Date& d)
    {
        return _year == d._year && _month == d._month && _day == d._day;
    }

    bool operator!=(const Date& d) { return !(*this == d); }

    bool operator>(const Date& d)
    {
        if (_year > d._year) return true;
        else if (_year == d._year && _month > d._month) return true;
        else if (_year == d._year && _month == d._month && _day > d._day) return true;
        return false;
    }

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

    // 日期-日期:返回天数差,最好的方法--最强王者。
    int operator-(const Date& d)
    {
        Date max = *this;
        Date min = d;
        int flag = 1;
        if (*this < d)
        {
            max = d;
            min = *this;
            flag = -1;
        }
        int dayCount = 0;
        while (min != max)
        {
            ++min;
            ++dayCount;
        }
        return dayCount * flag;
    }

    // 打印日期
    void Print() { cout << _year << "-" << _month << "-" << _day << endl; }

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

7. const 成员函数

7.1 概念

const修饰的类成员函数,本质是修饰该函数隐含的this指针,限制函数对类成员的修改(this指针类型变为const 类名* const)。

语法格式
cpp 复制代码
class Date
{
public:
    // const成员函数:const修饰this指针
    void Print() const
    {
        // _year = 2023; // 编译报错:const成员函数不能修改成员变量
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
编译器处理逻辑
  • 普通成员函数的this指针:类名* const(指针指向不可改,指向的内容可改);
  • const 成员函数的this指针:const 类名* const(指针指向不可改,指向的内容也不可改)。

7.2 调用规则(面试高频)

  1. const 对象只能调用 const 成员函数 (const 对象的thisconst 类名*,只能匹配 const 成员函数的this类型);
  2. 非 const 对象可以调用任意成员函数 (非 const 对象的this类名*,可隐式转换为const 类名*);
  3. const 成员函数不能调用非 const 成员函数 (非 const 成员函数的this可修改成员,与 const 成员函数的限制冲突);
  4. 非 const 成员函数可以调用 const 成员函数 (const 成员函数的thisconst 类名*,非 const 成员函数的this可兼容)。
示例验证
cpp 复制代码
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    // 非const成员函数
    void Print()
    {
        cout << "Print()" << endl;
        PrintConst(); // 规则4:非const可调用const成员函数
    }
    // const成员函数
    void PrintConst() const
    {
        cout << "PrintConst()" << endl;
        // Print(); // 规则3:const不能调用非const成员函数(编译报错)
    }
private:
    int _year;
    int _month;
    int _day;
};

void Test()
{
    Date d1(2022, 1, 13); // 非const对象
    d1.Print();       // 规则2:非const可调用非const成员函数
    d1.PrintConst();  // 规则2:非const可调用const成员函数

    const Date d2(2022, 1, 13); // const对象
    // d2.Print();       // 规则1:const不能调用非const成员函数(编译报错)
    d2.PrintConst();  // 规则1:const可调用const成员函数
}

实践建议若成员函数不修改任何成员变量,建议声明为 const 成员函数 ------ 既保证代码安全性,又支持 const 对象调用。

8. 取地址及 const 取地址操作符重载

这两个默认成员函数用于获取对象的地址,编译器会自动生成,无需手动实现,仅在特殊场景(如隐藏对象真实地址)下需显式重载。

8.1 默认生成的行为

cpp 复制代码
class Date
{
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    const Date d2;
    cout << &d1 << endl; // 调用默认的取地址重载(返回&d1)
    cout << &d2 << endl; // 调用默认的const取地址重载(返回&d2)
    return 0;
}

8.2 显式重载示例(特殊需求)

cpp 复制代码
class Date
{
public:
    // 取地址重载:返回普通对象地址
    Date* operator&()
    {
        // 特殊需求:返回nullptr隐藏真实地址
        return nullptr;
    }
    // const取地址重载:返回const对象地址
    const Date* operator&() const
    {
        return nullptr;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    const Date d2;
    cout << &d1 << endl; // 输出:00000000(返回nullptr)
    cout << &d2 << endl; // 输出:00000000(返回nullptr)
    return 0;
}

关键提示绝大多数场景下,无需显式重载这两个运算符,使用编译器生成的默认版本即可。

相关推荐
2501_941877137 小时前
大规模系统稳定性建设方法论与工程实践分享
java·开发语言
2501_941820497 小时前
面向零信任安全与最小权限模型的互联网系统防护设计思路与多语言工程实践分享
开发语言·leetcode·rabbitmq
浩瀚地学7 小时前
【Java】面向对象进阶-接口
java·开发语言·经验分享·笔记·学习
cpp_25017 小时前
P1583 魔法照片
数据结构·c++·算法·题解·洛谷
fpcc7 小时前
跟我学C++中级篇——constinit避免SIOF
c++
2501_941802487 小时前
面向微服务限流、熔断与降级协同的互联网系统高可用架构与多语言工程实践分享
开发语言·python
2501_941875287 小时前
分布式系统中的安全权限与审计工程实践方法论经验总结与多语言示例解析分享
开发语言·rabbitmq
无限进步_7 小时前
【C语言】堆排序:从堆构建到高效排序的完整解析
c语言·开发语言·数据结构·c++·后端·算法·visual studio
雾岛听蓝7 小时前
STL 容器适配器:stack、queue 与 priority_queue
开发语言·c++
vortex57 小时前
AppArmor 受限 Shell 环境绕过技术分析:利用动态链接器路径差异实现 Profile 逃逸
linux·运维·服务器·网络安全