C++:类和对象(中)

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

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

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

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

class Date {};

2.构造函数:

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();
    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();
    return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置

信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性:

       构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

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

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

class Date
{
public:
    //Date()
    //{
    //}
    //Date(int year, int month, int day)
    //{
    //    _year = year;
    //    _month = month;
    //    _day = day;
    //}
    Date(int year = 10, int month = 10, int day = 10)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    //Date d1();注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    Date d1;
    d1.Print();
    Date d2(2023,10,31);
    d2.Print();
    return 0;
}

       5. 如果在类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

注意:

       如果用户显式定义了构造函数,编译器将不再生成;(即自己写了一个构造函数)

       将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数;

       将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用(因为这里放开后需要的是一个无参的构造函数,取消注释后,不在构造一个默认的无参构造函数,就导致没法与构造函数匹配)

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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
  // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
  Date d1;
  return 0;
}

       6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

       解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。内置类型不做处理,自定义类型编译器会去调用默认构造函数。

内置类型:int、char、double、……以及指针

自定义类型:如上面的Date、Time就是我们自己定义的类型。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。

即可以:

private:

       int _year = 10;

       int _month = 10;

       int _day = 10;

class Time
{
public:
    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 _t;
};
int main()
{
    Date d;
    return 0;
}

     7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:1.无参构造函数、2.全缺省构造函数、3.我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。可以不传参数就调用构造,都可以叫默认构造,这三个函数不能同时存在,只能存在一个。

总结:

1.一般情况下,我们都要自己写构造函数;

2.成员都是自定义类型,或者声明时给了缺省值,可以考虑让编译器自己生成构造函数。

3.析构函数:

概念:

      析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

特性:

析构函数是特殊的成员函数,其特征如下:

       1. 析构函数名是在类名前加上字符 ~

      2. 无参数无返回值类型

     3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

注意:析构函数不能重载

      4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

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)
  {
    // CheckCapacity();
    _array[_size] = data;
    _size++;
  }
  // 其他方法...
  ~Stack()
  {
    if (_array)
    {
      free(_array);
      _array = NULL;
      _capacity = 0;
      _size = 0;
    }
  }
private:
  DataType* _array;
  int _capacity;
  int _size;
};
void TestStack()
{
  Stack s;
  s.Push(1);
  s.Push(2);
}//结束自动调用析构函数
int main()
{
    TestStack();
    return 0;
}

      5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:
  ~Time()
  {
    cout << "~Time()" << 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;
}

程序运行结束后输出:~Time(),

在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

       因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,

_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

      6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4.拷贝构造函数:

概念:

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

特征:

拷贝构造函数也是特殊的成员函数,其特征如下:

      1. 拷贝构造函数是构造函数的一个重载形式。

       2. 拷贝构造函数的参数只有一个且必须是类的类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

      3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

       4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

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);
  Stack s2(s1);
  return 0;
}

在这个代码中我们为什么会崩溃呢?

此时,s1,s2都指向了同一块malloc开辟的空间,程序运行结束时,调用析构函数,先将s2释放了,但是s1也需要释放,所以就造成同一块空间释放了两次,造成程序崩溃!!!

注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝/值拷贝。

上面代码正确的书写是:

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(const Stack& stt)
  {
    _array = (DataType*)malloc(sizeof(DataType) * stt._capacity);
    if (_array == nullptr)
    {
      perror("malloc fail");
      exit(-1);
    }
    memcpy(_array, stt._array, sizeof(DataType) * stt._size);
    _capacity = stt._capacity;
    _size = stt._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);
  Stack s2(s1);
  return 0;
}

5. 拷贝构造函数典型调用场景:

      1.使用已存在对象创建新对象

      2.函数参数类型为类类型对象

      3.函数返回值类型为类类型对象

class Date
{
public:
  //构造函数
  Date(int year, int minute, int day)
  {
    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)
{
  Date temp(d);
  return temp;
}
int main()
{
  Date d1(2022, 1, 13);
  Test(d1);
  return 0;
}

       为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

总结:

       1.浅拷贝/值拷贝:内置类型成员完成的拷贝;

      2.深拷贝:本质是熬吧指向的资源,让新对象拥有跟拷贝对象一样大的空间和一样的值;自定义类型成员调用这个成员的拷贝构造;

       比如:Stack就需要自己写拷贝构造完成深拷贝,顺序表、链表、二叉树等等的类都需要深拷贝(需要开辟资源!!!)。

5.赋值运算符重载:

运算符重载:

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型  operator操作符(参数列表)

注意:

       1.不能通过连接其他符号来创建新的操作符:比如operator[ 运算符 ]

       2.重载操作符必须有一个类类型参数,比如:不能int operator+(int x,int y)这样就不会调用内置的+,导致改变了内置的运算规则

       3.用于内置类型的运算符,其含义不能改变,例如:内置的整型"+",不能改变其含义

       4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

       5.[ .* ] [ :: ] [ sizeof ] [ ?: ](三目运算符) [ . ] 注意以上5个运算符不能重载。这个经常在笔试选择题中出

现。

// 全局的operator==
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //private:
  int _year;
  int _month;
  int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
    //这个是类外定义!!!
  return d1._year == d2._year
    && d1._month == d2._month
    && d1._day == d2._day;
}
void Test()
{
  Date d1(2018, 9, 26);
  Date d2(2018, 9, 27);
  cout << (d1 == d2) << endl;
}
int main()
{
  Test();
  return 0;
}

这里会发现运算符重载成全局需要成员变量是公有的,那么问题来了,封装性(自己的不让被别人使用)如何保证?

赋值运算符重载:

1. 赋值运算符重载格式

       1.参数类型:const T(类名)&,传递引用可以提高传参效率

      2.返回值类型:T(类名)&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

       3.检测是否自己给自己赋值(operator>(&d1, d2))

      4.返回*this :要符合连续赋值的含义

class Date
{
public:
  Date(int year = 1900, 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;
  }
bool operator>(const Date& y)//(const Date* this,const Date& y)
  {
    if (_year > y._year)
    {
      return true;
    }
    else if (_year == y._year && _month > y._month)
    {
      return true;
    }
    else if (_year == y._year && _month == y._month && _day > y._day)
    {
      return true;
    }
    return false;
  }
bool operator==(const Date& y)//(const Date* this,const Date& y)
  {
    return _year == y._year
      && _month == y._month
      && _day == y._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
    Date d1(2023,11,2);
    Dare d2(2000,1,1);
  bool ret1 = d1 > d2;  // operator>(d1, d2)-> d1.operator>(&d1, d2)
  bool ret2 = d1 == d2; // operator==(d1, d2)-> d1.operator==(&d1, d2)
}

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 =”必须是非静态成员
int main()
{
  Date d1(2000, 1, 1);
  Date d2(2000, 1, 2);
  (d1 = d2);
  return 0;
}

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了
故赋值

运算符重载只能是类的成员函数。

运算符重载和函数重载 他们之间没有关联!

       运算符重载:自定义类型可以直接使用运算符

       函数重载:可以允许参数不同的同名函数,

operator= 我们不写,编译器会生成默认的operator =
跟拷贝构造的行为类似,内置类型值拷贝,自定义类型调用他的赋值
比如:Date MyQueue (用两个栈实现队列)也可以不写,默认生成operator =就可以用
Stack必须自己实现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 = 1970;
  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;
}

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

我们知道cout<<、cin>>是打印(流插入)和输入(流提取),如果我们想对“ << ”、“ >> ”重载呢!

此时我们看到" cout << d16 '无法通过,需要d16在前才能通过,但是这样看着不顺,所以我们需要写成全局函数,但是又无法保证成员变量的封装性,此时引入友元函数,就能保证封装性了。

//cout <<
ostream& operator<<(ostream& out,Date& d)
{
  out << d._year << "-" << d._month << "-" << d._day << endl;
  return out;
}
//cout >>
istream& operator>>(istream& in, Date& d)
{
  in >> d._year >> d._month >> d._day;
  return in;
}

总结:

       其他运算度一般是实现成成员函数,“<<” ">>" 流运算符必须实现到全局,这样才能让流对象在第一个参数位置,才符合可读性。

分析:

       流本质是为了解决自定义类型的输入输出问题,printf 、scanf 无法解决自定义类型的输入问题,所以用:面向对象 + 运算符重载解决。

还需注意:

       有的人会所这样不会与左移>>,右移<<冲突吗?这里这个符号是双重定义的(两个意思),但是注意这里是运算符重载条件是自定义类型,而左移,右移是内置类型的!!!

前置++和后置++重载:

#include <iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  // 前置++:返回+1之后的结果
  // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
  Date& operator++()
  {
    _day += 1;
    return *this;
  }
  // 后置++:
  // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
  // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
  //自动传递
  // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
  //一份,然后给this + 1
  // 而temp是临时对象,因此只能以值的方式返回,不能返回引用
  Date operator++(int)
  {
    Date temp(*this);
    _day += 1;
    return temp;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d;
  Date d1(2022, 1, 13);
  d = d1++; // d: 2022,1,13 d1:2022,1,14
  d = ++d1; // d: 2022,1,15 d1:2022,1,15
  return 0;
}

前置++:返回+1之后的结果

注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率

后置++:

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

注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存

一份,然后给this+1而temp是临时对象,因此只能以值的方式返回,不能返回引用。

6.日期类的实现:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
  assert(_year >= 1 && _month >= 1);
  int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
  if (_month == 2 && ((year % 4 == 0 && year != 100) || (year % 400 == 0)))
  {
    return 29;
  }
  return arr[month];
}
//打印
void Date::Print()
{
  cout << _year << "-" << _month << "-" << _day << endl;
}
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
  _year = year;
  _month = month;
  _day = day;
  if (_year < 1 ||
    _month < 1 || _month > 12 ||
    _day < 1 || _day > GetMonthDay(_year, _month))
  {
    //assert(false);
    Print();
    cout << "日期非法" << endl;
  }
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
  if (_year < 1 || _month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month))
  {
    方法一
    //exit(-1);
    方法二
    //Print();
    //cout << "输入格式错误" << endl;
    //方法三
    assert(false);
  }
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
  if (this != &d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  return *this;
}
// 析构函数
Date::~Date()
{
  _year = 0;
  _month = 0;
  _day = 0;
}
// 日期+=天数
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 == 13)
    {
      _year++;
      _month = 1;
    }
  }
  return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
  Date tmp(*this);
  tmp += day;
  return tmp;
}
// 日期-天数
Date Date::operator-(int day)
{
  Date tmp(*this);
  tmp -= day;
  return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
  if (day < 0)
  {
    return *this += (-day);
  }
  _day -= day;
  while (_day <= 0)
  {
    _month--;
    if (_month == 0)
    {
      _year--;
      _month = 12;
    }
    _day += GetMonthDay(_year, _month);
  }
  return *this;
}
// 前置++
Date& Date::operator++()
{
  *this += 1;
  return *this;
}
// 后置++
Date Date::operator++(int)
{
  Date tmp(*this);
  *this += 1;
  return tmp;
}
// 后置--
Date Date::operator--(int)
{
  Date tmp(*this);
  *this -= 1;
  return tmp;
}
// 前置--
Date& Date::operator--()
{
  *this -= 1;
  return *this;
}
// >运算符重载
bool Date::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 Date::operator==(const Date& d)
{
  return _year == d._year
    && _month == d._month
    && _day == d._day;
}
// >=运算符重载
bool Date::operator>= (const Date& d)
{
  if (*this > d || *this == d)
  {
    return true;
  }
  return false;
  return *this > d || *this == d;
}
// <运算符重载
bool Date::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;
    
    //可以复用尽量复用
  return !(*this >= d);
}
// <=运算符重载
bool Date::operator<= (const Date& d)
{
  //if (*this < d || *this == d)
  //{
  //  return true;
  //}
  //return false;
  return !(*this > d);
}
// !=运算符重载
bool Date::operator!= (const Date& d)
{
  //if (*this == d)
  //{
  //  return false;
  //}
  //return true;
  return !(*this == d);
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
  Date max = d;
  Date min = *this;
  int flag = 1;
  if (*this > d)
  {
    max = *this;
    min = d;
    flag = -1;
  }
  int n = 0;
  while (min != max)
  {
    ++min;
    ++n;
  }
  return n * flag;
}

这里需要了解我们程序员在工作上能写效率更高的代码,就写效率更高的,如下:

7.const成员:

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

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << "Print()" << endl;
    cout << "year:" << _year << endl;
    cout << "month:" << _month << endl;
    cout << "day:" << _day << endl << endl;
  }
  //const Date* this
  void Print() const
  {
    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, 13);
  d1.Print();//&d1:const Datr* d1
  const Date d2(2022, 1, 13);
  d2.Print();//&d2:const Datr* d2
}
int main()
{
  Test();
  return 0;
}

在成员函数后面加上‘ const ’ 即在传过来的参数前都加上const一般用于不改变传递过来的参数的情况:+ 、-、判断……

成员函数定义原则:

1。能定义成const的成员函数都应该定义成const,这样const对象和非对象都可以调用(即:权限平移或权限缩小)

2.要修改成员变量的成员函数,不可以定义成const,

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

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

注意:只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

Date* operator&()
  {
    return this;
  }
  const Date* operator&() const
  {
    return this;
  }

特殊情况:

我们不想让别人知道这个地址时才会自己创建,此时:

       1.返回一个假地址;

       2.返回空指针

Date* operator&()
  {
    //return this;
    //给一个假地址
    return (Date*)0x1111111;
  }
  const Date* operator&() const
  {
    //return this;
    //不给看地址
    return nullptr;
  }

以上就是个人学习见解和学习的解析,欢迎各位大佬在评论区探讨!

感谢大佬们的一键三连! 感谢大佬们的一键三连! 感谢大佬们的一键三连!

                                             

目录
相关文章
|
1月前
|
编译器 C++
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
35 0
|
8天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
36 4
|
9天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
33 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
16 0
|
1月前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
31 0
|
1月前
|
编译器 C++
C++之类与对象(3)(下)
C++之类与对象(3)(下)
32 0
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)