【C++】类与对象(三) 运算符重载 赋值重载 取地址及const取地址操作符重载(2)

简介: 【C++】类与对象(三) 运算符重载 赋值重载 取地址及const取地址操作符重载(1)

四、赋值运算符重载(默认成员函数)

1、引入

我们首先来看一个使用场景,我们想要把一个已经初始化的自定义类型的数据赋值给另一个已经初始化的自定义类型(不是对象初始化时赋值,对象初始化时赋值用的是拷贝构造)该怎么办?

看看下面的代码:

//赋值重载
#include<iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print()
  {
    cout << _year << "年" << _month << "月" << _month << "日" << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2 = d1;//或者Date d2(d1)  会调用默认生成的拷贝构造,对象初始化时赋值用的是拷贝构造
  Date d3;
  d3 = d1;//我们没有实现Date类的运算符 = 的赋值重载,所以会调用默认生成的赋值重载
          //最后d3里面的数据与d1一样
}

2、特性

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

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

实例代码:

如上面的代码

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

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

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

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

#include<iostream>
using namespace std;
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)
  {
    if (this != &d)
    {
      _year = d._year;
      _month = d._month;
      _day = d._day;
    }
    return *this;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2023,2,12);
  Date d2;
  d2 = d1;
  return 0;
}

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

#include<iostream>
using namespace std;
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 =”必须是非静态成员



原因:赋值运算符如果不显式实现,编译器会生成一个默认的。

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

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

和拷贝构造函数一样我们继续思考:既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,对于内置类型还需要自己实现吗?

和拷贝构造一样,特性4也是我们写与不写复制重载函数的判断条件!

例如:

// 这里会发现下面的程序会崩溃掉,编译器生成的是浅拷贝,导致我们析构了两次空间,
//这里就需要我们以后讲的深拷贝去解决。
#include<iostream>
using namespace std;
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;
}

到这里我们就把六个默认成员函数中的第四个:复制重载给讲完了。

复制重载其实是运算符重载的一部分!

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

1、取地址操作符重载(默认成员函数)

我们还是先看代码再思考:

#include<iostream>
using namespace std;
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;
};
int main()
{
  Date d1;
  cout << &d1 << endl;
}

结果符合我们的预期,你可能觉得没有什么值得思考的点。

但是我们说过:对于自定义类型我们不能对他们像对内置类型那样使用运算符,但是我们对Date类的对象 d1 使用了取地址运算符&,而我们并没有实现&的运算符重载,结果我们却可以使用&,而且结果很对。为什么呢?

这是因为第五个默认成员函数:取地址操作符重载,即我们不写,编译器会帮我们自动生成,它的作用就是帮我们实现自定义类型对象的取地址。

取地址重载的手动实现

通常情况下我们一般自己不写此函数,让编译器自动生成。那假设我们自己实现此函数该怎么办呢?

实现代码如下:

//取地址重载函数
#include<iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //取地址重载
  Date* operator&()
  {
    return this;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  cout << &d1 << endl;
}

2、const取地址操作符重载(默认成员函数)

我们定义对象时一般都不会加const,那我们如果给对象加const会发生什么?

那我们再看一段代码:

#include<iostream>
using namespace std;
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;
  }
private:
  int _year; // 年
  int _month; // 月
  int _day; // 日
};
int main()
{
  Date d1(2022, 1, 13);
  d1.Print();
  const Date d2(2022, 1, 13);
  d2.Print();
  return 0;
}

我们发现无法编译通过

为什么给对象加了const后我们调用函数就失败了呢?按照加const报错的常见原因,不难想应该是权限被放大了。

还记得this指针的类型是什么吗?答案是:* const类型。这里应该是Date * const

我们用const修饰的对象取地址后应该是什么类型?答案是:const *。这里应该是const Date*

两个类型不匹配,const修饰对象后内容不能被更改,所以我们的this指针要改变类型,在*前加一个const

但是呢 this指针是编译器传递的,我们无法加const ,这该怎么办呢?

这里C++编译器又做了特殊化处理我们需要加const在函数括号后面,才能对this指针进行修饰

正确代码:

#include<iostream>
using namespace std;
class Date
{
public:
  Date(int year, int month, int day)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  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; // 日
};
int main()
{
  Date d1(2022, 1, 13);
  d1.Print();
  const Date d2(2022, 1, 13);
  d2.Print();
  return 0;
}

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

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗?
    答案:不可以,传递this指针时权限会放大
  2. 非const对象可以调用const成员函数吗?
    答案:可以,传递this指针时权限缩小
  3. const成员函数内可以调用其它的非const成员函数吗?
    答案:不可以,传递this指针时权限会放大
  4. 非const成员函数内可以调用其它的const成员函数吗?
    答案:可以,传递this指针时权限缩小

const取地址重载手动实现

同理在前面的代码中我们取const类型的地址时没有对&进行重载,但我们却可以使用,同样是因为编译器自动帮我们实现了const取地址重载

注意两个不太一样,两个函数构成函数重载!

取地址操作符重载

Date* operator&()             //对非 const 对象取地址

const取地址重载

const Date* operator&()const  //对 const 对象取地址

手动实现:

//const取地址重载函数
#include<iostream>
using namespace std;
class Date
{
public:
  Date(int year=0, int month=0, int day=0)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  void Print() const
  {
    cout << "Print()const" << endl;
    cout << "year:" << _year << endl;
    cout << "month:" << _month << endl;
    cout << "day:" << _day << endl << endl;
  }
  const Date* operator&()const //返回值const Date * 是为了与this 指针保持一致
  {
    return this;
  }
private:
  int _year; // 年
  int _month; // 月
  int _day; // 日
};
int main()
{
  const Date d1;
  cout << &d1 << endl;
  return 0;
}

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

相关文章
|
17天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
60 30
|
6天前
|
并行计算 Unix Linux
超级好用的C++实用库之线程基类
超级好用的C++实用库之线程基类
12 4
|
6天前
|
C++ Windows
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
HTML+JavaScript构建C++类代码一键转换MASM32代码平台
|
6天前
|
C++
2合1,整合C++类(Class)代码转换为MASM32代码的平台
2合1,整合C++类(Class)代码转换为MASM32代码的平台
|
6天前
|
存储 运维 监控
超级好用的C++实用库之日志类
超级好用的C++实用库之日志类
12 0
|
1月前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
2月前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)
|
1月前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
1月前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
2月前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
下一篇
无影云桌面