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

文章目录

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

前言

六大默认成员函数

【C++进阶之路】类和对象(中)_第1张图片

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

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

一.构造函数

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

性质

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

默认构造函数

举例代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include
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;
}

调试结果:
【C++进阶之路】类和对象(中)_第2张图片

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

  • 既然函数参数可以重载我们是否可以这样写呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include
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
using namespace::std;
class Date
{
public:
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Date A;
	//Date A();
	return 0;
}

图解:
【C++进阶之路】类和对象(中)_第3张图片

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

  • 那我们如果想要不写构造函数的情况下,如何初始化对象的变量呢?
  • C++11引出我们可在类的变量处使用缺省值。
#include
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;
}

调试结果:
【C++进阶之路】类和对象(中)_第4张图片

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

二.析构函数

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

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

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

性质

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

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

#include
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函数执行结束时会完成堆空间的释放。

调试看结果:
【C++进阶之路】类和对象(中)_第5张图片
此时mainj函数差一步结束:
【C++进阶之路】类和对象(中)_第6张图片
继续调试:
【C++进阶之路】类和对象(中)_第7张图片
继续执行程序结束,对象才进行释放。

默认析构函数

  • 默认生成的析构函数会不会把动态申请的资源进行释放呢?
#include
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;
}

调试:
【C++进阶之路】类和对象(中)_第8张图片

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

  • 自定义类型呢?

我们定义一个对列:

#include
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修饰的局部对象
话不多说,开始调试:
【C++进阶之路】类和对象(中)_第9张图片

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

【C++进阶之路】类和对象(中)_第10张图片

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

继续调试:
【C++进阶之路】类和对象(中)_第11张图片

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

接着调试直到程序结束:

【C++进阶之路】类和对象(中)_第12张图片

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

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

三.拷贝构造函数

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

基本性质:

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

形参必须是引用

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

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

代码:

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

如果将拷贝构造的&去掉,编译一下。
【C++进阶之路】类和对象(中)_第13张图片

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

【C++进阶之路】类和对象(中)_第14张图片

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

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

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

默认拷贝构造

浅拷贝

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

进行调试:
【C++进阶之路】类和对象(中)_第15张图片

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

深拷贝

再给出一份代码:

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

进行调试:
【C++进阶之路】类和对象(中)_第16张图片
其只是简单的把地址copy过去了,但仍没有完成任务。
此代码实现的图解:
【C++进阶之路】类和对象(中)_第17张图片

应该实现的图解:
【C++进阶之路】类和对象(中)_第18张图片
那这样实现的我们就叫做深拷贝。

如何实现:

  • 借助malloc和memcpy
    代码:
#include
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;
}

调试一下:
【C++进阶之路】类和对象(中)_第19张图片

  • 由此我们的深拷贝就简单的完成了。
    我们回过头进行看写一个函数。
#include
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;
}

调试:
【C++进阶之路】类和对象(中)_第20张图片

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

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

四.赋值运算符重载函数

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

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

基本特征

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

注意:

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

全局的运算符重载函数

  • 因为要访问成员,所以为了写成全局的,我们不得不将成员变量公开。
    代码:
#include
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;
}

运行结果:
【C++进阶之路】类和对象(中)_第21张图片

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

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

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


局部的运算符重载函数

#define _CRT_SECURE_NO_WARNINGS 1
#include
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;
	//A==B 其实本质上就是A.operator==(B)
	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
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
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
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; 见到要额外注意——这是调用的是拷贝构造

如何证明:

  • 看汇编代码
    【C++进阶之路】类和对象(中)_第22张图片
    这里调用的是同一个函数——由下面是拷贝构造,可推理出上面的代码也是拷贝构造

日期类(练习)

  • 感兴趣可以把时期类实现一下:
    这里先把日期类的代码给出
#include
#include
#include
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
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;
}

报错原因:
【C++进阶之路】类和对象(中)_第23张图片

  • 分析: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 ; // 日
};
  • 说明:
  • 一般我们直接用默认编译器生成的就够了
  • 使用场景很少——作为了解即可

你可能感兴趣的:(C++进阶之路,c++)