类和对象(2)

文章目录

  • 1.类的6个默认成员函数(天选之子)
  • 2.构造函数
  • 3.析构函数
    • 3.1特性
  • 4.拷贝构造

1.类的6个默认成员函数(天选之子)

C语言中,可能中途return也可能最后return,destroy的地方很多,比较麻烦。
以下为C++进行的改进,俗称”天选之子“。
类和对象(2)_第1张图片

  • 构造函数和析构函数如果不写,编译器则会自动生成一个默认
  • 如果我们实现了任意一个,编译器就会自动生成了。
  • 对象在实例化的时候必须调用构造函数(包括自己写的和编译器自动生成的),应调用自己生成的那个。

C++类型分为:内置类型(int,char,任意类型的指针)不处理,
自定义类型(class,struct,union联合体)调用他的构造
默认生成的构造函数对于内置类型的成员不做处理,对于自定义类型的成员,会去调用它的构造(不用传参的构造)
与析构函数相似

class Date
{
public:
	Date()
	{
		_year = 1;
		_month= 1;
		_day= 1;


	}
	Date(int year, int month, int day)//带参的初始化
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
//基本类型或内置类型,因为带int
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(2023, 9, 13);
    d1.Print();
	d2.Print();
	return 0;
}
   

类和对象(2)_第2张图片

合并两个构造函数,用全缺省更好用

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

两个栈实现一个队列(push,pop,peek,empty)问题。
类和对象(2)_第3张图片
C

typedef struct {
	ST pushst;
	ST popst;
}MyQueue;
bool myQueueEmpty(MyQueue* obj);
MyQueue* myQueueCreate()
{
	MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
	StackInit(&pq->pushst);
	StackInit(&pq->popst);
	return pq;
}
void myQueueFree(MyQueue* obj)
{
	assert(obj);
	StackDestroy(&obj->pushst);
	StackDestroy(&obj->popst);

	return pq;
}

C++

class MyQueue {
public:
	//默认生成的构造函数,对自定义类型,会调用他的默认构造函数。
	void push(int x)
	{}
	//...
	Stack _pushST;
	Stack _popST;
};
int main()
{
	MyQueue q;
	return 0;
}

2.构造函数

实际上是在初始化对象特殊的成员函数。作用和Init相同。,主要任务不是开空间创造对象,而是初始化对象

  1. 函数名和类名相同。名字已经定好了
  2. 没有返回值,什么也不用写。
  3. 对象实例化时自动调用对应的构造函数需要调用显示的Init
  4. 可以重载(一个类可以有多个构造函数),有多个构造函数就有多种初始化方式
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,对于内置类型不做处理,对于自定义类型会去调用它的构造函数。一旦用户显式定义编译器再生成。
  6. 无参全缺省的构造函数都被称为默认构造函数,且仅有一个,同时调用时存在歧义。不写编译器自动生成的也是默认构造函数。不传参数就可以调用构造函数。一般建议每个类都提供一个默认构造函数。
#include 
using namespace std;
class Stack
{
public:
	//成员函数
	void Init(int n=4)
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == a)
		{
			perror("malloc申请空间失败");
			return;
		}
		capacity = n;
		size = 0;
	}
	void Push(int x)
	{
		//...
		a[size++] = x;
	}
	void Destroy()
	{
		//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。
	}
	//...
private:
	//成员变量
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
	//定义一个栈
	Stack st;
	//初始化
	st.Init(4);
	st.Push(1);
	st.Push(1);
	st.Push(1);
	st.Push(1);
	//进行销毁
	st.Destroy();

	return 0;
}
class Stack
{
public:
	Stack()
	{
		_a = nullptr;
		_size = _capacity = 0;
	}
	Stack(int n)//有了构造函数的类就不需要Init的类
		//构成函数重载
	{
		_a = (int*)malloc(sizeof(int) * n);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = n;
		_size = 0;
	}
	void Push(int x)
			{
				//...
				_a[_size++] = x;
			}
	void Destroy()
			{
				//...防止内存泄漏,可以把这件事交给编译器,让他自动搞起来。
			}
private:
		//成员变量
		int* _a;
		int _size;
		int _capacity;
};
int main()
{//构造函数的调用
	//定义一个栈
	//Stack st;//无参,不带(),否则会报错
	//因为是声明函数还是调用对象分不清楚。有点混淆
	
	Stack st(4);//带参
	//Stack st(4,3);

	st.Push(1);
	st.Push(1);
	st.Push(1);
	st.Push(1);
	//进行销毁
	st.Destroy();

	return 0;
}

Stack st(4)不等同于st.Stack(4)
构造函数不能用对象调用,st定义好了才向下走。

3.析构函数

destroy,和刚才构造函数的方法类似,定义一个函数让他自动调用。功能和构造函数相反,析构函数不是完成对象本身的销毁,析构函数进行清理资源在对象出了作用域,生命周期销毁后进行调用

3.1特性

  1. 函数名是在类名前加==~==。C语言中代表按位取反。
  2. 参数返回值。
  3. 一个类只有一个析构函数,若未显式定义,系统则**自动生成默认的析构函数。**注意:析构函数不能重载。
  4. 生命周期结束时,C++编译系统自动调用析构函数
~Stack()
{

   free(_a);
   _a=nullptr;
   _size=_capacity=0;
}//只定义一个对象就释放一次,两个对象释放2次

类和对象(2)_第4张图片

4.拷贝构造

  1. 构造函数的一个重载,函数名和类名相同,没有返回值。
  2. 参数只有一个且必须是类类型对象的引用,使用传值编译器直接报错,因为会引发无穷递归调用
Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;//拷贝构造
	}//编译器不允许发生这种拷贝构造,会产生无穷递归,规定这个地方必须用引用

//普通整型,内置类型可以直接传递,但是自定义类型不可以

//传值传参
void Func1(Date d)
{}//Func传参的时候是一个拷贝,把d1拷贝给d
//传引用传参
void Func2(Date& d)
{
}//d是d1的别名

int main()
{
Func1(d1);
return 0;
}
  1. 自定义类型不能编译器随便拷贝。内置类型编译器可以直接拷贝,自定义类型的拷贝需要调用拷贝构造。

  2. 字节拷贝:浅拷贝

  3. 对于栈来说,浅拷贝两个栈指定同一空间,会析构两次。

  4. C++规定自定义类型要调用一个拷贝构造,栈这样的类要调用一个深拷贝自己去开一块空间)的拷贝构造。

  5. 调用func1先传参,传参就是一个拷贝构造

  6. Date d2(d1);//对象实例化调用,先调用对应的构造函数,此时是拷贝构造Date (Date d);,调用拷贝构造之前要先传参d1传给d,传值传参又形成一个拷贝构造;拷贝构造要先传参,传值传参又是一个拷贝构造。。。。。递归下去。套娃
    解决方法:引用。Date (Date &d);//d是d1的别名

  7. 拷贝构造也可以这么写:Date d2=d1;
    拷贝构造一般加const

  8. Date (const Date &d);//权限缩小,权限可以缩小不可放大。

  9. 引用不需要调用函数。调用就是传参

  10. 指针也是内置类型自定义类型必须用拷贝构造完成,编译器驾驭不了。

Date(const Date* d)
{
        cout<<"Date(Date& d)"<<endl;
        _year = d->_year;
		_month = d->_month;
		_day = d->_day;//不是拷贝构造
}//只是一个普通的构造函数
  Date d4(&d1);
  Dare d5=&d1;//复杂别扭

实现一个函数,实现多少天以后的日期。

int GetMonthDay(int year, int month)
	{//if,else/switch,case
		//数组
		assert(month > 0 && month < 13);

		int monthArray[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 monthArray[month];
		}
	}
	Date GetAfterXDay(int x)
	{
		//先用一个结果,先生成一个中间临时对象。拷贝d1,this是d1的地址
		//Date tmp;
		//tmp._year = _year;//传值返回不用tmp,因为tmp出了作用域后被销毁,返回tmp的拷贝。进行拷贝构造
		//用同类型对象
		//Date tmp(*this);拷贝构造
	    Date tmp = *this;//拷贝构造
	//不能用引用,用引用tmp是d1的别名。tmp的改变就是d1的改变。
 //不动自己的成员
		//加法进位
		tmp._day += x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month))
		{
			//进位
			tmp._day -= GetMonthDay(tmp._year, tmp._month);
			++tmp._month;
			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		//获取日期
		//return* this;//返回自己,自己的年月日都在往后加,this是这个对象的指针,*this是这个对象//除了作用于还在
		return tmp;//局部对象
	}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	int main()
	{
		Date d1(2023, 9, 13);
		Date d2 = d1.GetAfterXDay(100);
		d1.Print();
		d2.Print();//这时改变_year等会改变d1

		return 0;
	}

+(不改变)和+=(改变)返回值的区别。

你可能感兴趣的:(c++)