【C++】类和对象(中)(2)

简介: 【C++】类和对象(中)(2)

【C++】类和对象(中)(1)https://developer.aliyun.com/article/1514564?spm=a2c6h.13148508.setting.19.4b904f0ejdbHoA

2、赋值运算符重载

(1)赋值运算符重载格式
  • 参数类型const T&传递引用可以提高传参效率。
  • 返回值类型T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回 *this :要复合连续赋值的含义。
class Date
{ 
public :
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
 
    Date (const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
 
        return *this; // 支持连续赋值
    }
 
    Date& operator=(const Date& d) // 传引用返回
    {
        if(this != &d) // 检测是否自己给自己赋值
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }   
        return *this;
}
private:
    int _year ;
    int _month ;
    int _day ;
};
 
void TestDate()
{
    Date d1(2023, 9, 13);
    Date d2(d1);
 
    Date d3(2023, 11, 28);
    d2 = d1 = d3;
}

(2)赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    int _year;
    int _month;
    int _day;
};
 
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
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)用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

注意 :内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
    Time& operator=(const Time& t)
    {
        if (this != &t)
        {
            _hour = t._hour;
            _minute = t._minute;
            _second = t._second;
        }
        return *this;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
 
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1;
    int _month = 1;
    int _day = 1;
    
    // 自定义类型
    Time _t;
};
 
int main()
{
    Date d1;
    Date d2;
    d1 = d2;
    return 0;
}

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?

像日期类这样的类是没必要的。但有些需要深拷贝的类,其内部往往是很复杂的,是需要用户显式定义赋值运算符重载函数来完成深拷贝的。

// 下面这个程序会崩溃,这里需要用深拷贝去解决。
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;
    }
 
    void Push(const DataType& data)
    {
        // CheckCapacity();
        _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;
}

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。


3、前置++和后置++重载

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
 
    Date& operator++() // 前置++:返回+1之后的结果。
    {
        _day += 1;
        return *this; // this 指向的对象函数结束后不会销毁,故以引用的方式返回提高效率。
    }
 
    Date operator++(int) // 后置++是先使用后+1,因此需要返回+1之前的旧值
    {
        Date temp(*this); // 在实现时需要先将this保存一份,然后给this+1
        _day += 1;
        return temp; // 因为temp是临时对象,因此只能以值的方式返回,不能返回引用
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main()
{
    Date d;
    Date d1(2023, 9, 13);
    d = d1++; // d: 2023,9,13  d1:2023,9,14
    d = ++d1; // d: 2023,9,14  d1:2023,9,14
    return 0;
}

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++ 规定:后置++重载时多增加一个 int 类型的参数,但调用函数时该参数不用传递,编译器自动传递。前置返回的是引用后置返回的是


六、日期类的实现

1、Date.h

// Date.h
#pragma once
 
#include<iostream>
#include<assert>
#include<stdbool.h>
using namespace std;
 
class Date
{
public:
    // 获取某年某月的天数
    int GetMonthDay(int year, int month)
    {
        assert(month >= 1 && month <= 12);
 
        // 每月的天数(这个函数会被频繁调用,每次进来都要重新定义数组,所以将其定义为静态的)
    // 默认是平年
        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)))
        // 把month == 2写在前面可以直接筛选出更少的内容
        {
            day += 1; // 闰年的二月是29天
        }
        return day;
    }
 
    Date(int year = 1, int month = 1, int day = 1) // 全缺省的构造函数
  {
    _year = year;
    _month = month;
    _day = day;
 
    //判断日期是否合法
    if (_year < 0 || _month <= 0 || _month >= 13 || _day <= 0 || _day > GetMonthDay(_year, _month))
    {
      cout << _year << "/" << _month << "/" << _day << "->";
      cout << "非法日期" << endl;
    }
  }
 
    // 打印日期
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
    
    // 拷贝构造、赋值运算符、析构函数用编译器自动生成的就可以了(因为Date类是浅拷贝)
    
 
    // 日期 += 天数 --> d1 += 100
  Date& operator+=(int day);
    
    // 日期 + 天数 --> d1 + 100
  Date operator+(int day);
 
  // 日期 -= 天数 --> d1 -= 100
  Date& operator-=(int day);
    
    // 日期 - 天数 --> d1 - 100
  Date operator-(int day);
 
    // 前置++
  Date& operator++(); // 编译器会解释为:Date& operator++(Date* const this);
 
  // 后置++
  Date operator++(int); // 编译器会解释为:Date& operator++(Date* const this, int);
 
  // 前置--
  Date& operator--();
 
  // 后置--
  Date operator--(int);
 
    // >运算符重载
  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;
    else
      return false;
  }
 
    // ==运算符重载
  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; // 复用operator>、operator==
  }
 
  // <运算符重载
  bool operator<(const Date& d)
  {
    return !(*this >= d); // 复用operator>=,再取反
  }
 
  // <=运算符重载
  bool operator<=(const Date& d)
  {
    return !(*this > d); // 复用operator>,再取反
  }
 
  // !=运算符重载
  bool operator!=(const Date& d)
  {
    return !(*this == d); // 复用operator==,再取反
  }
 
    // 日期 - 日期(返回相差天数) --> d1 - d2
  int operator-(const Date& d);
 
private:
    int _year;
    int _month;
    int _day;
};

2、Date.cpp

(1)日期 += 天数(返回累加天数后的日期)

比如:d1 += 100

注意:d1本身要被更改,天数累加到 d1 上面去。

Date& Date::operator+=(int day)
{
  if (day < 0) // 如果day是负数,就向前计算,相当于 -=
  {
    return *this -= -day; // 调用-=运算符重载函数
  }
 
  _day += day; // 累加天数
 
    // 日期不合法,需要进位
  while (_day > GetDays(_year, _month)) // 表示当前月的天数已经过完了
  {
    _day -= GetDays(_year, _month);   // 减去当前月的天数
    _month++; // 月进位
 
    if (_month == 13) // 判断当前月份是否合法
    {
      _year++; // 年进位
      _month = 1; // 更新为1月
    }
  }
 
  return *this;
 
  /* 写法二:复用+运算符重载函数的代码
  *this = *this + day; // d1等价于*this,对d1进行+天数操作,再赋值给d1
  return *this;        // 返回d1
  */
}

(2)日期 + 天数(返回累加天数后的日期)

比如 :d1 + 100

注意:d1本身不能被更改,天数累加到一个临时对象上面去。

// 写法一:
Date Date::operator+(int day)
{
  Date tmp(*this); // 拷贝构造一份临时对象,防止调用本函数的对象被更改
 
  tmp._day += day; // 累加天数
  while (tmp._day > GetDays(tmp._year, tmp._month)) // 表示当前月的天数已经过完了
  {
    tmp._day -= GetDays(tmp._year, tmp._month);   // 减去当前月的天数
 
    tmp._month++; // 月进位
 
    if (tmp._month == 13) // 判断当前月份是否合法
    {
      tmp._year++;      // 年进位
      tmp._month = 1;   // 更新为1月
    }
  }
 
  return tmp; // 返回临时对象
}
 
// 写法二:
Date Date::operator+(int day)
{
  /* 复用 += 运算符重载函数的代码 */
 
  Date tmp(*this); // 拷贝构造一份临时对象
  tmp += day;      // 对临时对象进行 += 天数操作
  return tmp;      // 返回临时对象
}

(3)日期 -= 天数(返回累减天数后的日期)

比如:d1 -= 100

Date& Date::operator-=(int day)
{
  if (day < 0) // 如果day小于0,就往后计算,相当于 +=
  {
    return *this += -day; // 调用+=运算符重载函数
  }
 
  _day -= day; // 累减天数
 
  while (_day <= 0) // 说明天数不够减了,需要向上一个月去借
  {
    _month--; // 月份-1
    if (_month == 0)
    {
      _year--;
      _month = 12;
    }
    _day += GetDays(_year, _month); // 借上一个月的天数
  }
 
  return *this;
}

(4)日期 - 天数(返回累减天数后的日期)

比如:d1 - 100

Date Date::operator-(int day)
{
  // 复用 -= 运算符重载函数的代码
  Date tmp(*this); // 拷贝构造一份临时对象
  tmp -= day;      // 对临时对象进行 -= 天数操作
  return tmp;      // 返回临时对象
}

(5)前置++ 和 后置++

注意:按正常的运算符重载规则,无法区分 前置++ 和 后置++,为了区分,这里做了一个特殊处理,给 后置++ 增加了一个 int 参数,这个参数仅仅是为了区分,使 前置++ 和 后置++ 构成重载。

// 前置++
// ++d1
Date& Date::operator++()
{
  // 复用 += 运算符重载函数的代码
  *this += 1;
  return *this;
}
 
// 后置++
// d1++
Date Date::operator++(int)
{
  Date tmp(*this); // 保存当前对象自减前的值
  *this += 1; // 复用 += 运算符重载函数的代码
  return tmp; // 返回当前对象自减前的值
}

(6)前置-- 和 后置–
// 前置--
// --d1
Date& Date::operator--()
{
  // 复用 -= 运算符重载函数的代码 
  *this -= 1;
  return *this;
}
 
// 后置--
// d1--
Date Date::operator--(int)
{
  Date tmp(*this); // 保存当前对象自减前的值
  *this -= 1; // 复用 -= 运算符重载函数的代码
  return tmp; // 返回当前对象自减前的值
}

(7)日期 - 日期(返回相差的天数,有正负之分)

比如:d1 - d2

思路:让小的日期不断往后++,直到等于大的日期,统计加了多少次,就相差多少天。

  • 大的日期 - 小的日期 = 正的天数
  • 小的日期 - 大的日期 = 负的天数
int Date::operator-(const Date& d)
{
  // 判断出大的日期和小的日期
  Date max = *this;
  Date min = d;
  int flag = 1; // 加一个flag变量来控制天数的正负
 
  if (max < min)
  {
    max = d;
    min = *this;
    flag = -1;
  }
 
  // 让小的日期累加天数,加了多少次,说明就相差了多少天
  int count = 0;
  while (min != max)
  {
    ++min;
    ++count;
  }
 
  return flag * count;
}

【总结】
// Date.cpp
#include "Date.h"
 
// 日期 += 天数
Date& Date::operator+=(int day)
{
  if (day < 0) // 如果day是负数,就向前计算,相当于 -=
  {
    return *this -= -day; // 调用-=运算符重载函数
  }
 
  _day += day; // 累加天数
 
    // 日期不合法,需要进位
  while (_day > GetDays(_year, _month)) // 表示当前月的天数已经过完了
  {
    _day -= GetDays(_year, _month);   // 减去当前月的天数
    _month++; // 月进位
 
    if (_month == 13) // 判断当前月份是否合法
    {
      _year++; // 年进位
      _month = 1; // 更新为1月
    }
  }
  return *this;
}
 
// 日期 + 天数
Date Date::operator+(int day)
{
  Date tmp(*this); // 拷贝构造一份临时对象,防止调用本函数的对象被更改
 
  tmp._day += day; // 累加天数
  while (tmp._day > GetDays(tmp._year, tmp._month)) // 表示当前月的天数已经过完了
  {
    tmp._day -= GetDays(tmp._year, tmp._month);   // 减去当前月的天数
 
    tmp._month++; // 月进位
 
    if (tmp._month == 13) // 判断当前月份是否合法
    {
      tmp._year++;      // 年进位
      tmp._month = 1;   // 更新为1月
    }
  }
 
  return tmp; // 返回临时对象
}
 
// 日期 -= 天数
Date& Date::operator-=(int day)
{
  if (day < 0) // 如果day小于0,就往后计算,相当于 +=
  {
    return *this += -day; // 调用+=运算符重载函数
  }
 
  _day -= day; // 累减天数
 
  while (_day <= 0) // 说明天数不够减了,需要向上一个月去借
  {
    _month--; // 月份-1
    if (_month == 0)
    {
      _year--;
      _month = 12;
    }
    _day += GetDays(_year, _month); // 借上一个月的天数
  }
 
  return *this;
}
 
// 日期 -= 天数
Date Date::operator-(int day)
{
  // 复用 -= 运算符重载函数的代码
  Date tmp(*this); // 拷贝构造一份临时对象
  tmp -= day;      // 对临时对象进行 -= 天数操作
  return tmp;      // 返回临时对象
}
 
// 前置++
Date& Date::operator++()
{
  // 复用 += 运算符重载函数的代码
  *this += 1;
  return *this;
}
 
// 后置++
Date Date::operator++(int)
{
  Date tmp(*this); // 保存当前对象自减前的值
  *this += 1; // 复用 += 运算符重载函数的代码
  return tmp; // 返回当前对象自减前的值
}
 
// 前置--
Date& Date::operator--()
{
  // 复用 -= 运算符重载函数的代码 
  *this -= 1;
  return *this;
}
 
// 后置--
Date Date::operator--(int)
{
  Date tmp(*this); // 保存当前对象自减前的值
  *this -= 1; // 复用 -= 运算符重载函数的代码
  return tmp; // 返回当前对象自减前的值
}
 
// 日期 - 日期
int Date::operator-(const Date& d)
{
  // 判断出大的日期和小的日期
  Date max = *this;
  Date min = d;
  int flag = 1; // 加一个flag变量来控制天数的正负
 
  if (max < min)
  {
    max = d;
    min = *this;
    flag = -1;
  }
 
  // 让小的日期累加天数,加了多少次,说明就相差了多少天
  int count = 0;
  while (min != max)
  {
    ++min;
    ++count;
  }
  return flag * count;
}

七、const 成员

将 const 修饰的 “ 成员函数 ” 称之为 const 成员函数,const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中不能对类的任何成员进行修改。

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
 
    void Print() // void Print(Date* const this)
    {
        cout << "Print()" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
 
    void Print() const // void Print(const Date* const this)
    {
        cout << "Print() const" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
private:
    int _year;  // 年
    int _month; // 月
    int _day;   // 日
};
 
void Test()
{
    Date d1(2022,1,1);
    d1.Print();
 
    const Date d2(2023,1,1); // const修饰
    d2.Print(); // d2.Print(&d2); // &d2的类型是const Date*,只能读不能写
    // 传给第一个Print会导致权限放大为可读可写
}
  1. const 修饰成员函数是有好处的,这样 const 对象可以调用,非 const 对象也可以调用。
  2. 但并不是说所有的成员函数都要加 const ,具体得看成员函数的功能,如果成员函数是修改型(比如:operrato+=、Push),那就不能加;如果是只读型(比如:Print、operator+),那就最好加上 const。
  3. const 成员(只读)函数内不可以调用其它的非 const 成员(可读可写)函数(权限放大),非 const 成员(可读可写)函数内可以调用其它的 const 成员(只读)函数(权限缩小)。

八、取地址及const取地址操作符重载

下面这两个是默认成员函数,一般不用重新定义,不写编译器默认会自动生成。

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

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到这个类型对象的地址

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


相关文章
|
23小时前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
22小时前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
22小时前
|
编译器 C++
【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 )
本文探讨了C++中类的成员函数,特别是取地址及const取地址操作符重载,通常无需重载,但展示了如何自定义以适应特定需求。接着讨论了构造函数的重要性,尤其是使用初始化列表来高效地初始化类的成员,包括对象成员、引用和const成员。初始化列表确保在对象创建时正确赋值,并遵循特定的执行顺序。
|
22小时前
|
C语言 C++
【C++】日期类Date(详解)③
该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。
|
23小时前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
23小时前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
23小时前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。
|
23小时前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。
|
3天前
|
存储 编译器 C语言
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
9 2
|
3天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
5 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)