【C++进阶之路】类和对象(中)

news2024/9/28 15:29:14

文章目录

  • 前言
    • 六大默认成员函数
  • 一.构造函数
    • 性质
    • 默认构造函数
    • 构造函数(需要传参)
  • 二.析构函数
    • 性质
    • 默认析构函数
    • 练习
  • 三.拷贝构造函数
    • 基本性质:
    • 形参必须是引用
    • 默认拷贝构造
      • 浅拷贝
      • 深拷贝
      • 自定义类型
  • 四.赋值运算符重载函数
    • 基本特征
    • 全局的运算符重载函数
    • 局部的运算符重载函数
        • 前置++与后置++的实现
    • 赋值运算符重载函数
    • 日期类(练习)
  • 五.取地址重载和const取地址重载
    • const 成员
    • 取地址重载
    • const取地址重载

前言

六大默认成员函数

在这里插入图片描述

1.构造函数——完成对象成员变量的初始化
2.析构函数——完成空间(主要是堆)的释放
3.拷贝构造——用一个已初始化的对象初始另一个正在初始化的对象
4.赋值重载——用一个已初始化的对象赋值给另一个已经初始化的对象
5.取地址重载——
6.const修饰的取地址重载——

  • 为什么叫默认成员函数呢?
  • 因为即使是空类也会在类里面自动生成这六个函数(默认),这些函数如果定义的话只能在类里面定义,或者在类里面声明,在类外面用作用域限定符进行定义(成员)。

一.构造函数

  • 为了弥补C语言的缺陷——比如在写用括号匹配,我们会注意到一个点,就是一不小心就会忘记初始化两个栈,有构造函数这个问题就得到了很好的解决。

性质

    1. 函数名与类名相同。
    1. 无返回值。
    1. 对象实例化时编译器自动调用对应的构造函数。
    1. 构造函数可以重载。

默认构造函数

举例代码:

#define _CRT_SECURE_NO_WARNINGS 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;
	}
	Date(int year, int month = 1, int day = 1)//构成重载
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A;//自动调用析构函数
	//Date A();
	return 0;
}

调试结果:
在这里插入图片描述

  • 不少观众又要问了——在全缺省的情况下调用函数不是应该写成下面注释的代码吗?
  • 我们乍一看好像是的,如果我们换一种形式呢——Date func(); 像不像一个函数声明呢?那为什么不这样写,其实就是为了与函数的声明进行区分!
  • 到这里其实还不够,我们继续分析!

  • 既然函数参数可以重载我们是否可以这样写呢?
#define _CRT_SECURE_NO_WARNINGS 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;
	}
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	//Date A;
	return 0;
}
  • 语法上是支持的,但是如果将注释的代码放开,再进行编译就会报错,因为不知道该调用哪一个构造函数,所以是可以的,但是不支持

  • 既然是默认成员函数,那么不写编译器应该也会帮我们生成一个。
#include<iostream>
using namespace::std;
class Date
{
public:
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A;
	//Date A();
	return 0;
}

图解:
在这里插入图片描述

  • 这是为什么呢?编译器竟然没有帮我们完成初始化。
  • 语法上对编译器生成的构造函数只是说明了一点:
  • 1.对内置类型不做处理——这里的内置类型指的是编译器原本就有的类型
  • 说明:有的编译器还是会处理的,我们就当做不处理对待。
  • 2.对自定义类型去调用它的默认构造

  • 那我们如果想要不写构造函数的情况下,如何初始化对象的变量呢?
  • C++11引出我们可在类的变量处使用缺省值。
#include<iostream>
using namespace::std;
class Date
{
public:
private:
	int _day = 1;
	int _month = 1;
	int _year = 1;
};
int main()
{
	Date A;
	return 0;
}

调试结果:
在这里插入图片描述

  • 很显然编译器帮助我们初始化了对象。

  • 总结:
  • 1 .默认构造函数指的是不传参就能调用的函数。
  • 2 .目前我们知道的默认构造函数要么是全缺省的,要么是无参的,要么就是编译器生成的。

构造函数(需要传参)

using namespace::std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A(2023,5,3);//这就跟函数调用差不多了,就多了个类型。
	return 0;
}

调试结果:
在这里插入图片描述

  • 总结:
    1. 一般情况下,内置类型一般都需要写构造函数,不能用编译器生成的。
    1. 自定义类型我们可以考虑不写构造函数,可以让编译器生成,其本质上是使用自定义类型的默认构造函数。
    1. 成员变量可以给缺省值。

二.析构函数

  • 如果在写括号匹配,初始化的问题不容易出错,那么在返回时的销毁就极大概率会出错,但这道编程题即使你不销毁也是可以过的,但作为一名合格的程序员,怎能放任不管?
  • 因此,析构函数就是为了解决这个问题,让程序在销毁时,自动释放空间(主要是堆)。

析构函数的定义:析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

  • 重要的事情说三遍:
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。
    1. 析构函数不完成对象的销毁,只是完成内部资源(堆空间)的清理工作。

性质

    1. 函数名为:~ + 类名
    1. 无参无返回值,不构成重载
    1. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
    1. 对象生命周期结束时 自动调用。

我们手写一个栈的初始化和释放

#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
	Stack stack1;
	return 0;
}

当我们创建一个栈对象时,会完成对象的初始化,当main函数执行结束时会完成堆空间的释放。

调试看结果:
在这里插入图片描述
此时mainj函数差一步结束:
在这里插入图片描述
继续调试:
在这里插入图片描述
继续执行程序结束,对象才进行释放。

默认析构函数

  • 默认生成的析构函数会不会把动态申请的资源进行释放呢?
#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
	Stack stack1;
	return 0;
}

调试:
在这里插入图片描述

  • 可知:在返回过后,并没有将动态申请的资源进行释放。
  • 因此:默认的构造函数不会对内置类型进行处理

  • 自定义类型呢?

我们定义一个对列:

#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

class MyQueue
{
	Stack stack_push;
	Stack stack_pop;
};
int main()
{
	MyQueue Q;
	return 0;
}

接着进行调试:
在这里插入图片描述

  • 可知:默认生成的析构函数会将其成员的资源进行释放,其本质上是调用其自定义成员的析构函数。
  • 因此:对象全为自定义成员,默认生成的析构函数会去调用其自定义成员的析构函数,完成资源的释放与清理。

  • 总结:
  • 默认生成的析构函数:
    1. 对内置类型不做处理。
    1. 对自定义类型会去调用它的析构函数。

那什么时候用写析构函数?什么时候不用写析构函数呢?

  • 对象有资源需要清理,且管理资源的是内置类型,这时就需要我们自己写析构函数
  • 对象有资源需要清理,但管理资源的是自定义类型,这时就不需要写
  • 对象没有资源需要清理,这时也不需要写

练习

  • 不同声明周期和作用域对象的析构和构造的调用顺序
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}
};


class C
{
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
};

class D
{
public:
    D()
	{
		cout << "D()" << endl;
	}
	~D()
	{
		cout << "~D()" << endl;
	}
};
class E
{
public:
	E()
	{
		cout << "E()" << endl;
	}
	~E()
	{
		cout << "~E()" << endl;
	}
};

A a;
B b;
int main()
{
	C c;
    D d;
	static E e;
	return 0;
}

这里我们列举了三种对象——全局对象,局部对象,static修饰的局部对象
话不多说,开始调试:
在这里插入图片描述

  • 到这可知:程序开始时a就调用了它的构造函数,并且按照语句的顺序进行先后构造
  • 因此:全局对象的构造优先,且遵循语句先后顺序。
    继续调试:

在这里插入图片描述

  • 由此判断局部对象的构造函数调用的顺序后于全局对象,并且也是按照语句的顺序进行构造的。
  • 因此:构造函数的优先级:全局大于局部,且遵循语句的先后顺序。
  • 说明:static 修饰的局部变量也是局部变量。

继续调试:
在这里插入图片描述

  • 可知:不加static修饰局部对象的析构函数的调用顺序与构造顺序相反且比加static的对象优先调用析构函数

接着调试直到程序结束:

在这里插入图片描述

  • 可以看出
    1. 析构函数static在局部之后释放,且顺序与构造函数的顺序相反。
    1. 全局对象最后释放且顺序与构造函数的顺序相反。

  • 总结:
    1. 构造函数调用顺序都是按语句的先后顺序进行调用的。
    1. 全局大于局部
    1. 析构函数的调用顺序都是与构造的调用顺序相反
    1. 优先级:不加static的局部对象>加static的局部对象>全局对象

三.拷贝构造函数

  • 在我们的日常使用时,如果需要一个对象进行修改,但不要破坏原来的对象,这时就需要我们单独拷贝一份出来——有点像后置++的效果,那怎么拷贝呢?
  • 这时我们就引出了拷贝构造函数——拷贝构造函数是构造函数的一个重载形式。

基本性质:

  1. 只有单个形参。 该形参是对本类类型对象的引用**(一般常用const修饰)。**
  2. 无返回值
  3. 类名与函数名相同
  4. 用已存在的类的类型对象创建新对象时由编译器自动调用
  • 说明:拷贝构造完成的是已初始化的对象另一个正在进行初始化对象赋值操作

形参必须是引用

  • 为什么是形参一定是引用而不是拷贝呢?

我们按照语法分析:用已存在的类类型对象创建新对象时由编译器自动调用(拷贝构造的调用条件)。

代码:

#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
    }
private:
	int _day;
	int _month;
	int _year;
};
int main()
{

	Date d;
	Date B(d);
	return 0;
}

如果将拷贝构造的&去掉,编译一下。
在这里插入图片描述

  • 可见语法上会进行强制检查,进行报错。
  • 但如果我们假设不报错进行分析呢?

在这里插入图片描述

  • 可见我们如果这样分析只会——南辕北辙越行越远(只有递没有归)。

  • 还看上面的代码:补充一段
void Fun(Date A)
{

}
  • 当我们进行调用这个函数时,也满足拷贝的条件
  • 因此:在传参的时候也会调用拷贝构造。

默认拷贝构造

浅拷贝

#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date d;
	Date B(d);
	return 0;
}

进行调试:
在这里插入图片描述

  • 可见完成了任务,这是否意味着我们就不用写拷贝构造了呢?
  • 并不是,拷贝还分为深拷贝和浅拷贝
  • 默认生成的只完成了浅拷贝,那深拷贝是啥呢?

深拷贝

再给出一份代码:

#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	void PushBack(int data)
	{
		_arr[_top++] = data;

	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

进行调试:
在这里插入图片描述
其只是简单的把地址copy过去了,但仍没有完成任务。
此代码实现的图解:
在这里插入图片描述

应该实现的图解:
在这里插入图片描述
那这样实现的我们就叫做深拷贝。

如何实现:

  • 借助malloc和memcpy
    代码:
#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	Stack(const Stack& A)
	{
		int* tmp = (int*)malloc(sizeof(int) * A._capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			memcpy(tmp, A._arr, sizeof(int) * A._capacity);
			_arr = tmp;
			_top = A._top;
			_capacity = A._capacity;
		}
	}
	void PushBack(int data)
	{
		_arr[_top++] = data;

	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

调试一下:
在这里插入图片描述

  • 由此我们的深拷贝就简单的完成了。
    我们回过头进行看写一个函数。
#include<iostream>
using namespace::std;
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		int* tmp = (int*)malloc(sizeof(int) * capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			_arr = tmp;
			_top = 0;
			_capacity = capacity;
		}
	}
	Stack(const Stack& A)
	{
		int* tmp = (int*)malloc(sizeof(int) * A._capacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);

		}
		else
		{
			memcpy(tmp, A._arr, sizeof(int) * A._capacity);
			_arr = tmp;
			_top = A._top;
			_capacity = A._capacity;
		}
	}
	void PushBack(int data)
	{
		_arr[_top++] = data;

	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};
Stack Func()
{
	static Stack A;
	return A;
}
int main()
{
	Stack A = Func();
	return 0;
}
  • 这里我们写了一个函数,这里返回的是函数里面A对象的一份深拷贝。
  • 假设:_arr开辟的很大,那么我们的开销就会很大,所以一般都是引用返回,并且函数的参数一般都是引用,其目的就是为了避免空间的开销。

自定义类型

对象里面是自定义类型的拷贝构造,如何拷贝呢?

class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
    }
private:
	int _day;
	int _month;
	int _year;
};
class Dates
{
	Date A;
	Date B;
};
int main()
{

	Dates A;

	Dates B(A);

	return 0;
}

调试:
在这里插入图片描述

  • 因此:默认生成的拷贝构造会调用其类型的拷贝构造进行拷贝。

  • 总结:
    1. 深拷贝需要我们写拷贝构造。
    1. 浅拷贝或者成员全是自定义类型时,我们可以不写拷贝构造。
    1. 拷贝构造是为了完成已初始化的对象对另一个未初始化的对象的拷贝。
    1. 拷贝构造的形参必须是引用,且拷贝构造是构造函数的重载。

四.赋值运算符重载函数

  • 讲赋值运算符之前我们得清楚什么是运算符重载函数。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

基本特征

  • 目的:增强代码可读性
  • 基本特征
    1. 具备返回类型与参数以及返回值
    1. 函数名为operate+操作符(必须是已经存在的!)

注意:

    1. *. ——没用过
    1. :: ——作用域限定符
    1. sizeof ——求类型大小
    1. ?: ——三目操作符
    1. .
  • 以上5个运算符不能重载。

全局的运算符重载函数

  • 因为要访问成员,所以为了写成全局的,我们不得不将成员变量公开。
    代码:
#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
//private:
	int _day;
	int _month;
	int _year;
};
bool operator== (const Date& A, const Date& B)
{
	return A._day == B._day
		&& A._month == B._month
		&& A._year == B._year;
}
int main()
{
	Date A;
	Date B;
	cout <<( A == B )<< endl;
	//这里括号不可以省去,因为流插入的优先级比较高所以我们需要加括号让表达式先计算。
	return 0;
}

运行结果:
在这里插入图片描述

  • 返回的bool值为1,所以为真,因此相等。

  • 全部的显然不是很好有没有办法写到局部呢?

  • 答案是肯定的,写到类里面不就好了么?


局部的运算符重载函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	bool operator== (const Date& B)
	{
	return _day == B._day
		&& _month == B._month
		&& _year == B._year;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A;
	Date B;
	cout <<( A == B )<< endl;
	return 0;
}

我们单独把这里的重载函数列出来:

	bool operator== (const Date& B)
	//其实就是bool operator== (Date *const this,const Date& B)
	{
	return _day == B._day
		&& _month == B._month
		&& _year == B._year;
	}
  • 比如这里需要A==B,那么就相当于 A.operator==(B)
  • 表面上缺少了一个参数其实是this指针。
  • 因此:比较n个操作数我们只需要传进去n-1个参数即可。

前置++与后置++的实现

  • 到这我们对一些运算符有了一定的了解,但是当我们实现后置++与前置++时该如何实现呢?
  • 由于++是一元操作符,只对一个对象进行操作,因此实现的函数的参数没有或者说只有一个隐含的this 指针。 这就是我们区分的条件,一个加上一个参数,是后置++,一个不加是前置++。
    实现代码:
#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator++(int)//加一个参数以示区分,别的到没什么用处,
	{
		_day++;
		return *this;
	}
	Date operator++()
	{
		Date tmp(*this);
		_day++;
		return tmp;
	}
private:
	int _day;
	int _month;
	int _year;
};

赋值运算符重载函数

  • 说明:赋值运算符重载函数是默认成员函数,不写编译器会自动生成一个,但是其余的运算符重载函数可不是默认成员函数。
#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Date A;
	Date B(2023,5,3);
	A = B;
	return 0;
}
  • *this作为返回值
  • 1.这个对象出了作用域还在
  • 2.返回类型建议是引用

赋值操作符返回类型是左值本身,因此我们返回引用比较合适可以减少空间上的开销。
当赋值为本身时,可以不进行此操作,结果也是一样的。

  • 赋值运算符重载与拷贝构造函数的区别:
    1. 赋值运算符重载——两个已经初始化的对象进行的赋值操作
    1. 拷贝构造函数c——一个初始化的对象对另一个正在初始化的对象性的操作。

看下面的代码:

#include<iostream>
using namespace::std;
class Date
{
public:
	//构造函数
	Date(int year = 1949, int month = 10, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造
	Date(const Date& A)
	{
		_year = A._year;
		_month = A._month;
		_day = A._day;
     }
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A;
	Date B = A;//这个代码不是赋值重载而是拷贝构造
	//这是已初始化的对象对另一个正在初始化的对象的赋值——拷贝构造。
	return 0;
}
  • 这里的Date B = A; 见到要额外注意——这是调用的是拷贝构造

如何证明:

  • 看汇编代码
    在这里插入图片描述
    这里调用的是同一个函数——由下面是拷贝构造,可推理出上面的代码也是拷贝构造

日期类(练习)

  • 感兴趣可以把时期类实现一下:
    这里先把日期类的代码给出
#include<stdbool.h>
#include<assert.h>
#include<iostream>
using namespace::std;
class Date
{

public:
	// 获取某年某月的天数
	bool is_leap_year(int year)
	{
		if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
		{
			return true;
		}
		return false;
	}
	int GetMonthDay(int year, int month)
	{
		int day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (is_leap_year(year) && month == 2)
		{
			return 29;
		}
		else
		{
			return day[month];
		}
	}
	//检查一下输入或者赋值的时候是否日期非法
	bool is_legal_Date()
	{
		if (_month >= 1 && _month <= 12 && _day >= 1 && _day <= GetMonthDay(_year,_month))
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	// 全缺省的构造函数
	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)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
	// 析构函数
	~Date()
	{
		;
	}
	// 日期+=天数
	Date& operator+=(int day)
	{
		if (day < 0)
		{
			return *this -= -day;
		}
		_day += day;

		while (_day >= GetMonthDay(_year, _month))
		{
			int tmp = GetMonthDay(_year, _month);
			_day -= tmp;
			_month += 1;
			if (_month == 13)
			{
				_year += 1;
				_month = 1;
			}
		}
		return *this;
	}
	// 日期+天数
	Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}
	// 日期-天数
	Date operator-(int day)
	{
		Date tmp(*this);
		tmp._day -= day;
		while (tmp._day <= 0)//等于0不能忘了
		{
			int tmp1 = GetMonthDay(tmp._year, tmp._month - 1);
			if (tmp._month == 1)
			{
				tmp1 = GetMonthDay(tmp._year, 12);
			}
			tmp._day += tmp1;
			tmp._month--;
			if (tmp._month == 0)
			{
				tmp._year--;
				assert(tmp._year);
				tmp._month = 12;
			}
		}
		return tmp;
	}
	// 日期-=天数
	Date& operator-=(int day)
	{
		if (day < 0)
		{
			return *this += -day;
		}
		*this = *this - day;
		return *this;
	}
	// 前置++
	Date& operator++()
	{
		*this += 1;
		return *this;
	}
	// 后置++
	Date operator++(int)
	{
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	// 后置--
	Date operator--(int)
	{
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}
	// 前置--
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}
	// >运算符重载
	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;
		}
	}
	// ==运算符重载
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
	// >=运算符重载
	bool operator >= (const Date& d)
	{
		return *this == d || *this > d;
	}
	// <运算符重载
	bool operator < (const Date& d)
	{
		return !(*this >= d);
	}
	// <=运算符重载
	bool operator <= (const Date& d)
	{
		return !(*this > d);
	}
	// !=运算符重载
	bool operator != (const Date& d)
	{
		return !(*this == d);
	}
	// 日期-日期 返回天数
	int operator-(const Date& d)
	{
		int day = 0;
		if (*this >= d)
		{
			Date tmp(d);
			while (*this != tmp)
			{
				++tmp;
				day++;
			}
			return day;
		}
		else
		{
			Date tmp(*this);
			while (tmp != d)
			{
				++tmp;
				day++;
			}
			return -day;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};

五.取地址重载和const取地址重载

const 成员

不妨看这样一段代码:

#include<iostream>
using namespace::std;
class Date
{
public:
	void Print()
	{
		cout << _year << _month << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	const Date A;
	A.Print();
	return 0;
}

报错原因:
在这里插入图片描述

  • 分析:this 指针是一个类型为 int *const this,还记得const 的修饰规则吗?
  • 举例:
		定义一个变量int * const this
		因为:const具有就近原则
		所以:放在thisthis本身不能修改
		补充:放在*前说明*this不能被修改

再看我们传进去的参数是什么类型的——const Date(说明Date不能修改)

  • 转化为指针就是*this 不能修改,所以应该是——const int* const this。
  • 为什么要这样写呢?——权限不能放大,只能缩小或者平移。
  • 那由于——this 指针不能显示表示,这个const该加哪呢?
  • 祖师爷这样放的:
	void Print() const
	{
		cout << _year << _month << _day << endl;
	}
  • 为什么这样放呢?家人们谁懂啊?
    *猜测: 可能是祖师爷想不到地方放了。。。。

适用场景:

    1. 传参对象有const修饰。
    1. 只要函数内部不对成员变量进行修改。

取地址重载

class Date
{
public :
	Date* operator&()
	{
		return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};

const取地址重载

class Date
{
public :

	const Date* operator&()const
	{
	return this ;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};
  • 说明:
  • 一般我们直接用默认编译器生成的就够了
  • 使用场景很少——作为了解即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/503690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入学习 Kotlin 枚举的进阶用法:简洁又高效~

翻译自&#xff1a;https://towardsdev.com/mastering-enums-in-kotlin-a-guide-to-simplify-your-code-130b5934cb16 Kotlin 作为现代的、强大的编程语言&#xff0c;可以给开发者提供诸多特性和工具&#xff0c;得以帮助我们编写更加高效、更具可读性的代码。 其中一个重要的…

开源工具系列7:Kube-bench

导语 Kube-Bench 是一个基于Go开发的应用程序&#xff0c;属于 Kubernete 的安全检测的工具。它可以帮助研究人员对部署的 Kubernete 进行安全检测。 Kube-Bench 是什么 从本质上来说&#xff0c;Kube-Bench 是一个基于Go开发的应用程序&#xff0c;属于 Kubernete 的安全检…

免费版的mp3格式转换器有哪些?这三款软件帮你实现!

在娱乐文化越来越丰富的今天&#xff0c;人们越来越追求音乐、视频等娱乐方式&#xff0c;其中音乐作为一种能够治愈心灵的艺术形式备受欢迎。但要欣赏一首美妙的音乐&#xff0c;就需要我们自己去制作、编辑并转换其格式&#xff0c;以适应各种软件如MP3、MP4等格式。 方法一…

在 Python 中将 Tqdm 与 Asyncio 结合使用

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 困扰 在 Python 中使用并发编程来提高效率对于数据科学家来说并不罕见。在后台观察各种子进程或并发线程以保持我的计算或 IO 绑定任务的顺序总是令人满意的。 但是还有一点困扰我的是&#xff0c;当我在后台并发处理成百…

数据结构篇五:队列

文章目录 前言1.队列1.1 队列的概念及结构1.2 队列的实现 2. 各功能的解析及实现2.1 队列的创建2.2 初始化队列2.3 队尾入队列2.4 队头出队列2.5 获取队头元素2.6 获取队尾元素2.7 队列中有效元素个数2.8 检查队列是否为空2.9 销毁队列 3.代码实现3.1 Queue.h3.2 Queue.c3.3 te…

JavaWeb ( 七 ) JSTL Tag标签

2.5.JSTL标签与EL表达式 2.5.1.EL表达式 EL表达式 : Expression Language 目的&#xff1a;为了使JSP写起来更加简单 格式&#xff1a;${expression} EL 提供“.“和“[ ]“两种运算符来存取数据。${user.name}, ${user[“name”] }支持算术操作符, 关系操作符, 逻辑操作符…

Python:Python进阶:内存管理机制

Python内存管理机制 1. 堆2. 栈3. 引用4. Python中可变对象和不可变对象有个问题&#xff1a;你可以好好思考下总结 Python内存管理程序是用 C/C写的&#xff0c;这里我们以 CPython解释器为例说明。 在Python 中 所有数据类型 包括&#xff1a;int dict str都是一个对象&#…

层次分析法及找工作问题实战

学习知识要实时简单回顾&#xff0c;我把学习的层次分析法简单梳理一下&#xff0c;方便入门与复习。 AHP 层次分析法&#xff08;Analytic Hierarchy Process&#xff0c;简称 AHP&#xff09;是对一些较为复杂、较为模糊的问题作出决策的简易方法&#xff0c;它特别适用于那…

C++类和对象上

专栏&#xff1a;C/C 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本章为大家带来C类和对象相关内容。 类和对象 前言面向过程和面向对象类的引入类的定义对于类中成员的命名建议 类的访问限定符及封装访问限定符封装 类的作用域类的实例化如何计算类对象的大小this指针t…

Web自动化测试——XAPTH高级定位

XAPTH高级定位 一、xpath 基本概念二、xpath 使用场景三、xpath 相对定位的优点四、xpath 定位的调试方法五、xpath 基础语法&#xff08;包含关系&#xff09;六、xpath 顺序关系&#xff08;索引&#xff09;七、xpath 高级用法1、[last()]: 选取最后一个2、[属性名属性值 an…

ESP32设备驱动-PCF8575IO扩展器驱动

PCF8575IO扩展器驱动 文章目录 PCF8575IO扩展器驱动1、PCF8575介绍2、硬件准备3、软件准备4、驱动实现1、PCF8575介绍 PCF8575用于两线双向总线 (I2C) 的 16 位 I/O 扩展器专为 2.5-V 至 5.5-V VCC 操作而设计。 PCF8575 器件通过 I2C 接口 [串行时钟 (SCL)、串行数据 (SDA)]…

flask教程8:模板

文章目录 一、模板与自定义过滤器1 模板2 过滤器转义过滤器讲解 3自定义过滤器 二、表单1表单2表单扩展 三、创建表单模型类与模板使用3.1 表单模型类 四 、使用表单接受并检验参数五、模板宏的使用六 、宏定义在外部的使用七 &#xff1a;模板继承与包含继承包含include 八 、…

PVE 安装 windows10

pve 安装教程大家可以参考视频&#xff1a;pve 安装 pve 安装 Windows10 视频教程&#xff1a;pve 安装Windows10 在安装好 pve 后我们就可以进行虚拟机的安装了。当然我们可以自行决定是否有必要进行 win10 的安装。 准备工作 1. 下载 win10 镜像文件&#xff1a;https://…

数据结构与算法基础(王卓)(35):交换排序之快排【第二阶段:标准答案、初步发现问题】

目录 第二阶段&#xff1a;一分为二 整个快排算法的程序运行大框架&#xff1a; 做出的改动&#xff08;和原来程序的区别&#xff09;&#xff1a; Project 1: PPT标准答案&#xff1a; Project 1小问题&#xff1a; Project 1还存在着一个巨大的问题&#xff1a; 具体问…

嵌入式软考备考_8 软件测试

软件测试 测试&#xff1a;在规定的条件下操作程序&#xff0c;以发现错误&#xff0c;对软件质量进行评估。 对象&#xff1a;程序&#xff0c;数据&#xff0c;文档。 目的&#xff1a;发现错误&#xff0c;看是否满足用户需求&#xff0c;发现错误产生的原因&#xff08;…

汇编四、51单片机汇编指令2

1、机器码 (1)MOV A,#0x60对应机器码为7460 (2)7460对应二进制 0111 0100 0110 0000 0x74对应指令&#xff0c;0x60对应立即数。 (3)immediate data翻译为立即数。 (4)可人为查表把汇编转为机器码&#xff0c;也可通过编译器把汇编转为机器码。 2、汇编常见缩写 (1)Rn: n可…

leetcode-040-组合总和2

题目及测试 package pid040; /* 40. 组合总和 II 给定一个候选人编号的集合 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。注意&#xff1a;解集不能包含重复的组合…

Vue中使用EasyPlayer播放H265视频流

需求说明 需要在Vue2的项目中使用EasyPlayer进行H265视频流的播放。使用官方的最新版本加载H265会有问题。一直处于加载中… 实现步骤 引入easyplayer,这里最开始引入了最新版会有问题&#xff0c;因此引入的是3.3.12版本&#xff0c;可参照官方文档进行配置。 EasyPlayer示…

HBase整合Phoenix

HBase整合Phoenix 创建软件目录 mkdir -p /opt/soft cd /opt/soft下载软件 wget https://dlcdn.apache.org/phoenix/phoenix-5.1.3/phoenix-hbase-2.5-5.1.3-bin.tar.gz解压 hbase tar -zxvf phoenix-hbase-2.5-5.1.3-bin.tar.gz修改 hbase 目录名称 mv phoenix-hbase-2.5…

(初)进程概念

目录 认识冯诺依曼系统 操作系统(Operator System) 设计OS的目的&#xff1a; 定位&#xff1a; 如何理解管理&#xff1a; 总结&#xff1a; 系统调用和库函数概念&#xff1a; 进程 基本概念 &#xff1a; 描述进程PCB task_struct - PCB的一种 task_struct内容分…