类与对象(中)

类的6个默认成员函数

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

在这里插入图片描述

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
默认成员函数:特殊的成员函数,我们不写,编译器会自己生成一个,我们写了,编译器就不会生成。
隐含的意思:对于有些类,需要我们自己写。
对于另外一些类,编译器默认生成的就可以用。

1.构造函数

主要完成初始化功能,C语言中经常忘记Init和Destroy,C++就是利用构造函数在对象定义的时候自动初始化。

其特征如下:
1.函数名与类名相同。
2.无返回值。
3.对象实例化时编译器自动调用对应的构造函数。

class Date
{
public:
	//初始化
	Date(int year = 1, int month = 1, int day = 1)   //缺省值
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//日期类不需要写析构函数,因为类中没有资源需要销毁
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{

	Date d1(2023, 3, 15);
	Date d2(2021, 3, 12);
	
	//不支持以下写法,编译器无法判断是定义一个对象还是函数声明
	//Date d3();
	//Date func();
	return 0;
}

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证
每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

4.构造函数可以重载
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}
	void push(int x)
	{
		//扩容...
		_a[_top++] = x;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	//普通对象的生命周期:出了函数作用域就不存在了
	//手动开辟的对象,手动释放的时候就不存在了
	Stack st; //生命周期结束后,会自动调用析构函数
	st.push(1);
	st.push(2);
	st.push(3);
	return 0;
}

6.关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,默认的构造函数会对内置类型不处理,对自定类型成员调用的它的默认构造函数。(这一点非常重要)

class MyQueue
{
public:
	//这种情况需要用初始化列表解决,后续会说明
	/*MyQueue(size_t capacity = 8)
		:_popST(capacity)
		,_pushST(capacity)
		,_size(0)
	{}*/
	void push(int x)
	{
		//...
	}
private:
	//自定义类型,不需要自己写构造函数,默认生成的构造函数会调用Stack自己的构造函数
	Stack _pushST;
	Stack _popST;
	//既有自定义类型也有内置类型要如何处理???
	//size_t _size = 0;  //这里不是初始化,给的是缺省值
};
class A
{
public:
	A()
	{
		_a = 1;
		cout << "A()构造函数" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//自定义类型
	int _year; // 年
	int _month; // 月
	int _day; // 日

	//内置类型
	A _aa;
};
int main()
{
    Date d;
    d.Print();
    return 0;
}

在这里插入图片描述

7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(调用的时候会存在二义性)
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

不用传参数就可以调用的构造函数就叫默认构造函数!!!
构造函数可以重载意味着可以提供多个构造函数!!!

总结:什么时候要自己写构造函数???
面向需求,如果编译器默认生成的就可以满足,就不用自己写,不满足需求就要自己写。
Data Stack的构造函数需要自己写
MyQueue就不需要自己写,默认生成的构造函数就可以用
总而言之,看自己的需求,具体来看

补充!!!

class Stack
{
public:
	/*Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}*/

	void push(int x)
	{
		//扩容...
		_a[_top++] = x;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	//给了缺省值,就可以不写默认构造函数了
	int* _a = nullptr;
	int _top = 0;
	int _capacity = 0;
	//但这样非常不好
};
class MyQueue
{
public:
	
	void push(int x)
	{
		//...
	}
private:
	Stack _pushST;
	Stack _popST;
	size_t _size = 0;  //这里不是初始化,给的是缺省值
};

2.析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。(与之前在C中写的Destroy函数类似)

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

1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4.对象生命周期结束时,C++编译系统系统自动调用析构函数
5.关于编译器自动生成的析构函数,是否会完成一些事情呢?编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

总结:
什么时候需要自己写析构函数
Stack的析构函数,需要我们自己写。
MyQueue Data的析构函数不需要自己写,默认生成的就可以用。

3.拷贝构造函数

拷贝构造函数也是特殊的成员函数,其特征如下:
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
传值过程本身也是需要调用拷贝构造函数的,所以用传值的方式会引发无穷递归。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//d2(d1)
	//最好加一个const 防止d1被修改
	Date(const Date& d1)
	{
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;

		//形参加const,防止写反,加const下面的问题就可以被检查出来
		//d1._day = _day;
		cout << "Date 的拷贝构造" << endl;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日

};
//形参是实参的拷贝
void Func1(Date d)
{
	cout << "Func1(Date d)" << endl;
}
//形参是实参的别名
void Func2(Date& d)
{
	cout << "Func2(Date& d)" << endl;
}
int main()
{
	Date d1(2023, 3, 18);   //构造是初始化的意思
	//拷贝一份d1
	Date d2(d1); //拷贝构造--拷贝初始化
	Func1(d1);
	Func2(d1);
	return 0;
}

在这里插入图片描述

3.若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

//对于Stack类默认生成的拷贝构造(浅拷贝)不能满足要求
//此时我们需要写一个深拷贝
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}
	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);
		_capacity = st._capacity;
	}
	void push(int x)
	{
		//扩容...
		_a[_top++] = x;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
	
};
int main()
{
	Stack st1;
	st1.push(1);
	st1.push(2);

	Stack st2(st1);
	st2.push(10);
	return 0;
}

原理
在这里插入图片描述

4.赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

运算符的真正意义是使代码更具有可读性!!!
注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 注意以上5个运算符不能重载。
    在这里插入图片描述

日期类很好的体现了运算符重载的特性

赋值运算符重载

  1. 赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
    原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
    一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
    运算符重载只能是类的成员函数。
  2. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
    意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
    重载完成赋值。
//Date.h
#pragma once
#include <iostream>
using namespace std;
class Date
{
public:
	//友元声明的时候可以在任意位置
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
	int GetMonthDay(int year, int month)
	{
		//只用生成一次数据
		static int MonthDay[13] = { 0,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 MonthDay[month];
		}
	}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		//检查日期的合法性
		if (!(year >= 1 &&
			(month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}

	//赋值运算符重载
	//d1 = d2;
	//一般这里的返回值不用加const,返回值一般可以修改
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		//return d;  //权限的放大
		//出了作用域返回对象还在,就可以用引用返回
		return *this;
	}
	//d2(d1)
	//最好加一个const 防止d1被修改,一定得传引用
	Date(const Date& d1)
	{
		_year = d1._year;
		_month = d1._month;
		_day = d1._day;
	}
	void Print() const   //修饰*this
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//成员函数的第一个参数为隐藏的this指针
	bool operator==(const Date& d) const;
	//d1 > d2
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator!=(const Date& d) const;

	//d1+=100
	Date& operator+=(int day);
	//d2 = d1+100
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	Date& operator++();
	Date operator++(int); //C++规定后置加一个参数,但是不用接收这个实参
	//多一个参数主要是为了跟前置进行区分,构成重载
	Date& operator--();
	Date operator--(int);

	//d1-d2
	int operator-(const Date& d) const; 

	//取地址重载(默认成员函数),会默认生成,自己不需要实现
	Date* operator&()
	{
		return this;
	}
	const Date* operator&() const
	{
		return this;
	}
	//要求某个类的对象不让取地址
	Date* operator&()
	{
		return nullptr;
	}
	const Date* operator&() const
	{
		return nullptr;
	}

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

//写成有友元函数,就可以访问类中的成员变量了
//static void operator<<(ostream& out, const Date& d)
//{
//	out << d._year << "-" << d._month << "-" << d._day << endl;
//}
//1.改成static进行,前面加一个static是改变链接属性,只在当前文件可见,不会进入符号表

//2.声明和定义分离,只在.cpp中有定义,只会在.cpp中才会进入符号表
//ostream& operator<<(ostream& out, const Date& d);
//3.加一个内联,同样不会进入符号表,直接再用的地方展开
//cout<<d1   因为传参的顺序,这个必须要写成全局函数
inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
//Date.cpp
#include "Date.h"
bool Date::operator>(const Date & d) const
{
	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;
	}
	return false;
}
bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
bool Date::operator>=(const Date& d) const
{
	return *this > d || *this == d;
}
bool Date::operator<(const Date& d) const
{
	return !(*this >= d);
}
bool Date::operator<=(const Date& d) const
{
	return *this < d || *this == d;
}
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}


Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= (-day);
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day) const
{
	Date ret(*this);
	ret += day;
	return ret;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += (-day);
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day) const
{
	Date ret(*this);
	ret -= day;
	return ret;
}

//对于自定义类型来说,尽量用前置,后置会多两层拷贝
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}


//它们既是函数重载,也分别是运算符重载
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
Date Date::operator--(int)
{
	Date tmp(*this);  //拷贝构造
	*this -= 1;
	return tmp;
}

//日期-日期(复用)
int Date::operator-(const Date& d) const
{
	Date max = *this;  //拷贝构造
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++n;
		++min;
	}
	return n * flag;
}

//ostream& operator<<(ostream& out, const Date& d)
//{
//	out << d._year << "-" << d._month << "-" << d._day;
//	return out;
//}
//test.cpp
#include "Date.h"

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}
	//st1 = st2
	Stack& operator=(const Stack& st)
	{
		cout << "Stack& operator=(const Stack& st)" << endl;
		if (this != &st)
		{
			free(_a);
			_a = (int*)malloc(sizeof(int) * st._capacity);
			if (_a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			memcpy(_a, st._a, sizeof(int) * st._top);
			_capacity = st._capacity;
		}
		return *this;
	}
	Stack(const Stack& st)
	{
		_a = (int*)malloc(sizeof(int) * st._capacity);
		if (_a == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_a, st._a, sizeof(int) * st._top);
		_capacity = st._capacity;
	}
	void push(int x)
	{
		//扩容...
		_a[_top++] = x;
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
	
};

class MyQueue
{
public:
	
	void push(int x)
	{
		//...
	}
private:
	Stack _pushST;
	Stack _popST;
};

void TestDate1()
{
	Date d1(2022, 2, 30);
	d1.Print();
	d1 + 100; //转换成函数调用
}

//bool operator+(int a,int b)
//{}

void TestDate2()
{
	Date d0;
	Date d1;
	Date d2(2022, 10, 8);
	d1.Print();
	Date d3(d2); // 拷贝构造(初始化)
	d1 = d2;     //赋值重载(复制拷贝),已经存在的两个对象之间的拷贝
	d0 = d1 = d2;  //链式赋值,一般是返回左操作数  
	d1.Print();

	Date d4 = d2;//这里是拷贝构造还是赋值重载呢???
	//此处是拷贝构造

	int i, j, k;
	i = j = k =10;//整形会有链式赋值
	i++;  //i可以修改  
}
void TestStack()
{
	Stack st1;
	st1.push(1);
	st1.push(1);
	st1.push(1);

	Stack st2;
	st2.push(10);
	st2.push(20);
	//st1 = st2;   //崩溃,析构两次+内存泄露

	MyQueue q1;
	MyQueue q2;
	q2 = q1;
	//st1 = st1;//错误
}

void TestDate3()
{
	Date d1(2022, 10, 8);
	d1 -= 10000;
	d1.Print();

	Date d2(d1);

	(d2 - 10000).Print();

	d1 -= -10000;
	d1.Print();
	(++d1).Print();//d1.opeartor++();
	(d1++).Print();//d1.opeartor++(int);

	(--d1).Print();
	(--d1).Print();
	
	/*Date d1(2023, 3, 19);
	Date d2(2023, 9, 1);
	cout << d2 - d1 << endl;*/

}
void TestDate4()
{
	//为什么能自动识别类型
	//本质原因是函数重载+运算符重载
	Date d1, d2;
	cin >> d1 >> d2;
	//链式调用
	cout << d1 << endl << d2 << endl;;

}

void TestDate5()
{
	const Date d1(2023, 3, 19);
	d1.Print();
}
int main()
{
	TestDate5();
	return 0;
}

在这里插入图片描述
MyQueue这样的类也不需要自己写赋值重载,自定义类型会调用它自己赋值重载函数!!!

函数定义写在.h里面的函数在多个链接包,会出现链接冲突,解决办法见代码!!!
在这里插入图片描述
会在两个文件中包含,进入在符号表中两次,会链接报错

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

在这里插入图片描述

取地址重载

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值