C++初阶(3) 类中的默认成员函数——构造函数、析构函数、拷贝构造函数、赋值运算符的重载

类中的默认成员函数有6个,但重点需要掌握的有构造函数、析构函数、拷贝构造函数、赋值运算符的重载这四个。今天我们就来一一讲解。

一、构造函数
·功能

虽然名称叫“构造”,但主要任务并不是开空间创建对象,而是初始化对象

·特征

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用相应的构造函数
  4. 构造函数可以重载(一个类的对象可以有多种初始化方式)

我们以Date类为例,分别讲述构造函数的几种情况:

class Date
{
private:
	int _year;
	int _month;
	int _day;
};

(1)没有自己定义构造函数时,编译器会自动生成构造函数,对象也可以创建成功

int main()
{
   Dates d1;
   return 0;
}

那系统自动生成的构造函数到底做了哪些事呢?

  1. 对于编译器的内置类型,如int,char,float,double以及指针等,编译器不做处理
  2. 对于自定义类型,如我们自己创建的类和结构体,编译器会自动找到他们的默认构造函数,进行初始化

但这样显得极不公平,因为对不同类型做了不同处理,有人将它视为C++的一个小缺陷。在C++11以后的版本里,针对此推出了一个新语法:

class Data
{
private:
	int _year = 2021;//不是赋值,是给缺省值
	int _month = 8;
	int _day = 5;
};

这样在调用系统自动生成的构造函数时,会按照所给的缺省值给内置类型的变量进行赋值

举个例子:

class a
{
public:
	a(int a = 0)
	{
		cout << "a(int a=0)构造函数" << endl;
		_a = a;
	} 
	void print()
	{
		cout << _a << endl;
	}
private:
	int _a;

};

class Date
{
public:
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		_aa.print();
	}
private:
	int _year;
	int _month;
	int _day;

	a _aa;
};
int main()
{
	Date d2;
	d2.print();//output:a(int a=0)构造函数
	return 0;
}

_year,_month,_day是内置类型的变量,编译器不处理;
_aa是自定义类型的变量,编译器会找到_aa所在类的默认构造函数,并调用

2.绝大多数情况下,需要我们自己写构造函数。我们可以根据需要,利用构造函数可重载的特性,结合缺省参数,编写多个构造函数
例如:

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date()
	{
		_year = 2021;
		_month = 8;
		_day = 5;
	}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
};
int main()
{
	Date d1;//调用第一个构造函数
	Date d2(2020, 9, 25);//调用第二个构造函数
	return  0;
}

自己写构造函数时,最好写一个全缺省的,这样适应性最广

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year = 2021, int month = 8, int day = 5)
	{
		_year = year;
		_month = month;
		_day = day;
	}
};
int main()
{
	Date d1;
	Date d2(2020, 9, 25);
	return 0;
}

构造函数还有一个很重要的概念:默认构造函数
很多人将其等同于编译器自动生成的构造函数,这是严重的误区!
实际上,默认构造函数有以下三类:

  1. 自己写的无参构造函数
  2. 自己写的全缺省构造函数
  3. 编译器自动生成的构造函数

这三者都被称为默认构造函数,
即:不用传实际参数的为默认构造函数。
默认构造函数可以跟其他需要传参的构造函数一起构成重载,
在运用时,三者只能选一个来使用。

二、析构函数

·概念
析构函数是特殊的成员函数,不是完成对象的销毁。对象本身的销毁工作由编译器完成。对象销毁时会自动调用析构函数,完成类的一些资源清理的工作。

·特征

  1. 析构函数名是固定的,为 ~类名
  2. 无参数无返回值
  3. 一个类只有一个析构函数,若未显式定义,系统会自动生成默认的析构函数
  4. 对象生命周期结束时,编译系统自动调用析构函数

同构造函数一样,我们也分几种情况讲解:
(1)对于系统自动生成的构造函数,系统对内置类型/自定义类型区别对待

对于内置类型,编译器生成一个毫无作用的析构函数,不做处理
因为临时对象在存在于栈帧中,函数结束,栈帧销毁,对象随之销毁,不需要析构函数处理

对于自定义类型,会调用它自己的析构函数

例如:

class stack
{
private:
	int* _a;
	int size;
	int capacity;
public:
	~stack()
	{
		cout << "stack析构函数" << endl;
		free(_a);
		_a = nullptr;
		size = 0;
		capacity = 0;
	}
};
class Date
{
private:
	int _year;
	int _month;
	int _day;
    
	stack st;
};
int main()
{
	Date d1;//output:stack析构函数
	return 0;
}

date类本身没有析构函数,对于自定义类型对象st, 调用了stack的析构函数

(2)自己定义的析构函数

对于date类

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	~Date();
};

由于全是内置类型,我们可以不写析构函数。
但对于如stack类有指针类的变量,一般都需要自己写。

有一点要明确:对于_a,析构函数不是回收_a本身,而是清理_a所指向空间上的内容。
可以理解成_a有两块内容要清理,一块是本身,一块是它所指向的空间,析构函数清理的是后者。

class stack
{
private:
	int* _a;
	int _size;
	int _capacity;
public:
	~stack()
	{
		//需要在析构函数中 手动回收空间以及置为nullptr
		free(_a);
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}
};

·关于调用顺序
一个类对应的构造的多个对象,遵循一个顺序:先构造的后析构,后构造的先析构
在这里插入图片描述

三、拷贝构造函数
·概念:
是完成对象的拷贝的构造函数,是一种特殊的构造函数。

还是以日期类为例:

class Date
{
public:
	//拷贝构造函数
	Date(Date& d)//参数最好加上const修饰,即Date(Date const& d);
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 5, 25);
	Date d2(d1);//调用拷贝构造函数,用d1来初始化d2
    return 0;
}
Date(Date& d)

这里必须用引用!若传值,将会引发无穷调用拷贝构造(传值时,实参与形参时两块空间。为形参开辟空间的过程又是一次拷贝构造)

拷贝构造函数如果不自己实现,编译器会自动生成拷贝构造函数:

(1)默认拷贝构造函数对内置类型:完成浅拷贝(值拷贝)

上方代码中d2本身是随机值,Date d2(d1)会按照字节序将d1中的内容拷贝到 d2中
注意:默认的构造和析构函数对内置类型是不管的,但默认拷贝构造函数对内置也会进行赋值。

(2)默认拷贝构造函数对自定义类型:调用该类中它自己的拷贝构造函数

但有时候,调用系统自动生成的拷贝构造函数会有问题
比如下面的Stack类:

class Stack
{
private:
	int* a;
	int size;
	int capacity;
};
int main()
{
	Stack p1;
	Stack p2(p1);
	return 0;
}

stack类中有指针,意味着p1和p2的a对象指向同一块空间,那么:
1.其中一个对象插入或删除数据,都会导致另外一个对象被执行相同的操作
2.调用析构函数时,会导致同一块空间被free(delete)两次

因此我们得自己写拷贝构造函数:

	Stack(Stack& st)
	{
		a = (int*)malloc(st.size*sizeof(int));
		memcpy(a, st.a, st.size * sizeof(int));
	}

小结一下:

Date这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了

但是像Stack这样含有指针的类,得自己写拷贝构造函数实现深拷贝

四、重载赋值运算符=
·概念
已经定义出来的对象之间的复制拷贝(拷贝构造函数是定义时实现复制拷贝)

重载赋值运算符的特性跟拷贝构造函数是一致的
编译器自动生成的重载赋值运算符,对内置类型、自定义类型区别处理:

(1)重载赋值运算符对内置类型:完成浅拷贝(值拷贝)
(2)重载赋值运算符对自定义类型:调用该类中它自己的重载赋值运算符函数

因此同样地,对于Stack类,我们要自己写重载赋值运算符函数:

class Stack
{
private:
	int* a;
	int size;
	int capacity;
public:
    //构造函数
	Stack(int* a = NULL, int size = 4, int capacity = 0)
	{
		memcpy(this->a, a, sizeof(a));
		this->size = size;
		this->capacity = capacity;
	}

    //重载赋值运算符
	Stack operator=(Stack st)
	{
		this->a = (int*)malloc(sizeof(int) * st.size);
		memcpy(this->a, st.a,sizeof(int)*st.size);
		this->size = st.size;
		this->capacity = st.capacity;
		return *this;
	}

};
int main()
{
	int a[] = { 1,2,3,4,5 };
	Stack p1(a,4,0);
	Stack p2;
	p2 = p1;
	return 0;
}

到现在为止,我已向大家讲了C++中有关类的基本语法知识点,那在下一篇C++初阶(4)中,我想进行一次实践,用前三篇的知识实现Date日期类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值