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


相关文章
|
28天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
50 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
88 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
105 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
32 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
21 0
|
2月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
2月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)