【C++】类和对象(中)

1、类的6个默认成员函数

  • 如果一个类中什么成员都没有,简称为空类。那么空类中真的什么都没有吗?
  • 答:并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
  • 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

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


2、构造函数

(1)构造函数的使用

【C++】类和对象(中)_第2张图片

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
using namespace std;

class Date
{
public:
	/*Date()
	{
		cout << "Date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}*/

	//Date(int year, int month, int day)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//全缺省【无参和全缺省的情况不能同时出现,可能出现调用歧义】
	Date(int year = 1, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

	void Print()
	{
		//cout << this << endl;

		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year;  //声明
	int _month;
	int _day;
};


class Stack
{
public:
	/*Stack()
	{
		a = nullptr;
		top = capacity = 0;
	}*/

	//既初始化了栈,还避开了很多次扩容
	Stack(size_t n = 4) //构造函数
	{
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}

	// 成员函数
	//void Init()
	//{
	//	a = nullptr;
	//	top = capacity = 0;
	//}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}

private:
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);
	
	while (!st1.Empty())
	{
		cout << st1.Top() << " ";
		st1.Pop();
	}
	cout << endl;

	st1.Destroy();

	//Stack st2(1000);
	Stack st2;
	for (size_t i = 0; i < 100; i++)
	{
		st2.Push(i);
	}

	while (!st2.Empty())
	{
		cout << st2.Top() << " ";
		st2.Pop();
	}
	cout << endl;

	st2.Destroy();

	//无参的情况不能这么写,编译器分不清是函数声明还是定义对象
	//Date d1();
	//d1.Print();

	Date d1; //无参初始化
	d1.Print();

	Date d2(2023, 7, 21); //有参,参数在对象后边
	d2.Print();

	Date d3(2023, 7);
	d3.Print();

	Date d4(2023);
	d4.Print();

	return 0;
}

【C++】类和对象(中)_第3张图片

class Date
{
public:
	//全缺省【无参和全缺省的情况不能同时出现,可能出现调用歧义】
	Date(int year = 1, int month = 1, int day = 1) //构造函数
	{
		cout << "Date的构造函数" << endl;
		
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 1; //声明给的缺省值
	int _month = 1;
	int _day = 1;
};


class Stack
{
public:
	//既初始化了栈,还避开了很多次扩容
	Stack(size_t n = 4) //构造函数
	{
		cout << "Stack的构造函数" << endl;

		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}

private:
	int* a;
	int top;
	int capacity;
};

//两个栈实现队列
class MyQueue
{
private:
	Stack Push;
	Stack Pop;
};

// 构造函数,也是默认成员函数,我们不写,编译器会自动生成
// 编译生成的默认构造的特点:
// 1、我们不写才会生成,我们写了任意一个构造函数就不会生成了
// 2、内置类型的成员不会处理(C++11,声明支持给缺省值,给了缺省值,编译器生成的默认构造函数就不是随机值,会用缺省值初始化)
// 3、自定义类型的成员才会处理,会去调用这个成员的默认构造函数

// 总结:【一般情况都需要我们自己写构造函数,决定初始化方式】
// 【成员变量全是自定义类型,可以考虑不写构造函数】

int main()
{
	//构造函数,也是默认成员函数,我们不写,编译器会自动生成
	Date d1;
	d1.Print();

	MyQueue mq; //自动生成自己的析构函数,然后再调用两个栈的构造函数

	return 0;
}

【C++】类和对象(中)_第4张图片

(2)构造函数的特点

  • 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
  • 构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象
  • 其特征如下:
    (1)函数名与类名相同。
    (2)无返回值【不需要写 void】
    (3)对象实例化时编译器自动调用对应的构造函数。
    (4)构造函数可以重载【本质就是可以写多个构造函数,提供多种初始化方式】

    (5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
    (6)关于编译器生成的默认成员函数,大家可能会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用?
    ❗答:C++把类型分成内置类型(基本类型、指针)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数
    ❗注意:C++11 中【内置类型成员变量在类中声明时可以给默认值,给了缺省值,编译器生成的默认构造函数初始化就不是随机值,会用缺省值初始化
    【C++】类和对象(中)_第5张图片
    (7)无参的构造函数全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。多个并存会存在调用二义性
    ❗注意:【无参构造函数、全缺省构造函数、 我们没写编译器默认生成的构造函数,都可以认为是默认构造函数】【PS:不传参就可以调用的构造就是默认构造

3、析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时(出了作用域)会自动调用析构函数,完成对象中资源的清理工作。【资源就是动态申请的或者显示申请的

(1)析构函数的使用

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date()构造函数" << endl;
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()析构函数" << endl;
	}

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

class Stack
{
public:
	//既初始化了栈,还避开了很多次扩容
	Stack(size_t n = 4) //构造函数
	{
		cout << "Stack()构造函数" << endl;

		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}

	~Stack() //析构函数
	{
		cout << "~Stack()析构函数" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	bool Empty()
	{
		return top == 0;
	}

private:
	int* a;
	int top;
	int capacity;
};


//两个栈实现队列
class MyQueue
{
private:
	Stack Push;
	Stack Pop;
};

// 构造函数、析构函数都是默认成员函数,我们不写,编译器会自动生成
// 编译生成的默认析构的特点:
// 1、我们不写才会生成,我们写了任意一个析构函数就不会生成了
// 2、内置类型的成员不会处理
// 3、自定义类型的成员才会处理,会去调用这个成员的默认析构函数

// 总结:【一般情况都需要我们自己写析构函数】
// 【成员变量全是自定义类型,可以考虑不写析构函数】

int main()
{
	//后定义的先析构
	Date d1;
	Date d2;

	Stack st1;
	Stack st2;

	MyQueue q1; //自动生成自己的析构函数,然后再调用两个栈的构造函数和析构函数

	return 0;
}

【C++】类和对象(中)_第6张图片

//例题:括号匹配问题
bool isValid(const char* s)
{
	Stack st;

	while (*s)
	{
		if (*s == '[' || *s == '(' || *s == '{')
		{
			st.Push(*s);
			s++;
		}
		else
		{
			if (st.Empty())
				return false;

			char top = st.Top();
			st.Pop();

			if ((*s == ']' && top != '[') || (*s == '}' && top != '{') || (*s == ')' && top != '('))
			{
				return false;
			}
			++s;
		}
	}
	return st.Empty(); //先return再调用析构
}

int main()
{
	cout << isValid("([{}])") << endl << endl; //1
	cout << isValid("(){}[]") << endl << endl; //1
	cout << isValid("(]{}[])") << endl << endl; //0

	return 0;
}

【C++】类和对象(中)_第7张图片

(2)析构函数的特点

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型【不需要写 void
  3. 一个类只能有一个析构函数若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
  5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
  6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
    【C++】类和对象(中)_第8张图片
    【C++】类和对象(中)_第9张图片

4、拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的同类型对象创建新对象时由编译器自动调用【自定义类型传值传参必须调用拷贝构造】

(1)拷贝构造函数使用

#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date()构造函数" << endl;

		_year = year;
		_month = month;
		_day = day;
	}

	//Date d2(d1);
	Date(const Date& d) //拷贝构造
	{
		cout << "Date(Date& d)拷贝构造" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	
	~Date()
	{
		cout << "~Date()析构函数" << endl;
	}

private:
	// 内置类型
	int _year;
	int _month;
	int _day;
};

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack()构造函数" << endl;

		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	//Stack s2(s1);
	Stack(const Stack& s) //拷贝构造
	{
		cout << "Stack(Stack& s)拷贝构造" << endl;
		//深拷贝
		_array = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		memcpy(_array, s._array, sizeof(DataType) * s._size);
		_size = s._size;
		_capacity = s._capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	~Stack()
	{
		cout << "~Stack()析构函数" << endl;

		free(_array);
		_array = nullptr;
		_size = _capacity = 0;
	}

private:
	// 内置类型
	DataType* _array;
	int _capacity;
	int _size;
};

void func1(Date d)
{
	d.Print();
}

//void func2(Stack& s) //s的修改会影响s1

//期望,s要插入一些数据,s的改变,不影响s1
void func2(Stack s)
{
	s.Push(1);
	s.Push(2);
}

int main()
{
	Date d1(2023, 7, 21);
	//func1(d1);

	Stack s1;
	func2(s1); //s1不会被s的修改所影响
	Stack s2(s1); //s2先析构

	//以下两个写法是等价的,都是拷贝构造
	Date d2(d1); //【Date d3 = d1;】

	return 0;
}

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

【C++】类和对象(中)_第11张图片

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

(2)拷贝构造函数特点

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
    【C++】类和对象(中)_第13张图片
    【C++】类和对象(中)_第14张图片
    【C++】类和对象(中)_第15张图片
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
    ❗注:【编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的】
    【C++】类和对象(中)_第16张图片
  4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。
    ❗注:【类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝】
  5. 拷贝构造函数典型调用场景:
    (1) 使用已存在对象创建新对象
    (2) 函数参数类型为类类型对象
    (3) 函数返回值类型为类类型对象
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date()构造函数" << endl;

		_year = year;
		_month = month;
		_day = day;
	}

	//Date d2(d1);
	/*Date(const Date& d)
	{
		cout << "Date(Date& d)拷贝构造" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}*/

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()析构函数" << endl;
	}

private:
	//内置类型
	int _year;
	int _month;
	int _day;
};

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack()构造函数" << endl;

		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	//Stack s2(s1);
	Stack(const Stack& s) //拷贝构造
	{
		cout << "Stack(Stack& s)拷贝构造" << endl;
		//深拷贝
		_array = (DataType*)malloc(sizeof(DataType) * s._capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		memcpy(_array, s._array, sizeof(DataType) * s._size);
		_size = s._size;
		_capacity = s._capacity;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	~Stack()
	{
		cout << "~Stack()析构函数" << endl;

		free(_array);
		_array = nullptr;
		_size = _capacity = 0;
	}

private:
	//内置类型
	DataType* _array;
	int _capacity;
	int _size;
};

class MyQueue
{
private:
	Stack _pushst;
	Stack _popst;
};

int main()
{
	// 我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性不一样
	// (1)内置类型, 值拷贝【浅拷贝】
	// (2)自定义的类型,调用他的拷贝
	// 总结:Date类不需要我们实现拷贝构造,默认生成就可以用
	// Stack类需要我们自己实现深拷贝的拷贝构造,默认生成会出问题

	Date d1(2023, 7, 23);
	Date d2 = d1;

	//Stack st1;
	//Stack st2(st1);

	//MyQueue对于默认生成的几个函数都非常受用
	MyQueue m1;
	MyQueue m2(m1); //自动生成默认拷贝构造

	return 0;
}

5、赋值运算符重载

【1】运算符重载

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

  • 函数名字为:关键字 operator 后面接需要重载的运算符符号。
  • 函数原型:返回值类型 operator 操作符(参数列表)
  • ❗注:
    (1)不能通过连接其他符号来创建新的操作符:比如operator@
    (2)重载操作符必须有一个类类型参数
    (3)用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
    (4)作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    (5).* :: sizeof ?: . 注意以上5个运算符不能重载。
    (6)不能改变操作符的操作个数。一个操作符是几个操作数,那么重载的时候就有几个参数。

(1)写在全局

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date()构造函数" << endl;
	
		_year = year;
		_month = month;
		_day = day;
	}
	
	//Date d2(d1);
	Date(Date& d)
	{
		cout << "Date(Date& d)拷贝构造" << endl;
	
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
		
	~Date()
	{
		cout << "~Date()析构函数" << endl;
	}
	
//private:
	// 内置类型
	int _year;
	int _month;
	int _day;
};

//运算符重载
//bool DateLess(const Date& x1, const Date& x2)
bool operator<(const Date& x1, const Date& x2)
{
	if (x1._year < x2._year)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month < x2._month)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
	{
		return true;
	}
	else
	{
		return false;
	}
}

int main()
{
	Date d1(2023, 7, 23);
	Date d2(2023, 8, 23);

	//d1
	//cout << DateLess(d1, d2) << endl;
	d1 < d2;
	cout << (d1 < d2) << endl; //<<运算符优先级高于<
	cout << operator<(d1, d2) << endl; //显式调用

	return 0;
}

(2)写成成员函数

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		cout << "Date()构造函数" << endl;

		_year = year;
		_month = month;
		_day = day;
	}

	//Date d2(d1);
	Date(Date& d)
	{
		cout << "Date(Date& d)拷贝构造" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()析构函数" << endl;
	}

	//d1
	//d1.operator<(d2)
	bool operator<(const Date& d) //d1 就是 this,d2 就是 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;
		}
	}

private:
	//内置类型
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 7, 23);
	Date d2(2023, 8, 23);

	//d1
	//cout << DateLess(d1, d2) << endl;
	d1 < d2;
	cout << (d1 < d2) << endl; //<<运算符优先级高于<
	cout << d1.operator<(d2) << endl; //显式调用

	return 0;
}

【C++】类和对象(中)_第17张图片

【2】赋值运算符重载

  • 赋值运算符重载格式
  • 参数类型:const T&【传递引用可以提高传参效率】
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
  • ❗ 注:赋值运算符只能重载成类的成员函数不能重载成全局函数【原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数】
    在这里插入图片描述
  • 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
    意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
    重载完成赋值。
  • 既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
    现吗?当然像日期类这样的类是没必要的。【注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现
    【C++】类和对象(中)_第18张图片
Date& Date::operator=(const Date& d)
{
	if (this != &d) //屏蔽d1=d1的操作
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;
}

【3】 前置++和后置++重载

【C++】类和对象(中)_第19张图片

  • 内置类型用(++i)或者(i++)差别不大
  • 自定义类型用前置++(++i) 更好,减少两次拷贝
    【C++】类和对象(中)_第20张图片
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

Date Date::operator++(int i)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int i)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

6、日期类的实现

链接:日期类的实现【C++】


7、const成员

  • 将 const 修饰的“成员函数”称之为 const 成员函数,const 修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改
    【C++】类和对象(中)_第21张图片
  • (1)const对象可以调用非const成员函数吗?【❌】
  • (2)非const对象可以调用const成员函数吗?【✅】
  • (3)const成员函数内可以调用其它的非const成员函数吗?【❌】
  • (4)非const成员函数内可以调用其它的const成员函数吗?【✅】
    【C++】类和对象(中)_第22张图片
  • void Print() const 和 void Print() 构成函数重载,对象调用时会自动选择最匹配的
  • ❗【总结:只读函数可以加 const,内部不涉及修改成员的都是只读函数】
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	//void Print(Date* this)
	void Print()
	{
		cout << "Print():";
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	//void Print(const Date* this)
	void Print() const
	{
		cout << "Print()const:";
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2023, 7, 28);
	d2.Print();

	return 0;
}

【C++】类和对象(中)_第23张图片

class SeqList
{
public:
	void PushBack(int x)
	{
		_a[_size++] = x;
	}

	size_t size() const
	{
		return _size;
	}

	//读
	const int& operator[](size_t i) const
	{
		assert(i < _size);
		return _a[i];
	}

	//读/写
	int& operator[](size_t i)
	{
		assert(i < _size);
		return _a[i];
	}
	
private:
	int* _a = (int*)malloc(sizeof(int) * 10);
	size_t _size;
	size_t _capacity;
};

//只读不修改,调【const int& operator[](size_t i) const】
void Print(const SeqList& sl)
{
	for (size_t i = 0; i < sl.size(); i++)
	{
		//sl[i]++;
		cout << sl[i] << " ";
	}
	cout << endl;
}

int main()
{
	SeqList s;
	s.PushBack(1);
	s.PushBack(2);
	s.PushBack(3);
	s.PushBack(4);

	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i] << " ";
		//cout << s.operator[](i) << " ";
	}
	cout << endl;

	for (size_t i = 0; i < s.size(); i++)
	{
		//s[i]++; //error【operator[](size_t i)函数是传值返回,返回的是拷贝的临时变量,具有常性,不能修改】
		s[i]++; //因为数组开在堆上,_a[i]出了函数还存在,函数返回值可以改为int&
	}
	
	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i] << " ";
	}
	cout << endl;

	Print(s);

	return 0;
}

【C++】类和对象(中)_第24张图片


8、取地址重载

  • 这两个默认成员函数一般不用重新定义,编译器默认会生成。
  • 这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。
#define _CRT_SECURE_NO_WARNINGS 1
#include 
using namespace std;

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

	void Print()
	{
		cout << "Print():";
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	void Print() const
	{
		cout << "Print()const:";
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	//日常自动生成的就可以
	Date* operator&()
	{
		return this; //this就是对象的地址
	}

	const Date* operator&() const
	{
		return this;
	}

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

int main()
{
	const Date d1(2023, 7, 28);
	d1.Print();

	cout << &d1 << endl;

	Date d2(2023, 8, 13);
	d2.Print();

	cout << &d2 << endl;

	return 0;
}

【C++】类和对象(中)_第25张图片

你可能感兴趣的:(C++,c++,开发语言)