c++——类和对象(1)构造,析构函数

类的六个默认函数

如果一个类当中没有成员的话,那叫空类,实际上空类有6个编译器默认生成的函数成员

默认成员函数:没有显示实现,编译器生成的成员函数称为默认成员函数

1,构造函数与构析函数

1.1构造函数的概念

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以

保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

1.2构造函数的特性

构造函数主要任务是初始化对象,并不是开辟空间

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

class Data {
public:
//无参构造函数
	Data(){
	_year = 1;
	_month = 1;
	_day = 1;
	}
//含参构造函数
	Data(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;


	}
//全缺省函数
	Data(int year = 2, int month = 2, int day = 2)
	{

	}
	
	void printf()
	{
		
		cout << _year << _month << _day << endl;//崩溃,
		cout << "printf()" << endl;//正常
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	
	Data d1;
	d1.printf();
	Data d2(2024, 2, 2);
	d2.printf();
	Data d3;
	d3.printf();
	return 0;
}


4.1无参构造函数与含参构造函数的使用,无参函数直接Data d1;不加括号为了

避免重复声明新函数

Data d1;
d1.printf();
Data d2(2024, 2, 2);
d2.printf();

4.2全缺省函数与无参构造函数,d1与d3构成重载函数,但下面会构成重载失误

Data d1;
d1.printf();

Data d3;
d3.printf();//d3与d1构成重载,

会出现下面错误

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

6. C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。

class Time {
public:
	Time()
	{
		cout << "time()" << endl;
		_hour = 0;
		_minute = 0;
	}
private:
	int _hour;
	int _minute;
};
class Data
{
public:

private:int _year;
	   int _month;
	   int _day;
	   Time _t;
};
int main()
{
	Data d;
	return 0;
}

上面的代码的处理结果是内置类型不一会被处理(具体看编译器vs不会处理),而自定义类型会被处理,Time是自定义类型所以会初始化

但只有是无参构造函数才会成功,若有参数会报错Time(int a){}

1.3析构函数的概念

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

1.4析构函数的特性

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数

typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 3)
 {
 _array = (DataType*)malloc(sizeof(DataType) * capacity);
 if (NULL == _array)
 {
 perror("malloc申请空间失败!!!");
 return;
 }
 _capacity = capacity;
 _size = 0;
 }
 void Push(DataType data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 // 其他方法...
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = NULL;
 _capacity = 0;
 _size = 0;

 }
 }
private:
 DataType* _array;
 int _capacity;
 int _size;
};
void TestStack()
{
 Stack s;
 s.Push(1);
 s.Push(2);
}

如果像这一类,申请了空间,没有调用显示析构函数,会有内存泄漏的风险,所以如果有类调用堆上的空间,一定要调用析构函数

2,拷贝构造函数

2,1概念

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

2.2特性

1, 拷贝构造函数的参数只有一个

2参数必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // Date(const Date& d)   // 正确写法
    Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归
 {
 _year = d._year;
 _month = d._month;
 _day = d._day;
 }
private:
 int _year;
 int _month;
 int _day;
};
int main()
{
 Date d1;
 Date d2(d1);
 return 0;
}

因为传值会调用拷贝构造函数,造成无限递归,所以用引用

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

4类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

typedef int DataType;
class Stack
{
public:
 Stack(size_t capacity = 10)
 {
 _array = (DataType*)malloc(capacity * sizeof(DataType));
 if (nullptr == _array)
 {
 perror("malloc申请空间失败");
 return;
 }

 _size = 0;
 _capacity = capacity;
 }
 void Push(const DataType& data)
 {
 // CheckCapacity();
 _array[_size] = data;
 _size++;
 }
 ~Stack()
 {
 if (_array)
 {
 free(_array);
 _array = nullptr;
 _capacity = 0;
 _size = 0;
 }
 }
private:
 DataType *_array;
 size_t _size;
 size_t _capacity;
};
int main()
{
 Stack s1;
 s1.Push(1);
 s1.Push(2);
 s1.Push(3);
 s1.Push(4);
 Stack s2(s1);
 return 0;
}

这是为什么,是因为浅拷贝是在同一块空间,如果创建两个对象,s1,s2,都指向同一内存,调用析构函数时,会将同一块空间free两次,造成错误

5. 拷贝构造函数典型调用场景:

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date {
public:
	Date(int year,int month,int day)
	{
		cout << "Data()"<<endl;
		_year = year;
		_month = month;
		_day = day;
	}
	~Date()
	{
		cout << "~Data()"<<endl;
	}
	Date(const Date& d)
	{
		cout << "Date(const Date&d)"<<endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	
private:
	int _year;
	int _month;
	int _day;

};
Date test(Date d1)
{
	Date tem(d1);第二次拷贝构造
	return tem;//本来是第三次拷贝构造,但被编译被优化了因为第二次拷贝构造的值可以做返回值
}
int main()
{
	Date d1(2024, 2, 1);
	test(d1);//第一次拷贝构造函数


	
	return 0;
}

下面是结果

6.传值与引用的细分

1传值会多拷贝一次,比较麻烦,但是可以避免一些错误

2而引用虽然方便但是会有一些情况出问题;

 class Date {
	public:
		Date(int year,int month,int day)
		{
			cout << "Data()"<<endl;
			_year = year;
			_month = month;
			_day = day;
		}
		~Date()
		{
			cout << "~Data()"<<endl;
			_year = -1;
			_month = -1;
			_day = -1;
		}
		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;//this是d2的形参,d是_d4
			//然后d1是this的位置,d2是d4
					 return *this;
			}
		void printf()
		{
			cout << _year <<" " << _month <<" " << _day << endl;
		}
	private:
		int _year;
		int _month;
		int _day;
	
	};

 Date& fun()
 {
	 Date d(1, 2, 2);
	 return d;
 }void func()
 {

 }
 int main()
 {
	 Date d1(2024, 4, 14);
	 Date d2(d1);
	 Date d3 = d1;//拷贝构造,把存在的拷贝给不存在的
	 Date d4(2024, 5, 1);
	d1= d4;//赋值重载,不会调用拷贝构造函数

	 //连续赋值
	 d1 = d2 = d4;
	 //到这一行总共5个调用拷贝,但是把上面的变成引用返回,只有两个
return 0;

}

下面是结果

但是如果是引用的话

下面是一些错误的发生

 Date& fun()
 {
	 Date d(1, 2, 2);
	 return d;
 }
 int main()
 {
	 
	  Date& ret = fun();
	 ret.printf();
	 return 0;
 }

因为return d前先析构,再返回,本来是调试看到析构后this是随机值是野空间,如果下面在调用一个函数,就会可能会变野空间会  变化,随机值被改变
 

总 结  返回对象是临时对象,或局部变量不用引用返回,有风险
 1如果返回对象生命周期到了,不会析构,可以用传引用返回uobuyi

创作不易,希望点赞!!!

目录

类的六个默认函数

1,构造函数与构析函数

2,拷贝构造函数


  • 47
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值