C++类与对象(中).

简介: ✅<1>主页:我的代码爱吃辣📃<2>知识讲解:C++🔥<3>创作者:我的代码爱吃辣☂️<4>开发环境:Visual Studio 2022💬<5>前言:C++类中一共有六个默认成员函数,今天我们先来将剩下的赋值重载,和取地址重载,以及实现Date类。

一.运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其

返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。


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


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

假如我们需要实现一个日期加上一个天数得到一个新的日期,此时按照我们以前的思考方式,是实现一个函数,但是利用运算符重载,会变得更加直观明了

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void show()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
  int _year;
  int _month;
  int _day;
};
//函数判断日期相等
bool DateEqual(const Date& d1, const Date& d2)
{
  return d1._year == d2._year &&
    d1._month == d2._month &&
    d1._day == d2._day;
}
//运算符重载的定义 使用关键字 operator 【运算符】
bool operator==(const Date& d1 ,const Date& d2)
{
  return d1._year == d2._year &&
       d1._month == d2._month && 
       d1._day == d2._day;
}
int main()
{
  Date d2(2022, 4, 23);
  Date d1(2022, 1, 13);
  //函数使用
  if (DateEqual(d1, d2))
    cout << "true" << endl;
  else
    cout << "false" << endl;
  //运算符重载的使用
  if (d1 == d2)
    cout << "true" << endl;
  else
    cout << "false" << endl;
  return 0;
}



但是一般我们的成员变量都是私有的,如果是私有的我们就不能直接访问,我们通过添加 get函数来解决,或者通过将运算符重载的函数写道类的里面,变成成员函数,这里我们就直接封装到类里面:

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
    // bool operator==(Date* this, const Date& date)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
  bool operator ==(const Date& date)
  {
    return _year == date._year &&
               _month == date._month && 
               _day == date._day;
  }
  void show()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d2(2022, 1, 13);
  Date d1(2022, 1, 13);
  //运算符重载的使用
  if (d1 == d2)
    cout << "true" << endl;
  else
    cout << "false" << endl;
  return 0;
}

注意:在将运算符重载写成成员成员函数时,重载运算符只需要写一个参数即可,因为有一个参数就是本对象本身,就是隐藏在成员函数列表的this指针。


6d5fa5596b654b818e3d20cc7d2efdf3.png


二.赋值运算符重载格式:

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


2.返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值


3.返回*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;
  }
    //赋值运算符重载
  Date& operator=(const Date& d)
  {
        //如果不是自身赋值,即d1=d1,才需要成员变量对用赋值。
    if (this != &d)
    {
      _year = d._year;
      _month = d._month;
      _day = d._day;
    }
    return *this;
  }
  void show()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date date1;
  Date date2(2023, 2, 7);
  date1.show();
  //因为有返回值就可以实现链式访问
  (date1 = date2).show();
  return 0;
}



4. 赋值运算符只能重载成类的成员函数不能重载成全局函数

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



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



5.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

class Time
{
public:
  Time()
  {
    _hour = 1;
    _minute = 1;
    _second = 1;
  }
  Time& operator=(const Time& t)
  {
    if (this != &t)
    {
      cout << "Time:operator=" << endl;
      _hour = t._hour;
      _minute = t._minute;
      _second = t._second;
    }
    return *this;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void show()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  // 基本类型(内置类型)
  int _year ;
  int _month;
  int _day;
  // 自定义类型
  Time _t;
};
int main()
{
  Date d1;
  d1.show();
  Date d2(2023,2,7);
  d1 = d2;
  d1.show();
  return 0;
}



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

但是对于有空间申请的空间是需要,我们不能光靠编译器自己生成的赋值运算符重载的:

class A
{
public:
  A()
  {
    a = 10;
    arr = (int*)malloc(sizeof(int) * a);
    for (int i = 0; i < a; i++)
    {
      arr[i] = i;
    }
  }
  void show()
  {
    for (int i = 0; i < a; i++)
    {
      cout << arr[i] << " ";
    }
    cout << endl;
  }
  int* arr;
  int a;
};
int main()
{
  A a;
  A b;
  a.show();
  b.show();
  b = a;
  //修改b对象的arr数组
  for (int i = 0; i < b.a; i++)
  {
    b.arr[i] = 0;
  }
  //观察两个对象中arr的元素
  b.show();
  a.show();
  return 0;
}



我们只是修改了b对象的 arr 数组,但是a对象的arr数组也被跟着修改了。

具体的情况是:



三.前置++和后置++重载

我们知道前置++和后置++唯一的区别就是,返回值不同。前置++返回++之后的结果,前置++返回++之后的结果。那么运算符重载以后也应该支持前置++和后置++的特性。

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;
  }
  void show()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2022, 10, 1);
  d1.show();
  Date d2 = d1++;
  d2.show();
  d1.show();
  d2 = ++d1;
  d1.show();
  d2.show();
  return 0;
}



四.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 ConstPrint() 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; // 日
};
int main()
{
  Date d1(2022, 1, 13);
  d1.Print();
  const Date d2(2022, 1, 13);
  d2.ConstPrint();
  return 0;
}


930dbe7d90574f5fac6eb3c24c3218f5.png


思考下面的几个问题:

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

解答:const对象不可以调用非const成员函数,因为非const成员函数的*this也是没有经过const修饰的,如果调用了const成员函数,就相当于权限放大。


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

解答:非const对象可以调用const成员函数,因为由const修饰的成员函数实际上修饰的就是*this,相当于权限缩小,是允许的。


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

解答:const成员函数不可以调用其他的非const成员函数,也是一个权限放大的例子。


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

解答:非const成员函数可以调用其他的const成员函数,也是一个权限缩小的例子。


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

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

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



这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需

要重载,比如想让别人获取到指定的内容!

class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  Date* operator&()
  {
    return (Date*)0x00000001;
  }
  const Date* operator&()const
  {
    return (Date*)0x0000250;
  }
private:
  int _year; // 年
  int _month; // 月
  int _day; // 日
};
int main()
{
  const Date date(2022,10,1);
  Date date1(date);
  cout << &date << endl;
  cout << &date1 << endl;
  return 0;
}


e0d6071c09944a0e80ec22d6179b8728.png


六. cout 输出类信息


f3f69056762c40629fba8086d5405d21.png


cout是类 ostream 的对象,表示面向窄字符(字符类型)的标准输出流。它对应于 C 流标准输出。


cf0bb28ba6974fef93f291b96eb9d363.png


作为 ostream 类的对象,可以使用插入运算符(运算符<<)将字符作为格式化数据写入其中,也可以使用成员函数(如 write)将字符作为无格式数据写入其中。


e476808d72164d63a4e9a25b904b3896.png


对象在带有外部链接和静态持续时间的头文件声明<iostream>:它持续整个程序的持续时间。


我们可以将ostream的对象cout,也作为运算符重载的参数:

class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //<<运算符重载
  void operator<<(ostream& out)
  {
    out << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date date(2023, 2, 8);
  //按照运算符左边到右边是参数从左往右对应
  //成员函数第一个参数就是this指针,所以<<运算符左边就是date对象
  date << cout;
  //等价于
  date.operator<<(cout);
  return 0;
}



但是 date<<cout ,非常不符合我们的使用习惯,有没有一种办法可以实现 cout << date 呢?造成这种情况的原因是成员函数的第一个参数默认是 this 指针,如果我们不将 << 设计为成员函数是不是就可以解决这个问题呢?

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;
};
//<<运算符重载
void operator<<(ostream& out, Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
  Date date(2023, 2, 8);
  //按照运算符左右参数,分别对应<<运算符重载从左往右的参数
  cout << date;
  //等价于
  operator<<(cout, date);
  return 0;
}


这就又有一个新的问题,成员变量私有化,不允许外部函数直接访问,面对这个问题,我们有两种解决方案:

1.设置get函数

设计成员函数用于返回私有成员变量的值,供外部读取。

class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //Get函数,用于外部读取私有成员变量
  int& Get_year()
  {
    return _year;
  }
  int& Get_month()
  {
    return _month;
  }
  int& Get_day()
  {
    return _day;
  }
private:
  int _year;
  int _month;
  int _day;
};
//<<运算符重载,为了支持连续访问,加上返回值
otsream& operator<<(ostream& out, Date& d)
{
  out << d.Get_year() << "/" << d.Get_month() << "/" << d.Get_day() << endl;
    return out;
}
int main()
{
  Date date(2023, 2, 8);
  //按照运算符左右参数,分别对应<<运算符重载从左往右的参数
  cout << date;
  //等价于
  operator<<(cout, date);
  return 0;
}



2.设计友元函数

只需在将函数声明写在Date类的内部,并在函数的前面加上关键字 friend,就可以和类建立友好关系,友元函数可以像成员函数一样随意访问成员变量。

class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //友元函数
  friend void operator<<(ostream& out, Date& d);
private:
  int _year;
  int _month;
  int _day;
};
//<<运算符重载
void operator<<(ostream& out, Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
  Date date(2023, 2, 8);
  //按照运算符左右参数,分别对应<<运算符重载从左往右的参数
  cout << date;
  //等价于
  operator<<(cout, date);
  return 0;
}



七.Date类实现

前面一直在用Date类举例子,这次我们来全面实现一下,练习我们之前学过的类与对象的姿势。

我们严格按照开发标准来实现Date类:

(1)Date类的主要功能模块

//Date.h文件
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
  friend ostream& operator<<(ostream& out, Date& d);
public:
  Date(int year, int month, int day );
  //日期比较大小
  bool operator>(const Date& date);
  bool operator >= (const Date& date);
  bool operator <(const Date& date);
  bool operator<=(const Date& date);
  bool operator ==(const Date& date);
  //日期+天数
  Date operator+(int x);
  //日期+=天数
  Date& operator+=(int x);
  //日期-日期=天数
  int operator-(const Date& date);
  //日期-天数=日期
  Date operator-(int x);
  //日期-=天数
  Date operator-=(int x);
  //Date++
  Date operator++(int);
  //++Date
  Date& operator++();
private:
  int GetMonthDay(int year, int month);//获取每月天数
  int _year;//年
  int _month;//月
  int _day;//日
};
//Date.cpp
//构造函数定义
Date::Date(int year , int month, int day)
{
  assert(month <= 12 && day <= this->GetMonthDay(year, month));
  _year = year;
  _month = month;
  _day = day;
}
//输出日期:年/月/日(友元函数)
void operator<<(ostream& out, Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
}

(2)日期大小比较

//Date.cpp
bool Date::operator>(const Date& date)
{
  if (_year != date._year)//如果年不相等,直接判断出结果
    return _year > date._year;
  if (_month != date._month)//年相等的时候,如果月不相等,直接判断出结果
    return _month > date._month;
  if (_day != date._day)//年.月相等的时候,如果日不相等,直接判断出结果
    return _day > date._day;
}
bool Date::operator >= (const Date& date)
{
    //>=就是或上一个相等
  return *this > date || *this == date;
}
bool Date::operator ==(const Date& date)
{    
    //相等需要年月日都相等
  return _year == date._year && _month == date._month && _day == date._day;
}
bool Date::operator <(const Date& date)
{    
    // >=取反
  return !(*this >= date);
}
bool Date::operator<=(const Date& date)
{    
    //<或上==
  return *this < date || *this == date;
}

(3)日期+天数,日期+=天数,++日期,日期++

int Date::GetMonthDay(int year, int month)
{
  int montharr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
  if (((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)&&month==2)
  {
    return 29;
  }
  return montharr[month];
}
Date Date::operator+(int x)
{
  Date tmp(*this);
  if (x < 0)
  {
    tmp -= -x;
    return tmp;
  }
  tmp += x;
  return tmp;
}
//先将天数全部加上去,然后依次减去当月天数,并判断年月的变换。
Date& Date::operator+=(int x)
{
  if (x < 0)
  {
    *this -= -x;
    return *this;
  }
  _day += x;
  while (_day > GetMonthDay(_year,_month))
  {
    _day -= this->GetMonthDay(_year,_month);
    _month++;
    if (_month > 12)
    {
      _year++;
      _month = 1;
    }
  }
  return *this;
}
//Date++(后置++)
Date Date::operator++(int)
{
  Date temp(*this);
  *this+=1;
  return temp;
}
//++Date(前置++)
Date& Date::operator++()
{
  *this += 1;
  return *this;
}

(4)日期-日期,日期-天数,日期-=天数

//计数计算,一天一天加,直到日期相等。
int Date::operator-(const Date& date)
{
  int count = 0;
  Date bigdate = *this;
  Date smalldate = date;
  if (bigdate < smalldate)
  {
    Date tmp = bigdate;
    bigdate = smalldate;
    smalldate = tmp;
  }
  while (!(smalldate == bigdate))
  {
    smalldate++;
    count++;
  }
  return count;
}
//日期-天数,先算整数月的减法,直到剩下的天数,小于本月天数,
//加上第一个月,和最后一个月的剩下的天数计算
Date Date::operator-(int x)
{
  Date tmp(*this);
  if (x < 0)//如果天数小于0,就相当于加上天数
  {
    tmp += -x;
    return tmp;
  }
  tmp._month--;
  if (tmp._month == 0)
  {
    tmp._month = 12;
    tmp._year--;
  }
  tmp._day = tmp.GetMonthDay(tmp._year, tmp._month);
  while (x > tmp.GetMonthDay(tmp._year, tmp._month))
  {
    x -= tmp.GetMonthDay(tmp._year, tmp._month);
    tmp._month--;
    if (tmp._month == 0)
    {
      tmp._month = 12;
      tmp._year--;
    }
    tmp._day = tmp.GetMonthDay(tmp._year, tmp._month);
  }
  tmp._day -= x;
  return tmp + this->_day;
}
//不仅计算返回,而且修改当前对象
Date Date::operator-=(int x)
{
  if (x < 0)
  {
    *this += -x;
    return *this;
  }
  *this = *this - x;
  return *this;
}

测试



 

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