C++之面向对象(中)(三)

简介: C++之面向对象(中)(三)

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

运行结果:

这种因为值拷贝造成的程序崩溃和拷贝构造函数中讲到的程序崩溃原因相同。

程序分析图解如下:

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

4. 前置++和后置++重载的概念与区别

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)拷贝构造函数生成新的类对象,赋值运算符不能;

(2)拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检测源对象是否与新建对象相同。赋值运算符需要检测,如果原来的对象中有内存分配要先把内存释放掉

七、取地址重载(普通对象和const对象取地址)

1.日期类的实现

class Date
{
public:
  void Print()
  {
    cout << _year << "-" << _month << "-" << _day << endl;
  }
  // 获取某年某月的天数
  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];
    if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))//如果是闰年,则二月是29天
    {
      ++day;
    }
    return day;
  }
  // 全缺省的构造函数
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
    //检查日期是否合法
    if (_month > 12 || _day > GetMonthDay(_year, _month))
    {
      cout << "非法日期" << endl;
    }
  }
  // 拷贝构造函数
  // d2(d1)
  Date(const Date& d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
  }
  // 赋值运算符重载
  // d2 = d3 -> d2.operator=(&d2, d3)
  Date& operator=(const Date& d)
  {
    _year = d._year;
    _month = d._month;
    _day = d._day;
    return *this;
  }
  // 析构函数
  ~Date()
  {
    _year = 0;
    _month = 0;
    _day = 0;
  }
  // 日期+=天数
  Date& operator+=(int day)
  {
    do
    {
      int redays = GetMonthDay(_year, _month) - _day;
      if (redays > day)
      {
        _day += day;
        day = 0;
      }
      else
      {
        day -= redays;
        _month++;
        if (_month == 13)
        {
          _month = 1;
          _year++;
        }
        _day = 0;
      }
    } while (0 != day);
    return *this;
  }
  // 日期+天数
  Date operator+(int day)
  {
    Date temp(_year, _month, _day);
    do
    {
      int redays = GetMonthDay(temp._year, temp._month) - _day;
      if (redays > day)
      {
        temp._day += day;
        day = 0;
      }
      else
      {
        day -= redays;
        temp._month++;
        if (_month == 13)
        {
          _month = 1;
          _year++;
        }
        temp._day = 0;
      }
    } while (0 != day);
    return temp;
  }
  // 日期-天数
  Date operator-(int day)
  {
    Date temp(_year, _month, _day);
    do
    {
      if (temp._day >= day)
      {
        temp._day -= day;
        day = 0;
      }
      else
      {
        day = day - temp._day;
        temp._month--;
        if (temp._month == 0)
        {
          temp._year--;
          temp._month = 12;
        }
        temp._day = GetMonthDay(temp._year, temp._month);
      }
    } while (0 != day);
    return temp;
  }
  // 日期-=天数
  Date& operator-=(int day)
  {
    do
    {
      if (_day >= day)
      {
        _day -= day;
        day = 0;
      }
      else
      {
        day = day - _day;
        _month--;
        if (_month == 0)
        {
          _year--;
          _month = 12;
        }
        _day = GetMonthDay(_year,_month);
      }
    } while (0 != day);
    return *this;
  }
  // 前置++
  Date& operator++()
  {
    *this += 1;
    return *this;
  }
  // 后置++ 
  Date operator++(int) 
  {
    Date temp(_year, _month, _day);
    *this += 1;
    return temp;
  }
  // 后置--
  Date operator--(int)
  {
    Date temp(_year,_month,_day);
    *this -= 1;
    return temp;
  }
  // 前置--
  Date& operator--()
  {
    *this -= 1;
    return *this;
  }
  // >运算符重载
  bool operator>(const Date& d) const
  {
    if (_year > d._year)
    {
      return true;
    }
    else if (_month > d._month)
    {
      return true;
    }
    else if (_day > d._day)
    {
      return true;
    }
    return false;
  }
  // ==运算符重载
  bool operator==(const Date& d) const
  {
    return (_year == d._year) && (_month == d._month) && (_day == d._day);
  }
  // >=运算符重载
  bool operator >= (const Date& d) const
  {
    return *this > d || *this== d;
  }
  // <运算符重载
  bool operator < (const Date& d) const
  {
    if (_year < d._year)
    {
      return true;
    }
    else if (_month < d._month)
    {
      return true;
    }
    else if (_day < d._day)
    {
      return true;
    }
    return false;
  }
  // <=运算符重载
  bool operator <= (const Date& d) const
  {
    return *this < d || *this == d;
  }
  // !=运算符重载
  bool operator != (const Date& d) const
  {
    return !(*this == d);
  }
  // 日期-日期 返回天数
  int operator-(const Date& d) const
  {
    Date max = *this;
    Date min = d;
    int flag = 1;
    int day = 0;
    if (*this < d)
    {
      max = d;
      min = *this;
      flag = -1;
    }
    while (max != min)
    {
      min++;
      day++;
    }
    return day * flag;
  }
private:
  int _year;
  int _month;
  int _day;
};

2.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;
  }
  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();
  const Date d2(2022, 1, 13);
  d2.Print();
}

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

这两个默认成员函数一般不用重新定义,编译器默生成的就够用。

class Date
{
public:
  Date* operator&()
  {
  //return nullptr;//如果不想被取到地址
    return this;
  }
  const Date* operator&()const
  {
  //return nullptr;//如果不想被取到地址
    return this;
  }
private:
  int _year; // 年
  int _month; // 月
  int _day; // 日
};

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

九、练习题

学习过本文后,或许我们可以尝试解答以下几个问题:

OJ题:两个栈实现一个队列

思考题:

1.const对象可以调用非const成员函数吗?

答:不能,这属于权限放大,是不允许的。非const成员函数中可能会存在修改const对象的可能。

2.非const对象可以调用const成员函数吗?

答:可以,这属于权限缩小,是允许的。

3.const成员函数内可以调用其它的非const成员函数吗?

答:不能,这属于权限放大,是不允许的。非const成员函数中可能会存在修改const对象的可能。

4.非const成员函数内可以调用其它的const成员函数吗?

答:可以,这属于权限缩小,是允许的。


总结

以上就是今天要讲的内容,本文介绍了C++面向对象中的六个默认成员函数的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。

最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

相关文章
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
84 11
|
2月前
|
存储 安全 编译器
【C++核心】一文理解C++面向对象(超级详细!)
这篇文章详细讲解了C++面向对象的核心概念,包括类和对象、封装、继承、多态等。
27 2
|
1月前
|
存储 编译器 C语言
【C++】初识面向对象:类与对象详解
【C++】初识面向对象:类与对象详解
|
6月前
|
算法 Java 程序员
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
53 0
|
3月前
|
存储 安全 数据处理
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
|
3月前
|
算法 数据可视化 C++
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
|
4月前
|
存储 开发框架 Java
|
5月前
|
算法 编译器 C语言
C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)
C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)
70 3
|
4月前
|
Java C++ iOS开发
|
5月前
|
C++
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性