• 类和对象(3)


    1.回顾上节

    在这里插入图片描述
    默认成员函数:我们不写,编译器自动生成。我们不写,编译器不会自动生成
    默认生成构造和析构:

    1. 对于内置类型不做处理
    2. 对于自定义类型会调用对应的构造/析构。

    2. 拷贝构造

    #include 
    using namespace std;
    class Date
    {
    public:
    	Date(int year=1, int month=1, int day=1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	//拷贝构造,函数名和类名相同
    	//拷贝构造的参数为什么不能是传值?
    	//C++自定义类型的成员在这个地方传值需要调用拷贝构造,无穷无尽
    	//因此自定义类型必须调用拷贝构造,所以要用引用&。最好加const
    	Date(const Date& d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    	void Print()
    	{
    		cout << _year << "年" << _month << "月" << _day << "日" << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d1;
    	Date d2(d1);
    	d1.Print();
    	d2.Print();//这时改变_year等会改变d1
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    在这里插入图片描述
    以下为不调用拷贝构造时,会默认生成拷贝构造
    在这里插入图片描述
    内置类型会处理,因此日期类不需要自己去写拷贝构造
    自定义类型会去调用他的拷贝构造

    
    	Stack st1;
    	Stack st2(st1);
    	//栈中保持后进先出,后定义的先析构。
    	//st1变成野指针。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    指向同一块空间的问题:

    1. 插入删除数据会互相影响
    2. 析构两次,程序崩溃。
      默认的拷贝:
      浅拷贝/值拷贝
      **深拷贝:**让各自有各自独立的空间,开另外的空间,把值拷贝下来。
      更深入层次的拷贝
    typedef int DataType;
    class Stack
    {
    public:
    	Stack(size_t capacity = 10)
    	{
    		_array = (DataType*)malloc(capacity * sizeof(DataType));
    		if (nullptr == _array)
    		{
    			perror("malloc申请空间失败");
    				exit(-1);
    		}
    		_size = 0;
    		_capacity = capacity;
    	}
    void Push(const DataType& data)
    {
    	_array[_size] = data;
    	_size++;
    }
    Stack(const Stack& st)//深拷贝
    {
    	_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
    	if (nullptr == _array)
    	{
    		perror("malloc申请空间失败");
    		exit(-1);//直接终止程序
    	}
    	//拷贝数组空间上的值
    	memcpy(_array, st._array, sizeof(DataType) * st._size);
    	_size = st._size;
    	_capacity = st._capacity;
    }
    ~Stack()
    {
    	if (_array)
    	{
    		free(_array);
    		_array = nullptr;
    		_capacity = 0;
    		_size = 0;
    	}
    }
    private:
    	DataType *_array;
    	size_t _size;
    	size_t _capacity;
    	};
    int main()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    	st1.Push(3);
    	st1.Push(4);
    
    	Stack st2(st1);
    	//栈中保持后进先出,后定义的先析构。
    	//没有写拷贝构造,编译器自动生成了一个
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    什么情况下需要写拷贝构造呢?
    不能用指针来衡量,如果自己实现了析构函数释放了空间,就需要实现拷贝构造。

    1. 对于内置类型完成浅拷贝/值拷贝–按byte一个个拷贝
    2. 自定义类型,去调用这个成员拷贝构造/赋值重载
      2种大方向的特性
    typedef int DataType;
    class Stack
    {
    public:
    	Stack(size_t capacity = 10)
    	{
    		_array = (DataType*)malloc(capacity * sizeof(DataType));
    		if (nullptr == _array)
    		{
    			perror("malloc申请空间失败");
    				exit(-1);
    		}
    		_size = 0;
    		_capacity = capacity;
    	}
    void Push(const DataType& data)
    {
    	_array[_size] = data;
    	_size++;
    }
    Stack(const Stack& st)
    {//拷贝构造对内置类型完成值拷贝或者浅拷贝。
    	cout << "Stack(const Stack& st)" << endl;
    	_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
    	if (nullptr == _array)
    	{
    		perror("malloc申请空间失败");
    		exit(-1);//直接终止程序
    	}
    	//拷贝数组空间上的值
    	memcpy(_array, st._array, sizeof(DataType) * st._size);
    	_size = st._size;
    	_capacity = st._capacity;
    }
    ~Stack()
    {
    	if (_array)
    	{
    		free(_array);
    		_array = nullptr;
    		_capacity = 0;
    		_size = 0;
    	}
    }
    
    
    private:
    	DataType *_array;
    	size_t _size;
    	size_t _capacity;
    	};
    //对于自定义类型,不需要写拷贝构造和构造。不写编译器会自动生成构造函数,构造函数符合我们的需求
    class MyQueue
    {
    public:
    	//默认生成构造和析构
    	//默认生成拷贝构造
    private:
    	Stack _pushST;
    	Stack _popST;
    	int _size = 0;//缺省值,用缺省值处理
    };
    int main()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    	st1.Push(3);
    	st1.Push(4);
    
    	Stack st2(st1);
    	//栈中保持后进先出,后定义的先析构。
    	//没有写拷贝构造,编译器自动生成了一个
    	MyQueue q1;
    	//调用了拷贝构造
    	MyQueue q2(q1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    在这里插入图片描述
    那些场景存在拷贝构造
    Date d2(d1);
    Date d3=d1;//拷贝构造
    传返回值的过程中能用引用就用引用,减少拷贝。除非就是想让他自己调用拷贝构造,拷贝一份独立的出来
    参数基本都可以用引用,返回值不一定。局部对象不能用引用。

    Date Test(Date d)
    {
      Date temp(d);
      return temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. 运算符重载(非常重要)

    为了增强程序的可读性,是具有特殊函数名的函数,也有其返回值类型函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

    • 比较日期大小
      内置类型可以比较大小,自定义类型不可以
    • 运算符重载和函数重载无关:
      函数重载是支持参数名相同,参数不同的函数,随时可以用
      运算符重载:自定义类型对象可以使用运算符。
      两个地方都用了重载,但两个地方没有关联
    • 运算符重载:实现一个函数。新增一个关键字operator加操作符有参数有返回值
    • 参数和返回值根据运算符确定。有的有返回值有的没有。
    • 运算符有几个操作数就有几个参数
    class Date
    {
    public:
    	Date(int year=1, 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;
    }
    int main()
    {
    	Date d1(2023, 9, 14);
    	Date d2(2023, 9, 14);
    
    	cout<<operator==(d1, d2)<<endl;
    	cout <<( d1 == d2) << endl;//全局函数,转换成调用这个函数operator==(d1,d2);和上一行一样
    	
    	//运算符优先级<<高于==
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述
    当放成私有时

    class Date
    {
    public:
    	Date(int year=1, int month=1, int day=1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    	
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    直接把函数放在类里面。类外面受到访问限定符的限制,放到类里面就解决问题了。但是会报错
    在这里插入图片描述
    其中还有隐藏的参数(2个):this
    成员函数调用的方式也不同了。

    //d1==d2转换为d1.operator==(d2)
    bool operator==(const Date& d)
    	{
    	//this:d1;d:d2
    		return this->_year == d._year
    			&& _month == d._month
    			&& _day == d._day;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    
    	cout<<d1.operator==(d2)<<endl;
    	cout <<( d1 == d2) << endl;//成员函数转换成调用这个函数d1.operator==(d2);和上一行一样
    	
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    运算符重载

    1. 函数名:operator+运算符或操作符
    2. 返回值类型/参数:根据需求调用
    3. 不能乱接其他符号创造一个新的操作符,如:operator@
    4. 必须有一个类类型参数**(自定义类型)**
    5. 不能对内置类型重载,其含义不能改变。如内置类型的整型——,不能改变其含义
    6. 作为类成员函数重载时,其形参看起来比操作数目少1,因为成员函数的第一个参数为隐藏的this
    7. .* :: sizeof ?:(三目运算符) .(成员访问) 注意以上5个运算符不能重载,这个经常在笔试选择题中出现
    //b1
    bool 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;
    	}
    	else
    	{
    		return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    //b1<=b2复用,根据上面有<有=

    bool operator<=(const Date& d)
    {
    	return *this < d || *this == d;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    //b1>b2,取反。

    bool operator>(const Date& d)
    {
    	return !(*this <= d) ;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4. 赋值运算符重载

    d1=d2;//是一种拷贝

    //d1=d2
    void operator=(const Date& d)//不用引用不会无穷递归,但会白白走一次拷贝构造,所以最好加上引用
    {
    
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    d3=d2=d1;//编译不通过d1赋值给d2,d2的返回值传给d3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    连续赋值,从右往左赋值。i=j=k; k赋值给j,返回j

    Date& operator=(const Date& d)
    {
    
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    	//*this是d1
    	return *this;
    	//出了作用域还在,应该加引用。
    	//返回值是为了支持连续赋值,保持运算符的特性。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    d1=d1
    自己给自己赋值,可以加一个判断

    Date& operator=(const Date& d)//引用
    {
     if(this!=&d)//取地址,this是左操作数的地址,d是右操作数的别名,地址相同则不用自己给自己赋值
     {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
     }
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    +=支持连续赋值,只要支持连续赋值就都有返回值。。
    前置++,d1.operator();
    后置++,d2.operator(int);
    int仅仅是为了占位,和牵制重载区分

    //++d1;
    Date& Date::operator++()
    {
      Date tmp(*this);
      *this+=1;
      return tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    //d1++
    Date Date::operator++(int)
    {
      Date tmp(*this);
     *this+=1;
     return tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    对于内置类型,前置和后置++没有区别
    对于自定义类型,**前置++**效率高,后置++还要拷贝

  • 相关阅读:
    数组常用的几种排序方式
    ACM MM 2023 | 基于去中心化表征的人体姿态估计方法
    Vulnhub系列靶机---HarryPotter-Fawkes-哈利波特系列靶机-3
    全栈---Proxy
    「聊设计模式」之外观模式(Facade)
    SpringBoot中如何集成ThymeLeaf呢?
    Paddle训练COCO-stuff数据集学习记录
    FFmpeg解复用器(解封装)简单测试【2】
    架构师总结了22条API设计的最佳实践
    Redis 官方可视化工具,功能真的强大
  • 原文地址:https://blog.csdn.net/weixin_73236117/article/details/132870212