【C++】类和对象(中) —— 构造函数 | 析构函数 | 拷贝构造 | 赋值运算符重载【C++】类和对象(中) —— 构造函数 | 析构函数 | 拷贝构造 | 赋值运算符重载(下)

简介: 【C++】类和对象(中) —— 构造函数 | 析构函数 | 拷贝构造 | 赋值运算符重载【C++】类和对象(中) —— 构造函数 | 析构函数 | 拷贝构造 | 赋值运算符重载(下)

🍉这种叫做浅拷贝


1、一个对象修改会影响另一个对象

2、 会析构两次,程序崩溃


像这种类,就不能用默认的了,要我们自己实现深拷贝 —— 后面专门讲解


对于自定义类型变量,确实会调用它的拷贝构造函数,我们可以验证 ——


class A
{
public:
  A(const A& a)
  {
  cout << "A(const A&)" << endl;
  }
};
class Date
{
public:
  //构造函数
  Date(int year = 0, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
  A _a;
};
int main()
{
  Date d1(2002, 3, 7);
  Date d2(d1);
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png


六. 赋值运算符重载


我们知道内置类型可以直接使用运算符运算,但在默认情况下,C++是不支持自定义类型对象使用运算符,因为编译器也不知道运算规则


比如日期类:


Date d(2022, 10, 8);
  Date d(2022, 10, 26);


我想比较任意两个日期大小d1 < d2,想计算还有多少天国庆d2 - d1,都是没办法直接用运算符计算的。


为此,我们引入了运算符重载。


💦运算符重载

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


函数原型如下:


0a2653c851af460fa595bd959398a8f1.png


注:


不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数

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

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

.*(很少见)、 :: 、sizeof、 ? :(三目) 、. 注意以上5个运算符不能重载,这个经常在笔试选择题中出现

于是我们就实现了这样一个比较日期类大小的逻辑 ——


注:传参可以传值,但是在C++中建议传引用,这样可以减少拷贝提高效率(减少拷贝构造的消耗)。并且如果我们无需改变操作数,就用常引用const Date&,(防止我们在修改形参同时把实参也改了,同时const引用接收对象,权限缩小或者不变)


0a2653c851af460fa595bd959398a8f1.png


因为我的成员变量是private私有的,在类外不能访问,所以都出错了,那我们暂且先把访问修饰限定去掉


接下来有两个问题 ——


🔮1、d1==d2是怎么样调用这个函数的?


🔮2、成员变量私有在类外面访问不了,如何解决?


🧐问题1:如何调用

d1==d2是怎么样调用这个函数的?


经过调试,确实是调用了,实际上编译器会把d1 == d2转化为operator==(d1, d2)


但一般不会这样去显示写,但是这样就违背了我们增强可读性的初衷了,不然还不如直接写一个函数呢?


0a2653c851af460fa595bd959398a8f1.png


🧐问题2

成员变量私有在类外面访问不了,如何解决?


我们为了让程序运行简单粗暴去掉了访问修饰限定符private,这实际上破坏了封装性。


我们也可以在类中写上诸如int GetYear() {}这样的函数,这不破坏封装性,但还是有些麻烦,也不常用


int GetYear()
{
    return _year;
}


那我们把它放进类里面—— 又出现问题了


2d65d23f6d4748949b924e4057485923.png


这是因为成员函数默认多了一个this指针


class Date
{
public:
  Date(int year = 0, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  bool operator==( const Date& x)
  {
  return _year == x._year
    && _month == x._month
    && _day == x._day;
  }
//private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2022, 10, 8);
  Date d2(2022, 10, 26);
  cout << (d1 == d2) << endl;
  cout << d1.operator==(d2)<< endl; //编译器处理-> d1.operator==(&d1,d2)
  return 0;
}


当d1 == d2;我们这样调用它时,实际上会被编译器处理成d1.operator==(d2);


0a2653c851af460fa595bd959398a8f1.png


所以我们推荐这样写


cout << d1.operator==(d2)<< endl;


💦赋值运算符重载

🎨 赋值重载的概念

有了上面拷贝构造的基础,接下来的理解就轻松很多了


我们介绍了拷贝构造函数 —— 它是用一个已经存在的对象,拷贝初始化一个即将创建的对象


//拷贝构造
  Date d1(2022, 10, 9);
  Date d2(d1);


接下来介绍的是赋值重载—— 它是两个已经存在的对象,之间进行拷贝赋值


//赋值重载
  Date d1(2022, 10, 9);
  Date d3(2022, 10, 26);
  d1 = d3;


提问:这个是赋值重载还是拷贝构造?


Date d1(2002, 3, 7);
  Date d2 = d1;


根据定义出发 :只要一个存在的对象,这是拷贝构造


🎨赋值重载的实现细节

🔥1 .赋值运算符重载格式


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

返回值类型:T&,返回引用可以提高返回的效率(不用生成临时拷贝),有返回值目的是为了支持连续赋值

检测是否自己给自己赋值(判断)

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

class Date
{
public:
  //构造会频繁的调用,所以直接放在类里面定义作为inline
  Date(int year = 2022, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  //d1 = d3 赋值重载
  //d1.operator=(d2);
  void operator=(const Date& d)
  {
  _year = d._year;
  _month = d._month;
  _day = d._day;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2022, 10, 9);
  Date d2(d1);
  Date d3(2022, 10, 26);
  d1 = d3;//转化成 -> d1.operator(&d1, d3);
}


但是这样写不够完美,原因有两点:


🐋1、不能连续的赋值


打比方:


  int i = 0, j = 0, k = 2;

  k = i = j =10;


0a2653c851af460fa595bd959398a8f1.png


对应到C++中就是,日期类的连续赋值,我们需要返回值


d1 = d2 = d3;


⚡传值返回——


// 返回的是d2,类型为Date
Date operator=(const Date& d)
{
  _year = d._year;
  _month = d._month;
  _day = d._day;
    //即返回d1 -- 返回了调用这个函数隐含的操作数
     return *this; 
}


this是d2的地址,解引用*this就是返回值对象,这样就可以连续赋值了


⚡传值返回可以是可以,但是会生成一个临时拷贝的对象(占空间),况且这里出了作用域,d1(*this)还在,可以传引用返回:Date&


🐋2、自己给自己赋值,要判断

对于:自己给自己赋值


d2 = d2;


Date& operator=(const Date& d)
{
  if (this != &d)
  {
  _year = d._year;
  _month = d._month;
  _day = d._day;
  }
  return *this;
}


ps: 不能用对象 if (*this != d)去判断,会去调用operate!=,代价很大,而是用地址去判断


🔥如果我们没写,编译器默认生成的赋值重载,参考拷贝构造——


对于内置类型成员,会完成字节序值拷贝 —— 浅拷贝

对于自定义类型成员,会调用对应类的赋值运算符重载完成赋值

举例:


#include<iostream>
using namespace std;
class Time
{
public:
  Time()
  {
  _hour = 1;
  _minute = 1;
  _second = 1;
  }
  Time& operator=(const Time& t)
  {
  cout << "Time& operator=(const Time& t)" << endl;//为了调试看得到
  if (this != &t)
  {
    _hour = t._hour;
    _minute = t._minute;
    _second = t._second;
  }
  return *this;
  }
private:
  int _hour;
  int _minute;
  int _second;
};
class Date
{
public:
  Date(int year = 2022, int month = 1, int day = 1)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  //d1 = d3 赋值重载
  //d1.operator=(d2);
  /*void operator=(const Date& d)
  {
  _year = d._year;
  _month = d._month;
  _day = d._day;
  }*/
private:
  //内置类型
  int _year;
  int _month;
  int _day;
  // 自定义类型
  Time _t;
};
int main()
{
  Date d1(2022, 10, 9);
  Date d2(d1);
  Date d3(2022, 10, 26);
  d2 = d1 = d3;//转化成 -> d1.operator(&d1, d3);
}

0a2653c851af460fa595bd959398a8f1.png

🍅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 =”必须是非静态成员


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


·0a2653c851af460fa595bd959398a8f1.png


七. 小总结


➰拷贝构造函数


2d65d23f6d4748949b924e4057485923.png


➰赋值运算符重载


6de278e6d6694ce5bb08e7e842b7e74b.png


📢写在最后


英雄联盟世界赛要准备开打了,有爱看的兄弟吗?


相关文章
|
9天前
|
存储 编译器 C++
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
【C++成长记】C++入门 | 类和对象(中) |拷贝构造函数、赋值运算符重载、const成员函数、 取地址及const取地址操作符重载
|
16天前
|
C++
C++中拷贝构造会出现的情况
C++中拷贝构造会出现的情况
12 3
|
28天前
|
编译器 C语言 C++
【c++】类和对象(三)构造函数和析构函数
朋友们大家好,本篇文章我们带来类和对象重要的部分,构造函数和析构函数
|
1月前
|
编译器 C++
【C/C++ 构造函数 详解】深入解析C++ 构造函数:C++ 11 中的新特性与实践
【C/C++ 构造函数 详解】深入解析C++ 构造函数:C++ 11 中的新特性与实践
106 0
|
1月前
|
编译器 C++
『C++成长记』构造函数和析构函数
『C++成长记』构造函数和析构函数
|
3天前
|
存储 编译器 C语言
c++的学习之路:5、类和对象(1)
c++的学习之路:5、类和对象(1)
19 0
|
3天前
|
C++
c++的学习之路:7、类和对象(3)
c++的学习之路:7、类和对象(3)
19 0
|
2天前
|
设计模式 Java C++
【C++高阶(八)】单例模式&特殊类的设计
【C++高阶(八)】单例模式&特殊类的设计
|
2天前
|
编译器 C++
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
|
6天前
|
存储 安全 C语言
【C++】string类
【C++】string类