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

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

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

                                             

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