一、类和对象初阶

1.类的创建以及成员
  • 类

类的创建和struct结构体的创建书写格式一样 class Date {};
在这里你可以理解未 class是一种新的定义类型, Date是类名 ,Date d1是创建类的对象

  • 类的创建

创建类使用的关键字为class
类里面包含成员变量和成员函数

//创建一个日期类
class Date 
{
public:
    void Init(int year,int month,int day)
    {
    _year = year; _month = month; _day = day;
    }
private:
    int _year; int _month; int _day;
};
2.访问限定符

访问限定符分为三种:public对外开放区,private私人区域,protected保护区域(public在类里面和外面都可以直接访问,private和protected在类外面无法访问,在类里面可以访问)
class中默认访问限定符为private 在使用时往往需要对成员函数进行 public表示 对成员变量进行private表示

例如下面代码并未对Init函数进行public访问限定符表示,在main函数中st.Init()会报错无法访问

class Date 
{
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	Date d;
	d.Init(2024, 3, 31);
}
3.类域

创建类时,所有的局部变量和局部函数都存在于同一个类域中,若是在类域外面定义成员时,需要用::操作符知名成员属于哪个类域

#include<iostream>
using namespace std;

class Stack
{
public:
	//成员函数
	void Init(int n = 4);
private:
	//成员变量
	int* array;
	size_t capacity;
	size_t top;
};

void Stack::Init(int n)  //在类域外定义函数时 对Init进行表示 属于Stack类
{
	array = (int*)malloc(sizeof(int) * n);
	if (nullptr == array)
	{
		perror("malloc申请空间失败");
		return;

	}
	capacity = n;
	top = 0;

}

int main()
{
	Stack st;
	st.Init();
	return 0;
}
4.类的实例化

类创建完之后成员变量仅仅完成了声明那一步,就相当于一张图纸,而想要盖出真正的房子。需要付诸实践。即实例化;

#include<iostream>

using namespace std;

class Date

{

public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	// 这⾥只是声明,没有开空间 
	int _year;
	int _month;
	int _day;
};

int main()
{
	// Date类实例化出对象d1和d2 
	Date d1;
	Date d2;
	d1.Init(2024, 3, 31);
	d1.Print();
	d2.Init(2024, 7, 5);
	d2.Print();
	return 0;
}
5.类的大小及对齐

多个不同类在创建完之后,他们公用一个成员函数,但各有各自的成员变量

  • 为了节省空间,类的大小在计算时仅仅计算成员变量的大小,而成员函数不参与计算

内存对齐
• 第⼀个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
• 注意:对⻬数=编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值。
• VS中默认的对⻬数为8
• 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩
就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
若类的大小为0 则默认改为1证明这个类声明过

#include<iostream>
using namespace std;

class A
{
public:
	void Print(){}
private:
	char _ch;
	int _i;
};


class B
{
public:
	void Print(){}
};


class C{};

int main()
{
	A a;
	B b;
	C c;
	cout << sizeof(a) << endl;  //大小为8
	cout << sizeof(b) << endl;  //大小为1
	cout << sizeof(c) << endl;  //大小为1
}
6.隐藏的this指针

为了防止访问成员函数时,分不清究竟是谁访问的成员函数。编译器编译后类的成员函数默认都会在形参第一个为止增加一个指针类型,叫做this 指针。而成员函数中 通过this->_year = year; this->month = month; this->day = day;

void Init(Date* const this,int year,int month,int day)
#include<iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	//这个代码不会报错的原因: p->Print()调用到 Print(Date *const this)不会对 p(空指针)指向的内存空间进行访问。 而是直接进行cout << "A::Print()" << endl;打印 。因此不会设计空指针解引用的问题
	return 0;
}
#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}
	//这个代码会报错的原因: p->Print()调用到 Print(Date *const this)会对 p(空指针)指向的内存空间进行访问。 原因是,在Print()函数中存在_a,当你对_a进行访问时,_a存在于p指向的内存空间。因此会涉及到空指针的解引用,所以会报错。

上述的错误属于运行崩溃,和编译毫无关系,编译只设计到语法,例如if else while 等等

this指针位于栈区域

二、类的6个默认成员

1.初始化和清理——构造函数、析构函数

负责初始化的函数——构造函数
  • 构造函数的特点

构造函数是特殊的成员函数,构造函数虽然叫构造,但是其主要任务是实例对象初始化。构造函数可以完美替代Init函数的功能构造函数最重要的功能就是完成初始化

  • 构造函数的书写特点

1.函数名与类名相同

  • 无返回值 (啥返回值也不需要给,更不需要写void)
  • 可以重载
==编译器自动生成的构造函数==

==无参构造函数==
class Date {
public:
    Date() {
        _year = 1;
        _month = 1;
        _day = 1;
    }
private:
    int _year;
    int _month;
    int _day;
};

==全缺省的构造函数==
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;
};

  • 无参构造函数
#include<iostream>
using namespace std;

class Date
{
public:
	Date()
	{
		_year = 1;_month = 1;_day = 1;
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day;
	}
private:
	int _year; int _month; int _day;
};


int main()
{
	Date d1;  //注意这里和有参构造函数不一样
	d1.print();
	return 0;
}
  • 全缺省的构造函数
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year,int month,int day)
	//这里省略的是 Date (int year = 1,int month = 1,int day = 1)
	{
		_year = year;_month = month;_day = day;
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day;
	}
private:
	int _year; int _month; int _day;
};


int main()
{
	Date d1(2025,9,10);
	d1.print();
	return 0;
}

4.对象实例化时系统会自动调用对应的构造函数目前除了static修饰的对象比较特殊外,其他对象第一步都是需要先初始化的,因此可以认为,无论哪个对象开始都要调用构造函数
5.如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,若有显示的构造函数将不在生成因此如果有显示的构造函数那么默认构造函数将不在生成
6.构造函数分为 无参构造函数、全缺省构造函数、编译器自动生成的构造函数。这三个构造函数有且只能存在一个,不能同时存在。
7.构造函数对内置成员变量的初始化没有要求。默认对内置成员没有进行初始化。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化(下面可匹配调用stack默认的构造函数完成初始化)。如果这个成员变量,没有默认构造函数,那么就会报错

#include<iostream>
using namespace std;


class Stack
{
public:
	Stack(int n = 4)
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}
private:
	int* _a;
	size_t _capacity;
	size_t _top;
};

class MyQueue
{
public:
	//编译器默认生成MyQueue的函数构造 调用了Stack的构造  完成了两个成员的初始化
private:
	Stack pushst;
	Stack popst;
};




int main()
{
	MyQueue mq;
	return;
}
负责清理的函数——析构函数
  • 析构函数的特点

析构函数主要任务不是完成对对象本身的销毁,二是对资源的清理释放。C++规定再对象销毁之后会自动调用析构函数完成对资源的释放。其功能类比Stack实现的Destroy功能。而向Date是不需要Destroy的因此 Date不需要析构函数

  • 析构函数的书写特点
    1.析构函数是在类名上加上字符~
    2.虚构函数无参数无返回值也不需要加void
#include<iostream>
using namespace std;


class Stack
{
public:
	Stack(int n = 4)  //构造函数 代替了Init函数——有参构造函数
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	size_t _capacity;
	size_t _top;
};


//自定义类
class MyQueue
{
public:  
private:
	Stack pushst;  
	Stack popst;  //这里调用两次析构
};

int main()
{
	Stack st;
	MyQueue mq;
	return 0;
}

3.一个类只能有一个析构函数。若未显示定义系统将自动生成析构函数。
4.对象生命周期结束,系统自动调用析构函数
5.编译器生成的析构函数对内置类型的成员不做处理
6.自定义成员无论什么情况都会自动调用析构函数
7.如果类中没有申请资源,析构函数可以不写。或者遇到Date这一类,本身不需要析构,那么析构函数也可以不写。但是一旦出现有资源申请的情况,就一定要自己写析构,否则容易造成资源泄露。
8.C++规定后定义的先析构。

  • 建议——若有资源申请 那么一定要自己写析构。因为编译器无法确定你书写类型是结构体、数组、还是字典等等……因此出Date或者没有资源申请之外 我们需要自己书写析构

2.拷贝构造函数

拷贝构造函数是一类特殊的构造函数,只不过它的第一个参数是对自身类 类型的引用。并且任意额外的参数都有默认值不需要书写。

  • 拷贝构造桉树的特点

1.拷贝构造函数 是构造函数的重载
2.拷贝构造函数第一个参数必须是类类型对象的引用,倘若使用传值赋值就会引发无穷递归调用。
3.C++规定自定义类型对象进行拷贝必须调用拷贝构造,所以自定义类型 传参和传值返回都会调用拷贝构造完成

在这里插入图片描述

d1赋值给d时候 他的本质就是传参,而传参就需要调用拷贝构造。所以第一次d1给d d1调用拷贝构造 然后成为死循环,所以拷贝构造一定要使用引用 切不能使用传值调用

赋值和return本身都需要调用拷贝

以下实例进行解释
在这里插入图片描述

在Date内部会出现两次拷贝
Date tmp (*this)拷贝一次
return tmp 拷贝一次
在这里return tmp 结束后tmp 自动销毁 但是ret接收的返回值是tmp拷贝之后的值 因此这就是为什么tmp销毁了但是ret还能接受到值的原因

4.若未显示定义拷贝构造,编译器就会自动生成拷贝构造函数,自动生成的拷贝构造函数会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。

class Date
{
public:
	Date(int year = 1, 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;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day;
	}

	Date(Date* d)  //这里就不在是拷贝构造 而是一个普通的函数
	{
		_year = d->_year; _month = d->_month; _day = d->_day;
	}

private:
	int _year; int _month; int _day;
};

void Func1(Date d)
{
	cout << &d << endl;
	d.print();
}

Date& Func2()
{
	Date tmp(2024, 7, 5);
	tmp.print();
	return tmp;
}

int main()
{
	Date d1(2024, 7, 5);  //正确的拷贝构造
	Func1(d1);
	cout << &d1 << endl;

	Date d2(&d1);  //调用普通函数  不会调用构造函数
	d1.print();
	d2.print();

	Date d3(d1);   //正确的拷贝构造
	d2.print();
 
	Date d4 = d1;   // 拷贝构造另一种书写方式
	d2.print();

	Date ret = Func2();
	ret.print();

	return 0;

}

对应关系

	Date d2(&d1);  //调用普通函数  不会调用构造函数
	d1.print();
	d2.print();


	Date(Date* d)  //这里就不在是拷贝构造 而是一个普通的函数
	{
		_year = d->_year; _month = d->_month; _day = d->_day;
	}
	Date d1(2024, 7, 5);  //正确的拷贝构造
	Func1(d1);
	cout << &d1 << endl;

void Func1(Date d)
{
	cout << &d << endl;
	d.print();
}

	Date(const Date& d)
	{
		_year = d._year; _month = d._month; _day = d._day;
	}
	Date d3(d1);   //正确的拷贝构造
	d2.print();
 
	Date d4 = d1;   // 拷贝构造另一种书写方式
	d2.print();

	Date(const Date& d)
	{
		_year = d._year; _month = d._month; _day = d._day;
	}

5.像Date这样的类 编译器自动生成的拷贝构造就可以实现我们的需求。 像MyQueue这样的类,编译器会自动调用Stack拷贝构造也不需要我们写显示构造。 像Stack这样的类 因为_a的存在,浅构造无法满足我们的需求 我们需要手写显示构造。(即实现对地址以及地址指向的空间进行拷贝)

因此需要修改成以下代码

class Stack
{
public:
	Stack(int n = 4) //全缺省的构造函数
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_a, st._a, sizeof(int) * st._top);

		_top = st._top;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
}
private:
	int* _a; int _capacity; int _top;
};


int main()
{
	Stack st1;
	Stack st2 = st1;
	return 0;
}

拷贝构造函数之间易混点

class Stack
{
public:
	Stack(int n = 4) //全缺省的构造函数
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_top = 0;
	}

	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_a, st._a, sizeof(int) * st._top);

		_top = st._top;
		_capacity = st._capacity;
	}
	void Push(int x)
	{
		if (_top == _capacity)
		{
			int newcapacity = _capacity * 2;
			int* tmp = (int*)realloc(_a, newcapacity * sizeof(int));
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_top++] = x;
	}


	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
}
private:
	int* _a; int _capacity; int _top;
};

class MyQueue
{
public:
private:
	Stack pushst;
	Stack popst;
};

int main()
{
	Stack st1;
	Stack st2 = st1;

	st1.Push(1);
	st1.Push(2);
	//这里的拷贝构造会出现问题,因为他们的_a都是同一块地址,指向的也都是同一块空间,所以析构时就会崩溃

	MyQueue mq1;
	MyQueue mq2 = mq1;
	//这里拷贝构造不会出错,因为MyQueue自动生成拷贝构造,会自动调用Stack拷贝构造完成
	return 0;
}

6.传值返回会产⽣⼀个临时对象调⽤拷⻉构造。传值引⽤返回,返回的是返回对象的别名(引⽤),没
有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤
引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少
拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

	Date ret = Func2();
	ret.print();
	Date& Func2()
{
	Date tmp(2024, 7, 5);
	tmp.print();
	return tmp;
}
	Date(int year = 1, int month = 1, int day = 1)//全缺省的构造函数
	{
		_year = year; _month = month; _day = day;
	}

引用函数可以这么理解
这一段代码是对函数的引用,引用在Date后面,所以所在函数内部Date后面哪个tmp就是被引用的,即别名。
tmp这个类是在函数内部创建的,出了函数内部就会自动销毁。因此ret接受的就是空指针(上述说的野指针)。

3.重载

运算符重载

类进行运算或者比较时,无法识别符号,需要运算符重载函数来进行识别比较。

  • 运算符重载的特点

1.当运算符重载函数不是成员函数时,该传几个参数就传几个参数。当运算符重载函数是成员函数时,参数里面会有一个隐藏的this,因此参数会少一个

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year; _month = month; _day = day;
	}
	void Printf()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
//private:
	int _year; int _month; int _day;
};


// 重载为全局的⾯临对象访问私有成员变量的问题 

// 有⼏种⽅法可以解决: 

// 1、成员放公有  

// 2、Date提供getxxx函数 

// 3、友元函数 

// 4、重载为成员函数 


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(2024, 7, 5);
	Date d2(2024, 7, 6);
	operator == (d1, d2);//运算符重载函数可以显示调用
	d1 == d2;
	
	return 0;
}

重载为成员函数的代码

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)//全缺省的构造函数
	{
		_year = year; _month = month; _day = day;
	}
	void Printf()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
	Date& operator++()
	{
		cout << "前置++" << endl;
		return *this;
	}
	Date operator++(int)
	{
		Date tmp;
		cout << "后置++" << endl;
		return tmp;
	}
private:
	int _year; int _month; int _day;
};

2.不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@、

.* 、::、 sizeof 、?: . 注意以上5个运算符不能重载。重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义。

  • 流插入和流提取

流插入和流提取是一个需要写在全局里面的函数,不能写在类里面,主要原因是<<流插入 需要后者流向前面。而默认的成员函数一般不能满足需求(成员函数一般是指向后面)。流插入>>是由前面流向后面。我们需要自己来控制流入哪里就必须写在全局。

ostream& operator<<(ostream& out, const Date& d)
 {
        out << d._year << "年" << d._month << "⽉" << d._day << "⽇" << endl;
        return out;
 }
 istream& operator>>(istream& in, Date& d)
 {
        cout << "请依次输⼊年⽉⽇:>";
        in >> d._year >> d._month >> d._day;
赋值运算符重载

赋值运算符重载与拷贝构造功能相似,但是基本原理却有很大差异,赋值运算符重载规定两个已经存在的对象。之间的赋值。而拷贝构造是一个已经存在的用于拷贝在一个将要创建的对象。

  • 赋值运算符重载的特点

1.规定必须重载未成员函数,赋值运算符重载建议改写成const当前类类型的引用,否则穿传值传参调用拷贝构造(浪费时间)
2.没有显式实现时,编译器会自动生成一个默认运算符重载,跟拷贝构造类似,对内置成员进行浅拷贝,对自定义类型成员调用其他赋值重载函数。
3.像Date这样的类 编译器自动生成的拷贝构造就可以实现我们的需求。 像MyQueue这样的类,编译器会自动调用Stack拷贝构造也不需要我们写赋值运算符重载。 像Stack这样的类 因为_a的存在,浅构造无法满足我们的需求 我们需要手写赋值运算符重载。(即实现对地址以及地址指向的空间进行拷贝)

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)//全缺省的构造函数
	{
		_year = year; _month = month; _day = day;
	}
	Date(const Date& d) //拷贝构造函数
	{
		cout << "Date(const Date&d)" << endl;
		_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;
	}
	void Printf()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year; int _month; int _day;
};

int main()
{
	Date d1(2024, 7, 5);
	Date d2(d1);

	Date d3(2024, 7, 6);
	d1 = d3;

	return 0;
}

前置++和后置++代码

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year; _month = month; _day = day;
	}
	void Printf()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//-------------------------------------------------------------------
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month&& _day == d._day;
	}
	//-------------------------------------------------------------------

	Date& operator++()
	{
		cout << "前置++" << endl;
		return *this;
	}
	Date operator++(int)
	{
		Date tmp;
		cout << "后置++" << endl;
		return tmp;
	}
	//-------------------------------------------------------------------
private:
	int _year; int _month; int _day;
};

void Func1(Date & d1,Date &d2)
{
	d1.operator==(d2);//其实本质就是 d1 == d2
}

void Func2(Date& d1, Date& d2)
{
	++d1; //本质就是 d1.operator++()
	d1++; // 本质就是d1.operator++(0)
}


int main()
{
	Date d1(2024, 9, 13);
	Date d2(2025, 9, 13);
	Func1(d1,d2);
	Func2(d1,d2);

	return 0;
}
取地址运算符重载
cosnt 成员函数

const修饰的成员函数称之为const成员函数。

const修饰的实际是该成员函数的隐含的this指针

  • const 书写格式

const写在定义的函数的整体最后面 例如
void print() const
bool operator++(int) const

隐含的指针是Date* const this const修饰的是this 表示this这个地址不能变,但是this 指向的内容可以进行修改
const修改后变为 const Date* const this 表示this这个地址不能变,并且this指针指向的内容也不可以发生变化。
#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(){}

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year; int _month; int _day;
};




int main()
{
	Date d1(2024, 7, 5);
	d1.Print();
	const Date d2(2024, 8, 5);
	d2.Print();

	return 0;
}
const实战实例补充——10-13补充

const 修饰的成员函数 在C++类和对象使用中出现的较为频繁
const修饰的成员函数 是值得你本身不会发生变化 *this 这个函数它的成员不会发生变化

ex: class vector 私有成员有 _start _finish _end_of_strage
当你修饰 capacity()const 的时候 它的_start- _finish 这个值是不能修改的!!

push_back(T&x) T为自定义的一个类模板。
再看push_back(T&x)const 和push_back(const T&x) 他们两个的含义就有着天差地别的大区别
push_back(T&x)const 指的是我this指向的内容不可以发生改变,但是我得初衷就是要在原来的内容添加新内容,因此,这个const函数是一个巨大的陷阱。
push_back(const T&x) 它的含义则是 我x内容可以不发生改变 添加到this指向的内容

取地址运算符重载
  • 取地址运算符重载分为普通取地址运算符重载和const 取地址运算符重载,一般情况下我们不需要取显示实现 编译器自动生成的就够我们使用。除非特殊场景,例如当我们不想让别人看到我们的当前对象的地址时,就可以自己实现一份。或者胡乱返回一个地址。
class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	//注意这里的类型是 Date* 而非Date
	{
		return this;
	}
private:
	int _year; int _month; int _day;
};

——作业日期类综合代码

#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && (year % 4 == 0) && year % 100 != 0 || year % 400 == 0)
		{
			return 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year; _month = month; _day = day;
	}

	// 拷贝构造函数
    // d2(d1)
	Date(const Date& d)
	{
		this->_year = d._year; this->_month = d._month; this->_day = d._day;
	}

	// 赋值运算符重载
    // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d)
	{
		_year = d._year; _month = d._month; _day = d._day;
		return *this;
	}
	//打印
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	// 析构函数
	~Date(){}


	//--------------------------------------------------------------
	// 日期+=天数
	Date& operator+=(int day)
	{
		if (day < 0)
		{
			return *this -= -day;
		}
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month == 13)
			{
				++_year;
				_month = 1;
			}
		}
		return *this;
	}
	// 日期-=天数
	Date& operator-=(int day)
	{
		if (day < 0)
		{
			return *this += -day;
		}
		_day -= day;
		while (_day <= 0)
		{
			_month--;
			if (_month == 0)
			{
				_month = 12;
				_year--;
			}
			_day += GetMonthDay(_year, _month);
		}
		return *this;
	}

	// 日期+天数
	Date operator+(int day)
	{
		Date tmp = *this;
		tmp += day;
		return tmp;
	}
	// 日期-天数
	Date operator-(int day)
	{
		Date tmp = *this;
		tmp -= day;
		return tmp;
	}
	//--------------------------------------------------------------



	//--------------------------------------------------------------
	// 前置++
	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)
		{
			if (_month > d._month)
			{
				return true;
			}
			else if (_month == d._month)
			{
				return _day > d._day;
			}
		}
		return false;
	}
	// <运算符重载
	bool operator<(const Date& d)
	{
		if (_year < d._year)
		{
			return true;
		}
		else if (_year == d._year)
		{
			if (_month < d._month)
			{
				return true;
			}
			else if (_month == d._month)
			{
				return _day < d._day;
			}
		}
		return false;
	}

	// >=运算符重载
	bool operator >= (const Date& d)
	{
		return *this < d || *this == d;
	}
	// <=运算符重载
	bool operator <= (const Date& d)
	{
		return *this < d || *this == d;
	}
	// ==运算符重载
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && _day == d._day;
	}
	// !=运算符重载
	bool operator != (const Date& d)
	{
		return !(*this == d);
	}
	//--------------------------------------------------------------



	// 日期-日期 返回天数
	int operator-(const Date& d)
	{
		Date max = *this;
		Date min = d;
		int flag = 1;
		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}
		int n = 0;
		while (min != max)
		{
			++min;
			++n;
		}
		return n * flag;
	}

private:
	int _year;int _month;int _day;
};

void TestDate1()
{

	Date d1(2024, 4, 14);
	Date d2 = d1 + 30000;
	d1.Print();
	d2.Print();
	Date d3(2024, 4, 14);
	Date d4 = d3 - 5000;
	d3.Print();
	d4.Print();
	Date d5(2024, 4, 14);
	d5 += -5000;
	d5.Print();
}
void TestDate2()
{
	Date d1(2024, 4, 14);
	Date d2 = ++d1;
	d1.Print();
	d2.Print();
	Date d3 = d1++;
	d1.Print();
	d3.Print();
	/*d1.operator++(1);
	d1.operator++(100);
	d1.operator++(0);
	d1.Print();*/
}
void TestDate3()
{
	Date d1(2024, 4, 14);
	Date d2(2034, 4, 14);
	int n = d1 - d2;
	cout << n << endl;
	n = d2 - d1;
}



int main()
{
	TestDate1();
	TestDate2();
	TestDate3();
	return 0;
}


——小结

Date(int year = 1900, int month = 1, int day = 1)  构造函数
Date(const Date& d)   拷贝构造
Date& operator=(const Date& d)  赋值运算符重载
Date operator+(int day)  运算符重载
bool operator > (const Date& d) 运算符重载
int operator-(const Date& d) 运算符重载

发现 只有构造和拷贝构造存在一个类名,其他的函数在声明过程中都需要知名返回类型。因为他们都有返回值。
因此只要不是构造、拷贝 那么就是其他函数,其他函数有返回值
发现 但凡参数带&符号的 不是拷贝函数就是运算重载函数

更多推荐